Fix bug in finding another thread's TCB.

Change-Id: I06c86ca0c077b464fc6c9fbdf5b89889a26da5fb
diff --git a/libc/bionic/sys_thread_properties.cpp b/libc/bionic/sys_thread_properties.cpp
index 24d7551..d1a73b7 100644
--- a/libc/bionic/sys_thread_properties.cpp
+++ b/libc/bionic/sys_thread_properties.cpp
@@ -39,6 +39,11 @@
 #include <sys/uio.h>
 #include <sys/user.h>
 
+#if defined(__i386__)
+#include <asm/ldt.h>
+#endif
+
+#include "private/ErrnoRestorer.h"
 #include "private/bionic_elf_tls.h"
 #include "private/bionic_globals.h"
 #include "private/bionic_tls.h"
@@ -72,21 +77,30 @@
 
   // Find the thread-pointer register for the given thread.
   void** tp_reg = nullptr;
-
-#if defined(__x86_64__) || defined(__i386__)
+#if defined(__x86_64__)
+  {
+    ErrnoRestorer errno_restorer;
+    errno = 0;
+    uintptr_t fs_base = ptrace(PTRACE_PEEKUSER, tid, offsetof(user_regs_struct, fs_base), nullptr);
+    if (errno == 0) {
+      tp_reg = reinterpret_cast<void**>(fs_base);
+    }
+  }
+#elif defined(__i386__)
   struct user_regs_struct regs;
   struct iovec pt_iov = {
       .iov_base = &regs,
       .iov_len = sizeof(regs),
   };
+
   if (ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &pt_iov) == 0) {
-#if defined(__x86_64__)
-    tp_reg = reinterpret_cast<void**>(regs.fs);
-#elif defined(__i386__)
-    tp_reg = reinterpret_cast<void**>(regs.xgs);
-#endif
+    struct user_desc u_info;
+    u_info.entry_number = regs.xgs >> 3;
+    if (ptrace(PTRACE_GET_THREAD_AREA, tid, u_info.entry_number, &u_info) == 0) {
+      tp_reg = reinterpret_cast<void**>(u_info.base_addr);
+    }
   }
-#elif defined(__aarch64__) || defined(__arm__)
+#elif defined(__aarch64__)
   uint64_t reg;
   struct iovec pt_iov {
     .iov_base = &reg, .iov_len = sizeof(reg),
@@ -95,6 +109,11 @@
   if (ptrace(PTRACE_GETREGSET, tid, NT_ARM_TLS, &pt_iov) == 0) {
     tp_reg = reinterpret_cast<void**>(reg);
   }
+#elif defined(__arm__)
+  if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &tp_reg) != 0) {
+    // Reset the tp_reg if ptrace was unsuccessful.
+    tp_reg = nullptr;
+  }
 #endif
 
   if (tp_reg == nullptr) {
diff --git a/tests/Android.bp b/tests/Android.bp
index bf921c8..9b95c61 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -991,6 +991,8 @@
         "ld_config_test_helper_lib1",
         "ld_config_test_helper_lib2",
         "ld_config_test_helper_lib3",
+        "tls_properties_helper",
+        "thread_exit_cb_helper",
     ],
 }
 
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index ef4fddd..4b86faf 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -80,7 +80,7 @@
 }
 
 cc_test {
-   name: "thread_exit_cb_helper.cpp",
+   name: "thread_exit_cb_helper",
    defaults: ["bionic_testlib_defaults"],
    srcs: ["thread_exit_cb_helper.cpp"],
    cflags: ["-fno-emulated-tls"],
diff --git a/tests/libs/tls_properties_helper.cpp b/tests/libs/tls_properties_helper.cpp
index 3f8d118..5982de4 100644
--- a/tests/libs/tls_properties_helper.cpp
+++ b/tests/libs/tls_properties_helper.cpp
@@ -34,8 +34,19 @@
 
 #include <assert.h>
 #include <dlfcn.h>
+#include <elf.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
 #include <stdio.h>
-#include <unistd.h>  // for gettid
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/uio.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+#include <unistd.h>
 
 // Helper binary to use TLS-related functions in thread_properties
 
@@ -59,20 +70,54 @@
 // Tests iterate_dynamic tls chunks.
 // Export a var from the shared so.
 __thread char large_tls_var[4 * 1024 * 1024];
+// found_count  has to be Global variable so that the non-capturing lambda
+// can access it.
+int found_count = 0;
 void test_iter_tls() {
   void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
-
-  int i = 0;
-  auto cb = [&](void* dtls_begin, void* dtls_end, size_t dso_id, void* arg) {
-    printf("iterate_cb i = %d\n", i++);
+  large_tls_var[1025] = 'a';
+  auto cb = +[](void* dtls_begin, void* dtls_end, size_t dso_id, void* arg) {
+    if (&large_tls_var >= dtls_begin && &large_tls_var < dtls_end) ++found_count;
   };
   __libc_iterate_dynamic_tls(gettid(), cb, nullptr);
+
+  // It should be found exactly once.
+  assert(found_count == 1);
   printf("done_iterate_dynamic_tls\n");
 }
 
+void* parent_addr = nullptr;
+void test_iterate_another_thread_tls() {
+  large_tls_var[1025] = 'b';
+  parent_addr = &large_tls_var;
+  found_count = 0;
+
+  pid_t pid = fork();
+  assert(pid != -1);
+  int status;
+  if (pid) {
+    // Parent.
+    assert(pid == wait(&status));
+    assert(0 == status);
+  } else {
+    // Child.
+    pid_t parent_pid = getppid();
+    assert(0 == ptrace(PTRACE_ATTACH, parent_pid));
+    assert(parent_pid == waitpid(parent_pid, &status, 0));
+
+    auto cb = +[](void* dtls_begin, void* dtls_end, size_t dso_id, void* arg) {
+      if (parent_addr >= dtls_begin && parent_addr < dtls_end) ++found_count;
+    };
+    __libc_iterate_dynamic_tls(parent_pid, cb, nullptr);
+    // It should be found exactly once.
+    assert(found_count == 1);
+    printf("done_iterate_another_thread_tls\n");
+  }
+}
 int main() {
   test_static_tls_bounds();
   test_iter_tls();
+  test_iterate_another_thread_tls();
   return 0;
 }