#include "OutputSections.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
using namespace llvm::support::endian;
using namespace llvm::ELF;
using namespace lld;
using namespace lld::elf;
namespace {
class SystemZ : public TargetInfo {
public:
SystemZ();
int getTlsGdRelaxSkip(RelType type) const override;
RelExpr getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const override;
RelType getDynRel(RelType type) const override;
void writeGotHeader(uint8_t *buf) 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 addPltHeaderSymbols(InputSection &isd) const override;
void writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const override;
RelExpr adjustTlsExpr(RelType type, RelExpr expr) const override;
RelExpr adjustGotPcExpr(RelType type, int64_t addend,
const uint8_t *loc) const override;
bool relaxOnce(int pass) const override;
void relocate(uint8_t *loc, const Relocation &rel,
uint64_t val) const override;
int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override;
private:
void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const;
void relaxTlsGdToIe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
void relaxTlsGdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
void relaxTlsLdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
};
}
SystemZ::SystemZ() {
copyRel = R_390_COPY;
gotRel = R_390_GLOB_DAT;
pltRel = R_390_JMP_SLOT;
relativeRel = R_390_RELATIVE;
iRelativeRel = R_390_IRELATIVE;
symbolicRel = R_390_64;
tlsGotRel = R_390_TLS_TPOFF;
tlsModuleIndexRel = R_390_TLS_DTPMOD;
tlsOffsetRel = R_390_TLS_DTPOFF;
gotHeaderEntriesNum = 3;
gotPltHeaderEntriesNum = 0;
gotEntrySize = 8;
pltHeaderSize = 32;
pltEntrySize = 32;
ipltEntrySize = 32;
trapInstr = {0x07, 0x07, 0x07, 0x07};
defaultImageBase = 0x1000000;
}
RelExpr SystemZ::getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const {
switch (type) {
case R_390_NONE:
return R_NONE;
case R_390_8:
case R_390_12:
case R_390_16:
case R_390_20:
case R_390_32:
case R_390_64:
return R_ABS;
case R_390_PC16:
case R_390_PC32:
case R_390_PC64:
case R_390_PC12DBL:
case R_390_PC16DBL:
case R_390_PC24DBL:
case R_390_PC32DBL:
return R_PC;
case R_390_GOTOFF16:
case R_390_GOTOFF:
case R_390_GOTOFF64:
return R_GOTREL;
case R_390_PLT32:
case R_390_PLT64:
case R_390_PLT12DBL:
case R_390_PLT16DBL:
case R_390_PLT24DBL:
case R_390_PLT32DBL:
return R_PLT_PC;
case R_390_PLTOFF16:
case R_390_PLTOFF32:
case R_390_PLTOFF64:
return R_PLT_GOTREL;
case R_390_GOTENT:
return R_GOT_PC;
case R_390_GOT12:
case R_390_GOT16:
case R_390_GOT20:
case R_390_GOT32:
case R_390_GOT64:
return R_GOT_OFF;
case R_390_GOTPLTENT:
return R_GOTPLT_PC;
case R_390_GOTPLT12:
case R_390_GOTPLT16:
case R_390_GOTPLT20:
case R_390_GOTPLT32:
case R_390_GOTPLT64:
return R_GOTPLT_GOTREL;
case R_390_GOTPC:
case R_390_GOTPCDBL:
return R_GOTONLY_PC;
case R_390_TLS_LOAD:
return R_NONE;
case R_390_TLS_GDCALL:
return R_TLSGD_PC;
case R_390_TLS_LDCALL:
return R_TLSLD_PC;
case R_390_TLS_GD32:
case R_390_TLS_GD64:
return R_TLSGD_GOT;
case R_390_TLS_LDM32:
case R_390_TLS_LDM64:
return R_TLSLD_GOT;
case R_390_TLS_LDO32:
case R_390_TLS_LDO64:
return R_DTPREL;
case R_390_TLS_LE32:
case R_390_TLS_LE64:
return R_TPREL;
case R_390_TLS_IE32:
case R_390_TLS_IE64:
return R_GOT;
case R_390_TLS_GOTIE12:
case R_390_TLS_GOTIE20:
case R_390_TLS_GOTIE32:
case R_390_TLS_GOTIE64:
return R_GOT_OFF;
case R_390_TLS_IEENT:
return R_GOT_PC;
default:
error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) +
") against symbol " + toString(s));
return R_NONE;
}
}
void SystemZ::writeGotHeader(uint8_t *buf) const {
write64be(buf, mainPart->dynamic->getVA());
}
void SystemZ::writeGotPlt(uint8_t *buf, const Symbol &s) const {
write64be(buf, s.getPltVA() + 14);
}
void SystemZ::writeIgotPlt(uint8_t *buf, const Symbol &s) const {
if (config->writeAddends)
write64be(buf, s.getVA());
}
void SystemZ::writePltHeader(uint8_t *buf) const {
const uint8_t pltData[] = {
0xe3, 0x10, 0xf0, 0x38, 0x00, 0x24,
0xc0, 0x10, 0x00, 0x00, 0x00, 0x00,
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08,
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04,
0x07, 0xf1,
0x07, 0x00,
0x07, 0x00,
0x07, 0x00,
};
memcpy(buf, pltData, sizeof(pltData));
uint64_t got = in.got->getVA();
uint64_t plt = in.plt->getVA();
write32be(buf + 8, (got - plt - 6) >> 1);
}
void SystemZ::addPltHeaderSymbols(InputSection &isec) const {
in.got->hasGotOffRel.store(true, std::memory_order_relaxed);
}
void SystemZ::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const {
const uint8_t inst[] = {
0xc0, 0x10, 0x00, 0x00, 0x00, 0x00,
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04,
0x07, 0xf1,
0x0d, 0x10,
0xe3, 0x10, 0x10, 0x0c, 0x00, 0x14,
0xc0, 0xf4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
memcpy(buf, inst, sizeof(inst));
write32be(buf + 2, (sym.getGotPltVA() - pltEntryAddr) >> 1);
write32be(buf + 24, (in.plt->getVA() - pltEntryAddr - 22) >> 1);
write32be(buf + 28, in.relaPlt->entsize * sym.getPltIdx());
}
int64_t SystemZ::getImplicitAddend(const uint8_t *buf, RelType type) const {
switch (type) {
case R_390_8:
return SignExtend64<8>(*buf);
case R_390_16:
case R_390_PC16:
return SignExtend64<16>(read16be(buf));
case R_390_PC16DBL:
return SignExtend64<16>(read16be(buf)) << 1;
case R_390_32:
case R_390_PC32:
return SignExtend64<32>(read32be(buf));
case R_390_PC32DBL:
return SignExtend64<32>(read32be(buf)) << 1;
case R_390_64:
case R_390_PC64:
case R_390_TLS_DTPMOD:
case R_390_TLS_DTPOFF:
case R_390_TLS_TPOFF:
case R_390_GLOB_DAT:
case R_390_RELATIVE:
case R_390_IRELATIVE:
return read64be(buf);
case R_390_COPY:
case R_390_JMP_SLOT:
case R_390_NONE:
return 0;
default:
internalLinkerError(getErrorLocation(buf),
"cannot read addend for relocation " + toString(type));
return 0;
}
}
RelType SystemZ::getDynRel(RelType type) const {
if (type == R_390_64 || type == R_390_PC64)
return type;
return R_390_NONE;
}
RelExpr SystemZ::adjustTlsExpr(RelType type, RelExpr expr) const {
if (expr == R_RELAX_TLS_GD_TO_IE)
return R_RELAX_TLS_GD_TO_IE_GOT_OFF;
return expr;
}
int SystemZ::getTlsGdRelaxSkip(RelType type) const {
if (type == R_390_TLS_GDCALL || type == R_390_TLS_LDCALL)
return 2;
return 1;
}
void SystemZ::relaxTlsGdToIe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
switch (rel.type) {
case R_390_TLS_GDCALL:
write16be(loc, 0xe322);
write32be(loc + 2, 0xc0000004);
break;
case R_390_TLS_GD64:
relocateNoSym(loc, R_390_TLS_GOTIE64, val);
break;
default:
llvm_unreachable("unsupported relocation for TLS GD to IE relaxation");
}
}
void SystemZ::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
switch (rel.type) {
case R_390_TLS_GDCALL:
write16be(loc, 0xc004);
write32be(loc + 2, 0x00000000);
break;
case R_390_TLS_GD64:
relocateNoSym(loc, R_390_TLS_LE64, val);
break;
default:
llvm_unreachable("unsupported relocation for TLS GD to LE relaxation");
}
}
void SystemZ::relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
switch (rel.type) {
case R_390_TLS_LDCALL:
write16be(loc, 0xc004);
write32be(loc + 2, 0x00000000);
break;
case R_390_TLS_LDM64:
break;
case R_390_TLS_LDO64:
relocateNoSym(loc, R_390_TLS_LE64, val);
break;
default:
llvm_unreachable("unsupported relocation for TLS LD to LE relaxation");
}
}
RelExpr SystemZ::adjustGotPcExpr(RelType type, int64_t addend,
const uint8_t *loc) const {
if (!config->relax || addend != 2 || type != R_390_GOTENT)
return R_GOT_PC;
const uint16_t op = read16be(loc - 2);
if ((op & 0xff0f) == 0xc408)
return R_RELAX_GOT_PC;
return R_GOT_PC;
}
bool SystemZ::relaxOnce(int pass) const {
SmallVector<InputSection *, 0> storage;
bool changed = false;
for (OutputSection *osec : outputSections) {
if (!(osec->flags & SHF_EXECINSTR))
continue;
for (InputSection *sec : getInputSections(*osec, storage)) {
for (Relocation &rel : sec->relocs()) {
if (rel.expr != R_RELAX_GOT_PC)
continue;
uint64_t v = sec->getRelocTargetVA(
sec->file, rel.type, rel.addend,
sec->getOutputSection()->addr + rel.offset, *rel.sym, rel.expr);
if (isInt<33>(v) && !(v & 1))
continue;
if (rel.sym->auxIdx == 0) {
rel.sym->allocateAux();
addGotEntry(*rel.sym);
changed = true;
}
rel.expr = R_GOT_PC;
}
}
}
return changed;
}
void SystemZ::relaxGot(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
assert(isInt<33>(val) &&
"R_390_GOTENT should not have been relaxed if it overflows");
assert(!(val & 1) &&
"R_390_GOTENT should not have been relaxed if it is misaligned");
const uint16_t op = read16be(loc - 2);
if ((op & 0xff0f) == 0xc408) {
write16be(loc - 2, 0xc000 | (op & 0x00f0));
write32be(loc, val >> 1);
}
}
void SystemZ::relocate(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
switch (rel.expr) {
case R_RELAX_GOT_PC:
return relaxGot(loc, rel, val);
case R_RELAX_TLS_GD_TO_IE_GOT_OFF:
return relaxTlsGdToIe(loc, rel, val);
case R_RELAX_TLS_GD_TO_LE:
return relaxTlsGdToLe(loc, rel, val);
case R_RELAX_TLS_LD_TO_LE:
return relaxTlsLdToLe(loc, rel, val);
default:
break;
}
switch (rel.type) {
case R_390_8:
checkIntUInt(loc, val, 8, rel);
*loc = val;
break;
case R_390_12:
case R_390_GOT12:
case R_390_GOTPLT12:
case R_390_TLS_GOTIE12:
checkUInt(loc, val, 12, rel);
write16be(loc, (read16be(loc) & 0xF000) | val);
break;
case R_390_PC12DBL:
case R_390_PLT12DBL:
checkInt(loc, val, 13, rel);
checkAlignment(loc, val, 2, rel);
write16be(loc, (read16be(loc) & 0xF000) | ((val >> 1) & 0x0FFF));
break;
case R_390_16:
case R_390_GOT16:
case R_390_GOTPLT16:
case R_390_GOTOFF16:
case R_390_PLTOFF16:
checkIntUInt(loc, val, 16, rel);
write16be(loc, val);
break;
case R_390_PC16:
checkInt(loc, val, 16, rel);
write16be(loc, val);
break;
case R_390_PC16DBL:
case R_390_PLT16DBL:
checkInt(loc, val, 17, rel);
checkAlignment(loc, val, 2, rel);
write16be(loc, val >> 1);
break;
case R_390_20:
case R_390_GOT20:
case R_390_GOTPLT20:
case R_390_TLS_GOTIE20:
checkInt(loc, val, 20, rel);
write32be(loc, (read32be(loc) & 0xF00000FF) | ((val & 0xFFF) << 16) |
((val & 0xFF000) >> 4));
break;
case R_390_PC24DBL:
case R_390_PLT24DBL:
checkInt(loc, val, 25, rel);
checkAlignment(loc, val, 2, rel);
loc[0] = val >> 17;
loc[1] = val >> 9;
loc[2] = val >> 1;
break;
case R_390_32:
case R_390_GOT32:
case R_390_GOTPLT32:
case R_390_GOTOFF:
case R_390_PLTOFF32:
case R_390_TLS_IE32:
case R_390_TLS_GOTIE32:
case R_390_TLS_GD32:
case R_390_TLS_LDM32:
case R_390_TLS_LDO32:
case R_390_TLS_LE32:
checkIntUInt(loc, val, 32, rel);
write32be(loc, val);
break;
case R_390_PC32:
case R_390_PLT32:
checkInt(loc, val, 32, rel);
write32be(loc, val);
break;
case R_390_PC32DBL:
case R_390_PLT32DBL:
case R_390_GOTPCDBL:
case R_390_GOTENT:
case R_390_GOTPLTENT:
case R_390_TLS_IEENT:
checkInt(loc, val, 33, rel);
checkAlignment(loc, val, 2, rel);
write32be(loc, val >> 1);
break;
case R_390_64:
case R_390_PC64:
case R_390_PLT64:
case R_390_GOT64:
case R_390_GOTPLT64:
case R_390_GOTOFF64:
case R_390_PLTOFF64:
case R_390_GOTPC:
case R_390_TLS_IE64:
case R_390_TLS_GOTIE64:
case R_390_TLS_GD64:
case R_390_TLS_LDM64:
case R_390_TLS_LDO64:
case R_390_TLS_LE64:
case R_390_TLS_DTPMOD:
case R_390_TLS_DTPOFF:
case R_390_TLS_TPOFF:
write64be(loc, val);
break;
case R_390_TLS_LOAD:
case R_390_TLS_GDCALL:
case R_390_TLS_LDCALL:
break;
default:
llvm_unreachable("unknown relocation");
}
}
TargetInfo *elf::getSystemZTargetInfo() {
static SystemZ t;
return &t;
}