[compiler-rt] r303887 - Implement tls scanning for darwin LSan

Francis Ricci via llvm-commits llvm-commits at lists.llvm.org
Thu May 25 10:41:13 PDT 2017


Author: fjricci
Date: Thu May 25 12:41:13 2017
New Revision: 303887

URL: http://llvm.org/viewvc/llvm-project?rev=303887&view=rev
Log:
Implement tls scanning for darwin LSan

Summary:
This required for any users who call exit() after creating
thread-specific data, as tls destructors are only called when
pthread_exit() or pthread_cancel() are used. This should also
match tls behavior on linux.

Getting the base address of the tls section is straightforward,
as it's stored as a section offset in %gs. The size is a bit trickier
to work out, as there doesn't appear to be any official documentation
or source code referring to it. The size used in this patch was determined
by taking the difference between the base address and the address of the
subsequent memory region returned by vm_region_recurse_64, which was
1024 * sizeof(uptr) on all threads except the main thread, where it was
larger. Since the section must be the same size on all of the threads,
1024 * sizeof(uptr) seemed to be a reasonable size to use, barring
a more programtic way to get the size.

1024 seems like a reasonable number, given that PTHREAD_KEYS_MAX
is 512 on darwin, so pthread keys will fit inside the region while
leaving space for other tls data. A larger size would overflow the
memory region returned by vm_region_recurse_64, and a smaller size
wouldn't leave room for all the pthread keys. In addition, the
stress test added here passes, which means that we are scanning at
least the full set of possible pthread keys, and probably
the full tls section.

Reviewers: alekseyshl, kubamracek

Subscribers: krytarowski, llvm-commits

Differential Revision: https://reviews.llvm.org/D33215

Added:
    compiler-rt/trunk/test/lsan/TestCases/many_tls_keys.cc
Modified:
    compiler-rt/trunk/lib/lsan/lsan_common.cc
    compiler-rt/trunk/lib/lsan/lsan_common_mac.cc
    compiler-rt/trunk/lib/lsan/lsan_flags.inc
    compiler-rt/trunk/lib/sanitizer_common/sanitizer_mac.cc

