Index: compiler-rt/trunk/cmake/config-ix.cmake =================================================================== --- compiler-rt/trunk/cmake/config-ix.cmake +++ compiler-rt/trunk/cmake/config-ix.cmake @@ -589,7 +589,7 @@ endif() if (COMPILER_RT_HAS_SANITIZER_COMMON AND LSAN_SUPPORTED_ARCH AND - OS_NAME MATCHES "Darwin|Linux") + OS_NAME MATCHES "Darwin|Linux|NetBSD") set(COMPILER_RT_HAS_LSAN TRUE) else() set(COMPILER_RT_HAS_LSAN FALSE) Index: compiler-rt/trunk/lib/lsan/lsan_common.h =================================================================== --- compiler-rt/trunk/lib/lsan/lsan_common.h +++ compiler-rt/trunk/lib/lsan/lsan_common.h @@ -21,8 +21,8 @@ #include "sanitizer_common/sanitizer_stoptheworld.h" #include "sanitizer_common/sanitizer_symbolizer.h" -// LeakSanitizer relies on some Glibc's internals (e.g. TLS machinery) thus -// supported for Linux only. Also, LSan doesn't like 32 bit architectures +// LeakSanitizer relies on some Glibc's internals (e.g. TLS machinery) on Linux. +// Also, LSan doesn't like 32 bit architectures // because of "small" (4 bytes) pointer size that leads to high false negative // ratio on large leaks. But we still want to have it for some 32 bit arches // (e.g. x86), see https://github.com/google/sanitizers/issues/403. @@ -40,6 +40,8 @@ #elif defined(__arm__) && \ SANITIZER_LINUX && !SANITIZER_ANDROID #define CAN_SANITIZE_LEAKS 1 +#elif SANITIZER_NETBSD +#define CAN_SANITIZE_LEAKS 1 #else #define CAN_SANITIZE_LEAKS 0 #endif Index: compiler-rt/trunk/lib/lsan/lsan_common_linux.cc =================================================================== --- compiler-rt/trunk/lib/lsan/lsan_common_linux.cc +++ compiler-rt/trunk/lib/lsan/lsan_common_linux.cc @@ -7,14 +7,15 @@ //===----------------------------------------------------------------------===// // // This file is a part of LeakSanitizer. -// Implementation of common leak checking functionality. Linux-specific code. +// Implementation of common leak checking functionality. Linux/NetBSD-specific +// code. // //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_platform.h" #include "lsan_common.h" -#if CAN_SANITIZE_LEAKS && SANITIZER_LINUX +#if CAN_SANITIZE_LEAKS && (SANITIZER_LINUX || SANITIZER_NETBSD) #include #include "sanitizer_common/sanitizer_common.h" @@ -136,4 +137,4 @@ } // namespace __lsan -#endif // CAN_SANITIZE_LEAKS && SANITIZER_LINUX +#endif Index: compiler-rt/trunk/lib/lsan/lsan_linux.cc =================================================================== --- compiler-rt/trunk/lib/lsan/lsan_linux.cc +++ compiler-rt/trunk/lib/lsan/lsan_linux.cc @@ -6,13 +6,13 @@ // //===----------------------------------------------------------------------===// // -// This file is a part of LeakSanitizer. Linux-specific code. +// This file is a part of LeakSanitizer. Linux/NetBSD-specific code. // //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_platform.h" -#if SANITIZER_LINUX +#if SANITIZER_LINUX || SANITIZER_NETBSD #include "lsan_allocator.h" @@ -29,4 +29,4 @@ } // namespace __lsan -#endif // SANITIZER_LINUX +#endif // SANITIZER_LINUX || SANITIZER_NETBSD Index: compiler-rt/trunk/lib/sanitizer_common/CMakeLists.txt =================================================================== --- compiler-rt/trunk/lib/sanitizer_common/CMakeLists.txt +++ compiler-rt/trunk/lib/sanitizer_common/CMakeLists.txt @@ -58,6 +58,7 @@ sanitizer_mac_libcdep.cc sanitizer_posix_libcdep.cc sanitizer_stoptheworld_linux_libcdep.cc + sanitizer_stoptheworld_netbsd_libcdep.cc ) set(SANITIZER_COVERAGE_SOURCES Index: compiler-rt/trunk/lib/sanitizer_common/sanitizer_linux.h =================================================================== --- compiler-rt/trunk/lib/sanitizer_common/sanitizer_linux.h +++ compiler-rt/trunk/lib/sanitizer_common/sanitizer_linux.h @@ -67,6 +67,9 @@ #endif #elif SANITIZER_FREEBSD void internal_sigdelset(__sanitizer_sigset_t *set, int signum); +#elif SANITIZER_NETBSD +void internal_sigdelset(__sanitizer_sigset_t *set, int signum); +uptr internal_clone(int (*fn)(void *), void *child_stack, int flags, void *arg); #endif // SANITIZER_LINUX // This class reads thread IDs from /proc//task using only syscalls. Index: compiler-rt/trunk/lib/sanitizer_common/sanitizer_netbsd.cc =================================================================== --- compiler-rt/trunk/lib/sanitizer_common/sanitizer_netbsd.cc +++ compiler-rt/trunk/lib/sanitizer_common/sanitizer_netbsd.cc @@ -245,10 +245,9 @@ return _REAL(__clock_gettime50, clk_id, tp); } -uptr internal_ptrace(int request, int pid, void *addr, void *data) { - Printf("internal_ptrace not implemented for NetBSD"); - Die(); - return 0; +uptr internal_ptrace(int request, int pid, void *addr, int data) { + DEFINE__REAL(int, ptrace, int a, int b, void *c, int d); + return _REAL(ptrace, request, pid, addr, data); } uptr internal_waitpid(int pid, int *status, int options) { @@ -322,11 +321,16 @@ (void)_REAL(__sigemptyset14, set); } -uptr intrnal_clone(int (*fn)(void *), void *child_stack, int flags, void *arg, - int *parent_tidptr, void *newtls, int *child_tidptr) { - Printf("internal_clone not implemented for NetBSD"); - Die(); - return 0; +void internal_sigdelset(__sanitizer_sigset_t *set, int signo) { + DEFINE__REAL(int, __sigdelset14, const void *a, int b); + (void)_REAL(__sigdelset14, set, signo); +} + +uptr internal_clone(int (*fn)(void *), void *child_stack, int flags, + void *arg) { + DEFINE__REAL(int, clone, int (*a)(void *b), void *c, int d, void *e); + + return _REAL(clone, fn, child_stack, flags, arg); } } // namespace __sanitizer Index: compiler-rt/trunk/lib/sanitizer_common/sanitizer_posix.h =================================================================== --- compiler-rt/trunk/lib/sanitizer_common/sanitizer_posix.h +++ compiler-rt/trunk/lib/sanitizer_common/sanitizer_posix.h @@ -55,7 +55,11 @@ uptr internal_rename(const char *oldpath, const char *newpath); uptr internal_lseek(fd_t fd, OFF_T offset, int whence); +#if SANITIZER_NETBSD +uptr internal_ptrace(int request, int pid, void *addr, int data); +#else uptr internal_ptrace(int request, int pid, void *addr, void *data); +#endif uptr internal_waitpid(int pid, int *status, int options); int internal_fork(); Index: compiler-rt/trunk/lib/sanitizer_common/sanitizer_stoptheworld_netbsd_libcdep.cc =================================================================== --- compiler-rt/trunk/lib/sanitizer_common/sanitizer_stoptheworld_netbsd_libcdep.cc +++ compiler-rt/trunk/lib/sanitizer_common/sanitizer_stoptheworld_netbsd_libcdep.cc @@ -0,0 +1,356 @@ +//===-- sanitizer_stoptheworld_netbsd_libcdep.cc --------------------------===// +// +// 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. +// This implementation was inspired by Markus Gutschke's linuxthreads.cc. +// +// This is a NetBSD variation of Linux stoptheworld implementation +// See sanitizer_stoptheworld_linux_libcdep.cc for code comments. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_platform.h" + +#if SANITIZER_NETBSD + +#include "sanitizer_stoptheworld.h" + +#include "sanitizer_atomic.h" +#include "sanitizer_platform_limits_posix.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define internal_sigaction_norestorer internal_sigaction + +#include "sanitizer_common.h" +#include "sanitizer_flags.h" +#include "sanitizer_libc.h" +#include "sanitizer_linux.h" +#include "sanitizer_mutex.h" +#include "sanitizer_placement_new.h" + +namespace __sanitizer { + +class SuspendedThreadsListNetBSD : public SuspendedThreadsList { + public: + SuspendedThreadsListNetBSD() { thread_ids_.reserve(1024); } + + tid_t GetThreadID(uptr index) const; + uptr ThreadCount() const; + bool ContainsTid(tid_t thread_id) const; + void Append(tid_t tid); + + PtraceRegistersStatus GetRegistersAndSP(uptr index, uptr *buffer, + uptr *sp) const; + uptr RegisterCount() const; + + private: + InternalMmapVector thread_ids_; +}; + +struct TracerThreadArgument { + StopTheWorldCallback callback; + void *callback_argument; + BlockingMutex mutex; + atomic_uintptr_t done; + uptr parent_pid; +}; + +class ThreadSuspender { + public: + explicit ThreadSuspender(pid_t pid, TracerThreadArgument *arg) + : arg(arg), pid_(pid) { + CHECK_GE(pid, 0); + } + bool SuspendAllThreads(); + void ResumeAllThreads(); + void KillAllThreads(); + SuspendedThreadsListNetBSD &suspended_threads_list() { + return suspended_threads_list_; + } + TracerThreadArgument *arg; + + private: + SuspendedThreadsListNetBSD suspended_threads_list_; + pid_t pid_; +}; + +void ThreadSuspender::ResumeAllThreads() { + int pterrno; + if (!internal_iserror(internal_ptrace(PT_DETACH, pid_, (void *)(uptr)1, 0), + &pterrno)) { + VReport(2, "Detached from process %d.\n", pid_); + } else { + VReport(1, "Could not detach from process %d (errno %d).\n", pid_, pterrno); + } +} + +void ThreadSuspender::KillAllThreads() { + internal_ptrace(PT_KILL, pid_, nullptr, 0); +} + +bool ThreadSuspender::SuspendAllThreads() { + int pterrno; + if (internal_iserror(internal_ptrace(PT_ATTACH, pid_, nullptr, 0), + &pterrno)) { + Printf("Could not attach to process %d (errno %d).\n", pid_, pterrno); + return false; + } + + int status; + uptr waitpid_status; + HANDLE_EINTR(waitpid_status, internal_waitpid(pid_, &status, 0)); + + VReport(2, "Attached to process %d.\n", pid_); + + struct ptrace_lwpinfo pl; + int val; + pl.pl_lwpid = 0; + while ((val = ptrace(PT_LWPINFO, pid_, (void *)&pl, sizeof(pl))) != -1 && + pl.pl_lwpid != 0) { + suspended_threads_list_.Append(pl.pl_lwpid); + VReport(2, "Appended thread %d in process %d.\n", pl.pl_lwpid, pid_); + } + return true; +} + +// Pointer to the ThreadSuspender instance for use in signal handler. +static ThreadSuspender *thread_suspender_instance = nullptr; + +// Synchronous signals that should not be blocked. +static const int kSyncSignals[] = {SIGABRT, SIGILL, SIGFPE, SIGSEGV, + SIGBUS, SIGXCPU, SIGXFSZ}; + +static void TracerThreadDieCallback() { + ThreadSuspender *inst = thread_suspender_instance; + if (inst && stoptheworld_tracer_pid == internal_getpid()) { + inst->KillAllThreads(); + thread_suspender_instance = nullptr; + } +} + +// Signal handler to wake up suspended threads when the tracer thread dies. +static void TracerThreadSignalHandler(int signum, __sanitizer_siginfo *siginfo, + void *uctx) { + SignalContext ctx(siginfo, uctx); + Printf("Tracer caught signal %d: addr=0x%zx pc=0x%zx sp=0x%zx\n", signum, + ctx.addr, ctx.pc, ctx.sp); + ThreadSuspender *inst = thread_suspender_instance; + if (inst) { + if (signum == SIGABRT) + inst->KillAllThreads(); + else + inst->ResumeAllThreads(); + RAW_CHECK(RemoveDieCallback(TracerThreadDieCallback)); + thread_suspender_instance = nullptr; + atomic_store(&inst->arg->done, 1, memory_order_relaxed); + } + internal__exit((signum == SIGABRT) ? 1 : 2); +} + +// Size of alternative stack for signal handlers in the tracer thread. +static const int kHandlerStackSize = 8192; + +// This function will be run as a cloned task. +static int TracerThread(void *argument) { + TracerThreadArgument *tracer_thread_argument = + (TracerThreadArgument *)argument; + + // Check if parent is already dead. + if (internal_getppid() != tracer_thread_argument->parent_pid) + internal__exit(4); + + // Wait for the parent thread to finish preparations. + tracer_thread_argument->mutex.Lock(); + tracer_thread_argument->mutex.Unlock(); + + RAW_CHECK(AddDieCallback(TracerThreadDieCallback)); + + ThreadSuspender thread_suspender(internal_getppid(), tracer_thread_argument); + // Global pointer for the signal handler. + thread_suspender_instance = &thread_suspender; + + // Alternate stack for signal handling. + InternalMmapVector handler_stack_memory(kHandlerStackSize); + stack_t handler_stack; + internal_memset(&handler_stack, 0, sizeof(handler_stack)); + handler_stack.ss_sp = handler_stack_memory.data(); + handler_stack.ss_size = kHandlerStackSize; + internal_sigaltstack(&handler_stack, nullptr); + + // Install our handler for synchronous signals. Other signals should be + // blocked by the mask we inherited from the parent thread. + for (uptr i = 0; i < ARRAY_SIZE(kSyncSignals); i++) { + __sanitizer_sigaction act; + internal_memset(&act, 0, sizeof(act)); + act.sigaction = TracerThreadSignalHandler; + act.sa_flags = SA_ONSTACK | SA_SIGINFO; + internal_sigaction_norestorer(kSyncSignals[i], &act, 0); + } + + int exit_code = 0; + if (!thread_suspender.SuspendAllThreads()) { + VReport(1, "Failed suspending threads.\n"); + exit_code = 3; + } else { + tracer_thread_argument->callback(thread_suspender.suspended_threads_list(), + tracer_thread_argument->callback_argument); + thread_suspender.ResumeAllThreads(); + exit_code = 0; + } + RAW_CHECK(RemoveDieCallback(TracerThreadDieCallback)); + thread_suspender_instance = nullptr; + atomic_store(&tracer_thread_argument->done, 1, memory_order_relaxed); + return exit_code; +} + +class ScopedStackSpaceWithGuard { + public: + explicit ScopedStackSpaceWithGuard(uptr stack_size) { + stack_size_ = stack_size; + guard_size_ = GetPageSizeCached(); + // FIXME: Omitting MAP_STACK here works in current kernels but might break + // in the future. + guard_start_ = + (uptr)MmapOrDie(stack_size_ + guard_size_, "ScopedStackWithGuard"); + CHECK(MprotectNoAccess((uptr)guard_start_, guard_size_)); + } + ~ScopedStackSpaceWithGuard() { + UnmapOrDie((void *)guard_start_, stack_size_ + guard_size_); + } + void *Bottom() const { + return (void *)(guard_start_ + stack_size_ + guard_size_); + } + + private: + uptr stack_size_; + uptr guard_size_; + uptr guard_start_; +}; + +static __sanitizer_sigset_t blocked_sigset; +static __sanitizer_sigset_t old_sigset; + +struct ScopedSetTracerPID { + explicit ScopedSetTracerPID(uptr tracer_pid) { + stoptheworld_tracer_pid = tracer_pid; + stoptheworld_tracer_ppid = internal_getpid(); + } + ~ScopedSetTracerPID() { + stoptheworld_tracer_pid = 0; + stoptheworld_tracer_ppid = 0; + } +}; + +void StopTheWorld(StopTheWorldCallback callback, void *argument) { + // Prepare the arguments for TracerThread. + struct TracerThreadArgument tracer_thread_argument; + tracer_thread_argument.callback = callback; + tracer_thread_argument.callback_argument = argument; + tracer_thread_argument.parent_pid = internal_getpid(); + atomic_store(&tracer_thread_argument.done, 0, memory_order_relaxed); + const uptr kTracerStackSize = 2 * 1024 * 1024; + ScopedStackSpaceWithGuard tracer_stack(kTracerStackSize); + + tracer_thread_argument.mutex.Lock(); + + internal_sigfillset(&blocked_sigset); + for (uptr i = 0; i < ARRAY_SIZE(kSyncSignals); i++) + internal_sigdelset(&blocked_sigset, kSyncSignals[i]); + int rv = internal_sigprocmask(SIG_BLOCK, &blocked_sigset, &old_sigset); + CHECK_EQ(rv, 0); + uptr tracer_pid = internal_clone(TracerThread, tracer_stack.Bottom(), + CLONE_VM | CLONE_FS | CLONE_FILES, + &tracer_thread_argument); + internal_sigprocmask(SIG_SETMASK, &old_sigset, 0); + int local_errno = 0; + if (internal_iserror(tracer_pid, &local_errno)) { + VReport(1, "Failed spawning a tracer thread (errno %d).\n", local_errno); + tracer_thread_argument.mutex.Unlock(); + } else { + ScopedSetTracerPID scoped_set_tracer_pid(tracer_pid); + + tracer_thread_argument.mutex.Unlock(); + + while (atomic_load(&tracer_thread_argument.done, memory_order_relaxed) == 0) + sched_yield(); + + for (;;) { + uptr waitpid_status = internal_waitpid(tracer_pid, nullptr, __WALL); + if (!internal_iserror(waitpid_status, &local_errno)) + break; + if (local_errno == EINTR) + continue; + VReport(1, "Waiting on the tracer thread failed (errno %d).\n", + local_errno); + break; + } + } +} + +tid_t SuspendedThreadsListNetBSD::GetThreadID(uptr index) const { + CHECK_LT(index, thread_ids_.size()); + return thread_ids_[index]; +} + +uptr SuspendedThreadsListNetBSD::ThreadCount() const { + return thread_ids_.size(); +} + +bool SuspendedThreadsListNetBSD::ContainsTid(tid_t thread_id) const { + for (uptr i = 0; i < thread_ids_.size(); i++) { + if (thread_ids_[i] == thread_id) + return true; + } + return false; +} + +void SuspendedThreadsListNetBSD::Append(tid_t tid) { + thread_ids_.push_back(tid); +} + +PtraceRegistersStatus SuspendedThreadsListNetBSD::GetRegistersAndSP( + uptr index, uptr *buffer, uptr *sp) const { + lwpid_t tid = GetThreadID(index); + pid_t ppid = internal_getppid(); + struct reg regs; + int pterrno; + bool isErr = + internal_iserror(internal_ptrace(PT_GETREGS, ppid, ®s, tid), &pterrno); + if (isErr) { + VReport(1, + "Could not get registers from process %d thread %d (errno %d).\n", + ppid, tid, pterrno); + return pterrno == ESRCH ? REGISTERS_UNAVAILABLE_FATAL + : REGISTERS_UNAVAILABLE; + } + + *sp = PTRACE_REG_SP(®s); + internal_memcpy(buffer, ®s, sizeof(regs)); + + return REGISTERS_AVAILABLE; +} + +uptr SuspendedThreadsListNetBSD::RegisterCount() const { + return sizeof(struct reg) / sizeof(uptr); +} +} // namespace __sanitizer + +#endif Index: compiler-rt/trunk/lib/tsan/go/buildgo.sh =================================================================== --- compiler-rt/trunk/lib/tsan/go/buildgo.sh +++ compiler-rt/trunk/lib/tsan/go/buildgo.sh @@ -52,6 +52,7 @@ ../../sanitizer_common/sanitizer_linux.cc ../../sanitizer_common/sanitizer_linux_libcdep.cc ../../sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc + ../../sanitizer_common/sanitizer_stoptheworld_netbsd_libcdep.cc " if [ "`uname -a | grep ppc64le`" != "" ]; then SUFFIX="linux_ppc64le" @@ -79,6 +80,7 @@ ../../sanitizer_common/sanitizer_linux.cc ../../sanitizer_common/sanitizer_linux_libcdep.cc ../../sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc + ../../sanitizer_common/sanitizer_stoptheworld_netbsd_libcdep.cc " elif [ "`uname -a | grep NetBSD`" != "" ]; then SUFFIX="netbsd_amd64" @@ -96,6 +98,7 @@ ../../sanitizer_common/sanitizer_linux_libcdep.cc ../../sanitizer_common/sanitizer_netbsd.cc ../../sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc + ../../sanitizer_common/sanitizer_stoptheworld_netbsd_libcdep.cc " elif [ "`uname -a | grep Darwin`" != "" ]; then SUFFIX="darwin_amd64" Index: compiler-rt/trunk/test/lsan/lit.common.cfg.py =================================================================== --- compiler-rt/trunk/test/lsan/lit.common.cfg.py +++ compiler-rt/trunk/test/lsan/lit.common.cfg.py @@ -70,7 +70,8 @@ # LeakSanitizer tests are currently supported on x86-64 Linux, PowerPC64 Linux, arm Linux, mips64 Linux, and x86_64 Darwin. supported_linux = config.host_os is 'Linux' and config.host_arch in ['x86_64', 'ppc64', 'ppc64le', 'mips64', 'arm', 'armhf', 'armv7l'] supported_darwin = config.host_os is 'Darwin' and config.target_arch is 'x86_64' -if not (supported_linux or supported_darwin): +supported_netbsd = config.host_os is 'NetBSD' and config.target_arch is 'x86_64' +if not (supported_linux or supported_darwin or supported_netbsd): config.unsupported = True # Don't support Thumb due to broken fast unwinder