tsan: move shadow stack from thread descriptors to fixed addresses

This allows to increase max shadow stack size to 64K,
and reliably catch shadow stack overflows instead of silently
corrupting memory.

llvm-svn: 192797
This commit is contained in:
Dmitry Vyukov 2013-10-16 15:35:12 +00:00
parent b665d79f14
commit 464ebbd67b
12 changed files with 132 additions and 33 deletions

View File

@ -0,0 +1,38 @@
// RUN: %clangxx_tsan -O1 %s -o %t && not %t 2>&1 | FileCheck %s
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
volatile int X;
volatile int N;
void (*volatile F)();
static void foo() {
if (--N == 0)
X = 42;
else
F();
}
void *Thread(void *p) {
sleep(1);
F();
return 0;
}
int main() {
N = 50000;
F = foo;
pthread_t t;
pthread_attr_t a;
pthread_attr_init(&a);
pthread_attr_setstacksize(&a, N * 256 + (1 << 20));
pthread_create(&t, &a, Thread, 0);
X = 43;
pthread_join(t, 0);
}
// CHECK: WARNING: ThreadSanitizer: data race
// CHECK: #100 foo
// We must output suffucuently large stack (at least 100 frames)

View File

@ -0,0 +1,38 @@
// RUN: %clangxx_tsan -O1 %s -o %t && not %t 2>&1 | FileCheck %s
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
volatile int X;
volatile int N;
void (*volatile F)();
static void foo() {
if (--N == 0)
X = 42;
else
F();
}
void *Thread(void *p) {
F();
return 0;
}
int main() {
N = 50000;
F = foo;
pthread_t t;
pthread_attr_t a;
pthread_attr_init(&a);
pthread_attr_setstacksize(&a, N * 256 + (1 << 20));
pthread_create(&t, &a, Thread, 0);
sleep(1);
X = 43;
pthread_join(t, 0);
}
// CHECK: WARNING: ThreadSanitizer: data race
// CHECK: #100 foo
// We must output suffucuently large stack (at least 100 frames)

View File

