#include "InputFiles.h"
#include "OutputSections.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Filesystem.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
using namespace llvm::support::endian;
using namespace llvm::support;
using namespace llvm::ELF;
using namespace lld;
using namespace lld::elf;
using namespace llvm::object;
namespace {
class ARM final : public TargetInfo {
public:
ARM();
uint32_t calcEFlags() const override;
RelExpr getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const override;
RelType getDynRel(RelType type) const override;
int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override;
void writeGotPlt(uint8_t *buf, const Symbol &s) const override;
void writeIgotPlt(uint8_t *buf, const Symbol &s) const override;
void writePltHeader(uint8_t *buf) const override;
void writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const override;
void addPltSymbols(InputSection &isec, uint64_t off) const override;
void addPltHeaderSymbols(InputSection &isd) const override;
bool needsThunk(RelExpr expr, RelType type, const InputFile *file,
uint64_t branchAddr, const Symbol &s,
int64_t a) const override;
uint32_t getThunkSectionSpacing() const override;
bool inBranchRange(RelType type, uint64_t src, uint64_t dst) const override;
void relocate(uint8_t *loc, const Relocation &rel,
uint64_t val) const override;
};
enum class CodeState { Data = 0, Thumb = 2, Arm = 4 };
}
static DenseMap<InputSection *, SmallVector<const Defined *, 0>> sectionMap{};
ARM::ARM() {
copyRel = R_ARM_COPY;
relativeRel = R_ARM_RELATIVE;
iRelativeRel = R_ARM_IRELATIVE;
gotRel = R_ARM_GLOB_DAT;
pltRel = R_ARM_JUMP_SLOT;
symbolicRel = R_ARM_ABS32;
tlsGotRel = R_ARM_TLS_TPOFF32;
tlsModuleIndexRel = R_ARM_TLS_DTPMOD32;
tlsOffsetRel = R_ARM_TLS_DTPOFF32;
pltHeaderSize = 32;
pltEntrySize = 16;
ipltEntrySize = 16;
trapInstr = {0xd4, 0xd4, 0xd4, 0xd4};
needsThunks = true;
defaultMaxPageSize = 65536;
}
uint32_t ARM::calcEFlags() const {
uint32_t abiFloatType = 0;
uint32_t armBE8 = 0;
if (config->armVFPArgs == ARMVFPArgKind::Base ||
config->armVFPArgs == ARMVFPArgKind::Default)
abiFloatType = EF_ARM_ABI_FLOAT_SOFT;
else if (config->armVFPArgs == ARMVFPArgKind::VFP)
abiFloatType = EF_ARM_ABI_FLOAT_HARD;
if (!config->isLE && config->armBe8)
armBE8 = EF_ARM_BE8;
return EF_ARM_EABI_VER5 | abiFloatType | armBE8;
}
RelExpr ARM::getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const {
switch (type) {
case R_ARM_ABS32:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_ABS:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_ABS:
case R_ARM_THM_ALU_ABS_G0_NC:
case R_ARM_THM_ALU_ABS_G1_NC:
case R_ARM_THM_ALU_ABS_G2_NC:
case R_ARM_THM_ALU_ABS_G3:
return R_ABS;
case R_ARM_THM_JUMP8:
case R_ARM_THM_JUMP11:
return R_PC;
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_PREL31:
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
case R_ARM_THM_CALL:
return R_PLT_PC;
case R_ARM_GOTOFF32:
return R_GOTREL;
case R_ARM_GOT_BREL:
return R_GOT_OFF;
case R_ARM_GOT_PREL:
case R_ARM_TLS_IE32:
return R_GOT_PC;
case R_ARM_SBREL32:
return R_ARM_SBREL;
case R_ARM_TARGET1:
return config->target1Rel ? R_PC : R_ABS;
case R_ARM_TARGET2:
if (config->target2 == Target2Policy::Rel)
return R_PC;
if (config->target2 == Target2Policy::Abs)
return R_ABS;
return R_GOT_PC;
case R_ARM_TLS_GD32:
return R_TLSGD_PC;
case R_ARM_TLS_LDM32:
return R_TLSLD_PC;
case R_ARM_TLS_LDO32:
return R_DTPREL;
case R_ARM_BASE_PREL:
return R_GOTONLY_PC;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVT_PREL:
case R_ARM_REL32:
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVT_PREL:
return R_PC;
case R_ARM_ALU_PC_G0:
case R_ARM_ALU_PC_G0_NC:
case R_ARM_ALU_PC_G1:
case R_ARM_ALU_PC_G1_NC:
case R_ARM_ALU_PC_G2:
case R_ARM_LDR_PC_G0:
case R_ARM_LDR_PC_G1:
case R_ARM_LDR_PC_G2:
case R_ARM_LDRS_PC_G0:
case R_ARM_LDRS_PC_G1:
case R_ARM_LDRS_PC_G2:
case R_ARM_THM_ALU_PREL_11_0:
case R_ARM_THM_PC8:
case R_ARM_THM_PC12:
return R_ARM_PCA;
case R_ARM_MOVW_BREL_NC:
case R_ARM_MOVW_BREL:
case R_ARM_MOVT_BREL:
case R_ARM_THM_MOVW_BREL_NC:
case R_ARM_THM_MOVW_BREL:
case R_ARM_THM_MOVT_BREL:
return R_ARM_SBREL;
case R_ARM_NONE:
return R_NONE;
case R_ARM_TLS_LE32:
return R_TPREL;
case R_ARM_V4BX:
return R_NONE;
default:
error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) +
") against symbol " + toString(s));
return R_NONE;
}
}
RelType ARM::getDynRel(RelType type) const {
if ((type == R_ARM_ABS32) || (type == R_ARM_TARGET1 && !config->target1Rel))
return R_ARM_ABS32;
return R_ARM_NONE;
}
void ARM::writeGotPlt(uint8_t *buf, const Symbol &) const {
write32(buf, in.plt->getVA());
}
void ARM::writeIgotPlt(uint8_t *buf, const Symbol &s) const {
write32(buf, s.getVA());
}
static void writePltHeaderLong(uint8_t *buf) {
write32(buf + 0, 0xe52de004);
write32(buf + 4, 0xe59fe004);
write32(buf + 8, 0xe08fe00e);
write32(buf + 12, 0xe5bef008);
write32(buf + 16, 0x00000000);
write32(buf + 20, 0xd4d4d4d4);
write32(buf + 24, 0xd4d4d4d4);
write32(buf + 28, 0xd4d4d4d4);
uint64_t gotPlt = in.gotPlt->getVA();
uint64_t l1 = in.plt->getVA() + 8;
write32(buf + 16, gotPlt - l1 - 8);
}
static bool useThumbPLTs() {
return config->armHasThumb2ISA && !config->armHasArmISA;
}
void ARM::writePltHeader(uint8_t *buf) const {
if (useThumbPLTs()) {
uint64_t offset = in.gotPlt->getVA() - in.plt->getVA() - 16;
assert(llvm::isUInt<32>(offset) && "This should always fit into a 32-bit offset");
write16(buf + 0, 0xb500);
write16(buf + 2, 0xf8df);
write16(buf + 4, 0xe008);
write16(buf + 6, 0x44fe);
write16(buf + 8, 0xf85e);
write16(buf + 10, 0xff08);
write32(buf + 12, offset);
memcpy(buf + 16, trapInstr.data(), 4);
memcpy(buf + 20, trapInstr.data(), 4);
memcpy(buf + 24, trapInstr.data(), 4);
memcpy(buf + 28, trapInstr.data(), 4);
} else {
const uint32_t pltData[] = {
0xe52de004,
0xe28fe600,
0xe28eea00,
0xe5bef000,
};
uint64_t offset = in.gotPlt->getVA() - in.plt->getVA() - 4;
if (!llvm::isUInt<27>(offset)) {
writePltHeaderLong(buf);
return;
}
write32(buf + 0, pltData[0]);
write32(buf + 4, pltData[1] | ((offset >> 20) & 0xff));
write32(buf + 8, pltData[2] | ((offset >> 12) & 0xff));
write32(buf + 12, pltData[3] | (offset & 0xfff));
memcpy(buf + 16, trapInstr.data(), 4);
memcpy(buf + 20, trapInstr.data(), 4);
memcpy(buf + 24, trapInstr.data(), 4);
memcpy(buf + 28, trapInstr.data(), 4);
}
}
void ARM::addPltHeaderSymbols(InputSection &isec) const {
if (useThumbPLTs()) {
addSyntheticLocal("$t", STT_NOTYPE, 0, 0, isec);
addSyntheticLocal("$d", STT_NOTYPE, 12, 0, isec);
} else {
addSyntheticLocal("$a", STT_NOTYPE, 0, 0, isec);
addSyntheticLocal("$d", STT_NOTYPE, 16, 0, isec);
}
}
static void writePltLong(uint8_t *buf, uint64_t gotPltEntryAddr,
uint64_t pltEntryAddr) {
write32(buf + 0, 0xe59fc004);
write32(buf + 4, 0xe08cc00f);
write32(buf + 8, 0xe59cf000);
write32(buf + 12, 0x00000000);
uint64_t l1 = pltEntryAddr + 4;
write32(buf + 12, gotPltEntryAddr - l1 - 8);
}
void ARM::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const {
if (!useThumbPLTs()) {
uint64_t offset = sym.getGotPltVA() - pltEntryAddr - 8;
const uint32_t pltData[] = {
0xe28fc600,
0xe28cca00,
0xe5bcf000,
};
if (!llvm::isUInt<27>(offset)) {
writePltLong(buf, sym.getGotPltVA(), pltEntryAddr);
return;
}
write32(buf + 0, pltData[0] | ((offset >> 20) & 0xff));
write32(buf + 4, pltData[1] | ((offset >> 12) & 0xff));
write32(buf + 8, pltData[2] | (offset & 0xfff));
memcpy(buf + 12, trapInstr.data(), 4);
} else {
uint64_t offset = sym.getGotPltVA() - pltEntryAddr - 12;
assert(llvm::isUInt<32>(offset) && "This should always fit into a 32-bit offset");
write16(buf + 2, 0x0c00);
relocateNoSym(buf, R_ARM_THM_MOVW_ABS_NC, offset);
write16(buf + 6, 0x0c00);
relocateNoSym(buf + 4, R_ARM_THM_MOVT_ABS, offset);
write16(buf + 8, 0x44fc);
write16(buf + 10, 0xf8dc);
write16(buf + 12, 0xf000);
write16(buf + 14, 0xe7fc);
}
}
void ARM::addPltSymbols(InputSection &isec, uint64_t off) const {
if (useThumbPLTs()) {
addSyntheticLocal("$t", STT_NOTYPE, off, 0, isec);
} else {
addSyntheticLocal("$a", STT_NOTYPE, off, 0, isec);
addSyntheticLocal("$d", STT_NOTYPE, off + 12, 0, isec);
}
}
bool ARM::needsThunk(RelExpr expr, RelType type, const InputFile *file,
uint64_t branchAddr, const Symbol &s,
int64_t a) const {
if (s.isUndefined() && !s.isInPlt())
return false;
switch (type) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
assert(!useThumbPLTs() &&
"If the source is ARM, we should not need Thumb PLTs");
if (s.isFunc() && expr == R_PC && (s.getVA() & 1))
return true;
[[fallthrough]];
case R_ARM_CALL: {
uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA() : s.getVA();
return !inBranchRange(type, branchAddr, dst + a) ||
(!config->armHasBlx && (s.getVA() & 1));
}
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
if ((expr == R_PLT_PC && !useThumbPLTs()) ||
(s.isFunc() && (s.getVA() & 1) == 0))
return true;
[[fallthrough]];
case R_ARM_THM_CALL: {
uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA() : s.getVA();
return !inBranchRange(type, branchAddr, dst + a) ||
(!config->armHasBlx && (s.getVA() & 1) == 0);;
}
}
return false;
}
uint32_t ARM::getThunkSectionSpacing() const {
return (config->armJ1J2BranchEncoding) ? 0x1000000 - 0x30000
: 0x400000 - 0x7500;
}
bool ARM::inBranchRange(RelType type, uint64_t src, uint64_t dst) const {
if ((dst & 0x1) == 0)
src &= ~0x3;
else
dst &= ~0x1;
int64_t offset = dst - src;
switch (type) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_CALL:
return llvm::isInt<26>(offset);
case R_ARM_THM_JUMP19:
return llvm::isInt<21>(offset);
case R_ARM_THM_JUMP24:
case R_ARM_THM_CALL:
return config->armJ1J2BranchEncoding ? llvm::isInt<25>(offset)
: llvm::isInt<23>(offset);
default:
return true;
}
}
static void stateChangeWarning(uint8_t *loc, RelType relt, const Symbol &s) {
assert(!s.isFunc());
const ErrorPlace place = getErrorPlace(loc);
std::string hint;
if (!place.srcLoc.empty())
hint = "; " + place.srcLoc;
if (s.isSection()) {
warn(place.loc + "branch and link relocation: " + toString(relt) +
" to STT_SECTION symbol " + cast<Defined>(s).section->name +
" ; interworking not performed" + hint);
} else {
warn(getErrorLocation(loc) + "branch and link relocation: " +
toString(relt) + " to non STT_FUNC symbol: " + s.getName() +
" interworking not performed; consider using directive '.type " +
s.getName() +
", %function' to give symbol type STT_FUNC if interworking between "
"ARM and Thumb is required" +
hint);
}
}
static uint32_t rotr32(uint32_t val, uint32_t amt) {
assert(amt < 32 && "Invalid rotate amount");
return (val >> amt) | (val << ((32 - amt) & 31));
}
static std::pair<uint32_t, uint32_t> getRemAndLZForGroup(unsigned group,
uint32_t val) {
uint32_t rem, lz;
do {
lz = llvm::countl_zero(val) & ~1;
rem = val;
if (lz == 32)
break;
val &= 0xffffff >> lz;
} while (group--);
return {rem, lz};
}
static void encodeAluGroup(uint8_t *loc, const Relocation &rel, uint64_t val,
int group, bool check) {
uint32_t opcode = 0x00800000;
if (val >> 63) {
opcode = 0x00400000;
val = -val;
}
uint32_t imm, lz;
std::tie(imm, lz) = getRemAndLZForGroup(group, val);
uint32_t rot = 0;
if (lz < 24) {
imm = rotr32(imm, 24 - lz);
rot = (lz + 8) << 7;
}
if (check && imm > 0xff)
error(getErrorLocation(loc) + "unencodeable immediate " + Twine(val).str() +
" for relocation " + toString(rel.type));
write32(loc, (read32(loc) & 0xff3ff000) | opcode | rot | (imm & 0xff));
}
static void encodeLdrGroup(uint8_t *loc, const Relocation &rel, uint64_t val,
int group) {
if (rel.sym->isFunc())
val &= ~0x1;
uint32_t opcode = 0x00800000;
if (val >> 63) {
opcode = 0x0;
val = -val;
}
uint32_t imm = getRemAndLZForGroup(group, val).first;
checkUInt(loc, imm, 12, rel);
write32(loc, (read32(loc) & 0xff7ff000) | opcode | imm);
}
static void encodeLdrsGroup(uint8_t *loc, const Relocation &rel, uint64_t val,
int group) {
if (rel.sym->isFunc())
val &= ~0x1;
uint32_t opcode = 0x00800000;
if (val >> 63) {
opcode = 0x0;
val = -val;
}
uint32_t imm = getRemAndLZForGroup(group, val).first;
checkUInt(loc, imm, 8, rel);
write32(loc, (read32(loc) & 0xff7ff0f0) | opcode | ((imm & 0xf0) << 4) |
(imm & 0xf));
}
void ARM::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
switch (rel.type) {
case R_ARM_ABS32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_BREL:
case R_ARM_GOT_PREL:
case R_ARM_REL32:
case R_ARM_RELATIVE:
case R_ARM_SBREL32:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
case R_ARM_TLS_GD32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_TPOFF32:
case R_ARM_TLS_DTPOFF32:
write32(loc, val);
break;
case R_ARM_PREL31:
checkInt(loc, val, 31, rel);
write32(loc, (read32(loc) & 0x80000000) | (val & ~0x80000000));
break;
case R_ARM_CALL: {
assert(rel.sym);
bool bit0Thumb = val & 1;
bool isBlx = (read32(loc) & 0xfe000000) == 0xfa000000;
if (!rel.sym->isFunc() && isBlx != bit0Thumb)
stateChangeWarning(loc, rel.type, *rel.sym);
if (rel.sym->isFunc() ? bit0Thumb : isBlx) {
checkInt(loc, val, 26, rel);
write32(loc, 0xfa000000 |
((val & 2) << 23) |
((val >> 2) & 0x00ffffff));
break;
}
write32(loc, 0xeb000000 | (read32(loc) & 0x00ffffff));
}
[[fallthrough]];
case R_ARM_JUMP24:
case R_ARM_PC24:
case R_ARM_PLT32:
checkInt(loc, val, 26, rel);
write32(loc, (read32(loc) & ~0x00ffffff) | ((val >> 2) & 0x00ffffff));
break;
case R_ARM_THM_JUMP8:
checkInt(loc, val, 9, rel);
write16(loc, (read32(loc) & 0xff00) | ((val >> 1) & 0x00ff));
break;
case R_ARM_THM_JUMP11:
checkInt(loc, val, 12, rel);
write16(loc, (read32(loc) & 0xf800) | ((val >> 1) & 0x07ff));
break;
case R_ARM_THM_JUMP19:
checkInt(loc, val, 21, rel);
write16(loc,
(read16(loc) & 0xfbc0) |
((val >> 10) & 0x0400) |
((val >> 12) & 0x003f));
write16(loc + 2,
0x8000 |
((val >> 8) & 0x0800) |
((val >> 5) & 0x2000) |
((val >> 1) & 0x07ff));
break;
case R_ARM_THM_CALL: {
assert(rel.sym);
bool bit0Thumb = val & 1;
bool useThumb = bit0Thumb || useThumbPLTs();
bool isBlx = (read16(loc + 2) & 0x1000) == 0;
if (!rel.sym->isFunc() && !rel.sym->isInPlt() && isBlx == useThumb)
stateChangeWarning(loc, rel.type, *rel.sym);
if ((rel.sym->isFunc() || rel.sym->isInPlt()) ? !useThumb : isBlx) {
val = alignTo(val, 4);
write16(loc + 2, read16(loc + 2) & ~0x1000);
} else {
write16(loc + 2, (read16(loc + 2) & ~0x1000) | 1 << 12);
}
if (!config->armJ1J2BranchEncoding) {
checkInt(loc, val, 23, rel);
write16(loc,
0xf000 |
((val >> 12) & 0x07ff));
write16(loc + 2,
(read16(loc + 2) & 0xd000) |
0x2800 |
((val >> 1) & 0x07ff));
break;
}
}
[[fallthrough]];
case R_ARM_THM_JUMP24:
checkInt(loc, val, 25, rel);
write16(loc,
0xf000 |
((val >> 14) & 0x0400) |
((val >> 12) & 0x03ff));
write16(loc + 2,
(read16(loc + 2) & 0xd000) |
(((~(val >> 10)) ^ (val >> 11)) & 0x2000) |
(((~(val >> 11)) ^ (val >> 13)) & 0x0800) |
((val >> 1) & 0x07ff));
break;
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_BREL_NC:
write32(loc, (read32(loc) & ~0x000f0fff) | ((val & 0xf000) << 4) |
(val & 0x0fff));
break;
case R_ARM_MOVT_ABS:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_BREL:
write32(loc, (read32(loc) & ~0x000f0fff) |
(((val >> 16) & 0xf000) << 4) | ((val >> 16) & 0xfff));
break;
case R_ARM_THM_MOVT_ABS:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_BREL:
write16(loc,
0xf2c0 |
((val >> 17) & 0x0400) |
((val >> 28) & 0x000f));
write16(loc + 2,
(read16(loc + 2) & 0x8f00) |
((val >> 12) & 0x7000) |
((val >> 16) & 0x00ff));
break;
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_BREL_NC:
write16(loc,
0xf240 |
((val >> 1) & 0x0400) |
((val >> 12) & 0x000f));
write16(loc + 2,
(read16(loc + 2) & 0x8f00) |
((val << 4) & 0x7000) |
(val & 0x00ff));
break;
case R_ARM_THM_ALU_ABS_G3:
write16(loc, (read16(loc) &~ 0x00ff) | ((val >> 24) & 0x00ff));
break;
case R_ARM_THM_ALU_ABS_G2_NC:
write16(loc, (read16(loc) &~ 0x00ff) | ((val >> 16) & 0x00ff));
break;
case R_ARM_THM_ALU_ABS_G1_NC:
write16(loc, (read16(loc) &~ 0x00ff) | ((val >> 8) & 0x00ff));
break;
case R_ARM_THM_ALU_ABS_G0_NC:
write16(loc, (read16(loc) &~ 0x00ff) | (val & 0x00ff));
break;
case R_ARM_ALU_PC_G0:
encodeAluGroup(loc, rel, val, 0, true);
break;
case R_ARM_ALU_PC_G0_NC:
encodeAluGroup(loc, rel, val, 0, false);
break;
case R_ARM_ALU_PC_G1:
encodeAluGroup(loc, rel, val, 1, true);
break;
case R_ARM_ALU_PC_G1_NC:
encodeAluGroup(loc, rel, val, 1, false);
break;
case R_ARM_ALU_PC_G2:
encodeAluGroup(loc, rel, val, 2, true);
break;
case R_ARM_LDR_PC_G0:
encodeLdrGroup(loc, rel, val, 0);
break;
case R_ARM_LDR_PC_G1:
encodeLdrGroup(loc, rel, val, 1);
break;
case R_ARM_LDR_PC_G2:
encodeLdrGroup(loc, rel, val, 2);
break;
case R_ARM_LDRS_PC_G0:
encodeLdrsGroup(loc, rel, val, 0);
break;
case R_ARM_LDRS_PC_G1:
encodeLdrsGroup(loc, rel, val, 1);
break;
case R_ARM_LDRS_PC_G2:
encodeLdrsGroup(loc, rel, val, 2);
break;
case R_ARM_THM_ALU_PREL_11_0: {
int64_t imm = val;
uint16_t sub = 0;
if (imm < 0) {
imm = -imm;
sub = 0x00a0;
}
checkUInt(loc, imm, 12, rel);
write16(loc, (read16(loc) & 0xfb0f) | sub | (imm & 0x800) >> 1);
write16(loc + 2,
(read16(loc + 2) & 0x8f00) | (imm & 0x700) << 4 | (imm & 0xff));
break;
}
case R_ARM_THM_PC8:
if (rel.sym->isFunc())
val &= ~0x1;
checkUInt(loc, val, 10, rel);
checkAlignment(loc, val, 4, rel);
write16(loc, (read16(loc) & 0xff00) | (val & 0x3fc) >> 2);
break;
case R_ARM_THM_PC12: {
if (rel.sym->isFunc())
val &= ~0x1;
int64_t imm12 = val;
uint16_t u = 0x0080;
if (imm12 < 0) {
imm12 = -imm12;
u = 0;
}
checkUInt(loc, imm12, 12, rel);
write16(loc, read16(loc) | u);
write16(loc + 2, (read16(loc + 2) & 0xf000) | imm12);
break;
}
default:
llvm_unreachable("unknown relocation");
}
}
int64_t ARM::getImplicitAddend(const uint8_t *buf, RelType type) const {
switch (type) {
default:
internalLinkerError(getErrorLocation(buf),
"cannot read addend for relocation " + toString(type));
return 0;
case R_ARM_ABS32:
case R_ARM_BASE_PREL:
case R_ARM_GLOB_DAT:
case R_ARM_GOTOFF32:
case R_ARM_GOT_BREL:
case R_ARM_GOT_PREL:
case R_ARM_IRELATIVE:
case R_ARM_REL32:
case R_ARM_RELATIVE:
case R_ARM_SBREL32:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
case R_ARM_TLS_DTPMOD32:
case R_ARM_TLS_DTPOFF32:
case R_ARM_TLS_GD32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_TPOFF32:
return SignExtend64<32>(read32(buf));
case R_ARM_PREL31:
return SignExtend64<31>(read32(buf));
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PC24:
case R_ARM_PLT32:
return SignExtend64<26>(read32(buf) << 2);
case R_ARM_THM_JUMP8:
return SignExtend64<9>(read16(buf) << 1);
case R_ARM_THM_JUMP11:
return SignExtend64<12>(read16(buf) << 1);
case R_ARM_THM_JUMP19: {
uint16_t hi = read16(buf);
uint16_t lo = read16(buf + 2);
return SignExtend64<20>(((hi & 0x0400) << 10) |
((lo & 0x0800) << 8) |
((lo & 0x2000) << 5) |
((hi & 0x003f) << 12) |
((lo & 0x07ff) << 1));
}
case R_ARM_THM_CALL:
if (!config->armJ1J2BranchEncoding) {
uint16_t hi = read16(buf);
uint16_t lo = read16(buf + 2);
return SignExtend64<22>(((hi & 0x7ff) << 12) |
((lo & 0x7ff) << 1));
break;
}
[[fallthrough]];
case R_ARM_THM_JUMP24: {
uint16_t hi = read16(buf);
uint16_t lo = read16(buf + 2);
return SignExtend64<24>(((hi & 0x0400) << 14) |
(~((lo ^ (hi << 3)) << 10) & 0x00800000) |
(~((lo ^ (hi << 1)) << 11) & 0x00400000) |
((hi & 0x003ff) << 12) |
((lo & 0x007ff) << 1));
}
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_ABS:
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVW_BREL_NC:
case R_ARM_MOVT_BREL: {
uint64_t val = read32(buf) & 0x000f0fff;
return SignExtend64<16>(((val & 0x000f0000) >> 4) | (val & 0x00fff));
}
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_ABS:
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVW_BREL_NC:
case R_ARM_THM_MOVT_BREL: {
uint16_t hi = read16(buf);
uint16_t lo = read16(buf + 2);
return SignExtend64<16>(((hi & 0x000f) << 12) |
((hi & 0x0400) << 1) |
((lo & 0x7000) >> 4) |
(lo & 0x00ff));
}
case R_ARM_THM_ALU_ABS_G0_NC:
case R_ARM_THM_ALU_ABS_G1_NC:
case R_ARM_THM_ALU_ABS_G2_NC:
case R_ARM_THM_ALU_ABS_G3:
return read16(buf) & 0xff;
case R_ARM_ALU_PC_G0:
case R_ARM_ALU_PC_G0_NC:
case R_ARM_ALU_PC_G1:
case R_ARM_ALU_PC_G1_NC:
case R_ARM_ALU_PC_G2: {
uint32_t instr = read32(buf);
uint32_t val = rotr32(instr & 0xff, ((instr & 0xf00) >> 8) * 2);
return (instr & 0x00400000) ? -val : val;
}
case R_ARM_LDR_PC_G0:
case R_ARM_LDR_PC_G1:
case R_ARM_LDR_PC_G2: {
bool u = read32(buf) & 0x00800000;
uint32_t imm12 = read32(buf) & 0xfff;
return u ? imm12 : -imm12;
}
case R_ARM_LDRS_PC_G0:
case R_ARM_LDRS_PC_G1:
case R_ARM_LDRS_PC_G2: {
uint32_t opcode = read32(buf);
bool u = opcode & 0x00800000;
uint32_t imm4l = opcode & 0xf;
uint32_t imm4h = (opcode & 0xf00) >> 4;
return u ? (imm4h | imm4l) : -(imm4h | imm4l);
}
case R_ARM_THM_ALU_PREL_11_0: {
uint16_t hi = read16(buf);
uint16_t lo = read16(buf + 2);
uint64_t imm = (hi & 0x0400) << 1 |
(lo & 0x7000) >> 4 |
(lo & 0x00ff);
return (hi & 0x00f0) ? -imm : imm;
}
case R_ARM_THM_PC8:
return ((((read16(buf) & 0xff) << 2) + 4) & 0x3ff) - 4;
case R_ARM_THM_PC12: {
bool u = read16(buf) & 0x0080;
uint64_t imm12 = read16(buf + 2) & 0x0fff;
return u ? imm12 : -imm12;
}
case R_ARM_NONE:
case R_ARM_V4BX:
case R_ARM_JUMP_SLOT:
return 0;
}
}
static bool isArmMapSymbol(const Symbol *b) {
return b->getName() == "$a" || b->getName().starts_with("$a.");
}
static bool isThumbMapSymbol(const Symbol *s) {
return s->getName() == "$t" || s->getName().starts_with("$t.");
}
static bool isDataMapSymbol(const Symbol *b) {
return b->getName() == "$d" || b->getName().starts_with("$d.");
}
void elf::sortArmMappingSymbols() {
for (auto &kv : sectionMap) {
SmallVector<const Defined *, 0> &mapSyms = kv.second;
llvm::stable_sort(mapSyms, [](const Defined *a, const Defined *b) {
return a->value < b->value;
});
}
}
void elf::addArmInputSectionMappingSymbols() {
for (ELFFileBase *file : ctx.objectFiles) {
for (Symbol *sym : file->getLocalSymbols()) {
auto *def = dyn_cast<Defined>(sym);
if (!def)
continue;
if (!isArmMapSymbol(def) && !isDataMapSymbol(def) &&
!isThumbMapSymbol(def))
continue;
if (auto *sec = cast_if_present<InputSection>(def->section))
if (sec->flags & SHF_EXECINSTR)
sectionMap[sec].push_back(def);
}
}
}
void elf::addArmSyntheticSectionMappingSymbol(Defined *sym) {
if (!isArmMapSymbol(sym) && !isDataMapSymbol(sym) && !isThumbMapSymbol(sym))
return;
if (auto *sec = cast_if_present<InputSection>(sym->section))
if (sec->flags & SHF_EXECINSTR)
sectionMap[sec].push_back(sym);
}
static void toLittleEndianInstructions(uint8_t *buf, uint64_t start,
uint64_t end, uint64_t width) {
CodeState curState = static_cast<CodeState>(width);
if (curState == CodeState::Arm)
for (uint64_t i = start; i < end; i += width)
write32le(buf + i, read32(buf + i));
if (curState == CodeState::Thumb)
for (uint64_t i = start; i < end; i += width)
write16le(buf + i, read16(buf + i));
}
void elf::convertArmInstructionstoBE8(InputSection *sec, uint8_t *buf) {
if (!sectionMap.contains(sec))
return;
SmallVector<const Defined *, 0> &mapSyms = sectionMap[sec];
if (mapSyms.empty())
return;
CodeState curState = CodeState::Data;
uint64_t start = 0, width = 0, size = sec->getSize();
for (auto &msym : mapSyms) {
CodeState newState = CodeState::Data;
if (isThumbMapSymbol(msym))
newState = CodeState::Thumb;
else if (isArmMapSymbol(msym))
newState = CodeState::Arm;
if (newState == curState)
continue;
if (curState != CodeState::Data) {
width = static_cast<uint64_t>(curState);
toLittleEndianInstructions(buf, start, msym->value, width);
}
start = msym->value;
curState = newState;
}
if (curState != CodeState::Data) {
width = static_cast<uint64_t>(curState);
toLittleEndianInstructions(buf, start, size, width);
}
}
template <class ELFT> void ObjFile<ELFT>::importCmseSymbols() {
ArrayRef<Elf_Sym> eSyms = getELFSyms<ELFT>();
for (size_t i = 1, end = firstGlobal; i != end; ++i) {
errorOrWarn("CMSE symbol '" + CHECK(eSyms[i].getName(stringTable), this) +
"' in import library '" + toString(this) + "' is not global");
}
for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
const Elf_Sym &eSym = eSyms[i];
Defined *sym = reinterpret_cast<Defined *>(make<SymbolUnion>());
memset(sym, 0, sizeof(Symbol));
sym->setName(CHECK(eSyms[i].getName(stringTable), this));
sym->value = eSym.st_value;
sym->size = eSym.st_size;
sym->type = eSym.getType();
sym->binding = eSym.getBinding();
sym->stOther = eSym.st_other;
if (eSym.st_shndx != SHN_ABS) {
error("CMSE symbol '" + sym->getName() + "' in import library '" +
toString(this) + "' is not absolute");
continue;
}
if (!(eSym.st_value & 1) || (eSym.getType() != STT_FUNC)) {
error("CMSE symbol '" + sym->getName() + "' in import library '" +
toString(this) + "' is not a Thumb function definition");
continue;
}
if (symtab.cmseImportLib.count(sym->getName())) {
error("CMSE symbol '" + sym->getName() +
"' is multiply defined in import library '" + toString(this) + "'");
continue;
}
if (eSym.st_size != ACLESESYM_SIZE) {
warn("CMSE symbol '" + sym->getName() + "' in import library '" +
toString(this) + "' does not have correct size of " +
Twine(ACLESESYM_SIZE) + " bytes");
}
symtab.cmseImportLib[sym->getName()] = sym;
}
}
static std::string checkCmseSymAttributes(Symbol *acleSeSym, Symbol *sym) {
auto check = [](Symbol *s, StringRef type) -> std::optional<std::string> {
auto d = dyn_cast_or_null<Defined>(s);
if (!(d && d->isFunc() && (d->value & 1)))
return (Twine(toString(s->file)) + ": cmse " + type + " symbol '" +
s->getName() + "' is not a Thumb function definition")
.str();
if (!d->section)
return (Twine(toString(s->file)) + ": cmse " + type + " symbol '" +
s->getName() + "' cannot be an absolute symbol")
.str();
return std::nullopt;
};
for (auto [sym, type] :
{std::make_pair(acleSeSym, "special"), std::make_pair(sym, "entry")})
if (auto err = check(sym, type))
return *err;
return "";
}
void elf::processArmCmseSymbols() {
if (!config->cmseImplib)
return;
for (Symbol *acleSeSym : symtab.getSymbols()) {
if (!acleSeSym->getName().starts_with(ACLESESYM_PREFIX))
continue;
if (!config->armCMSESupport) {
error("CMSE is only supported by ARMv8-M architecture or later");
config->cmseImplib = false;
break;
}
StringRef name = acleSeSym->getName().substr(std::strlen(ACLESESYM_PREFIX));
Symbol *sym = symtab.find(name);
if (!sym) {
error(toString(acleSeSym->file) + ": cmse special symbol '" +
acleSeSym->getName() +
"' detected, but no associated entry function definition '" + name +
"' with external linkage found");
continue;
}
std::string errMsg = checkCmseSymAttributes(acleSeSym, sym);
if (!errMsg.empty()) {
error(errMsg);
continue;
}
symtab.cmseSymMap[name] = {acleSeSym, sym};
}
parallelForEach(ctx.objectFiles, [&](InputFile *file) {
MutableArrayRef<Symbol *> syms = file->getMutableSymbols();
for (size_t i = 0, e = syms.size(); i != e; ++i) {
StringRef symName = syms[i]->getName();
if (symtab.cmseSymMap.count(symName))
syms[i] = symtab.cmseSymMap[symName].acleSeSym;
}
});
}
class elf::ArmCmseSGVeneer {
public:
ArmCmseSGVeneer(Symbol *sym, Symbol *acleSeSym,
std::optional<uint64_t> addr = std::nullopt)
: sym(sym), acleSeSym(acleSeSym), entAddr{addr} {}
static const size_t size{ACLESESYM_SIZE};
const std::optional<uint64_t> getAddr() const { return entAddr; };
Symbol *sym;
Symbol *acleSeSym;
uint64_t offset = 0;
private:
const std::optional<uint64_t> entAddr;
};
ArmCmseSGSection::ArmCmseSGSection()
: SyntheticSection(llvm::ELF::SHF_ALLOC | llvm::ELF::SHF_EXECINSTR,
llvm::ELF::SHT_PROGBITS,
32, ".gnu.sgstubs") {
entsize = ACLESESYM_SIZE;
for (auto &[_, sym] : symtab.cmseImportLib) {
if (impLibMaxAddr <= sym->value)
impLibMaxAddr = sym->value + sym->size;
}
if (symtab.cmseSymMap.empty())
return;
addMappingSymbol();
for (auto &[_, entryFunc] : symtab.cmseSymMap)
addSGVeneer(cast<Defined>(entryFunc.acleSeSym),
cast<Defined>(entryFunc.sym));
for (auto &[_, sym] : symtab.cmseImportLib) {
if (!symtab.inCMSEOutImpLib.count(sym->getName()))
warn("entry function '" + sym->getName() +
"' from CMSE import library is not present in secure application");
}
if (!symtab.cmseImportLib.empty() && config->cmseOutputLib.empty()) {
for (auto &[_, entryFunc] : symtab.cmseSymMap) {
Symbol *sym = entryFunc.sym;
if (!symtab.inCMSEOutImpLib.count(sym->getName()))
warn("new entry function '" + sym->getName() +
"' introduced but no output import library specified");
}
}
}
void ArmCmseSGSection::addSGVeneer(Symbol *acleSeSym, Symbol *sym) {
entries.emplace_back(acleSeSym, sym);
if (symtab.cmseImportLib.count(sym->getName()))
symtab.inCMSEOutImpLib[sym->getName()] = true;
if (acleSeSym->file != sym->file ||
cast<Defined>(*acleSeSym).value != cast<Defined>(*sym).value)
return;
ArmCmseSGVeneer *ss = nullptr;
if (symtab.cmseImportLib.count(sym->getName())) {
Defined *impSym = symtab.cmseImportLib[sym->getName()];
ss = make<ArmCmseSGVeneer>(sym, acleSeSym, impSym->value);
} else {
ss = make<ArmCmseSGVeneer>(sym, acleSeSym);
++newEntries;
}
sgVeneers.emplace_back(ss);
}
void ArmCmseSGSection::writeTo(uint8_t *buf) {
for (ArmCmseSGVeneer *s : sgVeneers) {
uint8_t *p = buf + s->offset;
write16(p + 0, 0xe97f);
write16(p + 2, 0xe97f);
write16(p + 4, 0xf000);
write16(p + 6, 0xb000);
target->relocateNoSym(p + 4, R_ARM_THM_JUMP24,
s->acleSeSym->getVA() -
(getVA() + s->offset + s->size));
}
}
void ArmCmseSGSection::addMappingSymbol() {
addSyntheticLocal("$t", STT_NOTYPE, 0, 0, *this);
}
size_t ArmCmseSGSection::getSize() const {
if (sgVeneers.empty())
return (impLibMaxAddr ? impLibMaxAddr - getVA() : 0) + newEntries * entsize;
return entries.size() * entsize;
}
void ArmCmseSGSection::finalizeContents() {
if (sgVeneers.empty())
return;
auto it =
std::stable_partition(sgVeneers.begin(), sgVeneers.end(),
[](auto *i) { return i->getAddr().has_value(); });
std::sort(sgVeneers.begin(), it, [](auto *a, auto *b) {
return a->getAddr().value() < b->getAddr().value();
});
uint64_t addr = (*sgVeneers.begin())->getAddr().has_value()
? (*sgVeneers.begin())->getAddr().value()
: getVA();
if ((getVA() & ~1) != (addr & ~1)) {
error("start address of '.gnu.sgstubs' is different from previous link");
return;
}
for (size_t i = 0; i < sgVeneers.size(); ++i) {
ArmCmseSGVeneer *s = sgVeneers[i];
s->offset = i * s->size;
Defined(file, StringRef(), s->sym->binding, s->sym->stOther, s->sym->type,
s->offset | 1, s->size, this)
.overwrite(*s->sym);
}
}
template <typename ELFT> void elf::writeARMCmseImportLib() {
StringTableSection *shstrtab =
make<StringTableSection>(".shstrtab", false);
StringTableSection *strtab =
make<StringTableSection>(".strtab", false);
SymbolTableBaseSection *impSymTab = make<SymbolTableSection<ELFT>>(*strtab);
SmallVector<std::pair<OutputSection *, SyntheticSection *>, 0> osIsPairs;
osIsPairs.emplace_back(make<OutputSection>(strtab->name, 0, 0), strtab);
osIsPairs.emplace_back(make<OutputSection>(impSymTab->name, 0, 0), impSymTab);
osIsPairs.emplace_back(make<OutputSection>(shstrtab->name, 0, 0), shstrtab);
std::sort(symtab.cmseSymMap.begin(), symtab.cmseSymMap.end(),
[](const auto &a, const auto &b) -> bool {
return a.second.sym->getVA() < b.second.sym->getVA();
});
for (auto &p : symtab.cmseSymMap) {
Defined *d = cast<Defined>(p.second.sym);
impSymTab->addSymbol(makeDefined(
ctx.internalFile, d->getName(), d->computeBinding(),
0, STT_FUNC, d->getVA(), d->getSize(), nullptr));
}
size_t idx = 0;
uint64_t off = sizeof(typename ELFT::Ehdr);
for (auto &[osec, isec] : osIsPairs) {
osec->sectionIndex = ++idx;
osec->recordSection(isec);
osec->finalizeInputSections();
osec->shName = shstrtab->addString(osec->name);
osec->size = isec->getSize();
isec->finalizeContents();
osec->offset = alignToPowerOf2(off, osec->addralign);
off = osec->offset + osec->size;
}
const uint64_t sectionHeaderOff = alignToPowerOf2(off, config->wordsize);
const auto shnum = osIsPairs.size() + 1;
const uint64_t fileSize =
sectionHeaderOff + shnum * sizeof(typename ELFT::Shdr);
const unsigned flags =
config->mmapOutputFile ? 0 : (unsigned)FileOutputBuffer::F_no_mmap;
unlinkAsync(config->cmseOutputLib);
Expected<std::unique_ptr<FileOutputBuffer>> bufferOrErr =
FileOutputBuffer::create(config->cmseOutputLib, fileSize, flags);
if (!bufferOrErr) {
error("failed to open " + config->cmseOutputLib + ": " +
llvm::toString(bufferOrErr.takeError()));
return;
}
std::unique_ptr<FileOutputBuffer> &buffer = *bufferOrErr;
uint8_t *const buf = buffer->getBufferStart();
memcpy(buf, "\177ELF", 4);
auto *eHdr = reinterpret_cast<typename ELFT::Ehdr *>(buf);
eHdr->e_type = ET_REL;
eHdr->e_entry = 0;
eHdr->e_shoff = sectionHeaderOff;
eHdr->e_ident[EI_CLASS] = ELFCLASS32;
eHdr->e_ident[EI_DATA] = config->isLE ? ELFDATA2LSB : ELFDATA2MSB;
eHdr->e_ident[EI_VERSION] = EV_CURRENT;
eHdr->e_ident[EI_OSABI] = config->osabi;
eHdr->e_ident[EI_ABIVERSION] = 0;
eHdr->e_machine = EM_ARM;
eHdr->e_version = EV_CURRENT;
eHdr->e_flags = config->eflags;
eHdr->e_ehsize = sizeof(typename ELFT::Ehdr);
eHdr->e_phnum = 0;
eHdr->e_shentsize = sizeof(typename ELFT::Shdr);
eHdr->e_phoff = 0;
eHdr->e_phentsize = 0;
eHdr->e_shnum = shnum;
eHdr->e_shstrndx = shstrtab->getParent()->sectionIndex;
auto *sHdrs = reinterpret_cast<typename ELFT::Shdr *>(buf + eHdr->e_shoff);
for (auto &[osec, _] : osIsPairs)
osec->template writeHeaderTo<ELFT>(++sHdrs);
{
parallel::TaskGroup tg;
for (auto &[osec, _] : osIsPairs)
osec->template writeTo<ELFT>(buf + osec->offset, tg);
}
if (auto e = buffer->commit())
fatal("failed to write output '" + buffer->getPath() +
"': " + toString(std::move(e)));
}
TargetInfo *elf::getARMTargetInfo() {
static ARM target;
return ⌖
}
template void elf::writeARMCmseImportLib<ELF32LE>();
template void elf::writeARMCmseImportLib<ELF32BE>();
template void elf::writeARMCmseImportLib<ELF64LE>();
template void elf::writeARMCmseImportLib<ELF64BE>();
template void ObjFile<ELF32LE>::importCmseSymbols();
template void ObjFile<ELF32BE>::importCmseSymbols();
template void ObjFile<ELF64LE>::importCmseSymbols();
template void ObjFile<ELF64BE>::importCmseSymbols();