#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