authorgravatar for opensource@geemili.xyzgeemili <opensource@geemili.xyz> 2025-02-10 18:47:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-22 17:33:49-05:00
log6ef2384c07ef6817f134ffc61e34bcea6e87df92
tree44b9690d032aea3d935762bef4b8cc064797f873
parentb7512c3e5d1734c301b8e5c1cda0385b0a967e0c

std.DynLib: add support for DT_GNU_HASH sections in elf files


2 files changed, 182 insertions(+), 13 deletions(-)

lib/std/dynamic_library.zig+145-13
...@@ -140,13 +140,18 @@ const ElfDynLibError = error{...@@ -140,13 +140,18 @@ const ElfDynLibError = error{
140pub const ElfDynLib = struct {140pub const ElfDynLib = struct {
141 strings: [*:0]u8,141 strings: [*:0]u8,
142 syms: [*]elf.Sym,142 syms: [*]elf.Sym,
143 hashtab: [*]posix.Elf_Symndx,143 hash_table: HashTable,
144 versym: ?[*]elf.Versym,144 versym: ?[*]elf.Versym,
145 verdef: ?*elf.Verdef,145 verdef: ?*elf.Verdef,
146 memory: []align(std.heap.page_size_min) u8,146 memory: []align(std.heap.page_size_min) u8,
147147
148 pub const Error = ElfDynLibError;148 pub const Error = ElfDynLibError;
149149
150 const HashTable = union(enum) {
151 dt_hash: [*]posix.Elf_Symndx,
152 dt_gnu_hash: *elf.gnu_hash.Header,
153 };
154
150 fn openPath(path: []const u8) !std.fs.Dir {155 fn openPath(path: []const u8) !std.fs.Dir {
151 if (path.len == 0) return error.NotDir;156 if (path.len == 0) return error.NotDir;
152 var parts = std.mem.tokenizeScalar(u8, path, '/');157 var parts = std.mem.tokenizeScalar(u8, path, '/');
...@@ -321,6 +326,7 @@ pub const ElfDynLib = struct {...@@ -321,6 +326,7 @@ pub const ElfDynLib = struct {
321 var maybe_strings: ?[*:0]u8 = null;326 var maybe_strings: ?[*:0]u8 = null;
322 var maybe_syms: ?[*]elf.Sym = null;327 var maybe_syms: ?[*]elf.Sym = null;
323 var maybe_hashtab: ?[*]posix.Elf_Symndx = null;328 var maybe_hashtab: ?[*]posix.Elf_Symndx = null;
329 var maybe_gnu_hash: ?*elf.gnu_hash.Header = null;
324 var maybe_versym: ?[*]elf.Versym = null;330 var maybe_versym: ?[*]elf.Versym = null;
325 var maybe_verdef: ?*elf.Verdef = null;331 var maybe_verdef: ?*elf.Verdef = null;
326332
...@@ -332,6 +338,7 @@ pub const ElfDynLib = struct {...@@ -332,6 +338,7 @@ pub const ElfDynLib = struct {
332 elf.DT_STRTAB => maybe_strings = @ptrFromInt(p),338 elf.DT_STRTAB => maybe_strings = @ptrFromInt(p),
333 elf.DT_SYMTAB => maybe_syms = @ptrFromInt(p),339 elf.DT_SYMTAB => maybe_syms = @ptrFromInt(p),
334 elf.DT_HASH => maybe_hashtab = @ptrFromInt(p),340 elf.DT_HASH => maybe_hashtab = @ptrFromInt(p),
341 elf.DT_GNU_HASH => maybe_gnu_hash = @ptrFromInt(p),
335 elf.DT_VERSYM => maybe_versym = @ptrFromInt(p),342 elf.DT_VERSYM => maybe_versym = @ptrFromInt(p),
336 elf.DT_VERDEF => maybe_verdef = @ptrFromInt(p),343 elf.DT_VERDEF => maybe_verdef = @ptrFromInt(p),
337 else => {},344 else => {},
...@@ -339,11 +346,18 @@ pub const ElfDynLib = struct {...@@ -339,11 +346,18 @@ pub const ElfDynLib = struct {
339 }346 }
340 }347 }
341348
349 const hash_table: HashTable = if (maybe_gnu_hash) |gnu_hash|
350 .{ .dt_gnu_hash = gnu_hash }
351 else if (maybe_hashtab) |hashtab|
352 .{ .dt_hash = hashtab }
353 else
354 return error.ElfHashTableNotFound;
355
342 return .{356 return .{
343 .memory = all_loaded_mem,357 .memory = all_loaded_mem,
344 .strings = maybe_strings orelse return error.ElfStringSectionNotFound,358 .strings = maybe_strings orelse return error.ElfStringSectionNotFound,
345 .syms = maybe_syms orelse return error.ElfSymSectionNotFound,359 .syms = maybe_syms orelse return error.ElfSymSectionNotFound,
346 .hashtab = maybe_hashtab orelse return error.ElfHashTableNotFound,360 .hash_table = hash_table,
347 .versym = maybe_versym,361 .versym = maybe_versym,
348 .verdef = maybe_verdef,362 .verdef = maybe_verdef,
349 };363 };
...@@ -368,6 +382,60 @@ pub const ElfDynLib = struct {...@@ -368,6 +382,60 @@ pub const ElfDynLib = struct {
368 }382 }
369 }383 }
370384
385 pub const GnuHashSection32 = struct {
386 symoffset: u32,
387 bloom_shift: u32,
388 bloom: []u32,
389 buckets: []u32,
390 chain: [*]elf.gnu_hash.ChainEntry,
391
392 pub fn fromPtr(header: *elf.gnu_hash.Header) @This() {
393 const header_offset = @intFromPtr(header);
394 const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header);
395 const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u32);
396 const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32);
397
398 const bloom_ptr: [*]u32 = @ptrFromInt(bloom_offset);
399 const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset);
400 const chain_ptr: [*]u32 = @ptrFromInt(chain_offset);
401
402 return .{
403 .symoffset = header.symoffset,
404 .bloom_shift = header.bloom_shift,
405 .bloom = bloom_ptr[0..header.bloom_size],
406 .buckets = buckets_ptr[0..header.nbuckets],
407 .chain = chain_ptr,
408 };
409 }
410 };
411
412 pub const GnuHashSection64 = struct {
413 symoffset: u32,
414 bloom_shift: u32,
415 bloom: []u64,
416 buckets: []u32,
417 chain: [*]elf.gnu_hash.ChainEntry,
418
419 pub fn fromPtr(header: *elf.gnu_hash.Header) @This() {
420 const header_offset = @intFromPtr(header);
421 const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header);
422 const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u64);
423 const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32);
424
425 const bloom_ptr: [*]u64 = @ptrFromInt(bloom_offset);
426 const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset);
427 const chain_ptr: [*]elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset);
428
429 return .{
430 .symoffset = header.symoffset,
431 .bloom_shift = header.bloom_shift,
432 .bloom = bloom_ptr[0..header.bloom_size],
433 .buckets = buckets_ptr[0..header.nbuckets],
434 .chain = chain_ptr,
435 };
436 }
437 };
438
371 /// ElfDynLib specific439 /// ElfDynLib specific
372 /// Returns the address of the symbol440 /// Returns the address of the symbol
373 pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize {441 pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize {
...@@ -376,17 +444,81 @@ pub const ElfDynLib = struct {...@@ -376,17 +444,81 @@ pub const ElfDynLib = struct {
376 const OK_TYPES = (1 << elf.STT_NOTYPE | 1 << elf.STT_OBJECT | 1 << elf.STT_FUNC | 1 << elf.STT_COMMON);444 const OK_TYPES = (1 << elf.STT_NOTYPE | 1 << elf.STT_OBJECT | 1 << elf.STT_FUNC | 1 << elf.STT_COMMON);
377 const OK_BINDS = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE);445 const OK_BINDS = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE);
378446
379 var i: usize = 0;447 switch (self.hash_table) {
380 while (i < self.hashtab[1]) : (i += 1) {448 .dt_hash => |hashtab| {
381 if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info & 0xf)) & OK_TYPES)) continue;449 var i: usize = 0;
382 if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info >> 4)) & OK_BINDS)) continue;450 while (i < hashtab[1]) : (i += 1) {
383 if (0 == self.syms[i].st_shndx) continue;451 if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info & 0xf)) & OK_TYPES)) continue;
384 if (!mem.eql(u8, name, mem.sliceTo(self.strings + self.syms[i].st_name, 0))) continue;452 if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info >> 4)) & OK_BINDS)) continue;
385 if (maybe_versym) |versym| {453 if (0 == self.syms[i].st_shndx) continue;
386 if (!checkver(self.verdef.?, versym[i], vername, self.strings))454 if (!mem.eql(u8, name, mem.sliceTo(self.strings + self.syms[i].st_name, 0))) continue;
387 continue;455 if (maybe_versym) |versym| {
388 }456 if (!checkver(self.verdef.?, versym[i], vername, self.strings))
389 return @intFromPtr(self.memory.ptr) + self.syms[i].st_value;457 continue;
458 }
459 return @intFromPtr(self.memory.ptr) + self.syms[i].st_value;
460 }
461 },
462 .dt_gnu_hash => |gnu_hash_header| {
463 const GnuHashSection = switch (@bitSizeOf(usize)) {
464 32 => GnuHashSection32,
465 64 => GnuHashSection64,
466 else => |bit_size| @compileError("Unsupported bit size " ++ bit_size),
467 };
468
469 const gnu_hash_section: GnuHashSection = .fromPtr(gnu_hash_header);
470 const hash = elf.gnu_hash.calculate(name);
471
472 const bloom_index = (hash / @bitSizeOf(usize)) % gnu_hash_header.bloom_size;
473 const bloom_val = gnu_hash_section.bloom[bloom_index];
474
475 const bit_index_0 = hash % @bitSizeOf(usize);
476 const bit_index_1 = (hash >> @intCast(gnu_hash_header.bloom_shift)) % @bitSizeOf(usize);
477
478 const one: usize = 1;
479 const bit_mask: usize = (one << @intCast(bit_index_0)) | (one << @intCast(bit_index_1));
480
481 if (bloom_val & bit_mask != bit_mask) {
482 // Symbol is not in bloom filter, so it definitely isn't here.
483 return null;
484 }
485
486 const bucket_index = hash % gnu_hash_header.nbuckets;
487 const chain_index = gnu_hash_section.buckets[bucket_index] - gnu_hash_header.symoffset;
488
489 const chains = gnu_hash_section.chain;
490 const hash_as_entry: elf.gnu_hash.ChainEntry = @bitCast(hash);
491
492 var current_index = chain_index;
493 var at_end_of_chain = false;
494 while (!at_end_of_chain) : (current_index += 1) {
495 const current_entry = chains[current_index];
496 at_end_of_chain = current_entry.end_of_chain;
497
498 if (current_entry.hash != hash_as_entry.hash) continue;
499
500 // check that symbol matches
501 const symbol_index = current_index + gnu_hash_header.symoffset;
502 const symbol = self.syms[symbol_index];
503
504 if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info & 0xf)) & OK_TYPES)) continue;
505 if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info >> 4)) & OK_BINDS)) continue;
506 if (0 == symbol.st_shndx) continue;
507
508 const symbol_name = mem.sliceTo(self.strings + symbol.st_name, 0);
509 if (!mem.eql(u8, name, symbol_name)) {
510 continue;
511 }
512
513 if (maybe_versym) |versym| {
514 if (!checkver(self.verdef.?, versym[symbol_index], vername, self.strings)) {
515 continue;
516 }
517 }
518
519 return @intFromPtr(self.memory.ptr) + symbol.st_value;
520 }
521 },
390 }522 }
391523
392 return null;524 return null;
lib/std/elf.zig+37
...@@ -2395,3 +2395,40 @@ pub const STRNAME = genSpecialMemberName("//");...@@ -2395,3 +2395,40 @@ pub const STRNAME = genSpecialMemberName("//");
2395pub const SYM64NAME = genSpecialMemberName("/SYM64/");2395pub const SYM64NAME = genSpecialMemberName("/SYM64/");
2396pub const SYMDEFNAME = genSpecialMemberName("__.SYMDEF");2396pub const SYMDEFNAME = genSpecialMemberName("__.SYMDEF");
2397pub const SYMDEFSORTEDNAME = genSpecialMemberName("__.SYMDEF SORTED");2397pub const SYMDEFSORTEDNAME = genSpecialMemberName("__.SYMDEF SORTED");
2398
2399pub const gnu_hash = struct {
2400
2401 // See https://flapenguin.me/elf-dt-gnu-hash
2402
2403 pub const Header = extern struct {
2404 nbuckets: u32,
2405 symoffset: u32,
2406 bloom_size: u32,
2407 bloom_shift: u32,
2408 };
2409
2410 pub const ChainEntry = packed struct(u32) {
2411 end_of_chain: bool,
2412 /// Contains the top bits of the hash value.
2413 hash: u31,
2414 };
2415
2416 /// Calculate the hash value for a name
2417 pub fn calculate(name: []const u8) u32 {
2418 var hash: u32 = 5381;
2419
2420 for (name) |char| {
2421 hash = (hash << 5) +% hash +% char;
2422 }
2423
2424 return hash;
2425 }
2426
2427 test calculate {
2428 try std.testing.expectEqual(0x00001505, calculate(""));
2429 try std.testing.expectEqual(0x156b2bb8, calculate("printf"));
2430 try std.testing.expectEqual(0x7c967e3f, calculate("exit"));
2431 try std.testing.expectEqual(0xbac212a0, calculate("syscall"));
2432 try std.testing.expectEqual(0x8ae9f18e, calculate("flapenguin.me"));
2433 }
2434};