Modified: compiler-rt/trunk/lib/lsan/lsan_common.cc
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/lsan/lsan_common.cc?rev=303887&r1=303886&r2=303887&view=diff
==============================================================================
--- compiler-rt/trunk/lib/lsan/lsan_common.cc (original)
+++ compiler-rt/trunk/lib/lsan/lsan_common.cc Thu May 25 12:41:13 2017
@@ -265,19 +265,21 @@ static void ProcessThreads(SuspendedThre
     }
 
     if (flags()->use_tls) {
-      LOG_THREADS("TLS at %p-%p.\n", tls_begin, tls_end);
-      if (cache_begin == cache_end) {
-        ScanRangeForPointers(tls_begin, tls_end, frontier, "TLS", kReachable);
-      } else {
-        // Because LSan should not be loaded with dlopen(), we can assume
-        // that allocator cache will be part of static TLS image.
-        CHECK_LE(tls_begin, cache_begin);
-        CHECK_GE(tls_end, cache_end);
-        if (tls_begin < cache_begin)
-          ScanRangeForPointers(tls_begin, cache_begin, frontier, "TLS",
-                               kReachable);
-        if (tls_end > cache_end)
-          ScanRangeForPointers(cache_end, tls_end, frontier, "TLS", kReachable);
+      if (tls_begin) {
+        LOG_THREADS("TLS at %p-%p.\n", tls_begin, tls_end);
+        // If the tls and cache ranges don't overlap, scan full tls range,
+        // otherwise, only scan the non-overlapping portions
+        if (cache_begin == cache_end || tls_end < cache_begin ||
+            tls_end > cache_end) {
+          ScanRangeForPointers(tls_begin, tls_end, frontier, "TLS", kReachable);
+        } else {
+          if (tls_begin < cache_begin)
+            ScanRangeForPointers(tls_begin, cache_begin, frontier, "TLS",
+                                 kReachable);
+          if (tls_end > cache_end)
+            ScanRangeForPointers(cache_end, tls_end, frontier, "TLS",
+                                 kReachable);
+        }
       }
       if (dtls && !DTLSInDestruction(dtls)) {
         for (uptr j = 0; j < dtls->dtv_size; ++j) {

Modified: compiler-rt/trunk/lib/lsan/lsan_common_mac.cc
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/lsan/lsan_common_mac.cc?rev=303887&r1=303886&r2=303887&view=diff
==============================================================================
--- compiler-rt/trunk/lib/lsan/lsan_common_mac.cc (original)
+++ compiler-rt/trunk/lib/lsan/lsan_common_mac.cc Thu May 25 12:41:13 2017
@@ -91,12 +91,7 @@ LoadedModule *GetLinker() { return nullp
 
 // Required on Linux for initialization of TLS behavior, but should not be
 // required on Darwin.
-void InitializePlatformSpecificModules() {
-  if (flags()->use_tls) {
-    Report("use_tls=1 is not supported on Darwin.\n");
-    Die();
-  }
-}
+void InitializePlatformSpecificModules() {}
 
 // Scans global variables for heap pointers.
 void ProcessGlobalRegions(Frontier *frontier) {

Modified: compiler-rt/trunk/lib/lsan/lsan_flags.inc
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/lsan/lsan_flags.inc?rev=303887&r1=303886&r2=303887&view=diff
==============================================================================
--- compiler-rt/trunk/lib/lsan/lsan_flags.inc (original)
+++ compiler-rt/trunk/lib/lsan/lsan_flags.inc Thu May 25 12:41:13 2017
@@ -30,7 +30,7 @@ LSAN_FLAG(bool, use_globals, true,
           "Root set: include global variables (.data and .bss)")
 LSAN_FLAG(bool, use_stacks, true, "Root set: include thread stacks")
 LSAN_FLAG(bool, use_registers, true, "Root set: include thread registers")
-LSAN_FLAG(bool, use_tls, !SANITIZER_MAC,
+LSAN_FLAG(bool, use_tls, true,
           "Root set: include TLS and thread-specific storage")
 LSAN_FLAG(bool, use_root_regions, true,
           "Root set: include regions added via __lsan_register_root_region().")

Modified: compiler-rt/trunk/lib/sanitizer_common/sanitizer_mac.cc
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/sanitizer_common/sanitizer_mac.cc?rev=303887&r1=303886&r2=303887&view=diff
==============================================================================
--- compiler-rt/trunk/lib/sanitizer_common/sanitizer_mac.cc (original)
+++ compiler-rt/trunk/lib/sanitizer_common/sanitizer_mac.cc Thu May 25 12:41:13 2017
@@ -370,6 +370,27 @@ uptr GetTlsSize() {
 void InitTlsSize() {
 }
 
+uptr TlsBaseAddr() {
+  uptr segbase = 0;
+#if defined(__x86_64__)
+  asm("movq %%gs:0,%0" : "=r"(segbase));
+#elif defined(__i386__)
+  asm("movl %%gs:0,%0" : "=r"(segbase));
+#endif
+  return segbase;
+}
+
+// The size of the tls on darwin does not appear to be well documented,
+// however the vm memory map suggests that it is 1024 uptrs in size,
+// with a size of 0x2000 bytes on x86_64 and 0x1000 bytes on i386.
+uptr TlsSize() {
+#if defined(__x86_64__) || defined(__i386__)
+  return 1024 * sizeof(uptr);
+#else
+  return 0;
+#endif
+}
+
 void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size,
                           uptr *tls_addr, uptr *tls_size) {
 #if !SANITIZER_GO
@@ -377,8 +398,8 @@ void GetThreadStackAndTls(bool main, upt
   GetThreadStackTopAndBottom(main, &stack_top, &stack_bottom);
   *stk_addr = stack_bottom;
   *stk_size = stack_top - stack_bottom;
-  *tls_addr = 0;
-  *tls_size = 0;
+  *tls_addr = TlsBaseAddr();
+  *tls_size = TlsSize();
 #else
   *stk_addr = 0;
   *stk_size = 0;

Added: compiler-rt/trunk/test/lsan/TestCases/many_tls_keys.cc
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/test/lsan/TestCases/many_tls_keys.cc?rev=303887&view=auto
==============================================================================
--- compiler-rt/trunk/test/lsan/TestCases/many_tls_keys.cc (added)
+++ compiler-rt/trunk/test/lsan/TestCases/many_tls_keys.cc Thu May 25 12:41:13 2017
@@ -0,0 +1,94 @@
+// Test that lsan handles tls correctly for many threads
+// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0"
+// RUN: %clangxx_lsan %s -DUSE_THREAD -o %t-thread
+// RUN: %clangxx_lsan %s -DUSE_PTHREAD -o %t-pthread
+// RUN: %env_lsan_opts=$LSAN_BASE:"use_tls=0" not %run %t-thread 2>&1 | FileCheck %s
+// RUN: %env_lsan_opts=$LSAN_BASE:"use_tls=1" %run %t-thread 2>&1
+// RUN: %env_lsan_opts="" %run %t-thread 2>&1
+// RUN: %env_lsan_opts=$LSAN_BASE:"use_tls=0" not %run %t-pthread 2>&1 | FileCheck %s
+// RUN: %env_lsan_opts=$LSAN_BASE:"use_tls=1" %run %t-pthread 2>&1
+// RUN: %env_lsan_opts="" %run %t-pthread 2>&1
+
+#include <assert.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static const int NUM_THREADS = 10;
+
+pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+int finished = 0;
+
+#if USE_THREAD
+__thread void *ptr1;
+__thread void *ptr2;
+__thread void *ptr3;
+__thread void *ptr4;
+__thread void *ptr5;
+
+void alloc() {
+  ptr1 = malloc(1111);
+  ptr2 = malloc(2222);
+  ptr3 = malloc(3333);
+  ptr4 = malloc(4444);
+  ptr5 = malloc(5555);
+}
+
+#elif USE_PTHREAD
+// We won't be able to create the maximum number of keys, due to other users
+// of the tls, but we'll use as many keys as we can before failing to create
+// a new key.
+pthread_key_t keys[PTHREAD_KEYS_MAX];
+static const int PTHREAD_KEY_INVALID = 0xffffffff;
+
+void alloc() {
+  for (int i = 0; i < PTHREAD_KEYS_MAX; ++i) {
+    void *ptr = malloc(123);
+    if ((keys[i] == PTHREAD_KEY_INVALID) || pthread_setspecific(keys[i], ptr)) {
+      free(ptr);
+      break;
+    }
+  }
+}
+
+void pthread_destructor(void *arg) {
+  assert(0 && "pthread destructors shouldn't be called");
+}
+#endif
+
+void *thread_start(void *arg) {
+  alloc();
+
+  pthread_mutex_lock(&mutex);
+  finished++;
+  pthread_mutex_unlock(&mutex);
+
+  // don't exit, to intentionally leak tls data
+  while (1)
+    sleep(100);
+}
+
+int main() {
+#if USE_PTHREAD
+  for (int i = 0; i < PTHREAD_KEYS_MAX; ++i) {
+    if (pthread_key_create(&keys[i], pthread_destructor)) {
+      keys[i] = PTHREAD_KEY_INVALID;
+      break;
+    }
+  }
+#endif
+
+  pthread_t thread[NUM_THREADS];
+  for (int i = 0; i < NUM_THREADS; ++i) {
+    assert(0 == pthread_create(&thread[i], 0, thread_start, 0));
+  }
+  // spin until all threads have finished
+  while (finished < NUM_THREADS)
+    sleep(1);
+  exit(0);
+}
+
+// CHECK: LeakSanitizer: detected memory leaks
+// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer:




More information about the llvm-commits mailing list