blob: 4d94a793665c984af399ad093179830dc7ae4885 [file] [log] [blame]
//===- lib/ReaderWriter/ELF/AArch64/AArch64RelocationPass.cpp -------------===//
//
// The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Defines the relocation processing pass for AArch64. This includes
/// GOT and PLT entries, TLS, COPY, and ifunc.
///
/// This also includes additional behavior that gnu-ld and gold implement but
/// which is not specified anywhere.
///
//===----------------------------------------------------------------------===//
#include "AArch64RelocationPass.h"
#include "AArch64LinkingContext.h"
#include "Atoms.h"
#include "lld/Core/Simple.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
using namespace lld;
using namespace lld::elf;
using namespace llvm::ELF;
// .got values
static const uint8_t AArch64GotAtomContent[8] = {0};
// tls descriptor .got values, the layout is:
// struct tlsdesc {
// ptrdiff_t (*entry) (struct tlsdesc *);
// void *arg;
// };
static const uint8_t AArch64TlsdescGotAtomContent[16] = {0};
// .plt value (entry 0)
static const uint8_t AArch64Plt0AtomContent[32] = {
0xf0, 0x7b, 0xbf, 0xa9, // stp x16, x30, [sp,#-16]!
0x10, 0x00, 0x00, 0x90, // adrp x16, Page(eh_frame)
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16,#offset]
0x10, 0x02, 0x00, 0x91, // add x16, x16, #offset
0x20, 0x02, 0x1f, 0xd6, // br x17
0x1f, 0x20, 0x03, 0xd5, // nop
0x1f, 0x20, 0x03, 0xd5, // nop
0x1f, 0x20, 0x03, 0xd5 // nop
};
// .plt values (other entries)
static const uint8_t AArch64PltAtomContent[16] = {
0x10, 0x00, 0x00, 0x90, // adrp x16, PAGE(<GLOBAL_OFFSET_TABLE>)
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16,#offset]
0x10, 0x02, 0x00, 0x91, // add x16, x16, #offset
0x20, 0x02, 0x1f, 0xd6 // br x17
};
// .plt tlsdesc values
static const uint8_t AArch64PltTlsdescAtomContent[32] = {
0xe2, 0x0f, 0xbf, 0xa9, // stp x2, x3, [sp, #-16]
0x02, 0x00, 0x00, 0x90, // adpr x2, 0
0x03, 0x00, 0x00, 0x90, // adpr x3, 0
0x42, 0x00, 0x40, 0xf9, // ldr x2, [x2, #0]
0x63, 0x00, 0x00, 0x91, // add x3, x3, 0
0x40, 0x00, 0x1f, 0xd6, // br x2
0x1f, 0x20, 0x03, 0xd5, // nop
0x1f, 0x20, 0x03, 0xd5 // nop
};
namespace {
/// \brief Atoms that are used by AArch64 dynamic linking
class AArch64GOTAtom : public GOTAtom {
public:
AArch64GOTAtom(const File &f) : GOTAtom(f, ".got") {}
ArrayRef<uint8_t> rawContent() const override {
return ArrayRef<uint8_t>(AArch64GotAtomContent, 8);
}
protected:
// Constructor for AArch64GOTAtom
AArch64GOTAtom(const File &f, StringRef secName) : GOTAtom(f, secName) {}
};
class AArch64GOTPLTAtom : public AArch64GOTAtom {
public:
AArch64GOTPLTAtom(const File &f) : AArch64GOTAtom(f, ".got.plt") {}
};
class AArch64TLSDESCGOTAtom : public AArch64GOTPLTAtom {
public:
AArch64TLSDESCGOTAtom(const File &f) : AArch64GOTPLTAtom(f) {}
ArrayRef<uint8_t> rawContent() const override {
return ArrayRef<uint8_t>(AArch64TlsdescGotAtomContent, 16);
}
};
class AArch64PLT0Atom : public PLT0Atom {
public:
AArch64PLT0Atom(const File &f) : PLT0Atom(f) {}
ArrayRef<uint8_t> rawContent() const override {
return ArrayRef<uint8_t>(AArch64Plt0AtomContent, 32);
}
};
class AArch64PLTAtom : public PLTAtom {
public:
AArch64PLTAtom(const File &f) : PLTAtom(f, ".plt") {}
ArrayRef<uint8_t> rawContent() const override {
return ArrayRef<uint8_t>(AArch64PltAtomContent, 16);
}
};
class AArch64PLTTLSDESCAtom : public PLTAtom {
public:
AArch64PLTTLSDESCAtom(const File &f) : PLTAtom(f, ".plt") {}
ArrayRef<uint8_t> rawContent() const override {
return ArrayRef<uint8_t>(AArch64PltTlsdescAtomContent, 32);
}
};
class ELFPassFile : public SimpleFile {
public:
ELFPassFile(const ELFLinkingContext &eti) : SimpleFile("ELFPassFile") {
setOrdinal(eti.getNextOrdinalAndIncrement());
}
llvm::BumpPtrAllocator _alloc;
};
/// \brief CRTP base for handling relocations.
template <class Derived> class AArch64RelocationPass : public Pass {
/// \brief Handle a specific reference.
void handleReference(const DefinedAtom &atom, const Reference &ref) {
DEBUG_WITH_TYPE(
"AArch64", llvm::dbgs()
<< "\t" << LLVM_FUNCTION_NAME << "()"
<< ": Name of Defined Atom: " << atom.name().str();
llvm::dbgs() << " kindValue: " << ref.kindValue() << "\n");
if (ref.kindNamespace() != Reference::KindNamespace::ELF)
return;
assert(ref.kindArch() == Reference::KindArch::AArch64);
switch (ref.kindValue()) {
case R_AARCH64_ABS32:
case R_AARCH64_ABS16:
case R_AARCH64_ABS64:
case R_AARCH64_PREL16:
case R_AARCH64_PREL32:
case R_AARCH64_PREL64:
static_cast<Derived *>(this)->handlePlain(ref);
break;
case R_AARCH64_GOTREL32:
case R_AARCH64_GOTREL64:
static_cast<Derived *>(this)->handleGOT(ref);
break;
case R_AARCH64_ADR_PREL_PG_HI21:
static_cast<Derived *>(this)->handlePlain(ref);
break;
case R_AARCH64_LDST8_ABS_LO12_NC:
case R_AARCH64_LDST16_ABS_LO12_NC:
case R_AARCH64_LDST32_ABS_LO12_NC:
case R_AARCH64_LDST64_ABS_LO12_NC:
case R_AARCH64_LDST128_ABS_LO12_NC:
static_cast<Derived *>(this)->handlePlain(ref);
break;
case R_AARCH64_ADD_ABS_LO12_NC:
static_cast<Derived *>(this)->handlePlain(ref);
break;
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
case R_AARCH64_CONDBR19:
static_cast<Derived *>(this)->handlePlain(ref);
break;
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
static_cast<Derived *>(this)->handlePlain(ref);
break;
case R_AARCH64_ADR_GOT_PAGE:
case R_AARCH64_LD64_GOT_LO12_NC:
static_cast<Derived *>(this)->handleGOT(ref);
break;
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
static_cast<Derived *>(this)->handleGOTTPREL(ref);
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12_NC:
case R_AARCH64_TLSDESC_ADD_LO12_NC:
static_cast<Derived *>(this)->handleTLSDESC(ref);
break;
}
}
protected:
/// \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) AArch64GOTPLTAtom(_file);
ga->addReferenceELF_AArch64(R_AARCH64_IRELATIVE, 0, da, 0);
auto pa = new (_file._alloc) AArch64PLTAtom(_file);
pa->addReferenceELF_AArch64(R_AARCH64_PREL32, 2, ga, -4);
#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 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_or_null<const DefinedAtom>(ref.target());
if (target && target->contentType() == DefinedAtom::typeResolver)
const_cast<Reference &>(ref).setTarget(getIFUNCPLTEntry(target));
return std::error_code();
}
/// \brief Create a GOT entry for the TP offset of a TLS atom.
const GOTAtom *getGOTTPREL(const Atom *atom) {
auto got = _gotMap.find(atom);
if (got == _gotMap.end()) {
auto g = new (_file._alloc) AArch64GOTAtom(_file);
g->addReferenceELF_AArch64(R_AARCH64_TLS_TPREL64, 0, atom, 0);
#ifndef NDEBUG
g->_name = "__got_tls_";
g->_name += atom->name();
#endif
_gotMap[atom] = g;
_gotVector.push_back(g);
return g;
}
return got->second;
}
/// \brief Create a GOT TPREL entry to local or external TLS variable.
std::error_code handleGOTTPREL(const Reference &ref) {
if (isa<DefinedAtom>(ref.target()) ||
isa<SharedLibraryAtom>(ref.target()))
const_cast<Reference &>(ref).setTarget(getGOTTPREL(ref.target()));
return std::error_code();
}
/// \brief Generates a double GOT entry with R_AARCH64_TLSDESC dynamic
/// relocation reference. Since the dynamic relocation is resolved
/// lazily so the GOT associated should be in .got.plt.
const GOTAtom *getTLSDESCPLTEntry(const Atom *da) {
auto got = _gotMap.find(da);
if (got != _gotMap.end())
return got->second;
auto ga = new (_file._alloc) AArch64TLSDESCGOTAtom(_file);
ga->addReferenceELF_AArch64(R_AARCH64_TLSDESC, 0, da, 0);
auto pa = new (_file._alloc) AArch64PLTTLSDESCAtom(_file);
pa->addReferenceELF_AArch64(R_AARCH64_ADR_PREL_PG_HI21, 4, ga, 0);
pa->addReferenceELF_AArch64(R_AARCH64_ADR_PREL_PG_HI21, 8, ga, 0);
pa->addReferenceELF_AArch64(R_AARCH64_LDST64_ABS_LO12_NC, 12, ga, 0);
pa->addReferenceELF_AArch64(R_AARCH64_ADD_ABS_LO12_NC, 16, ga, 0);
#ifndef NDEBUG
ga->_name = "__got_tlsdesc_";
ga->_name += da->name();
pa->_name = "__plt_tlsdesc_";
pa->_name += da->name();
#endif
_gotMap[da] = ga;
_pltMap[da] = pa;
_tlsdescVector.push_back(ga);
_pltVector.push_back(pa);
return ga;
}
std::error_code handleTLSDESC(const Reference &ref) {
if (isa<DefinedAtom>(ref.target()) ||
isa<SharedLibraryAtom>(ref.target())) {
const_cast<Reference &>(ref).setTarget(getTLSDESCPLTEntry(ref.target()));
}
return std::error_code();
}
/// \brief Create a GOT entry containing 0.
const GOTAtom *getNullGOT() {
if (!_null) {
_null = new (_file._alloc) AArch64GOTPLTAtom(_file);
#ifndef NDEBUG
_null->_name = "__got_null";
#endif
}
return _null;
}
const GOTAtom *getGOT(const DefinedAtom *da) {
auto got = _gotMap.find(da);
if (got == _gotMap.end()) {
auto g = new (_file._alloc) AArch64GOTAtom(_file);
g->addReferenceELF_AArch64(R_AARCH64_ABS64, 0, da, 0);
#ifndef NDEBUG
g->_name = "__got_";
g->_name += da->name();
#endif
_gotMap[da] = g;
_gotVector.push_back(g);
return g;
}
return got->second;
}
public:
AArch64RelocationPass(const ELFLinkingContext &ctx) : _file(ctx), _ctx(ctx) {}
/// \brief Do the pass.
///
/// The goal here is to first process each reference individually. Each call
/// to handleReference may modify the reference itself and/or create new
/// atoms which must be stored in one of the maps below.
///
/// After all references are handled, the atoms created during that are all
/// added to mf.
std::error_code perform(SimpleFile &mf) override {
ScopedTask task(getDefaultDomain(), "AArch64 GOT/PLT Pass");
DEBUG_WITH_TYPE(
"AArch64", llvm::dbgs() << "Undefined Atoms"
<< "\n";
for (const auto &atom
: mf.undefined()) {
llvm::dbgs() << " Name of Atom: " << atom->name().str() << "\n";
} llvm::dbgs()
<< "Shared Library Atoms"
<< "\n";
for (const auto &atom
: mf.sharedLibrary()) {
llvm::dbgs() << " Name of Atom: " << atom->name().str() << "\n";
} llvm::dbgs()
<< "Absolute Atoms"
<< "\n";
for (const auto &atom
: mf.absolute()) {
llvm::dbgs() << " Name of Atom: " << atom->name().str() << "\n";
}
// Process all references.
llvm::dbgs()
<< "Defined Atoms"
<< "\n");
for (const auto &atom : mf.defined()) {
for (const auto &ref : *atom) {
handleReference(*atom, *ref);
}
}
// Add all created atoms to the link.
uint64_t ordinal = 0;
if (_plt0) {
_plt0->setOrdinal(ordinal++);
mf.addAtom(*_plt0);
}
for (auto &plt : _pltVector) {
plt->setOrdinal(ordinal++);
mf.addAtom(*plt);
}
if (_null) {
_null->setOrdinal(ordinal++);
mf.addAtom(*_null);
}
if (_plt0) {
_got0->setOrdinal(ordinal++);
_got1->setOrdinal(ordinal++);
mf.addAtom(*_got0);
mf.addAtom(*_got1);
}
for (auto &got : _gotVector) {
got->setOrdinal(ordinal++);
mf.addAtom(*got);
}
// Add any tlsdesc GOT relocation after default PLT and iFUNC entries.
for (auto &tlsdesc : _tlsdescVector) {
tlsdesc->setOrdinal(ordinal++);
mf.addAtom(*tlsdesc);
}
for (auto obj : _objectVector) {
obj->setOrdinal(ordinal++);
mf.addAtom(*obj);
}
return std::error_code();
}
protected:
/// \brief Owner of all the Atoms created by this pass.
ELFPassFile _file;
const ELFLinkingContext &_ctx;
/// \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 Object entries.
llvm::DenseMap<const Atom *, ObjectAtom *> _objectMap;
/// \brief the list of GOT/PLT atoms
std::vector<GOTAtom *> _gotVector;
std::vector<GOTAtom *> _tlsdescVector;
std::vector<PLTAtom *> _pltVector;
std::vector<ObjectAtom *> _objectVector;
/// \brief GOT entry that is always 0. Used for undefined weaks.
GOTAtom *_null = nullptr;
/// \brief The got and plt entries for .PLT0. This is used to call into the
/// dynamic linker for symbol resolution.
/// @{
PLT0Atom *_plt0 = nullptr;
GOTAtom *_got0 = nullptr;
GOTAtom *_got1 = nullptr;
/// @}
};
/// This implements the static relocation model. Meaning GOT and PLT entries are
/// not created for references that can be directly resolved. These are
/// converted to a direct relocation. For entries that do require a GOT or PLT
/// entry, that entry is statically bound.
///
/// TLS always assumes module 1 and attempts to remove indirection.
class AArch64StaticRelocationPass final
: public AArch64RelocationPass<AArch64StaticRelocationPass> {
public:
AArch64StaticRelocationPass(const elf::AArch64LinkingContext &ctx)
: AArch64RelocationPass(ctx) {}
std::error_code handlePlain(const Reference &ref) { return handleIFUNC(ref); }
std::error_code handlePLT32(const Reference &ref) {
// __tls_get_addr is handled elsewhere.
if (ref.target() && ref.target()->name() == "__tls_get_addr") {
const_cast<Reference &>(ref).setKindValue(R_AARCH64_NONE);
return std::error_code();
}
// Static code doesn't need PLTs.
const_cast<Reference &>(ref).setKindValue(R_AARCH64_PREL32);
// Handle IFUNC.
if (const DefinedAtom *da =
dyn_cast_or_null<const DefinedAtom>(ref.target()))
if (da->contentType() == DefinedAtom::typeResolver)
return handleIFUNC(ref);
return std::error_code();
}
std::error_code handleGOT(const Reference &ref) {
if (isa<UndefinedAtom>(ref.target()))
const_cast<Reference &>(ref).setTarget(getNullGOT());
else if (const DefinedAtom *da = dyn_cast<const DefinedAtom>(ref.target()))
const_cast<Reference &>(ref).setTarget(getGOT(da));
return std::error_code();
}
};
class AArch64DynamicRelocationPass final
: public AArch64RelocationPass<AArch64DynamicRelocationPass> {
public:
AArch64DynamicRelocationPass(const elf::AArch64LinkingContext &ctx)
: AArch64RelocationPass(ctx) {}
const PLT0Atom *getPLT0() {
if (_plt0)
return _plt0;
// Fill in the null entry.
getNullGOT();
_plt0 = new (_file._alloc) AArch64PLT0Atom(_file);
_got0 = new (_file._alloc) AArch64GOTPLTAtom(_file);
_got1 = new (_file._alloc) AArch64GOTPLTAtom(_file);
_plt0->addReferenceELF_AArch64(R_AARCH64_ADR_GOT_PAGE, 4, _got0, 0);
_plt0->addReferenceELF_AArch64(R_AARCH64_LD64_GOT_LO12_NC, 8, _got1, 0);
_plt0->addReferenceELF_AArch64(ADD_AARCH64_GOTRELINDEX, 12, _got1, 0);
#ifndef NDEBUG
_plt0->_name = "__PLT0";
_got0->_name = "__got0";
_got1->_name = "__got1";
#endif
return _plt0;
}
const PLTAtom *getPLTEntry(const Atom *a) {
auto plt = _pltMap.find(a);
if (plt != _pltMap.end())
return plt->second;
auto ga = new (_file._alloc) AArch64GOTPLTAtom(_file);
ga->addReferenceELF_AArch64(R_AARCH64_JUMP_SLOT, 0, a, 0);
auto pa = new (_file._alloc) AArch64PLTAtom(_file);
pa->addReferenceELF_AArch64(R_AARCH64_ADR_GOT_PAGE, 0, ga, 0);
pa->addReferenceELF_AArch64(R_AARCH64_LD64_GOT_LO12_NC, 4, ga, 0);
pa->addReferenceELF_AArch64(ADD_AARCH64_GOTRELINDEX, 8, ga, 0);
pa->addReferenceELF_AArch64(R_AARCH64_NONE, 12, getPLT0(), 0);
// Set the starting address of the got entry to the first instruction in
// the plt0 entry.
ga->addReferenceELF_AArch64(R_AARCH64_ABS32, 0, getPLT0(), 0);
#ifndef NDEBUG
ga->_name = "__got_";
ga->_name += a->name();
pa->_name = "__plt_";
pa->_name += a->name();
#endif
_gotMap[a] = ga;
_pltMap[a] = pa;
_gotVector.push_back(ga);
_pltVector.push_back(pa);
return pa;
}
const ObjectAtom *getObjectEntry(const SharedLibraryAtom *a) {
auto obj = _objectMap.find(a);
if (obj != _objectMap.end())
return obj->second;
auto oa = new (_file._alloc) ObjectAtom(_file);
// This needs to point to the atom that we just created.
oa->addReferenceELF_AArch64(R_AARCH64_COPY, 0, oa, 0);
oa->_name = a->name();
oa->_size = a->size();
_objectMap[a] = oa;
_objectVector.push_back(oa);
return oa;
}
std::error_code handlePlain(const Reference &ref) {
if (!ref.target())
return std::error_code();
if (auto sla = dyn_cast<SharedLibraryAtom>(ref.target())) {
if (sla->type() == SharedLibraryAtom::Type::Data)
const_cast<Reference &>(ref).setTarget(getObjectEntry(sla));
else if (sla->type() == SharedLibraryAtom::Type::Code)
const_cast<Reference &>(ref).setTarget(getPLTEntry(sla));
} else
return handleIFUNC(ref);
return std::error_code();
}
std::error_code handlePLT32(const Reference &ref) {
// Turn this into a PC32 to the PLT entry.
const_cast<Reference &>(ref).setKindValue(R_AARCH64_PREL32);
// Handle IFUNC.
if (const DefinedAtom *da =
dyn_cast_or_null<const DefinedAtom>(ref.target()))
if (da->contentType() == DefinedAtom::typeResolver)
return handleIFUNC(ref);
if (isa<const SharedLibraryAtom>(ref.target()))
const_cast<Reference &>(ref).setTarget(getPLTEntry(ref.target()));
return std::error_code();
}
const GOTAtom *getSharedGOT(const SharedLibraryAtom *sla) {
auto got = _gotMap.find(sla);
if (got == _gotMap.end()) {
auto g = new (_file._alloc) AArch64GOTAtom(_file);
g->addReferenceELF_AArch64(R_AARCH64_GLOB_DAT, 0, sla, 0);
#ifndef NDEBUG
g->_name = "__got_";
g->_name += sla->name();
#endif
_gotMap[sla] = g;
_gotVector.push_back(g);
return g;
}
return got->second;
}
std::error_code handleGOT(const Reference &ref) {
if (isa<UndefinedAtom>(ref.target()))
const_cast<Reference &>(ref).setTarget(getNullGOT());
else if (const DefinedAtom *da = dyn_cast<const DefinedAtom>(ref.target()))
const_cast<Reference &>(ref).setTarget(getGOT(da));
else if (const auto sla = dyn_cast<const SharedLibraryAtom>(ref.target()))
const_cast<Reference &>(ref).setTarget(getSharedGOT(sla));
return std::error_code();
}
};
} // end anon namespace
std::unique_ptr<Pass>
lld::elf::createAArch64RelocationPass(const AArch64LinkingContext &ctx) {
switch (ctx.getOutputELFType()) {
case llvm::ELF::ET_EXEC:
if (ctx.isDynamic())
return llvm::make_unique<AArch64DynamicRelocationPass>(ctx);
return llvm::make_unique<AArch64StaticRelocationPass>(ctx);
case llvm::ELF::ET_DYN:
return llvm::make_unique<AArch64DynamicRelocationPass>(ctx);
case llvm::ELF::ET_REL:
return nullptr;
default:
llvm_unreachable("Unhandled output file type");
}
}