From bd5969fc876a10b18613302db7087ef3c40f18e1 Mon Sep 17 00:00:00 2001 From: Ian Moffett Date: Thu, 7 Mar 2024 17:28:00 -0500 Subject: lib: Add mlibc Signed-off-by: Ian Moffett --- lib/mlibc/options/rtdl/generic/linker.cpp | 1872 +++++++++++++++++++++++++++++ lib/mlibc/options/rtdl/generic/linker.hpp | 402 +++++++ lib/mlibc/options/rtdl/generic/main.cpp | 844 +++++++++++++ 3 files changed, 3118 insertions(+) create mode 100644 lib/mlibc/options/rtdl/generic/linker.cpp create mode 100644 lib/mlibc/options/rtdl/generic/linker.hpp create mode 100644 lib/mlibc/options/rtdl/generic/main.cpp (limited to 'lib/mlibc/options/rtdl/generic') diff --git a/lib/mlibc/options/rtdl/generic/linker.cpp b/lib/mlibc/options/rtdl/generic/linker.cpp new file mode 100644 index 0000000..a519c35 --- /dev/null +++ b/lib/mlibc/options/rtdl/generic/linker.cpp @@ -0,0 +1,1872 @@ +#include +#include +#include + +// keep a list of optional generic relocation types +enum { + R_OFFSET = (uintptr_t) -1, +}; + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "elf.hpp" +#include "linker.hpp" + +#if !MLIBC_MMAP_ALLOCATE_DSO +uintptr_t libraryBase = 0x41000000; +#endif + +constexpr bool verbose = false; +constexpr bool stillSlightlyVerbose = false; +constexpr bool logBaseAddresses = false; +constexpr bool logRpath = false; +constexpr bool logLdPath = false; +constexpr bool eagerBinding = true; + +#if defined(__x86_64__) || defined(__i386__) +constexpr inline bool tlsAboveTp = false; +#elif defined(__aarch64__) +constexpr inline bool tlsAboveTp = true; +#elif defined(__riscv) +constexpr inline bool tlsAboveTp = true; +#else +# error Unknown architecture +#endif + +extern DebugInterface globalDebugInterface; +extern uintptr_t __stack_chk_guard; + +extern frg::manual_box> libraryPaths; +extern frg::manual_box> preloads; + +#if MLIBC_STATIC_BUILD +extern "C" size_t __init_array_start[]; +extern "C" size_t __init_array_end[]; +extern "C" size_t __preinit_array_start[]; +extern "C" size_t __preinit_array_end[]; +#endif + +size_t tlsMaxAlignment = 16; + +// This is the global "resolution timestamp" (RTS) counter. +// It is incremented each time __dlapi_open() (i.e. dlopen()) is called. +// Each DSO stores its objectRts (i.e. RTS at the time the object was loaded). +// DSOs in the global scope also store a globalRts (i.e. RTS at the time the +// object became global). This mechanism is used to determine which +// part of the global scope is considered for symbol resolution. +uint64_t rtsCounter = 2; + +bool trySeek(int fd, int64_t offset) { + off_t noff; + return mlibc::sys_seek(fd, offset, SEEK_SET, &noff) == 0; +} + +bool tryReadExactly(int fd, void *data, size_t length) { + size_t offset = 0; + while(offset < length) { + ssize_t chunk; + if(mlibc::sys_read(fd, reinterpret_cast(data) + offset, + length - offset, &chunk)) + return false; + __ensure(chunk > 0); + offset += chunk; + } + __ensure(offset == length); + return true; +} + +void closeOrDie(int fd) { + if(mlibc::sys_close(fd)) + __ensure(!"sys_close() failed"); +} + +uintptr_t alignUp(uintptr_t address, size_t align) { + return (address + align - 1) & ~(align - 1); +} + +// -------------------------------------------------------- +// ObjectRepository +// -------------------------------------------------------- + +ObjectRepository::ObjectRepository() +: loadedObjects{getAllocator()}, + _nameMap{frg::hash{}, getAllocator()} {} + +SharedObject *ObjectRepository::injectObjectFromDts(frg::string_view name, + frg::string path, uintptr_t base_address, + elf_dyn *dynamic, uint64_t rts) { + __ensure(!findLoadedObject(name)); + + auto object = frg::construct(getAllocator(), + name.data(), std::move(path), false, globalScope.get(), rts); + object->baseAddress = base_address; + object->dynamic = dynamic; + _parseDynamic(object); + + _addLoadedObject(object); + _discoverDependencies(object, globalScope.get(), rts); + + return object; +} + +SharedObject *ObjectRepository::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) { + __ensure(!findLoadedObject(name)); + + auto object = frg::construct(getAllocator(), + name.data(), std::move(path), true, globalScope.get(), rts); + _fetchFromPhdrs(object, phdr_pointer, phdr_entry_size, num_phdrs, entry_pointer); + _parseDynamic(object); + + _addLoadedObject(object); + _discoverDependencies(object, globalScope.get(), rts); + + return object; +} + +SharedObject *ObjectRepository::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) { + __ensure(!findLoadedObject(name)); + auto object = frg::construct(getAllocator(), + name.data(), std::move(path), true, globalScope.get(), rts); + _fetchFromPhdrs(object, phdr_pointer, phdr_entry_size, num_phdrs, entry_pointer); + +#if MLIBC_STATIC_BUILD + object->initArray = reinterpret_cast(__init_array_start); + object->initArraySize = static_cast((uintptr_t)__init_array_end - + (uintptr_t)__init_array_start); + object->preInitArray = reinterpret_cast(__preinit_array_start); + object->preInitArraySize = static_cast((uintptr_t)__preinit_array_end - + (uintptr_t)__preinit_array_start); +#endif + + _addLoadedObject(object); + + return object; +} + +frg::expected ObjectRepository::requestObjectWithName(frg::string_view name, + SharedObject *origin, Scope *localScope, bool createScope, uint64_t rts) { + if (auto obj = findLoadedObject(name)) + return obj; + + auto tryToOpen = [&] (const char *path) { + int fd; + if(auto x = mlibc::sys_open(path, O_RDONLY, 0, &fd); x) { + return -1; + } + return fd; + }; + + // TODO(arsen): this process can probably undergo heavy optimization, by + // preprocessing the rpath only once on parse + auto processRpath = [&] (frg::string_view path) { + frg::string sPath { getAllocator() }; + if (path.starts_with("$ORIGIN")) { + frg::string_view dirname = origin->path; + auto lastsl = dirname.find_last('/'); + if (lastsl != size_t(-1)) { + dirname = dirname.sub_string(0, lastsl); + } else { + dirname = "."; + } + sPath = frg::string{ getAllocator(), dirname }; + sPath += path.sub_string(7, path.size() - 7); + } else { + sPath = frg::string{ getAllocator(), path }; + } + if (sPath[sPath.size() - 1] != '/') { + sPath += '/'; + } + sPath += name; + if (logRpath) + mlibc::infoLogger() << "rtdl: trying in rpath " << sPath << frg::endlog; + int fd = tryToOpen(sPath.data()); + if (logRpath && fd >= 0) + mlibc::infoLogger() << "rtdl: found in rpath" << frg::endlog; + return frg::tuple { fd, std::move(sPath) }; + }; + + frg::string chosenPath { getAllocator() }; + int fd = -1; + if (origin && origin->runPath) { + size_t start = 0; + size_t idx = 0; + frg::string_view rpath { origin->runPath }; + auto next = [&] () { + idx = rpath.find_first(':', start); + if (idx == (size_t)-1) + idx = rpath.size(); + }; + for (next(); idx < rpath.size(); next()) { + auto path = rpath.sub_string(start, idx - start); + start = idx + 1; + auto [fd_, fullPath] = processRpath(path); + if (fd_ != -1) { + fd = fd_; + chosenPath = std::move(fullPath); + break; + } + } + if (fd == -1) { + auto path = rpath.sub_string(start, rpath.size() - start); + auto [fd_, fullPath] = processRpath(path); + if (fd_ != -1) { + fd = fd_; + chosenPath = std::move(fullPath); + } + } + } else if (logRpath) { + mlibc::infoLogger() << "rtdl: no rpath set for object" << frg::endlog; + } + + for(size_t i = 0; i < libraryPaths->size() && fd == -1; i++) { + auto ldPath = (*libraryPaths)[i]; + auto path = frg::string{getAllocator(), ldPath} + '/' + name; + if(logLdPath) + mlibc::infoLogger() << "rtdl: Trying to load " << name << " from ldpath " << ldPath << "/" << frg::endlog; + fd = tryToOpen(path.data()); + if(fd >= 0) { + chosenPath = std::move(path); + break; + } + } + if(fd == -1) + return LinkerError::notFound; + + if (createScope) { + __ensure(localScope == nullptr); + + // TODO: Free this when the scope is no longer needed. + localScope = frg::construct(getAllocator()); + } + + __ensure(localScope != nullptr); + + auto object = frg::construct(getAllocator(), + name.data(), std::move(chosenPath), false, localScope, rts); + + auto result = _fetchFromFile(object, fd); + closeOrDie(fd); + if(!result) { + frg::destruct(getAllocator(), object); + return result.error(); + } + + _parseDynamic(object); + + _addLoadedObject(object); + _discoverDependencies(object, localScope, rts); + + return object; +} + +frg::expected ObjectRepository::requestObjectAtPath(frg::string_view path, + Scope *localScope, bool createScope, uint64_t rts) { + // TODO: Support SONAME correctly. + auto lastSlash = path.find_last('/') + 1; + auto name = path; + if (!lastSlash) { + name = name.sub_string(lastSlash, path.size() - lastSlash); + } + if (auto obj = findLoadedObject(name)) + return obj; + + if (createScope) { + __ensure(localScope == nullptr); + + // TODO: Free this when the scope is no longer needed. + localScope = frg::construct(getAllocator()); + } + + __ensure(localScope != nullptr); + + auto object = frg::construct(getAllocator(), + name.data(), path.data(), false, localScope, rts); + + frg::string no_prefix(getAllocator(), path); + + int fd; + if(mlibc::sys_open((no_prefix + '\0').data(), O_RDONLY, 0, &fd)) { + frg::destruct(getAllocator(), object); + return LinkerError::notFound; + } + auto result = _fetchFromFile(object, fd); + closeOrDie(fd); + if(!result) { + frg::destruct(getAllocator(), object); + return result.error(); + } + + _parseDynamic(object); + + _addLoadedObject(object); + _discoverDependencies(object, localScope, rts); + + return object; +} + +SharedObject *ObjectRepository::findCaller(void *addr) { + uintptr_t target = reinterpret_cast(addr); + + for (auto [name, object] : _nameMap) { + // Search all PT_LOAD segments for the specified address. + for(size_t j = 0; j < object->phdrCount; j++) { + auto phdr = (elf_phdr *)((uintptr_t)object->phdrPointer + j * object->phdrEntrySize); + if (phdr->p_type == PT_LOAD) { + uintptr_t start = object->baseAddress + phdr->p_vaddr; + uintptr_t end = start + phdr->p_memsz; + if (start <= target && target < end) + return object; + } + } + } + + return nullptr; +} + +SharedObject *ObjectRepository::findLoadedObject(frg::string_view name) { + auto it = _nameMap.get(name); + if (it) + return *it; + + for (auto object : loadedObjects) { + // See if any object has a matching SONAME. + if (object->soName && name == object->soName) + return object; + } + + // TODO: We should also look at the device and inode here as a fallback. + return nullptr; +} + +// -------------------------------------------------------- +// ObjectRepository: Fetching methods. +// -------------------------------------------------------- + +void ObjectRepository::_fetchFromPhdrs(SharedObject *object, void *phdr_pointer, + size_t phdr_entry_size, size_t phdr_count, void *entry_pointer) { + __ensure(object->isMainObject); + object->phdrPointer = phdr_pointer; + object->phdrEntrySize = phdr_entry_size; + object->phdrCount = phdr_count; + if(verbose) + mlibc::infoLogger() << "rtdl: Loading " << object->name << frg::endlog; + + // Note: the entry pointer is absolute and not relative to the base address. + object->entry = entry_pointer; + + frg::optional dynamic_offset; + frg::optional tls_offset; + + // segments are already mapped, so we just have to find the dynamic section + for(size_t i = 0; i < phdr_count; i++) { + auto phdr = (elf_phdr *)((uintptr_t)phdr_pointer + i * phdr_entry_size); + switch(phdr->p_type) { + case PT_PHDR: + // Determine the executable's base address (in the PIE case) by comparing + // the PHDR segment's load address against it's address in the ELF file. + object->baseAddress = reinterpret_cast(phdr_pointer) - phdr->p_vaddr; + if(verbose) + mlibc::infoLogger() << "rtdl: Executable is loaded at " + << (void *)object->baseAddress << frg::endlog; + break; + case PT_DYNAMIC: + dynamic_offset = phdr->p_vaddr; + break; + case PT_TLS: { + object->tlsSegmentSize = phdr->p_memsz; + object->tlsAlignment = phdr->p_align; + object->tlsImageSize = phdr->p_filesz; + tls_offset = phdr->p_vaddr; + break; + case PT_INTERP: + object->interpreterPath = frg::string{ + (char*)(object->baseAddress + phdr->p_vaddr), + getAllocator() + }; + } break; + default: + //FIXME warn about unknown phdrs + break; + } + } + + if(dynamic_offset) + object->dynamic = (elf_dyn *)(object->baseAddress + *dynamic_offset); + if(tls_offset) + object->tlsImagePtr = (void *)(object->baseAddress + *tls_offset); +} + + +frg::expected ObjectRepository::_fetchFromFile(SharedObject *object, int fd) { + __ensure(!object->isMainObject); + + // read the elf file header + elf_ehdr ehdr; + if(!tryReadExactly(fd, &ehdr, sizeof(elf_ehdr))) + return LinkerError::fileTooShort; + + if(ehdr.e_ident[0] != 0x7F + || ehdr.e_ident[1] != 'E' + || ehdr.e_ident[2] != 'L' + || ehdr.e_ident[3] != 'F') + return LinkerError::notElf; + + if((ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN) + || ehdr.e_machine != ELF_MACHINE + || ehdr.e_ident[EI_CLASS] != ELF_CLASS) + return LinkerError::wrongElfType; + + // read the elf program headers + auto phdr_buffer = (char *)getAllocator().allocate(ehdr.e_phnum * ehdr.e_phentsize); + if(!phdr_buffer) + return LinkerError::outOfMemory; + + if(!trySeek(fd, ehdr.e_phoff)) { + getAllocator().deallocate(phdr_buffer, ehdr.e_phnum * ehdr.e_phentsize); + return LinkerError::invalidProgramHeader; + } + if(!tryReadExactly(fd, phdr_buffer, ehdr.e_phnum * ehdr.e_phentsize)) { + getAllocator().deallocate(phdr_buffer, ehdr.e_phnum * ehdr.e_phentsize); + return LinkerError::invalidProgramHeader; + } + + object->phdrPointer = phdr_buffer; + object->phdrCount = ehdr.e_phnum; + object->phdrEntrySize = ehdr.e_phentsize; + + // Allocate virtual address space for the DSO. + constexpr size_t hugeSize = 0x200000; + + uintptr_t highest_address = 0; + for(int i = 0; i < ehdr.e_phnum; i++) { + auto phdr = (elf_phdr *)(phdr_buffer + i * ehdr.e_phentsize); + + if(phdr->p_type != PT_LOAD) + continue; + + auto limit = phdr->p_vaddr + phdr->p_memsz; + if(limit > highest_address) + highest_address = limit; + } + + __ensure(!(object->baseAddress & (hugeSize - 1))); + + highest_address = (highest_address + mlibc::page_size - 1) & ~(mlibc::page_size - 1); + +#if MLIBC_MMAP_ALLOCATE_DSO + void *mappedAddr = nullptr; + + if (mlibc::sys_vm_map(nullptr, + highest_address - object->baseAddress, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0, &mappedAddr)) { + mlibc::infoLogger() << "sys_vm_map failed when allocating address space for DSO \"" + << object->name << "\"" + << ", base " << (void *)object->baseAddress + << ", requested " << (highest_address - object->baseAddress) << " bytes" + << frg::endlog; + getAllocator().deallocate(phdr_buffer, ehdr.e_phnum * ehdr.e_phentsize); + return LinkerError::outOfMemory; + } + + object->baseAddress = reinterpret_cast(mappedAddr); +#else + object->baseAddress = libraryBase; + libraryBase += (highest_address + (hugeSize - 1)) & ~(hugeSize - 1); +#endif + + if(verbose || logBaseAddresses) + mlibc::infoLogger() << "rtdl: Loading " << object->name + << " at " << (void *)object->baseAddress << frg::endlog; + + // Load all segments. + constexpr size_t pageSize = 0x1000; + for(int i = 0; i < ehdr.e_phnum; i++) { + auto phdr = (elf_phdr *)(phdr_buffer + i * ehdr.e_phentsize); + + if(phdr->p_type == PT_LOAD) { + size_t misalign = phdr->p_vaddr & (pageSize - 1); + __ensure(phdr->p_memsz > 0); + __ensure(phdr->p_memsz >= phdr->p_filesz); + + // If the following condition is violated, we cannot use mmap() the segment; + // however, GCC only generates ELF files that satisfy this. + __ensure(misalign == (phdr->p_offset & (pageSize - 1))); + + auto map_address = object->baseAddress + phdr->p_vaddr - misalign; + auto backed_map_size = (phdr->p_filesz + misalign + pageSize - 1) & ~(pageSize - 1); + auto total_map_size = (phdr->p_memsz + misalign + pageSize - 1) & ~(pageSize - 1); + + int prot = 0; + if(phdr->p_flags & PF_R) + prot |= PROT_READ; + if(phdr->p_flags & PF_W) + prot |= PROT_WRITE; + if(phdr->p_flags & PF_X) + prot |= PROT_EXEC; + + #if MLIBC_MAP_DSO_SEGMENTS + void *map_pointer; + if(mlibc::sys_vm_map(reinterpret_cast(map_address), + backed_map_size, prot | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED, fd, phdr->p_offset - misalign, &map_pointer)) + __ensure(!"sys_vm_map failed"); + if(total_map_size > backed_map_size) + if(mlibc::sys_vm_map(reinterpret_cast(map_address + backed_map_size), + total_map_size - backed_map_size, prot | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0, &map_pointer)) + __ensure(!"sys_vm_map failed"); + + if(mlibc::sys_vm_readahead) + if(mlibc::sys_vm_readahead(reinterpret_cast(map_address), + backed_map_size)) + mlibc::infoLogger() << "mlibc: sys_vm_readahead() failed in ld.so" + << frg::endlog; + + // Clear the trailing area at the end of the backed mapping. + // We do not clear the leading area; programs are not supposed to access it. + memset(reinterpret_cast(map_address + misalign + phdr->p_filesz), + 0, phdr->p_memsz - phdr->p_filesz); + #else + (void)backed_map_size; + + void *map_pointer; + if(mlibc::sys_vm_map(reinterpret_cast(map_address), + total_map_size, prot | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0, &map_pointer)) + __ensure(!"sys_vm_map failed"); + + __ensure(trySeek(fd, phdr->p_offset)); + __ensure(tryReadExactly(fd, reinterpret_cast(map_address) + misalign, + phdr->p_filesz)); + #endif + // Take care of removing superfluous permissions. + if(mlibc::sys_vm_protect && ((prot & PROT_WRITE) == 0)) + if(mlibc::sys_vm_protect(map_pointer, total_map_size, prot)) + mlibc::infoLogger() << "mlibc: sys_vm_protect() failed in ld.so" << frg::endlog; + }else if(phdr->p_type == PT_TLS) { + object->tlsSegmentSize = phdr->p_memsz; + object->tlsAlignment = phdr->p_align; + object->tlsImageSize = phdr->p_filesz; + object->tlsImagePtr = (void *)(object->baseAddress + phdr->p_vaddr); + }else if(phdr->p_type == PT_DYNAMIC) { + object->dynamic = (elf_dyn *)(object->baseAddress + phdr->p_vaddr); + }else if(phdr->p_type == PT_INTERP + || phdr->p_type == PT_PHDR + || phdr->p_type == PT_NOTE + || phdr->p_type == PT_RISCV_ATTRIBUTES + || phdr->p_type == PT_GNU_EH_FRAME + || phdr->p_type == PT_GNU_RELRO + || phdr->p_type == PT_GNU_STACK + || phdr->p_type == PT_GNU_PROPERTY) { + // ignore the phdr + }else{ + mlibc::panicLogger() << "Unexpected PHDR type 0x" + << frg::hex_fmt(phdr->p_type) << " in DSO " << object->name << frg::endlog; + } + } + + return frg::success; +} + +// -------------------------------------------------------- +// ObjectRepository: Parsing methods. +// -------------------------------------------------------- + +void ObjectRepository::_parseDynamic(SharedObject *object) { + if(!object->dynamic) + mlibc::infoLogger() << "ldso: Object '" << object->name + << "' does not have a dynamic section" << frg::endlog; + __ensure(object->dynamic); + + // Fix up these offsets to addresses after the loop, since the + // addresses depend on the value of DT_STRTAB. + frg::optional runpath_offset; + /* If true, ignore the RPATH. */ + bool runpath_found = false; + frg::optional soname_offset; + + for(size_t i = 0; object->dynamic[i].d_tag != DT_NULL; i++) { + elf_dyn *dynamic = &object->dynamic[i]; + switch(dynamic->d_tag) { + // handle hash table, symbol table and string table + case DT_HASH: + object->hashStyle = HashStyle::systemV; + object->hashTableOffset = dynamic->d_un.d_ptr; + break; + case DT_GNU_HASH: + object->hashStyle = HashStyle::gnu; + object->hashTableOffset = dynamic->d_un.d_ptr; + break; + case DT_STRTAB: + object->stringTableOffset = dynamic->d_un.d_ptr; + break; + case DT_STRSZ: + break; // we don't need the size of the string table + case DT_SYMTAB: + object->symbolTableOffset = dynamic->d_un.d_ptr; + break; + case DT_SYMENT: + __ensure(dynamic->d_un.d_val == sizeof(elf_sym)); + break; + // handle lazy relocation table + case DT_PLTGOT: + object->globalOffsetTable = (void **)(object->baseAddress + + dynamic->d_un.d_ptr); + break; + case DT_JMPREL: + object->lazyRelocTableOffset = dynamic->d_un.d_ptr; + break; + case DT_PLTRELSZ: + object->lazyTableSize = dynamic->d_un.d_val; + break; + case DT_PLTREL: + if(dynamic->d_un.d_val == DT_RELA) { + object->lazyExplicitAddend = true; + }else{ + __ensure(dynamic->d_un.d_val == DT_REL); + object->lazyExplicitAddend = false; + } + break; + // TODO: Implement this correctly! + case DT_SYMBOLIC: + object->symbolicResolution = true; + break; + case DT_BIND_NOW: + object->eagerBinding = true; + break; + case DT_FLAGS: { + if(dynamic->d_un.d_val & DF_SYMBOLIC) + object->symbolicResolution = true; + if(dynamic->d_un.d_val & DF_STATIC_TLS) + object->haveStaticTls = true; + if(dynamic->d_un.d_val & DF_BIND_NOW) + object->eagerBinding = true; + + auto ignored = DF_BIND_NOW | DF_SYMBOLIC | DF_STATIC_TLS; +#ifdef __riscv + // Work around https://sourceware.org/bugzilla/show_bug.cgi?id=24673. + ignored |= DF_TEXTREL; +#else + if(dynamic->d_un.d_val & DF_TEXTREL) + mlibc::panicLogger() << "\e[31mrtdl: DF_TEXTREL is unimplemented" << frg::endlog; +#endif + if(dynamic->d_un.d_val & ~ignored) + mlibc::infoLogger() << "\e[31mrtdl: DT_FLAGS(" << frg::hex_fmt{dynamic->d_un.d_val & ~ignored} + << ") is not implemented correctly!\e[39m" + << frg::endlog; + } break; + case DT_FLAGS_1: + if(dynamic->d_un.d_val & DF_1_NOW) + object->eagerBinding = true; + // The DF_1_PIE flag is informational only. It is used by e.g file(1). + // The DF_1_NODELETE flag has a similar effect to RTLD_NODELETE, both of which we + // ignore because we don't implement dlclose(). + if(dynamic->d_un.d_val & ~(DF_1_NOW | DF_1_PIE | DF_1_NODELETE)) + mlibc::infoLogger() << "\e[31mrtdl: DT_FLAGS_1(" << frg::hex_fmt{dynamic->d_un.d_val} + << ") is not implemented correctly!\e[39m" + << frg::endlog; + break; + case DT_RPATH: + if (runpath_found) { + /* Ignore RPATH if RUNPATH was present. */ + break; + } + [[fallthrough]]; + case DT_RUNPATH: + runpath_found = dynamic->d_tag == DT_RUNPATH; + runpath_offset = dynamic->d_un.d_val; + break; + case DT_INIT: + if(dynamic->d_un.d_ptr != 0) + object->initPtr = (InitFuncPtr)(object->baseAddress + dynamic->d_un.d_ptr); + break; + case DT_INIT_ARRAY: + if(dynamic->d_un.d_ptr != 0) + object->initArray = (InitFuncPtr *)(object->baseAddress + dynamic->d_un.d_ptr); + break; + case DT_INIT_ARRAYSZ: + object->initArraySize = dynamic->d_un.d_val; + break; + case DT_PREINIT_ARRAY: + if(dynamic->d_un.d_ptr != 0) { + // Only the main object is allowed pre-initializers. + __ensure(object->isMainObject); + object->preInitArray = (InitFuncPtr *)(object->baseAddress + dynamic->d_un.d_ptr); + } + break; + case DT_PREINIT_ARRAYSZ: + // Only the main object is allowed pre-initializers. + __ensure(object->isMainObject); + object->preInitArraySize = dynamic->d_un.d_val; + break; + case DT_DEBUG: +#if ELF_CLASS == ELFCLASS32 + dynamic->d_un.d_val = reinterpret_cast(&globalDebugInterface); +#elif ELF_CLASS == ELFCLASS64 + dynamic->d_un.d_val = reinterpret_cast(&globalDebugInterface); +#endif + break; + case DT_SONAME: + soname_offset = dynamic->d_un.d_val; + break; + // ignore unimportant tags + case DT_NEEDED: // we handle this later + case DT_FINI: case DT_FINI_ARRAY: case DT_FINI_ARRAYSZ: + case DT_RELA: case DT_RELASZ: case DT_RELAENT: case DT_RELACOUNT: + case DT_REL: case DT_RELSZ: case DT_RELENT: case DT_RELCOUNT: + case DT_RELR: case DT_RELRSZ: case DT_RELRENT: + case DT_VERSYM: + case DT_VERDEF: case DT_VERDEFNUM: + case DT_VERNEED: case DT_VERNEEDNUM: +#ifdef __riscv + case DT_TEXTREL: // Work around https://sourceware.org/bugzilla/show_bug.cgi?id=24673. +#endif + break; + case DT_TLSDESC_PLT: case DT_TLSDESC_GOT: + break; + default: + // Ignore unknown entries in the os-specific area as we don't use them. + if(dynamic->d_tag < DT_LOOS || dynamic->d_tag > DT_HIOS) { + mlibc::panicLogger() << "Unexpected dynamic entry " + << (void *)dynamic->d_tag << " in object" << frg::endlog; + } + } + } + + if(runpath_offset) { + object->runPath = reinterpret_cast(object->baseAddress + + object->stringTableOffset + *runpath_offset); + } + if(soname_offset) { + object->soName = reinterpret_cast(object->baseAddress + + object->stringTableOffset + *soname_offset); + } +} + +void ObjectRepository::_discoverDependencies(SharedObject *object, + Scope *localScope, uint64_t rts) { + if(object->isMainObject) { + for(auto preload : *preloads) { + frg::expected libraryResult; + if (preload.find_first('/') == size_t(-1)) { + libraryResult = requestObjectWithName(preload, object, globalScope.get(), false, 1); + } else { + libraryResult = requestObjectAtPath(preload, globalScope.get(), false, 1); + } + if(!libraryResult) + mlibc::panicLogger() << "rtdl: Could not load preload " << preload << frg::endlog; + + if(verbose) + mlibc::infoLogger() << "rtdl: Preloading " << preload << frg::endlog; + + object->dependencies.push_back(libraryResult.value()); + } + } + + // Load required dynamic libraries. + for(size_t i = 0; object->dynamic[i].d_tag != DT_NULL; i++) { + elf_dyn *dynamic = &object->dynamic[i]; + if(dynamic->d_tag != DT_NEEDED) + continue; + + const char *library_str = (const char *)(object->baseAddress + + object->stringTableOffset + dynamic->d_un.d_val); + + auto library = requestObjectWithName(frg::string_view{library_str}, + object, localScope, false, rts); + if(!library) + mlibc::panicLogger() << "Could not satisfy dependency " << library_str << frg::endlog; + object->dependencies.push(library.value()); + } +} + +void ObjectRepository::_addLoadedObject(SharedObject *object) { + _nameMap.insert(object->name, object); + loadedObjects.push_back(object); +} + +// -------------------------------------------------------- +// SharedObject +// -------------------------------------------------------- + +SharedObject::SharedObject(const char *name, frg::string path, + bool is_main_object, Scope *local_scope, uint64_t object_rts) + : name(name, getAllocator()), path(std::move(path)), + interpreterPath(getAllocator()), soName(nullptr), + isMainObject(is_main_object), objectRts(object_rts), inLinkMap(false), + baseAddress(0), localScope(local_scope), dynamic(nullptr), + globalOffsetTable(nullptr), entry(nullptr), tlsSegmentSize(0), + tlsAlignment(0), tlsImageSize(0), tlsImagePtr(nullptr), + tlsInitialized(false), hashTableOffset(0), symbolTableOffset(0), + stringTableOffset(0), lazyRelocTableOffset(0), lazyTableSize(0), + lazyExplicitAddend(false), symbolicResolution(false), + eagerBinding(false), haveStaticTls(false), + dependencies(getAllocator()), tlsModel(TlsModel::null), + tlsOffset(0), globalRts(0), wasLinked(false), + scheduledForInit(false), onInitStack(false), + wasInitialized(false) { } + +SharedObject::SharedObject(const char *name, const char *path, + bool is_main_object, Scope *localScope, uint64_t object_rts) + : SharedObject(name, + frg::string { path, getAllocator() }, + is_main_object, localScope, object_rts) {} + +void processLateRelocation(Relocation rel) { + // resolve the symbol if there is a symbol + frg::optional p; + if(rel.symbol_index()) { + auto symbol = (elf_sym *)(rel.object()->baseAddress + rel.object()->symbolTableOffset + + rel.symbol_index() * sizeof(elf_sym)); + ObjectSymbol r(rel.object(), symbol); + + p = Scope::resolveGlobalOrLocal(*globalScope, rel.object()->localScope, + r.getString(), rel.object()->objectRts, Scope::resolveCopy); + } + + switch(rel.type()) { + case R_COPY: + __ensure(p); + memcpy(rel.destination(), (void *)p->virtualAddress(), p->symbol()->st_size); + break; + +// TODO: R_IRELATIVE also exists on other architectures but will likely need a different implementation. +#if defined(__x86_64__) || defined(__i386__) + case R_IRELATIVE: { + uintptr_t addr = rel.object()->baseAddress + rel.addend_rel(); + auto* fn = reinterpret_cast(addr); + rel.relocate(fn()); + } break; +#elif defined(__aarch64__) + case R_IRELATIVE: { + uintptr_t addr = rel.object()->baseAddress + rel.addend_rel(); + auto* fn = reinterpret_cast(addr); + // TODO: the function should get passed AT_HWCAP value. + rel.relocate(fn(0)); + } break; +#endif + + default: + break; + } +} + +void processLateRelocations(SharedObject *object) { + frg::optional rel_offset; + frg::optional rel_length; + + frg::optional rela_offset; + frg::optional rela_length; + + for(size_t i = 0; object->dynamic[i].d_tag != DT_NULL; i++) { + elf_dyn *dynamic = &object->dynamic[i]; + + switch(dynamic->d_tag) { + case DT_REL: + rel_offset = dynamic->d_un.d_ptr; + break; + case DT_RELSZ: + rel_length = dynamic->d_un.d_val; + break; + case DT_RELENT: + __ensure(dynamic->d_un.d_val == sizeof(elf_rel)); + break; + case DT_RELA: + rela_offset = dynamic->d_un.d_ptr; + break; + case DT_RELASZ: + rela_length = dynamic->d_un.d_val; + break; + case DT_RELAENT: + __ensure(dynamic->d_un.d_val == sizeof(elf_rela)); + break; + } + } + + if(rela_offset && rela_length) { + for(size_t offset = 0; offset < *rela_length; offset += sizeof(elf_rela)) { + auto reloc = (elf_rela *)(object->baseAddress + *rela_offset + offset); + auto r = Relocation(object, reloc); + processLateRelocation(r); + } + } else if(rel_offset && rel_length) { + for(size_t offset = 0; offset < *rel_length; offset += sizeof(elf_rel)) { + auto reloc = (elf_rel *)(object->baseAddress + *rel_offset + offset); + auto r = Relocation(object, reloc); + processLateRelocation(r); + } + }else{ + __ensure(!rela_offset && !rela_length); + __ensure(!rel_offset && !rel_length); + } +} + +void doInitialize(SharedObject *object) { + __ensure(object->wasLinked); + __ensure(!object->wasInitialized); + + // if the object has dependencies we initialize them first + for(size_t i = 0; i < object->dependencies.size(); i++) + __ensure(object->dependencies[i]->wasInitialized); + + if(verbose) + mlibc::infoLogger() << "rtdl: Initialize " << object->name << frg::endlog; + + if(verbose) + mlibc::infoLogger() << "rtdl: Running DT_INIT function" << frg::endlog; + if(object->initPtr != nullptr) + object->initPtr(); + + if(verbose) + mlibc::infoLogger() << "rtdl: Running DT_INIT_ARRAY functions" << frg::endlog; + __ensure((object->initArraySize % sizeof(InitFuncPtr)) == 0); + for(size_t i = 0; i < object->initArraySize / sizeof(InitFuncPtr); i++) + object->initArray[i](); + + if(verbose) + mlibc::infoLogger() << "rtdl: Object initialization complete" << frg::endlog; + object->wasInitialized = true; +} + +// -------------------------------------------------------- +// RuntimeTlsMap +// -------------------------------------------------------- + +RuntimeTlsMap::RuntimeTlsMap() +: initialPtr{0}, initialLimit{0}, indices{getAllocator()} { } + +void initTlsObjects(Tcb *tcb, const frg::vector &objects, bool checkInitialized) { + // Initialize TLS segments that follow the static model. + for(auto object : objects) { + if(object->tlsModel == TlsModel::initial) { + if(checkInitialized && object->tlsInitialized) + continue; + + char *tcb_ptr = reinterpret_cast(tcb); + auto tls_ptr = tcb_ptr + object->tlsOffset; + memset(tls_ptr, 0, object->tlsSegmentSize); + memcpy(tls_ptr, object->tlsImagePtr, object->tlsImageSize); + + if (verbose) { + mlibc::infoLogger() << "rtdl: wrote tls image at " << (void *)tls_ptr + << ", size = 0x" << frg::hex_fmt{object->tlsSegmentSize} << frg::endlog; + } + + if (checkInitialized) + object->tlsInitialized = true; + } + } +} + +Tcb *allocateTcb() { + size_t tlsInitialSize = runtimeTlsMap->initialLimit; + + // To make sure that both the TCB and TLS data are sufficiently aligned, allocate + // slightly more than necessary and adjust alignment afterwards. + size_t alignOverhead = frg::max(alignof(Tcb), tlsMaxAlignment); + size_t allocSize = tlsInitialSize + sizeof(Tcb) + alignOverhead; + auto allocation = reinterpret_cast(getAllocator().allocate(allocSize)); + memset(reinterpret_cast(allocation), 0, allocSize); + + uintptr_t tlsAddress, tcbAddress; + if constexpr (tlsAboveTp) { + // Here we must satisfy two requirements of the TCB and the TLS data: + // 1. One should follow the other immediately in memory. We do this so that + // we can simply add or subtract sizeof(Tcb) to obtain the address of the other. + // 2. Both should be sufficiently aligned. + // To do this, we will fix whichever address has stricter alignment requirements, and + // derive the other from it. + if (tlsMaxAlignment > alignof(Tcb)) { + tlsAddress = alignUp(allocation + sizeof(Tcb), tlsMaxAlignment); + tcbAddress = tlsAddress - sizeof(Tcb); + } else { + tcbAddress = alignUp(allocation, alignof(Tcb)); + tlsAddress = tcbAddress + sizeof(Tcb); + } + __ensure((tlsAddress & (tlsMaxAlignment - 1)) == 0); + __ensure(tlsAddress == tcbAddress + sizeof(Tcb)); + } else { + // The TCB should be aligned such that the preceding blocks are aligned too. + tcbAddress = alignUp(allocation + tlsInitialSize, alignOverhead); + tlsAddress = tcbAddress - tlsInitialSize; + } + __ensure((tcbAddress & (alignof(Tcb) - 1)) == 0); + + if (verbose) { + mlibc::infoLogger() << "rtdl: tcb allocated at " << (void *)tcbAddress + << ", size = 0x" << frg::hex_fmt{sizeof(Tcb)} << frg::endlog; + mlibc::infoLogger() << "rtdl: tls allocated at " << (void *)tlsAddress + << ", size = 0x" << frg::hex_fmt{tlsInitialSize} << frg::endlog; + } + + Tcb *tcb_ptr = new ((char *)tcbAddress) Tcb; + tcb_ptr->selfPointer = tcb_ptr; + + tcb_ptr->stackCanary = __stack_chk_guard; + tcb_ptr->cancelBits = tcbCancelEnableBit; + tcb_ptr->didExit = 0; + tcb_ptr->isJoinable = 1; + memset(&tcb_ptr->returnValue, 0, sizeof(tcb_ptr->returnValue)); + tcb_ptr->localKeys = frg::construct>(getAllocator()); + tcb_ptr->dtvSize = runtimeTlsMap->indices.size(); + tcb_ptr->dtvPointers = frg::construct_n(getAllocator(), runtimeTlsMap->indices.size()); + memset(tcb_ptr->dtvPointers, 0, sizeof(void *) * runtimeTlsMap->indices.size()); + for(size_t i = 0; i < runtimeTlsMap->indices.size(); ++i) { + auto object = runtimeTlsMap->indices[i]; + if(object->tlsModel != TlsModel::initial) + continue; + tcb_ptr->dtvPointers[i] = reinterpret_cast(tcb_ptr) + object->tlsOffset; + } + + return tcb_ptr; +} + +void *accessDtv(SharedObject *object) { + Tcb *tcb_ptr = mlibc::get_current_tcb(); + + // We might need to reallocate the DTV. + if(object->tlsIndex >= tcb_ptr->dtvSize) { + // TODO: need to protect runtimeTlsMap against concurrent access. + auto ndtv = frg::construct_n(getAllocator(), runtimeTlsMap->indices.size()); + memset(ndtv, 0, sizeof(void *) * runtimeTlsMap->indices.size()); + memcpy(ndtv, tcb_ptr->dtvPointers, sizeof(void *) * tcb_ptr->dtvSize); + frg::destruct_n(getAllocator(), tcb_ptr->dtvPointers, tcb_ptr->dtvSize); + tcb_ptr->dtvSize = runtimeTlsMap->indices.size(); + tcb_ptr->dtvPointers = ndtv; + } + + // We might need to fill in a new DTV entry. + if(!tcb_ptr->dtvPointers[object->tlsIndex]) { + __ensure(object->tlsModel == TlsModel::dynamic); + + auto buffer = getAllocator().allocate(object->tlsSegmentSize); + __ensure(!(reinterpret_cast(buffer) & (object->tlsAlignment - 1))); + memset(buffer, 0, object->tlsSegmentSize); + memcpy(buffer, object->tlsImagePtr, object->tlsImageSize); + tcb_ptr->dtvPointers[object->tlsIndex] = buffer; + + if (verbose) { + mlibc::infoLogger() << "rtdl: accessDtv wrote tls image at " << buffer + << ", size = 0x" << frg::hex_fmt{object->tlsSegmentSize} << frg::endlog; + } + } + + return (void *)((char *)tcb_ptr->dtvPointers[object->tlsIndex] + TLS_DTV_OFFSET); +} + +void *tryAccessDtv(SharedObject *object) { + Tcb *tcb_ptr = mlibc::get_current_tcb(); + + if (object->tlsIndex >= tcb_ptr->dtvSize) + return nullptr; + if (!tcb_ptr->dtvPointers[object->tlsIndex]) + return nullptr; + + return (void *)((char *)tcb_ptr->dtvPointers[object->tlsIndex] + TLS_DTV_OFFSET); +} + +// -------------------------------------------------------- +// ObjectSymbol +// -------------------------------------------------------- + +ObjectSymbol::ObjectSymbol(SharedObject *object, const elf_sym *symbol) +: _object(object), _symbol(symbol) { } + +const char *ObjectSymbol::getString() { + __ensure(_symbol->st_name != 0); + return (const char *)(_object->baseAddress + + _object->stringTableOffset + _symbol->st_name); +} + +uintptr_t ObjectSymbol::virtualAddress() { + auto bind = ELF_ST_BIND(_symbol->st_info); + __ensure(bind == STB_GLOBAL || bind == STB_WEAK || bind == STB_GNU_UNIQUE); + __ensure(_symbol->st_shndx != SHN_UNDEF); + return _object->baseAddress + _symbol->st_value; +} + +// -------------------------------------------------------- +// Scope +// -------------------------------------------------------- + +uint32_t elf64Hash(frg::string_view string) { + uint32_t h = 0, g; + + for(size_t i = 0; i < string.size(); ++i) { + h = (h << 4) + (uint32_t)string[i]; + g = h & 0xF0000000; + if(g) + h ^= g >> 24; + h &= 0x0FFFFFFF; + } + + return h; +} + +uint32_t gnuHash(frg::string_view string) { + uint32_t h = 5381; + for(size_t i = 0; i < string.size(); ++i) + h = (h << 5) + h + string[i]; + return h; +} + +// TODO: move this to some namespace or class? +frg::optional resolveInObject(SharedObject *object, frg::string_view string) { + // Checks if the symbol can be used to satisfy the dependency. + auto eligible = [&] (ObjectSymbol cand) { + if(cand.symbol()->st_shndx == SHN_UNDEF) + return false; + + auto bind = ELF_ST_BIND(cand.symbol()->st_info); + if(bind != STB_GLOBAL && bind != STB_WEAK && bind != STB_GNU_UNIQUE) + return false; + + return true; + }; + + if (object->hashStyle == HashStyle::systemV) { + auto hash_table = (Elf64_Word *)(object->baseAddress + object->hashTableOffset); + Elf64_Word num_buckets = hash_table[0]; + auto bucket = elf64Hash(string) % num_buckets; + + auto index = hash_table[2 + bucket]; + while(index != 0) { + ObjectSymbol cand{object, (elf_sym *)(object->baseAddress + + object->symbolTableOffset + index * sizeof(elf_sym))}; + if(eligible(cand) && frg::string_view{cand.getString()} == string) + return cand; + + index = hash_table[2 + num_buckets + index]; + } + + return frg::optional{}; + }else{ + __ensure(object->hashStyle == HashStyle::gnu); + + struct GnuTable { + uint32_t nBuckets; + uint32_t symbolOffset; + uint32_t bloomSize; + uint32_t bloomShift; + }; + + auto hash_table = reinterpret_cast(object->baseAddress + + object->hashTableOffset); + auto buckets = reinterpret_cast(object->baseAddress + + object->hashTableOffset + sizeof(GnuTable) + + hash_table->bloomSize * sizeof(elf_addr)); + auto chains = reinterpret_cast(object->baseAddress + + object->hashTableOffset + sizeof(GnuTable) + + hash_table->bloomSize * sizeof(elf_addr) + + hash_table->nBuckets * sizeof(uint32_t)); + + // TODO: Use the bloom filter. + + // The symbols of a given bucket are contiguous in the table. + auto hash = gnuHash(string); + auto index = buckets[hash % hash_table->nBuckets]; + + if(!index) + return frg::optional{}; + + while(true) { + // chains[] contains an array of hashes, parallel to the symbol table. + auto chash = chains[index - hash_table->symbolOffset]; + if ((chash & ~1) == (hash & ~1)) { + ObjectSymbol cand{object, (elf_sym *)(object->baseAddress + + object->symbolTableOffset + index * sizeof(elf_sym))}; + if(eligible(cand) && frg::string_view{cand.getString()} == string) + return cand; + } + + // If we hit the end of the chain, the symbol is not present. + if(chash & 1) + return frg::optional{}; + index++; + } + } +} + +frg::optional Scope::_resolveNext(frg::string_view string, + SharedObject *target) { + // Skip objects until we find the target, and only look for symbols after that. + size_t i; + for (i = 0; i < _objects.size(); i++) { + if (_objects[i] == target) + break; + } + + if (i == _objects.size()) { + mlibc::infoLogger() << "rtdl: object passed to Scope::resolveAfter was not found" << frg::endlog; + return frg::optional(); + } + + for (i = i + 1; i < _objects.size(); i++) { + if(_objects[i]->isMainObject) + continue; + + frg::optional p = resolveInObject(_objects[i], string); + if(p) + return p; + } + + return frg::optional(); +} + +Scope::Scope(bool isGlobal) +: isGlobal{isGlobal}, _objects(getAllocator()) { } + +void Scope::appendObject(SharedObject *object) { + // Don't insert duplicates. + for (auto obj : _objects) { + if (obj == object) + return; + } + + _objects.push(object); +} + +frg::optional Scope::resolveGlobalOrLocal(Scope &globalScope, + Scope *localScope, frg::string_view string, uint64_t skipRts, ResolveFlags flags) { + auto sym = globalScope.resolveSymbol(string, skipRts, flags | skipGlobalAfterRts); + if(!sym && localScope) + sym = localScope->resolveSymbol(string, skipRts, flags | skipGlobalAfterRts); + return sym; +} + +frg::optional Scope::resolveGlobalOrLocalNext(Scope &globalScope, + Scope *localScope, frg::string_view string, SharedObject *origin) { + auto sym = globalScope._resolveNext(string, origin); + if(!sym && localScope) { + sym = localScope->_resolveNext(string, origin); + } + return sym; +} + +// TODO: let this return uintptr_t +frg::optional Scope::resolveSymbol(frg::string_view string, + uint64_t skipRts, ResolveFlags flags) { + for (auto object : _objects) { + if((flags & resolveCopy) && object->isMainObject) + continue; + if((flags & skipGlobalAfterRts) && object->globalRts > skipRts) { + // globalRts should be monotone increasing for objects in the global scope, + // so as an optimization we can break early here. + // TODO: If we implement DT_SYMBOLIC, this assumption fails. + if(isGlobal) + break; + else + continue; + } + + frg::optional p = resolveInObject(object, string); + if(p) + return p; + } + + return frg::optional(); +} + +// -------------------------------------------------------- +// Loader +// -------------------------------------------------------- + +Loader::Loader(Scope *scope, SharedObject *mainExecutable, bool is_initial_link, uint64_t rts) +: _mainExecutable{mainExecutable}, _loadScope{scope}, _isInitialLink{is_initial_link}, + _linkRts{rts}, _linkBfs{getAllocator()}, _initQueue{getAllocator()} { } + +void Loader::_buildLinkBfs(SharedObject *root) { + __ensure(_linkBfs.size() == 0); + + struct Token {}; + using Set = frg::hash_map, MemoryAllocator>; + Set set{frg::hash{}, getAllocator()}; + _linkBfs.push(root); + + // Loop over indices (not iterators) here: We are adding elements in the loop! + for(size_t i = 0; i < _linkBfs.size(); i++) { + auto current = _linkBfs[i]; + + // At this point the object is loaded and we can fill in its debug struct, + // the linked list fields will be filled later. + current->linkMap.base = current->baseAddress; + current->linkMap.name = current->path.data(); + current->linkMap.dynv = current->dynamic; + + __ensure((current->tlsAlignment & (current->tlsAlignment - 1)) == 0); + + if (_isInitialLink && current->tlsAlignment > tlsMaxAlignment) { + tlsMaxAlignment = current->tlsAlignment; + } + + for (auto dep : current->dependencies) { + if (!set.get(dep)) { + set.insert(dep, Token{}); + _linkBfs.push(dep); + } + } + } +} + +void Loader::linkObjects(SharedObject *root) { + _buildLinkBfs(root); + _buildTlsMaps(); + + // Promote objects to the desired scope. + for(auto object : _linkBfs) { + if (object->globalRts == 0 && _loadScope->isGlobal) + object->globalRts = _linkRts; + + _loadScope->appendObject(object); + } + + // Process regular relocations. + for(auto object : _linkBfs) { + // Some objects have already been linked before. + if(object->objectRts < _linkRts) + continue; + + if(object->dynamic == nullptr) + continue; + + if(verbose) + mlibc::infoLogger() << "rtdl: Linking " << object->name << frg::endlog; + + __ensure(!object->wasLinked); + + // TODO: Support this. + if(object->symbolicResolution) + mlibc::infoLogger() << "\e[31mrtdl: DT_SYMBOLIC is not implemented correctly!\e[39m" + << frg::endlog; + + _processStaticRelocations(object); + _processLazyRelocations(object); + } + + // Process copy relocations. + for(auto object : _linkBfs) { + if(!object->isMainObject) + continue; + + // Some objects have already been linked before. + if(object->objectRts < _linkRts) + continue; + + if(object->dynamic == nullptr) + continue; + + processLateRelocations(object); + } + + for(auto object : _linkBfs) { + object->wasLinked = true; + + if(object->inLinkMap) + continue; + + auto linkMap = reinterpret_cast(globalDebugInterface.head); + + object->linkMap.prev = linkMap; + object->linkMap.next = linkMap->next; + if(linkMap->next) + linkMap->next->prev = &(object->linkMap); + linkMap->next = &(object->linkMap); + object->inLinkMap = true; + } +} + +void Loader::_buildTlsMaps() { + if(_isInitialLink) { + __ensure(runtimeTlsMap->initialPtr == 0); + __ensure(runtimeTlsMap->initialLimit == 0); + + __ensure(!_linkBfs.empty()); + __ensure(_linkBfs.front()->isMainObject); + + for(auto object : _linkBfs) { + __ensure(object->tlsModel == TlsModel::null); + + if(object->tlsSegmentSize == 0) + continue; + + // Allocate an index for the object. + object->tlsIndex = runtimeTlsMap->indices.size(); + runtimeTlsMap->indices.push_back(object); + + object->tlsModel = TlsModel::initial; + + if constexpr (tlsAboveTp) { + // As per the comment in allocateTcb(), we may simply add sizeof(Tcb) to + // reach the TLS data. + object->tlsOffset = runtimeTlsMap->initialPtr + sizeof(Tcb); + runtimeTlsMap->initialPtr += object->tlsSegmentSize; + + size_t misalign = runtimeTlsMap->initialPtr & (object->tlsAlignment - 1); + if(misalign) + runtimeTlsMap->initialPtr += object->tlsAlignment - misalign; + } else { + runtimeTlsMap->initialPtr += object->tlsSegmentSize; + + size_t misalign = runtimeTlsMap->initialPtr & (object->tlsAlignment - 1); + if(misalign) + runtimeTlsMap->initialPtr += object->tlsAlignment - misalign; + + object->tlsOffset = -runtimeTlsMap->initialPtr; + } + + if(verbose) + mlibc::infoLogger() << "rtdl: TLS of " << object->name + << " mapped to 0x" << frg::hex_fmt{object->tlsOffset} + << ", size: " << object->tlsSegmentSize + << ", alignment: " << object->tlsAlignment << frg::endlog; + } + + // Reserve some additional space for future libraries. + runtimeTlsMap->initialLimit = runtimeTlsMap->initialPtr + 64; + }else{ + for(auto object : _linkBfs) { + if(object->tlsModel != TlsModel::null) + continue; + if(object->tlsSegmentSize == 0) + continue; + + // Allocate an index for the object. + object->tlsIndex = runtimeTlsMap->indices.size(); + runtimeTlsMap->indices.push_back(object); + + // There are some libraries (e.g. Mesa) that require static TLS even though + // they expect to be dynamically loaded. + if(object->haveStaticTls) { + auto ptr = runtimeTlsMap->initialPtr + object->tlsSegmentSize; + size_t misalign = ptr & (object->tlsAlignment - 1); + if(misalign) + ptr += object->tlsAlignment - misalign; + + if(ptr > runtimeTlsMap->initialLimit) + mlibc::panicLogger() << "rtdl: Static TLS space exhausted while while" + " allocating TLS for " << object->name << frg::endlog; + + object->tlsModel = TlsModel::initial; + + if constexpr (tlsAboveTp) { + size_t tcbSize = ((sizeof(Tcb) + tlsMaxAlignment - 1) & ~(tlsMaxAlignment - 1)); + + object->tlsOffset = runtimeTlsMap->initialPtr + tcbSize; + runtimeTlsMap->initialPtr = ptr; + } else { + runtimeTlsMap->initialPtr = ptr; + object->tlsOffset = -runtimeTlsMap->initialPtr; + } + + if(verbose) + mlibc::infoLogger() << "rtdl: TLS of " << object->name + << " mapped to 0x" << frg::hex_fmt{object->tlsOffset} + << ", size: " << object->tlsSegmentSize + << ", alignment: " << object->tlsAlignment << frg::endlog; + }else{ + object->tlsModel = TlsModel::dynamic; + } + } + } +} + +void Loader::initObjects() { + initTlsObjects(mlibc::get_current_tcb(), _linkBfs, true); + + if (_mainExecutable && _mainExecutable->preInitArray) { + if (verbose) + mlibc::infoLogger() << "rtdl: Running DT_PREINIT_ARRAY functions" << frg::endlog; + + __ensure(_mainExecutable->isMainObject); + __ensure(!_mainExecutable->wasInitialized); + __ensure((_mainExecutable->preInitArraySize % sizeof(InitFuncPtr)) == 0); + for(size_t i = 0; i < _mainExecutable->preInitArraySize / sizeof(InitFuncPtr); i++) + _mainExecutable->preInitArray[i](); + } + + // Convert the breadth-first representation to a depth-first post-order representation, + // so that every object is initialized *after* its dependencies. + for(auto object : _linkBfs) { + if(!object->scheduledForInit) + _scheduleInit(object); + } + + for(auto object : _initQueue) { + if(!object->wasInitialized) + doInitialize(object); + } +} + +// TODO: Use an explicit vector to reduce stack usage to O(1)? +void Loader::_scheduleInit(SharedObject *object) { + // Here we detect cyclic dependencies. + __ensure(!object->onInitStack); + object->onInitStack = true; + + __ensure(!object->scheduledForInit); + object->scheduledForInit = true; + + for(size_t i = 0; i < object->dependencies.size(); i++) { + if(!object->dependencies[i]->scheduledForInit) + _scheduleInit(object->dependencies[i]); + } + + _initQueue.push(object); + object->onInitStack = false; +} + +void Loader::_processRelocations(Relocation &rel) { + // copy and irelative relocations have to be performed after all other relocations + if(rel.type() == R_COPY || rel.type() == R_IRELATIVE) + return; + + // resolve the symbol if there is a symbol + frg::optional p; + if(rel.symbol_index()) { + auto symbol = (elf_sym *)(rel.object()->baseAddress + rel.object()->symbolTableOffset + + rel.symbol_index() * sizeof(elf_sym)); + ObjectSymbol r(rel.object(), symbol); + + p = Scope::resolveGlobalOrLocal(*globalScope, rel.object()->localScope, + r.getString(), rel.object()->objectRts, 0); + if(!p) { + if(ELF_ST_BIND(symbol->st_info) != STB_WEAK) + mlibc::panicLogger() << "Unresolved load-time symbol " + << r.getString() << " in object " << rel.object()->name << frg::endlog; + + if(verbose) + mlibc::infoLogger() << "rtdl: Unresolved weak load-time symbol " + << r.getString() << " in object " << rel.object()->name << frg::endlog; + } + } + + switch(rel.type()) { + case R_NONE: + break; + + case R_JUMP_SLOT: { + __ensure(!rel.addend_norel()); + uintptr_t symbol_addr = p ? p->virtualAddress() : 0; + rel.relocate(symbol_addr); + } break; + +#if !defined(__riscv) + // on some architectures, R_GLOB_DAT can be defined to other relocations + case R_GLOB_DAT: { + __ensure(rel.symbol_index()); + uintptr_t symbol_addr = p ? p->virtualAddress() : 0; + rel.relocate(symbol_addr + rel.addend_norel()); + } break; +#endif + + case R_ABSOLUTE: { + __ensure(rel.symbol_index()); + uintptr_t symbol_addr = p ? p->virtualAddress() : 0; + rel.relocate(symbol_addr + rel.addend_rel()); + } break; + + case R_RELATIVE: { + __ensure(!rel.symbol_index()); + rel.relocate(rel.object()->baseAddress + rel.addend_rel()); + } break; + + // DTPMOD and DTPREL are dynamic TLS relocations (for __tls_get_addr()). + // TPOFF is a relocation to the initial TLS model. + case R_TLS_DTPMOD: { + // sets the first `sizeof(uintptr_t)` bytes of `struct __abi_tls_entry` + // this means that we can just use the `SharedObject *` to resolve whatever we need + __ensure(!rel.addend_rel()); + if(rel.symbol_index()) { + __ensure(p); + rel.relocate(elf_addr(p->object())); + }else{ + if(stillSlightlyVerbose) + mlibc::infoLogger() << "rtdl: Warning: TLS_DTPMOD64 with no symbol in object " + << rel.object()->name << frg::endlog; + rel.relocate(elf_addr(rel.object())); + } + } break; + case R_TLS_DTPREL: { + __ensure(rel.symbol_index()); + __ensure(p); + rel.relocate(p->symbol()->st_value + rel.addend_rel() - TLS_DTV_OFFSET); + } break; + case R_TLS_TPREL: { + uintptr_t off = rel.addend_rel(); + uintptr_t tls_offset = 0; + + if(rel.symbol_index()) { + __ensure(p); + if(p->object()->tlsModel != TlsModel::initial) + mlibc::panicLogger() << "rtdl: In object " << rel.object()->name + << ": Static TLS relocation to symbol " << p->getString() + << " in dynamically loaded object " + << p->object()->name << frg::endlog; + off += p->symbol()->st_value; + tls_offset = p->object()->tlsOffset; + }else{ + if(stillSlightlyVerbose) + mlibc::infoLogger() << "rtdl: Warning: TPOFF64 with no symbol" + " in object " << rel.object()->name << frg::endlog; + if(rel.object()->tlsModel != TlsModel::initial) + mlibc::panicLogger() << "rtdl: In object " << rel.object()->name + << ": Static TLS relocation to dynamically loaded object " + << rel.object()->name << frg::endlog; + tls_offset = rel.object()->tlsOffset; + } + + if constexpr (tlsAboveTp) { + off += tls_offset - sizeof(Tcb); + } else { + off += tls_offset; + } + + rel.relocate(off); + } break; + default: + mlibc::panicLogger() << "Unexpected relocation type " + << (void *) rel.type() << frg::endlog; + } +} + +void Loader::_processStaticRelocations(SharedObject *object) { + frg::optional rela_offset; + frg::optional rela_length; + + frg::optional rel_offset; + frg::optional rel_length; + + frg::optional relr_offset; + frg::optional relr_length; + + for(size_t i = 0; object->dynamic[i].d_tag != DT_NULL; i++) { + elf_dyn *dynamic = &object->dynamic[i]; + + switch(dynamic->d_tag) { + case DT_RELA: + rela_offset = dynamic->d_un.d_ptr; + break; + case DT_RELASZ: + rela_length = dynamic->d_un.d_val; + break; + case DT_RELAENT: + __ensure(dynamic->d_un.d_val == sizeof(elf_rela)); + break; + case DT_REL: + rel_offset = dynamic->d_un.d_ptr; + break; + case DT_RELSZ: + rel_length = dynamic->d_un.d_val; + break; + case DT_RELENT: + __ensure(dynamic->d_un.d_val == sizeof(elf_rel)); + break; + case DT_RELR: + relr_offset = dynamic->d_un.d_ptr; + break; + case DT_RELRSZ: + relr_length = dynamic->d_un.d_val; + break; + case DT_RELRENT: + __ensure(dynamic->d_un.d_val == sizeof(elf_relr)); + break; + } + } + + if(rela_offset && rela_length) { + __ensure(!rel_offset && !rel_length); + + for(size_t offset = 0; offset < *rela_length; offset += sizeof(elf_rela)) { + auto reloc = (elf_rela *)(object->baseAddress + *rela_offset + offset); + auto r = Relocation(object, reloc); + + _processRelocations(r); + } + }else if(rel_offset && rel_length) { + __ensure(!rela_offset && !rela_length); + + for(size_t offset = 0; offset < *rel_length; offset += sizeof(elf_rel)) { + auto reloc = (elf_rel *)(object->baseAddress + *rel_offset + offset); + auto r = Relocation(object, reloc); + + _processRelocations(r); + } + } + + if(relr_offset && relr_length) { + elf_addr *addr = nullptr; + + for(size_t offset = 0; offset < *relr_length; offset += sizeof(elf_relr)) { + auto entry = *(elf_relr *)(object->baseAddress + *relr_offset + offset); + + // Even entry indicates the beginning address. + if(!(entry & 1)) { + addr = (elf_addr *)(object->baseAddress + entry); + __ensure(addr); + *addr++ += object->baseAddress; + }else { + // Odd entry indicates entry is a bitmap of the subsequent locations to be relocated. + for(int i = 0; entry; ++i) { + if(entry & 1) { + addr[i] += object->baseAddress; + } + entry >>= 1; + } + + // Each entry describes at max 63 (on 64bit) or 31 (on 32bit) subsequent locations. + addr += CHAR_BIT * sizeof(elf_relr) - 1; + } + } + } +} + +// TODO: TLSDESC relocations aren't aarch64 specific +#ifdef __aarch64__ +extern "C" void *__mlibcTlsdescStatic(void *); +extern "C" void *__mlibcTlsdescDynamic(void *); +#endif + +void Loader::_processLazyRelocations(SharedObject *object) { + if(object->globalOffsetTable == nullptr) { + __ensure(object->lazyRelocTableOffset == 0); + return; + } + object->globalOffsetTable[1] = object; + object->globalOffsetTable[2] = (void *)&pltRelocateStub; + + if(!object->lazyTableSize) + return; + + // adjust the addresses of JUMP_SLOT relocations + __ensure(object->lazyExplicitAddend.has_value()); + size_t rel_size = (*object->lazyExplicitAddend) ? sizeof(elf_rela) : sizeof(elf_rel); + + for(size_t offset = 0; offset < object->lazyTableSize; offset += rel_size) { + elf_info type; + elf_info symbol_index; + + uintptr_t rel_addr; + uintptr_t addend [[maybe_unused]] = 0; + + if(*object->lazyExplicitAddend) { + auto reloc = (elf_rela *)(object->baseAddress + object->lazyRelocTableOffset + offset); + type = ELF_R_TYPE(reloc->r_info); + symbol_index = ELF_R_SYM(reloc->r_info); + rel_addr = object->baseAddress + reloc->r_offset; + addend = reloc->r_addend; + } else { + auto reloc = (elf_rel *)(object->baseAddress + object->lazyRelocTableOffset + offset); + type = ELF_R_TYPE(reloc->r_info); + symbol_index = ELF_R_SYM(reloc->r_info); + rel_addr = object->baseAddress + reloc->r_offset; + } + + switch (type) { + case R_JUMP_SLOT: + if(eagerBinding) { + auto symbol = (elf_sym *)(object->baseAddress + object->symbolTableOffset + + symbol_index * sizeof(elf_sym)); + ObjectSymbol r(object, symbol); + auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, r.getString(), object->objectRts, 0); + + if(!p) { + if(ELF_ST_BIND(symbol->st_info) != STB_WEAK) + mlibc::panicLogger() << "rtdl: Unresolved JUMP_SLOT symbol " + << r.getString() << " in object " << object->name << frg::endlog; + + if(verbose) + mlibc::infoLogger() << "rtdl: Unresolved weak JUMP_SLOT symbol " + << r.getString() << " in object " << object->name << frg::endlog; + *((uintptr_t *)rel_addr) = 0; + }else{ + *((uintptr_t *)rel_addr) = p->virtualAddress(); + } + }else{ + *((uintptr_t *)rel_addr) += object->baseAddress; + } + break; +#if defined(__x86_64__) + case R_X86_64_IRELATIVE: { + auto ptr = object->baseAddress + addend; + auto target = reinterpret_cast(ptr)(); + *((uintptr_t *)rel_addr) = target; + break; + } +#endif +// TODO: TLSDESC relocations aren't aarch64 specific +#if defined(__aarch64__) + case R_AARCH64_TLSDESC: { + size_t symValue = 0; + SharedObject *target = nullptr; + + if (symbol_index) { + auto symbol = (elf_sym *)(object->baseAddress + object->symbolTableOffset + + symbol_index * sizeof(elf_sym)); + ObjectSymbol r(object, symbol); + auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, r.getString(), object->objectRts, 0); + + if (!p) { + __ensure(ELF_ST_BIND(symbol->st_info) != STB_WEAK); + mlibc::panicLogger() << "rtdl: Unresolved TLSDESC for symbol " + << r.getString() << " in object " << object->name << frg::endlog; + } else { + target = p->object(); + if (p->symbol()) + symValue = p->symbol()->st_value; + } + } else { + target = object; + } + + __ensure(target); + + if (target->tlsModel == TlsModel::initial) { + ((uint64_t *)rel_addr)[0] = reinterpret_cast(&__mlibcTlsdescStatic); + // TODO: guard the subtraction of TCB size with `if constexpr (tlsAboveTp)` + // for the arch-generic case + __ensure(tlsAboveTp == true); + ((uint64_t *)rel_addr)[1] = symValue + target->tlsOffset + addend - sizeof(Tcb); + } else { + struct TlsdescData { + uintptr_t tlsIndex; + uintptr_t addend; + }; + + // Access DTV for object to force the entry to be allocated and initialized + accessDtv(target); + + __ensure(target->tlsIndex < mlibc::get_current_tcb()->dtvSize); + + // TODO: We should free this when the DSO gets destroyed + auto data = frg::construct(getAllocator()); + data->tlsIndex = target->tlsIndex; + data->addend = symValue + addend; + + ((uint64_t *)rel_addr)[0] = reinterpret_cast(&__mlibcTlsdescDynamic); + ((uint64_t *)rel_addr)[1] = reinterpret_cast(data); + } + } break; +#endif + default: + mlibc::panicLogger() << "unimplemented lazy relocation type " << type << frg::endlog; + break; + } + } +} + diff --git a/lib/mlibc/options/rtdl/generic/linker.hpp b/lib/mlibc/options/rtdl/generic/linker.hpp new file mode 100644 index 0000000..ad84ca9 --- /dev/null +++ b/lib/mlibc/options/rtdl/generic/linker.hpp @@ -0,0 +1,402 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include "elf.hpp" + +struct ObjectRepository; +struct Scope; +struct Loader; +struct SharedObject; + +extern uint64_t rtsCounter; + +enum class TlsModel { + null, + initial, + dynamic +}; + +enum class LinkerError { + success, + notFound, + fileTooShort, + notElf, + wrongElfType, + outOfMemory, + invalidProgramHeader +}; + +// -------------------------------------------------------- +// ObjectRepository +// -------------------------------------------------------- + +struct ObjectRepository { + ObjectRepository(); + + ObjectRepository(const ObjectRepository &) = delete; + + ObjectRepository &operator= (const ObjectRepository &) = delete; + + // This is primarily used to create a SharedObject for the RTDL 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); + + SharedObject *findCaller(void *address); + + SharedObject *findLoadedObject(frg::string_view name); + + // Used by dl_iterate_phdr: stores objects in the order they are loaded. + frg::vector loadedObjects; + +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 _discoverDependencies(SharedObject *object, Scope *localScope, uint64_t rts); + + void _addLoadedObject(SharedObject *object); + + frg::hash_map, MemoryAllocator> _nameMap; +}; + +// -------------------------------------------------------- +// SharedObject +// -------------------------------------------------------- + +enum class HashStyle { + none, + systemV, + gnu +}; + +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 *initArray = nullptr; + InitFuncPtr *preInitArray = nullptr; + size_t initArraySize = 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; + + 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; + size_t tlsOffset; + + uint64_t globalRts; + bool wasLinked; + + bool scheduledForInit; + bool onInitStack; + bool wasInitialized; + + // PHDR related stuff, we only set these for the main executable + void *phdrPointer = nullptr; + size_t phdrEntrySize = 0; + size_t phdrCount = 0; +}; + +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(); + +private: + SharedObject *_object; + const elf_sym *_symbol; +}; + +frg::optional resolveInObject(SharedObject *object, frg::string_view string); + +// -------------------------------------------------------- +// 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); + static frg::optional resolveGlobalOrLocalNext(Scope &globalScope, + Scope *localScope, frg::string_view string, SharedObject *origin); + + Scope(bool isGlobal = false); + + void appendObject(SharedObject *object); + + frg::optional resolveSymbol(frg::string_view string, uint64_t skipRts, ResolveFlags flags); + + bool isGlobal; + +private: + frg::optional _resolveNext(frg::string_view string, SharedObject *target); +public: // TODO: Make this private again. (Was made public for __dlapi_reverse()). + frg::vector _objects; +}; + +extern frg::manual_box globalScope; + +// -------------------------------------------------------- +// Loader +// -------------------------------------------------------- + +class 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(); + +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"))); + +// -------------------------------------------------------- +// RTDL interface +// -------------------------------------------------------- + +void *rtdl_auxvector(); + diff --git a/lib/mlibc/options/rtdl/generic/main.cpp b/lib/mlibc/options/rtdl/generic/main.cpp new file mode 100644 index 0000000..3cff1e4 --- /dev/null +++ b/lib/mlibc/options/rtdl/generic/main.cpp @@ -0,0 +1,844 @@ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "elf.hpp" +#include "linker.hpp" + +#if __MLIBC_POSIX_OPTION +#include +#endif + +#define HIDDEN __attribute__((__visibility__("hidden"))) +#define EXPORT __attribute__((__visibility__("default"))) + +static constexpr bool logEntryExit = false; +static constexpr bool logStartup = false; +static constexpr bool logDlCalls = false; + +#ifndef MLIBC_STATIC_BUILD +extern HIDDEN void *_GLOBAL_OFFSET_TABLE_[]; +extern HIDDEN elf_dyn _DYNAMIC[]; +#endif + +namespace mlibc { + // Declared in options/internal/mlibc/tcb.hpp. + bool tcb_available_flag = false; +} + +mlibc::RtdlConfig rtdlConfig; + +bool ldShowAuxv = false; + +uintptr_t *entryStack; +frg::manual_box initialRepository; +frg::manual_box globalScope; + +frg::manual_box runtimeTlsMap; + +// We use a small vector of size 4 to avoid memory allocation for the default library paths +frg::manual_box> libraryPaths; + +frg::manual_box> preloads; + +static SharedObject *executableSO; +extern HIDDEN char __ehdr_start[]; + +// Global debug interface variable +DebugInterface globalDebugInterface; + +#ifndef MLIBC_STATIC_BUILD + +// Use a PC-relative instruction sequence to find our runtime load address. +uintptr_t getLdsoBase() { +#if defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) + // On x86_64, the first GOT entry holds the link-time address of _DYNAMIC. + // TODO: This isn't guaranteed on AArch64, so this might fail with some linkers. + auto linktime_dynamic = reinterpret_cast(_GLOBAL_OFFSET_TABLE_[0]); + auto runtime_dynamic = reinterpret_cast(_DYNAMIC); + return runtime_dynamic - linktime_dynamic; +#elif defined(__riscv) + return reinterpret_cast(&__ehdr_start); +#endif +} + +// Relocates the dynamic linker (i.e. this DSO) itself. +// Assumptions: +// - There are no references to external symbols. +// Note that this code is fragile in the sense that it must not contain relocations itself. +// TODO: Use tooling to verify this at compile time. +extern "C" void relocateSelf() { + size_t rela_offset = 0; + size_t rela_size = 0; + size_t rel_offset = 0; + size_t rel_size = 0; + size_t relr_offset = 0; + size_t relr_size = 0; + for(size_t i = 0; _DYNAMIC[i].d_tag != DT_NULL; i++) { + auto ent = &_DYNAMIC[i]; + switch(ent->d_tag) { + case DT_REL: rel_offset = ent->d_un.d_ptr; break; + case DT_RELSZ: rel_size = ent->d_un.d_val; break; + case DT_RELA: rela_offset = ent->d_un.d_ptr; break; + case DT_RELASZ: rela_size = ent->d_un.d_val; break; + case DT_RELR: relr_offset = ent->d_un.d_ptr; break; + case DT_RELRSZ: relr_size = ent->d_un.d_val; break; + } + } + + auto ldso_base = getLdsoBase(); + + __ensure((rel_offset != 0) ^ (rela_offset != 0)); + + for(size_t disp = 0; disp < rela_size; disp += sizeof(elf_rela)) { + auto reloc = reinterpret_cast(ldso_base + rela_offset + disp); + + auto type = ELF_R_TYPE(reloc->r_info); + if(ELF_R_SYM(reloc->r_info)) + __builtin_trap(); + + auto p = reinterpret_cast(ldso_base + reloc->r_offset); + switch(type) { + case R_RELATIVE: + *p = ldso_base + reloc->r_addend; + break; + default: + __builtin_trap(); + } + } + + for(size_t disp = 0; disp < rel_size; disp += sizeof(elf_rel)) { + auto reloc = reinterpret_cast(ldso_base + rel_offset + disp); + + auto type = ELF_R_TYPE(reloc->r_info); + if(ELF_R_SYM(reloc->r_info)) + __builtin_trap(); + + auto p = reinterpret_cast(ldso_base + reloc->r_offset); + switch(type) { + case R_RELATIVE: + *p += ldso_base; + break; + default: + __builtin_trap(); + } + } + + elf_addr *addr = nullptr; + for(size_t disp = 0; disp < relr_size; disp += sizeof(elf_relr)) { + auto entry = *(elf_relr *)(ldso_base + relr_offset + disp); + + // Even entry indicates the beginning address. + if(!(entry & 1)) { + addr = (elf_addr *)(ldso_base + entry); + __ensure(addr); + *addr++ += ldso_base; + }else { + // Odd entry indicates entry is a bitmap of the subsequent locations to be relocated. + for(int i = 0; entry; ++i) { + if(entry & 1) { + addr[i] += ldso_base; + } + entry >>= 1; + } + + // Each entry describes at max 63 (on 64bit) or 31 (on 32bit) subsequent locations. + addr += CHAR_BIT * sizeof(elf_relr) - 1; + } + } +} +#endif + +extern "C" void *lazyRelocate(SharedObject *object, unsigned int rel_index) { + __ensure(object->lazyExplicitAddend); + auto reloc = (elf_rela *)(object->baseAddress + object->lazyRelocTableOffset + + rel_index * sizeof(elf_rela)); + auto type = ELF_R_TYPE(reloc->r_info); + auto symbol_index = ELF_R_SYM(reloc->r_info); + + __ensure(type == R_X86_64_JUMP_SLOT); + __ensure(ELF_CLASS == ELFCLASS64); + + auto symbol = (elf_sym *)(object->baseAddress + object->symbolTableOffset + + symbol_index * sizeof(elf_sym)); + ObjectSymbol r(object, symbol); + auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, r.getString(), object->objectRts, 0); + if(!p) + mlibc::panicLogger() << "Unresolved JUMP_SLOT symbol" << frg::endlog; + + //mlibc::infoLogger() << "Lazy relocation to " << symbol_str + // << " resolved to " << pointer << frg::endlog; + + *(uint64_t *)(object->baseAddress + reloc->r_offset) = p->virtualAddress(); + return (void *)p->virtualAddress(); +} + +extern "C" [[ gnu::visibility("default") ]] void *__rtdl_allocateTcb() { + auto tcb = allocateTcb(); + initTlsObjects(tcb, globalScope->_objects, false); + return tcb; +} + +extern "C" { + [[ gnu::visibility("hidden") ]] void dl_debug_state() { + // This function is used to signal changes in the debugging link map, + // GDB just sets a breakpoint on this function and we can call it + // everytime we update the link map. We don't need to implement + // anything besides defining and calling it. + } +} + +extern "C" [[gnu::alias("dl_debug_state"), gnu::visibility("default")]] void _dl_debug_state() noexcept; + +// This symbol can be used by GDB to find the global interface structure +[[ gnu::visibility("default") ]] DebugInterface *_dl_debug_addr = &globalDebugInterface; + +static frg::vector parseList(frg::string_view paths, frg::string_view separators) { + frg::vector list{getAllocator()}; + + size_t p = 0; + while(p < paths.size()) { + size_t s; // Offset of next colon or end of string. + if(size_t cs = paths.find_first_of(separators, p); cs != size_t(-1)) { + s = cs; + }else{ + s = paths.size(); + } + + auto path = paths.sub_string(p, s - p); + p = s + 1; + + if(path.size() == 0) + continue; + + if(path.ends_with("/")) { + size_t i = path.size() - 1; + while(i > 0 && path[i] == '/') + i--; + path = path.sub_string(0, i + 1); + } + + if(path == "/") + path = ""; + + list.push_back(path); + } + + return list; +} + +extern "C" void *interpreterMain(uintptr_t *entry_stack) { + if(logEntryExit) + mlibc::infoLogger() << "Entering ld.so" << frg::endlog; + entryStack = entry_stack; + runtimeTlsMap.initialize(); + libraryPaths.initialize(getAllocator()); + preloads.initialize(getAllocator()); + + void *phdr_pointer = 0; + size_t phdr_entry_size = 0; + size_t phdr_count = 0; + void *entry_pointer = 0; + void *stack_entropy = nullptr; + + const char *execfn = "(executable)"; + +#ifndef MLIBC_STATIC_BUILD + using ctor_fn = void(*)(void); + + ctor_fn *ldso_ctors = nullptr; + size_t num_ldso_ctors = 0; + + auto ldso_base = getLdsoBase(); + if(logStartup) { + mlibc::infoLogger() << "ldso: Own base address is: 0x" + << frg::hex_fmt(ldso_base) << frg::endlog; + mlibc::infoLogger() << "ldso: Own dynamic section is at: " << _DYNAMIC << frg::endlog; + } + +#ifdef __x86_64__ + // These entries are reserved on x86_64. + // TODO: Use a fake PLT stub that reports an error message? + _GLOBAL_OFFSET_TABLE_[1] = 0; + _GLOBAL_OFFSET_TABLE_[2] = 0; +#endif + + // Validate our own dynamic section. + // Here, we make sure that the dynamic linker does not need relocations itself. + uintptr_t strtab_offset = 0; + uintptr_t soname_str = 0; + for(size_t i = 0; _DYNAMIC[i].d_tag != DT_NULL; i++) { + auto ent = &_DYNAMIC[i]; + switch(ent->d_tag) { + case DT_STRTAB: strtab_offset = ent->d_un.d_ptr; break; + case DT_SONAME: soname_str = ent->d_un.d_val; break; + case DT_INIT_ARRAY: ldso_ctors = reinterpret_cast(ent->d_un.d_ptr + ldso_base); break; + case DT_INIT_ARRAYSZ: num_ldso_ctors = ent->d_un.d_val / sizeof(ctor_fn); break; + case DT_HASH: + case DT_GNU_HASH: + case DT_STRSZ: + case DT_SYMTAB: + case DT_SYMENT: + case DT_RELA: + case DT_RELASZ: + case DT_RELAENT: + case DT_RELACOUNT: + case DT_DEBUG: + case DT_REL: + case DT_RELSZ: + case DT_RELENT: + case DT_RELCOUNT: + case DT_RELR: + case DT_RELRSZ: + case DT_RELRENT: + continue; + default: + __ensure(!"Unexpected dynamic entry in program interpreter"); + } + } + __ensure(strtab_offset); + __ensure(soname_str); + + // Find the auxiliary vector by skipping args and environment. + auto aux = entryStack; + aux += *aux + 1; // First, we skip argc and all args. + __ensure(!*aux); + aux++; + while(*aux) { // Loop through the environment. + auto env = reinterpret_cast(*aux); + frg::string_view view{env}; + size_t s = view.find_first('='); + + if(s == size_t(-1)) + mlibc::panicLogger() << "rtdl: environment '" << env << "' is missing a '='" << frg::endlog; + + auto name = view.sub_string(0, s); + auto value = const_cast(view.data() + s + 1); + + if(name == "LD_SHOW_AUXV" && *value && *value != '0') { + ldShowAuxv = true; + }else if(name == "LD_LIBRARY_PATH" && *value) { + for(auto path : parseList(value, ":;")) + libraryPaths->push_back(path); + }else if(name == "LD_PRELOAD" && *value) { + *preloads = parseList(value, " :"); + } + + aux++; + } + aux++; + + // Add default library paths + libraryPaths->push_back("/lib"); + libraryPaths->push_back("/lib64"); + libraryPaths->push_back("/usr/lib"); + libraryPaths->push_back("/usr/lib64"); + + // Parse the actual vector. + while(true) { + auto value = aux + 1; + if(!(*aux)) + break; + + if(ldShowAuxv) { + switch(*aux) { + case AT_PHDR: mlibc::infoLogger() << "AT_PHDR: 0x" << frg::hex_fmt{*value} << frg::endlog; break; + case AT_PHENT: mlibc::infoLogger() << "AT_PHENT: " << *value << frg::endlog; break; + case AT_PHNUM: mlibc::infoLogger() << "AT_PHNUM: " << *value << frg::endlog; break; + case AT_ENTRY: mlibc::infoLogger() << "AT_ENTRY: 0x" << frg::hex_fmt{*value} << frg::endlog; break; + case AT_PAGESZ: mlibc::infoLogger() << "AT_PAGESZ: " << *value << frg::endlog; break; + case AT_BASE: mlibc::infoLogger() << "AT_BASE: 0x" << frg::hex_fmt{*value} << frg::endlog; break; + case AT_FLAGS: mlibc::infoLogger() << "AT_FLAGS: 0x" << frg::hex_fmt{*value} << frg::endlog; break; + case AT_NOTELF: mlibc::infoLogger() << "AT_NOTELF: " << frg::hex_fmt{*value} << frg::endlog; break; + case AT_UID: mlibc::infoLogger() << "AT_UID: " << *value << frg::endlog; break; + case AT_EUID: mlibc::infoLogger() << "AT_EUID: " << *value << frg::endlog; break; + case AT_GID: mlibc::infoLogger() << "AT_GID: " << *value << frg::endlog; break; + case AT_EGID: mlibc::infoLogger() << "AT_EGID: " << *value << frg::endlog; break; +#ifdef AT_PLATFORM + case AT_PLATFORM: mlibc::infoLogger() << "AT_PLATFORM: " << reinterpret_cast(*value) << frg::endlog; break; +#endif +#ifdef AT_HWCAP + case AT_HWCAP: mlibc::infoLogger() << "AT_HWCAP: " << frg::hex_fmt{*value} << frg::endlog; break; +#endif +#ifdef AT_CLKTCK + case AT_CLKTCK: mlibc::infoLogger() << "AT_CLKTCK: " << *value << frg::endlog; break; +#endif +#ifdef AT_FPUCW + case AT_FPUCW: mlibc::infoLogger() << "AT_FPUCW: " << frg::hex_fmt{*value} << frg::endlog; break; +#endif +#ifdef AT_SECURE + case AT_SECURE: mlibc::infoLogger() << "AT_SECURE: " << *value << frg::endlog; break; +#endif +#ifdef AT_RANDOM + case AT_RANDOM: mlibc::infoLogger() << "AT_RANDOM: 0x" << frg::hex_fmt{*value} << frg::endlog; break; +#endif +#ifdef AT_EXECFN + case AT_EXECFN: mlibc::infoLogger() << "AT_EXECFN: " << reinterpret_cast(*value) << frg::endlog; break; +#endif +#ifdef AT_SYSINFO_EHDR + case AT_SYSINFO_EHDR: mlibc::infoLogger() << "AT_SYSINFO_EHDR: 0x" << frg::hex_fmt{*value} << frg::endlog; break; +#endif + } + } + + // TODO: Whitelist auxiliary vector entries here? + switch(*aux) { + case AT_PHDR: phdr_pointer = reinterpret_cast(*value); break; + case AT_PHENT: phdr_entry_size = *value; break; + case AT_PHNUM: phdr_count = *value; break; + case AT_ENTRY: entry_pointer = reinterpret_cast(*value); break; + case AT_EXECFN: execfn = reinterpret_cast(*value); break; + case AT_RANDOM: stack_entropy = reinterpret_cast(*value); break; + case AT_SECURE: rtdlConfig.secureRequired = reinterpret_cast(*value); break; + } + + aux += 2; + } + globalDebugInterface.base = reinterpret_cast(ldso_base); + +// This is here because libgcc will add a global constructor on glibc Linux +// (which is what it believes we are due to the aarch64-linux-gnu toolchain) +// in order to check if LSE atomics are supported. +// +// This is not necessary on a custom Linux toolchain and is purely an artifact of +// using the host toolchain. +#if defined(__aarch64__) && defined(__gnu_linux__) + for (size_t i = 0; i < num_ldso_ctors; i++) { + if(logStartup) + mlibc::infoLogger() << "ldso: Running own constructor at " + << reinterpret_cast(ldso_ctors[i]) + << frg::endlog; + ldso_ctors[i](); + } +#else + if (num_ldso_ctors > 0) { + mlibc::panicLogger() << "ldso: Found unexpected own global constructor(s), init_array starts at: " + << ldso_ctors + << frg::endlog; + } +#endif + +#else + auto ehdr = reinterpret_cast(__ehdr_start); + phdr_pointer = reinterpret_cast((uintptr_t)ehdr->e_phoff + (uintptr_t)ehdr); + phdr_entry_size = ehdr->e_phentsize; + phdr_count = ehdr->e_phnum; + entry_pointer = reinterpret_cast(ehdr->e_entry); +#endif + __ensure(phdr_pointer); + __ensure(entry_pointer); + + if(logStartup) + mlibc::infoLogger() << "ldso: Executable PHDRs are at " << phdr_pointer + << frg::endlog; + + // perform the initial dynamic linking + initialRepository.initialize(); + + globalScope.initialize(true); + + // Add the dynamic linker, as well as the exectuable to the repository. +#ifndef MLIBC_STATIC_BUILD + auto ldso_soname = reinterpret_cast(ldso_base + strtab_offset + soname_str); + auto ldso = initialRepository->injectObjectFromDts(ldso_soname, + frg::string { getAllocator() }, + ldso_base, _DYNAMIC, 1); + ldso->phdrPointer = phdr_pointer; + ldso->phdrCount = phdr_count; + ldso->phdrEntrySize = phdr_entry_size; + + // TODO: support non-zero base addresses? + executableSO = initialRepository->injectObjectFromPhdrs(execfn, + frg::string { execfn, getAllocator() }, + phdr_pointer, phdr_entry_size, phdr_count, entry_pointer, 1); + + // We can't initialise the ldso object after the executable SO, + // so we have to set the ldso path after loading both. + ldso->path = executableSO->interpreterPath; + +#else + executableSO = initialRepository->injectStaticObject(execfn, + frg::string{ execfn, getAllocator() }, + phdr_pointer, phdr_entry_size, phdr_count, entry_pointer, 1); + globalDebugInterface.base = (void*)executableSO->baseAddress; +#endif + + globalDebugInterface.head = &executableSO->linkMap; + executableSO->inLinkMap = true; + Loader linker{globalScope.get(), executableSO, true, 1}; + linker.linkObjects(executableSO); + + mlibc::initStackGuard(stack_entropy); + + auto tcb = allocateTcb(); + if(mlibc::sys_tcb_set(tcb)) + __ensure(!"sys_tcb_set() failed"); + tcb->tid = mlibc::this_tid(); + mlibc::tcb_available_flag = true; + + globalDebugInterface.ver = 1; + globalDebugInterface.brk = &dl_debug_state; + globalDebugInterface.state = 0; + dl_debug_state(); + + linker.initObjects(); + + if(logEntryExit) + mlibc::infoLogger() << "Leaving ld.so, jump to " + << (void *)executableSO->entry << frg::endlog; + return executableSO->entry; +} + +const char *lastError; + +extern "C" [[ gnu::visibility("default") ]] uintptr_t *__dlapi_entrystack() { + return entryStack; +} + +extern "C" [[ gnu::visibility("default") ]] +const char *__dlapi_error() { + auto error = lastError; + lastError = nullptr; + return error; +} + +extern "C" [[ gnu::visibility("default") ]] +void *__dlapi_get_tls(struct __abi_tls_entry *entry) { + return reinterpret_cast(accessDtv(entry->object)) + entry->offset; +} + +extern "C" [[ gnu::visibility("default") ]] +const mlibc::RtdlConfig &__dlapi_get_config() { + return rtdlConfig; +} + +#if __MLIBC_POSIX_OPTION + +extern "C" [[ gnu::visibility("default") ]] +void *__dlapi_open(const char *file, int flags, void *returnAddress) { + if (logDlCalls) + mlibc::infoLogger() << "rtdl: __dlapi_open(" << (file ? file : "nullptr") << ")" << frg::endlog; + + if (flags & RTLD_DEEPBIND) + mlibc::infoLogger() << "rtdl: dlopen(RTLD_DEEPBIND) is unsupported" << frg::endlog; + + if(!file) + return executableSO; + + // TODO: Thread-safety! + auto rts = rtsCounter++; + + SharedObject *object; + if (flags & RTLD_NOLOAD) { + object = initialRepository->findLoadedObject(file); + if (object && object->globalRts == 0 && (flags & RTLD_GLOBAL)) { + // The object was opened with RTLD_LOCAL, but we are called with RTLD_NOLOAD | RTLD_GLOBAL. + // According to the man page, we should promote to the global scope here. + object->globalRts = rts; + globalScope->appendObject(object); + } + } else { + bool isGlobal = flags & RTLD_GLOBAL; + Scope *newScope = isGlobal ? globalScope.get() : nullptr; + + frg::expected objectResult; + if (frg::string_view{file}.find_first('/') == size_t(-1)) { + // In order to know which RUNPATH / RPATH to process, we must find the calling object. + SharedObject *origin = initialRepository->findCaller(returnAddress); + if (!origin) { + mlibc::panicLogger() << "rtdl: unable to determine calling object of dlopen " + << "(ra = " << returnAddress << ")" << frg::endlog; + } + + objectResult = initialRepository->requestObjectWithName(file, origin, newScope, !isGlobal, rts); + } else { + objectResult = initialRepository->requestObjectAtPath(file, newScope, !isGlobal, rts); + } + + if(!objectResult) { + switch (objectResult.error()) { + case LinkerError::success: + __builtin_unreachable(); + case LinkerError::notFound: + lastError = "Cannot locate requested DSO"; + break; + case LinkerError::fileTooShort: + lastError = "File too short"; + break; + case LinkerError::notElf: + lastError = "File is not an ELF file"; + break; + case LinkerError::wrongElfType: + lastError = "File has wrong ELF type"; + break; + case LinkerError::outOfMemory: + lastError = "Out of memory"; + break; + case LinkerError::invalidProgramHeader: + lastError = "File has invalid program header"; + break; + } + return nullptr; + } + object = objectResult.value(); + + Loader linker{object->localScope, nullptr, false, rts}; + linker.linkObjects(object); + linker.initObjects(); + } + + dl_debug_state(); + + return object; +} + +extern "C" [[ gnu::visibility("default") ]] +void *__dlapi_resolve(void *handle, const char *string, void *returnAddress) { + if (logDlCalls) { + const char *name; + bool quote = false; + if (handle == RTLD_DEFAULT) { + name = "RTLD_DEFAULT"; + } else if (handle == RTLD_NEXT) { + name = "RTLD_NEXT"; + } else { + name = ((SharedObject *)handle)->name.data(); + quote = true; + } + + mlibc::infoLogger() << "rtdl: __dlapi_resolve(" << (quote ? "\"" : "") << name + << (quote ? "\"" : "") << ", \"" << string << "\")" << frg::endlog; + } + + frg::optional target; + + if (handle == RTLD_DEFAULT) { + target = globalScope->resolveSymbol(string, 0, 0); + } else if (handle == RTLD_NEXT) { + SharedObject *origin = initialRepository->findCaller(returnAddress); + if (!origin) { + mlibc::panicLogger() << "rtdl: unable to determine calling object of dlsym " + << "(ra = " << returnAddress << ")" << frg::endlog; + } + + target = Scope::resolveGlobalOrLocalNext(*globalScope, origin->localScope, string, origin); + } else { + // POSIX does not unambiguously state how dlsym() is supposed to work; it just + // states that "The symbol resolution algorithm used shall be dependency order + // as described in dlopen()". + // + // Linux libc's lookup the symbol in the given DSO and all of its dependencies + // in breadth-first order. That is also what we implement here. + // + // Note that this *differs* from the algorithm that is used for relocations + // (since the algorithm used for relocations takes (i) the global scope, + // and (ii) the local scope of the DSO into account (which can contain more objects + // than just the dependencies of the DSO, if the DSO was loaded as a dependency + // of a dlopen()ed DSO). + + frg::vector queue{getAllocator()}; + + struct Token { }; + frg::hash_map< + SharedObject *, Token, + frg::hash, MemoryAllocator + > visited{frg::hash{}, getAllocator()}; + + auto root = reinterpret_cast(handle); + visited.insert(root, Token{}); + queue.push_back(root); + + for(size_t i = 0; i < queue.size(); i++) { + auto current = queue[i]; + + target = resolveInObject(current, string); + if(target) + break; + + for(auto dep : current->dependencies) { + if(visited.get(dep)) + continue; + visited.insert(dep, Token{}); + queue.push_back(dep); + } + } + } + + if (!target) { + if (logDlCalls) + mlibc::infoLogger() << "rtdl: could not resolve \"" << string << "\"" << frg::endlog; + + lastError = "Cannot resolve requested symbol"; + return nullptr; + } + return reinterpret_cast(target->virtualAddress()); +} + +struct __dlapi_symbol { + const char *file; + void *base; + const char *symbol; + void *address; +}; + +extern "C" [[ gnu::visibility("default") ]] +int __dlapi_reverse(const void *ptr, __dlapi_symbol *info) { + if (logDlCalls) + mlibc::infoLogger() << "rtdl: __dlapi_reverse(" << ptr << ")" << frg::endlog; + + for(size_t i = 0; i < initialRepository->loadedObjects.size(); i++) { + auto object = initialRepository->loadedObjects[i]; + + auto eligible = [&] (ObjectSymbol cand) { + if(cand.symbol()->st_shndx == SHN_UNDEF) + return false; + + auto bind = ELF_ST_BIND(cand.symbol()->st_info); + if(bind != STB_GLOBAL && bind != STB_WEAK) + return false; + + return true; + }; + + auto hash_table = (Elf64_Word *)(object->baseAddress + object->hashTableOffset); + auto num_symbols = hash_table[1]; + for(size_t i = 0; i < num_symbols; i++) { + ObjectSymbol cand{object, (elf_sym *)(object->baseAddress + + object->symbolTableOffset + i * sizeof(elf_sym))}; + if(eligible(cand) && cand.virtualAddress() == reinterpret_cast(ptr)) { + if (logDlCalls) + mlibc::infoLogger() << "rtdl: Found symbol " << cand.getString() << " in object " + << object->path << frg::endlog; + + info->file = object->path.data(); + info->base = reinterpret_cast(object->baseAddress); + info->symbol = cand.getString(); + info->address = reinterpret_cast(cand.virtualAddress()); + return 0; + } + } + } + + // Not found, find the DSO it should be in. + for(size_t i = 0; i < initialRepository->loadedObjects.size(); i++) { + auto object = initialRepository->loadedObjects[i]; + + for(size_t j = 0; j < object->phdrCount; j++) { + auto phdr = (elf_phdr *)((uintptr_t)object->phdrPointer + j * object->phdrEntrySize); + if(phdr->p_type != PT_LOAD) { + continue; + } + uintptr_t start = object->baseAddress + phdr->p_vaddr; + uintptr_t end = start + phdr->p_memsz; + if(reinterpret_cast(ptr) >= start && reinterpret_cast(ptr) < end) { + mlibc::infoLogger() << "rtdl: Found DSO " << object->path << frg::endlog; + info->file = object->path.data(); + info->base = reinterpret_cast(object->baseAddress); + info->symbol = nullptr; + info->address = 0; + return 0; + } + } + } + + if (logDlCalls) + mlibc::infoLogger() << "rtdl: Could not find symbol in __dlapi_reverse()" << frg::endlog; + + return -1; +} + +extern "C" [[ gnu::visibility("default") ]] +int __dlapi_close(void *) { + if (logDlCalls) + mlibc::infoLogger() << "mlibc: dlclose() is a no-op" << frg::endlog; + return 0; +} + +#endif + +extern "C" [[ gnu::visibility("default") ]] +int __dlapi_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void*), void *data) { + int last_return = 0; + for (auto object : initialRepository->loadedObjects) { + struct dl_phdr_info info; + info.dlpi_addr = object->baseAddress; + info.dlpi_name = object->name.data(); + + if(object->isMainObject) { + info.dlpi_name = ""; + } else { + info.dlpi_name = object->name.data(); + } + info.dlpi_phdr = static_cast(object->phdrPointer); + info.dlpi_phnum = object->phdrCount; + info.dlpi_adds = rtsCounter; + info.dlpi_subs = 0; // TODO(geert): implement dlclose(). + if (object->tlsModel != TlsModel::null) + info.dlpi_tls_modid = object->tlsIndex; + else + info.dlpi_tls_modid = 0; + info.dlpi_tls_data = tryAccessDtv(object); + + last_return = callback(&info, sizeof(struct dl_phdr_info), data); + if(last_return) + return last_return; + } + + return last_return; +} + +extern "C" [[ gnu::visibility("default") ]] +void __dlapi_enter(uintptr_t *entry_stack) { +#if MLIBC_STATIC_BUILD + interpreterMain(entry_stack); +#else + (void)entry_stack; +#endif +} + +// XXX(qookie): +// This is here because libgcc will call into __getauxval on glibc Linux +// (which is what it believes we are due to the aarch64-linux-gnu toolchain) +// in order to find AT_HWCAP to discover if LSE atomics are supported. +// +// This is not necessary on a custom Linux toolchain and is purely an artifact of +// using the host toolchain. + +// __gnu_linux__ is the define checked by libgcc +#if defined(__aarch64__) && defined(__gnu_linux__) && !defined(MLIBC_STATIC_BUILD) + +extern "C" unsigned long __getauxval(unsigned long type) { + // Find the auxiliary vector by skipping args and environment. + auto aux = entryStack; + aux += *aux + 1; // Skip argc and all arguments + __ensure(!*aux); + aux++; + while(*aux) // Now, we skip the environment. + aux++; + aux++; + + // Parse the auxiliary vector. + while(true) { + auto value = aux + 1; + if(*aux == AT_NULL) { + return 0; + }else if(*aux == type) { + return *value; + } + aux += 2; + } +} + +#endif -- cgit v1.2.3