Skip to content

Commit

Permalink
[ARM] Implementation of PLT: handling of IFUNC calls (gnu_indirect_fu…
Browse files Browse the repository at this point in the history
…nction)

This diff includes implementation of linking calls to ifunc functions.
It provides ifunc entries in PLT and corresponding relocations (R_ARM_ALU_PC_G0_NC,
R_ARM_ALU_PC_G1_NC, R_ARM_LDR_PC_G2 for link-time and R_ARM_IRELATIVE for run-time).

Differential Revision: http://reviews.llvm.org/D7833

llvm-svn: 233277
  • Loading branch information
Leny Kholodov committed Mar 26, 2015
1 parent e99c72f commit 28074d6
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 10 deletions.
11 changes: 11 additions & 0 deletions lld/lib/ReaderWriter/ELF/ARM/ARMELFFile.h
Expand Up @@ -54,6 +54,8 @@ template <class ELFT> class ARMELFDefinedAtom : public ELFDefinedAtom<ELFT> {
};

template <class ELFT> class ARMELFFile : public ELFFile<ELFT> {
typedef llvm::object::Elf_Rel_Impl<ELFT, false> Elf_Rel;

public:
ARMELFFile(std::unique_ptr<MemoryBuffer> mb, ARMLinkingContext &ctx)
: ELFFile<ELFT>(std::move(mb), ctx) {}
Expand All @@ -64,6 +66,15 @@ template <class ELFT> class ARMELFFile : public ELFFile<ELFT> {
new ARMELFFile<ELFT>(std::move(mb), ctx));
}

protected:
/// Returns initial addend; for ARM it is 0, because it is read
/// during the relocations applying
Reference::Addend getInitialAddend(ArrayRef<uint8_t>,
uint64_t,
const Elf_Rel&) const override {
return 0;
}

private:
typedef llvm::object::Elf_Sym_Impl<ELFT> Elf_Sym;
typedef llvm::object::Elf_Shdr_Impl<ELFT> Elf_Shdr;
Expand Down
15 changes: 15 additions & 0 deletions lld/lib/ReaderWriter/ELF/ARM/ARMLinkingContext.h
Expand Up @@ -22,13 +22,28 @@ class ARMLinkingContext final : public ELFLinkingContext {
static std::unique_ptr<ELFLinkingContext> create(llvm::Triple);
ARMLinkingContext(llvm::Triple);

bool isRelaOutputFormat() const override { return false; }

void addPasses(PassManager &) override;

uint64_t getBaseAddress() const override {
if (_baseAddress == 0)
return 0x400000;
return _baseAddress;
}

bool isPLTRelocation(const Reference &r) const override {
if (r.kindNamespace() != Reference::KindNamespace::ELF)
return false;
assert(r.kindArch() == Reference::KindArch::ARM);
switch (r.kindValue()) {
case llvm::ELF::R_ARM_JUMP_SLOT:
case llvm::ELF::R_ARM_IRELATIVE:
return true;
default:
return false;
}
}
};

// Special methods to check code model of atoms.
Expand Down
83 changes: 82 additions & 1 deletion lld/lib/ReaderWriter/ELF/ARM/ARMRelocationHandler.cpp
Expand Up @@ -412,6 +412,75 @@ static void relocR_ARM_TLS_LE32(uint8_t *location, uint64_t P, uint64_t S,
applyArmReloc(location, result);
}

template <uint32_t lshift>
static void relocR_ARM_ALU_PC_GN_NC(uint8_t *location, uint32_t result) {
static_assert(lshift < 32 && lshift % 2 == 0,
"lshift must be even and less than word size");

const uint32_t rshift = 32 - lshift;
result = ((result >> lshift) & 0xFF) | ((rshift / 2) << 8);

applyArmReloc(location, result, 0xFFF);
}

