#include "Thunks.h"
#include "Config.h"
#include "InputFiles.h"
#include "InputSection.h"
#include "OutputSections.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
#include "lld/Common/CommonLinkerContext.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include <cstdint>
#include <cstring>
using namespace llvm;
using namespace llvm::object;
using namespace llvm::ELF;
using namespace lld;
using namespace lld::elf;
namespace {
class AArch64Thunk : public Thunk {
public:
AArch64Thunk(Symbol &dest, int64_t addend) : Thunk(dest, addend) {}
bool getMayUseShortThunk();
void writeTo(uint8_t *buf) override;
private:
bool mayUseShortThunk = true;
virtual void writeLong(uint8_t *buf) = 0;
};
class AArch64ABSLongThunk final : public AArch64Thunk {
public:
AArch64ABSLongThunk(Symbol &dest, int64_t addend)
: AArch64Thunk(dest, addend) {}
uint32_t size() override { return getMayUseShortThunk() ? 4 : 16; }
void addSymbols(ThunkSection &isec) override;
private:
void writeLong(uint8_t *buf) override;
};
class AArch64ADRPThunk final : public AArch64Thunk {
public:
AArch64ADRPThunk(Symbol &dest, int64_t addend) : AArch64Thunk(dest, addend) {}
uint32_t size() override { return getMayUseShortThunk() ? 4 : 12; }
void addSymbols(ThunkSection &isec) override;
private:
void writeLong(uint8_t *buf) override;
};
class ARMThunk : public Thunk {
public:
ARMThunk(Symbol &dest, int64_t addend) : Thunk(dest, addend) {}
bool getMayUseShortThunk();
uint32_t size() override { return getMayUseShortThunk() ? 4 : sizeLong(); }
void writeTo(uint8_t *buf) override;
bool isCompatibleWith(const InputSection &isec,
const Relocation &rel) const override;
virtual uint32_t sizeLong() = 0;
virtual void writeLong(uint8_t *buf) = 0;
private:
bool mayUseShortThunk = true;
};
class ThumbThunk : public Thunk {
public:
ThumbThunk(Symbol &dest, int64_t addend) : Thunk(dest, addend) {
alignment = 2;
}
bool getMayUseShortThunk();
uint32_t size() override { return getMayUseShortThunk() ? 4 : sizeLong(); }
void writeTo(uint8_t *buf) override;
bool isCompatibleWith(const InputSection &isec,
const Relocation &rel) const override;
virtual uint32_t sizeLong() = 0;
virtual void writeLong(uint8_t *buf) = 0;
private:
bool mayUseShortThunk = true;
};
class ARMV7ABSLongThunk final : public ARMThunk {
public:
ARMV7ABSLongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ARMV7PILongThunk final : public ARMThunk {
public:
ARMV7PILongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
uint32_t sizeLong() override { return 16; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV7ABSLongThunk final : public ThumbThunk {
public:
ThumbV7ABSLongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 10; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV7PILongThunk final : public ThumbThunk {
public:
ThumbV7PILongThunk(Symbol &dest, int64_t addend) : ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV6MABSLongThunk final : public ThumbThunk {
public:
ThumbV6MABSLongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV6MABSXOLongThunk final : public ThumbThunk {
public:
ThumbV6MABSXOLongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 20; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV6MPILongThunk final : public ThumbThunk {
public:
ThumbV6MPILongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 16; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ARMV5LongLdrPcThunk final : public ARMThunk {
public:
ARMV5LongLdrPcThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
uint32_t sizeLong() override { return 8; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ARMV4PILongBXThunk final : public ARMThunk {
public:
ARMV4PILongBXThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
uint32_t sizeLong() override { return 16; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ARMV4PILongThunk final : public ARMThunk {
public:
ARMV4PILongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV4PILongBXThunk final : public ThumbThunk {
public:
ThumbV4PILongBXThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 16; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV4PILongThunk final : public ThumbThunk {
public:
ThumbV4PILongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 20; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ARMV4ABSLongBXThunk final : public ARMThunk {
public:
ARMV4ABSLongBXThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV4ABSLongBXThunk final : public ThumbThunk {
public:
ThumbV4ABSLongBXThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 12; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class ThumbV4ABSLongThunk final : public ThumbThunk {
public:
ThumbV4ABSLongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 16; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class AVRThunk : public Thunk {
public:
AVRThunk(Symbol &dest, int64_t addend) : Thunk(dest, addend) {}
uint32_t size() override { return 4; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class MipsThunk final : public Thunk {
public:
MipsThunk(Symbol &dest) : Thunk(dest, 0) {}
uint32_t size() override { return 16; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
InputSection *getTargetInputSection() const override;
};
class MicroMipsThunk final : public Thunk {
public:
MicroMipsThunk(Symbol &dest) : Thunk(dest, 0) {}
uint32_t size() override { return 14; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
InputSection *getTargetInputSection() const override;
};
class MicroMipsR6Thunk final : public Thunk {
public:
MicroMipsR6Thunk(Symbol &dest) : Thunk(dest, 0) {}
uint32_t size() override { return 12; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
InputSection *getTargetInputSection() const override;
};
class PPC32PltCallStub final : public Thunk {
public:
PPC32PltCallStub(const InputSection &isec, const Relocation &rel,
Symbol &dest)
: Thunk(dest, rel.addend), file(isec.file) {}
uint32_t size() override { return 16; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
bool isCompatibleWith(const InputSection &isec, const Relocation &rel) const override;
private:
const InputFile *file;
};
class PPC32LongThunk final : public Thunk {
public:
PPC32LongThunk(Symbol &dest, int64_t addend) : Thunk(dest, addend) {}
uint32_t size() override { return config->isPic ? 32 : 16; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
};
class PPC64PltCallStub final : public Thunk {
public:
PPC64PltCallStub(Symbol &dest) : Thunk(dest, 0) {}
uint32_t size() override { return 20; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
bool isCompatibleWith(const InputSection &isec,
const Relocation &rel) const override;
};
class PPC64R2SaveStub final : public Thunk {
public:
PPC64R2SaveStub(Symbol &dest, int64_t addend) : Thunk(dest, addend) {
alignment = 16;
}
bool getMayUseShortThunk() {
if (!mayUseShortThunk)
return false;
if (!isInt<26>(computeOffset())) {
mayUseShortThunk = false;
return false;
}
return true;
}
uint32_t size() override { return getMayUseShortThunk() ? 8 : 32; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
bool isCompatibleWith(const InputSection &isec,
const Relocation &rel) const override;
private:
bool mayUseShortThunk = true;
int64_t computeOffset() const {
return destination.getVA() - (getThunkTargetSym()->getVA() + 4);
}
};
class PPC64R12SetupStub final : public Thunk {
public:
PPC64R12SetupStub(Symbol &dest, bool gotPlt)
: Thunk(dest, 0), gotPlt(gotPlt) {
alignment = 16;
}
uint32_t size() override { return 32; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
bool isCompatibleWith(const InputSection &isec,
const Relocation &rel) const override;
private:
bool gotPlt;
};
class PPC64LongBranchThunk : public Thunk {
public:
uint32_t size() override { return 32; }
void writeTo(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
bool isCompatibleWith(const InputSection &isec,
const Relocation &rel) const override;
protected:
PPC64LongBranchThunk(Symbol &dest, int64_t addend) : Thunk(dest, addend) {}
};
class PPC64PILongBranchThunk final : public PPC64LongBranchThunk {
public:
PPC64PILongBranchThunk(Symbol &dest, int64_t addend)
: PPC64LongBranchThunk(dest, addend) {
assert(!dest.isPreemptible);
if (std::optional<uint32_t> index =
in.ppc64LongBranchTarget->addEntry(&dest, addend)) {
mainPart->relaDyn->addRelativeReloc(
target->relativeRel, *in.ppc64LongBranchTarget, *index * UINT64_C(8),
dest, addend + getPPC64GlobalEntryToLocalEntryOffset(dest.stOther),
target->symbolicRel, R_ABS);
}
}
};
class PPC64PDLongBranchThunk final : public PPC64LongBranchThunk {
public:
PPC64PDLongBranchThunk(Symbol &dest, int64_t addend)
: PPC64LongBranchThunk(dest, addend) {
in.ppc64LongBranchTarget->addEntry(&dest, addend);
}
};
}
Defined *Thunk::addSymbol(StringRef name, uint8_t type, uint64_t value,
InputSectionBase §ion) {
Defined *d = addSyntheticLocal(name, type, value, 0, section);
syms.push_back(d);
return d;
}
void Thunk::setOffset(uint64_t newOffset) {
for (Defined *d : syms)
d->value = d->value - offset + newOffset;
offset = newOffset;
}
static uint64_t getAArch64ThunkDestVA(const Symbol &s, int64_t a) {
uint64_t v = s.isInPlt() ? s.getPltVA() : s.getVA(a);
return v;
}
bool AArch64Thunk::getMayUseShortThunk() {
if (!mayUseShortThunk)
return false;
uint64_t s = getAArch64ThunkDestVA(destination, addend);
uint64_t p = getThunkTargetSym()->getVA();
mayUseShortThunk = llvm::isInt<28>(s - p);
return mayUseShortThunk;
}
void AArch64Thunk::writeTo(uint8_t *buf) {
if (!getMayUseShortThunk()) {
writeLong(buf);
return;
}
uint64_t s = getAArch64ThunkDestVA(destination, addend);
uint64_t p = getThunkTargetSym()->getVA();
write32(buf, 0x14000000);
target->relocateNoSym(buf, R_AARCH64_CALL26, s - p);
}
void AArch64ABSLongThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x50, 0x00, 0x00, 0x58,
0x00, 0x02, 0x1f, 0xd6,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
uint64_t s = getAArch64ThunkDestVA(destination, addend);
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 8, R_AARCH64_ABS64, s);
}
void AArch64ABSLongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__AArch64AbsLongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$x", STT_NOTYPE, 0, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 8, isec);
}
void AArch64ADRPThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x10, 0x00, 0x00, 0x90,
0x10, 0x02, 0x00, 0x91,
0x00, 0x02, 0x1f, 0xd6,
};
uint64_t s = getAArch64ThunkDestVA(destination, addend);
uint64_t p = getThunkTargetSym()->getVA();
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf, R_AARCH64_ADR_PREL_PG_HI21,
getAArch64Page(s) - getAArch64Page(p));
target->relocateNoSym(buf + 4, R_AARCH64_ADD_ABS_LO12_NC, s);
}
void AArch64ADRPThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__AArch64ADRPThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$x", STT_NOTYPE, 0, isec);
}
static uint64_t getARMThunkDestVA(const Symbol &s) {
uint64_t v = s.isInPlt() ? s.getPltVA() : s.getVA();
return SignExtend64<32>(v);
}
bool ARMThunk::getMayUseShortThunk() {
if (!mayUseShortThunk)
return false;
uint64_t s = getARMThunkDestVA(destination);
if (s & 1) {
mayUseShortThunk = false;
return false;
}
uint64_t p = getThunkTargetSym()->getVA();
int64_t offset = s - p - 8;
mayUseShortThunk = llvm::isInt<26>(offset);
return mayUseShortThunk;
}
void ARMThunk::writeTo(uint8_t *buf) {
if (!getMayUseShortThunk()) {
writeLong(buf);
return;
}
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA();
int64_t offset = s - p - 8;
write32(buf, 0xea000000);
target->relocateNoSym(buf, R_ARM_JUMP24, offset);
}
bool ARMThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
if (!config->armHasBlx && rel.type == R_ARM_THM_CALL)
return false;
return rel.type != R_ARM_THM_JUMP19 && rel.type != R_ARM_THM_JUMP24;
}
bool ThumbThunk::getMayUseShortThunk() {
if (!mayUseShortThunk || !config->armJ1J2BranchEncoding)
return false;
uint64_t s = getARMThunkDestVA(destination);
if ((s & 1) == 0) {
mayUseShortThunk = false;
return false;
}
uint64_t p = getThunkTargetSym()->getVA() & ~1;
int64_t offset = s - p - 4;
mayUseShortThunk = llvm::isInt<25>(offset);
return mayUseShortThunk;
}
void ThumbThunk::writeTo(uint8_t *buf) {
if (!getMayUseShortThunk()) {
writeLong(buf);
return;
}
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA();
int64_t offset = s - p - 4;
write16(buf + 0, 0xf000);
write16(buf + 2, 0xb000);
target->relocateNoSym(buf, R_ARM_THM_JUMP24, offset);
}
bool ThumbThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
if (!config->armHasBlx && rel.type == R_ARM_CALL)
return false;
return rel.type != R_ARM_JUMP24 && rel.type != R_ARM_PC24 && rel.type != R_ARM_PLT32;
}
void ARMV7ABSLongThunk::writeLong(uint8_t *buf) {
write32(buf + 0, 0xe300c000);
write32(buf + 4, 0xe340c000);
write32(buf + 8, 0xe12fff1c);
uint64_t s = getARMThunkDestVA(destination);
target->relocateNoSym(buf, R_ARM_MOVW_ABS_NC, s);
target->relocateNoSym(buf + 4, R_ARM_MOVT_ABS, s);
}
void ARMV7ABSLongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv7ABSLongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
}
void ThumbV7ABSLongThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0xf240);
write16(buf + 2, 0x0c00);
write16(buf + 4, 0xf2c0);
write16(buf + 6, 0x0c00);
write16(buf + 8, 0x4760);
uint64_t s = getARMThunkDestVA(destination);
target->relocateNoSym(buf, R_ARM_THM_MOVW_ABS_NC, s);
target->relocateNoSym(buf + 4, R_ARM_THM_MOVT_ABS, s);
}
void ThumbV7ABSLongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv7ABSLongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
}
void ARMV7PILongThunk::writeLong(uint8_t *buf) {
write32(buf + 0, 0xe30fcff0);
write32(buf + 4, 0xe340c000);
write32(buf + 8, 0xe08cc00f);
write32(buf + 12, 0xe12fff1c);
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA();
int64_t offset = s - p - 16;
target->relocateNoSym(buf, R_ARM_MOVW_PREL_NC, offset);
target->relocateNoSym(buf + 4, R_ARM_MOVT_PREL, offset);
}
void ARMV7PILongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMV7PILongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
}
void ThumbV7PILongThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0xf64f);
write16(buf + 2, 0x7cf4);
write16(buf + 4, 0xf2c0);
write16(buf + 6, 0x0c00);
write16(buf + 8, 0x44fc);
write16(buf + 10, 0x4760);
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
int64_t offset = s - p - 12;
target->relocateNoSym(buf, R_ARM_THM_MOVW_PREL_NC, offset);
target->relocateNoSym(buf + 4, R_ARM_THM_MOVT_PREL, offset);
}
void ThumbV7PILongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ThumbV7PILongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
}
void ThumbV6MABSLongThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0xb403);
write16(buf + 2, 0x4801);
write16(buf + 4, 0x9001);
write16(buf + 6, 0xbd01);
write32(buf + 8, 0x00000000);
uint64_t s = getARMThunkDestVA(destination);
target->relocateNoSym(buf + 8, R_ARM_ABS32, s);
}
void ThumbV6MABSLongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv6MABSLongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 8, isec);
}
void ThumbV6MABSXOLongThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0xb403);
write16(buf + 2, 0x2000);
write16(buf + 4, 0x0200);
write16(buf + 6, 0x3000);
write16(buf + 8, 0x0200);
write16(buf + 10, 0x3000);
write16(buf + 12, 0x0200);
write16(buf + 14, 0x3000);
write16(buf + 16, 0x9001);
write16(buf + 18, 0xbd01);
uint64_t s = getARMThunkDestVA(destination);
target->relocateNoSym(buf + 2, R_ARM_THM_ALU_ABS_G3, s);
target->relocateNoSym(buf + 6, R_ARM_THM_ALU_ABS_G2_NC, s);
target->relocateNoSym(buf + 10, R_ARM_THM_ALU_ABS_G1_NC, s);
target->relocateNoSym(buf + 14, R_ARM_THM_ALU_ABS_G0_NC, s);
}
void ThumbV6MABSXOLongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv6MABSXOLongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
}
void ThumbV6MPILongThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0xb401);
write16(buf + 2, 0x4802);
write16(buf + 4, 0x4684);
write16(buf + 6, 0xbc01);
write16(buf + 8, 0x44e7);
write16(buf + 10, 0x46c0);
write32(buf + 12, 0x00000000);
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 12);
}
void ThumbV6MPILongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv6MPILongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 12, isec);
}
void ARMV5LongLdrPcThunk::writeLong(uint8_t *buf) {
write32(buf + 0, 0xe51ff004);
write32(buf + 4, 0x00000000);
target->relocateNoSym(buf + 4, R_ARM_ABS32, getARMThunkDestVA(destination));
}
void ARMV5LongLdrPcThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv5LongLdrPcThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 4, isec);
}
void ARMV4ABSLongBXThunk::writeLong(uint8_t *buf) {
write32(buf + 0, 0xe59fc000);
write32(buf + 4, 0xe12fff1c);
write32(buf + 8, 0x00000000);
target->relocateNoSym(buf + 8, R_ARM_ABS32, getARMThunkDestVA(destination));
}
void ARMV4ABSLongBXThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv4ABSLongBXThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 8, isec);
}
void ThumbV4ABSLongBXThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0x4778);
write16(buf + 2, 0xe7fd);
write32(buf + 4, 0xe51ff004);
write32(buf + 8, 0x00000000);
target->relocateNoSym(buf + 8, R_ARM_ABS32, getARMThunkDestVA(destination));
}
void ThumbV4ABSLongBXThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv4ABSLongBXThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
addSymbol("$a", STT_NOTYPE, 4, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 8, isec);
}
void ThumbV4ABSLongThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0x4778);
write16(buf + 2, 0xe7fd);
write32(buf + 4, 0xe59fc000);
write32(buf + 8, 0xe12fff1c);
write32(buf + 12, 0x00000000);
target->relocateNoSym(buf + 12, R_ARM_ABS32, getARMThunkDestVA(destination));
}
void ThumbV4ABSLongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv4ABSLongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
addSymbol("$a", STT_NOTYPE, 4, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 12, isec);
}
void ARMV4PILongBXThunk::writeLong(uint8_t *buf) {
write32(buf + 0, 0xe59fc004);
write32(buf + 4, 0xe08fc00c);
write32(buf + 8, 0xe12fff1c);
write32(buf + 12, 0x00000000);
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 12);
}
void ARMV4PILongBXThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv4PILongBXThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 12, isec);
}
void ARMV4PILongThunk::writeLong(uint8_t *buf) {
write32(buf + 0, 0xe59fc000);
write32(buf + 4, 0xe08ff00c);
write32(buf + 8, 0x00000000);
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
target->relocateNoSym(buf + 8, R_ARM_REL32, s - p - 12);
}
void ARMV4PILongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__ARMv4PILongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 8, isec);
}
void ThumbV4PILongBXThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0x4778);
write16(buf + 2, 0xe7fd);
write32(buf + 4, 0xe59fc000);
write32(buf + 8, 0xe08cf00f);
write32(buf + 12, 0x00000000);
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 16);
}
void ThumbV4PILongBXThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv4PILongBXThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
addSymbol("$a", STT_NOTYPE, 4, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 12, isec);
}
void ThumbV4PILongThunk::writeLong(uint8_t *buf) {
write16(buf + 0, 0x4778);
write16(buf + 2, 0xe7fd);
write32(buf + 4, 0xe59fc004);
write32(buf + 8, 0xe08fc00c);
write32(buf + 12, 0xe12fff1c);
write32(buf + 16, 0x00000000);
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
target->relocateNoSym(buf + 16, R_ARM_REL32, s - p - 16);
}
void ThumbV4PILongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__Thumbv4PILongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
addSymbol("$a", STT_NOTYPE, 4, isec);
if (!getMayUseShortThunk())
addSymbol("$d", STT_NOTYPE, 16, isec);
}
void AVRThunk::writeTo(uint8_t *buf) {
write32(buf, 0x940c);
target->relocateNoSym(buf, R_AVR_CALL, destination.getVA());
}
void AVRThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__AVRThunk_" + destination.getName()), STT_FUNC, 0,
isec);
}
void MipsThunk::writeTo(uint8_t *buf) {
uint64_t s = destination.getVA();
write32(buf, 0x3c190000);
write32(buf + 4, 0x08000000 | (s >> 2));
write32(buf + 8, 0x27390000);
write32(buf + 12, 0x00000000);
target->relocateNoSym(buf, R_MIPS_HI16, s);
target->relocateNoSym(buf + 8, R_MIPS_LO16, s);
}
void MipsThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__LA25Thunk_" + destination.getName()), STT_FUNC, 0,
isec);
}
InputSection *MipsThunk::getTargetInputSection() const {
auto &dr = cast<Defined>(destination);
return dyn_cast<InputSection>(dr.section);
}
void MicroMipsThunk::writeTo(uint8_t *buf) {
uint64_t s = destination.getVA();
write16(buf, 0x41b9);
write16(buf + 4, 0xd400);
write16(buf + 8, 0x3339);
write16(buf + 12, 0x0c00);
target->relocateNoSym(buf, R_MICROMIPS_HI16, s);
target->relocateNoSym(buf + 4, R_MICROMIPS_26_S1, s);
target->relocateNoSym(buf + 8, R_MICROMIPS_LO16, s);
}
void MicroMipsThunk::addSymbols(ThunkSection &isec) {
Defined *d =
addSymbol(saver().save("__microLA25Thunk_" + destination.getName()),
STT_FUNC, 0, isec);
d->stOther |= STO_MIPS_MICROMIPS;
}
InputSection *MicroMipsThunk::getTargetInputSection() const {
auto &dr = cast<Defined>(destination);
return dyn_cast<InputSection>(dr.section);
}
void MicroMipsR6Thunk::writeTo(uint8_t *buf) {
uint64_t s = destination.getVA();
uint64_t p = getThunkTargetSym()->getVA();
write16(buf, 0x1320);
write16(buf + 4, 0x3339);
write16(buf + 8, 0x9400);
target->relocateNoSym(buf, R_MICROMIPS_HI16, s);
target->relocateNoSym(buf + 4, R_MICROMIPS_LO16, s);
target->relocateNoSym(buf + 8, R_MICROMIPS_PC26_S1, s - p - 12);
}
void MicroMipsR6Thunk::addSymbols(ThunkSection &isec) {
Defined *d =
addSymbol(saver().save("__microLA25Thunk_" + destination.getName()),
STT_FUNC, 0, isec);
d->stOther |= STO_MIPS_MICROMIPS;
}
InputSection *MicroMipsR6Thunk::getTargetInputSection() const {
auto &dr = cast<Defined>(destination);
return dyn_cast<InputSection>(dr.section);
}
void elf::writePPC32PltCallStub(uint8_t *buf, uint64_t gotPltVA,
const InputFile *file, int64_t addend) {
if (!config->isPic) {
write32(buf + 0, 0x3d600000 | (gotPltVA + 0x8000) >> 16);
write32(buf + 4, 0x816b0000 | (uint16_t)gotPltVA);
write32(buf + 8, 0x7d6903a6);
write32(buf + 12, 0x4e800420);
return;
}
uint32_t offset;
if (addend >= 0x8000) {
offset = gotPltVA -
(in.ppc32Got2->getParent()->getVA() +
(file->ppc32Got2 ? file->ppc32Got2->outSecOff : 0) + addend);
} else {
offset = gotPltVA - in.got->getVA();
}
uint16_t ha = (offset + 0x8000) >> 16, l = (uint16_t)offset;
if (ha == 0) {
write32(buf + 0, 0x817e0000 | l);
write32(buf + 4, 0x7d6903a6);
write32(buf + 8, 0x4e800420);
write32(buf + 12, 0x60000000);
} else {
write32(buf + 0, 0x3d7e0000 | ha);
write32(buf + 4, 0x816b0000 | l);
write32(buf + 8, 0x7d6903a6);
write32(buf + 12, 0x4e800420);
}
}
void PPC32PltCallStub::writeTo(uint8_t *buf) {
writePPC32PltCallStub(buf, destination.getGotPltVA(), file, addend);
}
void PPC32PltCallStub::addSymbols(ThunkSection &isec) {
std::string buf;
raw_string_ostream os(buf);
os << format_hex_no_prefix(addend, 8);
if (!config->isPic)
os << ".plt_call32.";
else if (addend >= 0x8000)
os << ".got2.plt_pic32.";
else
os << ".plt_pic32.";
os << destination.getName();
addSymbol(saver().save(os.str()), STT_FUNC, 0, isec);
}
bool PPC32PltCallStub::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
return !config->isPic || (isec.file == file && rel.addend == addend);
}
void PPC32LongThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__LongThunk_" + destination.getName()), STT_FUNC, 0,
isec);
}
void PPC32LongThunk::writeTo(uint8_t *buf) {
auto ha = [](uint32_t v) -> uint16_t { return (v + 0x8000) >> 16; };
auto lo = [](uint32_t v) -> uint16_t { return v; };
uint32_t d = destination.getVA(addend);
if (config->isPic) {
uint32_t off = d - (getThunkTargetSym()->getVA() + 8);
write32(buf + 0, 0x7c0802a6);
write32(buf + 4, 0x429f0005);
write32(buf + 8, 0x7d8802a6);
write32(buf + 12, 0x3d8c0000 | ha(off));
write32(buf + 16, 0x398c0000 | lo(off));
write32(buf + 20, 0x7c0803a6);
buf += 24;
} else {
write32(buf + 0, 0x3d800000 | ha(d));
write32(buf + 4, 0x398c0000 | lo(d));
buf += 8;
}
write32(buf + 0, 0x7d8903a6);
write32(buf + 4, 0x4e800420);
}
void elf::writePPC64LoadAndBranch(uint8_t *buf, int64_t offset) {
uint16_t offHa = (offset + 0x8000) >> 16;
uint16_t offLo = offset & 0xffff;
write32(buf + 0, 0x3d820000 | offHa);
write32(buf + 4, 0xe98c0000 | offLo);
write32(buf + 8, 0x7d8903a6);
write32(buf + 12, 0x4e800420);
}
void PPC64PltCallStub::writeTo(uint8_t *buf) {
int64_t offset = destination.getGotPltVA() - getPPC64TocBase();
write32(buf + 0, 0xf8410018);
writePPC64LoadAndBranch(buf + 4, offset);
}
void PPC64PltCallStub::addSymbols(ThunkSection &isec) {
Defined *s = addSymbol(saver().save("__plt_" + destination.getName()),
STT_FUNC, 0, isec);
s->setNeedsTocRestore(true);
s->file = destination.file;
}
bool PPC64PltCallStub::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
return rel.type == R_PPC64_REL24 || rel.type == R_PPC64_REL14;
}
void PPC64R2SaveStub::writeTo(uint8_t *buf) {
const int64_t offset = computeOffset();
write32(buf + 0, 0xf8410018);
if (getMayUseShortThunk()) {
write32(buf + 4, 0x48000000 | (offset & 0x03fffffc));
} else if (isInt<34>(offset)) {
int nextInstOffset;
uint64_t tocOffset = destination.getVA() - getPPC64TocBase();
if (tocOffset >> 16 > 0) {
const uint64_t addi = ADDI_R12_TO_R12_NO_DISP | (tocOffset & 0xffff);
const uint64_t addis =
ADDIS_R12_TO_R2_NO_DISP | ((tocOffset >> 16) & 0xffff);
write32(buf + 4, addis);
write32(buf + 8, addi);
nextInstOffset = 12;
} else {
const uint64_t addi = ADDI_R12_TO_R2_NO_DISP | (tocOffset & 0xffff);
write32(buf + 4, addi);
nextInstOffset = 8;
}
write32(buf + nextInstOffset, MTCTR_R12);
write32(buf + nextInstOffset + 4, BCTR);
} else {
in.ppc64LongBranchTarget->addEntry(&destination, addend);
const int64_t offsetFromTOC =
in.ppc64LongBranchTarget->getEntryVA(&destination, addend) -
getPPC64TocBase();
writePPC64LoadAndBranch(buf + 4, offsetFromTOC);
}
}
void PPC64R2SaveStub::addSymbols(ThunkSection &isec) {
Defined *s = addSymbol(saver().save("__toc_save_" + destination.getName()),
STT_FUNC, 0, isec);
s->setNeedsTocRestore(true);
}
bool PPC64R2SaveStub::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
return rel.type == R_PPC64_REL24 || rel.type == R_PPC64_REL14;
}
void PPC64R12SetupStub::writeTo(uint8_t *buf) {
int64_t offset = (gotPlt ? destination.getGotPltVA() : destination.getVA()) -
getThunkTargetSym()->getVA();
if (!isInt<34>(offset))
reportRangeError(buf, offset, 34, destination, "R12 setup stub offset");
int nextInstOffset;
if (config->power10Stubs) {
const uint64_t imm = (((offset >> 16) & 0x3ffff) << 32) | (offset & 0xffff);
writePrefixedInstruction(
buf, (gotPlt ? PLD_R12_NO_DISP : PADDI_R12_NO_DISP) | imm);
nextInstOffset = 8;
} else {
uint32_t off = offset - 8;
write32(buf + 0, 0x7d8802a6);
write32(buf + 4, 0x429f0005);
write32(buf + 8, 0x7d6802a6);
write32(buf + 12, 0x7d8803a6);
write32(buf + 16,
0x3d8b0000 | ((off + 0x8000) >> 16));
if (gotPlt)
write32(buf + 20, 0xe98c0000 | (off & 0xffff));
else
write32(buf + 20, 0x398c0000 | (off & 0xffff));
nextInstOffset = 24;
}
write32(buf + nextInstOffset, MTCTR_R12);
write32(buf + nextInstOffset + 4, BCTR);
}
void PPC64R12SetupStub::addSymbols(ThunkSection &isec) {
addSymbol(saver().save((gotPlt ? "__plt_pcrel_" : "__gep_setup_") +
destination.getName()),
STT_FUNC, 0, isec);
}
bool PPC64R12SetupStub::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
return rel.type == R_PPC64_REL24_NOTOC;
}
void PPC64LongBranchThunk::writeTo(uint8_t *buf) {
int64_t offset = in.ppc64LongBranchTarget->getEntryVA(&destination, addend) -
getPPC64TocBase();
writePPC64LoadAndBranch(buf, offset);
}
void PPC64LongBranchThunk::addSymbols(ThunkSection &isec) {
addSymbol(saver().save("__long_branch_" + destination.getName()), STT_FUNC, 0,
isec);
}
bool PPC64LongBranchThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
return rel.type == R_PPC64_REL24 || rel.type == R_PPC64_REL14;
}
Thunk::Thunk(Symbol &d, int64_t a) : destination(d), addend(a), offset(0) {
destination.thunkAccessed = true;
}
Thunk::~Thunk() = default;
static Thunk *addThunkAArch64(RelType type, Symbol &s, int64_t a) {
if (type != R_AARCH64_CALL26 && type != R_AARCH64_JUMP26 &&
type != R_AARCH64_PLT32)
fatal("unrecognized relocation type");
if (config->picThunk)
return make<AArch64ADRPThunk>(s, a);
return make<AArch64ABSLongThunk>(s, a);
}
static Thunk *addThunkArmv4(RelType reloc, Symbol &s, int64_t a) {
bool thumb_target = s.getVA(a) & 1;
switch (reloc) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_CALL:
if (config->picThunk) {
if (thumb_target)
return make<ARMV4PILongBXThunk>(s, a);
return make<ARMV4PILongThunk>(s, a);
}
if (thumb_target)
return make<ARMV4ABSLongBXThunk>(s, a);
return make<ARMV5LongLdrPcThunk>(s, a);
case R_ARM_THM_CALL:
if (config->picThunk) {
if (thumb_target)
return make<ThumbV4PILongThunk>(s, a);
return make<ThumbV4PILongBXThunk>(s, a);
}
if (thumb_target)
return make<ThumbV4ABSLongThunk>(s, a);
return make<ThumbV4ABSLongBXThunk>(s, a);
}
fatal("relocation " + toString(reloc) + " to " + toString(s) +
" not supported for Armv4 or Armv4T target");
}
static Thunk *addThunkArmv5v6(RelType reloc, Symbol &s, int64_t a) {
switch (reloc) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_CALL:
case R_ARM_THM_CALL:
if (config->picThunk)
return make<ARMV4PILongBXThunk>(s, a);
return make<ARMV5LongLdrPcThunk>(s, a);
}
fatal("relocation " + toString(reloc) + " to " + toString(s) +
" not supported for Armv5 or Armv6 targets");
}
static Thunk *addThunkV6M(const InputSection &isec, RelType reloc, Symbol &s,
int64_t a) {
const bool isPureCode = isec.getParent()->flags & SHF_ARM_PURECODE;
switch (reloc) {
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
case R_ARM_THM_CALL:
if (config->isPic) {
if (!isPureCode)
return make<ThumbV6MPILongThunk>(s, a);
fatal("relocation " + toString(reloc) + " to " + toString(s) +
" not supported for Armv6-M targets for position independent"
" and execute only code");
}
if (isPureCode)
return make<ThumbV6MABSXOLongThunk>(s, a);
return make<ThumbV6MABSLongThunk>(s, a);
}
fatal("relocation " + toString(reloc) + " to " + toString(s) +
" not supported for Armv6-M targets");
}
static Thunk *addThunkArm(const InputSection &isec, RelType reloc, Symbol &s,
int64_t a) {
if (!config->armHasMovtMovw) {
if (config->armJ1J2BranchEncoding)
return addThunkV6M(isec, reloc, s, a);
if (config->armHasBlx)
return addThunkArmv5v6(reloc, s, a);
return addThunkArmv4(reloc, s, a);
}
switch (reloc) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_CALL:
if (config->picThunk)
return make<ARMV7PILongThunk>(s, a);
return make<ARMV7ABSLongThunk>(s, a);
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
case R_ARM_THM_CALL:
if (config->picThunk)
return make<ThumbV7PILongThunk>(s, a);
return make<ThumbV7ABSLongThunk>(s, a);
}
fatal("unrecognized relocation type");
}
static Thunk *addThunkAVR(RelType type, Symbol &s, int64_t a) {
switch (type) {
case R_AVR_LO8_LDI_GS:
case R_AVR_HI8_LDI_GS:
return make<AVRThunk>(s, a);
default:
fatal("unrecognized relocation type " + toString(type));
}
}
static Thunk *addThunkMips(RelType type, Symbol &s) {
if ((s.stOther & STO_MIPS_MICROMIPS) && isMipsR6())
return make<MicroMipsR6Thunk>(s);
if (s.stOther & STO_MIPS_MICROMIPS)
return make<MicroMipsThunk>(s);
return make<MipsThunk>(s);
}
static Thunk *addThunkPPC32(const InputSection &isec, const Relocation &rel,
Symbol &s) {
assert((rel.type == R_PPC_LOCAL24PC || rel.type == R_PPC_REL24 ||
rel.type == R_PPC_PLTREL24) &&
"unexpected relocation type for thunk");
if (s.isInPlt())
return make<PPC32PltCallStub>(isec, rel, s);
return make<PPC32LongThunk>(s, rel.addend);
}
static Thunk *addThunkPPC64(RelType type, Symbol &s, int64_t a) {
assert((type == R_PPC64_REL14 || type == R_PPC64_REL24 ||
type == R_PPC64_REL24_NOTOC) &&
"unexpected relocation type for thunk");
if (type == R_PPC64_REL24_NOTOC)
getPPC64TargetInfo()->ppc64DynamicSectionOpt = 0x2;
if (s.isInPlt())
return type == R_PPC64_REL24_NOTOC
? (Thunk *)make<PPC64R12SetupStub>(s, true)
: (Thunk *)make<PPC64PltCallStub>(s);
if ((type == R_PPC64_REL14 || type == R_PPC64_REL24) && (s.stOther >> 5) == 1)
return make<PPC64R2SaveStub>(s, a);
if (type == R_PPC64_REL24_NOTOC)
return make<PPC64R12SetupStub>(s, false);
if (config->picThunk)
return make<PPC64PILongBranchThunk>(s, a);
return make<PPC64PDLongBranchThunk>(s, a);
}
Thunk *elf::addThunk(const InputSection &isec, Relocation &rel) {
Symbol &s = *rel.sym;
int64_t a = rel.addend;
switch (config->emachine) {
case EM_AARCH64:
return addThunkAArch64(rel.type, s, a);
case EM_ARM:
return addThunkArm(isec, rel.type, s, a);
case EM_AVR:
return addThunkAVR(rel.type, s, a);
case EM_MIPS:
return addThunkMips(rel.type, s);
case EM_PPC:
return addThunkPPC32(isec, rel, s);
case EM_PPC64:
return addThunkPPC64(rel.type, s, a);
default:
llvm_unreachable("add Thunk only supported for ARM, AVR, Mips and PowerPC");
}
}