diff options
author | Ryan Prichard <rprichard@google.com> | 2019-04-01 17:42:14 -0700 |
---|---|---|
committer | Ryan Prichard <rprichard@google.com> | 2019-04-04 16:46:59 -0700 |
commit | 3f05d3e971a8c69fd083c66222e5bac3f44be137 (patch) | |
tree | 06cf7e46fa09f15bbca29da27cb17a838848402c | |
parent | 20a0001e05cb84c03076021ecf4cbf3b28bda3f8 (diff) | |
download | bionic-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.cpp | 19 | ||||
-rw-r--r-- | linker/linker_soinfo.cpp | 4 | ||||
-rw-r--r-- | tests/elftls_dl_test.cpp | 66 | ||||
-rw-r--r-- | tests/libs/elftls_dynamic.cpp | 18 |
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. // |