@ -4,7 +4,7 @@
#include <stddef.h>
int GlobalData[10];
int y;
int qwerty;
namespace XXX {
struct YYY {
static int ZZZ[10];
@ -14,19 +14,19 @@ namespace XXX {
void *Thread(void *a) {
GlobalData[2] = 42;
y = 1;
qwerty = 1;
XXX::YYY::ZZZ[0] = 1;
return 0;
}
int main() {
fprintf(stderr, "addr=%p\n", GlobalData);
fprintf(stderr, "addr2=%p\n", &y);
fprintf(stderr, "addr2=%p\n", &qwerty);
fprintf(stderr, "addr3=%p\n", XXX::YYY::ZZZ);
pthread_t t;
pthread_create(&t, 0, Thread, 0);
GlobalData[2] = 43;
y = 0;
qwerty = 0;
XXX::YYY::ZZZ[0] = 0;
pthread_join(t, 0);
}
@ -37,6 +37,6 @@ int main() {
// CHECK: WARNING: ThreadSanitizer: data race
// CHECK: Location is global 'GlobalData' of size 40 at [[ADDR]] ({{.*}}+0x{{[0-9,a-f]+}})
// CHECK: WARNING: ThreadSanitizer: data race
// CHECK: Location is global 'y' of size 4 at [[ADDR2]] ({{.*}}+0x{{[0-9,a-f]+}})
// CHECK: Location is global 'qwerty' of size 4 at [[ADDR2]] ({{.*}}+0x{{[0-9,a-f]+}})
// CHECK: WARNING: ThreadSanitizer: data race
// CHECK: Location is global 'XXX::YYY::ZZZ' of size 40 at [[ADDR3]] ({{.*}}+0x{{[0-9,a-f]+}})

View File

@ -41,10 +41,8 @@ const int kTidBits = 13;
const unsigned kMaxTid = 1 << kTidBits;
const unsigned kMaxTidInClock = kMaxTid * 2; // This includes msb 'freed' bit.
const int kClkBits = 42;
#ifndef TSAN_GO
const int kShadowStackSize = 4 * 1024;
const int kTraceStackSize = 256;
#endif
const uptr kShadowStackSize = 64 * 1024;
const uptr kTraceStackSize = 256;
#ifdef TSAN_SHADOW_COUNT
# if TSAN_SHADOW_COUNT == 2 \

View File

@ -138,14 +138,20 @@ uptr GetRSS();
const char *InitializePlatform();
void FinalizePlatform();
// The additional page is to catch shadow stack overflow as paging fault.
const uptr kTotalTraceSize = (kTraceSize * sizeof(Event) + sizeof(Trace) + 4096
+ 4095) & ~4095;
uptr ALWAYS_INLINE GetThreadTrace(int tid) {
uptr p = kTraceMemBegin + (uptr)(tid * 2) * kTraceSize * sizeof(Event);
uptr p = kTraceMemBegin + (uptr)tid * kTotalTraceSize;
DCHECK_LT(p, kTraceMemBegin + kTraceMemSize);
return p;
}
uptr ALWAYS_INLINE GetThreadTraceHeader(int tid) {
uptr p = kTraceMemBegin + (uptr)(tid * 2 + 1) * kTraceSize * sizeof(Event);
uptr p = kTraceMemBegin + (uptr)tid * kTotalTraceSize
+ kTraceSize * sizeof(Event);
DCHECK_LT(p, kTraceMemBegin + kTraceMemSize);
return p;
}

View File

@ -90,7 +90,6 @@ ThreadState::ThreadState(Context *ctx, int tid, int unique_id, u64 epoch,
// they may be accessed before the ctor.
// , ignore_reads_and_writes()
// , in_rtl()
, shadow_stack_pos(&shadow_stack[0])
#ifndef TSAN_GO
, jmp_bufs(MBlockJmpBuf)
#endif
@ -201,8 +200,10 @@ void MapThreadTrace(uptr addr, uptr size) {
DPrintf("#0: Mapping trace at %p-%p(0x%zx)\n", addr, addr + size, size);
CHECK_GE(addr, kTraceMemBegin);
CHECK_LE(addr + size, kTraceMemBegin + kTraceMemSize);
if (addr != (uptr)MmapFixedNoReserve(addr, size)) {
Printf("FATAL: ThreadSanitizer can not mmap thread trace\n");
uptr addr1 = (uptr)MmapFixedNoReserve(addr, size);
if (addr1 != addr) {
Printf("FATAL: ThreadSanitizer can not mmap thread trace (%p/%p->%p)\n",
addr, size, addr1);
Die();
}
}
@ -660,9 +661,9 @@ void FuncEntry(ThreadState *thr, uptr pc) {
// Shadow stack maintenance can be replaced with
// stack unwinding during trace switch (which presumably must be faster).
DCHECK_GE(thr->shadow_stack_pos, &thr->shadow_stack[0]);
DCHECK_GE(thr->shadow_stack_pos, thr->shadow_stack);
#ifndef TSAN_GO
DCHECK_LT(thr->shadow_stack_pos, &thr->shadow_stack[kShadowStackSize]);
DCHECK_LT(thr->shadow_stack_pos, thr->shadow_stack_end);
#else
if (thr->shadow_stack_pos == thr->shadow_stack_end) {
const int sz = thr->shadow_stack_end - thr->shadow_stack;
@ -688,9 +689,9 @@ void FuncExit(ThreadState *thr) {
thr->fast_state.IncrementEpoch();
TraceAddEvent(thr, thr->fast_state, EventTypeFuncExit, 0);
DCHECK_GT(thr->shadow_stack_pos, &thr->shadow_stack[0]);
DCHECK_GT(thr->shadow_stack_pos, thr->shadow_stack);
#ifndef TSAN_GO
DCHECK_LT(thr->shadow_stack_pos, &thr->shadow_stack[kShadowStackSize]);
DCHECK_LT(thr->shadow_stack_pos, thr->shadow_stack_end);
#endif
thr->shadow_stack_pos--;
}

View File

@ -413,17 +413,13 @@ struct ThreadState {
// for better performance.
int ignore_reads_and_writes;
int ignore_sync;
// C/C++ uses fixed size shadow stack embed into Trace.
// Go uses malloc-allocated shadow stack with dynamic size.
uptr *shadow_stack;
uptr *shadow_stack_end;
uptr *shadow_stack_pos;
u64 *racy_shadow_addr;
u64 racy_state[2];
#ifndef TSAN_GO
// C/C++ uses embed shadow stack of fixed size.
uptr shadow_stack[kShadowStackSize];
#else
// Go uses satellite shadow stack with dynamic size.
uptr *shadow_stack;
uptr *shadow_stack_end;
#endif
MutexSet mset;
ThreadClock clock;
#ifndef TSAN_GO

View File

@ -410,7 +410,7 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset) {
const u64 ebegin = RoundDown(eend, kTracePartSize);
DPrintf("#%d: RestoreStack epoch=%zu ebegin=%zu eend=%zu partidx=%d\n",
tid, (uptr)epoch, (uptr)ebegin, (uptr)eend, partidx);
InternalScopedBuffer<uptr> stack(1024); // FIXME: de-hardcode 1024
InternalScopedBuffer<uptr> stack(kShadowStackSize);
for (uptr i = 0; i < hdr->stack0.Size(); i++) {
stack[i] = hdr->stack0.Get(i);
DPrintf2(" #%02lu: pc=%zx\n", i, stack[i]);

View File

@ -91,18 +91,21 @@ void ThreadContext::OnStarted(void *arg) {
epoch1 = (u64)-1;
new(thr) ThreadState(CTX(), tid, unique_id,
epoch0, args->stk_addr, args->stk_size, args->tls_addr, args->tls_size);
#ifdef TSAN_GO
#ifndef TSAN_GO
thr->shadow_stack = &ThreadTrace(thr->tid)->shadow_stack[0];
thr->shadow_stack_pos = thr->shadow_stack;
thr->shadow_stack_end = thr->shadow_stack + kShadowStackSize;
#else
// Setup dynamic shadow stack.
const int kInitStackSize = 8;
args->thr->shadow_stack = (uptr*)internal_alloc(MBlockShadowStack,
thr->shadow_stack = (uptr*)internal_alloc(MBlockShadowStack,
kInitStackSize * sizeof(uptr));
args->thr->shadow_stack_pos = thr->shadow_stack;
args->thr->shadow_stack_end = thr->shadow_stack + kInitStackSize;
thr->shadow_stack_pos = thr->shadow_stack;
thr->shadow_stack_end = thr->shadow_stack + kInitStackSize;
#endif
#ifndef TSAN_GO
AllocatorThreadStart(args->thr);
AllocatorThreadStart(thr);
#endif
thr = args->thr;
thr->fast_synch_epoch = epoch0;
AcquireImpl(thr, 0, &sync);
thr->fast_state.SetHistorySize(flags()->history_size);

View File

@ -265,6 +265,11 @@ void StackTrace::ObtainCurrent(ThreadState *thr, uptr toppc) {
n_ = c_ - !!toppc;
}
} else {
// Cap potentially huge stacks.
if (n_ + !!toppc > kTraceStackSize) {
start = n_ - kTraceStackSize + !!toppc;
n_ = kTraceStackSize - !!toppc;
}
s_ = (uptr*)internal_alloc(MBlockStackTrace,
(n_ + !!toppc) * sizeof(s_[0]));
}

View File

@ -62,6 +62,11 @@ struct TraceHeader {
struct Trace {
TraceHeader headers[kTraceParts];
Mutex mtx;
#ifndef TSAN_GO
// Must be last to catch overflow as paging fault.
// Go shadow stack is dynamically allocated.
uptr shadow_stack[kShadowStackSize];
#endif
Trace()
: mtx(MutexTypeTrace, StatMtxTrace) {

View File

@ -19,6 +19,10 @@ namespace __tsan {
static void TestStackTrace(StackTrace *trace) {
ThreadState thr(0, 0, 0, 0, 0, 0, 0, 0);
uptr stack[128];
thr.shadow_stack = &stack[0];
thr.shadow_stack_pos = &stack[0];
thr.shadow_stack_end = &stack[128];
trace->ObtainCurrent(&thr, 0);
EXPECT_EQ(trace->Size(), (uptr)0);
@ -60,7 +64,12 @@ TEST(StackTrace, StaticTrim) {
ScopedInRtl in_rtl;
uptr buf[2];
StackTrace trace(buf, 2);
ThreadState thr(0, 0, 0, 0, 0, 0, 0, 0);
uptr stack[128];
thr.shadow_stack = &stack[0];
thr.shadow_stack_pos = &stack[0];
thr.shadow_stack_end = &stack[128];
*thr.shadow_stack_pos++ = 100;
*thr.shadow_stack_pos++ = 101;