]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
LoongArch: Fix bad reloc with mixed visibility ifunc symbols in shared libraries
authorXi Ruoyao <xry111@xry111.site>
Sun, 30 Jun 2024 07:18:22 +0000 (15:18 +0800)
committerliuzhensong <liuzhensong@loongson.cn>
Fri, 5 Jul 2024 04:11:11 +0000 (12:11 +0800)
With a simple test case:

    .globl  ifunc
    .globl  ifunc_hidden
    .hidden ifunc_hidden
    .type   ifunc, %gnu_indirect_function
    .type   ifunc_hidden, %gnu_indirect_function

    .text
    .align  2
    ifunc:  ret
    ifunc_hidden: ret

    test:
      bl ifunc
      bl ifunc_hidden

"ld -shared" produces a shared object with one R_LARCH_NONE (instead of
R_LARCH_JUMP_SLOT as we expect) to relocate the GOT entry of "ifunc".
It's because the indices in .plt and .rela.plt mismatches for
STV_DEFAULT STT_IFUNC symbols when another PLT entry exists for a
STV_HIDDEN STT_IFUNC symbol, and such a mismatch breaks the logic of
loongarch_elf_finish_dynamic_symbol.  Fix the issue by reordering .plt
so the indices no longer mismatch.

Signed-off-by: Xi Ruoyao <xry111@xry111.site>
bfd/elfnn-loongarch.c
ld/testsuite/ld-loongarch-elf/ifunc-reloc.d [new file with mode: 0644]
ld/testsuite/ld-loongarch-elf/ifunc-reloc.s [new file with mode: 0644]
ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp

