Merge commit 26bf39cdba0b from llvm-project (by Jessica Clarke):

[ELF][PowerPC] Don't assume TOC pointer is valid in IPLT entries (#207555)

  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.
  On 32-bit PowerPC even direct calls to IPLT entries face the same issue,
  since we'd use the TOC base of the resolver, which may not be the same
  as the caller, even within the same object.

  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.

Bump LLD_FREEBSD_VERSION for this fix as otherwise an existing system
linker will be deemed new enough to use and produce broken kernels for
TARGET=powerpc (regardless of TARGET_ARCH/MACHINE/MACHINE_ARCH) builds.

PR:		294369
MFC after:	1 week
This commit is contained in:
Jessica Clarke committed 2026-07-27 17:54:04 +01:00
1 parent bcbcd73030
commit b68f2fe193
5 files changed
+67 -31

No files matched your search

+7 -6
View File
@@ -77,7 +77,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;
}
@@ -165,7 +165,7 @@ PPC::PPC(Ctx &ctx) : TargetInfo(ctx) {
gotPltHeaderEntriesNum = 0;
pltHeaderSize = 0;
pltEntrySize = 4;
ipltEntrySize = 16;
ipltEntrySize = ctx.arg.isPic ? 32 : 16;
needsThunks = true;
@@ -180,10 +180,11 @@ PPC::PPC(Ctx &ctx) : TargetInfo(ctx) {
}
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, nor for
// direct calls with a different TOC base to the resolver.
writePPC32PltCallStub(ctx, buf, pltEntryAddr, sym.getGotPltVA(ctx), sym.file,
std::nullopt);
}
void PPC::writeGotHeader(uint8_t *buf) const {
+3 -2
View File
@@ -1167,8 +1167,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));
uint64_t pltEntryAddr) const {
writePPC64LoadAndBranch(ctx, buf, pltEntryAddr, sym.getGotPltVA(ctx),
/*toc=*/false);
}
static std::pair<RelType, uint64_t> toAddr16Rel(RelType type, uint64_t val) {
+50 -19
View File
@@ -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,34 +1327,52 @@ void elf::writePPC32PltCallStub(Ctx &ctx, uint8_t *buf, uint64_t gotPltVA,
return;
}
uint32_t offset;
if (addend >= 0x8000) {
uint32_t reg;
uint64_t written = 0;
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;
written = 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 + 4, 0x7d6903a6); // mtctr r11
write32(ctx, buf + 8, 0x4e800420); // bctr
write32(ctx, buf + 12, 0x60000000); // nop
write32(ctx, buf + written + 0,
0x81600000 | (reg << 16) | l); // lwz r11,l(r[11|30])
write32(ctx, buf + written + 4, 0x7d6903a6); // mtctr r11
write32(ctx, buf + written + 8, 0x4e800420); // bctr
write32(ctx, buf + written + 12, 0x60000000); // nop
} else {
write32(ctx, buf + 0, 0x3d7e0000 | ha); // addis r11,r30,ha
write32(ctx, buf + 4, 0x816b0000 | l); // lwz r11,l(r11)
write32(ctx, buf + 8, 0x7d6903a6); // mtctr r11
write32(ctx, buf + 12, 0x4e800420); // bctr
write32(ctx, buf + written + 0,
0x3d600000 | (reg << 16) | ha); // addis r11,r[11|30],ha
write32(ctx, buf + written + 4, 0x816b0000 | l); // lwz r11,l(r11)
write32(ctx, buf + written + 8, 0x7d6903a6); // mtctr r11
write32(ctx, buf + written + 12, 0x4e800420); // bctr
}
}
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,12 +1421,22 @@ void PPC32LongThunk::writeTo(uint8_t *buf) {
write32(ctx, buf + 4, 0x4e800420); // bctr
}
void elf::writePPC64LoadAndBranch(Ctx &ctx, uint8_t *buf, uint64_t addr) {
uint64_t offset = addr - getPPC64TocBase(ctx);
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
@@ -1416,7 +1445,8 @@ void elf::writePPC64LoadAndBranch(Ctx &ctx, uint8_t *buf, uint64_t addr) {
void PPC64PltCallStub::writeTo(uint8_t *buf) {
// 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, destination.getGotPltVA(ctx));
writePPC64LoadAndBranch(ctx, buf + 4, getThunkTargetSym()->getVA(ctx) + 4,
destination.getGotPltVA(ctx));
}
void PPC64PltCallStub::addSymbols(ThunkSection &isec) {
@@ -1458,7 +1488,8 @@ void PPC64R2SaveStub::writeTo(uint8_t *buf) {
ctx.in.ppc64LongBranchTarget->addEntry(&destination, addend);
const uint64_t addr =
ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend);
writePPC64LoadAndBranch(ctx, buf + 4, addr);
writePPC64LoadAndBranch(ctx, buf + 4, getThunkTargetSym()->getVA(ctx) + 4,
addr);
}
}
@@ -1520,7 +1551,7 @@ bool PPC64R12SetupStub::isCompatibleWith(const InputSection &isec,
void PPC64LongBranchThunk::writeTo(uint8_t *buf) {
uint64_t addr =
ctx.in.ppc64LongBranchTarget->getEntryVA(&destination, addend);
writePPC64LoadAndBranch(ctx, buf, addr);
writePPC64LoadAndBranch(ctx, buf, getThunkTargetSym()->getVA(ctx), addr);
}
void PPC64LongBranchThunk::addSymbols(ThunkSection &isec) {
+6 -3
View File
@@ -83,9 +83,12 @@ 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, uint64_t addr);
// Call with addend as nullopt for an IPLT entry with no valid TOC pointer
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
+1 -1
View File
@@ -1,4 +1,4 @@
// Local identifier in __FreeBSD_version style
#define LLD_FREEBSD_VERSION 1600000
#define LLD_FREEBSD_VERSION 1600001
#define LLD_VERSION_STRING "21.1.8 (FreeBSD llvmorg-21.1.8-0-g2078da43e25a-" __XSTRING(LLD_FREEBSD_VERSION) ")"