/// \brief R_ARM_ALU_PC_G0_NC - ((S + A) | T) - P => S + A - P
static void relocR_ARM_ALU_PC_G0_NC(uint8_t *location, uint64_t P, uint64_t S,
int64_t A) {
int32_t result = (int32_t)((S + A) - P);

if (result < 0)
llvm_unreachable(
"Negative offsets for group relocations has not been implemented");

DEBUG_WITH_TYPE(
"ARM", llvm::dbgs() << "\t\tHandle " << LLVM_FUNCTION_NAME << " -";
llvm::dbgs() << " S: 0x" << Twine::utohexstr(S);
llvm::dbgs() << " A: 0x" << Twine::utohexstr(A);
llvm::dbgs() << " P: 0x" << Twine::utohexstr(P);
llvm::dbgs() << " result: 0x" << Twine::utohexstr((uint32_t)result) << "\n");

relocR_ARM_ALU_PC_GN_NC<20>(location, (uint32_t)result);
}

/// \brief R_ARM_ALU_PC_G1_NC - ((S + A) | T) - P => S + A - P
static void relocR_ARM_ALU_PC_G1_NC(uint8_t *location, uint64_t P, uint64_t S,
int64_t A) {
int32_t result = (int32_t)((S + A) - P);

if (result < 0)
llvm_unreachable(
"Negative offsets for group relocations has not been implemented");

DEBUG_WITH_TYPE(
"ARM", llvm::dbgs() << "\t\tHandle " << LLVM_FUNCTION_NAME << " -";
llvm::dbgs() << " S: 0x" << Twine::utohexstr(S);
llvm::dbgs() << " A: 0x" << Twine::utohexstr(A);
llvm::dbgs() << " P: 0x" << Twine::utohexstr(P);
llvm::dbgs() << " result: 0x" << Twine::utohexstr((uint32_t)result) << "\n");

relocR_ARM_ALU_PC_GN_NC<12>(location, (uint32_t)result);
}

/// \brief R_ARM_LDR_PC_G2 - S + A - P
static void relocR_ARM_LDR_PC_G2(uint8_t *location, uint64_t P, uint64_t S,
int64_t A) {
int32_t result = (int32_t)((S + A) - P);

if (result < 0)
llvm_unreachable(
"Negative offsets for group relocations has not been implemented");

DEBUG_WITH_TYPE(
"ARM", llvm::dbgs() << "\t\tHandle " << LLVM_FUNCTION_NAME << " -";
llvm::dbgs() << " S: 0x" << Twine::utohexstr(S);
llvm::dbgs() << " A: 0x" << Twine::utohexstr(A);
llvm::dbgs() << " P: 0x" << Twine::utohexstr(P);
llvm::dbgs() << " result: 0x" << Twine::utohexstr((uint32_t)result) << "\n");

const uint32_t mask = 0xFFF;
applyArmReloc(location, (uint32_t)result & mask, mask);
}

