[ELF][PPC64] Implement IPLT code sequence for non-preemptible IFUNC
authorFangrui Song <maskray@google.com>
Sat, 14 Dec 2019 02:30:21 +0000 (18:30 -0800)
committerFangrui Song <maskray@google.com>
Mon, 30 Dec 2019 06:40:03 +0000 (22:40 -0800)
commit45acc35ac21323bafaf5d4367df10ebc4eed35f4
tree546cadf36d30514266f18eb19d7451e0c5146244
parent6f9b4c6826d0d7ee263e1eb44768297e0ce639c7
[ELF][PPC64] Implement IPLT code sequence for non-preemptible IFUNC

Non-preemptible IFUNC are placed in in.iplt (.glink on EM_PPC64).  If
there is a non-GOT non-PLT relocation, for pointer equality, we change
the type of the symbol from STT_IFUNC and STT_FUNC and bind it to the
.glink entry.

On EM_386, EM_X86_64, EM_ARM, and EM_AARCH64, the PLT code sequence
loads the address from its associated .got.plt slot. An IPLT also has an
associated .got.plt slot and can use the same code sequence.

On EM_PPC64, the PLT code sequence is actually a bl instruction in
.glink .  It jumps to `__glink_PLTresolve` (the PLT header). and
`__glink_PLTresolve` computes the .plt slot (relocated by
R_PPC64_JUMP_SLOT).

An IPLT does not have an associated R_PPC64_JUMP_SLOT, so we cannot use
`bl` in .iplt . Instead, create a call stub which has a similar code
sequence as PPC64PltCallStub. We don't save the TOC pointer, so such
scenarios will not work: a function pointer to a non-preemptible ifunc,
which resolves to a function defined in another DSO. This is the
restriction described by https://sourceware.org/glibc/wiki/GNU_IFUNC
(though on many architectures it works in practice):

  Requirement (a): Resolver must be defined in the same translation unit as the implementations.

If an ifunc is taken address but not called, technically we don't need
an entry for it, but we currently do that.

This patch makes

  // clang -fuse-ld=lld -fno-pie -no-pie a.c
  // clang -fuse-ld=lld -fPIE -pie a.c
  #include <stdio.h>
  static void impl(void) { puts("meow"); }
  void thefunc(void) __attribute__((ifunc("resolver")));
  void *resolver(void) { return &impl; }
  int main(void) {
    thefunc();
    void (*theptr)(void) = &thefunc;
    theptr();
  }

work on Linux glibc and FreeBSD. Calling a function pointer pointing to
a Non-preemptible IFUNC never worked before.

Differential Revision: https://reviews.llvm.org/D71509
lld/ELF/Arch/PPC64.cpp
lld/ELF/Thunks.cpp
lld/ELF/Thunks.h
lld/test/ELF/ppc64-ifunc.s
lld/test/ELF/ppc64-toc-relax-ifunc.s