forked from OSchip/llvm-project
[esan] Intercept and chain signal handlers
Summary: In preparation for fault-based shadow memory iteration, we add support for our own signal handler by adding app signal handler interception as well as chaining for SIGSEGV. This is done in a simple manner: we do not honor the app's alternate stack nor any sigaction flags for SIGSEGV. Adds a new test of transparency in app signal handling. Reviewers: aizatsky Subscribers: filcab, kubabrecka, vitalybuka, zhaoqin, kcc, eugenis, llvm-commits Differential Revision: http://reviews.llvm.org/D20577 llvm-svn: 271272
This commit is contained in:
parent
16c24f4d6e
commit
8e74c1084b
|
@ -14,7 +14,8 @@ set(ESAN_SOURCES
|
|||
esan_interceptors.cpp
|
||||
esan_linux.cpp
|
||||
cache_frag.cpp
|
||||
working_set.cpp)
|
||||
working_set.cpp
|
||||
working_set_posix.cpp)
|
||||
|
||||
foreach (arch ${ESAN_SUPPORTED_ARCH})
|
||||
add_compiler_rt_runtime(clang_rt.esan
|
||||
|
|
|
@ -73,6 +73,18 @@ void processRangeAccess(uptr PC, uptr Addr, int Size, bool IsWrite) {
|
|||
}
|
||||
}
|
||||
|
||||
bool processSignal(int SigNum, void (*Handler)(int), void (**Result)(int)) {
|
||||
if (WhichTool == ESAN_WorkingSet)
|
||||
return processWorkingSetSignal(SigNum, Handler, Result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processSigaction(int SigNum, const void *Act, void *OldAct) {
|
||||
if (WhichTool == ESAN_WorkingSet)
|
||||
return processWorkingSetSigaction(SigNum, Act, OldAct);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if SANITIZER_DEBUG
|
||||
static bool verifyShadowScheme() {
|
||||
// Sanity checks for our shadow mapping scheme.
|
||||
|
|
|
@ -49,6 +49,9 @@ void initializeInterceptors();
|
|||
void verifyAddressSpace();
|
||||
bool fixMmapAddr(void **Addr, SIZE_T Size, int Flags);
|
||||
uptr checkMmapResult(uptr Addr, SIZE_T Size);
|
||||
// The return value indicates whether to call the real version or not.
|
||||
bool processSignal(int SigNum, void (*Handler)(int), void (**Result)(int));
|
||||
bool processSigaction(int SigNum, const void *Act, void *OldAct);
|
||||
|
||||
} // namespace __esan
|
||||
|
||||
|
|
|
@ -351,6 +351,43 @@ INTERCEPTOR(void *, mmap64, void *addr, SIZE_T sz, int prot, int flags,
|
|||
#define ESAN_MAYBE_INTERCEPT_MMAP64
|
||||
#endif
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Signal-related interceptors
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if SANITIZER_LINUX
|
||||
typedef void (*signal_handler_t)(int);
|
||||
INTERCEPTOR(signal_handler_t, signal, int signum, signal_handler_t handler) {
|
||||
void *ctx;
|
||||
COMMON_INTERCEPTOR_ENTER(ctx, signal, signum, handler);
|
||||
signal_handler_t result;
|
||||
if (!processSignal(signum, handler, &result))
|
||||
return result;
|
||||
else
|
||||
return REAL(signal)(signum, handler);
|
||||
}
|
||||
#define ESAN_MAYBE_INTERCEPT_SIGNAL INTERCEPT_FUNCTION(signal)
|
||||
#else
|
||||
#error Platform not supported
|
||||
#define ESAN_MAYBE_INTERCEPT_SIGNAL
|
||||
#endif
|
||||
|
||||
#if SANITIZER_LINUX
|
||||
INTERCEPTOR(int, sigaction, int signum, const struct sigaction *act,
|
||||
struct sigaction *oldact) {
|
||||
void *ctx;
|
||||
COMMON_INTERCEPTOR_ENTER(ctx, sigaction, signum, act, oldact);
|
||||
if (!processSigaction(signum, act, oldact))
|
||||
return 0;
|
||||
else
|
||||
return REAL(sigaction)(signum, act, oldact);
|
||||
}
|
||||
#define ESAN_MAYBE_INTERCEPT_SIGACTION INTERCEPT_FUNCTION(sigaction)
|
||||
#else
|
||||
#error Platform not supported
|
||||
#define ESAN_MAYBE_INTERCEPT_SIGACTION
|
||||
#endif
|
||||
|
||||
namespace __esan {
|
||||
|
||||
void initializeInterceptors() {
|
||||
|
@ -372,6 +409,9 @@ void initializeInterceptors() {
|
|||
INTERCEPT_FUNCTION(mmap);
|
||||
ESAN_MAYBE_INTERCEPT_MMAP64;
|
||||
|
||||
ESAN_MAYBE_INTERCEPT_SIGNAL;
|
||||
ESAN_MAYBE_INTERCEPT_SIGACTION;
|
||||
|
||||
// TODO(bruening): we should intercept calloc() and other memory allocation
|
||||
// routines that zero memory and update our shadow memory appropriately.
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ void processRangeAccessWorkingSet(uptr PC, uptr Addr, SIZE_T Size,
|
|||
void initializeWorkingSet() {
|
||||
// The shadow mapping assumes 64 so this cannot be changed.
|
||||
CHECK(getFlags()->cache_line_size == 64);
|
||||
registerMemoryFaultHandler();
|
||||
}
|
||||
|
||||
int finalizeWorkingSet() {
|
||||
|
|
|
@ -25,6 +25,12 @@ int finalizeWorkingSet();
|
|||
void processRangeAccessWorkingSet(uptr PC, uptr Addr, SIZE_T Size,
|
||||
bool IsWrite);
|
||||
|
||||
// Platform-dependent.
|
||||
void registerMemoryFaultHandler();
|
||||
bool processWorkingSetSignal(int SigNum, void (*Handler)(int),
|
||||
void (**Result)(int));
|
||||
bool processWorkingSetSigaction(int SigNum, const void *Act, void *OldAct);
|
||||
|
||||
} // namespace __esan
|
||||
|
||||
#endif // WORKING_SET_H
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
//===-- working_set_posix.cpp -----------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of EfficiencySanitizer, a family of performance tuners.
|
||||
//
|
||||
// POSIX-specific working set tool code.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "working_set.h"
|
||||
#include "esan_flags.h"
|
||||
#include "esan_shadow.h"
|
||||
#include "sanitizer_common/sanitizer_common.h"
|
||||
#include "sanitizer_common/sanitizer_linux.h"
|
||||
#include <signal.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
namespace __esan {
|
||||
|
||||
// We only support regular POSIX threads with a single signal handler
|
||||
// for the whole process == thread group.
|
||||
// Thus we only need to store one app signal handler.
|
||||
// FIXME: Store and use any alternate stack and signal flags set by
|
||||
// the app. For now we just call the app handler from our handler.
|
||||
static __sanitizer_sigaction AppSigAct;
|
||||
|
||||
bool processWorkingSetSignal(int SigNum, void (*Handler)(int),
|
||||
void (**Result)(int)) {
|
||||
VPrintf(2, "%s: %d\n", __FUNCTION__, SigNum);
|
||||
if (SigNum == SIGSEGV) {
|
||||
*Result = AppSigAct.handler;
|
||||
AppSigAct.sigaction = (void (*)(int, void*, void*))Handler;
|
||||
return false; // Skip real call.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processWorkingSetSigaction(int SigNum, const void *ActVoid,
|
||||
void *OldActVoid) {
|
||||
VPrintf(2, "%s: %d\n", __FUNCTION__, SigNum);
|
||||
if (SigNum == SIGSEGV) {
|
||||
const struct sigaction *Act = (const struct sigaction *) ActVoid;
|
||||
struct sigaction *OldAct = (struct sigaction *) OldActVoid;
|
||||
if (OldAct)
|
||||
internal_memcpy(OldAct, &AppSigAct, sizeof(OldAct));
|
||||
if (Act)
|
||||
internal_memcpy(&AppSigAct, Act, sizeof(AppSigAct));
|
||||
return false; // Skip real call.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void reinstateDefaultHandler(int SigNum) {
|
||||
__sanitizer_sigaction SigAct;
|
||||
internal_memset(&SigAct, 0, sizeof(SigAct));
|
||||
SigAct.sigaction = (void (*)(int, void*, void*)) SIG_DFL;
|
||||
int Res = internal_sigaction(SigNum, &SigAct, nullptr);
|
||||
CHECK(Res == 0);
|
||||
VPrintf(1, "Unregistered for %d handler\n", SigNum);
|
||||
}
|
||||
|
||||
// If this is a shadow fault, we handle it here; otherwise, we pass it to the
|
||||
// app to handle it just as the app would do without our tool in place.
|
||||
static void handleMemoryFault(int SigNum, void *Info, void *Ctx) {
|
||||
if (SigNum == SIGSEGV) {
|
||||
|
||||
// TODO: Add shadow memory fault detection and handling.
|
||||
|
||||
if (AppSigAct.sigaction) {
|
||||
// FIXME: For simplicity we ignore app options including its signal stack
|
||||
// (we just use ours) and all the delivery flags.
|
||||
AppSigAct.sigaction(SigNum, Info, Ctx);
|
||||
} else {
|
||||
// Crash instead of spinning with infinite faults.
|
||||
reinstateDefaultHandler(SigNum);
|
||||
}
|
||||
} else
|
||||
UNREACHABLE("signal not registered");
|
||||
}
|
||||
|
||||
void registerMemoryFaultHandler() {
|
||||
// We do not use an alternate signal stack, as doing so would require
|
||||
// setting it up for each app thread.
|
||||
// FIXME: This could result in problems with emulating the app's signal
|
||||
// handling if the app relies on an alternate stack for SIGSEGV.
|
||||
|
||||
// We assume SIGSEGV is not blocked and won't be blocked by the app, so
|
||||
// we leave the mask alone.
|
||||
|
||||
__sanitizer_sigaction SigAct;
|
||||
internal_memset(&SigAct, 0, sizeof(SigAct));
|
||||
SigAct.sigaction = handleMemoryFault;
|
||||
// We want to handle nested signals b/c we need to handle a
|
||||
// shadow fault in an app signal handler.
|
||||
SigAct.sa_flags = SA_SIGINFO | SA_NODEFER;
|
||||
int Res = internal_sigaction(SIGSEGV, &SigAct, &AppSigAct);
|
||||
CHECK(Res == 0);
|
||||
VPrintf(1, "Registered for SIGSEGV handler\n");
|
||||
}
|
||||
|
||||
} // namespace __esan
|
|
@ -0,0 +1,59 @@
|
|||
// RUN: %clang_esan_wset -O0 %s -o %t 2>&1
|
||||
// RUN: %run %t 2>&1 | FileCheck %s
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
|
||||
sigjmp_buf mark;
|
||||
|
||||
static void SignalHandler(int Sig) {
|
||||
if (Sig == SIGSEGV) {
|
||||
fprintf(stderr, "Handling SIGSEGV for signal\n");
|
||||
siglongjmp(mark, 1);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void SigactionHandler(int Sig, siginfo_t *Info, void *Ctx) {
|
||||
if (Sig == SIGSEGV) {
|
||||
fprintf(stderr, "Handling SIGSEGV for sigaction\n");
|
||||
siglongjmp(mark, 1);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
__sighandler_t Prior = signal(SIGSEGV, SignalHandler);
|
||||
assert(Prior == SIG_DFL);
|
||||
if (sigsetjmp(mark, 1) == 0)
|
||||
*((volatile int *)(ssize_t)argc) = 42; // Raise SIGSEGV
|
||||
fprintf(stderr, "Past longjmp for signal\n");
|
||||
|
||||
Prior = signal(SIGSEGV, SIG_DFL);
|
||||
assert(Prior == SignalHandler);
|
||||
|
||||
struct sigaction SigAct;
|
||||
SigAct.sa_sigaction = SigactionHandler;
|
||||
int Res = sigfillset(&SigAct.sa_mask);
|
||||
assert(Res == 0);
|
||||
SigAct.sa_flags = SA_SIGINFO;
|
||||
Res = sigaction(SIGSEGV, &SigAct, NULL);
|
||||
assert(Res == 0);
|
||||
|
||||
if (sigsetjmp(mark, 1) == 0)
|
||||
*((volatile int *)(ssize_t)argc) = 42; // Raise SIGSEGV
|
||||
fprintf(stderr, "Past longjmp for sigaction\n");
|
||||
|
||||
Res = sigaction(SIGSEGV, NULL, &SigAct);
|
||||
assert(Res == 0);
|
||||
assert(SigAct.sa_sigaction == SigactionHandler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
// CHECK: Handling SIGSEGV for signal
|
||||
// CHECK-NEXT: Past longjmp for signal
|
||||
// CHECK-NEXT: Handling SIGSEGV for sigaction
|
||||
// CHECK-NEXT: Past longjmp for sigaction
|
Loading…
Reference in New Issue