95646e6666
We've talked about this many times in the past, but partners struggle to understand "expected 38, got 22" in these contexts, and I always have to go and check the header files just to be sure I'm sure. I actually think the glibc geterrorname_np() function (which would return "ENOSYS" rather than "Function not implemented") would be more helpful, but I'll have to go and implement that first, and then come back. Being forced to go through all our errno assertions did also make me want to use a more consistent style for our ENOSYS assertions in particular --- there's a particularly readable idiom, and I'll also come back and move more of those checks to the most readable idiom. I've added a few missing `errno = 0`s before tests, and removed a few stray `errno = 0`s from tests that don't actually make assertions about errno, since I had to look at every single reference to errno anyway. Test: treehugger Change-Id: Iba7c56f2adc30288c3e00ade106635e515e88179
1709 lines
58 KiB
C++
1709 lines
58 KiB
C++
/*
|
|
* 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) {
|
|
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) & ~(PAGE_SIZE - 1);
|
|
ASSERT_TRUE(mprotect(reinterpret_cast<void*>(page_start), PAGE_SIZE, 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();
|
|
}
|
|
|
|
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
|