]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
elf: Properly place base symbols in DT_GNU_HASH and DT_HASH
authorH.J. Lu <hjl.tools@gmail.com>
Fri, 28 Nov 2025 00:05:18 +0000 (08:05 +0800)
committerH.J. Lu <hjl.tools@gmail.com>
Sun, 30 Nov 2025 04:32:54 +0000 (12:32 +0800)
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 <hjl.tools@gmail.com>
bfd/elf-bfd.h
bfd/elflink.c
ld/testsuite/ld-elfvers/pr33577-unversioned-a.rd [moved from ld/testsuite/ld-elfvers/pr33577-unversioned.rd with 72% similarity]
ld/testsuite/ld-elfvers/pr33577-unversioned-b.rd [new file with mode: 0644]
ld/testsuite/ld-elfvers/pr33577-versioned-a.rd [moved from ld/testsuite/ld-elfvers/pr33577-versioned.rd with 72% similarity]
ld/testsuite/ld-elfvers/pr33577-versioned-b.rd [new file with mode: 0644]
ld/testsuite/ld-elfvers/vers.exp

index 1a0d476c37dff1b291f3a3fd4afd3bc91fa4df7c..01bf6236a75e7acdeb9a099228fdcbdeb79bbf46 100644 (file)
@@ -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.  */
index 529f81159e337f0072aa4674d80d3c2ad97b5880..5fe83cc6693c21436a12671b981cf8d3bc7f6b68 100644 (file)
@@ -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
similarity index 72%
rename from ld/testsuite/ld-elfvers/pr33577-unversioned.rd
rename to ld/testsuite/ld-elfvers/pr33577-unversioned-a.rd
index 8ce9ea711151ae7e4aee40c85d30f41fb0bd04e4..7739b097eff4f5dd1bfdc15382b4f402213bcef9 100644 (file)
@@ -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 (file)
index 0000000..199ef04
--- /dev/null
@@ -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
similarity index 72%
rename from ld/testsuite/ld-elfvers/pr33577-versioned.rd
rename to ld/testsuite/ld-elfvers/pr33577-versioned-a.rd
index 1a579bc935723a9532b45b8e5fee883b77d8a903..74e4dd41b95e559dc81223a101b9c5780d6b1f2c 100644 (file)
@@ -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 (file)
index 0000000..199ef04
--- /dev/null
@@ -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
index d744c3600e88e8e2d691fed911ad2a6cec2c717b..590b0a4239942c576f32925e53ad16d629edd50c 100644 (file)
@@ -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"