#include #include #include #include #include #include #include #include #include #include "elf.hpp" struct ObjectRepository; struct Scope; struct Loader; struct SharedObject; struct ObjectSymbol; struct SymbolVersion; extern uint64_t rtsCounter; enum class TlsModel { null, initial, dynamic }; enum class LinkerError { success, notFound, fileTooShort, notElf, wrongElfType, outOfMemory, invalidProgramHeader }; uint32_t elf64Hash(frg::string_view string); // -------------------------------------------------------- // ObjectRepository // -------------------------------------------------------- struct ObjectRepository { ObjectRepository(); ObjectRepository(const ObjectRepository &) = delete; ObjectRepository &operator= (const ObjectRepository &) = delete; // This is primarily used to create a SharedObject for the RTLD itself. SharedObject *injectObjectFromDts(frg::string_view name, frg::string path, uintptr_t base_address, elf_dyn *dynamic, uint64_t rts); // This is used to create a SharedObject for the executable that we want to link. SharedObject *injectObjectFromPhdrs(frg::string_view name, frg::string path, void *phdr_pointer, size_t phdr_entry_size, size_t num_phdrs, void *entry_pointer, uint64_t rts); SharedObject *injectStaticObject(frg::string_view name, frg::string path, void *phdr_pointer, size_t phdr_entry_size, size_t num_phdrs, void *entry_pointer, uint64_t rts); frg::expected requestObjectWithName(frg::string_view name, SharedObject *origin, Scope *localScope, bool createScope, uint64_t rts); frg::expected requestObjectAtPath(frg::string_view path, Scope *localScope, bool createScope, uint64_t rts); void discoverDependenciesFromLoadedObject(SharedObject *object); SharedObject *findCaller(void *address); SharedObject *findLoadedObject(frg::string_view name); void addObjectToDestructQueue(SharedObject *object); void destructObjects(); // Used by dl_iterate_phdr: stores objects in the order they are loaded. frg::vector loadedObjects; // Used for breadth-first searching dependencies. frg::vector dependencyQueue; private: void _fetchFromPhdrs(SharedObject *object, void *phdr_pointer, size_t phdr_entry_size, size_t num_phdrs, void *entry_pointer); frg::expected _fetchFromFile(SharedObject *object, int fd); void _parseDynamic(SharedObject *object); void _parseVerdef(SharedObject *object); void _parseVerneed(SharedObject *object); void _discoverDependencies(SharedObject *object, Scope *localScope, uint64_t rts); void _addLoadedObject(SharedObject *object); frg::hash_map, MemoryAllocator> _nameMap; // Used for destructing the objects, stores all the objects in the order they are initialized. frg::stack _destructQueue; }; // -------------------------------------------------------- // SharedObject // -------------------------------------------------------- enum class HashStyle { none, systemV, gnu }; struct GnuHashTableHeader { uint32_t nBuckets; uint32_t symbolOffset; uint32_t bloomSize; uint32_t bloomShift; }; using InitFuncPtr = void (*)(); // The ABI of this struct is fixed by GDB struct DebugInterface { int ver; void *head; void (*brk)(void); int state; void *base; }; // The ABI of this struct is fixed by GDB struct LinkMap { uintptr_t base = 0; const char *name = nullptr; elf_dyn *dynv = nullptr; LinkMap *next = nullptr, *prev = nullptr; }; struct SharedObject { // path is copied SharedObject(const char *name, frg::string path, bool is_main_object, Scope *localScope, uint64_t object_rts); SharedObject(const char *name, const char *path, bool is_main_object, Scope *localScope, uint64_t object_rts); frg::string name; frg::string path; frg::string interpreterPath; const char *soName; bool isMainObject; uint64_t objectRts; // link map for debugging LinkMap linkMap; bool inLinkMap; // base address this shared object was loaded to uintptr_t baseAddress; Scope *localScope; // pointers to the dynamic table, GOT and entry point elf_dyn *dynamic = nullptr; void **globalOffsetTable; void *entry; // object initialization information InitFuncPtr initPtr = nullptr; InitFuncPtr finiPtr = nullptr; InitFuncPtr *initArray = nullptr; InitFuncPtr *finiArray = nullptr; InitFuncPtr *preInitArray = nullptr; size_t initArraySize = 0; size_t finiArraySize = 0; size_t preInitArraySize = 0; // TODO: read this from the PHDR size_t tlsSegmentSize, tlsAlignment, tlsImageSize; void *tlsImagePtr; bool tlsInitialized; // symbol and string table of this shared object HashStyle hashStyle = HashStyle::none; uintptr_t hashTableOffset; uintptr_t symbolTableOffset; uintptr_t stringTableOffset; // Version tables of this shared object uintptr_t versionTableOffset = 0; uintptr_t versionDefinitionTableOffset = 0; size_t versionDefinitionCount = 0; uintptr_t versionRequirementTableOffset = 0; size_t versionRequirementCount = 0; // Versions we know about for this object's VERSYM. frg::hash_map< elf_version, SymbolVersion, frg::hash, MemoryAllocator > knownVersions; // Versions that this object defines. frg::vector definedVersions; const char *runPath = nullptr; // save the lazy JUMP_SLOT relocation table uintptr_t lazyRelocTableOffset; size_t lazyTableSize; frg::optional lazyExplicitAddend; bool symbolicResolution; bool eagerBinding; bool haveStaticTls; // vector of dependencies frg::vector dependencies; TlsModel tlsModel; size_t tlsIndex; ssize_t tlsOffset; uint64_t globalRts; bool wasLinked; bool scheduledForInit; bool onInitStack; bool wasInitialized; bool wasDestroyed = false; bool wasVisited = false; bool dependenciesDiscovered = false; // PHDR related stuff, we only set these for the main executable void *phdrPointer = nullptr; size_t phdrEntrySize = 0; size_t phdrCount = 0; frg::tuple getSymbolByIndex(size_t index); }; struct Relocation { Relocation(SharedObject *object, elf_rela *r) : object_{object}, type_{Addend::Explicit} { offset_ = r->r_offset; info_ = r->r_info; addend_ = r->r_addend; } Relocation(SharedObject *object, elf_rel *r) : object_{object}, type_{Addend::Implicit} { offset_ = r->r_offset; info_ = r->r_info; } SharedObject *object() { return object_; } elf_info type() const { return ELF_R_TYPE(info_); } elf_info symbol_index() const { return ELF_R_SYM(info_); } elf_addr addend_rel() { switch(type_) { case Addend::Explicit: return addend_; case Addend::Implicit: { auto ptr = reinterpret_cast(object_->baseAddress + offset_); return *ptr; } } __builtin_unreachable(); } elf_addr addend_norel() { switch(type_) { case Addend::Explicit: return addend_; case Addend::Implicit: return 0; } __builtin_unreachable(); } void *destination() { return reinterpret_cast(object_->baseAddress + offset_); } void relocate(elf_addr addr) { auto ptr = destination(); memcpy(ptr, &addr, sizeof(addr)); } private: enum class Addend { Implicit, Explicit }; SharedObject *object_; Addend type_; elf_addr offset_; elf_info info_; elf_addend addend_ = 0; }; void processCopyRelocations(SharedObject *object); // -------------------------------------------------------- // RuntimeTlsMap // -------------------------------------------------------- struct RuntimeTlsMap { RuntimeTlsMap(); // Amount of initialLimit that has already been allocated. size_t initialPtr; // Size of the inital TLS segment. size_t initialLimit; // TLS indices. frg::vector indices; }; extern frg::manual_box runtimeTlsMap; Tcb *allocateTcb(); void initTlsObjects(Tcb *tcb, const frg::vector &objects, bool checkInitialized); void *accessDtv(SharedObject *object); // Tries to access the DTV, if not allocated, or object doesn't have // PT_TLS, return nullptr. void *tryAccessDtv(SharedObject *object); // -------------------------------------------------------- // ObjectSymbol // -------------------------------------------------------- struct ObjectSymbol { ObjectSymbol(SharedObject *object, const elf_sym *symbol); SharedObject *object() { return _object; } const elf_sym *symbol() { return _symbol; } const char *getString(); uintptr_t virtualAddress(); size_t size(); // returns whether the address refers to the symbol bool contains(uintptr_t addr); private: SharedObject *_object; const elf_sym *_symbol; }; frg::optional resolveInObject(SharedObject *object, frg::string_view string, frg::optional version); // -------------------------------------------------------- // SymbolVersion // -------------------------------------------------------- struct SymbolVersion { SymbolVersion(const char *name, uint32_t hash) : _local{false}, _global{false}, _default{false} , _name{name}, _hash{hash} { } SymbolVersion(int idx) : _local{idx == 0}, _global{idx == 1}, _default{false} , _name{""}, _hash{0} { } SymbolVersion(const char *name) : _local{false}, _global{false}, _default{false} , _name{name}, _hash{elf64Hash(name)} { } bool isLocal() const { return _local; } bool isGlobal() const { return _global; } bool isDefault() const { return _default; } frg::string_view name() const { if(_local) return "(*local*)"; if(_global) return "(*global*)"; return _name; } uint32_t hash() const { return _hash; } bool operator==(const SymbolVersion &other) const { if(_local || other._local) return _local && other._local; if(_global || other._global) return _global && other._global; if(_hash != other._hash) return false; return _name == other._name; } SymbolVersion makeDefault() const { auto copy = *this; copy._default = true; return copy; } private: bool _local, _global; bool _default; frg::string_view _name; uint32_t _hash; }; // -------------------------------------------------------- // Scope // -------------------------------------------------------- struct Scope { using ResolveFlags = uint32_t; static inline constexpr ResolveFlags resolveCopy = 1; static inline constexpr ResolveFlags skipGlobalAfterRts = 1 << 1; static frg::optional resolveGlobalOrLocal(Scope &globalScope, Scope *localScope, frg::string_view string, uint64_t skipRts, ResolveFlags flags, frg::optional version); static frg::optional resolveGlobalOrLocalNext(Scope &globalScope, Scope *localScope, frg::string_view string, SharedObject *origin, frg::optional version); Scope(bool isGlobal = false); void appendObject(SharedObject *object); frg::optional resolveSymbol(frg::string_view string, uint64_t skipRts, ResolveFlags flags, frg::optional version); bool isGlobal; private: frg::optional _resolveNext(frg::string_view string, SharedObject *target, frg::optional version); public: // TODO: Make this private again. (Was made public for __dlapi_reverse()). frg::vector _objects; }; extern frg::manual_box globalScope; // -------------------------------------------------------- // Loader // -------------------------------------------------------- struct Loader { public: Loader(Scope *scope, SharedObject *mainExecutable, bool is_initial_link, uint64_t rts); public: void linkObjects(SharedObject *root); private: void _buildLinkBfs(SharedObject *root); void _buildTlsMaps(); void _processStaticRelocations(SharedObject *object); void _processLazyRelocations(SharedObject *object); void _processRelocations(Relocation &rel); public: void initObjects(ObjectRepository *repository); private: void _scheduleInit(SharedObject *object); private: SharedObject *_mainExecutable; Scope *_loadScope; bool _isInitialLink; uint64_t _linkRts; frg::vector _linkBfs; frg::vector _initQueue; }; // -------------------------------------------------------- // Namespace scope functions // -------------------------------------------------------- extern "C" void pltRelocateStub() __attribute__((__visibility__("hidden"))); // -------------------------------------------------------- // RTLD interface // -------------------------------------------------------- uintptr_t *rtld_auxvector();