Skip to content

Commit d59e342

Browse files
frobtechpetrhosek
authored andcommitted
[lsan] Support LeakSanitizer runtime on Fuchsia
Support LeakSanitizer runtime on Fuchsia. Patch By: mcgrathr Differential Revision: https://reviews.llvm.org/D72887
1 parent b276dec commit d59e342

15 files changed

+417
-20
lines changed

compiler-rt/cmake/config-ix.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ else()
640640
endif()
641641

642642
if (COMPILER_RT_HAS_SANITIZER_COMMON AND LSAN_SUPPORTED_ARCH AND
643-
OS_NAME MATCHES "Darwin|Linux|NetBSD")
643+
OS_NAME MATCHES "Darwin|Linux|NetBSD|Fuchsia")
644644
set(COMPILER_RT_HAS_LSAN TRUE)
645645
else()
646646
set(COMPILER_RT_HAS_LSAN FALSE)

compiler-rt/lib/asan/asan_thread.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,8 @@ bool GetThreadRangesLocked(tid_t os_id, uptr *stack_begin, uptr *stack_end,
480480
return true;
481481
}
482482

483+
void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr> *caches) {}
484+
483485
void ForEachExtraStackRange(tid_t os_id, RangeIteratorCallback callback,
484486
void *arg) {
485487
__asan::AsanThread *t = __asan::GetAsanThreadByOsIDLocked(os_id);

compiler-rt/lib/lsan/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ append_rtti_flag(OFF LSAN_CFLAGS)
55

66
set(LSAN_COMMON_SOURCES
77
lsan_common.cpp
8+
lsan_common_fuchsia.cpp
89
lsan_common_linux.cpp
910
lsan_common_mac.cpp
1011
)
1112

1213
set(LSAN_SOURCES
1314
lsan.cpp
1415
lsan_allocator.cpp
16+
lsan_fuchsia.cpp
1517
lsan_interceptors.cpp
1618
lsan_linux.cpp
1719
lsan_mac.cpp

compiler-rt/lib/lsan/lsan.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
#include "sanitizer_common/sanitizer_flags.h"
1717
#include "sanitizer_common/sanitizer_flag_parser.h"
18-
#include "sanitizer_common/sanitizer_stacktrace.h"
1918
#include "lsan_allocator.h"
2019
#include "lsan_common.h"
2120
#include "lsan_thread.h"
@@ -87,17 +86,6 @@ static void InitializeFlags() {
8786
__sanitizer_set_report_path(common_flags()->log_path);
8887
}
8988

90-
static void OnStackUnwind(const SignalContext &sig, const void *,
91-
BufferedStackTrace *stack) {
92-
stack->Unwind(StackTrace::GetNextInstructionPc(sig.pc), sig.bp, sig.context,
93-
common_flags()->fast_unwind_on_fatal);
94-
}
95-
96-
static void LsanOnDeadlySignal(int signo, void *siginfo, void *context) {
97-
HandleDeadlySignal(siginfo, context, GetCurrentThread(), &OnStackUnwind,
98-
nullptr);
99-
}
100-
10189
extern "C" void __lsan_init() {
10290
CHECK(!lsan_init_is_running);
10391
if (lsan_inited)

compiler-rt/lib/lsan/lsan.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "lsan_thread.h"
1515
#if SANITIZER_POSIX
1616
#include "lsan_posix.h"
17+
#elif SANITIZER_FUCHSIA
18+
#include "lsan_fuchsia.h"
1719
#endif
1820
#include "sanitizer_common/sanitizer_flags.h"
1921
#include "sanitizer_common/sanitizer_stacktrace.h"
@@ -36,6 +38,7 @@ namespace __lsan {
3638

3739
void InitializeInterceptors();
3840
void ReplaceSystemMalloc();
41+
void LsanOnDeadlySignal(int signo, void *siginfo, void *context);
3942

4043
#define ENSURE_LSAN_INITED do { \
4144
CHECK(!lsan_init_is_running); \

compiler-rt/lib/lsan/lsan_allocator.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ template <typename AddressSpaceView>
6666
using PrimaryAllocatorASVT = SizeClassAllocator32<AP32<AddressSpaceView>>;
6767
using PrimaryAllocator = PrimaryAllocatorASVT<LocalAddressSpaceView>;
6868
#elif defined(__x86_64__) || defined(__powerpc64__)
69-
# if defined(__powerpc64__)
69+
# if SANITIZER_FUCHSIA
70+
const uptr kAllocatorSpace = ~(uptr)0;
71+
const uptr kAllocatorSize = 0x40000000000ULL; // 4T.
72+
# elif defined(__powerpc64__)
7073
const uptr kAllocatorSpace = 0xa0000000000ULL;
7174
const uptr kAllocatorSize = 0x20000000000ULL; // 2T.
7275
# else

compiler-rt/lib/lsan/lsan_common.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ void ForEachExtraStackRangeCb(uptr begin, uptr end, void* arg) {
211211
ScanRangeForPointers(begin, end, frontier, "FAKE STACK", kReachable);
212212
}
213213

214+
#if SANITIZER_FUCHSIA
215+
216+
// Fuchsia handles all threads together with its own callback.
217+
static void ProcessThreads(SuspendedThreadsList const &, Frontier *) {}
218+
219+
#else
220+
214221
// Scans thread data (stacks and TLS) for heap pointers.
215222
static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
216223
Frontier *frontier) {
@@ -308,6 +315,8 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
308315
}
309316
}
310317

318+
#endif // SANITIZER_FUCHSIA
319+
311320
void ScanRootRegion(Frontier *frontier, const RootRegion &root_region,
312321
uptr region_begin, uptr region_end, bool is_readable) {
313322
uptr intersection_begin = Max(root_region.begin, region_begin);
@@ -531,6 +540,14 @@ static void ReportIfNotSuspended(ThreadContextBase *tctx, void *arg) {
531540
}
532541
}
533542

543+
#if SANITIZER_FUCHSIA
544+
545+
// Fuchsia provides a libc interface that guarantees all threads are
546+
// covered, and SuspendedThreadList is never really used.
547+
static void ReportUnsuspendedThreads(const SuspendedThreadsList &) {}
548+
549+
#else // !SANITIZER_FUCHSIA
550+
534551
static void ReportUnsuspendedThreads(
535552
const SuspendedThreadsList &suspended_threads) {
536553
InternalMmapVector<tid_t> threads(suspended_threads.ThreadCount());
@@ -543,6 +560,8 @@ static void ReportUnsuspendedThreads(
543560
&ReportIfNotSuspended, &threads);
544561
}
545562

563+
#endif // !SANITIZER_FUCHSIA
564+
546565
static void CheckForLeaksCallback(const SuspendedThreadsList &suspended_threads,
547566
void *arg) {
548567
CheckForLeaksParam *param = reinterpret_cast<CheckForLeaksParam *>(arg);

compiler-rt/lib/lsan/lsan_common.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
#elif defined(__arm__) && \
4141
SANITIZER_LINUX && !SANITIZER_ANDROID
4242
#define CAN_SANITIZE_LEAKS 1
43-
#elif SANITIZER_NETBSD
43+
#elif SANITIZER_NETBSD || SANITIZER_FUCHSIA
4444
#define CAN_SANITIZE_LEAKS 1
4545
#else
4646
#define CAN_SANITIZE_LEAKS 0
@@ -223,6 +223,7 @@ ThreadRegistry *GetThreadRegistryLocked();
223223
bool GetThreadRangesLocked(tid_t os_id, uptr *stack_begin, uptr *stack_end,
224224
uptr *tls_begin, uptr *tls_end, uptr *cache_begin,
225225
uptr *cache_end, DTLS **dtls);
226+
void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr> *caches);
226227
void ForEachExtraStackRange(tid_t os_id, RangeIteratorCallback callback,
227228
void *arg);
228229
// If called from the main thread, updates the main thread's TID in the thread
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//=-- lsan_common_fuchsia.cpp --------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===---------------------------------------------------------------------===//
8+
//
9+
// This file is a part of LeakSanitizer.
10+
// Implementation of common leak checking functionality. Fuchsia-specific code.
11+
//
12+
//===---------------------------------------------------------------------===//
13+
14+
#include "lsan_common.h"
15+
#include "sanitizer_common/sanitizer_platform.h"
16+
17+
#if CAN_SANITIZE_LEAKS && SANITIZER_FUCHSIA
18+
#include <zircon/sanitizer.h>
19+
20+
#include "lsan_allocator.h"
21+
#include "sanitizer_common/sanitizer_flags.h"
22+
#include "sanitizer_common/sanitizer_thread_registry.h"
23+
24+
// Ensure that the Zircon system ABI is linked in.
25+
#pragma comment(lib, "zircon")
26+
27+
namespace __lsan {
28+
29+
void InitializePlatformSpecificModules() {}
30+
31+
LoadedModule *GetLinker() { return nullptr; }
32+
33+
__attribute__((tls_model("initial-exec"))) THREADLOCAL int disable_counter;
34+
bool DisabledInThisThread() { return disable_counter > 0; }
35+
void DisableInThisThread() { disable_counter++; }
36+
void EnableInThisThread() {
37+
if (disable_counter == 0) {
38+
DisableCounterUnderflow();
39+
}
40+
disable_counter--;
41+
}
42+
43+
// There is nothing left to do after the globals callbacks.
44+
void ProcessGlobalRegions(Frontier *frontier) {}
45+
46+
// Nothing to do here.
47+
void ProcessPlatformSpecificAllocations(Frontier *frontier) {}
48+
49+
// On Fuchsia, we can intercept _Exit gracefully, and return a failing exit
50+
// code if required at that point. Calling Die() here is undefined
51+
// behavior and causes rare race conditions.
52+
void HandleLeaks() {}
53+
54+
int ExitHook(int status) {
55+
return status == 0 && HasReportedLeaks() ? common_flags()->exitcode : status;
56+
}
57+
58+
void LockStuffAndStopTheWorld(StopTheWorldCallback callback,
59+
CheckForLeaksParam *argument) {
60+
LockThreadRegistry();
61+
LockAllocator();
62+
63+
struct Params {
64+
InternalMmapVector<uptr> allocator_caches;
65+
StopTheWorldCallback callback;
66+
CheckForLeaksParam *argument;
67+
} params = {{}, callback, argument};
68+
69+
// Callback from libc for globals (data/bss modulo relro), when enabled.
70+
auto globals = +[](void *chunk, size_t size, void *data) {
71+
auto params = static_cast<const Params *>(data);
72+
uptr begin = reinterpret_cast<uptr>(chunk);
73+
uptr end = begin + size;
74+
ScanGlobalRange(begin, end, &params->argument->frontier);
75+
};
76+
77+
// Callback from libc for thread stacks.
78+
auto stacks = +[](void *chunk, size_t size, void *data) {
79+
auto params = static_cast<const Params *>(data);
80+
uptr begin = reinterpret_cast<uptr>(chunk);
81+
uptr end = begin + size;
82+
ScanRangeForPointers(begin, end, &params->argument->frontier, "STACK",
83+
kReachable);
84+
};
85+
86+
// Callback from libc for thread registers.
87+
auto registers = +[](void *chunk, size_t size, void *data) {
88+
auto params = static_cast<const Params *>(data);
89+
uptr begin = reinterpret_cast<uptr>(chunk);
90+
uptr end = begin + size;
91+
ScanRangeForPointers(begin, end, &params->argument->frontier, "REGISTERS",
92+
kReachable);
93+
};
94+
95+
if (flags()->use_tls) {
96+
// Collect the allocator cache range from each thread so these
97+
// can all be excluded from the reported TLS ranges.
98+
GetAllThreadAllocatorCachesLocked(&params.allocator_caches);
99+
__sanitizer::Sort(params.allocator_caches.data(),
100+
params.allocator_caches.size());
101+
}
102+
103+
// Callback from libc for TLS regions. This includes thread_local
104+
// variables as well as C11 tss_set and POSIX pthread_setspecific.
105+
auto tls = +[](void *chunk, size_t size, void *data) {
106+
auto params = static_cast<const Params *>(data);
107+
uptr begin = reinterpret_cast<uptr>(chunk);
108+
uptr end = begin + size;
109+
auto i = __sanitizer::InternalLowerBound(params->allocator_caches, 0,
110+
params->allocator_caches.size(),
111+
begin, CompareLess<uptr>());
112+
if (i < params->allocator_caches.size() &&
113+
params->allocator_caches[i] >= begin &&
114+
end - params->allocator_caches[i] <= sizeof(AllocatorCache)) {
115+
// Split the range in two and omit the allocator cache within.
116+
ScanRangeForPointers(begin, params->allocator_caches[i],
117+
&params->argument->frontier, "TLS", kReachable);
118+
uptr begin2 = params->allocator_caches[i] + sizeof(AllocatorCache);
119+
ScanRangeForPointers(begin2, end, &params->argument->frontier, "TLS",
120+
kReachable);
121+
} else {
122+
ScanRangeForPointers(begin, end, &params->argument->frontier, "TLS",
123+
kReachable);
124+
}
125+
};
126+
127+
// This stops the world and then makes callbacks for various memory regions.
128+
// The final callback is the last thing before the world starts up again.
129+
__sanitizer_memory_snapshot(
130+
flags()->use_globals ? globals : nullptr,
131+
flags()->use_stacks ? stacks : nullptr,
132+
flags()->use_registers ? registers : nullptr,
133+
flags()->use_tls ? tls : nullptr,
134+
[](zx_status_t, void *data) {
135+
auto params = static_cast<const Params *>(data);
136+
137+
// We don't use the thread registry at all for enumerating the threads
138+
// and their stacks, registers, and TLS regions. So use it separately
139+
// just for the allocator cache, and to call ForEachExtraStackRange,
140+
// which ASan needs.
141+
if (flags()->use_stacks) {
142+
GetThreadRegistryLocked()->RunCallbackForEachThreadLocked(
143+
[](ThreadContextBase *tctx, void *arg) {
144+
ForEachExtraStackRange(tctx->os_id, ForEachExtraStackRangeCb,
145+
arg);
146+
},
147+
&params->argument->frontier);
148+
}
149+
150+
params->callback({}, params->argument);
151+
},
152+
&params);
153+
154+
UnlockAllocator();
155+
UnlockThreadRegistry();
156+
}
157+
158+
} // namespace __lsan
159+
160+
// This is declared (in extern "C") by <zircon/sanitizer.h>.
161+
// _Exit calls this directly to intercept and change the status value.
162+
int __sanitizer_process_exit_hook(int status) {
163+
return __lsan::ExitHook(status);
164+
}
165+
166+
#endif

0 commit comments

Comments
 (0)