diff --git a/Libraries/LibELF/DynamicLinker.cpp b/Libraries/LibELF/DynamicLinker.cpp new file mode 100644 index 0000000000..2f47a42cdf --- /dev/null +++ b/Libraries/LibELF/DynamicLinker.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2020, Itamar S. + * Copyright (c) 2021, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #define DYNAMIC_LOAD_VERBOSE + +#ifdef DYNAMIC_LOAD_VERBOSE +# define VERBOSE(fmt, ...) dbgprintf(fmt, ##__VA_ARGS__) +#else +# define VERBOSE(fmt, ...) \ + do { \ + } while (0) +#endif +#define TLS_VERBOSE(fmt, ...) dbgprintf(fmt, ##__VA_ARGS__) + +namespace ELF { + +namespace { +HashMap> g_loaders; +HashMap> g_loaded_objects; +Vector> g_global_objects; + +using MainFunction = int (*)(int, char**, char**); +using LibCExitFunction = void (*)(int); + +size_t g_current_tls_offset = 0; +size_t g_total_tls_size = 0; +char** g_envp = nullptr; +LibCExitFunction g_libc_exit = nullptr; +} + +DynamicObject::SymbolLookupResult DynamicLinker::lookup_global_symbol(const char* symbol_name) +{ + DynamicObject::SymbolLookupResult weak_result = {}; + for (auto& lib : g_global_objects) { + auto res = lib->lookup_symbol(symbol_name); + if (res.found) { + if (res.bind == STB_GLOBAL) { + return res; + } else if (res.bind == STB_WEAK && !weak_result.found) { + weak_result = res; + } + // We don't want to allow local symbols to be pulled in to other modules + } + } + return weak_result; +} + +static void map_library(const String& name, int fd) +{ + struct stat lib_stat; + int rc = fstat(fd, &lib_stat); + ASSERT(!rc); + + auto loader = ELF::DynamicLoader::construct(name.characters(), fd, lib_stat.st_size); + loader->set_tls_offset(g_current_tls_offset); + + g_loaders.set(name, loader); + + g_current_tls_offset += loader->tls_size(); +} + +static void map_library(const String& name) +{ + // TODO: Do we want to also look for libs in other paths too? + String path = String::format("/usr/lib/%s", name.characters()); + int fd = open(path.characters(), O_RDONLY); + ASSERT(fd >= 0); + map_library(name, fd); +} + +static String get_library_name(const StringView& path) +{ + return LexicalPath(path).basename(); +} + +static Vector get_dependencies(const String& name) +{ + auto lib = g_loaders.get(name).value(); + Vector dependencies; + + lib->for_each_needed_library([&dependencies, &name](auto needed_name) { + if (name == needed_name) + return IterationDecision::Continue; + dependencies.append(needed_name); + return IterationDecision::Continue; + }); + return dependencies; +} + +static void map_dependencies(const String& name) +{ + VERBOSE("mapping dependencies for: %s\n", name.characters()); + + for (const auto& needed_name : get_dependencies(name)) { + VERBOSE("needed library: %s\n", needed_name.characters()); + String library_name = get_library_name(needed_name); + + if (!g_loaders.contains(library_name)) { + map_library(library_name); + map_dependencies(library_name); + } + } + VERBOSE("mapped dependencies for %s\n", name.characters()); +} + +static void allocate_tls() +{ + size_t total_tls_size = 0; + for (const auto& data : g_loaders) { + VERBOSE("%s: TLS Size: %zu\n", data.key.characters(), data.value->tls_size()); + total_tls_size += data.value->tls_size(); + } + if (total_tls_size) { + [[maybe_unused]] void* tls_address = ::allocate_tls(total_tls_size); + VERBOSE("from userspace, tls_address: %p\n", tls_address); + } + g_total_tls_size = total_tls_size; +} + +static void initialize_libc(DynamicObject& libc) +{ + // Traditionally, `_start` of the main program initializes libc. + // However, since some libs use malloc() and getenv() in global constructors, + // we have to initialize libc just after it is loaded. + // Also, we can't just mark `__libc_init` with "__attribute__((constructor))" + // because it uses getenv() internally, so `environ` has to be initialized before we call `__libc_init`. + auto res = libc.lookup_symbol("environ"); + ASSERT(res.found); + *((char***)res.address) = g_envp; + + res = libc.lookup_symbol("__environ_is_malloced"); + ASSERT(res.found); + *((bool*)res.address) = false; + + res = libc.lookup_symbol("exit"); + ASSERT(res.found); + g_libc_exit = (LibCExitFunction)res.address; + + res = libc.lookup_symbol("__libc_init"); + ASSERT(res.found); + typedef void libc_init_func(); + ((libc_init_func*)res.address)(); +} + +static void load_elf(const String& name) +{ + VERBOSE("load_elf: %s\n", name.characters()); + auto loader = g_loaders.get(name).value(); + VERBOSE("a1\n"); + for (const auto& needed_name : get_dependencies(name)) { + VERBOSE("needed library: %s\n", needed_name.characters()); + String library_name = get_library_name(needed_name); + if (!g_loaded_objects.contains(library_name)) { + load_elf(library_name); + } + } + + auto dynamic_object = loader->load_from_image(RTLD_GLOBAL | RTLD_LAZY, g_total_tls_size); + ASSERT(dynamic_object); + + g_loaded_objects.set(name, *dynamic_object); + g_global_objects.append(*dynamic_object); + + VERBOSE("load_elf: done %s\n", name.characters()); + if (name == "libc.so") { + initialize_libc(*dynamic_object); + } +} + +void ELF::DynamicLinker::linker_main(String&& main_program_name, int main_program_fd, int argc, char** argv, char** envp) +{ + g_envp = envp; + map_library(main_program_name, main_program_fd); + map_dependencies(main_program_name); + + VERBOSE("loaded all dependencies"); + for ([[maybe_unused]] auto& lib : g_loaders) { + VERBOSE("%s - tls size: %zu, tls offset: %zu\n", lib.key.characters(), lib.value->tls_size(), lib.value->tls_offset()); + } + + allocate_tls(); + + load_elf(main_program_name); + auto main_program_lib = g_loaders.get(main_program_name).value(); + + FlatPtr entry_point = reinterpret_cast(main_program_lib->image().entry().as_ptr()); + if (main_program_lib->is_dynamic()) + entry_point += reinterpret_cast(main_program_lib->text_segment_load_address().as_ptr()); + + VERBOSE("entry point: %p\n", (void*)entry_point); + g_loaders.clear(); + + MainFunction main_function = (MainFunction)(entry_point); + VERBOSE("jumping to main program entry point: %p\n", main_function); + int rc = main_function(argc, argv, envp); + VERBOSE("rc: %d\n", rc); + if (g_libc_exit != nullptr) { + g_libc_exit(rc); + } else { + _exit(rc); + } + + ASSERT_NOT_REACHED(); +} +} \ No newline at end of file diff --git a/Libraries/LibELF/DynamicLinker.h b/Libraries/LibELF/DynamicLinker.h new file mode 100644 index 0000000000..b6aa18312d --- /dev/null +++ b/Libraries/LibELF/DynamicLinker.h @@ -0,0 +1,45 @@ +/* + * Copyright 2021 (c), the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include + +namespace ELF { + +class DynamicLinker { +public: + static DynamicObject::SymbolLookupResult lookup_global_symbol(const char* symbol); + [[noreturn]] static void linker_main(String&& main_program_name, int fd, int argc, char** argv, char** envp); + +private: + DynamicLinker() = delete; + ~DynamicLinker() = delete; +}; + +} \ No newline at end of file diff --git a/Libraries/LibELF/DynamicLoader.cpp b/Libraries/LibELF/DynamicLoader.cpp index 8938f9f263..d222bc2198 100644 --- a/Libraries/LibELF/DynamicLoader.cpp +++ b/Libraries/LibELF/DynamicLoader.cpp @@ -162,8 +162,6 @@ RefPtr DynamicLoader::load_from_image(unsigned flags, size_t tota m_dynamic_object = DynamicObject::construct(m_text_segment_load_address, m_dynamic_section_address); m_dynamic_object->set_tls_offset(m_tls_offset); m_dynamic_object->set_tls_size(m_tls_size); - ASSERT(m_global_symbol_lookup_func); - m_dynamic_object->m_global_symbol_lookup_func = m_global_symbol_lookup_func; auto rc = load_stage_2(flags, total_tls_size); if (!rc) { diff --git a/Libraries/LibELF/DynamicLoader.h b/Libraries/LibELF/DynamicLoader.h index 3ced5ae4e5..7381c5d3fd 100644 --- a/Libraries/LibELF/DynamicLoader.h +++ b/Libraries/LibELF/DynamicLoader.h @@ -75,9 +75,6 @@ public: template void for_each_needed_library(F) const; - DynamicObject::SymbolLookupFunction m_global_symbol_lookup_func { nullptr }; - void set_global_symbol_lookup_function(DynamicObject::SymbolLookupFunction func) { m_global_symbol_lookup_func = func; } - VirtualAddress text_segment_load_address() const { return m_text_segment_load_address; } bool is_dynamic() const { return m_elf_image.is_dynamic(); } diff --git a/Libraries/LibELF/DynamicObject.cpp b/Libraries/LibELF/DynamicObject.cpp index 4f446203d7..10dcb2eb1d 100644 --- a/Libraries/LibELF/DynamicObject.cpp +++ b/Libraries/LibELF/DynamicObject.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -406,12 +407,12 @@ static const char* name_for_dtag(Elf32_Sword d_tag) } } -Optional DynamicObject::lookup_symbol(const char* name) const +DynamicObject::SymbolLookupResult DynamicObject::lookup_symbol(const char* name) const { auto res = hash_section().lookup_symbol(name); if (res.is_undefined()) return {}; - return SymbolLookupResult { true, res.value(), (FlatPtr)res.address().as_ptr(), this }; + return SymbolLookupResult { true, res.value(), (FlatPtr)res.address().as_ptr(), res.bind(), this }; } NonnullRefPtr DynamicObject::construct(VirtualAddress base_address, VirtualAddress dynamic_section_address) @@ -450,10 +451,9 @@ DynamicObject::SymbolLookupResult DynamicObject::lookup_symbol(const ELF::Dynami VERBOSE("looking up symbol: %s\n", symbol.name()); if (!symbol.is_undefined()) { VERBOSE("symbol is defined in its object\n"); - return { true, symbol.value(), (FlatPtr)symbol.address().as_ptr(), &symbol.object() }; + return { true, symbol.value(), (FlatPtr)symbol.address().as_ptr(), symbol.bind(), &symbol.object() }; } - ASSERT(m_global_symbol_lookup_func); - return m_global_symbol_lookup_func(symbol.name()); + return DynamicLinker::lookup_global_symbol(symbol.name()); } } // end namespace ELF diff --git a/Libraries/LibELF/DynamicObject.h b/Libraries/LibELF/DynamicObject.h index b9a8d3e9b9..f9906cc135 100644 --- a/Libraries/LibELF/DynamicObject.h +++ b/Libraries/LibELF/DynamicObject.h @@ -276,9 +276,10 @@ public: bool found { false }; FlatPtr value { 0 }; FlatPtr address { 0 }; + unsigned bind { STB_LOCAL }; const ELF::DynamicObject* dynamic_object { nullptr }; // The object in which the symbol is defined }; - Optional lookup_symbol(const char* name) const; + SymbolLookupResult lookup_symbol(const char* name) const; // Will be called from _fixup_plt_entry, as part of the PLT trampoline Elf32_Addr patch_plt_entry(u32 relocation_offset); diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 7ce92979db..a8e60570f3 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -44,6 +44,10 @@ file(GLOB LIBREGEX_LIBC_SOURCES "../../Libraries/LibRegex/C/Regex.cpp") file(GLOB LIBREGEX_SOURCES CONFIGURE_DEPENDS "../../Libraries/LibRegex/*.cpp") file(GLOB LIBCORE_SOURCES CONFIGURE_DEPENDS "../../Libraries/LibCore/*.cpp") file(GLOB LIBELF_SOURCES CONFIGURE_DEPENDS "../../Libraries/LibELF/*.cpp") +# There's no way we can reliably make this cross platform +list(REMOVE_ITEM LIBELF_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/../../Libraries/LibELF/DynamicLinker.cpp") + + file(GLOB LIBGEMINI_SOURCES CONFIGURE_DEPENDS "../../Libraries/LibGemini/*.cpp") file(GLOB LIBGFX_SOURCES CONFIGURE_DEPENDS "../../Libraries/LibGfx/*.cpp") file(GLOB LIBHTTP_SOURCES CONFIGURE_DEPENDS "../../Libraries/LibHTTP/*.cpp") diff --git a/Userland/DynamicLoader/main.cpp b/Userland/DynamicLoader/main.cpp index 65ef19c4b7..e3a7352a54 100644 --- a/Userland/DynamicLoader/main.cpp +++ b/Userland/DynamicLoader/main.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -57,23 +58,14 @@ char* __static_environ[] = { nullptr }; // We don't get the environment without some libc workarounds.. -static HashMap> g_loaders; -static HashMap> g_loaded_objects; - -using MainFunction = int (*)(int, char**, char**); -using LibCExitFunction = void (*)(int); - -static size_t g_current_tls_offset = 0; -static size_t g_total_tls_size = 0; -static char** g_envp = nullptr; -static LibCExitFunction g_libc_exit = nullptr; - static void init_libc() { environ = __static_environ; __environ_is_malloced = false; __stdio_is_initialized = false; - __malloc_init(); + // Initialise the copy of libc included statically in Loader.so, + // initialisation of the dynamic libc.so is done by the DynamicLinker + __libc_init(); } static void perform_self_relocations(auxv_t* auxvp) @@ -112,144 +104,6 @@ static void perform_self_relocations(auxv_t* auxvp) }); } -static ELF::DynamicObject::SymbolLookupResult global_symbol_lookup(const char* symbol_name) -{ - VERBOSE("global symbol lookup: %s\n", symbol_name); - for (auto& lib : g_loaded_objects) { - VERBOSE("looking up in object: %s\n", lib.key.characters()); - auto res = lib.value->lookup_symbol(symbol_name); - if (!res.has_value()) - continue; - return res.value(); - } - // ASSERT_NOT_REACHED(); - return {}; -} - -static void map_library(const String& name, int fd) -{ - struct stat lib_stat; - int rc = fstat(fd, &lib_stat); - ASSERT(!rc); - - auto loader = ELF::DynamicLoader::construct(name.characters(), fd, lib_stat.st_size); - loader->set_tls_offset(g_current_tls_offset); - loader->set_global_symbol_lookup_function(global_symbol_lookup); - - g_loaders.set(name, loader); - - g_current_tls_offset += loader->tls_size(); -} - -static void map_library(const String& name) -{ - // TODO: Do we want to also look for libs in other paths too? - String path = String::format("/usr/lib/%s", name.characters()); - int fd = open(path.characters(), O_RDONLY); - ASSERT(fd >= 0); - map_library(name, fd); -} - -static String get_library_name(const StringView& path) -{ - return LexicalPath(path).basename(); -} - -static Vector get_dependencies(const String& name) -{ - auto lib = g_loaders.get(name).value(); - Vector dependencies; - - lib->for_each_needed_library([&dependencies, &name](auto needed_name) { - if (name == needed_name) - return IterationDecision::Continue; - dependencies.append(needed_name); - return IterationDecision::Continue; - }); - return dependencies; -} - -static void map_dependencies(const String& name) -{ - VERBOSE("mapping dependencies for: %s\n", name.characters()); - - for (const auto& needed_name : get_dependencies(name)) { - VERBOSE("needed library: %s\n", needed_name.characters()); - String library_name = get_library_name(needed_name); - - if (!g_loaders.contains(library_name)) { - map_library(library_name); - map_dependencies(library_name); - } - } -} - -static void allocate_tls() -{ - size_t total_tls_size = 0; - for (const auto& data : g_loaders) { - VERBOSE("%s: TLS Size: %zu\n", data.key.characters(), data.value->tls_size()); - total_tls_size += data.value->tls_size(); - } - if (total_tls_size) { - [[maybe_unused]] void* tls_address = allocate_tls(total_tls_size); - VERBOSE("from userspace, tls_address: %p\n", tls_address); - } - g_total_tls_size = total_tls_size; -} - -static void initialize_libc() -{ - // Traditionally, `_start` of the main program initializes libc. - // However, since some libs use malloc() and getenv() in global constructors, - // we have to initialize libc just after it is loaded. - // Also, we can't just mark `__libc_init` with "__attribute__((constructor))" - // because it uses getenv() internally, so `environ` has to be initialized before we call `__libc_init`. - auto res = global_symbol_lookup("environ"); - *((char***)res.address) = g_envp; - ASSERT(res.found); - res = global_symbol_lookup("__environ_is_malloced"); - ASSERT(res.found); - *((bool*)res.address) = false; - - res = global_symbol_lookup("exit"); - ASSERT(res.found); - g_libc_exit = (LibCExitFunction)res.address; - - res = global_symbol_lookup("__libc_init"); - ASSERT(res.found); - typedef void libc_init_func(); - ((libc_init_func*)res.address)(); -} - -static void load_elf(const String& name) -{ - VERBOSE("load_elf: %s\n", name.characters()); - auto loader = g_loaders.get(name).value(); - VERBOSE("a1\n"); - for (const auto& needed_name : get_dependencies(name)) { - VERBOSE("needed library: %s\n", needed_name.characters()); - String library_name = get_library_name(needed_name); - if (!g_loaded_objects.contains(library_name)) { - load_elf(library_name); - } - } - - auto dynamic_object = loader->load_from_image(RTLD_GLOBAL | RTLD_LAZY, g_total_tls_size); - ASSERT(!dynamic_object.is_null()); - g_loaded_objects.set(name, dynamic_object.release_nonnull()); - - if (name == "libc.so") { - initialize_libc(); - } -} - -static void clear_temporary_objects_mappings() -{ - - g_loaders.clear(); -} - static void display_help() { const char message[] = @@ -261,11 +115,24 @@ This helper program loads the shared libraries needed by the program, prepares the program to run, and runs it. You do not need to invoke this helper program directly. )"; - write(1, message, sizeof(message)); + fprintf(stderr, "%s", message); } -static FlatPtr loader_main(auxv_t* auxvp) +extern "C" { + +// The compiler expects a previous declaration +void _start(int, char**, char**); + +void _start(int argc, char** argv, char** envp) { + char** env; + for (env = envp; *env; ++env) { + } + + auxv_t* auxvp = (auxv_t*)++env; + perform_self_relocations(auxvp); + init_libc(); + int main_program_fd = -1; String main_program_name; for (; auxvp->a_type != AT_NULL; ++auxvp) { @@ -288,62 +155,7 @@ static FlatPtr loader_main(auxv_t* auxvp) _exit(1); } - map_library(main_program_name, main_program_fd); - map_dependencies(main_program_name); - - VERBOSE("loaded all dependencies"); - for ([[maybe_unused]] auto& lib : g_loaders) { - VERBOSE("%s - tls size: %zu, tls offset: %zu\n", lib.key.characters(), lib.value->tls_size(), lib.value->tls_offset()); - } - - allocate_tls(); - - load_elf(main_program_name); - - auto main_program_lib = g_loaders.get(main_program_name).value(); - - FlatPtr entry_point = reinterpret_cast(main_program_lib->image().entry().as_ptr()); - if (main_program_lib->is_dynamic()) - entry_point += reinterpret_cast(main_program_lib->text_segment_load_address().as_ptr()); - - VERBOSE("entry point: %p\n", (void*)entry_point); - - // This will unmap the temporary memory maps we had for loading the libraries - clear_temporary_objects_mappings(); - - return entry_point; -} - -extern "C" { - -// The compiler expects a previous declaration -void _start(int, char**, char**); - -void _start(int argc, char** argv, char** envp) -{ - g_envp = envp; - char** env; - for (env = envp; *env; ++env) { - } - - auxv_t* auxvp = (auxv_t*)++env; - perform_self_relocations(auxvp); - init_libc(); - - FlatPtr entry = loader_main(auxvp); - VERBOSE("Loaded libs:\n"); - for ([[maybe_unused]] auto& obj : g_loaded_objects) { - VERBOSE("%s: %p\n", obj.key.characters(), obj.value->base_address().as_ptr()); - } - - MainFunction main_function = (MainFunction)(entry); - VERBOSE("jumping to main program entry point: %p\n", main_function); - int rc = main_function(argc, argv, envp); - VERBOSE("rc: %d\n", rc); - if (g_libc_exit != nullptr) { - g_libc_exit(rc); - } else { - _exit(rc); - } + ELF::DynamicLinker::linker_main(move(main_program_name), main_program_fd, argc, argv, envp); + ASSERT_NOT_REACHED(); } }