#include "InputFiles.h"
#include "OutputSections.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.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(Ctx &);
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;
DenseMap<InputSection *, SmallVector<const Defined *, 0>> sectionMap;
private:
void encodeAluGroup(uint8_t *loc, const Relocation &rel, uint64_t val,
int group, bool check) const;
};
enum class CodeState { Data = 0, Thumb = 2, Arm = 4 };
}
ARM::ARM(Ctx &ctx) : TargetInfo(ctx) {
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 (ctx.arg.armVFPArgs == ARMVFPArgKind::Base ||
ctx.arg.armVFPArgs == ARMVFPArgKind::Default)
abiFloatType = EF_ARM_ABI_FLOAT_SOFT;
else if (ctx.arg.armVFPArgs == ARMVFPArgKind::VFP)
abiFloatType = EF_ARM_ABI_FLOAT_HARD;
if (!ctx.arg.isLE && ctx.arg.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 RE_ARM_SBREL;
case R_ARM_TARGET1:
return ctx.arg.target1Rel ? R_PC : R_ABS;
case R_ARM_TARGET2:
if (ctx.arg.target2 == Target2Policy::Rel)
return R_PC;
if (ctx.arg.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 RE_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 RE_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:
Err(ctx) << getErrorLoc(ctx, loc) << "unknown relocation (" << type.v
<< ") against symbol " << &s;
return R_NONE;
}
}
RelType ARM::getDynRel(RelType type) const {
if ((type == R_ARM_ABS32) || (type == R_ARM_TARGET1 && !ctx.arg.target1Rel))
return R_ARM_ABS32;
return R_ARM_NONE;
}
void ARM::writeGotPlt(uint8_t *buf, const Symbol &) const {
write32(ctx, buf, ctx.in.plt->getVA());
}
void ARM::writeIgotPlt(uint8_t *buf, const Symbol &s) const {
write32(ctx, buf, s.getVA(ctx));
}
static void writePltHeaderLong(Ctx &ctx, uint8_t *buf) {
write32(ctx, buf + 0, 0xe52de004);
write32(ctx, buf + 4, 0xe59fe004);
write32(ctx, buf + 8, 0xe08fe00e);
write32(ctx, buf + 12, 0xe5bef008);
write32(ctx, buf + 16, 0x00000000);
write32(ctx, buf + 20, 0xd4d4d4d4);
write32(ctx, buf + 24, 0xd4d4d4d4);
write32(ctx, buf + 28, 0xd4d4d4d4);
uint64_t gotPlt = ctx.in.gotPlt->getVA();
uint64_t l1 = ctx.in.plt->getVA() + 8;
write32(ctx, buf + 16, gotPlt - l1 - 8);
}
static bool useThumbPLTs(Ctx &ctx) {
return ctx.arg.armHasThumb2ISA && !ctx.arg.armHasArmISA;
}
void ARM::writePltHeader(uint8_t *buf) const {
if (useThumbPLTs(ctx)) {
uint64_t offset = ctx.in.gotPlt->getVA() - ctx.in.plt->getVA() - 16;
assert(llvm::isUInt<32>(offset) && "This should always fit into a 32-bit offset");
write16(ctx, buf + 0, 0xb500);
write16(ctx, buf + 2, 0xf8df);
write16(ctx, buf + 4, 0xe008);
write16(ctx, buf + 6, 0x44fe);
write16(ctx, buf + 8, 0xf85e);
write16(ctx, buf + 10, 0xff08);
write32(ctx, 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 = ctx.in.gotPlt->getVA() - ctx.in.plt->getVA() - 4;
if (!llvm::isUInt<27>(offset)) {
writePltHeaderLong(ctx, buf);
return;
}
write32(ctx, buf + 0, pltData[0]);
write32(ctx, buf + 4, pltData[1] | ((offset >> 20) & 0xff));
write32(ctx, buf + 8, pltData[2] | ((offset >> 12) & 0xff));
write32(ctx, 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(ctx)) {
addSyntheticLocal(ctx, "$t", STT_NOTYPE, 0, 0, isec);
addSyntheticLocal(ctx, "$d", STT_NOTYPE, 12, 0, isec);
} else {
addSyntheticLocal(ctx, "$a", STT_NOTYPE, 0, 0, isec);
addSyntheticLocal(ctx, "$d", STT_NOTYPE, 16, 0, isec);
}
}
static void writePltLong(Ctx &ctx, uint8_t *buf, uint64_t gotPltEntryAddr,
uint64_t pltEntryAddr) {
write32(ctx, buf + 0, 0xe59fc004);
write32(ctx, buf + 4, 0xe08cc00f);
write32(ctx, buf + 8, 0xe59cf000);
write32(ctx, buf + 12, 0x00000000);
uint64_t l1 = pltEntryAddr + 4;
write32(ctx, buf + 12, gotPltEntryAddr - l1 - 8);
}
void ARM::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const {
if (!useThumbPLTs(ctx)) {
uint64_t offset = sym.getGotPltVA(ctx) - pltEntryAddr - 8;
const uint32_t pltData[] = {
0xe28fc600,
0xe28cca00,
0xe5bcf000,
};
if (!llvm::isUInt<27>(offset)) {
writePltLong(ctx, buf, sym.getGotPltVA(ctx), pltEntryAddr);
return;
}
write32(ctx, buf + 0, pltData[0] | ((offset >> 20) & 0xff));
write32(ctx, buf + 4, pltData[1] | ((offset >> 12) & 0xff));
write32(ctx, buf + 8, pltData[2] | (offset & 0xfff));
memcpy(buf + 12, trapInstr.data(), 4);
} else {
uint64_t offset = sym.getGotPltVA(ctx) - pltEntryAddr - 12;
assert(llvm::isUInt<32>(offset) && "This should always fit into a 32-bit offset");
write16(ctx, buf + 2, 0x0c00);
relocateNoSym(buf, R_ARM_THM_MOVW_ABS_NC, offset);
write16(ctx, buf + 6, 0x0c00);
relocateNoSym(buf + 4, R_ARM_THM_MOVT_ABS, offset);
write16(ctx, buf + 8, 0x44fc);
write16(ctx, buf + 10, 0xf8dc);
write16(ctx, buf + 12, 0xf000);
write16(ctx, buf + 14, 0xe7fc);
}
}
void ARM::addPltSymbols(InputSection &isec, uint64_t off) const {
if (useThumbPLTs(ctx)) {
addSyntheticLocal(ctx, "$t", STT_NOTYPE, off, 0, isec);
} else {
addSyntheticLocal(ctx, "$a", STT_NOTYPE, off, 0, isec);
addSyntheticLocal(ctx, "$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(ctx))
return false;
switch (type) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_JUMP24:
assert(!useThumbPLTs(ctx) &&
"If the source is ARM, we should not need Thumb PLTs");
if (s.isFunc() && expr == R_PC && (s.getVA(ctx) & 1))
return true;
[[fallthrough]];
case R_ARM_CALL: {
uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA(ctx) : s.getVA(ctx);
return !inBranchRange(type, branchAddr, dst + a) ||
(!ctx.arg.armHasBlx && (s.getVA(ctx) & 1));
}
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
if ((expr == R_PLT_PC && !useThumbPLTs(ctx)) ||
(s.isFunc() && (s.getVA(ctx) & 1) == 0))
return true;
[[fallthrough]];
case R_ARM_THM_CALL: {
uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA(ctx) : s.getVA(ctx);
return !inBranchRange(type, branchAddr, dst + a) ||
(!ctx.arg.armHasBlx && (s.getVA(ctx) & 1) == 0);
}
}
return false;
}
uint32_t ARM::getThunkSectionSpacing() const {
return (ctx.arg.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 = llvm::SignExtend64<32>(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 ctx.arg.armJ1J2BranchEncoding ? llvm::isInt<25>(offset)
: llvm::isInt<23>(offset);
default:
return true;
}
}
static void stateChangeWarning(Ctx &ctx, uint8_t *loc, RelType relt,
const Symbol &s) {
assert(!s.isFunc());
const ErrorPlace place = getErrorPlace(ctx, loc);
std::string hint;
if (!place.srcLoc.empty())
hint = "; " + place.srcLoc;
if (s.isSection()) {
Warn(ctx) << place.loc << "branch and link relocation: " << relt
<< " to STT_SECTION symbol " << cast<Defined>(s).section->name
<< " ; interworking not performed" << hint;
} else {
Warn(ctx)
<< getErrorLoc(ctx, loc) << "branch and link relocation: " << 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};
}
void ARM::encodeAluGroup(uint8_t *loc, const Relocation &rel, uint64_t val,
int group, bool check) const {
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)
Err(ctx) << getErrorLoc(ctx, loc) << "unencodeable immediate " << val
<< " for relocation " << rel.type;
write32(ctx, loc,
(read32(ctx, loc) & 0xff3ff000) | opcode | rot | (imm & 0xff));
}
static void encodeLdrGroup(Ctx &ctx, 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(ctx, loc, imm, 12, rel);
write32(ctx, loc, (read32(ctx, loc) & 0xff7ff000) | opcode | imm);
}
static void encodeLdrsGroup(Ctx &ctx, 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(ctx, loc, imm, 8, rel);
write32(ctx, loc,
(read32(ctx, 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(ctx, loc, val);
break;
case R_ARM_PREL31:
checkInt(ctx, loc, val, 31, rel);
write32(ctx, loc, (read32(ctx, loc) & 0x80000000) | (val & ~0x80000000));
break;
case R_ARM_CALL: {
assert(rel.sym);
bool bit0Thumb = val & 1;
bool isBlx = (read32(ctx, loc) & 0xfe000000) == 0xfa000000;
if (!rel.sym->isFunc() && isBlx != bit0Thumb)
stateChangeWarning(ctx, loc, rel.type, *rel.sym);
if (rel.sym->isFunc() ? bit0Thumb : isBlx) {
checkInt(ctx, loc, val, 26, rel);
write32(ctx, loc,
0xfa000000 |
((val & 2) << 23) |
((val >> 2) & 0x00ffffff));
break;
}
write32(ctx, loc, 0xeb000000 | (read32(ctx, loc) & 0x00ffffff));
}
[[fallthrough]];
case R_ARM_JUMP24:
case R_ARM_PC24:
case R_ARM_PLT32:
checkInt(ctx, loc, val, 26, rel);
write32(ctx, loc,
(read32(ctx, loc) & ~0x00ffffff) | ((val >> 2) & 0x00ffffff));
break;
case R_ARM_THM_JUMP8:
checkInt(ctx, loc, val, 9, rel);
write16(ctx, loc, (read16(ctx, loc) & 0xff00) | ((val >> 1) & 0x00ff));
break;
case R_ARM_THM_JUMP11:
checkInt(ctx, loc, val, 12, rel);
write16(ctx, loc, (read16(ctx, loc) & 0xf800) | ((val >> 1) & 0x07ff));
break;
case R_ARM_THM_JUMP19:
checkInt(ctx, loc, val, 21, rel);
write16(ctx, loc,
(read16(ctx, loc) & 0xfbc0) |
((val >> 10) & 0x0400) |
((val >> 12) & 0x003f));
write16(ctx, 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(ctx);
bool isBlx = (read16(ctx, loc + 2) & 0x1000) == 0;
if (!rel.sym->isFunc() && !rel.sym->isInPlt(ctx) && isBlx == useThumb)
stateChangeWarning(ctx, loc, rel.type, *rel.sym);
if ((rel.sym->isFunc() || rel.sym->isInPlt(ctx)) ? !useThumb : isBlx) {
val = alignTo(val, 4);
write16(ctx, loc + 2, read16(ctx, loc + 2) & ~0x1000);
} else {
write16(ctx, loc + 2, (read16(ctx, loc + 2) & ~0x1000) | 1 << 12);
}
if (!ctx.arg.armJ1J2BranchEncoding) {
checkInt(ctx, loc, val, 23, rel);
write16(ctx, loc,
0xf000 |
((val >> 12) & 0x07ff));
write16(ctx, loc + 2,
(read16(ctx, loc + 2) & 0xd000) |
0x2800 |
((val >> 1) & 0x07ff));
break;
}
}
[[fallthrough]];
case R_ARM_THM_JUMP24:
checkInt(ctx, loc, val, 25, rel);
write16(ctx, loc,
0xf000 |
((val >> 14) & 0x0400) |
((val >> 12) & 0x03ff));
write16(ctx, loc + 2,
(read16(ctx, 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(ctx, loc,
(read32(ctx, loc) & ~0x000f0fff) | ((val & 0xf000) << 4) |
(val & 0x0fff));
break;
case R_ARM_MOVT_ABS:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_BREL:
write32(ctx, loc,
(read32(ctx, 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(ctx, loc,
0xf2c0 |
((val >> 17) & 0x0400) |
((val >> 28) & 0x000f));
write16(ctx, loc + 2,
(read16(ctx, 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(ctx, loc,
0xf240 |
((val >> 1) & 0x0400) |
((val >> 12) & 0x000f));
write16(ctx, loc + 2,
(read16(ctx, loc + 2) & 0x8f00) |
((val << 4) & 0x7000) |
(val & 0x00ff));
break;
case R_ARM_THM_ALU_ABS_G3:
write16(ctx, loc, (read16(ctx, loc) & ~0x00ff) | ((val >> 24) & 0x00ff));
break;
case R_ARM_THM_ALU_ABS_G2_NC:
write16(ctx, loc, (read16(ctx, loc) & ~0x00ff) | ((val >> 16) & 0x00ff));
break;
case R_ARM_THM_ALU_ABS_G1_NC:
write16(ctx, loc, (read16(ctx, loc) & ~0x00ff) | ((val >> 8) & 0x00ff));
break;
case R_ARM_THM_ALU_ABS_G0_NC:
write16(ctx, loc, (read16(ctx, 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(ctx, loc, rel, val, 0);
break;
case R_ARM_LDR_PC_G1:
encodeLdrGroup(ctx, loc, rel, val, 1);
break;
case R_ARM_LDR_PC_G2:
encodeLdrGroup(ctx, loc, rel, val, 2);
break;
case R_ARM_LDRS_PC_G0:
encodeLdrsGroup(ctx, loc, rel, val, 0);
break;
case R_ARM_LDRS_PC_G1:
encodeLdrsGroup(ctx, loc, rel, val, 1);
break;
case R_ARM_LDRS_PC_G2:
encodeLdrsGroup(ctx, 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(ctx, loc, imm, 12, rel);
write16(ctx, loc, (read16(ctx, loc) & 0xfb0f) | sub | (imm & 0x800) >> 1);
write16(ctx, loc + 2,
(read16(ctx, loc + 2) & 0x8f00) | (imm & 0x700) << 4 |
(imm & 0xff));
break;
}
case R_ARM_THM_PC8:
if (rel.sym->isFunc())
val &= ~0x1;
checkUInt(ctx, loc, val, 10, rel);
checkAlignment(ctx, loc, val, 4, rel);
write16(ctx, loc, (read16(ctx, 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(ctx, loc, imm12, 12, rel);
write16(ctx, loc, read16(ctx, loc) | u);
write16(ctx, loc + 2, (read16(ctx, loc + 2) & 0xf000) | imm12);
break;
}
default:
llvm_unreachable("unknown relocation");
}
}
int64_t ARM::getImplicitAddend(const uint8_t *buf, RelType type) const {
switch (type) {
default:
InternalErr(ctx, buf) << "cannot read addend for relocation " << 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(ctx, buf));
case R_ARM_PREL31:
return SignExtend64<31>(read32(ctx, buf));
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PC24:
case R_ARM_PLT32:
return SignExtend64<26>(read32(ctx, buf) << 2);
case R_ARM_THM_JUMP8:
return SignExtend64<9>(read16(ctx, buf) << 1);
case R_ARM_THM_JUMP11:
return SignExtend64<12>(read16(ctx, buf) << 1);
case R_ARM_THM_JUMP19: {
uint16_t hi = read16(ctx, buf);
uint16_t lo = read16(ctx, 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 (!ctx.arg.armJ1J2BranchEncoding) {
uint16_t hi = read16(ctx, buf);
uint16_t lo = read16(ctx, buf + 2);
return SignExtend64<22>(((hi & 0x7ff) << 12) |
((lo & 0x7ff) << 1));
break;
}
[[fallthrough]];
case R_ARM_THM_JUMP24: {
uint16_t hi = read16(ctx, buf);
uint16_t lo = read16(ctx, 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(ctx, 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(ctx, buf);
uint16_t lo = read16(ctx, 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(ctx, 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(ctx, 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(ctx, buf) & 0x00800000;
uint32_t imm12 = read32(ctx, 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(ctx, 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(ctx, buf);
uint16_t lo = read16(ctx, 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(ctx, buf) & 0xff) << 2) + 4) & 0x3ff) - 4;
case R_ARM_THM_PC12: {
bool u = read16(ctx, buf) & 0x0080;
uint64_t imm12 = read16(ctx, 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(Ctx &ctx) {
for (auto &kv : static_cast<ARM &>(*ctx.target).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(Ctx &ctx) {
auto §ionMap = static_cast<ARM &>(*ctx.target).sectionMap;
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)
static_cast<ARM &>(*sec->file->ctx.target).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, read32be(buf + i));
if (curState == CodeState::Thumb)
for (uint64_t i = start; i < end; i += width)
write16le(buf + i, read16be(buf + i));
}
void elf::convertArmInstructionstoBE8(Ctx &ctx, InputSection *sec,
uint8_t *buf) {
auto §ionMap = static_cast<ARM &>(*ctx.target).sectionMap;
auto it = sectionMap.find(sec);
if (it == sectionMap.end())
return;
SmallVector<const Defined *, 0> &mapSyms = it->second;
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) {
Err(ctx) << "CMSE symbol '" << CHECK2(eSyms[i].getName(stringTable), this)
<< "' in import library '" << 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(static_cast<void *>(sym), 0, sizeof(Symbol));
sym->setName(CHECK2(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) {
Err(ctx) << "CMSE symbol '" << sym->getName() << "' in import library '"
<< this << "' is not absolute";
continue;
}
if (!(eSym.st_value & 1) || (eSym.getType() != STT_FUNC)) {
Err(ctx) << "CMSE symbol '" << sym->getName() << "' in import library '"
<< this << "' is not a Thumb function definition";
continue;
}
if (ctx.symtab->cmseImportLib.count(sym->getName())) {
Err(ctx) << "CMSE symbol '" << sym->getName()
<< "' is multiply defined in import library '" << this << "'";
continue;
}
if (eSym.st_size != ACLESESYM_SIZE) {
Warn(ctx) << "CMSE symbol '" << sym->getName() << "' in import library '"
<< this << "' does not have correct size of " << ACLESESYM_SIZE
<< " bytes";
}
ctx.symtab->cmseImportLib[sym->getName()] = sym;
}
}
static std::string checkCmseSymAttributes(Ctx &ctx, 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(toStr(ctx, s->file)) + ": cmse " + type + " symbol '" +
s->getName() + "' is not a Thumb function definition")
.str();
if (!d->section)
return (Twine(toStr(ctx, 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(Ctx &ctx) {
if (!ctx.arg.cmseImplib)
return;
for (Symbol *acleSeSym : ctx.symtab->getSymbols()) {
if (!acleSeSym->getName().starts_with(ACLESESYM_PREFIX))
continue;
if (!ctx.arg.armCMSESupport) {
Err(ctx) << "CMSE is only supported by ARMv8-M architecture or later";
ctx.arg.cmseImplib = false;
break;
}
StringRef name = acleSeSym->getName().substr(std::strlen(ACLESESYM_PREFIX));
Symbol *sym = ctx.symtab->find(name);
if (!sym) {
Err(ctx) << acleSeSym->file << ": cmse special symbol '"
<< acleSeSym->getName()
<< "' detected, but no associated entry function definition '"
<< name << "' with external linkage found";
continue;
}
std::string errMsg = checkCmseSymAttributes(ctx, acleSeSym, sym);
if (!errMsg.empty()) {
Err(ctx) << errMsg;
continue;
}
ctx.symtab->cmseSymMap[name] = {acleSeSym, sym};
}
parallelForEach(ctx.objectFiles, [&](InputFile *file) {
MutableArrayRef<Symbol *> syms = file->getMutableSymbols();
for (Symbol *&sym : syms) {
StringRef symName = sym->getName();
auto it = ctx.symtab->cmseSymMap.find(symName);
if (it != ctx.symtab->cmseSymMap.end())
sym = it->second.acleSeSym;
}
});
}
ArmCmseSGSection::ArmCmseSGSection(Ctx &ctx)
: SyntheticSection(ctx, ".gnu.sgstubs", SHT_PROGBITS,
SHF_ALLOC | SHF_EXECINSTR,
32) {
entsize = ACLESESYM_SIZE;
for (auto &[_, sym] : ctx.symtab->cmseImportLib) {
if (impLibMaxAddr <= sym->value)
impLibMaxAddr = sym->value + sym->size;
}
if (ctx.symtab->cmseSymMap.empty())
return;
addMappingSymbol();
for (auto &[_, entryFunc] : ctx.symtab->cmseSymMap)
addSGVeneer(cast<Defined>(entryFunc.acleSeSym),
cast<Defined>(entryFunc.sym));
for (auto &[_, sym] : ctx.symtab->cmseImportLib) {
if (!ctx.symtab->inCMSEOutImpLib.count(sym->getName()))
Warn(ctx)
<< "entry function '" << sym->getName()
<< "' from CMSE import library is not present in secure application";
}
if (!ctx.symtab->cmseImportLib.empty() && ctx.arg.cmseOutputLib.empty()) {
for (auto &[_, entryFunc] : ctx.symtab->cmseSymMap) {
Symbol *sym = entryFunc.sym;
if (!ctx.symtab->inCMSEOutImpLib.count(sym->getName()))
Warn(ctx) << "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 (ctx.symtab->cmseImportLib.count(sym->getName()))
ctx.symtab->inCMSEOutImpLib[sym->getName()] = true;
if (acleSeSym->file != sym->file ||
cast<Defined>(*acleSeSym).value != cast<Defined>(*sym).value)
return;
std::unique_ptr<ArmCmseSGVeneer> ss;
auto it = ctx.symtab->cmseImportLib.find(sym->getName());
if (it != ctx.symtab->cmseImportLib.end()) {
Defined *impSym = it->second;
ss = std::make_unique<ArmCmseSGVeneer>(sym, acleSeSym, impSym->value);
} else {
ss = std::make_unique<ArmCmseSGVeneer>(sym, acleSeSym);
++newEntries;
}
sgVeneers.emplace_back(std::move(ss));
}
void ArmCmseSGSection::writeTo(uint8_t *buf) {
for (std::unique_ptr<ArmCmseSGVeneer> &s : sgVeneers) {
uint8_t *p = buf + s->offset;
write16(ctx, p + 0, 0xe97f);
write16(ctx, p + 2, 0xe97f);
write16(ctx, p + 4, 0xf000);
write16(ctx, p + 6, 0xb000);
ctx.target->relocateNoSym(p + 4, R_ARM_THM_JUMP24,
s->acleSeSym->getVA(ctx) -
(getVA() + s->offset + s->size));
}
}
void ArmCmseSGSection::addMappingSymbol() {
addSyntheticLocal(ctx, "$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)) {
Err(ctx)
<< "start address of '.gnu.sgstubs' is different from previous link";
return;
}
for (auto [i, s] : enumerate(sgVeneers)) {
s->offset = i * s->size;
Defined(ctx, 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(Ctx &ctx) {
auto shstrtab =
std::make_unique<StringTableSection>(ctx, ".shstrtab", false);
auto strtab =
std::make_unique<StringTableSection>(ctx, ".strtab", false);
auto impSymTab = std::make_unique<SymbolTableSection<ELFT>>(ctx, *strtab);
SmallVector<std::pair<std::unique_ptr<OutputSection>, SyntheticSection *>, 0>
osIsPairs;
osIsPairs.emplace_back(
std::make_unique<OutputSection>(ctx, strtab->name, 0, 0), strtab.get());
osIsPairs.emplace_back(
std::make_unique<OutputSection>(ctx, impSymTab->name, 0, 0),
impSymTab.get());
osIsPairs.emplace_back(
std::make_unique<OutputSection>(ctx, shstrtab->name, 0, 0),
shstrtab.get());
llvm::sort(ctx.symtab->cmseSymMap, [&](const auto &a, const auto &b) {
return a.second.sym->getVA(ctx) < b.second.sym->getVA(ctx);
});
for (auto &p : ctx.symtab->cmseSymMap) {
Defined *d = cast<Defined>(p.second.sym);
impSymTab->addSymbol(makeDefined(
ctx, ctx.internalFile, d->getName(), d->computeBinding(ctx),
0, STT_FUNC, d->getVA(ctx), 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, ctx.arg.wordsize);
const auto shnum = osIsPairs.size() + 1;
const uint64_t fileSize =
sectionHeaderOff + shnum * sizeof(typename ELFT::Shdr);
const unsigned flags =
ctx.arg.mmapOutputFile ? (unsigned)FileOutputBuffer::F_mmap : 0;
unlinkAsync(ctx.arg.cmseOutputLib);
Expected<std::unique_ptr<FileOutputBuffer>> bufferOrErr =
FileOutputBuffer::create(ctx.arg.cmseOutputLib, fileSize, flags);
if (!bufferOrErr) {
Err(ctx) << "failed to open " << ctx.arg.cmseOutputLib << ": "
<< 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] = ctx.arg.isLE ? ELFDATA2LSB : ELFDATA2MSB;
eHdr->e_ident[EI_VERSION] = EV_CURRENT;
eHdr->e_ident[EI_OSABI] = ctx.arg.osabi;
eHdr->e_ident[EI_ABIVERSION] = 0;
eHdr->e_machine = EM_ARM;
eHdr->e_version = EV_CURRENT;
eHdr->e_flags = ctx.arg.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>(ctx, buf + osec->offset, tg);
}
if (auto e = buffer->commit())
Err(ctx) << "failed to write output '" << buffer->getPath()
<< "': " << std::move(e);
}
void elf::setARMTargetInfo(Ctx &ctx) { ctx.target.reset(new ARM(ctx)); }
template void elf::writeARMCmseImportLib<ELF32LE>(Ctx &);
template void elf::writeARMCmseImportLib<ELF32BE>(Ctx &);
template void elf::writeARMCmseImportLib<ELF64LE>(Ctx &);
template void elf::writeARMCmseImportLib<ELF64BE>(Ctx &);
template void ObjFile<ELF32LE>::importCmseSymbols();
template void ObjFile<ELF32BE>::importCmseSymbols();
template void ObjFile<ELF64LE>::importCmseSymbols();
template void ObjFile<ELF64BE>::importCmseSymbols();