platform_bionic/tests/dlfcn_test.cpp

1712 lines
58 KiB
C++
Raw Normal View History

/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include <dlfcn.h>
#include <elf.h>
#include <limits.h>
#include <link.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/cdefs.h>
#if __has_include(<sys/auxv.h>)
#include <sys/auxv.h>
#endif
#include <sys/user.h>
#include <string>
#include <thread>
#include <android-base/file.h>
#include <android-base/macros.h>
#include <android-base/scopeguard.h>
#include "gtest_globals.h"
#include "gtest_utils.h"
#include "dlfcn_symlink_support.h"
#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;
}
static int g_ctor_function_called = 0;
static int g_ctor_argc = 0;
static char** g_ctor_argv = reinterpret_cast<char**>(0xDEADBEEF);
static char** g_ctor_envp = g_ctor_envp;
extern "C" void ctor_function(int argc, char** argv, char** envp) __attribute__ ((constructor));
extern "C" void ctor_function(int argc, char** argv, char** envp) {
g_ctor_function_called = 17;
g_ctor_argc = argc;
g_ctor_argv = argv;
g_ctor_envp = envp;
}
TEST(dlfcn, ctor_function_call) {
ASSERT_EQ(17, g_ctor_function_called);
ASSERT_TRUE(g_ctor_argc = GetArgc());
ASSERT_TRUE(g_ctor_argv = GetArgv());
ASSERT_TRUE(g_ctor_envp = GetEnvp());
}
TEST(dlfcn, dlsym_in_executable) {
dlerror(); // Clear any pending errors.
void* self = dlopen(nullptr, RTLD_NOW);
ASSERT_TRUE(self != nullptr);
ASSERT_TRUE(dlerror() == nullptr);
void* sym = dlsym(self, "DlSymTestFunction");
ASSERT_TRUE(sym != nullptr);
void (*function)() = reinterpret_cast<void(*)()>(sym);
g_called = false;
function();
ASSERT_TRUE(g_called);
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 can't 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 lookup_dlsym_symbol_using_RTLD_DEFAULT =
reinterpret_cast<fn_t>(dlsym(handle, "lookup_dlsym_symbol_using_RTLD_DEFAULT"));
ASSERT_TRUE(lookup_dlsym_symbol_using_RTLD_DEFAULT != nullptr) << dlerror();
int* ptr = lookup_dlsym_symbol_using_RTLD_DEFAULT();
ASSERT_TRUE(ptr != nullptr) << dlerror();
ASSERT_EQ(42, *ptr);
fn_t lookup_dlsym_symbol2_using_RTLD_DEFAULT =
reinterpret_cast<fn_t>(dlsym(handle, "lookup_dlsym_symbol2_using_RTLD_DEFAULT"));
ASSERT_TRUE(lookup_dlsym_symbol2_using_RTLD_DEFAULT != nullptr) << dlerror();
ptr = lookup_dlsym_symbol2_using_RTLD_DEFAULT();
ASSERT_TRUE(ptr != nullptr) << dlerror();
ASSERT_EQ(44, *ptr);
fn_t lookup_dlsym_symbol_using_RTLD_NEXT =
reinterpret_cast<fn_t>(dlsym(handle, "lookup_dlsym_symbol_using_RTLD_NEXT"));
ASSERT_TRUE(lookup_dlsym_symbol_using_RTLD_NEXT != nullptr) << dlerror();
ptr = lookup_dlsym_symbol_using_RTLD_NEXT();
ASSERT_TRUE(ptr != nullptr) << dlerror();
ASSERT_EQ(43, *ptr);
dlclose(handle);
}
TEST(dlfcn, dlsym_from_sofile_with_preload) {
void* preload = dlopen("libtest_dlsym_from_this_grandchild.so", RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(preload != nullptr) << dlerror();
void* handle = dlopen("libtest_dlsym_from_this.so", RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(handle != nullptr) << dlerror();
// check that we can't 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 lookup_dlsym_symbol_using_RTLD_DEFAULT =
reinterpret_cast<fn_t>(dlsym(handle, "lookup_dlsym_symbol_using_RTLD_DEFAULT"));
ASSERT_TRUE(lookup_dlsym_symbol_using_RTLD_DEFAULT != nullptr) << dlerror();
int* ptr = lookup_dlsym_symbol_using_RTLD_DEFAULT();
ASSERT_TRUE(ptr != nullptr) << dlerror();
ASSERT_EQ(42, *ptr);
fn_t lookup_dlsym_symbol2_using_RTLD_DEFAULT =
reinterpret_cast<fn_t>(dlsym(handle, "lookup_dlsym_symbol2_using_RTLD_DEFAULT"));
ASSERT_TRUE(lookup_dlsym_symbol2_using_RTLD_DEFAULT != nullptr) << dlerror();
ptr = lookup_dlsym_symbol2_using_RTLD_DEFAULT();
ASSERT_TRUE(ptr != nullptr) << dlerror();
ASSERT_EQ(44, *ptr);
fn_t lookup_dlsym_symbol_using_RTLD_NEXT =
reinterpret_cast<fn_t>(dlsym(handle, "lookup_dlsym_symbol_using_RTLD_NEXT"));
ASSERT_TRUE(lookup_dlsym_symbol_using_RTLD_NEXT != nullptr) << dlerror();
ptr = lookup_dlsym_symbol_using_RTLD_NEXT();
ASSERT_TRUE(ptr != nullptr) << dlerror();
ASSERT_EQ(43, *ptr);
dlclose(handle);
dlclose(preload);
}
TEST(dlfcn, dlsym_handle_global_sym) {
// check that we do not look into global group
// when looking up symbol by handle
void* handle = dlopen("libtest_empty.so", RTLD_NOW);
dlopen("libtest_with_dependency.so", RTLD_NOW | RTLD_GLOBAL);
void* sym = dlsym(handle, "getRandomNumber");
ASSERT_TRUE(sym == nullptr);
ASSERT_SUBSTR("undefined symbol: getRandomNumber", dlerror());
sym = dlsym(handle, "DlSymTestFunction");
ASSERT_TRUE(sym == nullptr);
ASSERT_SUBSTR("undefined symbol: DlSymTestFunction", dlerror());
dlclose(handle);
}
TEST(dlfcn, dlsym_handle_empty_symbol) {
// check that dlsym of an empty symbol fails (see http://b/33530622)
void* handle = dlopen("libtest_dlsym_from_this.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
void* sym = dlsym(handle, "");
ASSERT_TRUE(sym == nullptr);
ASSERT_SUBSTR("undefined symbol: ", dlerror());
dlclose(handle);
}
TEST(dlfcn, dlsym_with_dependencies) {
void* handle = dlopen("libtest_with_dependency.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr);
dlerror();
// This symbol is in DT_NEEDED library.
void* sym = dlsym(handle, "getRandomNumber");
ASSERT_TRUE(sym != nullptr) << dlerror();
int (*fn)(void);
fn = reinterpret_cast<int (*)(void)>(sym);
EXPECT_EQ(4, fn());
dlclose(handle);
}
TEST(dlfcn, dlopen_noload) {
void* handle = dlopen("libtest_simple.so", RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
handle = dlopen("libtest_simple.so", RTLD_NOW);
void* handle2 = dlopen("libtest_simple.so", RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle != nullptr);
ASSERT_TRUE(handle2 != nullptr);
ASSERT_TRUE(handle == handle2);
ASSERT_EQ(0, dlclose(handle));
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);
}
TEST(dlfcn, dlopen_vdso) {
#if __has_include(<sys/auxv.h>)
if (getauxval(AT_SYSINFO_EHDR) == 0) {
GTEST_SKIP() << "getauxval(AT_SYSINFO_EHDR) == 0, skipping this test";
}
#endif
const char* vdso_name = "linux-vdso.so.1";
#if defined(__i386__)
vdso_name = "linux-gate.so.1";
#endif
void* handle = dlopen(vdso_name, RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
dlclose(handle);
}
// HWASan uses an ifunc to describe the location of its shadow memory,
// so even though it's an unusual case, Android needs to support
// "ifunc variables".
TEST(dlfcn, ifunc_variable) {
typedef const char* (*fn_ptr)();
// ifunc's choice depends on whether IFUNC_CHOICE has a value
// first check the set case
setenv("IFUNC_CHOICE", "set", 1);
// preload libtest_ifunc_variable_impl.so
void* handle_impl = dlopen("libtest_ifunc_variable_impl.so", RTLD_NOW);
void* handle = dlopen("libtest_ifunc_variable.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
const char** foo_ptr = reinterpret_cast<const char**>(dlsym(handle, "foo"));
fn_ptr foo_library_ptr = reinterpret_cast<fn_ptr>(dlsym(handle, "foo_library"));
ASSERT_TRUE(foo_ptr != nullptr) << dlerror();
ASSERT_TRUE(foo_library_ptr != nullptr) << dlerror();
ASSERT_EQ(strncmp("set", *foo_ptr, 3), 0);
ASSERT_EQ(strncmp("set", foo_library_ptr(), 3), 0);
dlclose(handle);
dlclose(handle_impl);
// then check the unset case
unsetenv("IFUNC_CHOICE");
handle_impl = dlopen("libtest_ifunc_variable_impl.so", RTLD_NOW);
handle = dlopen("libtest_ifunc_variable.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
foo_ptr = reinterpret_cast<const char**>(dlsym(handle, "foo"));
foo_library_ptr = reinterpret_cast<fn_ptr>(dlsym(handle, "foo_library"));
ASSERT_TRUE(foo_ptr != nullptr) << dlerror();
ASSERT_TRUE(foo_library_ptr != nullptr) << dlerror();
ASSERT_EQ(strncmp("unset", *foo_ptr, 5), 0);
ASSERT_EQ(strncmp("unset", foo_library_ptr(), 5), 0);
dlclose(handle);
dlclose(handle_impl);
}
TEST(dlfcn, ifunc) {
typedef const char* (*fn_ptr)();
// ifunc's choice depends on whether IFUNC_CHOICE has a value
// first check the set case
setenv("IFUNC_CHOICE", "set", 1);
void* handle = dlopen("libtest_ifunc.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
fn_ptr foo_ptr = reinterpret_cast<fn_ptr>(dlsym(handle, "foo"));
fn_ptr foo_library_ptr = reinterpret_cast<fn_ptr>(dlsym(handle, "foo_library"));
ASSERT_TRUE(foo_ptr != nullptr) << dlerror();
ASSERT_TRUE(foo_library_ptr != nullptr) << dlerror();
ASSERT_EQ(strncmp("set", foo_ptr(), 3), 0);
ASSERT_EQ(strncmp("set", foo_library_ptr(), 3), 0);
dlclose(handle);
// then check the unset case
unsetenv("IFUNC_CHOICE");
handle = dlopen("libtest_ifunc.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
foo_ptr = reinterpret_cast<fn_ptr>(dlsym(handle, "foo"));
foo_library_ptr = reinterpret_cast<fn_ptr>(dlsym(handle, "foo_library"));
ASSERT_TRUE(foo_ptr != nullptr) << dlerror();
ASSERT_TRUE(foo_library_ptr != nullptr) << dlerror();
ASSERT_EQ(strncmp("unset", foo_ptr(), 5), 0);
ASSERT_EQ(strncmp("unset", foo_library_ptr(), 5), 0);
dlclose(handle);
}
TEST(dlfcn, ifunc_ctor_call) {
typedef const char* (*fn_ptr)();
void* handle = dlopen("libtest_ifunc.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
fn_ptr is_ctor_called = reinterpret_cast<fn_ptr>(dlsym(handle, "is_ctor_called_irelative"));
ASSERT_TRUE(is_ctor_called != nullptr) << dlerror();
ASSERT_STREQ("false", is_ctor_called());
is_ctor_called = reinterpret_cast<fn_ptr>(dlsym(handle, "is_ctor_called_jump_slot"));
ASSERT_TRUE(is_ctor_called != nullptr) << dlerror();
ASSERT_STREQ("true", is_ctor_called());
dlclose(handle);
}
TEST(dlfcn, ifunc_ctor_call_rtld_lazy) {
typedef const char* (*fn_ptr)();
void* handle = dlopen("libtest_ifunc.so", RTLD_LAZY);
ASSERT_TRUE(handle != nullptr) << dlerror();
fn_ptr is_ctor_called = reinterpret_cast<fn_ptr>(dlsym(handle, "is_ctor_called_irelative"));
ASSERT_TRUE(is_ctor_called != nullptr) << dlerror();
ASSERT_STREQ("false", is_ctor_called());
is_ctor_called = reinterpret_cast<fn_ptr>(dlsym(handle, "is_ctor_called_jump_slot"));
ASSERT_TRUE(is_ctor_called != nullptr) << dlerror();
ASSERT_STREQ("true", is_ctor_called());
dlclose(handle);
}
TEST(dlfcn, dlopen_check_relocation_dt_needed_order) {
// This is the structure of the test library and
// its dt_needed libraries
// libtest_relo_check_dt_needed_order.so
// |
// +-> libtest_relo_check_dt_needed_order_1.so
// |
// +-> libtest_relo_check_dt_needed_order_2.so
//
// The root library references relo_test_get_answer_lib - which is defined
// in both dt_needed libraries, the correct relocation should
// use the function defined in libtest_relo_check_dt_needed_order_1.so
void* handle = nullptr;
auto guard = android::base::make_scope_guard([&]() { dlclose(handle); });
handle = dlopen("libtest_relo_check_dt_needed_order.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
typedef int (*fn_t) (void);
fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "relo_test_get_answer"));
ASSERT_TRUE(fn != nullptr) << dlerror();
ASSERT_EQ(1, fn());
}
TEST(dlfcn, dlopen_check_order_dlsym) {
// Here is how the test library and its dt_needed
// libraries are arranged
//
// libtest_check_order_children.so
// |
// +-> ..._1_left.so
// | |
// | +-> ..._a.so
// | |
// | +-> ...r_b.so
// |
// +-> ..._2_right.so
// | |
// | +-> ..._d.so
// | |
// | +-> ..._b.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, "check_order_dlsym_get_answer");
ASSERT_TRUE(sym == 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, "check_order_dlsym_get_answer"));
ASSERT_TRUE(fn != nullptr) << dlerror();
fn2 = reinterpret_cast<fn_t>(dlsym(RTLD_DEFAULT, "check_order_dlsym_get_answer2"));
ASSERT_TRUE(fn2 != nullptr) << dlerror();
ASSERT_EQ(42, fn());
ASSERT_EQ(43, fn2());
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);
// Check if dlsym() for main program's handle searches RTLD_GLOBAL
// shared libraries after symbol was not found in the main executable
// and dependent libraries.
void* handle_for_main_executable = dlopen(nullptr, RTLD_NOW);
sym = dlsym(handle_for_main_executable, "dlopen_testlib_simple_func");
ASSERT_TRUE(sym != nullptr) << dlerror();
dlclose(handle_for_main_executable);
}
// 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);
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__
ASSERT_STREQ("dlopen failed: library \"libtest_with_dependency_loop.so\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror());
#else
// TODO: glibc returns nullptr on dlerror() here. Is it bug?
ASSERT_TRUE(dlerror() == nullptr);
#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) {
void* self = dlopen("/does/not/exist", RTLD_NOW);
ASSERT_TRUE(self == nullptr);
#if defined(__BIONIC__)
ASSERT_STREQ("dlopen failed: library \"/does/not/exist\" not found", dlerror());
#else
ASSERT_STREQ("/does/not/exist: cannot open shared object file: No such file or directory", dlerror());
#endif
}
TEST(dlfcn, dlclose_unload) {
const size_t kPageSize = getpagesize();
void* handle = dlopen("libtest_simple.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
uint32_t* taxicab_number = static_cast<uint32_t*>(dlsym(handle, "dlopen_testlib_taxicab_number"));
ASSERT_TRUE(taxicab_number != nullptr) << dlerror();
EXPECT_EQ(1729U, *taxicab_number);
dlclose(handle);
// Making sure that the library has been unmapped as part of library unload
// process. Note that mprotect somewhat counter-intuitively returns ENOMEM in
// this case.
uintptr_t page_start = reinterpret_cast<uintptr_t>(taxicab_number) & ~(kPageSize - 1);
ASSERT_TRUE(mprotect(reinterpret_cast<void*>(page_start), kPageSize, PROT_NONE) != 0);
ASSERT_ERRNO(ENOMEM);
}
static void ConcurrentDlErrorFn(std::string& error) {
ASSERT_TRUE(dlerror() == nullptr);
void* handle = dlopen("/child/thread", RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
const char* err = dlerror();
ASSERT_TRUE(err != nullptr);
error = err;
}
TEST(dlfcn, dlerror_concurrent_buffer) {
void* handle = dlopen("/main/thread", RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
const char* main_thread_error = dlerror();
ASSERT_TRUE(main_thread_error != nullptr);
ASSERT_SUBSTR("/main/thread", main_thread_error);
std::string child_thread_error;
std::thread t(ConcurrentDlErrorFn, std::ref(child_thread_error));
t.join();
ASSERT_SUBSTR("/child/thread", child_thread_error.c_str());
// Check that main thread local buffer was not modified.
ASSERT_SUBSTR("/main/thread", main_thread_error);
}
TEST(dlfcn, dlerror_concurrent) {
void* handle = dlopen("/main/thread", RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string child_thread_error;
std::thread t(ConcurrentDlErrorFn, std::ref(child_thread_error));
t.join();
ASSERT_SUBSTR("/child/thread", child_thread_error.c_str());
const char* main_thread_error = dlerror();
ASSERT_TRUE(main_thread_error != nullptr);
ASSERT_SUBSTR("/main/thread", main_thread_error);
}
TEST(dlfcn, dlsym_failures) {
dlerror(); // Clear any pending errors.
void* self = dlopen(nullptr, RTLD_NOW);
ASSERT_TRUE(self != nullptr);
ASSERT_TRUE(dlerror() == nullptr);
void* sym;
#if defined(__BIONIC__) && !defined(__LP64__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
// RTLD_DEFAULT in lp32 bionic is not (void*)0
// so it can be distinguished from the NULL handle.
sym = dlsym(nullptr, "test");
ASSERT_TRUE(sym == nullptr);
ASSERT_STREQ("dlsym failed: library handle is null", dlerror());
#pragma clang diagnostic pop
#endif
// Symbol that doesn't exist.
sym = dlsym(self, "ThisSymbolDoesNotExist");
ASSERT_TRUE(sym == nullptr);
ASSERT_SUBSTR("undefined symbol: ThisSymbolDoesNotExist", dlerror());
ASSERT_EQ(0, dlclose(self));
}
TEST(dlfcn, dladdr_executable) {
dlerror(); // Clear any pending errors.
void* self = dlopen(nullptr, RTLD_NOW);
ASSERT_TRUE(self != nullptr);
ASSERT_TRUE(dlerror() == nullptr);
void* sym = dlsym(self, "DlSymTestFunction");
ASSERT_TRUE(sym != nullptr);
// Deliberately ask dladdr for an address inside a symbol, rather than the symbol base address.
void* addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(sym) + 2);
Dl_info info;
int rc = dladdr(addr, &info);
ASSERT_NE(rc, 0); // Zero on error, non-zero on success.
// Get the name of this executable.
const std::string executable_path = android::base::GetExecutablePath();
// The filename should be that of this executable.
char dli_realpath[PATH_MAX];
ASSERT_TRUE(realpath(info.dli_fname, dli_realpath) != nullptr);
ASSERT_STREQ(executable_path.c_str(), dli_realpath);
// The symbol name should be the symbol we looked up.
ASSERT_STREQ(info.dli_sname, "DlSymTestFunction");
// The address should be the exact address of the symbol.
ASSERT_EQ(info.dli_saddr, sym);
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;
}
}
// The base address should be the address we were loaded at.
ASSERT_EQ(info.dli_fbase, base_address);
ASSERT_EQ(0, dlclose(self));
}
TEST(dlfcn, dlopen_executable_by_absolute_path) {
void* handle1 = dlopen(nullptr, RTLD_NOW);
ASSERT_TRUE(handle1 != nullptr) << dlerror();
void* handle2 = dlopen(android::base::GetExecutablePath().c_str(), RTLD_NOW);
ASSERT_TRUE(handle2 != nullptr) << dlerror();
#if defined(__BIONIC__)
ASSERT_EQ(handle1, handle2);
#else
GTEST_SKIP() << "Skipping ASSERT_EQ(handle1, handle2) for glibc: "
"it loads a separate copy of the main executable "
"on dlopen by absolute path";
#endif
}
#define ALTERNATE_PATH_TO_SYSTEM_LIB "/system/lib64/" ABI_STRING "/"
#if __has_feature(hwaddress_sanitizer)
#define PATH_TO_LIBC PATH_TO_SYSTEM_LIB "hwasan/libc.so"
#define PATH_TO_BOOTSTRAP_LIBC PATH_TO_SYSTEM_LIB "bootstrap/hwasan/libc.so"
#define ALTERNATE_PATH_TO_LIBC ALTERNATE_PATH_TO_SYSTEM_LIB "hwasan/libc.so"
#else
#define PATH_TO_LIBC PATH_TO_SYSTEM_LIB "libc.so"
#define PATH_TO_BOOTSTRAP_LIBC PATH_TO_SYSTEM_LIB "bootstrap/libc.so"
#define ALTERNATE_PATH_TO_LIBC ALTERNATE_PATH_TO_SYSTEM_LIB "libc.so"
#endif
TEST(dlfcn, dladdr_libc) {
#if defined(__GLIBC__)
GTEST_SKIP() << "glibc returns libc.so's ldconfig path, which is a symlink (not a realpath)";
#endif
Dl_info info;
void* addr = reinterpret_cast<void*>(puts); // An arbitrary libc function.
ASSERT_TRUE(dladdr(addr, &info) != 0);
// Check if libc is in canonical path or in alternate path.
const char* expected_path;
if (strncmp(ALTERNATE_PATH_TO_SYSTEM_LIB,
info.dli_fname,
sizeof(ALTERNATE_PATH_TO_SYSTEM_LIB) - 1) == 0) {
// Platform with emulated architecture. Symlink on ARC++.
expected_path = ALTERNATE_PATH_TO_LIBC;
} else if (strncmp(PATH_TO_BOOTSTRAP_LIBC, info.dli_fname,
sizeof(PATH_TO_BOOTSTRAP_LIBC) - 1) == 0) {
expected_path = PATH_TO_BOOTSTRAP_LIBC;
} else {
// /system/lib is symlink when this test is executed on host.
expected_path = PATH_TO_LIBC;
}
char libc_realpath[PATH_MAX];
ASSERT_TRUE(realpath(expected_path, libc_realpath) != nullptr) << strerror(errno);
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);
}
TEST(dlfcn, dladdr_invalid) {
Dl_info info;
dlerror(); // Clear any pending errors.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
// No symbol corresponding to NULL.
ASSERT_EQ(dladdr(nullptr, &info), 0); // Zero on error, non-zero on success.
ASSERT_TRUE(dlerror() == nullptr); // dladdr(3) doesn't set dlerror(3).
#pragma clang diagnostic pop
// No symbol corresponding to a stack address.
ASSERT_EQ(dladdr(&info, &info), 0); // Zero on error, non-zero on success.
ASSERT_TRUE(dlerror() == nullptr); // dladdr(3) doesn't set dlerror(3).
}
TEST(dlfcn, dlopen_library_with_only_gnu_hash) {
dlerror(); // Clear any pending errors.
void* handle = dlopen("libgnu-hash-table-library.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
auto guard = android::base::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);
}
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 = android::base::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.
void* handle;
#if defined(__GLIBC__)
// glibc was smart enough not to define RTLD_NOW as 0, so it can detect missing flags.
handle = dlopen(nullptr, 0);
ASSERT_TRUE(handle == nullptr);
ASSERT_SUBSTR("invalid", dlerror());
#endif
handle = dlopen(nullptr, 0xffffffff);
ASSERT_TRUE(handle == nullptr);
ASSERT_SUBSTR("invalid", dlerror());
// glibc actually allows you to choose both RTLD_NOW and RTLD_LAZY at the same time, and so do we.
handle = dlopen(nullptr, RTLD_NOW|RTLD_LAZY);
ASSERT_TRUE(handle != nullptr);
ASSERT_SUBSTR(nullptr, dlerror());
}
TEST(dlfcn, rtld_default_unknown_symbol) {
void* addr = dlsym(RTLD_DEFAULT, "ANY_UNKNOWN_SYMBOL_NAME");
ASSERT_TRUE(addr == nullptr);
}
TEST(dlfcn, rtld_default_known_symbol) {
void* addr = dlsym(RTLD_DEFAULT, "fopen");
ASSERT_TRUE(addr != nullptr);
}
TEST(dlfcn, rtld_next_unknown_symbol) {
void* addr = dlsym(RTLD_NEXT, "ANY_UNKNOWN_SYMBOL_NAME");
ASSERT_TRUE(addr == nullptr);
}
TEST(dlfcn, rtld_next_known_symbol) {
void* addr = dlsym(RTLD_NEXT, "fopen");
ASSERT_TRUE(addr != nullptr);
}
// Check that RTLD_NEXT of a libc symbol works in dlopened library
TEST(dlfcn, rtld_next_from_library) {
void* library_with_fclose = dlopen("libtest_check_rtld_next_from_library.so", RTLD_NOW | RTLD_GLOBAL);
ASSERT_TRUE(library_with_fclose != nullptr) << dlerror();
void* expected_addr = dlsym(RTLD_DEFAULT, "fclose");
ASSERT_TRUE(expected_addr != nullptr) << dlerror();
typedef void* (*get_libc_fclose_ptr_fn_t)();
get_libc_fclose_ptr_fn_t get_libc_fclose_ptr =
reinterpret_cast<get_libc_fclose_ptr_fn_t>(dlsym(library_with_fclose, "get_libc_fclose_ptr"));
ASSERT_TRUE(get_libc_fclose_ptr != nullptr) << dlerror();
ASSERT_EQ(expected_addr, get_libc_fclose_ptr());
dlclose(library_with_fclose);
}
TEST(dlfcn, dlsym_weak_func) {
dlerror();
void* handle = dlopen("libtest_dlsym_weak_func.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr);
int (*weak_func)();
weak_func = reinterpret_cast<int (*)()>(dlsym(handle, "weak_func"));
ASSERT_TRUE(weak_func != nullptr) << "dlerror: " << dlerror();
EXPECT_EQ(42, 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) {
DlfcnSymlink symlink("dlopen_symlink");
const std::string symlink_name = android::base::Basename(symlink.get_symlink_path());
void* handle1 = dlopen("libdlext_test.so", RTLD_NOW);
void* handle2 = dlopen(symlink_name.c_str(), RTLD_NOW);
ASSERT_TRUE(handle1 != nullptr);
ASSERT_TRUE(handle2 != nullptr);
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(__GLIBC__)
GTEST_SKIP() << "glibc segfaults if you try to call dlopen from a constructor";
#endif
void* handle = dlopen("libtest_dlopen_from_ctor_main.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
dlclose(handle);
}
static std::string g_fini_call_order_str;
static void register_fini_call(const char* s) {
g_fini_call_order_str += s;
}
static void test_init_fini_call_order_for(const char* libname) {
g_fini_call_order_str.clear();
void* handle = dlopen(libname, RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
typedef int (*get_init_order_number_t)();
get_init_order_number_t get_init_order_number =
reinterpret_cast<get_init_order_number_t>(dlsym(handle, "get_init_order_number"));
ASSERT_EQ(321, get_init_order_number());
typedef void (*set_fini_callback_t)(void (*f)(const char*));
set_fini_callback_t set_fini_callback =
reinterpret_cast<set_fini_callback_t>(dlsym(handle, "set_fini_callback"));
set_fini_callback(register_fini_call);
dlclose(handle);
ASSERT_EQ("(root)(child)(grandchild)", g_fini_call_order_str);
}
TEST(dlfcn, init_fini_call_order) {
test_init_fini_call_order_for("libtest_init_fini_order_root.so");
test_init_fini_call_order_for("libtest_init_fini_order_root2.so");
}
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);
}
TEST(dlfcn, dlvsym_smoke) {
#if !defined(ANDROID_HOST_MUSL)
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>(dlvsym(handle, "versioned_function", "nonversion"));
ASSERT_TRUE(fn == nullptr);
ASSERT_SUBSTR("undefined symbol: versioned_function, version nonversion", dlerror());
}
{
fn_t fn = reinterpret_cast<fn_t>(dlvsym(handle, "versioned_function", "TESTLIB_V2"));
ASSERT_TRUE(fn != nullptr) << dlerror();
ASSERT_EQ(2, fn());
}
dlclose(handle);
#else
GTEST_SKIP() << "musl doesn't have dlvsym";
#endif
}
// 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;
}
TEST(dlfcn, dt_runpath_smoke) {
void* handle = dlopen("libtest_dt_runpath_d.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
typedef void *(* dlopen_b_fn)();
dlopen_b_fn fn = (dlopen_b_fn)dlsym(handle, "dlopen_b");
ASSERT_TRUE(fn != nullptr) << dlerror();
void *p = fn();
ASSERT_TRUE(p != nullptr);
dlclose(handle);
}
TEST(dlfcn, dt_runpath_absolute_path) {
std::string libpath = GetTestLibRoot() + "/libtest_dt_runpath_d.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
typedef void *(* dlopen_b_fn)();
dlopen_b_fn fn = (dlopen_b_fn)dlsym(handle, "dlopen_b");
ASSERT_TRUE(fn != nullptr) << dlerror();
void *p = fn();
ASSERT_TRUE(p != nullptr);
dlclose(handle);
}
static void test_dlclose_after_thread_local_dtor(const char* library_name) {
bool is_dtor_triggered = false;
auto f = [](void* handle, bool* is_dtor_triggered) {
typedef void (*fn_t)(bool*);
fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "init_thread_local_variable"));
ASSERT_TRUE(fn != nullptr) << dlerror();
fn(is_dtor_triggered);
ASSERT_TRUE(!*is_dtor_triggered);
};
void* handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
handle = dlopen(library_name, RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
std::thread t(f, handle, &is_dtor_triggered);
t.join();
ASSERT_TRUE(is_dtor_triggered);
dlclose(handle);
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
}
TEST(dlfcn, dlclose_after_thread_local_dtor) {
test_dlclose_after_thread_local_dtor("libtest_thread_local_dtor.so");
}
TEST(dlfcn, dlclose_after_thread_local_dtor_indirect) {
test_dlclose_after_thread_local_dtor("libtest_indirect_thread_local_dtor.so");
}
static void test_dlclose_before_thread_local_dtor(const char* library_name) {
bool is_dtor_triggered = false;
auto f = [library_name](bool* is_dtor_triggered) {
void* handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
handle = dlopen(library_name, RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
typedef void (*fn_t)(bool*);
fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "init_thread_local_variable"));
ASSERT_TRUE(fn != nullptr) << dlerror();
fn(is_dtor_triggered);
dlclose(handle);
ASSERT_TRUE(!*is_dtor_triggered);
// Since we have thread_atexit dtors associated with handle - the library should
// still be availabe.
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle != nullptr) << dlerror();
dlclose(handle);
};
void* handle = dlopen(library_name, RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
dlclose(handle);
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
std::thread t(f, &is_dtor_triggered);
t.join();
#if defined(__BIONIC__)
// ld-android.so unloads unreferenced libraries on pthread_exit()
ASSERT_TRUE(is_dtor_triggered);
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
#else
// GLIBC does not unload libraries with ref_count = 0 on pthread_exit
ASSERT_TRUE(is_dtor_triggered);
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle != nullptr) << dlerror();
#endif
}
TEST(dlfcn, dlclose_before_thread_local_dtor) {
test_dlclose_before_thread_local_dtor("libtest_thread_local_dtor.so");
}
TEST(dlfcn, dlclose_before_thread_local_dtor_indirect) {
test_dlclose_before_thread_local_dtor("libtest_indirect_thread_local_dtor.so");
}
TEST(dlfcn, dlclose_before_thread_local_dtor_multiple_dsos) {
const constexpr char* library_name = "libtest_indirect_thread_local_dtor.so";
bool is_dtor1_triggered = false;
bool is_dtor2_triggered = false;
std::mutex mtx;
std::condition_variable cv;
void* library_handle = nullptr;
bool thread1_dlopen_complete = false;
bool thread2_thread_local_dtor_initialized = false;
bool thread1_complete = false;
auto f1 = [&]() {
void* handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
handle = dlopen(library_name, RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
std::unique_lock<std::mutex> lock(mtx);
thread1_dlopen_complete = true;
library_handle = handle;
lock.unlock();
cv.notify_one();
typedef void (*fn_t)(bool*);
fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "init_thread_local_variable"));
ASSERT_TRUE(fn != nullptr) << dlerror();
fn(&is_dtor1_triggered);
lock.lock();
cv.wait(lock, [&] { return thread2_thread_local_dtor_initialized; });
lock.unlock();
dlclose(handle);
ASSERT_TRUE(!is_dtor1_triggered);
// Since we have thread_atexit dtors associated with handle - the library should
// still be availabe.
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle != nullptr) << dlerror();
dlclose(handle);
};
auto f2 = [&]() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return thread1_dlopen_complete; });
void* handle = library_handle;
lock.unlock();
typedef void (*fn_t)(bool*);
fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "init_thread_local_variable2"));
ASSERT_TRUE(fn != nullptr) << dlerror();
fn(&is_dtor2_triggered);
lock.lock();
thread2_thread_local_dtor_initialized = true;
lock.unlock();
cv.notify_one();
lock.lock();
cv.wait(lock, [&] { return thread1_complete; });
lock.unlock();
ASSERT_TRUE(!is_dtor2_triggered);
};
void* handle = dlopen(library_name, RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
dlclose(handle);
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
std::thread t1(f1);
std::thread t2(f2);
t1.join();
ASSERT_TRUE(is_dtor1_triggered);
ASSERT_TRUE(!is_dtor2_triggered);
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle != nullptr) << dlerror();
dlclose(handle);
std::unique_lock<std::mutex> lock(mtx);
thread1_complete = true;
lock.unlock();
cv.notify_one();
t2.join();
ASSERT_TRUE(is_dtor2_triggered);
#if defined(__BIONIC__)
// ld-android.so unloads unreferenced libraries on pthread_exit()
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle == nullptr);
#else
// GLIBC does not unload libraries with ref_count = 0 on pthread_exit
handle = dlopen(library_name, RTLD_NOW | RTLD_NOLOAD);
ASSERT_TRUE(handle != nullptr) << dlerror();
#endif
}
TEST(dlfcn, RTLD_macros) {
#if !defined(RTLD_LOCAL)
#error no RTLD_LOCAL
#elif !defined(RTLD_LAZY)
#error no RTLD_LAZY
#elif !defined(RTLD_NOW)
#error no RTLD_NOW
#elif !defined(RTLD_NOLOAD)
#error no RTLD_NOLOAD
#elif !defined(RTLD_GLOBAL)
#error no RTLD_GLOBAL
#elif !defined(RTLD_NODELETE)
#error no RTLD_NODELETE
#endif
}
// Bionic specific tests
#if defined(__BIONIC__)
#if defined(__arm__)
void validate_compatibility_of_native_library(const std::string& soname, const std::string& path) {
// Grab the dynamic section in text form...
ExecTestHelper eth;
eth.SetArgs({"readelf", "-dW", path.c_str(), nullptr});
eth.Run([&]() { execvpe("readelf", eth.GetArgs(), eth.GetEnv()); }, 0, nullptr);
std::string output = eth.GetOutput();
// Check that there *is* a legacy DT_HASH (not just a GNU hash)...
ASSERT_TRUE(std::regex_search(output, std::regex("\\(HASH\\)"))) << output;
// Check that there is no DT_ANDROID_REL or DT_ANDROID_RELA...
ASSERT_FALSE(std::regex_search(output, std::regex("\\(ANDROID_REL\\)"))) << output;
ASSERT_FALSE(std::regex_search(output, std::regex("\\(ANDROID_RELA\\)"))) << output;
// Check that we have regular non-packed relocations.
// libdl.so is simple enough that it doesn't have any relocations.
ASSERT_TRUE(std::regex_search(output, std::regex("\\(RELA?\\)")) || soname == "libdl.so")
<< output;
}
void validate_compatibility_of_native_library(const std::string& soname) {
// On the systems with emulation system libraries would be of different
// architecture. Try to use alternate paths first.
std::string path = std::string(ALTERNATE_PATH_TO_SYSTEM_LIB) + soname;
if (access(path.c_str(), R_OK) != 0) {
path = std::string(PATH_TO_SYSTEM_LIB) + soname;
ASSERT_EQ(0, access(path.c_str(), R_OK));
}
validate_compatibility_of_native_library(soname, path);
}
// This is a test for app compatibility workaround for arm apps
// affected by http://b/24465209
TEST(dlext, compat_elf_hash_and_relocation_tables) {
validate_compatibility_of_native_library("libc.so");
validate_compatibility_of_native_library("liblog.so");
validate_compatibility_of_native_library("libstdc++.so");
validate_compatibility_of_native_library("libdl.so");
validate_compatibility_of_native_library("libm.so");
validate_compatibility_of_native_library("libz.so");
validate_compatibility_of_native_library("libjnigraphics.so");
}
#endif // defined(__arm__)
TEST(dlfcn, dlopen_invalid_rw_load_segment) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-rw_load_segment.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\": W+E load segments are not allowed";
ASSERT_STREQ(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_unaligned_shdr_offset) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-unaligned_shdr_offset.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\" has invalid shdr offset/size: ";
ASSERT_SUBSTR(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_zero_shentsize) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-zero_shentsize.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\" has unsupported e_shentsize: 0x0 (expected 0x";
ASSERT_SUBSTR(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_zero_shstrndx) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-zero_shstrndx.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\" has invalid e_shstrndx";
ASSERT_STREQ(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_empty_shdr_table) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-empty_shdr_table.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\" has no section headers";
ASSERT_STREQ(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_zero_shdr_table_offset) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-zero_shdr_table_offset.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\" has invalid shdr offset/size: 0/";
ASSERT_SUBSTR(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_zero_shdr_table_content) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-zero_shdr_table_content.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\" .dynamic section header was not found";
ASSERT_SUBSTR(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_textrels) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-textrels.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\" has text relocations";
ASSERT_SUBSTR(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_textrels2) {
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-textrels2.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
std::string expected_dlerror = std::string("dlopen failed: \"") + libpath + "\" has text relocations";
ASSERT_SUBSTR(expected_dlerror.c_str(), dlerror());
}
TEST(dlfcn, dlopen_invalid_local_tls) {
#if defined(__riscv)
// This is a test for bad gold behavior, and gold doesn't support riscv64.
#else
const std::string libpath = GetPrebuiltElfDir() + "/libtest_invalid-local-tls.so";
void* handle = dlopen(libpath.c_str(), RTLD_NOW);
ASSERT_TRUE(handle == nullptr);
#if defined(__arm__)
const char* referent = "local section";
#else
const char* referent = "local symbol \"tls_var_2\"";
#endif
std::string expected_dlerror = std::string("dlopen failed: unexpected TLS reference to ") +
referent + " in \"" + libpath + "\"";
ASSERT_SUBSTR(expected_dlerror.c_str(), dlerror());
#endif
}
TEST(dlfcn, dlopen_df_1_global) {
void* handle = dlopen("libtest_dlopen_df_1_global.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
}
linker: Handle libraries with disjoint mappings correctly. It's possible and sometimes beneficial for a library to have disjoint mappings and for other libraries to be mapped into the gap between the mappings using ANDROID_DLEXT_RESERVED_ADDRESS. See for example the proposal for partitioning in lld [1]. Because the find_containing_library and do_dl_unwind_find_exidx functions use a simple bounds check to figure out whether a pointer belongs to a library they will, given a pointer into a library mapped into the gap of a library with disjoint mappings, return a pointer to the soinfo for the outer library instead of the inner one, because the outer library will appear before the inner one in the solist. From a user perspective this means that we won't be able to unwind the inner library's frames on 32-bit ARM with libgcc, dladdr() will return information for the outer library given a pointer to the inner one and dlopen() et al will use the linker namespace of the outer library when called from the inner one (although they will usually be the same). To make this work correctly, make it so that once find_containing_library sees a match for the bounds check, it examines the library's PT_LOADs to make sure that there is a mapping for the given address. This is similar to how libgcc and libunwind_llvm already handle finding the PT_GNU_EH_FRAME on non-ARM32 platforms [2,3]. do_dl_unwind_find_exidx is reimplemented in terms of find_containing_library. [1] http://lists.llvm.org/pipermail/llvm-dev/2019-February/130583.html [2] https://github.com/llvm/llvm-project/blob/e739ac0e255597d818c907223034ddf3bc18a593/libunwind/src/AddressSpace.hpp#L523 [3] https://android.googlesource.com/toolchain/gcc/+/master/gcc-4.9/libgcc/unwind-dw2-fde-dip.c#294 Test: /data/nativetest{,64}/bionic-unit-tests/bionic-unit-tests on walleye-userdebug Change-Id: I368fe6ad3c470b3dff80f7d9b04253566d63a7d2
2019-03-01 22:12:49 +01:00
TEST(dlfcn, segment_gap) {
void* handle = dlopen("libsegment_gap_outer.so", RTLD_NOW);
ASSERT_TRUE(handle != nullptr) << dlerror();
auto get_inner = reinterpret_cast<void* (*)()>(dlsym(handle, "get_inner"));
void* inner = get_inner();
(void)inner;
#if __arm__
int count;
_Unwind_Ptr outer_exidx = dl_unwind_find_exidx(reinterpret_cast<_Unwind_Ptr>(get_inner), &count);
_Unwind_Ptr inner_exidx = dl_unwind_find_exidx(reinterpret_cast<_Unwind_Ptr>(inner), &count);
EXPECT_NE(0u, outer_exidx);
EXPECT_NE(0u, inner_exidx);
EXPECT_NE(inner_exidx, outer_exidx);
#endif
Dl_info info;
int rc = dladdr(inner, &info);
ASSERT_NE(rc, 0);
EXPECT_NE(nullptr, strstr(info.dli_fname, "libsegment_gap_inner.so"));
}
#endif