[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