[sanitizer] Handle Die() in StopTheWorld.

Handle calls to Die() from the tracer thread. Fixes a bug where a CHECK
could fail in the tracer thread, resulting in a call to AsanDie. The tracer
thread then exited and the parent process continued execution despite its
address space being in an unusable state.

llvm-svn: 189216
This commit is contained in:
Sergey Matveev 2013-08-26 13:20:31 +00:00
parent 0a2b6290f1
commit ef7db73e11
3 changed files with 82 additions and 42 deletions

View File

@ -37,11 +37,15 @@ static char report_path_prefix[4096]; // Set via __sanitizer_set_report_path.
// child thread will be different from |report_fd_pid|.
static uptr report_fd_pid = 0;
static void (*DieCallback)(void);
void SetDieCallback(void (*callback)(void)) {
static DieCallbackType DieCallback;
void SetDieCallback(DieCallbackType callback) {
DieCallback = callback;
}
DieCallbackType GetDieCallback() {
return DieCallback;
}
void NORETURN Die() {
if (DieCallback) {
DieCallback();

View File

@ -165,7 +165,9 @@ bool SanitizerGetThreadName(char *name, int max_len);
// Specific tools may override behavior of "Die" and "CheckFailed" functions
// to do tool-specific job.
void SetDieCallback(void (*callback)(void));
typedef void (*DieCallbackType)(void);
void SetDieCallback(DieCallbackType);
DieCallbackType GetDieCallback();
typedef void (*CheckFailedCallbackType)(const char *, int, const char *,
u64, u64);
void SetCheckFailedCallback(CheckFailedCallbackType callback);

View File

@ -191,6 +191,8 @@ struct TracerThreadArgument {
BlockingMutex mutex;
};
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 *) {
if (thread_suspender_instance != NULL) {
@ -202,6 +204,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;
@ -214,6 +229,8 @@ static int TracerThread(void* argument) {
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;
@ -283,40 +300,67 @@ NOINLINE static void WipeStack() {
internal_memset(arr, 0, sizeof(arr));
}
// We have a limitation on the stack frame size, so some stuff had to be moved
// into globals.
static sigset_t blocked_sigset;
static sigset_t old_sigset;
static struct sigaction old_sigactions[ARRAY_SIZE(kUnblockedSignals)];
void StopTheWorld(StopTheWorldCallback callback, void *argument) {
// Glibc's sigaction() has a side-effect where it copies garbage stack values
// into oldact, which can cause false negatives in LSan. As a quick workaround
// we zero some stack space here.
WipeStack();
// 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() {
// Glibc's sigaction() has a side-effect where it copies garbage stack values
// into oldact, which can cause false negatives in LSan. As a quick workaround
// we zero some stack space here.
WipeStack();
// 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]);
}
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.
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++) {
sigaction(kUnblockedSignals[signal_index],
&old_sigactions[signal_index], NULL);
}
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;
@ -349,16 +393,6 @@ void StopTheWorld(StopTheWorldCallback callback, void *argument) {
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);
}
sigprocmask(SIG_SETMASK, &old_sigset, &old_sigset);
}
// Platform-specific methods from SuspendedThreadsList.