//===- lib/ReaderWriter/MachO/GOTPass.cpp -----------------------*- C++ -*-===// // // The LLVM Linker // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// /// /// \file /// This linker pass transforms all GOT kind references to real references. /// That is, in assembly you can write something like: /// movq foo@GOTPCREL(%rip), %rax /// which means you want to load a pointer to "foo" out of the GOT (global /// Offsets Table). In the object file, the Atom containing this instruction /// has a Reference whose target is an Atom named "foo" and the Reference /// kind is a GOT load. The linker needs to instantiate a pointer sized /// GOT entry. This is done be creating a GOT Atom to represent that pointer /// sized data in this pass, and altering the Atom graph so the Reference now /// points to the GOT Atom entry (corresponding to "foo") and changing the /// Reference Kind to reflect it is now pointing to a GOT entry (rather /// then needing a GOT entry). /// /// There is one optimization the linker can do here. If the target of the GOT /// is in the same linkage unit and does not need to be interposable, and /// the GOT use is just a load (not some other operation), this pass can /// transform that load into an LEA (add). This optimizes away one memory load /// which at runtime that could stall the pipeline. This optimization only /// works for architectures in which a (GOT) load instruction can be change to /// an LEA instruction that is the same size. The method isGOTAccess() should /// only return true for "canBypassGOT" if this optimization is supported. /// //===----------------------------------------------------------------------===// #include "ArchHandler.h" #include "File.h" #include "MachOPasses.h" #include "lld/Common/LLVM.h" #include "lld/Core/DefinedAtom.h" #include "lld/Core/File.h" #include "lld/Core/Reference.h" #include "lld/Core/Simple.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" namespace lld { namespace mach_o { // // GOT Entry Atom created by the GOT pass. // class GOTEntryAtom : public SimpleDefinedAtom { public: GOTEntryAtom(const File &file, bool is64, StringRef name) : SimpleDefinedAtom(file), _is64(is64), _name(name) { } ~GOTEntryAtom() override = default; ContentType contentType() const override { return DefinedAtom::typeGOT; } Alignment alignment() const override { return _is64 ? 8 : 4; } uint64_t size() const override { return _is64 ? 8 : 4; } ContentPermissions permissions() const override { return DefinedAtom::permRW_; } ArrayRef rawContent() const override { static const uint8_t zeros[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; return llvm::makeArrayRef(zeros, size()); } StringRef slotName() const { return _name; } private: const bool _is64; StringRef _name; }; /// Pass for instantiating and optimizing GOT slots. /// class GOTPass : public Pass { public: GOTPass(const MachOLinkingContext &context) : _ctx(context), _archHandler(_ctx.archHandler()), _file(*_ctx.make_file("")) { _file.setOrdinal(_ctx.getNextOrdinalAndIncrement()); } private: llvm::Error perform(SimpleFile &mergedFile) override { // Scan all references in all atoms. for (const DefinedAtom *atom : mergedFile.defined()) { for (const Reference *ref : *atom) { // Look at instructions accessing the GOT. bool canBypassGOT; if (!_archHandler.isGOTAccess(*ref, canBypassGOT)) continue; const Atom *target = ref->target(); assert(target != nullptr); if (!shouldReplaceTargetWithGOTAtom(target, canBypassGOT)) { // Update reference kind to reflect that target is a direct accesss. _archHandler.updateReferenceToGOT(ref, false); } else { // Replace the target with a reference to a GOT entry. const DefinedAtom *gotEntry = makeGOTEntry(target); const_cast(ref)->setTarget(gotEntry); // Update reference kind to reflect that target is now a GOT entry. _archHandler.updateReferenceToGOT(ref, true); } } } // Sort and add all created GOT Atoms to master file std::vector entries; entries.reserve(_targetToGOT.size()); for (auto &it : _targetToGOT) entries.push_back(it.second); std::sort(entries.begin(), entries.end(), [](const GOTEntryAtom *left, const GOTEntryAtom *right) { return (left->slotName().compare(right->slotName()) < 0); }); for (const GOTEntryAtom *slot : entries) mergedFile.addAtom(*slot); return llvm::Error::success(); } bool shouldReplaceTargetWithGOTAtom(const Atom *target, bool canBypassGOT) { // Accesses to shared library symbols must go through GOT. if (isa(target)) return true; // Accesses to interposable symbols in same linkage unit must also go // through GOT. const DefinedAtom *defTarget = dyn_cast(target); if (defTarget != nullptr && defTarget->interposable() != DefinedAtom::interposeNo) { assert(defTarget->scope() != DefinedAtom::scopeTranslationUnit); return true; } // Target does not require indirection. So, if instruction allows GOT to be // by-passed, do that optimization and don't create GOT entry. return !canBypassGOT; } const DefinedAtom *makeGOTEntry(const Atom *target) { auto pos = _targetToGOT.find(target); if (pos == _targetToGOT.end()) { auto *gotEntry = new (_file.allocator()) GOTEntryAtom(_file, _ctx.is64Bit(), target->name()); _targetToGOT[target] = gotEntry; const ArchHandler::ReferenceInfo &nlInfo = _archHandler.stubInfo(). nonLazyPointerReferenceToBinder; gotEntry->addReference(Reference::KindNamespace::mach_o, nlInfo.arch, nlInfo.kind, 0, target, 0); return gotEntry; } return pos->second; } const MachOLinkingContext &_ctx; mach_o::ArchHandler &_archHandler; MachOFile &_file; llvm::DenseMap _targetToGOT; }; void addGOTPass(PassManager &pm, const MachOLinkingContext &ctx) { assert(ctx.needsGOTPass()); pm.add(llvm::make_unique(ctx)); } } // end namesapce mach_o } // end namesapce lld