authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-08-14 04:13:30+02:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-16 10:37:20+01:00
log8a539f903b72f18ae57c4f1334ff37eabb4b4b1e
treec9aa4db7fa851b1726950c0eec2c48efeada6e1c
parent4f15073e4ffd095832fe3b25fcc89099a3c2e5a2
signaturelock-open Commit is signed but in an unrecognized format.

Elf2: support the 64-bit hash table format

This is used on s390x and alpha. While `Elf2` doesn't yet support either of those targets, we'll probably work on s390x soon, so it's worth getting this support in now.

1 files changed, 161 insertions(+), 111 deletions(-)

src/link/Elf2.zig+161-111
...@@ -1794,66 +1794,74 @@ fn ensureDynsymHashCapacity(elf: *Elf, max_dynsym_count: u32) Error!void {...@@ -1794,66 +1794,74 @@ fn ensureDynsymHashCapacity(elf: *Elf, max_dynsym_count: u32) Error!void {
1794 )),1794 )),
1795 };1795 };
17961796
1797 {1797 switch (elf.targetDynsymHashInfo()) {
1798 const section_slice: []align(4) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));1798 inline else => |info| {
1799 const header: *std.elf.hash.Header = @ptrCast(section_slice[0..@sizeOf(std.elf.hash.Header)]);1799 {
1800 assert(elf.targetLoad(&header.nchain) == cur_dynsym_count);1800 const section_slice: []align(@sizeOf(info.Int())) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));
1801 const nbucket = elf.targetLoad(&header.nbucket);1801 const header: *info.Header() = @ptrCast(section_slice[0..@sizeOf(info.Header())]);
1802 if (nbucket >= min_buckets) {1802 assert(elf.targetLoad(&header.nchain) == cur_dynsym_count);
1803 // We don't need to add any buckets, but we still need to make sure the section is large1803 const nbucket = elf.targetLoad(&header.nbucket);
1804 // enough to fit `max_dynsym_count` chains.1804 if (nbucket >= min_buckets) {
1805 const need_size = @sizeOf(std.elf.hash.Header) + (nbucket + max_dynsym_count) * 4;1805 // We don't need to add any buckets, but we still need to make sure the section is large
1806 try elf.ensureNodeSize(elf.shndx.hash.get(elf).ni, need_size);1806 // enough to fit `max_dynsym_count` chains.
1807 return;1807 const need_size = @sizeOf(info.Header()) + (nbucket + max_dynsym_count) * 4;
1808 }1808 try elf.ensureNodeSize(elf.shndx.hash.get(elf).ni, need_size);
1809 // We need more buckets, so we'll have to rebuild the hash table.1809 return;
1810 }1810 }
18111811 // We need more buckets, so we'll have to rebuild the hash table.
1812 // Rebuilding the hash table is quite expensive, so to avoid doing it too often we use a large1812 }
1813 // growth factor (* 2) for `nbucket`.
1814 const new_nbucket = min_buckets * 2;
18151813
1816 {1814 // Rebuilding the hash table is quite expensive, so to avoid doing it too often we use a large
1817 const need_size = @sizeOf(std.elf.hash.Header) + (new_nbucket + max_dynsym_count) * 4;1815 // growth factor (* 2) for `nbucket`.
1818 try elf.ensureNodeSize(elf.shndx.hash.get(elf).ni, need_size);1816 const new_nbucket = min_buckets * 2;
1819 }
18201817
1821 elf.mf.nodes_lock.lock();1818 {
1822 defer elf.mf.nodes_lock.unlock();1819 const need_size = @sizeOf(info.Header()) + (new_nbucket + max_dynsym_count) * 4;
1820 try elf.ensureNodeSize(elf.shndx.hash.get(elf).ni, need_size);
1821 }
18231822
1824 const section_slice: []align(4) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));1823 elf.mf.nodes_lock.lock();
1825 const header: *std.elf.hash.Header = @ptrCast(section_slice[0..@sizeOf(std.elf.hash.Header)]);1824 defer elf.mf.nodes_lock.unlock();
1826 const trailing: []u32 = @ptrCast(section_slice[@sizeOf(std.elf.hash.Header)..]);
18271825
1828 header.* = .{ .nbucket = new_nbucket, .nchain = cur_dynsym_count };1826 const section_slice: []align(@sizeOf(info.Int())) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));
1829 if (elf.targetEndian() != std.lang.Endian.native) {1827 const header: *info.Header() = @ptrCast(section_slice[0..@sizeOf(info.Header())]);
1830 std.mem.byteSwapAllFields(std.elf.hash.Header, header);1828 const trailing: []info.Int() = @ptrCast(section_slice[@sizeOf(info.Header())..]);
1831 }
1832 const buckets: []u32 = trailing[0..elf.targetLoad(&header.nbucket)];
1833 const chains: []u32 = trailing[elf.targetLoad(&header.nbucket)..][0..elf.targetLoad(&header.nchain)];
18341829
1835 @memset(buckets, 0);1830 header.* = .{ .nbucket = new_nbucket, .nchain = cur_dynsym_count };
1836 chains[0] = 0;1831 if (elf.targetEndian() != std.lang.Endian.native) {
1837 for (1..cur_dynsym_count, chains[1..]) |dynsym_index_usize, *chain| {1832 std.mem.byteSwapAllFields(info.Header(), header);
1838 const dynsym_index: u32 = @intCast(dynsym_index_usize);1833 }
1839 const sym_name: String(.dynstr) = switch (elf.dynsymPtr(dynsym_index)) {1834 const buckets: []info.Int() = trailing[0..elf.targetLoad(&header.nbucket)];
1840 inline else => |sym| @fromBackingInt(elf.targetLoad(&sym.name)),1835 const chains: []info.Int() = trailing[elf.targetLoad(&header.nbucket)..][0..elf.targetLoad(&header.nchain)];
1841 };1836
1842 const b = std.elf.hash.calculate(sym_name.slice(elf)) % buckets.len;1837 @memset(buckets, 0);
1843 // Make this symbol the head of that bucket, and chain to the old head.1838 chains[0] = 0;
1844 chain.* = buckets[b];1839 for (1..cur_dynsym_count, chains[1..]) |dynsym_index_usize, *chain| {
1845 elf.targetStore(&buckets[b], dynsym_index);1840 const dynsym_index: u32 = @intCast(dynsym_index_usize);
1841 const sym_name: String(.dynstr) = switch (elf.dynsymPtr(dynsym_index)) {
1842 inline else => |sym| @fromBackingInt(elf.targetLoad(&sym.name)),
1843 };
1844 const b = std.elf.hash.calculate(sym_name.slice(elf)) % buckets.len;
1845 // Make this symbol the head of that bucket, and chain to the old head.
1846 chain.* = buckets[b];
1847 elf.targetStore(&buckets[b], dynsym_index);
1848 }
1849 },
1846 }1850 }
1847}1851}
18481852
1849fn appendDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {1853fn appendDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {
1850 const section_slice: []align(4) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));1854 switch (elf.targetDynsymHashInfo()) {
1851 const header: *std.elf.hash.Header = @ptrCast(section_slice[0..@sizeOf(std.elf.hash.Header)]);1855 inline else => |info| {
1852 assert(elf.targetLoad(&header.nchain) == dynsym_index);1856 const section_slice: []align(@sizeOf(info.Int())) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));
1853 elf.targetStore(&header.nchain, dynsym_index + 1);1857 const header: *info.Header() = @ptrCast(section_slice[0..@sizeOf(info.Header())]);
18541858 assert(elf.targetLoad(&header.nchain) == dynsym_index);
1855 switch (elf.shdrPtr(elf.shndx.hash)) {1859 elf.targetStore(&header.nchain, dynsym_index + 1);
1856 inline else => |shdr| elf.targetStore(&shdr.size, elf.targetLoad(&shdr.size) + 4),1860
1861 switch (elf.shdrPtr(elf.shndx.hash)) {
1862 inline else => |shdr| elf.targetStore(&shdr.size, elf.targetLoad(&shdr.size) + @sizeOf(info.Int())),
1863 }
1864 },
1857 }1865 }
18581866
1859 elf.populateDynsymHashEntry(dynsym_index);1867 elf.populateDynsymHashEntry(dynsym_index);
...@@ -1864,31 +1872,39 @@ fn populateDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {...@@ -1864,31 +1872,39 @@ fn populateDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {
18641872
1865 assert(dynsym_index != 0);1873 assert(dynsym_index != 0);
18661874
1867 const section_slice: []align(4) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));1875 switch (elf.targetDynsymHashInfo()) {
1868 const header: *std.elf.hash.Header = @ptrCast(section_slice[0..@sizeOf(std.elf.hash.Header)]);1876 inline else => |info| {
1869 const trailing: []u32 = @ptrCast(section_slice[@sizeOf(std.elf.hash.Header)..]);1877 const section_slice: []align(@sizeOf(info.Int())) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));
1878 const header: *info.Header() = @ptrCast(section_slice[0..@sizeOf(info.Header())]);
1879 const trailing: []info.Int() = @ptrCast(section_slice[@sizeOf(info.Header())..]);
18701880
1871 const buckets: []u32 = trailing[0..elf.targetLoad(&header.nbucket)];1881 const buckets: []info.Int() = trailing[0..elf.targetLoad(&header.nbucket)];
1872 const chains: []u32 = trailing[elf.targetLoad(&header.nbucket)..][0..elf.targetLoad(&header.nchain)];1882 const chains: []info.Int() = trailing[elf.targetLoad(&header.nbucket)..][0..elf.targetLoad(&header.nchain)];
18731883
1874 const sym_name: String(.dynstr) = switch (elf.dynsymPtr(dynsym_index)) {1884 const sym_name: String(.dynstr) = switch (elf.dynsymPtr(dynsym_index)) {
1875 inline else => |sym| @fromBackingInt(elf.targetLoad(&sym.name)),1885 inline else => |sym| @fromBackingInt(elf.targetLoad(&sym.name)),
1876 };1886 };
1877 const b = std.elf.hash.calculate(sym_name.slice(elf)) % buckets.len;1887 const b = std.elf.hash.calculate(sym_name.slice(elf)) % buckets.len;
1878 // Make this symbol the head of that bucket, and chain to the old head.1888 // Make this symbol the head of that bucket, and chain to the old head.
1879 chains[dynsym_index] = buckets[b];1889 chains[dynsym_index] = buckets[b];
1880 elf.targetStore(&buckets[b], dynsym_index);1890 elf.targetStore(&buckets[b], dynsym_index);
1891 },
1892 }
1881}1893}
1882fn popDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {1894fn popDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {
1883 elf.clearDynsymHashEntry(dynsym_index);1895 elf.clearDynsymHashEntry(dynsym_index);
18841896
1885 const section_slice: []align(4) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));1897 switch (elf.targetDynsymHashInfo()) {
1886 const header: *std.elf.hash.Header = @ptrCast(section_slice[0..@sizeOf(std.elf.hash.Header)]);1898 inline else => |info| {
1887 assert(elf.targetLoad(&header.nchain) == dynsym_index + 1);1899 const section_slice: []align(@sizeOf(info.Int())) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));
1888 elf.targetStore(&header.nchain, dynsym_index);1900 const header: *info.Header() = @ptrCast(section_slice[0..@sizeOf(info.Header())]);
1901 assert(elf.targetLoad(&header.nchain) == dynsym_index + 1);
1902 elf.targetStore(&header.nchain, dynsym_index);
18891903
1890 switch (elf.shdrPtr(elf.shndx.hash)) {1904 switch (elf.shdrPtr(elf.shndx.hash)) {
1891 inline else => |shdr| elf.targetStore(&shdr.size, elf.targetLoad(&shdr.size) - 4),1905 inline else => |shdr| elf.targetStore(&shdr.size, elf.targetLoad(&shdr.size) - @sizeOf(info.Int())),
1906 }
1907 },
1892 }1908 }
1893}1909}
1894fn clearDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {1910fn clearDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {
...@@ -1897,34 +1913,38 @@ fn clearDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {...@@ -1897,34 +1913,38 @@ fn clearDynsymHashEntry(elf: *Elf, dynsym_index: u32) void {
18971913
1898 assert(dynsym_index != 0);1914 assert(dynsym_index != 0);
18991915
1900 const section_slice: []align(4) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));1916 switch (elf.targetDynsymHashInfo()) {
1901 const header: *std.elf.hash.Header = @ptrCast(section_slice[0..@sizeOf(std.elf.hash.Header)]);1917 inline else => |info| {
1902 const trailing: []u32 = @ptrCast(section_slice[@sizeOf(std.elf.hash.Header)..]);1918 const section_slice: []align(@sizeOf(info.Int())) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));
1919 const header: *info.Header() = @ptrCast(section_slice[0..@sizeOf(info.Header())]);
1920 const trailing: []info.Int() = @ptrCast(section_slice[@sizeOf(info.Header())..]);
19031921
1904 const buckets: []u32 = trailing[0..elf.targetLoad(&header.nbucket)];1922 const buckets: []info.Int() = trailing[0..elf.targetLoad(&header.nbucket)];
1905 const chains: []u32 = trailing[elf.targetLoad(&header.nbucket)..][0..elf.targetLoad(&header.nchain)];1923 const chains: []info.Int() = trailing[elf.targetLoad(&header.nbucket)..][0..elf.targetLoad(&header.nchain)];
19061924
1907 const sym_name: String(.dynstr) = switch (elf.dynsymPtr(dynsym_index)) {1925 const sym_name: String(.dynstr) = switch (elf.dynsymPtr(dynsym_index)) {
1908 inline else => |sym| @fromBackingInt(elf.targetLoad(&sym.name)),1926 inline else => |sym| @fromBackingInt(elf.targetLoad(&sym.name)),
1909 };1927 };
1910 const b = std.elf.hash.calculate(sym_name.slice(elf)) % buckets.len;1928 const b = std.elf.hash.calculate(sym_name.slice(elf)) % buckets.len;
19111929
1912 const next_dynsym_index = elf.targetLoad(&chains[dynsym_index]);1930 const next_dynsym_index = elf.targetLoad(&chains[dynsym_index]);
1913 elf.targetStore(&chains[dynsym_index], 0);1931 elf.targetStore(&chains[dynsym_index], 0);
19141932
1915 // To remove `dynsym_index` from the singly-linked list, we need to iterate the chain to find1933 // To remove `dynsym_index` from the singly-linked list, we need to iterate the chain to find
1916 // and replace it. But since this is, well, a hash table, that's actually fine.1934 // and replace it. But since this is, well, a hash table, that's actually fine.
1917 if (elf.targetLoad(&buckets[b]) == dynsym_index) {1935 if (elf.targetLoad(&buckets[b]) == dynsym_index) {
1918 elf.targetStore(&buckets[b], next_dynsym_index);1936 elf.targetStore(&buckets[b], next_dynsym_index);
1919 } else {1937 } else {
1920 var cur = elf.targetLoad(&buckets[b]);1938 var cur = elf.targetLoad(&buckets[b]);
1921 while (true) {1939 while (true) {
1922 assert(cur != 0); // `dynsym_index` is definitely somewhere in the chain1940 assert(cur != 0); // `dynsym_index` is definitely somewhere in the chain
1923 if (elf.targetLoad(&chains[cur]) == dynsym_index) break;1941 if (elf.targetLoad(&chains[cur]) == dynsym_index) break;
1924 cur = elf.targetLoad(&chains[cur]);1942 cur = elf.targetLoad(&chains[cur]);
1925 }1943 }
1926 // We found `dynsym_index`; replace it with `next_dynsym_index`.1944 // We found `dynsym_index`; replace it with `next_dynsym_index`.
1927 elf.targetStore(&chains[cur], next_dynsym_index);1945 elf.targetStore(&chains[cur], next_dynsym_index);
1946 }
1947 },
1928 }1948 }
1929}1949}
19301950
...@@ -4169,24 +4189,30 @@ fn initHeaders(...@@ -4169,24 +4189,30 @@ fn initHeaders(
4169 .entsize = @intCast(addr_align.toByteUnits() * 2),4189 .entsize = @intCast(addr_align.toByteUnits() * 2),
4170 .node_align = addr_align,4190 .node_align = addr_align,
4171 });4191 });
4172 elf.shndx.hash = try elf.addSection(elf.ni.rodata, .{4192 switch (elf.targetDynsymHashInfo()) {
4173 .name = ".hash",4193 inline else => |info| {
4174 .type = .HASH,4194 elf.shndx.hash = try elf.addSection(elf.ni.rodata, .{
4175 .flags = .{ .ALLOC = true },4195 .name = ".hash",
4176 .link = elf.shndx.dynsym.toSection().?,4196 .type = .HASH,
4177 .addralign = .@"4",4197 .flags = .{ .ALLOC = true },
4178 // initially: nbucket = 8, nchain = 14198 .link = elf.shndx.dynsym.toSection().?,
4179 .size = @sizeOf(std.elf.hash.Header) + (8 + 1) * 4,4199 // It's unclear what value is correct for the alignment. binutils uses 8 everywhere,
4180 });4200 // while lld uses 4 everywhere (but lld lacks support for the alpha/s390x special
4181 {4201 // case). Matching the hash word (= entry) size seems like the actually sane choice,
4182 const hash_slice: []align(4) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));4202 // and is what mold does too.
4183 const header: *std.elf.hash.Header = @ptrCast(hash_slice[0..@sizeOf(std.elf.hash.Header)]);4203 .addralign = .fromByteUnits(@sizeOf(info.Int())),
4184 header.* = .{ .nbucket = 8, .nchain = 1 };4204 // initially: nbucket = 8 + nchain = 1
4185 if (elf.targetEndian() != std.lang.Endian.native) {4205 .size = @sizeOf(info.Header()) + @sizeOf(info.Int()) * (8 + 1),
4186 std.mem.byteSwapAllFields(std.elf.hash.Header, header);4206 });
4187 }4207 const hash_slice: []align(@sizeOf(info.Int())) u8 = @alignCast(elf.shndx.hash.get(elf).ni.slice(&elf.mf));
4188 // The initial bucket and chain values are all 0, but `MappedFile` initialized the4208 const header: *info.Header() = @ptrCast(hash_slice[0..@sizeOf(info.Header())]);
4189 // node with zeroes anyway, so no need to memset.4209 header.* = .{ .nbucket = 8, .nchain = 1 };
4210 if (elf.targetEndian() != std.lang.Endian.native) {
4211 std.mem.byteSwapAllFields(info.Header(), header);
4212 }
4213 // The initial bucket and chain values are all 0, but `MappedFile` initialized
4214 // the node with zeroes anyway, so no need to memset.
4215 },
4190 }4216 }
41914217
4192 switch (machine) {4218 switch (machine) {
...@@ -4795,6 +4821,30 @@ const PltInfo = struct {...@@ -4795,6 +4821,30 @@ const PltInfo = struct {
4795fn targetPltInfo(elf: *const Elf) PltInfo {4821fn targetPltInfo(elf: *const Elf) PltInfo {
4796 return .fromMachine(elf.ehdrMachine());4822 return .fromMachine(elf.ehdrMachine());
4797}4823}
4824const DynsymHashInfo = enum(u32) {
4825 @"4" = 4,
4826 @"8" = 8,
4827
4828 fn Int(comptime self: DynsymHashInfo) type {
4829 return switch (self) {
4830 .@"4" => u32,
4831 .@"8" => u64,
4832 };
4833 }
4834
4835 fn Header(comptime self: DynsymHashInfo) type {
4836 return switch (self) {
4837 .@"4" => std.elf.hash.Header32,
4838 .@"8" => std.elf.hash.Header64,
4839 };
4840 }
4841};
4842fn targetDynsymHashInfo(elf: *const Elf) DynsymHashInfo {
4843 return switch (elf.ehdrMachine()) {
4844 else => .@"4",
4845 // TODO: Alpha and S390x will need to use either `."@4"` or `.@"8"` depending on `elf.identClass()`.
4846 };
4847}
4798fn targetLoad(elf: *const Elf, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {4848fn targetLoad(elf: *const Elf, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
4799 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;4849 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;
4800 const Child = pointer_ty.child;4850 const Child = pointer_ty.child;