forked from OSchip/llvm-project
[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:
parent
0a2b6290f1
commit
ef7db73e11
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,11 +300,15 @@ 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) {
|
||||
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.
|
||||
|
@ -310,13 +331,36 @@ void StopTheWorld(StopTheWorldCallback callback, void *argument) {
|
|||
sigaction(kUnblockedSignals[signal_index], &new_sigaction,
|
||||
&old_sigactions[signal_index]);
|
||||
}
|
||||
int sigprocmask_status = sigprocmask(SIG_BLOCK, &blocked_sigset, &old_sigset);
|
||||
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)
|
||||
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();
|
||||
}
|
||||
|
||||
~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.
|
||||
|
|
Loading…
Reference in New Issue