aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Prichard <rprichard@google.com>2019-04-01 17:42:14 -0700
committerRyan Prichard <rprichard@google.com>2019-04-04 16:46:59 -0700
commit3f05d3e971a8c69fd083c66222e5bac3f44be137 (patch)
tree06cf7e46fa09f15bbca29da27cb17a838848402c
parent20a0001e05cb84c03076021ecf4cbf3b28bda3f8 (diff)
downloadbionic-3f05d3e971a8c69fd083c66222e5bac3f44be137.tar.gz
Fix dlsym and dladdr for TLS symbols
* dlsym: call __tls_get_addr for TLS symbols * dladdr: skip TLS symbols Bug: b/123772574 Test: bionic unit tests Change-Id: I59a8bc4a7d455e1018b0d577b027b6417c8487cd (cherry picked from commit e4d620bc805aa675924a4f50c7179195126d65a6)
-rw-r--r--linker/linker.cpp19
-rw-r--r--linker/linker_soinfo.cpp4
-rw-r--r--tests/elftls_dl_test.cpp66
-rw-r--r--tests/libs/elftls_dynamic.cpp18
4 files changed, 106 insertions, 1 deletions
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 306b80014..d62eaec39 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -2383,9 +2383,26 @@ bool do_dlsym(void* handle,
if (sym != nullptr) {
uint32_t bind = ELF_ST_BIND(sym->st_info);
+ uint32_t type = ELF_ST_TYPE(sym->st_info);
if ((bind == STB_GLOBAL || bind == STB_WEAK) && sym->st_shndx != 0) {
- *symbol = reinterpret_cast<void*>(found->resolve_symbol_address(sym));
+ if (type == STT_TLS) {
+ // For a TLS symbol, dlsym returns the address of the current thread's
+ // copy of the symbol. This function may allocate a DTV and/or storage
+ // for the source TLS module. (Allocating a DTV isn't necessary if the
+ // symbol is part of static TLS, but it's simpler to reuse
+ // __tls_get_addr.)
+ soinfo_tls* tls_module = found->get_tls();
+ if (tls_module == nullptr) {
+ DL_ERR("TLS symbol \"%s\" in solib \"%s\" with no TLS segment",
+ sym_name, found->get_realpath());
+ return false;
+ }
+ const TlsIndex ti { tls_module->module_id, sym->st_value };
+ *symbol = TLS_GET_ADDR(&ti);
+ } else {
+ *symbol = reinterpret_cast<void*>(found->resolve_symbol_address(sym));
+ }
failure_guard.Disable();
LD_LOG(kLogDlsym,
"... dlsym successful: sym_name=\"%s\", sym_ver=\"%s\", found in=\"%s\", address=%p",
diff --git a/linker/linker_soinfo.cpp b/linker/linker_soinfo.cpp
index 89119aa2c..31ee74c86 100644
--- a/linker/linker_soinfo.cpp
+++ b/linker/linker_soinfo.cpp
@@ -297,7 +297,11 @@ ElfW(Sym)* soinfo::find_symbol_by_address(const void* addr) {
}
static bool symbol_matches_soaddr(const ElfW(Sym)* sym, ElfW(Addr) soaddr) {
+ // Skip TLS symbols. A TLS symbol's value is relative to the start of the TLS segment rather than
+ // to the start of the solib. The solib only reserves space for the initialized part of the TLS
+ // segment. (i.e. .tdata is followed by .tbss, and .tbss overlaps other sections.)
return sym->st_shndx != SHN_UNDEF &&
+ ELF_ST_TYPE(sym->st_info) != STT_TLS &&
soaddr >= sym->st_value &&
soaddr < sym->st_value + sym->st_size;
}
diff --git a/tests/elftls_dl_test.cpp b/tests/elftls_dl_test.cpp
index 88225d81d..6d8888039 100644
--- a/tests/elftls_dl_test.cpp
+++ b/tests/elftls_dl_test.cpp
@@ -269,3 +269,69 @@ TEST(elftls_dl, dlclose_removes_entry) {
GTEST_SKIP() << "test doesn't apply to glibc";
#endif
}
+
+// Use dlsym to get the address of a TLS variable in static TLS and compare it
+// against the ordinary address of the variable.
+TEST(elftls_dl, dlsym_static_tls) {
+ void* lib = dlopen("libtest_elftls_shared_var.so", RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+
+ int* var_addr = static_cast<int*>(dlsym(lib, "elftls_shared_var"));
+ ASSERT_EQ(&elftls_shared_var, var_addr);
+
+ std::thread([lib] {
+ int* var_addr = static_cast<int*>(dlsym(lib, "elftls_shared_var"));
+ ASSERT_EQ(&elftls_shared_var, var_addr);
+ }).join();
+}
+
+// Use dlsym to get the address of a TLS variable in dynamic TLS and compare it
+// against the ordinary address of the variable.
+TEST(elftls_dl, dlsym_dynamic_tls) {
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+ auto get_var_addr = reinterpret_cast<int*(*)()>(dlsym(lib, "get_large_tls_var_addr"));
+ ASSERT_NE(nullptr, get_var_addr);
+
+ int* var_addr = static_cast<int*>(dlsym(lib, "large_tls_var"));
+ ASSERT_EQ(get_var_addr(), var_addr);
+
+ std::thread([lib, get_var_addr] {
+ int* var_addr = static_cast<int*>(dlsym(lib, "large_tls_var"));
+ ASSERT_EQ(get_var_addr(), var_addr);
+ }).join();
+}
+
+// Calling dladdr on a TLS variable's address doesn't find anything.
+TEST(elftls_dl, dladdr_on_tls_var) {
+ Dl_info info;
+
+ // Static TLS variable
+ ASSERT_EQ(0, dladdr(&elftls_shared_var, &info));
+
+ // Dynamic TLS variable
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+ int* var_addr = static_cast<int*>(dlsym(lib, "large_tls_var"));
+ ASSERT_EQ(0, dladdr(var_addr, &info));
+}
+
+// Verify that dladdr does not misinterpret a TLS symbol's value as a virtual
+// address.
+TEST(elftls_dl, dladdr_skip_tls_symbol) {
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+
+ auto get_local_addr = reinterpret_cast<void*(*)()>(dlsym(lib, "get_local_addr"));
+ ASSERT_NE(nullptr, get_local_addr);
+ void* local_addr = get_local_addr();
+
+ Dl_info info;
+ ASSERT_NE(0, dladdr(local_addr, &info));
+
+ std::string libpath = GetTestlibRoot() + "/libtest_elftls_dynamic.so";
+ char dli_realpath[PATH_MAX];
+ ASSERT_TRUE(realpath(info.dli_fname, dli_realpath));
+ ASSERT_STREQ(libpath.c_str(), dli_realpath);
+ ASSERT_STREQ(nullptr, info.dli_sname);
+ ASSERT_EQ(nullptr, info.dli_saddr);
+}
diff --git a/tests/libs/elftls_dynamic.cpp b/tests/libs/elftls_dynamic.cpp
index 7fa239c58..6da2f6f40 100644
--- a/tests/libs/elftls_dynamic.cpp
+++ b/tests/libs/elftls_dynamic.cpp
@@ -27,6 +27,24 @@
*/
// This shared object test library is dlopen'ed by the main test executable.
+
+// Export a large TLS variable from a solib for testing dladdr and dlsym. The
+// TLS symbol's value will appear to overlap almost everything else in the
+// shared object, but dladdr must not return it.
+__thread char large_tls_var[4 * 1024 * 1024];
+
+extern "C" char* get_large_tls_var_addr() {
+ return large_tls_var;
+}
+
+// For testing dladdr, return an address that's part of the solib's .bss
+// section, but does not have an entry in the dynsym table and whose
+// solib-relative address appears to overlap with the large TLS variable.
+extern "C" void* get_local_addr() {
+ static char dummy[1024];
+ return &dummy[512];
+}
+
// This variable comes from libtest_elftls_shared_var.so, which is part of
// static TLS. Verify that a GD-model access can access the variable.
//