| //=-- 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_stoptheworld_fuchsia.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, ¶ms->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, ¶ms->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, ¶ms->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(¶ms.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, begin); |
| 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], |
| ¶ms->argument->frontier, "TLS", kReachable); |
| uptr begin2 = params->allocator_caches[i] + sizeof(AllocatorCache); |
| ScanRangeForPointers(begin2, end, ¶ms->argument->frontier, "TLS", |
| kReachable); |
| } else { |
| ScanRangeForPointers(begin, end, ¶ms->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); |
| }, |
| ¶ms->argument->frontier); |
| } |
| |
| params->callback(SuspendedThreadsListFuchsia(), params->argument); |
| }, |
| ¶ms); |
| |
| 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 |