std::error_code ARMTargetRelocationHandler::applyRelocation(
ELFWriter &writer, llvm::FileOutputBuffer &buf, const lld::AtomLayout &atom,
const Reference &ref) const {
Expand All @@ -426,7 +495,7 @@ std::error_code ARMTargetRelocationHandler::applyRelocation(

// Calculate proper initial addend for the relocation
const Reference::Addend addend =
readAddend(location, ref.kindValue());
readAddend(location, ref.kindValue()) + ref.addend();

// Flags that the relocation addresses Thumb instruction
bool addressesThumb = false;
Expand Down Expand Up @@ -492,6 +561,18 @@ std::error_code ARMTargetRelocationHandler::applyRelocation(
relocR_ARM_TLS_LE32(location, relocVAddress, targetVAddress, addend,
_armLayout.getTPOffset());
break;
case R_ARM_ALU_PC_G0_NC:
relocR_ARM_ALU_PC_G0_NC(location, relocVAddress, targetVAddress, addend);
break;
case R_ARM_ALU_PC_G1_NC:
relocR_ARM_ALU_PC_G1_NC(location, relocVAddress, targetVAddress, addend);
break;
case R_ARM_LDR_PC_G2:
relocR_ARM_LDR_PC_G2(location, relocVAddress, targetVAddress, addend);
break;
case R_ARM_IRELATIVE:
// Runtime only relocations. Ignore here.
break;
default:
return make_unhandled_reloc_error();
}
Expand Down
107 changes: 105 additions & 2 deletions lld/lib/ReaderWriter/ELF/ARM/ARMRelocationPass.cpp
Expand Up @@ -28,6 +28,7 @@ using namespace lld;
using namespace lld::elf;
using namespace llvm::ELF;

namespace {
// ARM B/BL instructions of static relocation veneer.
// TODO: consider different instruction set for archs below ARMv5
// (one as for Thumb may be used though it's less optimal).
Expand All @@ -48,7 +49,17 @@ static const uint8_t Veneer_THM_B_BL_StaticAtomContent[8] = {
// .got values
static const uint8_t ARMGotAtomContent[4] = {0};

namespace {
// .plt values (other entries)
static const uint8_t ARMPltAtomContent[12] = {
0x00, 0xc0, 0x8f,
0xe2, // add ip, pc, #offset[G0]
0x00, 0xc0, 0x8c,
0xe2, // add ip, ip, #offset[G1]

0x00, 0xf0, 0xbc,
0xe5, // ldr pc, [ip, #offset[G2]]!
};

/// \brief Atoms that hold veneer code.
class VeneerAtom : public SimpleELFDefinedAtom {
StringRef _section;
Expand Down Expand Up @@ -119,6 +130,15 @@ class ARMGOTAtom : public GOTAtom {
Alignment alignment() const override { return 4; }
};

class ARMPLTAtom : public PLTAtom {
public:
ARMPLTAtom(const File &f, StringRef secName) : PLTAtom(f, secName) {}

ArrayRef<uint8_t> rawContent() const override {
return llvm::makeArrayRef(ARMPltAtomContent);
}
};

class ELFPassFile : public SimpleFile {
public:
ELFPassFile(const ELFLinkingContext &eti) : SimpleFile("ELFPassFile") {
Expand All @@ -140,8 +160,16 @@ template <class Derived> class ARMRelocationPass : public Pass {
return;
assert(ref.kindArch() == Reference::KindArch::ARM);
switch (ref.kindValue()) {
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_TARGET1:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_ABS:
case R_ARM_THM_CALL:
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_THM_JUMP24:
static_cast<Derived *>(this)->handleIFUNC(ref);
static_cast<Derived *>(this)->handleVeneer(atom, ref);
break;
case R_ARM_TLS_IE32:
Expand All @@ -152,6 +180,15 @@ template <class Derived> class ARMRelocationPass : public Pass {

protected:
std::error_code handleVeneer(const DefinedAtom &atom, const Reference &ref) {
const auto kindValue = ref.kindValue();
switch (kindValue) {
case R_ARM_JUMP24:
case R_ARM_THM_JUMP24:
break;
default:
return std::error_code();
}

// Target symbol and relocated place should have different
// instruction sets in order a veneer to be generated in between.
const auto *target = dyn_cast<DefinedAtom>(ref.target());
Expand All @@ -163,7 +200,6 @@ template <class Derived> class ARMRelocationPass : public Pass {

// Veneers may only be generated for STT_FUNC target symbols
// or for symbols located in sections different to the place of relocation.
const auto kindValue = ref.kindValue();
StringRef secName = atom.customSectionName();
if (DefinedAtom::typeCode != target->contentType() &&
!target->customSectionName().equals(secName)) {
Expand Down Expand Up @@ -227,6 +263,65 @@ template <class Derived> class ARMRelocationPass : public Pass {
return g;
}

/// \brief Create a PLT entry referencing PLTGOT entry.
///
/// The function creates the PLT entry object and passes ownership
/// over it to the caller.
PLTAtom *createPLTforGOT(const GOTAtom *ga) {
auto pa = new (_file._alloc) ARMPLTAtom(_file, ".plt");
pa->addReferenceELF_ARM(R_ARM_ALU_PC_G0_NC, 0, ga, -8);
pa->addReferenceELF_ARM(R_ARM_ALU_PC_G1_NC, 4, ga, -4);
pa->addReferenceELF_ARM(R_ARM_LDR_PC_G2, 8, ga, 0);
return pa;
}

/// \brief get the PLT entry for a given IFUNC Atom.
///
/// If the entry does not exist. Both the GOT and PLT entry is created.
const PLTAtom *getIFUNCPLTEntry(const DefinedAtom *da) {
auto plt = _pltMap.find(da);
if (plt != _pltMap.end())
return plt->second;
auto ga = new (_file._alloc) ARMGOTAtom(_file, ".got.plt");
ga->addReferenceELF_ARM(R_ARM_ABS32, 0, da, 0);
ga->addReferenceELF_ARM(R_ARM_IRELATIVE, 0, da, 0);
auto pa = createPLTforGOT(ga);

#ifndef NDEBUG
ga->_name = "__got_ifunc_";
ga->_name += da->name();
pa->_name = "__plt_ifunc_";
pa->_name += da->name();
#endif

_gotMap[da] = ga;
_pltMap[da] = pa;
_gotVector.push_back(ga);
_pltVector.push_back(pa);
return pa;
}

/// \brief Handle adding of PLT entry by marking the reference
/// as requiring veneer generation.
std::error_code handlePLTEntry(Reference &ref, const PLTAtom *pa) {
ref.setTarget(pa);

return std::error_code();
}

/// \brief Redirect the call to the PLT stub for the target IFUNC.
///
/// This create a PLT and GOT entry for the IFUNC if one does not exist. The
/// GOT entry and a IRELATIVE relocation to the original target resolver.
std::error_code handleIFUNC(const Reference &ref) {
auto target = dyn_cast<const DefinedAtom>(ref.target());
if (target && target->contentType() == DefinedAtom::typeResolver) {
return handlePLTEntry(const_cast<Reference &>(ref),
getIFUNCPLTEntry(target));
}
return std::error_code();
}

public:
ARMRelocationPass(const ELFLinkingContext &ctx) : _file(ctx), _ctx(ctx) {}

Expand Down Expand Up @@ -278,6 +373,10 @@ template <class Derived> class ARMRelocationPass : public Pass {
got->setOrdinal(ordinal++);
mf->addAtom(*got);
}
for (auto &plt : _pltVector) {
plt->setOrdinal(ordinal++);
mf->addAtom(*plt);
}
for (auto &veneer : _veneerVector) {
veneer->setOrdinal(ordinal++);
mf->addAtom(*veneer);
Expand All @@ -292,11 +391,15 @@ template <class Derived> class ARMRelocationPass : public Pass {
/// \brief Map Atoms to their GOT entries.
llvm::DenseMap<const Atom *, GOTAtom *> _gotMap;

/// \brief Map Atoms to their PLT entries.
llvm::DenseMap<const Atom *, PLTAtom *> _pltMap;

/// \brief Map Atoms to their veneers.
llvm::DenseMap<const Atom *, VeneerAtom *> _veneerMap;

/// \brief the list of GOT/PLT atoms
std::vector<GOTAtom *> _gotVector;
std::vector<PLTAtom *> _pltVector;

/// \brief the list of veneer atoms.
std::vector<VeneerAtom *> _veneerVector;
Expand Down
9 changes: 8 additions & 1 deletion lld/lib/ReaderWriter/ELF/ELFFile.h
Expand Up @@ -316,6 +316,13 @@ template <class ELFT> class ELFFile : public File {
return symbol->st_value;
}

/// Returns initial addend
virtual Reference::Addend getInitialAddend(ArrayRef<uint8_t> symContent,
uint64_t symbolValue,
const Elf_Rel& reference) const {
return *(symContent.data() + reference.r_offset - symbolValue);
}

/// Process the common symbol and create an atom for it.
virtual ErrorOr<ELFCommonAtom<ELFT> *>
handleCommonSymbol(StringRef symName, const Elf_Sym *sym) {
Expand Down Expand Up @@ -1030,7 +1037,7 @@ void ELFFile<ELFT>::createRelocationReferences(const Elf_Sym *symbol,
auto elfRelocation = new (_readerStorage)
ELFReference<ELFT>(rel.r_offset - symValue, kindArch(),
rel.getType(isMips64EL), rel.getSymbol(isMips64EL));
int32_t addend = *(symContent.data() + rel.r_offset - symValue);
Reference::Addend addend = getInitialAddend(symContent, symValue, rel);
elfRelocation->setAddend(addend);
addReferenceToSymbol(elfRelocation, symbol);
_references.push_back(elfRelocation);
Expand Down

0 comments on commit 28074d6

Please sign in to comment.