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.

Reply via email to