From: H.J. Lu Date: Fri, 28 Nov 2025 00:05:18 +0000 (+0800) Subject: elf: Properly place base symbols in DT_GNU_HASH and DT_HASH X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=94ebb7810ebd966aa857aa55c5bdc34382f4572b;p=thirdparty%2Fbinutils-gdb.git elf: Properly place base symbols in DT_GNU_HASH and DT_HASH The base symbol, a symbol with the empty version string, created by asm (".symver foo_base, foo@"); is used to provide a compatibility symbol in a versioned shared library for binaries linked against the previous unversioned shared library. The dynamic linker will pick the first match to resolve the unversioned symbol reference. If the newest version, VERS_1, asm (".symver foo_v1, foo@@VERS_1"); is picked before the base symbol in DT_GNU_HASH and DT_HASH, foo@@VERS_1, instead of foo@, will be used to resolve the unversioned reference. Properly place base symbols in DT_GNU_HASH and DT_HASH so that they will be picked first. Also check defined function symbol, foo, and undefined function symbol, bar, separately to support different dynamic symbol orders. bfd/ PR ld/33577 PR ld/33673 * elf-bfd.h (elf_link_hash_entry): Add base_symbol. (elf_link_hash_table): Add has_base_symbols. * elflink.c (_bfd_elf_merge_symbol): Set base_symbol and has_base_symbols if the version string is empty. (collect_gnu_hash_codes): Add base_symbol. (elf_gnu_hash_process_symidx): Skip if base symbol doesn't match. (bfd_elf_size_dynsym_hash_dynstr): If there are base symbols, output base symbols first in DT_GNU_HASH. (elf_outext_info): Add base_symbol. (elf_link_output_extsym): Skip if base symbol doesn't match. (_bfd_elf_final_link): If there are base symbols, output base symbols last in DT_HASH. ld/ PR ld/33577 PR ld/33673 * testsuite/ld-elfvers/pr33577-unversioned.rd: Removed. * testsuite/ld-elfvers/pr33577-versioned.rd: Likewise. * testsuite/ld-elfvers/pr33577-unversioned-a.rd: New file. * testsuite/ld-elfvers/pr33577-unversioned-b.rd: Likewise. * testsuite/ld-elfvers/pr33577-versioned-a.rd: Likewise. * testsuite/ld-elfvers/pr33577-versioned-b.rd: Likewise. * ld-elfvers/vers.exp (base_symbol_test): New. Run PR ld/33673 tests. Signed-off-by: H.J. Lu --- diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h index 1a0d476c37d..01bf6236a75 100644 --- a/bfd/elf-bfd.h +++ b/bfd/elf-bfd.h @@ -204,6 +204,8 @@ struct elf_link_hash_entry unsigned int non_elf : 1; /* Symbol version information. */ ENUM_BITFIELD (elf_symbol_version) versioned : 2; + /* Symbol is a base symbol. */ + unsigned int base_symbol : 1; /* Symbol was forced to local scope due to a version script file. */ unsigned int forced_local : 1; /* Symbol was forced to be dynamic due to a version script file. */ @@ -656,6 +658,9 @@ struct elf_link_hash_table /* TRUE when we are handling DT_NEEDED entries. */ bool handling_dt_needed; + /* TRUE if there are base symbols. */ + bool has_base_symbols; + /* The BFD used to hold special sections created by the linker. This will be the first BFD found which requires these sections to be created. */ diff --git a/bfd/elflink.c b/bfd/elflink.c index 529f81159e3..5fe83cc6693 100644 --- a/bfd/elflink.c +++ b/bfd/elflink.c @@ -1182,6 +1182,8 @@ _bfd_elf_merge_symbol (bfd *abfd, bed = get_elf_backend_data (abfd); + htab = elf_hash_table (info); + /* NEW_VERSION is the symbol version of the new symbol. */ if (h->versioned != unversioned) { @@ -1191,6 +1193,12 @@ _bfd_elf_merge_symbol (bfd *abfd, { if (h->versioned == unknown) { + /* The base symbol has an empty version. */ + if (new_version[1] == '\0') + { + htab->has_base_symbols = true; + h->base_symbol = 1; + } if (new_version > name && new_version[-1] != ELF_VER_CHR) h->versioned = versioned_hidden; else @@ -1294,8 +1302,6 @@ _bfd_elf_merge_symbol (bfd *abfd, symbols. */ bfd_elf_link_mark_dynamic_symbol (info, h, sym); - htab = elf_hash_table (info); - /* NEWDYN and OLDDYN indicate whether the new or old symbol, respectively, is from a dynamic object. */ @@ -6525,6 +6531,7 @@ struct collect_gnu_hash_codes long int shift1, shift2; unsigned long int mask; bool error; + bool base_symbol; }; /* This function will be called though elf_link_hash_traverse to store @@ -6595,6 +6602,10 @@ elf_gnu_hash_process_symidx (struct elf_link_hash_entry *h, void *data) if (h->dynindx == -1) return true; + /* Skip if base symbol doesn't match. */ + if (s->base_symbol != !!h->base_symbol) + return true; + /* Ignore also local symbols and undefined symbols. */ if (! (*s->bed->elf_hash_symbol) (h)) { @@ -8148,8 +8159,23 @@ bfd_elf_size_dynsym_hash_dynstr (bfd *output_bfd, struct bfd_link_info *info) cinfo.contents = contents; cinfo.xlat = contents + cinfo.nsyms * 4 - s->contents; - /* Renumber dynamic symbols, if populating .gnu.hash section. - If using .MIPS.xhash, populate the translation table. */ + + if (elf_hash_table (info)->has_base_symbols) + { + /* Output base symbols first in DT_GNU_HASH so that + they will be picked before non-base symbols at + run-time. */ + cinfo.base_symbol = true; + + /* Renumber dynamic symbols, if populating .gnu.hash + section. If using .MIPS.xhash, populate the + translation table. */ + elf_link_hash_traverse (elf_hash_table (info), + elf_gnu_hash_process_symidx, &cinfo); + } + + /* Output non-base symbols last. */ + cinfo.base_symbol = false; elf_link_hash_traverse (elf_hash_table (info), elf_gnu_hash_process_symidx, &cinfo); @@ -9076,6 +9102,7 @@ struct elf_outext_info bool failed; bool localsyms; bool file_sym_done; + bool base_symbol; struct elf_final_link_info *flinfo; }; @@ -10723,6 +10750,10 @@ elf_link_output_extsym (struct bfd_hash_entry *bh, void *data) int ret; unsigned int type; + /* Skip if base symbol doesn't match. */ + if (eoinfo->base_symbol != !!h->base_symbol) + return true; + if (h->root.type == bfd_link_hash_warning) { h = (struct elf_link_hash_entry *) h->root.u.i.link; @@ -13262,6 +13293,8 @@ _bfd_elf_final_link (bfd *abfd, struct bfd_link_info *info) eoinfo.flinfo = &flinfo; eoinfo.localsyms = true; eoinfo.file_sym_done = false; + /* Output non-base symbols first. */ + eoinfo.base_symbol = false; bfd_hash_traverse (&info->hash->table, elf_link_output_extsym, &eoinfo); if (eoinfo.failed) goto error_return; @@ -13382,6 +13415,17 @@ _bfd_elf_final_link (bfd *abfd, struct bfd_link_info *info) if (eoinfo.failed) goto error_return; + if (htab->has_base_symbols) + { + /* Output base symbols last in DT_HASH so that they will be picked + before non-base symbols at run-time. */ + eoinfo.base_symbol = true; + bfd_hash_traverse (&info->hash->table, elf_link_output_extsym, + &eoinfo); + if (eoinfo.failed) + goto error_return; + } + /* If backend needs to output some symbols not present in the hash table, do it now. */ if (bed->elf_backend_output_arch_syms diff --git a/ld/testsuite/ld-elfvers/pr33577-unversioned.rd b/ld/testsuite/ld-elfvers/pr33577-unversioned-a.rd similarity index 72% rename from ld/testsuite/ld-elfvers/pr33577-unversioned.rd rename to ld/testsuite/ld-elfvers/pr33577-unversioned-a.rd index 8ce9ea71115..7739b097eff 100644 --- a/ld/testsuite/ld-elfvers/pr33577-unversioned.rd +++ b/ld/testsuite/ld-elfvers/pr33577-unversioned-a.rd @@ -1,7 +1,5 @@ Symbol table '\.dynsym' contains [0-9]+ entries: +Num: +Value +Size Type +Bind +Vis +Ndx Name -#... - +[0-9]+: +[a-f0-9]+ +0 +NOTYPE +GLOBAL +DEFAULT +UND +_?bar #... +[0-9]+: +[0-9a-f]+ +[0-9a-f]+ +FUNC +GLOBAL +DEFAULT .*[0-9]+ _?foo #pass diff --git a/ld/testsuite/ld-elfvers/pr33577-unversioned-b.rd b/ld/testsuite/ld-elfvers/pr33577-unversioned-b.rd new file mode 100644 index 00000000000..199ef04e8be --- /dev/null +++ b/ld/testsuite/ld-elfvers/pr33577-unversioned-b.rd @@ -0,0 +1,5 @@ +Symbol table '\.dynsym' contains [0-9]+ entries: + +Num: +Value +Size Type +Bind +Vis +Ndx Name +#... + +[0-9]+: +[a-f0-9]+ +0 +(FUNC|NOTYPE) +GLOBAL +DEFAULT +UND +_?bar +#pass diff --git a/ld/testsuite/ld-elfvers/pr33577-versioned.rd b/ld/testsuite/ld-elfvers/pr33577-versioned-a.rd similarity index 72% rename from ld/testsuite/ld-elfvers/pr33577-versioned.rd rename to ld/testsuite/ld-elfvers/pr33577-versioned-a.rd index 1a579bc9357..74e4dd41b95 100644 --- a/ld/testsuite/ld-elfvers/pr33577-versioned.rd +++ b/ld/testsuite/ld-elfvers/pr33577-versioned-a.rd @@ -1,7 +1,5 @@ Symbol table '\.dynsym' contains [0-9]+ entries: +Num: +Value +Size Type +Bind +Vis +Ndx Name -#... - +[0-9]+: +[a-f0-9]+ +0 +NOTYPE +GLOBAL +DEFAULT +UND +_?bar #... +[0-9]+: +[0-9a-f]+ +[0-9a-f]+ +FUNC +GLOBAL +DEFAULT .*[0-9]+ _?foo@ #pass diff --git a/ld/testsuite/ld-elfvers/pr33577-versioned-b.rd b/ld/testsuite/ld-elfvers/pr33577-versioned-b.rd new file mode 100644 index 00000000000..199ef04e8be --- /dev/null +++ b/ld/testsuite/ld-elfvers/pr33577-versioned-b.rd @@ -0,0 +1,5 @@ +Symbol table '\.dynsym' contains [0-9]+ entries: + +Num: +Value +Size Type +Bind +Vis +Ndx Name +#... + +[0-9]+: +[a-f0-9]+ +0 +(FUNC|NOTYPE) +GLOBAL +DEFAULT +UND +_?bar +#pass diff --git a/ld/testsuite/ld-elfvers/vers.exp b/ld/testsuite/ld-elfvers/vers.exp index d744c3600e8..590b0a42399 100644 --- a/ld/testsuite/ld-elfvers/vers.exp +++ b/ld/testsuite/ld-elfvers/vers.exp @@ -1019,22 +1019,24 @@ build_vers_lib_pic "vers31" vers31.c vers31 "" vers31.map vers31.ver vers31.dsym build_vers_lib_pic "vers32a" vers32a.c vers32a "" vers32.map vers32a.ver vers32a.dsym "" build_vers_lib_pic_flags "vers32b" vers32b.c vers32b "vers32a.so" vers32.map vers32b.ver vers32b.dsym "" "--defsym foo=0" -if [check_compiler_available] { +proc base_symbol_test { ldflags } { run_cc_link_tests [list \ [list \ - "Build pr33577-unversioned.so" \ - "-shared -Wl,-soname,libpr33577.so" \ + "Build libpr33577-unversioned.so ($ldflags)" \ + "$ldflags -shared -Wl,-soname,libpr33577.so" \ "-fPIC" \ { pr33577-unversioned.c } \ - {{readelf {--dyn-syms -W} pr33577-unversioned.rd}} \ + {{readelf {--dyn-syms -W} pr33577-unversioned-a.rd} \ + {readelf {--dyn-syms -W} pr33577-unversioned-b.rd}} \ "libpr33577-unversioned.so" \ ] \ [list \ - "Build pr33577-versioned.so" \ - "-shared -Wl,-soname,libpr33577.so,--version-script=pr33577.map" \ + "Build libpr33577-versioned.so ($ldflags)" \ + "$ldflags -shared -Wl,-soname,libpr33577.so,--version-script=pr33577.map" \ "-fPIC" \ { pr33577-versioned.c } \ - {{readelf {--dyn-syms -W} pr33577-versioned.rd}} \ + {{readelf {--dyn-syms -W} pr33577-versioned-a.rd} \ + {readelf {--dyn-syms -W} pr33577-versioned-b.rd}} \ "libpr33577-versioned.so" \ ] \ ] @@ -1045,7 +1047,7 @@ if [check_compiler_available] { run_cc_link_tests [list \ [list \ - "Build pr33577a with tmpdir/libpr33577-unversioned.so" \ + "Build pr33577a with tmpdir/libpr33577-unversioned.so ($ldflags)" \ "" \ "" \ { pr33577a.c } \ @@ -1059,7 +1061,7 @@ if [check_compiler_available] { if [isnative] { run_ld_link_exec_tests [list \ [list \ - "Run pr33577a with tmpdir/libpr33577-unversioned.so" \ + "Run pr33577a with tmpdir/libpr33577-unversioned.so ($ldflags)" \ "-Wl,-R,tmpdir" \ "" \ { pr33577a.c } \ @@ -1079,7 +1081,7 @@ if [check_compiler_available] { run_cc_link_tests [list \ [list \ - "Build pr33577b with tmpdir/libpr33577-versioned.so" \ + "Build pr33577b with tmpdir/libpr33577-versioned.so ($ldflags)" \ "-Wl,-R,tmpdir" \ "" \ { pr33577b.c } \ @@ -1091,7 +1093,7 @@ if [check_compiler_available] { ] if [isnative] { - set test_name "Run pr33577a with tmpdir/libpr33577-versioned.so" + set test_name "Run pr33577a with tmpdir/libpr33577-versioned.so ($ldflags)" set cmd tmpdir/pr33577a send_log "$cmd\n" set got [remote_exec host "$cmd"] @@ -1102,7 +1104,7 @@ if [check_compiler_available] { fail "$test_name" } - set test_name "Run pr33577b with tmpdir/libpr33577-versioned.so" + set test_name "Run pr33577b with tmpdir/libpr33577-versioned.so ($ldflags)" set cmd tmpdir/pr33577b send_log "$cmd\n" set got [remote_exec host "$cmd"] @@ -1114,3 +1116,10 @@ if [check_compiler_available] { } } } + +base_symbol_test "-Wl,--as-needed,--hash-style=sysv" +base_symbol_test "-Wl,--as-needed,--hash-style=gnu" +base_symbol_test "-Wl,--as-needed,--hash-style=both" +base_symbol_test "-Wl,--no-as-needed,--hash-style=sysv" +base_symbol_test "-Wl,--no-as-needed,--hash-style=gnu" +base_symbol_test "-Wl,--no-as-needed,--hash-style=both"