index 41463ea5c75ba806ff3b3d9a029aee31ced60c13..bcb144357ff34830f17c4846e32c61f250edc315 100644 (file)
@@ -1716,9 +1716,10 @@ local_allocate_ifunc_dyn_relocs (struct bfd_link_info *info,
    ifunc dynamic relocs.  */
 
 static bool
-elfNN_allocate_ifunc_dynrelocs (struct elf_link_hash_entry *h, void *inf)
+elfNN_allocate_ifunc_dynrelocs (struct elf_link_hash_entry *h,
+                               struct bfd_link_info *info,
+                               bool ref_local)
 {
-  struct bfd_link_info *info;
   /* An example of a bfd_link_hash_indirect symbol is versioned
      symbol. For example: __gxx_personality_v0(bfd_link_hash_indirect)
      -> __gxx_personality_v0(bfd_link_hash_defined)
@@ -1734,20 +1735,18 @@ elfNN_allocate_ifunc_dynrelocs (struct elf_link_hash_entry *h, void *inf)
   if (h->root.type == bfd_link_hash_warning)
     h = (struct elf_link_hash_entry *) h->root.u.i.link;
 
-  info = (struct bfd_link_info *) inf;
-
   /* Since STT_GNU_IFUNC symbol must go through PLT, we handle it
      here if it is defined and referenced in a non-shared object.  */
   if (h->type == STT_GNU_IFUNC && h->def_regular)
     {
-      if (SYMBOL_REFERENCES_LOCAL (info, h))
+      if (ref_local && SYMBOL_REFERENCES_LOCAL (info, h))
        return local_allocate_ifunc_dyn_relocs (info, h,
                                                &h->dyn_relocs,
                                                PLT_ENTRY_SIZE,
                                                PLT_HEADER_SIZE,
                                                GOT_ENTRY_SIZE,
                                                false);
-      else
+      else if (!ref_local && !SYMBOL_REFERENCES_LOCAL (info, h))
        return _bfd_elf_allocate_ifunc_dyn_relocs (info, h,
                                                   &h->dyn_relocs,
                                                   PLT_ENTRY_SIZE,
@@ -1759,6 +1758,23 @@ elfNN_allocate_ifunc_dynrelocs (struct elf_link_hash_entry *h, void *inf)
   return true;
 }
 
+static bool
+elfNN_allocate_ifunc_dynrelocs_ref_local (struct elf_link_hash_entry *h,
+                                         void *info)
+{
+  return elfNN_allocate_ifunc_dynrelocs (h, (struct bfd_link_info *) info,
+                                        true);
+}
+
+static bool
+elfNN_allocate_ifunc_dynrelocs_ref_global (struct elf_link_hash_entry *h,
+                                          void *info)
+{
+  return elfNN_allocate_ifunc_dynrelocs (h, (struct bfd_link_info *) info,
+                                        false);
+}
+
+
 /* Allocate space in .plt, .got and associated reloc sections for
    ifunc dynamic relocs.  */
 
@@ -1774,7 +1790,7 @@ elfNN_allocate_local_ifunc_dynrelocs (void **slot, void *inf)
       || h->root.type != bfd_link_hash_defined)
     abort ();
 
-  return elfNN_allocate_ifunc_dynrelocs (h, inf);
+  return elfNN_allocate_ifunc_dynrelocs_ref_local (h, inf);
 }
 
 /* Set DF_TEXTREL if we find any dynamic relocs that apply to
@@ -1934,11 +1950,48 @@ loongarch_elf_late_size_sections (bfd *output_bfd,
      sym dynamic relocs.  */
   elf_link_hash_traverse (&htab->elf, allocate_dynrelocs, info);
 
-  /* Allocate global ifunc sym .plt and .got entries, and space for global
-     ifunc sym dynamic relocs.  */
-  elf_link_hash_traverse (&htab->elf, elfNN_allocate_ifunc_dynrelocs, info);
-
-  /* Allocate .plt and .got entries, and space for local ifunc symbols.  */
+  /* Allocate global ifunc sym .plt and .got entries, and space for
+     *preemptible* ifunc sym dynamic relocs.  Note that we must do it
+     for *all* preemptible ifunc (including local ifuncs and STV_HIDDEN
+     ifuncs) before doing it for any non-preemptible ifunc symbol:
+     assuming we are not so careful, when we link a shared library the
+     correlation of .plt and .rela.plt might look like:
+
+                               idx in .plt     idx in .rela.plt
+       ext_func1@plt           0               0
+       ext_func2@plt           1               1
+       ext_func3@plt           2               2
+       hidden_ifunc1@plt       3               None: it's in .rela.got
+       hidden_ifunc2@plt       4               None: it's in .rela.got
+       normal_ifunc1@plt       5       !=      3
+       normal_ifunc2@plt       6       !=      4
+       local_ifunc@plt         7               None: it's in .rela.got
+
+     Now oops the indices for normal_ifunc{1,2} in .rela.plt were different
+     from the indices in .plt :(.  This would break finish_dynamic_symbol
+     which assumes the index in .rela.plt matches the index in .plt.
+
+     So let's be careful and make it correct:
+
+                               idx in .plt     idx in .rela.plt
+       ext_func1@plt           0               0
+       ext_func2@plt           1               1
+       ext_func3@plt           2               2
+       normal_ifunc1@plt       3               3
+       normal_ifunc2@plt       4               4
+       hidden_ifunc1@plt       5               None: it's in .rela.got
+       hidden_ifunc2@plt       6               None: it's in .rela.got
+       local_ifunc@plt         7               None: it's in .rela.got
+
+     Now normal_ifuncs first.  */
+  elf_link_hash_traverse (&htab->elf,
+                         elfNN_allocate_ifunc_dynrelocs_ref_global, info);
+
+  /* Next hidden_ifuncs follows.  */
+  elf_link_hash_traverse (&htab->elf,
+                         elfNN_allocate_ifunc_dynrelocs_ref_local, info);
+
+  /* Finally local_ifuncs.  */
   htab_traverse (htab->loc_hash_table,
                 elfNN_allocate_local_ifunc_dynrelocs, info);
 
diff --git a/ld/testsuite/ld-loongarch-elf/ifunc-reloc.d b/ld/testsuite/ld-loongarch-elf/ifunc-reloc.d
new file mode 100644 (file)
index 0000000..cb59287
--- /dev/null
@@ -0,0 +1,19 @@
+#ld: -shared
+#readelf: -Wr
+
+#...
+.*'\.rela\.dyn'.*
+#...
+.* R_LARCH_RELATIVE .*
+.* R_LARCH_IRELATIVE .*
+.* R_LARCH_IRELATIVE .*
+.* R_LARCH_IRELATIVE .*
+#...
+.*'\.rela\.plt'.*
+#...
+.* R_LARCH_JUMP_SLOT .*
+.* R_LARCH_JUMP_SLOT .*
+.* R_LARCH_JUMP_SLOT .*
+.* R_LARCH_JUMP_SLOT .*
+.* R_LARCH_JUMP_SLOT .*
+.* R_LARCH_JUMP_SLOT .*
diff --git a/ld/testsuite/ld-loongarch-elf/ifunc-reloc.s b/ld/testsuite/ld-loongarch-elf/ifunc-reloc.s
new file mode 100644 (file)
index 0000000..e59f2b2
--- /dev/null
@@ -0,0 +1,55 @@
+.globl foo
+.globl foo_hidden1
+.globl foo_hidden2
+.globl foo_protected
+
+.type  foo, %gnu_indirect_function
+.type  foo_hidden1, %gnu_indirect_function
+.type  foo_hidden2, %gnu_indirect_function
+.type  foo_protected, %gnu_indirect_function
+.type  foo_internal, %gnu_indirect_function
+
+.hidden        foo_hidden1
+.hidden        foo_hidden2
+
+.protected     foo_protected
+
+.globl ext_ifunc1
+.globl ext_ifunc2
+.type  ext_ifunc1, %gnu_indirect_function
+.type  ext_ifunc2, %gnu_indirect_function
+
+.text
+.align 2
+foo:
+       ret
+
+foo_hidden1:
+       ret
+
+foo_hidden2:
+       ret
+
+foo_protected:
+       ret
+
+foo_internal:
+       ret
+
+test:
+       la.got  $a0, num
+       # The order is deliberately shuffled.
+       bl      ext_ifunc1
+       bl      foo
+       bl      foo_hidden1
+       bl      ext_func1
+       bl      foo_protected
+       bl      foo_internal
+       bl      foo_hidden2
+       bl      ext_func2
+       bl      ext_ifunc2
+
+.data
+.align 3
+num:
+       .quad   114514
index 4d9f4aebaffc410cf0da163dc622b0668a3f6d6f..6cff117e29b2f1033a0ed8f3dafcc3df63ae15b8 100644 (file)
@@ -133,6 +133,7 @@ if [istarget "loongarch64-*-*"] {
     run_dump_test "reloc_ler_with_shared"
     run_dump_test "reloc_abs_with_shared"
     run_dump_test "r_larch_32_elf64"
+    run_dump_test "ifunc-reloc"
   }
 
   if [check_pie_support] {