diff options
Diffstat (limited to 'tests/dlfcn_test.cpp')
-rw-r--r-- | tests/dlfcn_test.cpp | 661 |
1 files changed, 603 insertions, 58 deletions
diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp index e24af13..6b1f109 100644 --- a/tests/dlfcn_test.cpp +++ b/tests/dlfcn_test.cpp @@ -26,9 +26,12 @@ #include <string> +#include "utils.h" + #define ASSERT_SUBSTR(needle, haystack) \ ASSERT_PRED_FORMAT2(::testing::IsSubstring, needle, haystack) + static bool g_called = false; extern "C" void DlSymTestFunction() { g_called = true; @@ -46,7 +49,7 @@ TEST(dlfcn, ctor_function_call) { ASSERT_EQ(17, g_ctor_function_called); } -TEST(dlfcn, dlsym_in_self) { +TEST(dlfcn, dlsym_in_executable) { dlerror(); // Clear any pending errors. void* self = dlopen(NULL, RTLD_NOW); ASSERT_TRUE(self != NULL); @@ -64,6 +67,27 @@ TEST(dlfcn, dlsym_in_self) { ASSERT_EQ(0, dlclose(self)); } +TEST(dlfcn, dlsym_from_sofile) { + void* handle = dlopen("libtest_dlsym_from_this.so", RTLD_LAZY | RTLD_LOCAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + // check that we cant find '_test_dlsym_symbol' via dlsym(RTLD_DEFAULT) + void* symbol = dlsym(RTLD_DEFAULT, "test_dlsym_symbol"); + ASSERT_TRUE(symbol == nullptr); + ASSERT_SUBSTR("undefined symbol: test_dlsym_symbol", dlerror()); + + typedef int* (*fn_t)(); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "lookup_dlsym_symbol_using_RTLD_DEFAULT")); + + ASSERT_TRUE(fn != nullptr) << dlerror(); + + int* ptr = fn(); + ASSERT_TRUE(ptr != nullptr) << dlerror(); + ASSERT_EQ(42, *ptr); + + dlclose(handle); +} + TEST(dlfcn, dlsym_with_dependencies) { void* handle = dlopen("libtest_with_dependency.so", RTLD_NOW); ASSERT_TRUE(handle != NULL); @@ -89,8 +113,34 @@ TEST(dlfcn, dlopen_noload) { ASSERT_EQ(0, dlclose(handle2)); } +TEST(dlfcn, dlopen_by_soname) { + static const char* soname = "libdlext_test_soname.so"; + static const char* filename = "libdlext_test_different_soname.so"; + // 1. Make sure there is no library with soname in default search path + void* handle = dlopen(soname, RTLD_NOW); + ASSERT_TRUE(handle == nullptr); + + // 2. Load a library using filename + handle = dlopen(filename, RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + // 3. Find library by soname + void* handle_soname = dlopen(soname, RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle_soname != nullptr) << dlerror(); + ASSERT_EQ(handle, handle_soname); + + // 4. RTLD_NOLOAD should still work with filename + void* handle_filename = dlopen(filename, RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle_filename != nullptr) << dlerror(); + ASSERT_EQ(handle, handle_filename); + + dlclose(handle_filename); + dlclose(handle_soname); + dlclose(handle); +} + // ifuncs are only supported on intel and arm64 for now -#if defined(__i386__) || defined(__x86_64__) +#if defined (__aarch64__) || defined(__i386__) || defined(__x86_64__) TEST(dlfcn, ifunc) { typedef const char* (*fn_ptr)(); @@ -162,39 +212,39 @@ TEST(dlfcn, dlopen_check_relocation_dt_needed_order) { ASSERT_EQ(1, fn()); } -TEST(dlfcn, dlopen_check_order) { +TEST(dlfcn, dlopen_check_order_dlsym) { // Here is how the test library and its dt_needed // libraries are arranged // - // libtest_check_order.so + // libtest_check_order_children.so // | - // +-> libtest_check_order_1_left.so + // +-> ..._1_left.so // | | - // | +-> libtest_check_order_a.so + // | +-> ..._a.so // | | - // | +-> libtest_check_order_b.so + // | +-> ...r_b.so // | - // +-> libtest_check_order_2_right.so + // +-> ..._2_right.so // | | - // | +-> libtest_check_order_d.so + // | +-> ..._d.so // | | - // | +-> libtest_check_order_b.so + // | +-> ..._b.so // | - // +-> libtest_check_order_3_c.so + // +-> ..._3_c.so // // load order should be (1, 2, 3, a, b, d) // // get_answer() is defined in (2, 3, a, b, c) // get_answer2() is defined in (b, d) - void* sym = dlsym(RTLD_DEFAULT, "dlopen_test_get_answer"); + void* sym = dlsym(RTLD_DEFAULT, "check_order_dlsym_get_answer"); ASSERT_TRUE(sym == nullptr); - void* handle = dlopen("libtest_check_order.so", RTLD_NOW | RTLD_GLOBAL); - ASSERT_TRUE(handle != nullptr); + void* handle = dlopen("libtest_check_order_dlsym.so", RTLD_NOW | RTLD_GLOBAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); typedef int (*fn_t) (void); fn_t fn, fn2; - fn = reinterpret_cast<fn_t>(dlsym(RTLD_DEFAULT, "dlopen_test_get_answer")); + fn = reinterpret_cast<fn_t>(dlsym(RTLD_DEFAULT, "check_order_dlsym_get_answer")); ASSERT_TRUE(fn != NULL) << dlerror(); - fn2 = reinterpret_cast<fn_t>(dlsym(RTLD_DEFAULT, "dlopen_test_get_answer2")); + fn2 = reinterpret_cast<fn_t>(dlsym(RTLD_DEFAULT, "check_order_dlsym_get_answer2")); ASSERT_TRUE(fn2 != NULL) << dlerror(); ASSERT_EQ(42, fn()); @@ -202,29 +252,387 @@ TEST(dlfcn, dlopen_check_order) { dlclose(handle); } +TEST(dlfcn, dlopen_check_order_reloc_siblings) { + // This is how this one works: + // we lookup and call get_answer which is defined in '_2.so' + // and in turn calls external get_answer_impl() defined in _1.so and in '_[a-f].so' + // the correct _impl() is implemented by '_a.so'; + // + // Note that this is test for RTLD_LOCAL (TODO: test for GLOBAL?) + // + // Here is the picture: + // + // libtest_check_order_reloc_siblings.so + // | + // +-> ..._1.so <- empty + // | | + // | +-> ..._a.so <- exports correct answer_impl() + // | | + // | +-> ..._b.so <- every other letter exporting incorrect one. + // | + // +-> ..._2.so <- empty + // | | + // | +-> ..._c.so + // | | + // | +-> ..._d.so + // | + // +-> ..._3.so <- empty + // | + // +-> ..._e.so + // | + // +-> ..._f.so <- exports get_answer() that calls get_anser_impl(); + // implements incorrect get_answer_impl() + + void* handle = dlopen("libtest_check_order_reloc_siblings.so", RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle == nullptr); +#ifdef __BIONIC__ + // TODO: glibc returns nullptr on dlerror() here. Is it bug? + ASSERT_STREQ("dlopen failed: library \"libtest_check_order_reloc_siblings.so\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror()); +#endif + + handle = dlopen("libtest_check_order_reloc_siblings.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + typedef int (*fn_t) (void); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "check_order_reloc_get_answer")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(42, fn()); + + ASSERT_EQ(0, dlclose(handle)); +} + +TEST(dlfcn, dlopen_check_order_reloc_siblings_with_preload) { + // This test uses the same library as dlopen_check_order_reloc_siblings. + // Unlike dlopen_check_order_reloc_siblings it preloads + // libtest_check_order_reloc_siblings_1.so (first dependency) prior to + // dlopen(libtest_check_order_reloc_siblings.so) + + void* handle = dlopen("libtest_check_order_reloc_siblings.so", RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle == nullptr); + handle = dlopen("libtest_check_order_reloc_siblings_1.so", RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle == nullptr); + + void* handle_for_1 = dlopen("libtest_check_order_reloc_siblings_1.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle_for_1 != nullptr) << dlerror(); + + handle = dlopen("libtest_check_order_reloc_siblings.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + ASSERT_EQ(0, dlclose(handle_for_1)); + + typedef int (*fn_t) (void); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "check_order_reloc_get_answer")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(42, fn()); + + ASSERT_EQ(0, dlclose(handle)); +} + +TEST(dlfcn, dlopen_check_order_reloc_grandchild) { + // This is how this one works: + // we lookup and call grandchild_get_answer which is defined in '_2.so' + // and in turn calls external get_answer_impl() defined in '_c_1.so and _c_2.so' + // the correct _impl() is implemented by '_c_1.so'; + // + // Here is the picture of subtree: + // + // libtest_check_order_reloc_siblings.so + // | + // +-> ..._2.so <- grandchild_get_answer() + // | + // +-> ..._c.so <- empty + // | | + // | +-> _c_1.so <- exports correct answer_impl() + // | | + // | +-> _c_2.so <- exports incorrect answer_impl() + // | + // +-> ..._d.so <- empty + + void* handle = dlopen("libtest_check_order_reloc_siblings.so", RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle == nullptr); +#ifdef __BIONIC__ + // TODO: glibc returns nullptr on dlerror() here. Is it bug? + ASSERT_STREQ("dlopen failed: library \"libtest_check_order_reloc_siblings.so\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror()); +#endif + + handle = dlopen("libtest_check_order_reloc_siblings.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + typedef int (*fn_t) (void); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "check_order_reloc_grandchild_get_answer")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(42, fn()); + + ASSERT_EQ(0, dlclose(handle)); +} + +TEST(dlfcn, dlopen_check_order_reloc_nephew) { + // This is how this one works: + // we lookup and call nephew_get_answer which is defined in '_2.so' + // and in turn calls external get_answer_impl() defined in '_[a-f].so' + // the correct _impl() is implemented by '_a.so'; + // + // Here is the picture: + // + // libtest_check_order_reloc_siblings.so + // | + // +-> ..._1.so <- empty + // | | + // | +-> ..._a.so <- exports correct answer_impl() + // | | + // | +-> ..._b.so <- every other letter exporting incorrect one. + // | + // +-> ..._2.so <- empty + // | | + // | +-> ..._c.so + // | | + // | +-> ..._d.so + // | + // +-> ..._3.so <- nephew_get_answer() that calls get_answer_impl(); + // | + // +-> ..._e.so + // | + // +-> ..._f.so + + void* handle = dlopen("libtest_check_order_reloc_siblings.so", RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle == nullptr); +#ifdef __BIONIC__ + // TODO: glibc returns nullptr on dlerror() here. Is it bug? + ASSERT_STREQ("dlopen failed: library \"libtest_check_order_reloc_siblings.so\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror()); +#endif + + handle = dlopen("libtest_check_order_reloc_siblings.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + typedef int (*fn_t) (void); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "check_order_reloc_nephew_get_answer")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(42, fn()); + + ASSERT_EQ(0, dlclose(handle)); +} + +TEST(dlfcn, check_unload_after_reloc) { + // This is how this one works: + // libtest_two_parents_parent1 <- answer_impl() used by libtest_two_parents_child + // | + // +-> libtest_two_parents_child + // + // libtest_two_parents_parent2 <- answer_impl() not used by libtest_two_parents_child + // | + // +-> libtest_two_parents_child + // + // Test dlopens parent1 which loads and relocates libtest_two_parents_child.so + // as a second step it dlopens parent2 and dlcloses parent1... + + void* handle = dlopen("libtest_two_parents_parent1.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + void* handle2 = dlopen("libtest_two_parents_parent2.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle2 != nullptr) << dlerror(); + + typedef int (*fn_t) (void); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle2, "check_order_reloc_get_answer")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(42, fn()); + + ASSERT_EQ(0, dlclose(handle)); + + handle = dlopen("libtest_two_parents_parent1.so", RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD); + ASSERT_TRUE(handle != nullptr); + ASSERT_EQ(0, dlclose(handle)); + + fn = reinterpret_cast<fn_t>(dlsym(handle2, "check_order_reloc_get_answer")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(42, fn()); + + ASSERT_EQ(0, dlclose(handle2)); + + handle = dlopen("libtest_two_parents_parent1.so", RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD); + ASSERT_TRUE(handle == nullptr); +} + +extern "C" int check_order_reloc_root_get_answer_impl() { + return 42; +} + +TEST(dlfcn, dlopen_check_order_reloc_main_executable) { + // This is how this one works: + // we lookup and call get_answer3 which is defined in 'root.so' + // and in turn calls external root_get_answer_impl() defined in _2.so and + // above the correct _impl() is one in the executable. + // + // libtest_check_order_reloc_root.so + // | + // +-> ..._1.so <- empty + // | + // +-> ..._2.so <- gives incorrect answer for answer_main_impl() + // + + void* handle = dlopen("libtest_check_order_reloc_root.so", RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle == nullptr); +#ifdef __BIONIC__ + // TODO: glibc returns nullptr on dlerror() here. Is it bug? + ASSERT_STREQ("dlopen failed: library \"libtest_check_order_reloc_root.so\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror()); +#endif + + handle = dlopen("libtest_check_order_reloc_root.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + typedef int (*fn_t) (void); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "check_order_reloc_root_get_answer")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(42, fn()); + + ASSERT_EQ(0, dlclose(handle)); +} + +TEST(dlfcn, dlopen_check_rtld_local) { + void* sym = dlsym(RTLD_DEFAULT, "dlopen_testlib_simple_func"); + ASSERT_TRUE(sym == nullptr); + + // implicit RTLD_LOCAL + void* handle = dlopen("libtest_simple.so", RTLD_NOW); + sym = dlsym(RTLD_DEFAULT, "dlopen_testlib_simple_func"); + ASSERT_TRUE(sym == nullptr); + ASSERT_SUBSTR("undefined symbol: dlopen_testlib_simple_func", dlerror()); + sym = dlsym(handle, "dlopen_testlib_simple_func"); + ASSERT_TRUE(sym != nullptr); + ASSERT_TRUE(reinterpret_cast<bool (*)(void)>(sym)()); + dlclose(handle); + + // explicit RTLD_LOCAL + handle = dlopen("libtest_simple.so", RTLD_NOW | RTLD_LOCAL); + sym = dlsym(RTLD_DEFAULT, "dlopen_testlib_simple_func"); + ASSERT_TRUE(sym == nullptr); + ASSERT_SUBSTR("undefined symbol: dlopen_testlib_simple_func", dlerror()); + sym = dlsym(handle, "dlopen_testlib_simple_func"); + ASSERT_TRUE(sym != nullptr); + ASSERT_TRUE(reinterpret_cast<bool (*)(void)>(sym)()); + dlclose(handle); +} + +TEST(dlfcn, dlopen_check_rtld_global) { + void* sym = dlsym(RTLD_DEFAULT, "dlopen_testlib_simple_func"); + ASSERT_TRUE(sym == nullptr); + + void* handle = dlopen("libtest_simple.so", RTLD_NOW | RTLD_GLOBAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + sym = dlsym(RTLD_DEFAULT, "dlopen_testlib_simple_func"); + ASSERT_TRUE(sym != nullptr) << dlerror(); + ASSERT_TRUE(reinterpret_cast<bool (*)(void)>(sym)()); + dlclose(handle); + + // RTLD_GLOBAL implies RTLD_NODELETE, let's check that + void* sym_after_dlclose = dlsym(RTLD_DEFAULT, "dlopen_testlib_simple_func"); + ASSERT_EQ(sym, sym_after_dlclose); +} + // libtest_with_dependency_loop.so -> libtest_with_dependency_loop_a.so -> // libtest_with_dependency_loop_b.so -> libtest_with_dependency_loop_c.so -> // libtest_with_dependency_loop_a.so TEST(dlfcn, dlopen_check_loop) { void* handle = dlopen("libtest_with_dependency_loop.so", RTLD_NOW); -#if defined(__BIONIC__) - ASSERT_TRUE(handle == nullptr); - ASSERT_STREQ("dlopen failed: recursive link to \"libtest_with_dependency_loop_a.so\"", dlerror()); - // This symbol should never be exposed - void* f = dlsym(RTLD_DEFAULT, "dlopen_test_invalid_function"); - ASSERT_TRUE(f == nullptr); - ASSERT_SUBSTR("undefined symbol: dlopen_test_invalid_function", dlerror()); - - // dlopen second time to make sure that the library wasn't loaded even though dlopen returned null. - // This may happen if during cleanup the root library or one of the depended libs were not removed - // from soinfo list. + ASSERT_TRUE(handle != nullptr) << dlerror(); + void* f = dlsym(handle, "dlopen_test_loopy_function"); + ASSERT_TRUE(f != nullptr) << dlerror(); + EXPECT_TRUE(reinterpret_cast<bool (*)(void)>(f)()); + ASSERT_EQ(0, dlclose(handle)); + + // dlopen second time to make sure that the library was unloaded correctly handle = dlopen("libtest_with_dependency_loop.so", RTLD_NOW | RTLD_NOLOAD); ASSERT_TRUE(handle == nullptr); +#ifdef __BIONIC__ + // TODO: glibc returns nullptr on dlerror() here. Is it bug? ASSERT_STREQ("dlopen failed: library \"libtest_with_dependency_loop.so\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror()); -#else // glibc allows recursive links - ASSERT_TRUE(handle != nullptr); - dlclose(handle); #endif + + handle = dlopen("libtest_with_dependency_a.so", RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle == nullptr); +} + +TEST(dlfcn, dlopen_nodelete) { + static bool is_unloaded = false; + + void* handle = dlopen("libtest_nodelete_1.so", RTLD_NOW | RTLD_NODELETE); + ASSERT_TRUE(handle != nullptr) << dlerror(); + void (*set_unload_flag_ptr)(bool*); + set_unload_flag_ptr = reinterpret_cast<void (*)(bool*)>(dlsym(handle, "dlopen_nodelete_1_set_unload_flag_ptr")); + ASSERT_TRUE(set_unload_flag_ptr != nullptr) << dlerror(); + set_unload_flag_ptr(&is_unloaded); + + uint32_t* taxicab_number = reinterpret_cast<uint32_t*>(dlsym(handle, "dlopen_nodelete_1_taxicab_number")); + ASSERT_TRUE(taxicab_number != nullptr) << dlerror(); + ASSERT_EQ(1729U, *taxicab_number); + *taxicab_number = 2; + + dlclose(handle); + ASSERT_TRUE(!is_unloaded); + + uint32_t* taxicab_number_after_dlclose = reinterpret_cast<uint32_t*>(dlsym(handle, "dlopen_nodelete_1_taxicab_number")); + ASSERT_EQ(taxicab_number_after_dlclose, taxicab_number); + ASSERT_EQ(2U, *taxicab_number_after_dlclose); + + + handle = dlopen("libtest_nodelete_1.so", RTLD_NOW); + uint32_t* taxicab_number2 = reinterpret_cast<uint32_t*>(dlsym(handle, "dlopen_nodelete_1_taxicab_number")); + ASSERT_EQ(taxicab_number2, taxicab_number); + + ASSERT_EQ(2U, *taxicab_number2); + + dlclose(handle); + ASSERT_TRUE(!is_unloaded); +} + +TEST(dlfcn, dlopen_nodelete_on_second_dlopen) { + static bool is_unloaded = false; + + void* handle = dlopen("libtest_nodelete_2.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + void (*set_unload_flag_ptr)(bool*); + set_unload_flag_ptr = reinterpret_cast<void (*)(bool*)>(dlsym(handle, "dlopen_nodelete_2_set_unload_flag_ptr")); + ASSERT_TRUE(set_unload_flag_ptr != nullptr) << dlerror(); + set_unload_flag_ptr(&is_unloaded); + + uint32_t* taxicab_number = reinterpret_cast<uint32_t*>(dlsym(handle, "dlopen_nodelete_2_taxicab_number")); + ASSERT_TRUE(taxicab_number != nullptr) << dlerror(); + + ASSERT_EQ(1729U, *taxicab_number); + *taxicab_number = 2; + + // This RTLD_NODELETE should be ignored + void* handle1 = dlopen("libtest_nodelete_2.so", RTLD_NOW | RTLD_NODELETE); + ASSERT_TRUE(handle1 != nullptr) << dlerror(); + ASSERT_EQ(handle, handle1); + + dlclose(handle1); + dlclose(handle); + + ASSERT_TRUE(is_unloaded); +} + +TEST(dlfcn, dlopen_nodelete_dt_flags_1) { + static bool is_unloaded = false; + + void* handle = dlopen("libtest_nodelete_dt_flags_1.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + void (*set_unload_flag_ptr)(bool*); + set_unload_flag_ptr = reinterpret_cast<void (*)(bool*)>(dlsym(handle, "dlopen_nodelete_dt_flags_1_set_unload_flag_ptr")); + ASSERT_TRUE(set_unload_flag_ptr != nullptr) << dlerror(); + set_unload_flag_ptr(&is_unloaded); + + dlclose(handle); + ASSERT_TRUE(!is_unloaded); +} + +TEST(dlfcn, dlsym_df_1_global) { + void* handle = dlopen("libtest_dlsym_df_1_global.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + int (*get_answer)(); + get_answer = reinterpret_cast<int (*)()>(dlsym(handle, "dl_df_1_global_get_answer")); + ASSERT_TRUE(get_answer != nullptr) << dlerror(); + ASSERT_EQ(42, get_answer()); + ASSERT_EQ(0, dlclose(handle)); } TEST(dlfcn, dlopen_failure) { @@ -290,7 +698,7 @@ TEST(dlfcn, dlsym_failures) { ASSERT_EQ(0, dlclose(self)); } -TEST(dlfcn, dladdr) { +TEST(dlfcn, dladdr_executable) { dlerror(); // Clear any pending errors. void* self = dlopen(NULL, RTLD_NOW); ASSERT_TRUE(self != NULL); @@ -311,13 +719,11 @@ TEST(dlfcn, dladdr) { rc = readlink("/proc/self/exe", executable_path, sizeof(executable_path)); ASSERT_NE(rc, -1); executable_path[rc] = '\0'; - std::string executable_name(basename(executable_path)); // The filename should be that of this executable. - // Note that we don't know whether or not we have the full path, so we want an "ends_with" test. - std::string dli_fname(info.dli_fname); - dli_fname = basename(&dli_fname[0]); - ASSERT_EQ(dli_fname, executable_name); + char dli_realpath[PATH_MAX]; + ASSERT_TRUE(realpath(info.dli_fname, dli_realpath) != nullptr); + ASSERT_STREQ(executable_path, dli_realpath); // The symbol name should be the symbol we looked up. ASSERT_STREQ(info.dli_sname, "DlSymTestFunction"); @@ -325,22 +731,16 @@ TEST(dlfcn, dladdr) { // The address should be the exact address of the symbol. ASSERT_EQ(info.dli_saddr, sym); - // Look in /proc/pid/maps to find out what address we were loaded at. - // TODO: factor /proc/pid/maps parsing out into a class and reuse all over bionic. - void* base_address = NULL; - char line[BUFSIZ]; - FILE* fp = fopen("/proc/self/maps", "r"); - ASSERT_TRUE(fp != NULL); - while (fgets(line, sizeof(line), fp) != NULL) { - uintptr_t start = strtoul(line, 0, 16); - line[strlen(line) - 1] = '\0'; // Chomp the '\n'. - char* path = strchr(line, '/'); - if (path != NULL && strcmp(executable_path, path) == 0) { - base_address = reinterpret_cast<void*>(start); + std::vector<map_record> maps; + ASSERT_TRUE(Maps::parse_maps(&maps)); + + void* base_address = nullptr; + for (const map_record& rec : maps) { + if (executable_path == rec.pathname) { + base_address = reinterpret_cast<void*>(rec.addr_start); break; } } - fclose(fp); // The base address should be the address we were loaded at. ASSERT_EQ(info.dli_fbase, base_address); @@ -348,6 +748,32 @@ TEST(dlfcn, dladdr) { ASSERT_EQ(0, dlclose(self)); } +#if defined(__LP64__) +#define BIONIC_PATH_TO_LIBC "/system/lib64/libc.so" +#else +#define BIONIC_PATH_TO_LIBC "/system/lib/libc.so" +#endif + +TEST(dlfcn, dladdr_libc) { +#if defined(__BIONIC__) + Dl_info info; + void* addr = reinterpret_cast<void*>(puts); // well-known libc function + ASSERT_TRUE(dladdr(addr, &info) != 0); + + // /system/lib is symlink when this test is executed on host. + char libc_realpath[PATH_MAX]; + ASSERT_TRUE(realpath(BIONIC_PATH_TO_LIBC, libc_realpath) == libc_realpath); + + ASSERT_STREQ(libc_realpath, info.dli_fname); + // TODO: add check for dfi_fbase + ASSERT_STREQ("puts", info.dli_sname); + ASSERT_EQ(addr, info.dli_saddr); +#else + GTEST_LOG_(INFO) << "This test does nothing for glibc. Glibc returns path from ldconfig " + "for libc.so, which is symlink itself (not a realpath).\n"; +#endif +} + TEST(dlfcn, dladdr_invalid) { Dl_info info; @@ -362,19 +788,52 @@ TEST(dlfcn, dladdr_invalid) { ASSERT_TRUE(dlerror() == NULL); // dladdr(3) doesn't set dlerror(3). } -// Our dynamic linker doesn't support GNU hash tables. -#if defined(__BIONIC__) // GNU-style ELF hash tables are incompatible with the MIPS ABI. // MIPS requires .dynsym to be sorted to match the GOT but GNU-style requires sorting by hash code. -#if !defined(__mips__) TEST(dlfcn, dlopen_library_with_only_gnu_hash) { +#if !defined(__mips__) dlerror(); // Clear any pending errors. - void* handle = dlopen("no-elf-hash-table-library.so", RTLD_NOW); - ASSERT_TRUE(handle == NULL); - ASSERT_STREQ("dlopen failed: empty/missing DT_HASH in \"no-elf-hash-table-library.so\" (built with --hash-style=gnu?)", dlerror()); -} -#endif + void* handle = dlopen("libgnu-hash-table-library.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + auto guard = make_scope_guard([&]() { + dlclose(handle); + }); + void* sym = dlsym(handle, "getRandomNumber"); + ASSERT_TRUE(sym != nullptr) << dlerror(); + int (*fn)(void); + fn = reinterpret_cast<int (*)(void)>(sym); + EXPECT_EQ(4, fn()); + + Dl_info dlinfo; + ASSERT_TRUE(0 != dladdr(reinterpret_cast<void*>(fn), &dlinfo)); + + ASSERT_TRUE(fn == dlinfo.dli_saddr); + ASSERT_STREQ("getRandomNumber", dlinfo.dli_sname); + ASSERT_SUBSTR("libgnu-hash-table-library.so", dlinfo.dli_fname); +#else + GTEST_LOG_(INFO) << "This test does nothing for mips/mips64; mips toolchain does not support '--hash-style=gnu'\n"; #endif +} + +TEST(dlfcn, dlopen_library_with_only_sysv_hash) { + void* handle = dlopen("libsysv-hash-table-library.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + auto guard = make_scope_guard([&]() { + dlclose(handle); + }); + void* sym = dlsym(handle, "getRandomNumber"); + ASSERT_TRUE(sym != nullptr) << dlerror(); + int (*fn)(void); + fn = reinterpret_cast<int (*)(void)>(sym); + EXPECT_EQ(4, fn()); + + Dl_info dlinfo; + ASSERT_TRUE(0 != dladdr(reinterpret_cast<void*>(fn), &dlinfo)); + + ASSERT_TRUE(fn == dlinfo.dli_saddr); + ASSERT_STREQ("getRandomNumber", dlinfo.dli_sname); + ASSERT_SUBSTR("libsysv-hash-table-library.so", dlinfo.dli_fname); +} TEST(dlfcn, dlopen_bad_flags) { dlerror(); // Clear any pending errors. @@ -419,7 +878,7 @@ TEST(dlfcn, rtld_next_known_symbol) { TEST(dlfcn, dlsym_weak_func) { dlerror(); - void* handle = dlopen("libtest_dlsym_weak_func.so",RTLD_NOW); + void* handle = dlopen("libtest_dlsym_weak_func.so", RTLD_NOW); ASSERT_TRUE(handle != NULL); int (*weak_func)(); @@ -429,10 +888,96 @@ TEST(dlfcn, dlsym_weak_func) { dlclose(handle); } +TEST(dlfcn, dlopen_undefined_weak_func) { + void* handle = dlopen("libtest_dlopen_weak_undefined_func.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + int (*weak_func)(); + weak_func = reinterpret_cast<int (*)()>(dlsym(handle, "use_weak_undefined_func")); + ASSERT_TRUE(weak_func != nullptr) << dlerror(); + EXPECT_EQ(6551, weak_func()); + dlclose(handle); +} + TEST(dlfcn, dlopen_symlink) { void* handle1 = dlopen("libdlext_test.so", RTLD_NOW); void* handle2 = dlopen("libdlext_test_v2.so", RTLD_NOW); ASSERT_TRUE(handle1 != NULL); ASSERT_TRUE(handle2 != NULL); ASSERT_EQ(handle1, handle2); + dlclose(handle1); + dlclose(handle2); +} + +// libtest_dlopen_from_ctor_main.so depends on +// libtest_dlopen_from_ctor.so which has a constructor +// that calls dlopen(libc...). This is to test the situation +// described in b/7941716. +TEST(dlfcn, dlopen_dlopen_from_ctor) { +#if defined(__BIONIC__) + void* handle = dlopen("libtest_dlopen_from_ctor_main.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + dlclose(handle); +#else + GTEST_LOG_(INFO) << "This test is disabled for glibc (glibc segfaults if you try to call dlopen from a constructor).\n"; +#endif +} + +TEST(dlfcn, symbol_versioning_use_v1) { + void* handle = dlopen("libtest_versioned_uselibv1.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + typedef int (*fn_t)(); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "get_function_version")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(1, fn()); + dlclose(handle); +} + +TEST(dlfcn, symbol_versioning_use_v2) { + void* handle = dlopen("libtest_versioned_uselibv2.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + typedef int (*fn_t)(); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "get_function_version")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(2, fn()); + dlclose(handle); +} + +TEST(dlfcn, symbol_versioning_use_other_v2) { + void* handle = dlopen("libtest_versioned_uselibv2_other.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + typedef int (*fn_t)(); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "get_function_version")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(20, fn()); + dlclose(handle); +} + +TEST(dlfcn, symbol_versioning_use_other_v3) { + void* handle = dlopen("libtest_versioned_uselibv3_other.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + typedef int (*fn_t)(); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "get_function_version")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(3, fn()); + dlclose(handle); +} + +TEST(dlfcn, symbol_versioning_default_via_dlsym) { + void* handle = dlopen("libtest_versioned_lib.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + typedef int (*fn_t)(); + fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "versioned_function")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + ASSERT_EQ(3, fn()); // the default version is 3 + dlclose(handle); +} + +// This preempts the implementation from libtest_versioned_lib.so +extern "C" int version_zero_function() { + return 0; +} + +// This preempts the implementation from libtest_versioned_uselibv*.so +extern "C" int version_zero_function2() { + return 0; } |