Fix incorrect use of canonical PLT in position-dependent executables
(PDE) that violated pointer equality. The linker must distinguish
between non-PIC code linked as PDE (which requires canonical PLT for
pointer equality) and PIC code linked as PDE (which must not use
canonical PLT). This is determined by examining relocations, not just
the executable type (PIE vs. PDE).
Canonical PLT entries are needed only when non-PIC code takes function
addresses. Non-PIC code uses absolute addresses and assumes all
addresses are known at link time. When such code both calls and takes
the address of a shared library function, the linker creates a canonical
PLT entry (setting the symbol's value to the PLT stub address) to ensure
all references use the same address, maintaining pointer equality.
However, PIC code uses GOT-indirect addressing for function pointers.
When PIC code takes a function's address, it loads from the GOT, which
the dynamic linker resolves to the actual function address in the shared
library. Using canonical PLT in this case is wrong, as it forces all
GOT entries to point to the PLT stub, breaking pointer equality when the
shared library compares function addresses internally.
Require pointer equality in PDE for symbols with non-PLT PC-relative
relocations, that are likely in address taken context, and direct
relocations, that are likely in function reference context. Do so
for IFUNC symbols defined in a non-shared object. Clear value of PLT
undefined symbols if pointer equality is not needed and do not hash them
in '.gnu.hash' section.
As workaround for GCC 12-14 treat PC32DBL relocation for address taking
instruction "larl rX,<sym>@PLT" as if it was without @PLT suffix and
require pointer equality. This ensures correct behavior even when the
compiler incorrectly marks address-taking instructions with @PLT.
GCC 12-14, since GCC commit
0990d93dd8a4 ("IBM Z: Use @PLT symbols for
local functions in 64-bit mode") [1], unconditionally suffix non-local
symbols with @PLT, regardless of whether they are used in function call
instructions (i.e. brasl) or address taking instructions (i.e. larl).
The assembler therefore generates a PLT32DBL instead of a PC32DBL
relocation for larl. The linker therefore cannot distinguish between
function call and address taking instructions solely from the relocation
type. The latter requiring pointer equality.
This complements GCC commit
a2e0a30c52fa ("IBM zSystems: Do not use
@PLT with larl") [2], which makes GCC stop suffixing @PLT to address
taking larl instructions, so that the correct behavior with regards to
pointer equality is also achieved with affected GCC 12-14.
Note that this workaround can be reverted once GCC 12-14 emitting
address taking larl instructions with @PLT suffix have become
irrelevant.
Note that without the workaround for GCC 12-14 suffixing @PLT to larl
the following linker tests would fail:
FAIL: shared
FAIL: visibility (hidden_normal)
FAIL: visibility (hidden_weak)
FAIL: visibility (protected)
FAIL: visibility (protected_undef_def)
FAIL: visibility (protected_weak)
FAIL: visibility (normal)
Based on x86-64, especially Jakub Jelinek's x86 commits
47a9f7b34f7a
(clearing value of PLT undefined symbols if pointer equality not needed)
and
fdc90cb46b0f (omitting PLT undefined symbols from '.gnu.hash').
Note that on x86-64 PC32 (and PC64) relocations are excluded as
indication for address taken context requiring function pointer
equality. This is because x86-64 used a PC32 relocation in function
calls from non-PIC code, which has been resolved with commit
bd7ab16b4537 ("x86-64: Generate branch with PLT32 relocation").
[1] GCC commit
0990d93dd8a4 ("IBM Z: Use @PLT symbols for local
functions in 64-bit mode"),
https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=
0990d93dd8a4
[2] GCC commit
a2e0a30c52fa ("IBM zSystems: Do not use @PLT with larl"),
https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=
a2e0a30c52fa
bfd/
PR ld/29655
* elf64-s390.c (elf_s390_check_relocs): Require pointer equality
for direct and non-PLT PC-relative relocations indicating
address taking instructions and for PLT32DBL relocations, when
used with address taking larl instruction.
(elf_s390_finish_dynamic_symbol): Do not use canonical PLT for
non-local undefined symbols if pointer equality is not needed.
Abort if pointer equality needed flag not set although required.
(elf_s390_copy_indirect_symbol): Copy pointer equality needed
flag.
(elf_s390_hash_symbol): New function. Based on x86-64.
(elf_backend_hash_symbol): Wire up elf_s390_hash_symbol.
ld/testsuite/
PR ld/29655
* ld-elf/shared.exp: Add new pr29655 test.
* ld-elf/pr29655a.c: New file. Based on Rui's sample in PR.
* ld-elf/pr29655b.c: Likewise.
* ld-elf/pr29655.rd: Expect zero fun_public symbol value.
* ld-s390/plt_64-1.wf: Adjust expected test output to change in
.gnu.hash due to omitted PLT undefined symbols that do not need
pointer equality.
* ld-s390/plt_64-1_eh.wf: Likewise.
Bug: https://sourceware.org/PR29655
Co-authored-by: Andreas Krebbel <krebbel@linux.ibm.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
dir->ref_regular |= ind->ref_regular;
dir->ref_regular_nonweak |= ind->ref_regular_nonweak;
dir->needs_plt |= ind->needs_plt;
+ dir->pointer_equality_needed |= ind->pointer_equality_needed;
}
else
_bfd_elf_link_hash_copy_indirect (info, dir, ind);
referenced. */
h->ref_regular = 1;
h->needs_plt = 1;
+ h->pointer_equality_needed = 1;
}
}
{
h->needs_plt = 1;
h->plt.refcount += 1;
+
+ /* GCC 12-14 unconditionally suffix non-local symbols
+ with @PLT, regardless of whether they are used in
+ function call instructions (i.e. brasl) or address
+ taking instructions (i.e. larl). Treat PLT32DBL
+ relocation for "larl rX,<sym>@PLT" instruction as
+ address taking and require pointer equality. */
+ if (bfd_link_executable (info)
+ && r_type == R_390_PLT32DBL
+ && rel->r_offset >= 2)
+ {
+ bfd_byte *contents;
+ void *insn_start;
+ uint16_t op;
+
+ if (elf_section_data (sec)->this_hdr.contents != NULL)
+ contents = elf_section_data (sec)->this_hdr.contents;
+ else if (!_bfd_elf_mmap_section_contents (abfd, sec, &contents))
+ return false;
+
+ insn_start = contents + rel->r_offset - 2;
+ op = bfd_get_16 (abfd, insn_start) & 0xff0f;
+
+ if (op == 0xc000)
+ {
+ /* larl rX,<sym>@PLT */
+ h->pointer_equality_needed = 1;
+ }
+
+ if (elf_section_data (sec)->this_hdr.contents != contents)
+ _bfd_elf_munmap_section_contents (sec, contents);
+ }
}
break;
refers to is in a shared lib. */
h->plt.refcount += 1;
}
+
+ /* Require pointer equality in PDE for above PC-relative
+ relocations, that are likely in address taken context,
+ and direct relocations, that are likely in function
+ reference context. */
+ h->pointer_equality_needed = 1;
}
/* If we are creating a shared library, and this is a reloc
if (!h->def_regular)
{
/* Mark the symbol as undefined, rather than as defined in
- the .plt section. Leave the value alone. This is a clue
+ the .plt section. Leave the value if there were any
+ relocations where pointer equality matters (this is a clue
for the dynamic linker, to make function pointer
comparisons work between an application and shared
- library. */
+ library), otherwise set it to zero. If a function is only
+ called from a binary, there is no need to slow down
+ shared libraries because of that. */
sym->st_shndx = SHN_UNDEF;
+ if (!h->pointer_equality_needed)
+ sym->st_value = 0;
}
}
}
}
else
{
+ if (!h->pointer_equality_needed)
+ abort ();
+
/* For non-shared objects explicit GOT slots must be
filled with the PLT slot address for pointer
equality reasons. */
return true;
}
+/* Return TRUE if symbol should be hashed in the `.gnu.hash' section. */
+
+static bool
+elf_s390_hash_symbol (struct elf_link_hash_entry *h)
+{
+ if (h->plt.offset != (bfd_vma) -1
+ && !h->def_regular
+ && !h->pointer_equality_needed)
+ return false;
+
+ return _bfd_elf_hash_symbol (h);
+}
+
/* Why was the hash table entry size definition changed from
ARCH_SIZE/8 to 4? This breaks the 64 bit dynamic linker and
this is the only reason for the s390_elf64_size_info structure. */
#define elf_backend_sort_relocs_p elf_s390_elf_sort_relocs_p
#define elf_backend_additional_program_headers elf_s390_additional_program_headers
#define elf_backend_modify_segment_map elf_s390_modify_segment_map
+#define elf_backend_hash_symbol elf_s390_hash_symbol
#define bfd_elf64_mkobject elf_s390_mkobject
#define elf_backend_object_p elf_s390_object_p
--- /dev/null
+Symbol table '\.dynsym' contains [0-9]+ entries:
+ +Num: +Value +Size Type +Bind +Vis +Ndx Name
+#...
+ +[0-9]+: +0+ +0 +FUNC +GLOBAL +DEFAULT +UND +fun_public
+#...
--- /dev/null
+#include <stdio.h>
+
+typedef void Fn();
+
+void __attribute__((visibility("hidden")))
+fun (void)
+{}
+
+extern void fun_public() __attribute__((alias("fun")));
+
+void
+call_callback (Fn *callback)
+{
+ if (callback == fun)
+ printf("PASS\n");
+ else
+ printf("FAIL\n");
+
+ callback ();
+}
--- /dev/null
+#ifndef __PIC__
+#error "this file must be compiled with -fPIC"
+#endif
+
+typedef void Fn();
+void fun_public(void);
+void call_callback(Fn *callback);
+
+int
+main ()
+{
+ fun_public ();
+ call_callback (fun_public);
+ return 0;
+}
"pr23658-2" \
] \
]
+
+# PR 29655
+run_cc_link_tests [list \
+ [list \
+ "Build pr29655.so" \
+ "-shared" \
+ "-fPIC" \
+ { pr29655a.c } \
+ {} \
+ "pr29655.so" \
+ ] \
+]
+# PR 29655 (cont.): Check that in PIC code linked as PDE taking the address
+# of a function defined in a DSO results in the function address (from GOT)
+# and not the "canonical PLT" address from the PDE.
+run_cc_link_tests [list \
+ [list \
+ "Build pr29655" \
+ "$NOPIE_LDFLAGS -Wl,--no-as-needed,-rpath,tmpdir tmpdir/pr29655.so" \
+ "-fPIC" \
+ { pr29655b.c } \
+ {{readelf {--dyn-syms --wide} pr29655.rd}} \
+ "pr29655" \
+ ] \
+]
DW_CFA_nop
DW_CFA_nop
-00000018 000000000000001c 0000001c FDE cie=00000000 pc=00000000010002b8..00000000010002e4
+00000018 000000000000001c 0000001c FDE cie=00000000 pc=00000000010002b0..00000000010002dc
DW_CFA_remember_state
- DW_CFA_advance_loc: 6 to 00000000010002be
+ DW_CFA_advance_loc: 6 to 00000000010002b6
DW_CFA_offset: r14 at cfa-48
DW_CFA_offset: r15 at cfa-40
- DW_CFA_advance_loc: 8 to 00000000010002c6
+ DW_CFA_advance_loc: 8 to 00000000010002be
DW_CFA_def_cfa_offset: 320
- DW_CFA_advance_loc: 24 to 00000000010002de
+ DW_CFA_advance_loc: 24 to 00000000010002d6
DW_CFA_restore_state
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
-00000018 0000000000000014 0000001c FDE cie=00000000 pc=0000000001000258..00000000010002b8
+00000018 0000000000000014 0000001c FDE cie=00000000 pc=0000000001000250..00000000010002b0
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop