diff options
Diffstat (limited to 'lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc')
-rw-r--r-- | lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc | 216 |
1 files changed, 134 insertions, 82 deletions
diff --git a/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc b/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc index e5284ee2211a..a34ddd872209 100644 --- a/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc +++ b/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc @@ -14,12 +14,14 @@ #include "sanitizer_platform.h" -#if SANITIZER_LINUX +#if SANITIZER_LINUX && defined(__x86_64__) #include "sanitizer_stoptheworld.h" +#include "sanitizer_platform_limits_posix.h" + #include <errno.h> -#include <sched.h> // for clone +#include <sched.h> // for CLONE_* definitions #include <stddef.h> #include <sys/prctl.h> // for PR_* definitions #include <sys/ptrace.h> // for PTRACE_* definitions @@ -31,7 +33,16 @@ #endif #include <sys/wait.h> // for signal-related stuff +#ifdef sa_handler +# undef sa_handler +#endif + +#ifdef sa_sigaction +# undef sa_sigaction +#endif + #include "sanitizer_common.h" +#include "sanitizer_flags.h" #include "sanitizer_libc.h" #include "sanitizer_linux.h" #include "sanitizer_mutex.h" @@ -47,31 +58,14 @@ // clone() interface (we want to share the address space with the caller // process, so we prefer clone() over fork()). // -// We avoid the use of libc for two reasons: +// We don't use any libc functions, relying instead on direct syscalls. There +// are two reasons for this: // 1. calling a library function while threads are suspended could cause a // deadlock, if one of the treads happens to be holding a libc lock; // 2. it's generally not safe to call libc functions from the tracer task, // because clone() does not set up a thread-local storage for it. Any // thread-local variables used by libc will be shared between the tracer task // and the thread which spawned it. -// -// We deal with this by replacing libc calls with calls to our own -// implementations defined in sanitizer_libc.h and sanitizer_linux.h. However, -// there are still some libc functions which are used here: -// -// * All of the system calls ultimately go through the libc syscall() function. -// We're operating under the assumption that syscall()'s implementation does -// not acquire any locks or use any thread-local data (except for the errno -// variable, which we handle separately). -// -// * We lack custom implementations of sigfillset() and sigaction(), so we use -// the libc versions instead. The same assumptions as above apply. -// -// * It is safe to call libc functions before the cloned thread is spawned or -// after it has exited. The following functions are used in this manner: -// sigdelset() -// sigprocmask() -// clone() COMPILER_CHECK(sizeof(SuspendedThreadID) == sizeof(pid_t)); @@ -106,10 +100,11 @@ bool ThreadSuspender::SuspendThread(SuspendedThreadID thread_id) { &pterrno)) { // Either the thread is dead, or something prevented us from attaching. // Log this event and move on. - Report("Could not attach to thread %d (errno %d).\n", thread_id, pterrno); + if (common_flags()->verbosity) + Report("Could not attach to thread %d (errno %d).\n", thread_id, pterrno); return false; } else { - if (SanitizerVerbosity > 0) + if (common_flags()->verbosity) Report("Attached to thread %d.\n", thread_id); // The thread is not guaranteed to stop before ptrace returns, so we must // wait on it. @@ -119,8 +114,9 @@ bool ThreadSuspender::SuspendThread(SuspendedThreadID thread_id) { if (internal_iserror(waitpid_status, &wperrno)) { // Got a ECHILD error. I don't think this situation is possible, but it // doesn't hurt to report it. - Report("Waiting on thread %d failed, detaching (errno %d).\n", thread_id, - wperrno); + if (common_flags()->verbosity) + Report("Waiting on thread %d failed, detaching (errno %d).\n", + thread_id, wperrno); internal_ptrace(PTRACE_DETACH, thread_id, NULL, NULL); return false; } @@ -135,13 +131,14 @@ void ThreadSuspender::ResumeAllThreads() { int pterrno; if (!internal_iserror(internal_ptrace(PTRACE_DETACH, tid, NULL, NULL), &pterrno)) { - if (SanitizerVerbosity > 0) + if (common_flags()->verbosity) Report("Detached from thread %d.\n", tid); } else { // Either the thread is dead, or we are already detached. // The latter case is possible, for instance, if this function was called // from a signal handler. - Report("Could not detach from thread %d (errno %d).\n", tid, pterrno); + if (common_flags()->verbosity) + Report("Could not detach from thread %d (errno %d).\n", tid, pterrno); } } } @@ -186,13 +183,16 @@ static const int kUnblockedSignals[] = { SIGABRT, SIGILL, SIGFPE, SIGSEGV, struct TracerThreadArgument { StopTheWorldCallback callback; void *callback_argument; - // The tracer thread waits on this mutex while the parent finished its + // The tracer thread waits on this mutex while the parent finishes its // preparations. BlockingMutex mutex; + uptr parent_pid; }; +static DieCallbackType old_die_callback; + // Signal handler to wake up suspended threads when the tracer thread dies. -void TracerThreadSignalHandler(int signum, siginfo_t *siginfo, void *) { +void TracerThreadSignalHandler(int signum, void *siginfo, void *) { if (thread_suspender_instance != NULL) { if (signum == SIGABRT) thread_suspender_instance->KillAllThreads(); @@ -202,6 +202,19 @@ void TracerThreadSignalHandler(int signum, siginfo_t *siginfo, void *) { internal__exit((signum == SIGABRT) ? 1 : 2); } +static void TracerThreadDieCallback() { + // Generally a call to Die() in the tracer thread should be fatal to the + // parent process as well, because they share the address space. + // This really only works correctly if all the threads are suspended at this + // point. So we correctly handle calls to Die() from within the callback, but + // not those that happen before or after the callback. Hopefully there aren't + // a lot of opportunities for that to happen... + if (thread_suspender_instance) + thread_suspender_instance->KillAllThreads(); + if (old_die_callback) + old_die_callback(); +} + // Size of alternative stack for signal handlers in the tracer thread. static const int kHandlerStackSize = 4096; @@ -210,10 +223,17 @@ static int TracerThread(void* argument) { TracerThreadArgument *tracer_thread_argument = (TracerThreadArgument *)argument; + internal_prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); + // 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(); + SetDieCallback(TracerThreadDieCallback); + ThreadSuspender thread_suspender(internal_getppid()); // Global pointer for the signal handler. thread_suspender_instance = &thread_suspender; @@ -230,17 +250,18 @@ static int TracerThread(void* argument) { // the mask we inherited from the caller thread. for (uptr signal_index = 0; signal_index < ARRAY_SIZE(kUnblockedSignals); signal_index++) { - struct sigaction new_sigaction; + __sanitizer_kernel_sigaction_t new_sigaction; internal_memset(&new_sigaction, 0, sizeof(new_sigaction)); - new_sigaction.sa_sigaction = TracerThreadSignalHandler; + new_sigaction.sigaction = TracerThreadSignalHandler; new_sigaction.sa_flags = SA_ONSTACK | SA_SIGINFO; - sigfillset(&new_sigaction.sa_mask); - sigaction(kUnblockedSignals[signal_index], &new_sigaction, NULL); + internal_sigfillset(&new_sigaction.sa_mask); + internal_sigaction(kUnblockedSignals[signal_index], &new_sigaction, NULL); } int exit_code = 0; if (!thread_suspender.SuspendAllThreads()) { - Report("Failed suspending threads.\n"); + if (common_flags()->verbosity) + Report("Failed suspending threads.\n"); exit_code = 3; } else { tracer_thread_argument->callback(thread_suspender.suspended_threads_list(), @@ -278,50 +299,84 @@ class ScopedStackSpaceWithGuard { uptr guard_start_; }; -static sigset_t blocked_sigset; -static sigset_t old_sigset; -static struct sigaction old_sigactions[ARRAY_SIZE(kUnblockedSignals)]; +// We have a limitation on the stack frame size, so some stuff had to be moved +// into globals. +static __sanitizer_kernel_sigset_t blocked_sigset; +static __sanitizer_kernel_sigset_t old_sigset; +static __sanitizer_kernel_sigaction_t old_sigactions + [ARRAY_SIZE(kUnblockedSignals)]; -void StopTheWorld(StopTheWorldCallback callback, void *argument) { - // Block all signals that can be blocked safely, and install default handlers - // for the remaining signals. - // We cannot allow user-defined handlers to run while the ThreadSuspender - // thread is active, because they could conceivably call some libc functions - // which modify errno (which is shared between the two threads). - sigfillset(&blocked_sigset); - for (uptr signal_index = 0; signal_index < ARRAY_SIZE(kUnblockedSignals); - signal_index++) { - // Remove the signal from the set of blocked signals. - sigdelset(&blocked_sigset, kUnblockedSignals[signal_index]); - // Install the default handler. - struct sigaction new_sigaction; - internal_memset(&new_sigaction, 0, sizeof(new_sigaction)); - new_sigaction.sa_handler = SIG_DFL; - sigfillset(&new_sigaction.sa_mask); - sigaction(kUnblockedSignals[signal_index], &new_sigaction, - &old_sigactions[signal_index]); +class StopTheWorldScope { + public: + StopTheWorldScope() { + // Block all signals that can be blocked safely, and install + // default handlers for the remaining signals. + // We cannot allow user-defined handlers to run while the ThreadSuspender + // thread is active, because they could conceivably call some libc functions + // which modify errno (which is shared between the two threads). + internal_sigfillset(&blocked_sigset); + for (uptr signal_index = 0; signal_index < ARRAY_SIZE(kUnblockedSignals); + signal_index++) { + // Remove the signal from the set of blocked signals. + internal_sigdelset(&blocked_sigset, kUnblockedSignals[signal_index]); + // Install the default handler. + __sanitizer_kernel_sigaction_t new_sigaction; + internal_memset(&new_sigaction, 0, sizeof(new_sigaction)); + new_sigaction.handler = SIG_DFL; + internal_sigfillset(&new_sigaction.sa_mask); + internal_sigaction(kUnblockedSignals[signal_index], &new_sigaction, + &old_sigactions[signal_index]); + } + int sigprocmask_status = + internal_sigprocmask(SIG_BLOCK, &blocked_sigset, &old_sigset); + CHECK_EQ(sigprocmask_status, 0); // sigprocmask should never fail + // Make this process dumpable. Processes that are not dumpable cannot be + // attached to. + process_was_dumpable_ = internal_prctl(PR_GET_DUMPABLE, 0, 0, 0, 0); + if (!process_was_dumpable_) + internal_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + old_die_callback = GetDieCallback(); } - int sigprocmask_status = sigprocmask(SIG_BLOCK, &blocked_sigset, &old_sigset); - CHECK_EQ(sigprocmask_status, 0); // sigprocmask should never fail - // Make this process dumpable. Processes that are not dumpable cannot be - // attached to. - int process_was_dumpable = internal_prctl(PR_GET_DUMPABLE, 0, 0, 0, 0); - if (!process_was_dumpable) - internal_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + + ~StopTheWorldScope() { + SetDieCallback(old_die_callback); + // Restore the dumpable flag. + if (!process_was_dumpable_) + internal_prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); + // Restore the signal handlers. + for (uptr signal_index = 0; signal_index < ARRAY_SIZE(kUnblockedSignals); + signal_index++) { + internal_sigaction(kUnblockedSignals[signal_index], + &old_sigactions[signal_index], NULL); + } + internal_sigprocmask(SIG_SETMASK, &old_sigset, &old_sigset); + } + + private: + int process_was_dumpable_; +}; + +void StopTheWorld(StopTheWorldCallback callback, void *argument) { + StopTheWorldScope in_stoptheworld; // 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(); const uptr kTracerStackSize = 2 * 1024 * 1024; ScopedStackSpaceWithGuard tracer_stack(kTracerStackSize); // Block the execution of TracerThread until after we have set ptrace // permissions. tracer_thread_argument.mutex.Lock(); - pid_t tracer_pid = clone(TracerThread, tracer_stack.Bottom(), - CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_UNTRACED, - &tracer_thread_argument, 0, 0, 0); - if (tracer_pid < 0) { - Report("Failed spawning a tracer thread (errno %d).\n", errno); + uptr tracer_pid = internal_clone( + TracerThread, tracer_stack.Bottom(), + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_UNTRACED, + &tracer_thread_argument, 0 /* parent_tidptr */, 0 /* newtls */, 0 + /* child_tidptr */); + int local_errno = 0; + if (internal_iserror(tracer_pid, &local_errno)) { + if (common_flags()->verbosity) + Report("Failed spawning a tracer thread (errno %d).\n", local_errno); tracer_thread_argument.mutex.Unlock(); } else { // On some systems we have to explicitly declare that we want to be traced @@ -336,20 +391,12 @@ void StopTheWorld(StopTheWorldCallback callback, void *argument) { // At this point, any signal will either be blocked or kill us, so waitpid // should never return (and set errno) while the tracer thread is alive. uptr waitpid_status = internal_waitpid(tracer_pid, NULL, __WALL); - int wperrno; - if (internal_iserror(waitpid_status, &wperrno)) - Report("Waiting on the tracer thread failed (errno %d).\n", wperrno); - } - // Restore the dumpable flag. - if (!process_was_dumpable) - internal_prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); - // Restore the signal handlers. - for (uptr signal_index = 0; signal_index < ARRAY_SIZE(kUnblockedSignals); - signal_index++) { - sigaction(kUnblockedSignals[signal_index], - &old_sigactions[signal_index], NULL); + if (internal_iserror(waitpid_status, &local_errno)) { + if (common_flags()->verbosity) + Report("Waiting on the tracer thread failed (errno %d).\n", + local_errno); + } } - sigprocmask(SIG_SETMASK, &old_sigset, &old_sigset); } // Platform-specific methods from SuspendedThreadsList. @@ -373,6 +420,10 @@ typedef user_regs_struct regs_struct; typedef pt_regs regs_struct; #define REG_SP gpr[PT_R1] +#elif defined(__mips__) +typedef struct user regs_struct; +#define REG_SP regs[EF_REG29] + #else #error "Unsupported architecture" #endif // SANITIZER_ANDROID && defined(__arm__) @@ -385,7 +436,8 @@ int SuspendedThreadsList::GetRegistersAndSP(uptr index, int pterrno; if (internal_iserror(internal_ptrace(PTRACE_GETREGS, tid, NULL, ®s), &pterrno)) { - Report("Could not get registers from thread %d (errno %d).\n", + if (common_flags()->verbosity) + Report("Could not get registers from thread %d (errno %d).\n", tid, pterrno); return -1; } @@ -400,4 +452,4 @@ uptr SuspendedThreadsList::RegisterCount() { } } // namespace __sanitizer -#endif // SANITIZER_LINUX +#endif // SANITIZER_LINUX && defined(__x86_64__) |