#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 logSymbolVersions = false; constexpr bool eagerBinding = true; #if defined(__x86_64__) || defined(__i386__) constexpr inline bool tlsAboveTp = false; constexpr inline uintptr_t tlsOffsetFromTp = 0; #elif defined(__aarch64__) constexpr inline bool tlsAboveTp = true; constexpr inline uintptr_t tlsOffsetFromTp = 16; #elif defined(__riscv) constexpr inline bool tlsAboveTp = true; constexpr inline uintptr_t tlsOffsetFromTp = 0; #elif defined(__m68k__) constexpr inline bool tlsAboveTp = true; constexpr inline ptrdiff_t tlsOffsetFromTp = -0x7000; #elif defined(__loongarch64) constexpr inline bool tlsAboveTp = true; constexpr inline uintptr_t tlsOffsetFromTp = 0; #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 __fini_array_start[]; extern "C" size_t __fini_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; namespace { unsigned long getauxval(unsigned long type) { auto aux = reinterpret_cast(rtld_auxvector()); __ensure(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; } } #if defined(__riscv) #include int __riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpusetsize, cpu_set_t *cpus, unsigned int flags) { return mlibc::sys_riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); } #endif elf_addr handleIfunc(elf_addr addr) { #if defined(__aarch64__) auto hwcap = getauxval(AT_HWCAP); ifunc_arg ifunc_arg = { ._size = sizeof(ifunc_arg), ._hwcap = hwcap, #if defined(AT_HWCAP2) ._hwcap2 = getauxval(AT_HWCAP2), #else ._hwcap2 = 0, #endif #if defined(AT_HWCAP3) ._hwcap3 = getauxval(AT_HWCAP3), #else ._hwcap3 = 0, #endif #if defined(AT_HWCAP4) ._hwcap4 = getauxval(AT_HWCAP4), #else ._hwcap4 = 0, #endif }; return reinterpret_cast(addr)(hwcap | (1ULL << 62), &ifunc_arg); #elif defined(__riscv) auto hwcap = getauxval(AT_HWCAP); return reinterpret_cast(addr) (hwcap, &__riscv_hwprobe, nullptr); #elif defined(__loongarch64) ifunc_arg ifunc_arg = { ._size = sizeof(ifunc_arg), ._hwcap = getauxval(AT_HWCAP), }; return reinterpret_cast(addr)(&ifunc_arg); #elif defined(__i386__) || defined(__x86_64__) || defined(__m68k__) return reinterpret_cast(addr)(); #endif } } // namespace 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()}, dependencyQueue{getAllocator()}, _nameMap{frg::hash{}, getAllocator()}, _destructQueue{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); _parseVerdef(object); object->wasVisited = true; dependencyQueue.push_back(object); _addLoadedObject(object); 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); _parseVerdef(object); object->wasVisited = true; dependencyQueue.push_back(object); _addLoadedObject(object); 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->finiArray = reinterpret_cast(__fini_array_start); object->finiArraySize = static_cast((uintptr_t)__fini_array_end - (uintptr_t)__fini_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() << "rtld: trying in rpath " << sPath << frg::endlog; int fd = tryToOpen(sPath.data()); if (logRpath && fd >= 0) mlibc::infoLogger() << "rtld: 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() << "rtld: 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() << "rtld: 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); _parseVerdef(object); _addLoadedObject(object); 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); _parseVerdef(object); _addLoadedObject(object); return object; } void ObjectRepository::discoverDependenciesFromLoadedObject(SharedObject *object) { _discoverDependencies(object, object->localScope, object->objectRts); _parseVerneed(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; } void ObjectRepository::addObjectToDestructQueue(SharedObject *object) { _destructQueue.push(object); } void doDestruct(SharedObject *object); void ObjectRepository::destructObjects() { while (_destructQueue.size() > 0) { auto top = _destructQueue.top(); doDestruct(top); _destructQueue.pop(); } } // -------------------------------------------------------- // 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() << "rtld: 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() << "rtld: 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() << "rtld: 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); if(!phdr->p_memsz) continue; __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); auto initial_prot = PROT_READ | PROT_WRITE; 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 // we can avoid the vm_protect call if we don't have to write to the segment if(phdr->p_memsz == phdr->p_filesz) initial_prot = prot; void *map_pointer; if(mlibc::sys_vm_map(reinterpret_cast(map_address), backed_map_size, initial_prot, 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, initial_prot, 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, initial_prot, 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 if(initial_prot != prot) { if (!mlibc::sys_vm_protect) __ensure(!"sys_vm_protect not provided"); if (mlibc::sys_vm_protect(reinterpret_cast(map_address), total_map_size, prot)) __ensure(!"sys_vm_protect failed"); } }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[31mrtld: DF_TEXTREL is unimplemented" << frg::endlog; #endif if(dynamic->d_un.d_val & ~ignored) mlibc::infoLogger() << "\e[31mrtld: 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[31mrtld: 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_FINI: if(dynamic->d_un.d_ptr != 0) object->finiPtr = (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_FINI_ARRAY: if(dynamic->d_un.d_ptr != 0) object->finiArray = (InitFuncPtr *)(object->baseAddress + dynamic->d_un.d_ptr); break; case DT_INIT_ARRAYSZ: object->initArraySize = dynamic->d_un.d_val; break; case DT_FINI_ARRAYSZ: object->finiArraySize = 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; // handle version information case DT_VERSYM: object->versionTableOffset = dynamic->d_un.d_ptr; break; case DT_VERDEF: object->versionDefinitionTableOffset = dynamic->d_un.d_ptr; break; case DT_VERDEFNUM: object->versionDefinitionCount = dynamic->d_un.d_val; break; case DT_VERNEED: object->versionRequirementTableOffset = dynamic->d_un.d_ptr; break; case DT_VERNEEDNUM: object->versionRequirementCount = dynamic->d_un.d_val; break; // ignore unimportant tags case DT_NEEDED: // we handle this later 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: #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) && (dynamic->d_tag < DT_LOPROC || dynamic->d_tag > DT_HIPROC)) { 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::_parseVerdef(SharedObject *object) { if(!object->versionDefinitionTableOffset) { if(verbose) mlibc::infoLogger() << "mlibc: Object " << object->name << " defines no versions" << frg::endlog; return; } if(verbose) mlibc::infoLogger() << "mlibc: Object " << object->name << " defines " << object->versionDefinitionCount << " version(s)" << frg::endlog; uintptr_t address = object->baseAddress + object->versionDefinitionTableOffset; for(size_t i = 0; i < object->versionDefinitionCount; i++) { elf_verdef def; memcpy(&def, reinterpret_cast(address), sizeof(elf_verdef)); // Required by spec. __ensure(def.vd_version == 1); __ensure(def.vd_cnt >= 1); __ensure(!(def.vd_flags & ~(VER_FLG_BASE | VER_FLG_WEAK))); // NOTE(qookie): glibc also ignores any additional Verdaux entries after the // first one. elf_verdaux aux; memcpy(&aux, reinterpret_cast(address + def.vd_aux), sizeof(elf_verdaux)); const char *name = reinterpret_cast( object->baseAddress + object->stringTableOffset + aux.vda_name); if(verbose) mlibc::infoLogger() << "mlibc: Object " << object->name << " defines version " << name << " (index " << def.vd_ndx << ")" << frg::endlog; if(!(def.vd_flags & VER_FLG_BASE)) { SymbolVersion ver{name, def.vd_hash}; object->definedVersions.push(ver); object->knownVersions.insert(def.vd_ndx, ver); } address += def.vd_next; } } void ObjectRepository::_parseVerneed(SharedObject *object) { if(!object->versionRequirementTableOffset) { if(verbose) mlibc::infoLogger() << "mlibc: Object " << object->name << " requires no versions" << frg::endlog; return; } if(verbose) mlibc::infoLogger() << "mlibc: Object " << object->name << " requires " << object->versionRequirementCount << " version(s)" << frg::endlog; uintptr_t address = object->baseAddress + object->versionRequirementTableOffset; for(size_t i = 0; i < object->versionRequirementCount; i++) { elf_verneed need; memcpy(&need, reinterpret_cast(address), sizeof(elf_verneed)); // Required by spec. __ensure(need.vn_version == 1); frg::string_view file = reinterpret_cast( object->baseAddress + object->stringTableOffset + need.vn_file); // Figure out the target object from file SharedObject *target = nullptr; for(auto dep : object->dependencies) { if(verbose) mlibc::infoLogger() << "mlibc: Trying " << dep->name << " (SONAME: " << dep->soName << ") to satisfy " << file << frg::endlog; if(dep->name == file || (dep->soName && dep->soName == file)) { target = dep; break; } } if(!target) mlibc::panicLogger() << "mlibc: No object named \"" << file << "\" found for VERNEED entry of object " << object->name << frg::endlog; if(verbose) mlibc::infoLogger() << "mlibc: Object " << object->name << " requires " << need.vn_cnt << " version(s) from DSO " << file << frg::endlog; uintptr_t auxAddr = address + need.vn_aux; for(size_t j = 0; j < need.vn_cnt; j++) { elf_vernaux aux; memcpy(&aux, reinterpret_cast(auxAddr), sizeof(elf_vernaux)); // TODO(qookie): Handle weak versions. __ensure(!aux.vna_flags); const char *name = reinterpret_cast( object->baseAddress + object->stringTableOffset + aux.vna_name); if(verbose) mlibc::infoLogger() << "mlibc: Object " << object->name << " requires version " << name << " (index " << aux.vna_other << ") from DSO " << file << frg::endlog; frg::optional ver; for(auto &def : target->definedVersions) { if(def.hash() != aux.vna_hash) continue; if(def.name() == name) { ver = def; break; } } if(!ver) mlibc::panicLogger() << "mlibc: Object " << target->name << " does not define version \"" << name << "\" needed by object " << object->name << frg::endlog; bool isDefault = !(aux.vna_other & 0x8000); // Bit 15 indicates whether the static linker should ignore this version. object->knownVersions.insert(aux.vna_other & 0x7FFF, isDefault ? ver->makeDefault() : *ver); auxAddr += aux.vna_next; } address += need.vn_next; } } 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() << "rtld: Could not load preload " << preload << frg::endlog; if(verbose) mlibc::infoLogger() << "rtld: Preloading " << preload << frg::endlog; auto library = libraryResult.value(); object->dependencies.push_back(library); if (library->wasVisited) continue; library->wasVisited = true; dependencyQueue.push_back(library); } } // 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 libraryResult = requestObjectWithName(frg::string_view{library_str}, object, localScope, false, rts); if(!libraryResult) mlibc::panicLogger() << "Could not satisfy dependency " << library_str << frg::endlog; auto library = libraryResult.value(); object->dependencies.push(library); if (library->wasVisited) continue; library->wasVisited = true; dependencyQueue.push_back(library); } } 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), knownVersions({}, getAllocator()), definedVersions(getAllocator()), 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) {} frg::tuple SharedObject::getSymbolByIndex(size_t index) { SymbolVersion ver{1}; // If we don't have any version information, treat all symbols as global. ObjectSymbol sym{ this, reinterpret_cast( baseAddress + symbolTableOffset + index * sizeof(elf_sym))}; if(versionTableOffset) { // Pull out the VERSYM entry for this symbol elf_version verIdx; memcpy( &verIdx, reinterpret_cast( baseAddress + versionTableOffset + index * sizeof(elf_version)), sizeof(elf_version) ); // Bit 15 indicates that this version is not the default one. bool isDefault = !(verIdx & 0x8000); verIdx &= 0x7FFF; // 0 and 1 are special, 0 is local, 1 is global (not in VERDEF/VERNEED) if(verIdx != 0 && verIdx != 1) { auto maybeVer = knownVersions.find(verIdx); if(maybeVer == knownVersions.end()) mlibc::panicLogger() << "mlibc: Symbol " << sym.getString() << " of object " << name << " has invalid version index " << verIdx << frg::endlog; ver = maybeVer->get<1>(); } else { ver = SymbolVersion{verIdx}; } if(isDefault) ver = ver.makeDefault(); if(logSymbolVersions) mlibc::infoLogger() << "mlibc: Symbol " << sym.getString() << " of object " << name << " has version " << ver.name() << " and " << (ver.isDefault() ? "is" : "isn't") << " the default version" << frg::endlog; } else { // If we have no version information, the only symbol we've got is the default. ver = ver.makeDefault(); } return {sym, ver}; } void processLateRelocation(Relocation rel) { // resolve the symbol if there is a symbol frg::optional p; if(rel.symbol_index()) { auto [sym, ver] = rel.object()->getSymbolByIndex(rel.symbol_index()); p = Scope::resolveGlobalOrLocal(*globalScope, rel.object()->localScope, sym.getString(), rel.object()->objectRts, Scope::resolveCopy, ver); } switch(rel.type()) { case R_COPY: __ensure(p); memcpy(rel.destination(), (void *)p->virtualAddress(), p->symbol()->st_size); break; case R_IRELATIVE: rel.relocate(handleIfunc(rel.object()->baseAddress + rel.addend_rel())); break; 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(verbose) mlibc::infoLogger() << "rtld: Initialize " << object->name << frg::endlog; if(verbose) mlibc::infoLogger() << "rtld: Running DT_INIT function" << frg::endlog; if(object->initPtr != nullptr) object->initPtr(); if(verbose) mlibc::infoLogger() << "rtld: 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() << "rtld: Object initialization complete" << frg::endlog; object->wasInitialized = true; } void doDestruct(SharedObject *object) { if(!object->wasInitialized || object->wasDestroyed) return; if(verbose) mlibc::infoLogger() << "rtld: Destruct " << object->name << frg::endlog; if(verbose) mlibc::infoLogger() << "rtld: Running DT_FINI_ARRAY functions" << frg::endlog; __ensure((object->finiArraySize % sizeof(InitFuncPtr)) == 0); for(size_t i = object->finiArraySize / sizeof(InitFuncPtr); i > 0; i--) object->finiArray[i - 1](); if(verbose) mlibc::infoLogger() << "rtld: Running DT_FINI function" << frg::endlog; if(object->finiPtr != nullptr) object->finiPtr(); if(verbose) mlibc::infoLogger() << "rtld: Object destruction complete" << frg::endlog; object->wasDestroyed = 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; if constexpr (tlsAboveTp) { tls_ptr += sizeof(Tcb); } memset(tls_ptr, 0, object->tlsSegmentSize); memcpy(tls_ptr, object->tlsImagePtr, object->tlsImageSize); if (verbose) { mlibc::infoLogger() << "rtld: 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() << "rtld: tcb allocated at " << (void *)tcbAddress << ", size = 0x" << frg::hex_fmt{sizeof(Tcb)} << frg::endlog; mlibc::infoLogger() << "rtld: 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; if constexpr (tlsAboveTp) { tcb_ptr->dtvPointers[i] = reinterpret_cast(tcb_ptr) + sizeof(Tcb) + object->tlsOffset; } else { 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() << "rtld: 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); if (ELF_ST_TYPE(_symbol->st_info) == STT_GNU_IFUNC) return handleIfunc(_object->baseAddress + _symbol->st_value); return _object->baseAddress + _symbol->st_value; } size_t ObjectSymbol::size() { return _symbol->st_size; } bool ObjectSymbol::contains(uintptr_t addr) { if(!size() && virtualAddress() == addr) return true; if(size() && addr >= virtualAddress() && addr < (virtualAddress() + size())) return true; return false; } // -------------------------------------------------------- // 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, frg::optional version) { // 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; }; // Checks if the symbol's version matches the desired version. auto correctVersion = [&] (SymbolVersion candVersion) { // TODO(qookie): Not sure if local symbols should participate in dynamic symbol resolution if(!version && (candVersion.isDefault() || candVersion.isLocal() || candVersion.isGlobal())) return true; // Caller requested default version, but this isn't it. if(!version) return false; // If the requested version is global (caller has VERNEED but not for this symbol), // use the default one. if(version->isGlobal() && !candVersion.isGlobal() && !candVersion.isLocal() && candVersion.isDefault()) return true; return *version == candVersion; }; 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) { auto [cand, ver] = object->getSymbolByIndex(index); if(eligible(cand) && frg::string_view{cand.getString()} == string && correctVersion(ver)) return cand; index = hash_table[2 + num_buckets + index]; } return frg::optional{}; }else{ __ensure(object->hashStyle == HashStyle::gnu); auto hash_table = reinterpret_cast(object->baseAddress + object->hashTableOffset); auto buckets = reinterpret_cast(object->baseAddress + object->hashTableOffset + sizeof(GnuHashTableHeader) + hash_table->bloomSize * sizeof(elf_addr)); auto chains = reinterpret_cast(object->baseAddress + object->hashTableOffset + sizeof(GnuHashTableHeader) + 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)) { auto [cand, ver] = object->getSymbolByIndex(index); if(eligible(cand) && frg::string_view{cand.getString()} == string && correctVersion(ver)) 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, frg::optional version) { // 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() << "rtld: 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, version); 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, frg::optional version) { auto sym = globalScope.resolveSymbol(string, skipRts, flags | skipGlobalAfterRts, version); if(!sym && localScope) sym = localScope->resolveSymbol(string, skipRts, flags | skipGlobalAfterRts, version); return sym; } frg::optional Scope::resolveGlobalOrLocalNext(Scope &globalScope, Scope *localScope, frg::string_view string, SharedObject *origin, frg::optional version) { auto sym = globalScope._resolveNext(string, origin, version); if(!sym && localScope) { sym = localScope->_resolveNext(string, origin, version); } return sym; } // TODO: let this return uintptr_t frg::optional Scope::resolveSymbol(frg::string_view string, uint64_t skipRts, ResolveFlags flags, frg::optional version) { 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, version); 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() << "rtld: Linking " << object->name << frg::endlog; __ensure(!object->wasLinked); // TODO: Support this. if(object->symbolicResolution) mlibc::infoLogger() << "\e[31mrtld: 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) { size_t misalign = runtimeTlsMap->initialPtr & (object->tlsAlignment - 1); if(misalign) runtimeTlsMap->initialPtr += object->tlsAlignment - misalign; object->tlsOffset = runtimeTlsMap->initialPtr; runtimeTlsMap->initialPtr += object->tlsSegmentSize; } 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() << "rtld: 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) { object->tlsModel = TlsModel::initial; if constexpr (tlsAboveTp) { size_t misalign = runtimeTlsMap->initialPtr & (object->tlsAlignment - 1); if(misalign) runtimeTlsMap->initialPtr += object->tlsAlignment - misalign; object->tlsOffset = runtimeTlsMap->initialPtr; runtimeTlsMap->initialPtr += object->tlsSegmentSize; } 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(runtimeTlsMap->initialPtr > runtimeTlsMap->initialLimit) mlibc::panicLogger() << "rtld: Static TLS space exhausted while while" " allocating TLS for " << object->name << frg::endlog; if(verbose) mlibc::infoLogger() << "rtld: 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(ObjectRepository *repository) { initTlsObjects(mlibc::get_current_tcb(), _linkBfs, true); if (_mainExecutable && _mainExecutable->preInitArray) { if (verbose) mlibc::infoLogger() << "rtld: 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); repository->addObjectToDestructQueue(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 [sym, ver] = rel.object()->getSymbolByIndex(rel.symbol_index()); p = Scope::resolveGlobalOrLocal(*globalScope, rel.object()->localScope, sym.getString(), rel.object()->objectRts, 0, ver); if(!p) { if(ELF_ST_BIND(sym.symbol()->st_info) != STB_WEAK) mlibc::panicLogger() << "Unresolved load-time symbol " << sym.getString() << " in object " << rel.object()->name << frg::endlog; if(verbose) mlibc::infoLogger() << "rtld: Unresolved weak load-time symbol " << sym.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) && !defined(__loongarch64) // 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() << "rtld: 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(); ssize_t tls_offset = 0; if(rel.symbol_index()) { __ensure(p); if(p->object()->tlsModel != TlsModel::initial) mlibc::panicLogger() << "rtld: 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() << "rtld: Warning: TPOFF64 with no symbol" " in object " << rel.object()->name << frg::endlog; if(rel.object()->tlsModel != TlsModel::initial) mlibc::panicLogger() << "rtld: In object " << rel.object()->name << ": Static TLS relocation to dynamically loaded object " << rel.object()->name << frg::endlog; tls_offset = rel.object()->tlsOffset; } off += tls_offset + tlsOffsetFromTp; 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. // The first bit of an entry is always a marker about whether the entry is an address or a bitmap, // discard it. entry >>= 1; 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/x86_64 specific #if defined(__aarch64__) || defined(__x86_64__) 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 [sym, ver] = object->getSymbolByIndex(symbol_index); auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, sym.getString(), object->objectRts, 0, ver); if(!p) { if(ELF_ST_BIND(sym.symbol()->st_info) != STB_WEAK) mlibc::panicLogger() << "rtld: Unresolved JUMP_SLOT symbol " << sym.getString() << " in object " << object->name << frg::endlog; if(verbose) mlibc::infoLogger() << "rtld: Unresolved weak JUMP_SLOT symbol " << sym.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/x86_64 specific #if defined(__aarch64__) || defined(__x86_64__) case R_TLSDESC: { size_t symValue = 0; SharedObject *target = nullptr; if (symbol_index) { auto [sym, ver] = object->getSymbolByIndex(symbol_index); auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, sym.getString(), object->objectRts, 0, ver); if (!p) { __ensure(ELF_ST_BIND(sym.symbol()->st_info) != STB_WEAK); mlibc::panicLogger() << "rtld: Unresolved TLSDESC for symbol " << sym.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); uint64_t value = symValue + target->tlsOffset + tlsOffsetFromTp + addend; ((uint64_t *)rel_addr)[1] = value; } 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; } } }