Use pthreads to manage thread-local storage on darwin for leak sanitizer

Summary:
__thread is supported on Darwin, but is implemented dynamically via
function calls to __tls_get_addr. This causes two issues when combined
with leak sanitizer, due to malloc() interception.

- The dynamic loader calls malloc during the process of loading
  the sanitizer dylib, while swapping a placeholder tlv_boostrap
  function for __tls_get_addr. This will cause tlv_bootstrap to
  be called in DisabledInThisThread() via the asan allocator.

- The first time __tls_get_addr is called, it allocates memory
  for the thread-local object, during which it calls malloc(). This
  call will be intercepted, leading to an infinite loop in the asan
  allocator, in which the allocator calls DisabledInThisThread,
  which calls tls_get_addr, which calls into the allocator again.

Reviewers: kcc, glider, kubamracek

Subscribers: llvm-commits

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

llvm-svn: 294994
This commit is contained in:
Francis Ricci 2017-02-13 22:20:07 +00:00
parent 48dfa1a6ed
commit 7cda03e6d1
4 changed files with 57 additions and 9 deletions

View File

@ -32,20 +32,15 @@ namespace __lsan {
// also to protect the global list of root regions.
BlockingMutex global_mutex(LINKER_INITIALIZED);
__attribute__((tls_model("initial-exec")))
THREADLOCAL int disable_counter;
bool DisabledInThisThread() { return disable_counter > 0; }
void DisableInThisThread() { disable_counter++; }
void EnableInThisThread() {
if (!disable_counter && common_flags()->detect_leaks) {
Flags lsan_flags;
void DisableCounterUnderflow() {
if (common_flags()->detect_leaks) {
Report("Unmatched call to __lsan_enable().\n");
Die();
}
disable_counter--;
}
Flags lsan_flags;
void Flags::SetDefaults() {
#define LSAN_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue;
#include "lsan_flags.inc"

View File

@ -127,6 +127,7 @@ enum IgnoreObjectResult {
// Functions called from the parent tool.
void InitCommonLsan();
void DoLeakCheck();
void DisableCounterUnderflow();
bool DisabledInThisThread();
// Used to implement __lsan::ScopedDisabler.

View File

@ -34,6 +34,17 @@ static bool IsLinker(const char* full_name) {
return LibraryNameIs(full_name, kLinkerName);
}
__attribute__((tls_model("initial-exec")))
THREADLOCAL int disable_counter;
bool DisabledInThisThread() { return disable_counter > 0; }
void DisableInThisThread() { disable_counter++; }
void EnableInThisThread() {
if (disable_counter == 0) {
DisableCounterUnderflow();
}
disable_counter--;
}
void InitializePlatformSpecificModules() {
ListOfModules modules;
modules.init();

View File

@ -12,12 +12,53 @@
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_allocator_internal.h"
#include "sanitizer_common/sanitizer_platform.h"
#include "lsan_common.h"
#if CAN_SANITIZE_LEAKS && SANITIZER_MAC
#include <pthread.h>
namespace __lsan {
static pthread_key_t key;
static pthread_once_t key_once = PTHREAD_ONCE_INIT;
static void make_tls_key() { CHECK_EQ(pthread_key_create(&key, NULL), 0); }
static int *get_tls_val(bool allocate) {
pthread_once(&key_once, make_tls_key);
int *ptr = (int *)pthread_getspecific(key);
if (ptr == NULL && allocate) {
ptr = (int *)InternalAlloc(sizeof(*ptr));
*ptr = 0;
pthread_setspecific(key, ptr);
}
return ptr;
}
bool DisabledInThisThread() {
int *disable_counter = get_tls_val(false);
return disable_counter ? *disable_counter > 0 : false;
}
void DisableInThisThread() {
int *disable_counter = get_tls_val(true);
++*disable_counter;
}
void EnableInThisThread() {
int *disable_counter = get_tls_val(true);
if (*disable_counter == 0) {
DisableCounterUnderflow();
}
--*disable_counter;
}
void InitializePlatformSpecificModules() {
CHECK(0 && "unimplemented");
}