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 "lsan_thread.h"
16#include "sanitizer_common/sanitizer_platform.h"
17
18#if CAN_SANITIZE_LEAKS && SANITIZER_FUCHSIA
19#include <zircon/sanitizer.h>
20
21#include "lsan_allocator.h"
22#include "sanitizer_common/sanitizer_flags.h"
23#include "sanitizer_common/sanitizer_stoptheworld_fuchsia.h"
24#include "sanitizer_common/sanitizer_thread_registry.h"
25
26// Ensure that the Zircon system ABI is linked in.
27#pragma comment(lib, "zircon")
28
29namespace __lsan {
30
31void InitializePlatformSpecificModules() {}
32
33LoadedModule *GetLinker() { return nullptr; }
34
35__attribute__((tls_model("initial-exec"))) THREADLOCAL int disable_counter;
36bool DisabledInThisThread() { return disable_counter > 0; }
37void DisableInThisThread() { disable_counter++; }
38void EnableInThisThread() {
39 if (disable_counter == 0) {
40 DisableCounterUnderflow();
41 }
42 disable_counter--;
43}
44
45// There is nothing left to do after the globals callbacks.
46void ProcessGlobalRegions(Frontier *frontier) {}
47
48// Nothing to do here.
49void ProcessPlatformSpecificAllocations(Frontier *frontier) {}
50
51// On Fuchsia, we can intercept _Exit gracefully, and return a failing exit
52// code if required at that point. Calling Die() here is undefined
53// behavior and causes rare race conditions.
54void HandleLeaks() {}
55
56// This is defined differently in asan_fuchsia.cpp and lsan_fuchsia.cpp.
57bool UseExitcodeOnLeak();
58
59int ExitHook(int status) {
60 if (common_flags()->detect_leaks && common_flags()->leak_check_at_exit) {
61 if (UseExitcodeOnLeak())
62 DoLeakCheck();
63 else
64 DoRecoverableLeakCheckVoid();
65 }
66 return status == 0 && HasReportedLeaks() ? common_flags()->exitcode : status;
67}
68
69void LockStuffAndStopTheWorld(StopTheWorldCallback callback,
70 CheckForLeaksParam *argument) {
71 ScopedStopTheWorldLock lock;
72
73 struct Params {
74 InternalMmapVector<uptr> allocator_caches;
75 StopTheWorldCallback callback;
76 CheckForLeaksParam *argument;
77 } params = {{}, callback, argument};
78
79 // Callback from libc for globals (data/bss modulo relro), when enabled.
80 auto globals = +[](void *chunk, size_t size, void *data) {
81 auto params = static_cast<const Params *>(data);
82 uptr begin = reinterpret_cast<uptr>(chunk);
83 uptr end = begin + size;
84 ScanGlobalRange(begin, end, &params->argument->frontier);
85 };
86
87 // Callback from libc for thread stacks.
88 auto stacks = +[](void *chunk, size_t size, void *data) {
89 auto params = static_cast<const Params *>(data);
90 uptr begin = reinterpret_cast<uptr>(chunk);
91 uptr end = begin + size;
92 ScanRangeForPointers(begin, end, &params->argument->frontier, "STACK",
93 kReachable);
94 };
95
96 // Callback from libc for thread registers.
97 auto registers = +[](void *chunk, size_t size, void *data) {
98 auto params = static_cast<const Params *>(data);
99 uptr begin = reinterpret_cast<uptr>(chunk);
100 uptr end = begin + size;
101 ScanRangeForPointers(begin, end, &params->argument->frontier, "REGISTERS",
102 kReachable);
103 };
104
105 if (flags()->use_tls) {
106 // Collect the allocator cache range from each thread so these
107 // can all be excluded from the reported TLS ranges.
108 GetAllThreadAllocatorCachesLocked(&params.allocator_caches);
109 __sanitizer::Sort(params.allocator_caches.data(),
110 params.allocator_caches.size());
111 }
112
113 // Callback from libc for TLS regions. This includes thread_local
114 // variables as well as C11 tss_set and POSIX pthread_setspecific.
115 auto tls = +[](void *chunk, size_t size, void *data) {
116 auto params = static_cast<const Params *>(data);
117 uptr begin = reinterpret_cast<uptr>(chunk);
118 uptr end = begin + size;
119 auto i = __sanitizer::InternalLowerBound(params->allocator_caches, begin);
120 if (i < params->allocator_caches.size() &&
121 params->allocator_caches[i] >= begin &&
122 params->allocator_caches[i] <= end &&
123 end - params->allocator_caches[i] >= sizeof(AllocatorCache)) {
124 // Split the range in two and omit the allocator cache within.
125 ScanRangeForPointers(begin, params->allocator_caches[i],
126 &params->argument->frontier, "TLS", kReachable);
127 uptr begin2 = params->allocator_caches[i] + sizeof(AllocatorCache);
128 ScanRangeForPointers(begin2, end, &params->argument->frontier, "TLS",
129 kReachable);
130 } else {
131 ScanRangeForPointers(begin, end, &params->argument->frontier, "TLS",
132 kReachable);
133 }
134 };
135
136 // This stops the world and then makes callbacks for various memory regions.
137 // The final callback is the last thing before the world starts up again.
138 __sanitizer_memory_snapshot(
139 flags()->use_globals ? globals : nullptr,
140 flags()->use_stacks ? stacks : nullptr,
141 flags()->use_registers ? registers : nullptr,
142 flags()->use_tls ? tls : nullptr,
143 [](zx_status_t, void *data) {
144 auto params = static_cast<const Params *>(data);
145
146 // We don't use the thread registry at all for enumerating the threads
147 // and their stacks, registers, and TLS regions. So use it separately
148 // just for the allocator cache, and to call ScanExtraStackRanges,
149 // which ASan needs.
150 if (flags()->use_stacks) {
151 InternalMmapVector<Range> ranges;
152 GetThreadExtraStackRangesLocked(&ranges);
153 ScanExtraStackRanges(ranges, &params->argument->frontier);
154 }
155 params->callback(SuspendedThreadsListFuchsia(), params->argument);
156 },
157 &params);
158}
159
160} // namespace __lsan
161
162// This is declared (in extern "C") by <zircon/sanitizer.h>.
163// _Exit calls this directly to intercept and change the status value.
164int __sanitizer_process_exit_hook(int status) {
165 return __lsan::ExitHook(status);
166}
167
168#endif
169