forked from OSchip/llvm-project
tsan: remember and print function that installed at_exit callbacks
Sometimes stacks for at_exit callbacks don't include any of the user functions/files. For example, a race with a global std container destructor will only contain the container type name and our at_exit_wrapper function. No signs what global variable this is. Remember and include in reports the function that installed the at_exit callback. This should give glues as to what variable is being destroyed. Depends on D114606. Reviewed By: vitalybuka, melver Differential Revision: https://reviews.llvm.org/D114607
This commit is contained in:
parent
3f87788de1
commit
a1dc97e472
|
@ -177,6 +177,7 @@ struct ThreadSignalContext {
|
||||||
struct AtExitCtx {
|
struct AtExitCtx {
|
||||||
void (*f)();
|
void (*f)();
|
||||||
void *arg;
|
void *arg;
|
||||||
|
uptr pc;
|
||||||
};
|
};
|
||||||
|
|
||||||
// InterceptorContext holds all global data required for interceptors.
|
// InterceptorContext holds all global data required for interceptors.
|
||||||
|
@ -367,7 +368,10 @@ TSAN_INTERCEPTOR(int, pause, int fake) {
|
||||||
return BLOCK_REAL(pause)(fake);
|
return BLOCK_REAL(pause)(fake);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void at_exit_wrapper() {
|
// Note: we specifically call the function in such strange way
|
||||||
|
// with "installed_at" because in reports it will appear between
|
||||||
|
// callback frames and the frame that installed the callback.
|
||||||
|
static void at_exit_callback_installed_at() {
|
||||||
AtExitCtx *ctx;
|
AtExitCtx *ctx;
|
||||||
{
|
{
|
||||||
// Ensure thread-safety.
|
// Ensure thread-safety.
|
||||||
|
@ -379,15 +383,21 @@ static void at_exit_wrapper() {
|
||||||
interceptor_ctx()->AtExitStack.PopBack();
|
interceptor_ctx()->AtExitStack.PopBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
Acquire(cur_thread(), (uptr)0, (uptr)ctx);
|
ThreadState *thr = cur_thread();
|
||||||
|
Acquire(thr, ctx->pc, (uptr)ctx);
|
||||||
|
FuncEntry(thr, ctx->pc);
|
||||||
((void(*)())ctx->f)();
|
((void(*)())ctx->f)();
|
||||||
|
FuncExit(thr);
|
||||||
Free(ctx);
|
Free(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cxa_at_exit_wrapper(void *arg) {
|
static void cxa_at_exit_callback_installed_at(void *arg) {
|
||||||
Acquire(cur_thread(), 0, (uptr)arg);
|
ThreadState *thr = cur_thread();
|
||||||
AtExitCtx *ctx = (AtExitCtx*)arg;
|
AtExitCtx *ctx = (AtExitCtx*)arg;
|
||||||
|
Acquire(thr, ctx->pc, (uptr)arg);
|
||||||
|
FuncEntry(thr, ctx->pc);
|
||||||
((void(*)(void *arg))ctx->f)(ctx->arg);
|
((void(*)(void *arg))ctx->f)(ctx->arg);
|
||||||
|
FuncExit(thr);
|
||||||
Free(ctx);
|
Free(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +411,7 @@ TSAN_INTERCEPTOR(int, atexit, void (*f)()) {
|
||||||
// We want to setup the atexit callback even if we are in ignored lib
|
// We want to setup the atexit callback even if we are in ignored lib
|
||||||
// or after fork.
|
// or after fork.
|
||||||
SCOPED_INTERCEPTOR_RAW(atexit, f);
|
SCOPED_INTERCEPTOR_RAW(atexit, f);
|
||||||
return setup_at_exit_wrapper(thr, pc, (void(*)())f, 0, 0);
|
return setup_at_exit_wrapper(thr, GET_CALLER_PC(), (void (*)())f, 0, 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -409,7 +419,7 @@ TSAN_INTERCEPTOR(int, __cxa_atexit, void (*f)(void *a), void *arg, void *dso) {
|
||||||
if (in_symbolizer())
|
if (in_symbolizer())
|
||||||
return 0;
|
return 0;
|
||||||
SCOPED_TSAN_INTERCEPTOR(__cxa_atexit, f, arg, dso);
|
SCOPED_TSAN_INTERCEPTOR(__cxa_atexit, f, arg, dso);
|
||||||
return setup_at_exit_wrapper(thr, pc, (void(*)())f, arg, dso);
|
return setup_at_exit_wrapper(thr, GET_CALLER_PC(), (void (*)())f, arg, dso);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int setup_at_exit_wrapper(ThreadState *thr, uptr pc, void(*f)(),
|
static int setup_at_exit_wrapper(ThreadState *thr, uptr pc, void(*f)(),
|
||||||
|
@ -417,6 +427,7 @@ static int setup_at_exit_wrapper(ThreadState *thr, uptr pc, void(*f)(),
|
||||||
auto *ctx = New<AtExitCtx>();
|
auto *ctx = New<AtExitCtx>();
|
||||||
ctx->f = f;
|
ctx->f = f;
|
||||||
ctx->arg = arg;
|
ctx->arg = arg;
|
||||||
|
ctx->pc = pc;
|
||||||
Release(thr, pc, (uptr)ctx);
|
Release(thr, pc, (uptr)ctx);
|
||||||
// Memory allocation in __cxa_atexit will race with free during exit,
|
// Memory allocation in __cxa_atexit will race with free during exit,
|
||||||
// because we do not see synchronization around atexit callback list.
|
// because we do not see synchronization around atexit callback list.
|
||||||
|
@ -432,25 +443,27 @@ static int setup_at_exit_wrapper(ThreadState *thr, uptr pc, void(*f)(),
|
||||||
// due to atexit_mu held on exit from the calloc interceptor.
|
// due to atexit_mu held on exit from the calloc interceptor.
|
||||||
ScopedIgnoreInterceptors ignore;
|
ScopedIgnoreInterceptors ignore;
|
||||||
|
|
||||||
res = REAL(__cxa_atexit)((void (*)(void *a))at_exit_wrapper, 0, 0);
|
res = REAL(__cxa_atexit)((void (*)(void *a))at_exit_callback_installed_at,
|
||||||
|
0, 0);
|
||||||
// Push AtExitCtx on the top of the stack of callback functions
|
// Push AtExitCtx on the top of the stack of callback functions
|
||||||
if (!res) {
|
if (!res) {
|
||||||
interceptor_ctx()->AtExitStack.PushBack(ctx);
|
interceptor_ctx()->AtExitStack.PushBack(ctx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res = REAL(__cxa_atexit)(cxa_at_exit_wrapper, ctx, dso);
|
res = REAL(__cxa_atexit)(cxa_at_exit_callback_installed_at, ctx, dso);
|
||||||
}
|
}
|
||||||
ThreadIgnoreEnd(thr);
|
ThreadIgnoreEnd(thr);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !SANITIZER_MAC && !SANITIZER_NETBSD
|
#if !SANITIZER_MAC && !SANITIZER_NETBSD
|
||||||
static void on_exit_wrapper(int status, void *arg) {
|
static void on_exit_callback_installed_at(int status, void *arg) {
|
||||||
ThreadState *thr = cur_thread();
|
ThreadState *thr = cur_thread();
|
||||||
uptr pc = 0;
|
|
||||||
Acquire(thr, pc, (uptr)arg);
|
|
||||||
AtExitCtx *ctx = (AtExitCtx*)arg;
|
AtExitCtx *ctx = (AtExitCtx*)arg;
|
||||||
|
Acquire(thr, ctx->pc, (uptr)arg);
|
||||||
|
FuncEntry(thr, ctx->pc);
|
||||||
((void(*)(int status, void *arg))ctx->f)(status, ctx->arg);
|
((void(*)(int status, void *arg))ctx->f)(status, ctx->arg);
|
||||||
|
FuncExit(thr);
|
||||||
Free(ctx);
|
Free(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,11 +474,12 @@ TSAN_INTERCEPTOR(int, on_exit, void(*f)(int, void*), void *arg) {
|
||||||
auto *ctx = New<AtExitCtx>();
|
auto *ctx = New<AtExitCtx>();
|
||||||
ctx->f = (void(*)())f;
|
ctx->f = (void(*)())f;
|
||||||
ctx->arg = arg;
|
ctx->arg = arg;
|
||||||
|
ctx->pc = GET_CALLER_PC();
|
||||||
Release(thr, pc, (uptr)ctx);
|
Release(thr, pc, (uptr)ctx);
|
||||||
// Memory allocation in __cxa_atexit will race with free during exit,
|
// Memory allocation in __cxa_atexit will race with free during exit,
|
||||||
// because we do not see synchronization around atexit callback list.
|
// because we do not see synchronization around atexit callback list.
|
||||||
ThreadIgnoreBegin(thr, pc);
|
ThreadIgnoreBegin(thr, pc);
|
||||||
int res = REAL(on_exit)(on_exit_wrapper, ctx);
|
int res = REAL(on_exit)(on_exit_callback_installed_at, ctx);
|
||||||
ThreadIgnoreEnd(thr);
|
ThreadIgnoreEnd(thr);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,4 +31,5 @@ int main() {
|
||||||
// CHECK: #0 thread
|
// CHECK: #0 thread
|
||||||
// CHECK: Previous write of size 4
|
// CHECK: Previous write of size 4
|
||||||
// CHECK: #0 race
|
// CHECK: #0 race
|
||||||
// CHECK: #1 at_exit_wrapper
|
// CHECK: #1 at_exit_callback_installed_at
|
||||||
|
// CHECK: #2 X
|
||||||
|
|
|
@ -23,4 +23,5 @@ int main() {
|
||||||
// CHECK: Write of size 8
|
// CHECK: Write of size 8
|
||||||
// The exact spelling and number of std frames is hard to guess.
|
// The exact spelling and number of std frames is hard to guess.
|
||||||
// CHECK: unique_ptr
|
// CHECK: unique_ptr
|
||||||
// CHECK: #{{1|2}} cxa_at_exit_wrapper
|
// CHECK: #{{1|2}} cxa_at_exit_callback_installed_at
|
||||||
|
// CHECK: #{{2|3}} __cxx_global_var_init
|
||||||
|
|
|
@ -28,4 +28,5 @@ int main() {
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK: Write of size 8
|
// CHECK: Write of size 8
|
||||||
// CHECK: #0 on_exit_callback
|
// CHECK: #0 on_exit_callback
|
||||||
// CHECK: #1 on_exit_wrapper
|
// CHECK: #1 on_exit_callback_installed_at
|
||||||
|
// CHECK: #2 main
|
||||||
|
|
Loading…
Reference in New Issue