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)
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 306b800..d62eaec 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -2383,9 +2383,26 @@
 
   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 89119aa..31ee74c 100644
--- a/linker/linker_soinfo.cpp
+++ b/linker/linker_soinfo.cpp
@@ -297,7 +297,11 @@
 }
 
 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 88225d8..6d88880 100644
--- a/tests/elftls_dl_test.cpp
+++ b/tests/elftls_dl_test.cpp
@@ -269,3 +269,69 @@
   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 7fa239c..6da2f6f 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.
 //