https://sourceware.org/bugzilla/show_bug.cgi?id=34736
Bug ID: 34736
Summary: An empty DT_GNU_HASH table's symoffset value is always
set to 1 instead of the number of non-hashed symbols
Product: binutils
Version: unspecified
Status: UNCONFIRMED
Severity: normal
Priority: P2
Component: ld
Assignee: unassigned at sourceware dot org
Reporter: jsoltan226 at gmail dot com
Target Milestone: ---
The GNU-style ELF dynamic symbol lookup table (`DT_GNU_HASH`), indexes the
names of symbols that participate in dynamic linking, that is, they are being
"exported" by the ELF.
The names of other symbols (local, imported, etc) are not hashed, and are
required by the semantics of `DT_GNU_HASH` to appear in the symbol table before
any exported (hashed) symbols.
The header of the `DT_GNU_HASH` structure marks the index of the first hashed
symbol with the value `symoffset`, in effect giving us the count of non-hashed
symbols.
However, in a special case where there are no hashed (exported) symbols, a
special "empty" table is generated with fixed values - 1 empty bucket, 1 zeroed
word in the bloom filter and `symoffset` always set to `1` (to point past the
always-present zeroed out first symbol).
This is problematic because in binaries that only have imports but no exports
(basically any normal dynamically linked executable), the "empty" GNU hash
table is generated with `symoffset` set to 1 despite there being e.g. 5
imported symbols, which in effect means that we cannot infer the count of
dynamic symbols from DT_GNU_HASH at all.
There are other sources of this value, like DT_HASH's `nchain` and the
SHT_DYNSYM section header, but both of these can be absent - DT_HASH isn't
generated by default anymore and the section headers can sometimes be stripped.
This leaves us with literally no way whatsoever to determine the dynamic symbol
count, maybe the only thing left being parsing the relocation entries and
determining the highest referenced dynsym index, but that only gives us a
"lower bound" of the symtab's size, nothing certain.
Furthermore, both the GNU `gold` and LLVM `lld` linkers correctly emit the
`symoffset` value in an empty hash table, which means that binaries generated
by them don't have the aforementioned problem.
The fix would be to simply write the total count of dynamic symbols as
`symoffset` instead of `1`:
diff --git a/bfd/elflink.c b/bfd/elflink.c
index 86712e7cec1..66d57437369 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -8108,8 +8108,8 @@ bfd_elf_size_dynsym_hash_dynstr (struct bfd_link_info
*info)
s->alloced = 1;
/* 1 empty bucket. */
bfd_put_32 (info->output_bfd, 1, contents);
- /* SYMIDX above the special symbol 0. */
- bfd_put_32 (info->output_bfd, 1, contents + 4);
+ /* SYMIDX equal to the number of unhashed symbols. */
+ bfd_put_32 (info->output_bfd, dynsymcount, contents + 4);
/* Just one word for bitmask. */
bfd_put_32 (info->output_bfd, 1, contents + 8);
/* Only hash fn bloom filter. */
The issue is present on the newest version:
GNU ld (GNU Binutils) 2.47.50.20261010
and applying the above patch fixes it, at least in my tests.
Thank you for your time and consideration.
Best regards,
Jan Sołtan
--
You are receiving this mail because:
You are on the CC list for the bug.