diff --git a/options/rtdl/generic/linker.cpp b/options/rtdl/generic/linker.cpp index d6e91180a5..62408f4b78 100644 --- a/options/rtdl/generic/linker.cpp +++ b/options/rtdl/generic/linker.cpp @@ -8,6 +8,7 @@ enum { #include +#include #include #include #include @@ -27,6 +28,7 @@ constexpr bool verbose = false; constexpr bool stillSlightlyVerbose = false; constexpr bool logBaseAddresses = false; constexpr bool logRpath = false; +constexpr bool logLdPath = false; constexpr bool eagerBinding = true; #if defined(__x86_64__) || defined(__i386__) @@ -42,6 +44,8 @@ constexpr inline bool tlsAboveTp = true; extern DebugInterface globalDebugInterface; extern uintptr_t __stack_chk_guard; +extern frg::manual_box> libraryPaths; + #if MLIBC_STATIC_BUILD extern "C" size_t __init_array_start[]; extern "C" size_t __init_array_end[]; @@ -157,13 +161,6 @@ SharedObject *ObjectRepository::requestObjectWithName(frg::string_view name, if (auto obj = findLoadedObject(name)) return obj; - const char *libdirs[4] = { - "/lib/", - "/lib64/", - "/usr/lib/", - "/usr/lib64/" - }; - auto tryToOpen = [&] (const char *path) { int fd; if(auto x = mlibc::sys_open(path, O_RDONLY, 0, &fd); x) { @@ -233,8 +230,12 @@ SharedObject *ObjectRepository::requestObjectWithName(frg::string_view name, } else if (logRpath) { mlibc::infoLogger() << "rtdl: no rpath set for object" << frg::endlog; } - for(int i = 0; i < 4 && fd == -1; i++) { - auto path = frg::string{getAllocator(), libdirs[i]} + name + '\0'; + + for(size_t i = 0; i < libraryPaths->size() && fd == -1; i++) { + auto ldPath = (*libraryPaths)[i]; + auto path = frg::string{getAllocator(), ldPath} + '/' + name; + if(logLdPath) + mlibc::infoLogger() << "rtdl: Trying to load " << name << " from ldpath " << ldPath << frg::endlog; fd = tryToOpen(path.data()); if(fd >= 0) { chosenPath = std::move(path); diff --git a/options/rtdl/generic/main.cpp b/options/rtdl/generic/main.cpp index 463a3e329b..eb4a5923b7 100644 --- a/options/rtdl/generic/main.cpp +++ b/options/rtdl/generic/main.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -47,6 +48,9 @@ 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; + static SharedObject *executableSO; extern HIDDEN char __ehdr_start[]; @@ -199,11 +203,27 @@ extern "C" [[gnu::alias("dl_debug_state"), gnu::visibility("default")]] void _dl // This symbol can be used by GDB to find the global interface structure [[ gnu::visibility("default") ]] DebugInterface *_dl_debug_addr = &globalDebugInterface; +static void parseLibraryPaths(frg::string_view paths) { + 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(':', p); cs != size_t(-1)) { + s = cs; + }else{ + s = paths.size(); + } + + libraryPaths->push_back(paths.sub_string(p, s - p)); + p = s + 1; + } +} + 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()); void *phdr_pointer = 0; size_t phdr_entry_size = 0; @@ -282,16 +302,25 @@ extern "C" void *interpreterMain(uintptr_t *entry_stack) { 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(view.sub_string(0, s) == "LD_SHOW_AUXV" && value && *value && *value != '0') { + if(name == "LD_SHOW_AUXV" && *value && *value != '0') { ldShowAuxv = true; + }else if(name == "LD_LIBRARY_PATH" && *value) { + parseLibraryPaths(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; diff --git a/tests/rtdl/ld_library_path/libfoo.c b/tests/rtdl/ld_library_path/libfoo.c new file mode 100644 index 0000000000..85e6cd8c39 --- /dev/null +++ b/tests/rtdl/ld_library_path/libfoo.c @@ -0,0 +1 @@ +void foo() {} diff --git a/tests/rtdl/ld_library_path/meson.build b/tests/rtdl/ld_library_path/meson.build new file mode 100644 index 0000000000..76a6631082 --- /dev/null +++ b/tests/rtdl/ld_library_path/meson.build @@ -0,0 +1,19 @@ +# Remove the RPATH and set the LD_LIBRARY_PATH environment variable +# instead when running tests to make sure the library can be found +# in a directory specified by the user at runtime + +test_rpath = '$ORIGIN/' + +test_ld_path = meson.build_root() / 'tests' / 'rtdl' / test_name +test_env += ['LD_LIBRARY_PATH=' + test_ld_path] +test_native_env += ['LD_LIBRARY_PATH=' + test_ld_path] + +libfoo = shared_library('foo', 'libfoo.c', + link_with: libc, include_directories: libc_include_dirs, + link_args: test_additional_link_args, +) + +libfoo_native = shared_library('native-foo', 'libfoo.c', + link_args: ['-ldl'] + test_additional_link_args, + native: true +) diff --git a/tests/rtdl/ld_library_path/test.c b/tests/rtdl/ld_library_path/test.c new file mode 100644 index 0000000000..15ca2001ee --- /dev/null +++ b/tests/rtdl/ld_library_path/test.c @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#else +#define LIBFOO "libfoo.so" +#endif + +int main() { + void *foo = dlopen(LIBFOO, RTLD_NOW); + assert(foo); + dlclose(foo); +} diff --git a/tests/rtdl/meson.build b/tests/rtdl/meson.build index c7b667666f..53cddb74e0 100644 --- a/tests/rtdl/meson.build +++ b/tests/rtdl/meson.build @@ -1,6 +1,7 @@ rtdl_test_cases = [ 'dl_iterate_phdr', 'dladdr_local', + 'ld_library_path', 'noload-promote', 'rtld_next', 'soname', @@ -17,8 +18,10 @@ foreach test_name : rtdl_test_cases test_rpath = meson.build_root() / 'tests' / 'rtdl' / test_name / '' test_rpath += ':$ORIGIN/' # Workaround old and buggy qemu-user on CI + test_env = [] test_link_with = [] test_depends = [] + test_native_env = [] test_native_link_with = [] test_native_depends = [] test_additional_link_args = [] @@ -34,7 +37,7 @@ foreach test_name : rtdl_test_cases c_args: test_c_args, link_args: test_link_args + test_additional_link_args, ) - test(test_name, exec, suite: 'rtdl', depends: test_depends) + test(test_name, exec, env: test_env, suite: 'rtdl', depends: test_depends) if build_tests_host_libc and not host_libc_excluded_test_cases.contains(test_name) exec = executable('host-libc-' + test_name, test_name / 'test.c', @@ -48,6 +51,6 @@ foreach test_name : rtdl_test_cases link_args: ['-ldl'] + test_additional_link_args, native: true, ) - test(test_name, exec, suite: ['host-libc', 'rtdl'], depends: test_native_depends) + test(test_name, exec, env: test_native_env, suite: ['host-libc', 'rtdl'], depends: test_native_depends) endif endforeach