[lsan] Support LeakSanitizer runtime on Fuchsia

Support LeakSanitizer runtime on Fuchsia.

Patch By: mcgrathr

Differential Revision: https://reviews.llvm.org/D72887
This commit is contained in:
Roland McGrath 2020-01-27 23:34:15 -08:00 committed by Petr Hosek
parent b276dec5b6
commit d59e3429f3
15 changed files with 417 additions and 20 deletions

View File

@ -640,7 +640,7 @@ else()
endif()
if (COMPILER_RT_HAS_SANITIZER_COMMON AND LSAN_SUPPORTED_ARCH AND
OS_NAME MATCHES "Darwin|Linux|NetBSD")
OS_NAME MATCHES "Darwin|Linux|NetBSD|Fuchsia")
set(COMPILER_RT_HAS_LSAN TRUE)
else()
set(COMPILER_RT_HAS_LSAN FALSE)

View File

@ -480,6 +480,8 @@ bool GetThreadRangesLocked(tid_t os_id, uptr *stack_begin, uptr *stack_end,
return true;
}
void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr> *caches) {}
void ForEachExtraStackRange(tid_t os_id, RangeIteratorCallback callback,
void *arg) {
__asan::AsanThread *t = __asan::GetAsanThreadByOsIDLocked(os_id);

View File

@ -5,6 +5,7 @@ append_rtti_flag(OFF LSAN_CFLAGS)
set(LSAN_COMMON_SOURCES
lsan_common.cpp
lsan_common_fuchsia.cpp
lsan_common_linux.cpp
lsan_common_mac.cpp
)
@ -12,6 +13,7 @@ set(LSAN_COMMON_SOURCES
set(LSAN_SOURCES
lsan.cpp
lsan_allocator.cpp
lsan_fuchsia.cpp
lsan_interceptors.cpp
lsan_linux.cpp
lsan_mac.cpp

View File

@ -15,7 +15,6 @@
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_flag_parser.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
#include "lsan_allocator.h"
#include "lsan_common.h"
#include "lsan_thread.h"
@ -87,17 +86,6 @@ static void InitializeFlags() {
__sanitizer_set_report_path(common_flags()->log_path);
}
static void OnStackUnwind(const SignalContext &sig, const void *,
BufferedStackTrace *stack) {
stack->Unwind(StackTrace::GetNextInstructionPc(sig.pc), sig.bp, sig.context,
common_flags()->fast_unwind_on_fatal);
}
static void LsanOnDeadlySignal(int signo, void *siginfo, void *context) {
HandleDeadlySignal(siginfo, context, GetCurrentThread(), &OnStackUnwind,
nullptr);
}
extern "C" void __lsan_init() {
CHECK(!lsan_init_is_running);
if (lsan_inited)

View File

@ -14,6 +14,8 @@
#include "lsan_thread.h"
#if SANITIZER_POSIX
#include "lsan_posix.h"
#elif SANITIZER_FUCHSIA
#include "lsan_fuchsia.h"
#endif
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
@ -36,6 +38,7 @@ namespace __lsan {
void InitializeInterceptors();
void ReplaceSystemMalloc();
void LsanOnDeadlySignal(int signo, void *siginfo, void *context);
#define ENSURE_LSAN_INITED do { \
CHECK(!lsan_init_is_running); \

View File

@ -66,7 +66,10 @@ template <typename AddressSpaceView>
using PrimaryAllocatorASVT = SizeClassAllocator32<AP32<AddressSpaceView>>;
using PrimaryAllocator = PrimaryAllocatorASVT<LocalAddressSpaceView>;
#elif defined(__x86_64__) || defined(__powerpc64__)
# if defined(__powerpc64__)
# if SANITIZER_FUCHSIA
const uptr kAllocatorSpace = ~(uptr)0;
const uptr kAllocatorSize = 0x40000000000ULL; // 4T.
# elif defined(__powerpc64__)
const uptr kAllocatorSpace = 0xa0000000000ULL;
const uptr kAllocatorSize = 0x20000000000ULL; // 2T.
# else

View File

@ -211,6 +211,13 @@ void ForEachExtraStackRangeCb(uptr begin, uptr end, void* arg) {
ScanRangeForPointers(begin, end, frontier, "FAKE STACK", kReachable);
}
#if SANITIZER_FUCHSIA
// Fuchsia handles all threads together with its own callback.
static void ProcessThreads(SuspendedThreadsList const &, Frontier *) {}
#else
// Scans thread data (stacks and TLS) for heap pointers.
static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
Frontier *frontier) {
@ -308,6 +315,8 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
}
}
#endif // SANITIZER_FUCHSIA
void ScanRootRegion(Frontier *frontier, const RootRegion &root_region,
uptr region_begin, uptr region_end, bool is_readable) {
uptr intersection_begin = Max(root_region.begin, region_begin);
@ -531,6 +540,14 @@ static void ReportIfNotSuspended(ThreadContextBase *tctx, void *arg) {
}
}
#if SANITIZER_FUCHSIA
// Fuchsia provides a libc interface that guarantees all threads are
// covered, and SuspendedThreadList is never really used.
static void ReportUnsuspendedThreads(const SuspendedThreadsList &) {}
#else // !SANITIZER_FUCHSIA
static void ReportUnsuspendedThreads(
const SuspendedThreadsList &suspended_threads) {
InternalMmapVector<tid_t> threads(suspended_threads.ThreadCount());
@ -543,6 +560,8 @@ static void ReportUnsuspendedThreads(
&ReportIfNotSuspended, &threads);
}
#endif // !SANITIZER_FUCHSIA
static void CheckForLeaksCallback(const SuspendedThreadsList &suspended_threads,
void *arg) {
CheckForLeaksParam *param = reinterpret_cast<CheckForLeaksParam *>(arg);

View File

@ -40,7 +40,7 @@
#elif defined(__arm__) && \
SANITIZER_LINUX && !SANITIZER_ANDROID
#define CAN_SANITIZE_LEAKS 1
#elif SANITIZER_NETBSD
#elif SANITIZER_NETBSD || SANITIZER_FUCHSIA
#define CAN_SANITIZE_LEAKS 1
#else
#define CAN_SANITIZE_LEAKS 0
@ -223,6 +223,7 @@ ThreadRegistry *GetThreadRegistryLocked();
bool GetThreadRangesLocked(tid_t os_id, uptr *stack_begin, uptr *stack_end,
uptr *tls_begin, uptr *tls_end, uptr *cache_begin,
uptr *cache_end, DTLS **dtls);
void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr> *caches);
void ForEachExtraStackRange(tid_t os_id, RangeIteratorCallback callback,
void *arg);
// If called from the main thread, updates the main thread's TID in the thread

View File

@ -0,0 +1,166 @@
//=-- lsan_common_fuchsia.cpp --------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===---------------------------------------------------------------------===//
//
// This file is a part of LeakSanitizer.
// Implementation of common leak checking functionality. Fuchsia-specific code.
//
//===---------------------------------------------------------------------===//
#include "lsan_common.h"
#include "sanitizer_common/sanitizer_platform.h"
#if CAN_SANITIZE_LEAKS && SANITIZER_FUCHSIA
#include <zircon/sanitizer.h>
#include "lsan_allocator.h"
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_thread_registry.h"
// Ensure that the Zircon system ABI is linked in.
#pragma comment(lib, "zircon")
namespace __lsan {
void InitializePlatformSpecificModules() {}
LoadedModule *GetLinker() { return nullptr; }
__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--;
}
// There is nothing left to do after the globals callbacks.
void ProcessGlobalRegions(Frontier *frontier) {}
// Nothing to do here.
void ProcessPlatformSpecificAllocations(Frontier *frontier) {}
// On Fuchsia, we can intercept _Exit gracefully, and return a failing exit
// code if required at that point. Calling Die() here is undefined
// behavior and causes rare race conditions.
void HandleLeaks() {}
int ExitHook(int status) {
return status == 0 && HasReportedLeaks() ? common_flags()->exitcode : status;
}
void LockStuffAndStopTheWorld(StopTheWorldCallback callback,
CheckForLeaksParam *argument) {
LockThreadRegistry();
LockAllocator();
struct Params {
InternalMmapVector<uptr> allocator_caches;
StopTheWorldCallback callback;
CheckForLeaksParam *argument;
} params = {{}, callback, argument};
// Callback from libc for globals (data/bss modulo relro), when enabled.
auto globals = +[](void *chunk, size_t size, void *data) {
auto params = static_cast<const Params *>(data);
uptr begin = reinterpret_cast<uptr>(chunk);
uptr end = begin + size;
ScanGlobalRange(begin, end, &params->argument->frontier);
};
// Callback from libc for thread stacks.
auto stacks = +[](void *chunk, size_t size, void *data) {
auto params = static_cast<const Params *>(data);
uptr begin = reinterpret_cast<uptr>(chunk);
uptr end = begin + size;
ScanRangeForPointers(begin, end, &params->argument->frontier, "STACK",
kReachable);
};
// Callback from libc for thread registers.
auto registers = +[](void *chunk, size_t size, void *data) {
auto params = static_cast<const Params *>(data);
uptr begin = reinterpret_cast<uptr>(chunk);
uptr end = begin + size;
ScanRangeForPointers(begin, end, &params->argument->frontier, "REGISTERS",
kReachable);
};
if (flags()->use_tls) {
// Collect the allocator cache range from each thread so these
// can all be excluded from the reported TLS ranges.
GetAllThreadAllocatorCachesLocked(&params.allocator_caches);
__sanitizer::Sort(params.allocator_caches.data(),
params.allocator_caches.size());
}
// Callback from libc for TLS regions. This includes thread_local
// variables as well as C11 tss_set and POSIX pthread_setspecific.
auto tls = +[](void *chunk, size_t size, void *data) {
auto params = static_cast<const Params *>(data);
uptr begin = reinterpret_cast<uptr>(chunk);
uptr end = begin + size;
auto i = __sanitizer::InternalLowerBound(params->allocator_caches, 0,
params->allocator_caches.size(),
begin, CompareLess<uptr>());
if (i < params->allocator_caches.size() &&
params->allocator_caches[i] >= begin &&
end - params->allocator_caches[i] <= sizeof(AllocatorCache)) {
// Split the range in two and omit the allocator cache within.
ScanRangeForPointers(begin, params->allocator_caches[i],
&params->argument->frontier, "TLS", kReachable);
uptr begin2 = params->allocator_caches[i] + sizeof(AllocatorCache);
ScanRangeForPointers(begin2, end, &params->argument->frontier, "TLS",
kReachable);
} else {
ScanRangeForPointers(begin, end, &params->argument->frontier, "TLS",
kReachable);
}
};
// This stops the world and then makes callbacks for various memory regions.
// The final callback is the last thing before the world starts up again.
__sanitizer_memory_snapshot(
flags()->use_globals ? globals : nullptr,
flags()->use_stacks ? stacks : nullptr,
flags()->use_registers ? registers : nullptr,
flags()->use_tls ? tls : nullptr,
[](zx_status_t, void *data) {
auto params = static_cast<const Params *>(data);
// We don't use the thread registry at all for enumerating the threads
// and their stacks, registers, and TLS regions. So use it separately
// just for the allocator cache, and to call ForEachExtraStackRange,
// which ASan needs.
if (flags()->use_stacks) {
GetThreadRegistryLocked()->RunCallbackForEachThreadLocked(
[](ThreadContextBase *tctx, void *arg) {
ForEachExtraStackRange(tctx->os_id, ForEachExtraStackRangeCb,
arg);
},
&params->argument->frontier);
}
params->callback({}, params->argument);
},
&params);
UnlockAllocator();
UnlockThreadRegistry();
}
} // namespace __lsan
// This is declared (in extern "C") by <zircon/sanitizer.h>.
// _Exit calls this directly to intercept and change the status value.
int __sanitizer_process_exit_hook(int status) {
return __lsan::ExitHook(status);
}
#endif

View File

@ -0,0 +1,123 @@
//=-- lsan_fuchsia.cpp ---------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===---------------------------------------------------------------------===//
//
// This file is a part of LeakSanitizer.
// Standalone LSan RTL code specific to Fuchsia.
//
//===---------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_platform.h"
#if SANITIZER_FUCHSIA
#include <zircon/sanitizer.h>
#include "lsan.h"
#include "lsan_allocator.h"
using namespace __lsan;
namespace __lsan {
void LsanOnDeadlySignal(int signo, void *siginfo, void *context) {}
ThreadContext::ThreadContext(int tid) : ThreadContextLsanBase(tid) {}
struct OnCreatedArgs {
uptr stack_begin, stack_end;
};
// On Fuchsia, the stack bounds of a new thread are available before
// the thread itself has started running.
void ThreadContext::OnCreated(void *arg) {
// Stack bounds passed through from __sanitizer_before_thread_create_hook
// or InitializeMainThread.
auto args = reinterpret_cast<const OnCreatedArgs *>(arg);
stack_begin_ = args->stack_begin;
stack_end_ = args->stack_end;
}
struct OnStartedArgs {
uptr cache_begin, cache_end;
};
void ThreadContext::OnStarted(void *arg) {
auto args = reinterpret_cast<const OnStartedArgs *>(arg);
cache_begin_ = args->cache_begin;
cache_end_ = args->cache_end;
}
void ThreadStart(u32 tid) {
OnStartedArgs args;
GetAllocatorCacheRange(&args.cache_begin, &args.cache_end);
CHECK_EQ(args.cache_end - args.cache_begin, sizeof(AllocatorCache));
ThreadContextLsanBase::ThreadStart(tid, GetTid(), ThreadType::Regular, &args);
}
void InitializeMainThread() {
OnCreatedArgs args;
__sanitizer::GetThreadStackTopAndBottom(true, &args.stack_end,
&args.stack_begin);
u32 tid = ThreadCreate(0, GetThreadSelf(), true, &args);
CHECK_EQ(tid, 0);
ThreadStart(tid);
}
void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr> *caches) {
GetThreadRegistryLocked()->RunCallbackForEachThreadLocked(
[](ThreadContextBase *tctx, void *arg) {
auto ctx = static_cast<ThreadContext *>(tctx);
static_cast<decltype(caches)>(arg)->push_back(ctx->cache_begin());
},
caches);
}
} // namespace __lsan
// These are declared (in extern "C") by <zircon/sanitizer.h>.
// The system runtime will call our definitions directly.
// This is called before each thread creation is attempted. So, in
// its first call, the calling thread is the initial and sole thread.
void *__sanitizer_before_thread_create_hook(thrd_t thread, bool detached,
const char *name, void *stack_base,
size_t stack_size) {
uptr user_id = reinterpret_cast<uptr>(thread);
ENSURE_LSAN_INITED;
EnsureMainThreadIDIsCorrect();
OnCreatedArgs args;
args.stack_begin = reinterpret_cast<uptr>(stack_base);
args.stack_end = args.stack_begin + stack_size;
u32 parent_tid = GetCurrentThread();
u32 tid = ThreadCreate(parent_tid, user_id, detached, &args);
return reinterpret_cast<void *>(static_cast<uptr>(tid));
}
// This is called after creating a new thread (in the creating thread),
// with the pointer returned by __sanitizer_before_thread_create_hook (above).
void __sanitizer_thread_create_hook(void *hook, thrd_t thread, int error) {
u32 tid = static_cast<u32>(reinterpret_cast<uptr>(hook));
// On success, there is nothing to do here.
if (error != thrd_success) {
// Clean up the thread registry for the thread creation that didn't happen.
GetThreadRegistryLocked()->FinishThread(tid);
}
}
// This is called in the newly-created thread before it runs anything else,
// with the pointer returned by __sanitizer_before_thread_create_hook (above).
void __sanitizer_thread_start_hook(void *hook, thrd_t self) {
u32 tid = static_cast<u32>(reinterpret_cast<uptr>(hook));
ThreadStart(tid);
}
// Each thread runs this just before it exits,
// with the pointer returned by BeforeThreadCreateHook (above).
// All per-thread destructors have already been called.
void __sanitizer_thread_exit_hook(void *hook, thrd_t self) { ThreadFinish(); }
#endif // SANITIZER_FUCHSIA

View File

@ -0,0 +1,35 @@
//=-- lsan_fuchsia.h ---------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===---------------------------------------------------------------------===//
//
// This file is a part of LeakSanitizer.
// Standalone LSan RTL code specific to Fuchsia.
//
//===---------------------------------------------------------------------===//
#ifndef LSAN_FUCHSIA_H
#define LSAN_FUCHSIA_H
#include "lsan_thread.h"
#include "sanitizer_common/sanitizer_platform.h"
#if !SANITIZER_FUCHSIA
#error "lsan_fuchsia.h is used only on Fuchsia systems (SANITIZER_FUCHSIA)"
#endif
namespace __lsan {
class ThreadContext : public ThreadContextLsanBase {
public:
explicit ThreadContext(int tid);
void OnCreated(void *arg) override;
void OnStarted(void *arg) override;
};
} // namespace __lsan
#endif // LSAN_FUCHSIA_H

View File

@ -63,6 +63,9 @@ INTERCEPTOR(void, free, void *p) {
}
INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
// This hack is not required for Fuchsia because there are no dlsym calls
// involved in setting up interceptors.
#if !SANITIZER_FUCHSIA
if (lsan_init_is_running) {
// Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym.
const uptr kCallocPoolSize = 1024;
@ -74,6 +77,7 @@ INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
CHECK(allocated < kCallocPoolSize);
return mem;
}
#endif // !SANITIZER_FUCHSIA
ENSURE_LSAN_INITED;
GET_STACK_TRACE_MALLOC;
return lsan_calloc(nmemb, size, stack);
@ -102,7 +106,7 @@ INTERCEPTOR(void*, valloc, uptr size) {
GET_STACK_TRACE_MALLOC;
return lsan_valloc(size, stack);
}
#endif
#endif // !SANITIZER_MAC
#if SANITIZER_INTERCEPT_MEMALIGN
INTERCEPTOR(void*, memalign, uptr alignment, uptr size) {
@ -309,7 +313,7 @@ INTERCEPTOR(void, _ZdaPvRKSt9nothrow_t, void *ptr, std::nothrow_t const&)
///// Thread initialization and finalization. /////
#if !SANITIZER_NETBSD && !SANITIZER_FREEBSD
#if !SANITIZER_NETBSD && !SANITIZER_FREEBSD && !SANITIZER_FUCHSIA
static unsigned g_thread_finalize_key;
static void thread_finalize(void *v) {
@ -396,6 +400,8 @@ INTERCEPTOR(char *, strerror, int errnum) {
#define LSAN_MAYBE_INTERCEPT_STRERROR
#endif
#if SANITIZER_POSIX
struct ThreadParam {
void *(*callback)(void *arg);
void *param;
@ -478,9 +484,13 @@ INTERCEPTOR(void, _exit, int status) {
#define COMMON_INTERCEPT_FUNCTION(name) INTERCEPT_FUNCTION(name)
#include "sanitizer_common/sanitizer_signal_interceptors.inc"
#endif // SANITIZER_POSIX
namespace __lsan {
void InitializeInterceptors() {
// Fuchsia doesn't use interceptors that require any setup.
#if !SANITIZER_FUCHSIA
InitializeSignalInterceptors();
INTERCEPT_FUNCTION(malloc);
@ -516,6 +526,8 @@ void InitializeInterceptors() {
Die();
}
#endif
#endif // !SANITIZER_FUCHSIA
}
} // namespace __lsan

View File

@ -6,13 +6,13 @@
//
//===----------------------------------------------------------------------===//
//
// This file is a part of LeakSanitizer. Linux/NetBSD-specific code.
// This file is a part of LeakSanitizer. Linux/NetBSD/Fuchsia-specific code.
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_platform.h"
#if SANITIZER_LINUX || SANITIZER_NETBSD
#if SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_FUCHSIA
#include "lsan_allocator.h"
@ -29,4 +29,4 @@ void ReplaceSystemMalloc() {}
} // namespace __lsan
#endif // SANITIZER_LINUX || SANITIZER_NETBSD
#endif // SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_FUCHSIA

View File

@ -35,6 +35,7 @@ set(SANITIZER_SOURCES_NOTERMINATION
sanitizer_procmaps_solaris.cpp
sanitizer_rtems.cpp
sanitizer_solaris.cpp
sanitizer_stoptheworld_fuchsia.cpp
sanitizer_stoptheworld_mac.cpp
sanitizer_suppressions.cpp
sanitizer_tls_get_addr.cpp

View File

@ -0,0 +1,42 @@
//===-- sanitizer_stoptheworld_fuchsia.cpp -------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===---------------------------------------------------------------------===//
//
// See sanitizer_stoptheworld.h for details.
//
//===---------------------------------------------------------------------===//
#include "sanitizer_platform.h"
#if SANITIZER_FUCHSIA
#include <zircon/sanitizer.h>
#include "sanitizer_stoptheworld.h"
namespace __sanitizer {
// The Fuchsia implementation stops the world but doesn't offer a real
// SuspendedThreadsList argument. This is enough for ASan's use case,
// and LSan does not use this API on Fuchsia.
void StopTheWorld(StopTheWorldCallback callback, void *argument) {
struct Params {
StopTheWorldCallback callback;
void *argument;
} params = {callback, argument};
__sanitizer_memory_snapshot(
nullptr, nullptr, nullptr, nullptr,
[](zx_status_t, void *data) {
auto params = reinterpret_cast<Params *>(data);
params->callback({}, params->argument);
},
&params);
}
} // namespace __sanitizer
#endif // SANITIZER_FUCHSIA