//===- 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() 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 rawContent() const override { return ArrayRef(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 rawContent() const override { return ArrayRef(AArch64TlsdescGotAtomContent, 16); } }; class AArch64PLT0Atom : public PLT0Atom { public: AArch64PLT0Atom(const File &f) : PLT0Atom(f) {} ArrayRef rawContent() const override { return ArrayRef(AArch64Plt0AtomContent, 32); } }; class AArch64PLTAtom : public PLTAtom { public: AArch64PLTAtom(const File &f) : PLTAtom(f, ".plt") {} ArrayRef rawContent() const override { return ArrayRef(AArch64PltAtomContent, 16); } }; class AArch64PLTTLSDESCAtom : public PLTAtom { public: AArch64PLTTLSDESCAtom(const File &f) : PLTAtom(f, ".plt") {} ArrayRef rawContent() const override { return ArrayRef(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 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(this)->handlePlain(ref); break; case R_AARCH64_GOTREL32: case R_AARCH64_GOTREL64: static_cast(this)->handleGOT(ref); break; case R_AARCH64_ADR_PREL_PG_HI21: static_cast(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(this)->handlePlain(ref); break; case R_AARCH64_ADD_ABS_LO12_NC: static_cast(this)->handlePlain(ref); break; case R_AARCH64_CALL26: case R_AARCH64_JUMP26: case R_AARCH64_CONDBR19: static_cast(this)->handlePlain(ref); break; case R_AARCH64_TLSLE_ADD_TPREL_HI12: case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: static_cast(this)->handlePlain(ref); break; case R_AARCH64_ADR_GOT_PAGE: case R_AARCH64_LD64_GOT_LO12_NC: static_cast(this)->handleGOT(ref); break; case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: static_cast(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(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(ref.target()); if (target && target->contentType() == DefinedAtom::typeResolver) const_cast(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(ref.target()) || isa(ref.target())) const_cast(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(ref.target()) || isa(ref.target())) { const_cast(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 _gotMap; /// \brief Map Atoms to their PLT entries. llvm::DenseMap _pltMap; /// \brief Map Atoms to their Object entries. llvm::DenseMap _objectMap; /// \brief the list of GOT/PLT atoms std::vector _gotVector; std::vector _tlsdescVector; std::vector _pltVector; std::vector _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 { 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(ref).setKindValue(R_AARCH64_NONE); return std::error_code(); } // Static code doesn't need PLTs. const_cast(ref).setKindValue(R_AARCH64_PREL32); // Handle IFUNC. if (const DefinedAtom *da = dyn_cast_or_null(ref.target())) if (da->contentType() == DefinedAtom::typeResolver) return handleIFUNC(ref); return std::error_code(); } std::error_code handleGOT(const Reference &ref) { if (isa(ref.target())) const_cast(ref).setTarget(getNullGOT()); else if (const DefinedAtom *da = dyn_cast(ref.target())) const_cast(ref).setTarget(getGOT(da)); return std::error_code(); } }; class AArch64DynamicRelocationPass final : public AArch64RelocationPass { 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(ref.target())) { if (sla->type() == SharedLibraryAtom::Type::Data) const_cast(ref).setTarget(getObjectEntry(sla)); else if (sla->type() == SharedLibraryAtom::Type::Code) const_cast(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(ref).setKindValue(R_AARCH64_PREL32); // Handle IFUNC. if (const DefinedAtom *da = dyn_cast_or_null(ref.target())) if (da->contentType() == DefinedAtom::typeResolver) return handleIFUNC(ref); if (isa(ref.target())) const_cast(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(ref.target())) const_cast(ref).setTarget(getNullGOT()); else if (const DefinedAtom *da = dyn_cast(ref.target())) const_cast(ref).setTarget(getGOT(da)); else if (const auto sla = dyn_cast(ref.target())) const_cast(ref).setTarget(getSharedGOT(sla)); return std::error_code(); } }; } // end anon namespace std::unique_ptr lld::elf::createAArch64RelocationPass(const AArch64LinkingContext &ctx) { switch (ctx.getOutputELFType()) { case llvm::ELF::ET_EXEC: if (ctx.isDynamic()) return llvm::make_unique(ctx); return llvm::make_unique(ctx); case llvm::ELF::ET_DYN: return llvm::make_unique(ctx); case llvm::ELF::ET_REL: return nullptr; default: llvm_unreachable("Unhandled output file type"); } }