[lld] [WIP/RFC][ELF][PowerPC] Don't assume TOC pointer is valid in IPLT entries (PR #207555)

Jessica Clarke via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 4 21:17:26 PDT 2026


https://github.com/jrtc27 updated https://github.com/llvm/llvm-project/pull/207555

>From 82f5faba92d9a77703bc1c6494fc1e44e524a59a Mon Sep 17 00:00:00 2001
From: Jessica Clarke <jrtc27 at jrtc27.com>
Date: Sun, 5 Jul 2026 05:16:19 +0100
Subject: [PATCH] [ELF][PowerPC] Don't assume TOC pointer is valid in IPLT
 entries

Unlike normal PLT entries, IPLT entries can be called indirectly even
when in PIEs/DSOs, and so there's no guarantee on what's in the TOC
pointer register at that time. Therefore we must emit variants of the
existing code that work without it, whether r12-relative (playing the
same role as MIPS's $25) in the same number of instructions, or first
retrieving PC in an i386-like manner, being careful not to clobber LR.

Normal canonical PLTs still look broken on 64-bit PowerPC as they use
the TOC pointer register too, and similarly on 32-bit PowerPC for PIEs.
We should probably treat these cases the same as PIE on i386 (except
including PDEs for 64-bit PowerPC), where it's an error due to the use
of %ebx in PLT entries.
---
 lld/ELF/Arch/PPC.cpp                          | 12 ++--
 lld/ELF/Arch/PPC64.cpp                        |  6 +-
 lld/ELF/Thunks.cpp                            | 67 +++++++++++++------
 lld/ELF/Thunks.h                              |  8 ++-
 lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s |  8 +--
 lld/test/ELF/ppc64-ifunc.s                    | 16 +++--
 6 files changed, 75 insertions(+), 42 deletions(-)

diff --git a/lld/ELF/Arch/PPC.cpp b/lld/ELF/Arch/PPC.cpp
index fa64271e12380..901757ccbaced 100644
--- a/lld/ELF/Arch/PPC.cpp
+++ b/lld/ELF/Arch/PPC.cpp
@@ -91,7 +91,7 @@ void elf::writePPC32GlinkSection(Ctx &ctx, uint8_t *buf, size_t numEntries) {
   if (!ctx.arg.isPic) {
     for (const Symbol *sym :
          cast<PPC32GlinkSection>(*ctx.in.plt).canonical_plts) {
-      writePPC32PltCallStub(ctx, buf, sym->getGotPltVA(ctx), nullptr, 0);
+      writePPC32PltCallStub(ctx, buf, glink, sym->getGotPltVA(ctx), nullptr, 0);
       buf += 16;
       glink += 16;
     }
@@ -179,7 +179,7 @@ PPC::PPC(Ctx &ctx) : TargetInfo(ctx) {
   gotPltHeaderEntriesNum = 0;
   pltHeaderSize = 0;
   pltEntrySize = 4;
-  ipltEntrySize = 16;
+  ipltEntrySize = ctx.arg.isPic ? 32 : 16;
 
   needsThunks = true;
 
@@ -199,10 +199,10 @@ void PPC::initTargetSpecificSections() {
 }
 
 void PPC::writeIplt(uint8_t *buf, const Symbol &sym,
-                    uint64_t /*pltEntryAddr*/) const {
-  // In -pie or -shared mode, assume r30 points to .got2+0x8000, and use a
-  // .got2.plt_pic32. thunk.
-  writePPC32PltCallStub(ctx, buf, sym.getGotPltVA(ctx), sym.file, 0x8000);
+                    uint64_t pltEntryAddr) const {
+  // In -pie or -shared mode we can't rely on r30 for indirect calls.
+  writePPC32PltCallStub(ctx, buf, pltEntryAddr, sym.getGotPltVA(ctx), sym.file,
+                        std::nullopt);
 }
 
 void PPC::writeGotHeader(uint8_t *buf) const {
diff --git a/lld/ELF/Arch/PPC64.cpp b/lld/ELF/Arch/PPC64.cpp
index 9f49605d89c58..3753920187a3d 100644
--- a/lld/ELF/Arch/PPC64.cpp
+++ b/lld/ELF/Arch/PPC64.cpp
@@ -1061,9 +1061,9 @@ void PPC64::writePlt(uint8_t *buf, const Symbol &sym,
 }
 
 void PPC64::writeIplt(uint8_t *buf, const Symbol &sym,
-                      uint64_t /*pltEntryAddr*/) const {
-  writePPC64LoadAndBranch(ctx, buf,
-                          sym.getGotPltVA(ctx) - getPPC64TocBase(ctx));
+                      uint64_t pltEntryAddr) const {
+  writePPC64LoadAndBranch(ctx, buf, pltEntryAddr, sym.getGotPltVA(ctx),
+                          /*toc=*/false);
 }
 
 static bool isTocOptType(RelType type) {
diff --git a/lld/ELF/Thunks.cpp b/lld/ELF/Thunks.cpp
index 1f161685f178d..eea74903b211d 100644
--- a/lld/ELF/Thunks.cpp
+++ b/lld/ELF/Thunks.cpp
@@ -1316,8 +1316,9 @@ InputSection *MicroMipsR6Thunk::getTargetInputSection() const {
   return dyn_cast<InputSection>(dr.section);
 }
 
-void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t gotPltVA,
-                                const InputFile *file, int64_t addend) {
+void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t p,
+                                uint64_t gotPltVA, const InputFile *file,
+                                std::optional<int64_t> addend) {
   if (!ctx.arg.isPic) {
     write32(ctx, buf + 0, 0x3d600000 | (gotPltVA + 0x8000) >> 16); // lis r11,ha
     write32(ctx, buf + 4, 0x816b0000 | (uint16_t)gotPltVA); // lwz r11,l(r11)
@@ -1326,26 +1327,43 @@ void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t gotPltVA,
     return;
   }
   uint32_t offset;
-  if (addend >= 0x8000) {
+  uint32_t reg;
+  if (!addend) {
+    // We're a (position-independent) IPLT entry, so cannot assume anything
+    // about what value the caller left in r30 as this could be an indirect
+    // call.
+    write32(ctx, buf + 0, 0x7c0802a6);  //    mflr r0
+    write32(ctx, buf + 4, 0x429f0005);  //    bcl 20, 31, 1f
+    write32(ctx, buf + 8, 0x7d6802a6);  // 1: mflr r11
+    write32(ctx, buf + 12, 0x7c0803a6); //    mtlr r0
+    offset = gotPltVA - p - 8;
+    reg = 11;
+    buf += 16;
+    p += 16;
+  } else if (*addend >= 0x8000) {
     // The stub loads an address relative to r30 (.got2+Addend). Addend is
     // almost always 0x8000. The address of .got2 is different in another object
     // file, so a stub cannot be shared.
+    reg = 30;
     offset = gotPltVA -
              (ctx.in.ppc32Got2->getParent()->getVA() +
-              (file->ppc32Got2 ? file->ppc32Got2->outSecOff : 0) + addend);
+              (file->ppc32Got2 ? file->ppc32Got2->outSecOff : 0) + *addend);
   } else {
     // The stub loads an address relative to _GLOBAL_OFFSET_TABLE_ (which is
     // currently the address of .got).
+    reg = 30;
     offset = gotPltVA - ctx.in.got->getVA();
   }
   uint16_t ha = (offset + 0x8000) >> 16, l = (uint16_t)offset;
   if (ha == 0) {
-    write32(ctx, buf + 0, 0x817e0000 | l); // lwz r11,l(r30)
+    write32(ctx, buf + 0,
+            0x81600000 | (reg << 16) | l); // lwz r11,l(r[11|30])
     write32(ctx, buf + 4, 0x7d6903a6);     // mtctr r11
     write32(ctx, buf + 8, 0x4e800420);     // bctr
     write32(ctx, buf + 12, 0x60000000);    // nop
   } else {
-    write32(ctx, buf + 0, 0x3d7e0000 | ha); // addis r11,r30,ha
+    write32(ctx, buf + 0,
+            0x3d600000 | (reg << 16) | ha); // addis r11,r[11|30],ha
     write32(ctx, buf + 4, 0x816b0000 | l);  // lwz r11,l(r11)
     write32(ctx, buf + 8, 0x7d6903a6);      // mtctr r11
     write32(ctx, buf + 12, 0x4e800420);     // bctr
@@ -1353,7 +1371,8 @@ void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t gotPltVA,
 }
 
 void PPC32PltCallStub::writeTo(uint8_t *buf) {
-  writePPC32PltCallStub(ctx, buf, destination.getGotPltVA(ctx), file, addend);
+  writePPC32PltCallStub(ctx, buf, getThunkTargetSym()->getVA(ctx),
+                        destination.getGotPltVA(ctx), file, addend);
 }
 
 void PPC32PltCallStub::addSymbols(ThunkSection &isec) {
@@ -1402,21 +1421,32 @@ void PPC32LongThunk::writeTo(uint8_t *buf) {
   write32(ctx, buf + 4, 0x4e800420); // bctr
 }
 
-void elf::writePPC64LoadAndBranch(Ctx &ctx, uint8_t *buf, int64_t offset) {
+void elf::writePPC64LoadAndBranch(Ctx &ctx, uint8_t *buf, uint64_t p,
+                                  uint64_t addr, bool toc) {
+  uint64_t offset;
+  uint32_t reg;
+  if (toc) {
+    offset = addr - getPPC64TocBase(ctx);
+    reg = 2;
+  } else {
+    offset = addr - p;
+    reg = 12;
+  }
   uint16_t offHa = (offset + 0x8000) >> 16;
   uint16_t offLo = offset & 0xffff;
 
-  write32(ctx, buf + 0, 0x3d820000 | offHa); // addis r12, r2, OffHa
+  write32(ctx, buf + 0,
+          0x3d800000 | (reg << 16) | offHa); // addis r12, r[2|12], OffHa
   write32(ctx, buf + 4, 0xe98c0000 | offLo); // ld    r12, OffLo(r12)
   write32(ctx, buf + 8, 0x7d8903a6);         // mtctr r12
   write32(ctx, buf + 12, 0x4e800420);        // bctr
 }
 
 void PPC64PltCallStub::writeTo(uint8_t *buf) {
-  int64_t offset = destination.getGotPltVA(ctx) - getPPC64TocBase(ctx);
   // Save the TOC pointer to the save-slot reserved in the call frame.
   write32(ctx, buf + 0, 0xf8410018); // std     r2,24(r1)
-  writePPC64LoadAndBranch(ctx, buf + 4, offset);
+  writePPC64LoadAndBranch(ctx, buf + 4, getThunkTargetSym()->getVA(ctx) + 4,
+                          destination.getGotPltVA(ctx));
 }
 
 void PPC64PltCallStub::addSymbols(ThunkSection &isec) {
@@ -1456,10 +1486,10 @@ void PPC64R2SaveStub::writeTo(uint8_t *buf) {
     write32(ctx, buf + nextInstOffset + 4, BCTR);  // bctr
   } else {
     ctx.in.ppc64LongBranchTarget->addEntry(&destination, addend);
-    const int64_t offsetFromTOC =
-        ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend) -
-        getPPC64TocBase(ctx);
-    writePPC64LoadAndBranch(ctx, buf + 4, offsetFromTOC);
+    const uint64_t addr =
+        ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend);
+    writePPC64LoadAndBranch(ctx, buf + 4, getThunkTargetSym()->getVA(ctx) + 4,
+                            addr);
   }
 }
 
@@ -1519,10 +1549,9 @@ bool PPC64R12SetupStub::isCompatibleWith(const InputSection &isec,
 }
 
 void PPC64LongBranchThunk::writeTo(uint8_t *buf) {
-  int64_t offset =
-      ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend) -
-      getPPC64TocBase(ctx);
-  writePPC64LoadAndBranch(ctx, buf, offset);
+  uint64_t addr =
+      ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend);
+  writePPC64LoadAndBranch(ctx, buf, getThunkTargetSym()->getVA(ctx), addr);
 }
 
 void PPC64LongBranchThunk::addSymbols(ThunkSection &isec) {
diff --git a/lld/ELF/Thunks.h b/lld/ELF/Thunks.h
index 446345b8517f9..ee5befeee843a 100644
--- a/lld/ELF/Thunks.h
+++ b/lld/ELF/Thunks.h
@@ -83,9 +83,11 @@ std::unique_ptr<Thunk> addThunk(Ctx &, const InputSection &isec,
 // are restricted.
 std::unique_ptr<Thunk> addLandingPadThunk(Ctx &, Symbol &s, int64_t a);
 
-void writePPC32PltCallStub(Ctx &, uint8_t *buf, uint64_t gotPltVA,
-                           const InputFile *file, int64_t addend);
-void writePPC64LoadAndBranch(Ctx &, uint8_t *buf, int64_t offset);
+void writePPC32PltCallStub(Ctx &, uint8_t *buf, uint64_t p, uint64_t gotPltVA,
+                           const InputFile *file,
+                           std::optional<int64_t> addend);
+void writePPC64LoadAndBranch(Ctx &, uint8_t *buf, uint64_t p, uint64_t addr,
+                             bool toc = true);
 
 } // namespace lld::elf
 
diff --git a/lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s b/lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s
index c9a0381b610a4..62c46586ed1a3 100644
--- a/lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s
+++ b/lld/test/ELF/ppc32-ifunc-nonpreemptible-pic.s
@@ -10,16 +10,16 @@
 # RUN: llvm-readelf -x .got2 %t | FileCheck --check-prefix=HEX2 %s
 
 # RELOC:      .rela.dyn {
-# RELOC-NEXT:   0x3022C R_PPC_RELATIVE - 0x101A0
-# RELOC-NEXT:   0x30230 R_PPC_IRELATIVE - 0x10188
+# RELOC-NEXT:   0x3023C R_PPC_RELATIVE - 0x101A0
+# RELOC-NEXT:   0x30240 R_PPC_IRELATIVE - 0x10188
 # RELOC-NEXT: }
 
 # SYM: 000101a0 0 FUNC GLOBAL DEFAULT {{.*}} func
 # HEX:      Hex dump of section '.got2':
-# HEX-NEXT: 0x0003022c 00000000 ....
+# HEX-NEXT: 0x0003023c 00000000 ....
 
 # HEX2:      Hex dump of section '.got2':
-# HEX2-NEXT: 0x0003022c 000101a0 ....
+# HEX2-NEXT: 0x0003023c 000101a0 ....
 
 .section .got2,"aw"
 .long func
diff --git a/lld/test/ELF/ppc64-ifunc.s b/lld/test/ELF/ppc64-ifunc.s
index d024c5d5e36c4..2b0cc4c75171d 100644
--- a/lld/test/ELF/ppc64-ifunc.s
+++ b/lld/test/ELF/ppc64-ifunc.s
@@ -55,22 +55,24 @@
 # CHECK-EMPTY:
 
 ## .glink has 3 IPLT entries for ifunc1, ifunc2 and ifunc3.
-## ifunc2 and ifunc3 have the same code sequence as their PLT call stubs.
+## ifunc1 at toc - ifunc1 at iplt = 0x100302a0 - 0x10010268 = (2<<16) + 56
+## ifunc2 at toc - ifunc2 at iplt = 0x100302a8 - 0x10010278 = (2<<16) + 48
+## ifunc3 at toc - ifunc3 at iplt = 0x100302b0 - 0x10010288 = (2<<16) + 40
 # CHECK:      Disassembly of section .glink:
 # CHECK-EMPTY:
 # CHECK-NEXT: 0000000010010268 <ifunc1>:
-# CHECK-NEXT:     addis 12, 2, 1
-# CHECK-NEXT:     ld 12, -32760(12)
+# CHECK-NEXT:     addis 12, 12, 2
+# CHECK-NEXT:     ld 12, 56(12)
 # CHECK-NEXT:     mtctr 12
 # CHECK-NEXT:     bctr
-# CHECK-NEXT:     addis 12, 2, 1
-# CHECK-NEXT:     ld 12, -32752(12)
+# CHECK-NEXT:     addis 12, 12, 2
+# CHECK-NEXT:     ld 12, 48(12)
 # CHECK-NEXT:     mtctr 12
 # CHECK-NEXT:     bctr
 # CHECK-EMPTY:
 # CHECK-NEXT: 0000000010010288 <ifunc3>:
-# CHECK-NEXT:     addis 12, 2, 1
-# CHECK-NEXT:     ld 12, -32744(12)
+# CHECK-NEXT:     addis 12, 12, 2
+# CHECK-NEXT:     ld 12, 40(12)
 # CHECK-NEXT:     mtctr 12
 # CHECK-NEXT:     bctr
 



More information about the llvm-commits mailing list