tsan: describe "file descriptor" location
llvm-svn: 170417
This commit is contained in:
parent
c0699854dd
commit
6d315cbcc3
|
@ -0,0 +1,33 @@
|
|||
// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int fds[2];
|
||||
|
||||
void *Thread1(void *x) {
|
||||
write(fds[1], "a", 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *Thread2(void *x) {
|
||||
sleep(1);
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
pipe(fds);
|
||||
pthread_t t[2];
|
||||
pthread_create(&t[0], NULL, Thread1, NULL);
|
||||
pthread_create(&t[1], NULL, Thread2, NULL);
|
||||
pthread_join(t[0], NULL);
|
||||
pthread_join(t[1], NULL);
|
||||
}
|
||||
|
||||
// CHECK: WARNING: ThreadSanitizer: data race
|
||||
// CHECK: Location is file descriptor {{[0-9]+}} created by main thread at:
|
||||
// CHECK: #0 pipe
|
||||
// CHECK: #1 main
|
||||
|
|
@ -21,154 +21,181 @@ const int kTableSizeL1 = 1024;
|
|||
const int kTableSizeL2 = 1024;
|
||||
const int kTableSize = kTableSizeL1 * kTableSizeL2;
|
||||
|
||||
struct FdDesc {
|
||||
struct FdSync {
|
||||
atomic_uint64_t rc;
|
||||
};
|
||||
|
||||
struct FdDesc {
|
||||
FdSync *sync;
|
||||
int creation_tid;
|
||||
u32 creation_stack;
|
||||
};
|
||||
|
||||
struct FdContext {
|
||||
atomic_uintptr_t tab[kTableSizeL1];
|
||||
// Addresses used for synchronization.
|
||||
FdDesc globdesc;
|
||||
FdDesc filedesc;
|
||||
FdDesc sockdesc;
|
||||
FdSync globsync;
|
||||
FdSync filesync;
|
||||
FdSync socksync;
|
||||
u64 connectsync;
|
||||
};
|
||||
|
||||
static FdContext fdctx;
|
||||
|
||||
static FdDesc *allocdesc() {
|
||||
FdDesc *pd = (FdDesc*)internal_alloc(MBlockFD, sizeof(FdDesc));
|
||||
atomic_store(&pd->rc, 1, memory_order_relaxed);
|
||||
return pd;
|
||||
static FdSync *allocsync() {
|
||||
FdSync *s = (FdSync*)internal_alloc(MBlockFD, sizeof(FdSync));
|
||||
atomic_store(&s->rc, 1, memory_order_relaxed);
|
||||
return s;
|
||||
}
|
||||
|
||||
static FdDesc *ref(FdDesc *pd) {
|
||||
if (pd && atomic_load(&pd->rc, memory_order_relaxed) != (u64)-1)
|
||||
atomic_fetch_add(&pd->rc, 1, memory_order_relaxed);
|
||||
return pd;
|
||||
static FdSync *ref(FdSync *s) {
|
||||
if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1)
|
||||
atomic_fetch_add(&s->rc, 1, memory_order_relaxed);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void unref(ThreadState *thr, uptr pc, FdDesc *pd) {
|
||||
if (pd && atomic_load(&pd->rc, memory_order_relaxed) != (u64)-1) {
|
||||
if (atomic_fetch_sub(&pd->rc, 1, memory_order_acq_rel) == 1) {
|
||||
CHECK_NE(pd, &fdctx.globdesc);
|
||||
CHECK_NE(pd, &fdctx.filedesc);
|
||||
CHECK_NE(pd, &fdctx.sockdesc);
|
||||
SyncVar *s = CTX()->synctab.GetAndRemove(thr, pc, (uptr)pd);
|
||||
if (s)
|
||||
DestroyAndFree(s);
|
||||
internal_free(pd);
|
||||
static void unref(ThreadState *thr, uptr pc, FdSync *s) {
|
||||
if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1) {
|
||||
if (atomic_fetch_sub(&s->rc, 1, memory_order_acq_rel) == 1) {
|
||||
CHECK_NE(s, &fdctx.globsync);
|
||||
CHECK_NE(s, &fdctx.filesync);
|
||||
CHECK_NE(s, &fdctx.socksync);
|
||||
SyncVar *v = CTX()->synctab.GetAndRemove(thr, pc, (uptr)s);
|
||||
if (v)
|
||||
DestroyAndFree(v);
|
||||
internal_free(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FdDesc **fdaddr(ThreadState *thr, uptr pc, int fd) {
|
||||
static FdDesc *fddesc(ThreadState *thr, uptr pc, int fd) {
|
||||
CHECK_LT(fd, kTableSize);
|
||||
atomic_uintptr_t *pl1 = &fdctx.tab[fd / kTableSizeL2];
|
||||
uptr l1 = atomic_load(pl1, memory_order_consume);
|
||||
if (l1 == 0) {
|
||||
uptr size = kTableSizeL2 * sizeof(uptr);
|
||||
uptr size = kTableSizeL2 * sizeof(FdDesc);
|
||||
void *p = internal_alloc(MBlockFD, size);
|
||||
internal_memset(p, 0, size);
|
||||
MemoryResetRange(thr, (uptr)&fdaddr, (uptr)p, size);
|
||||
MemoryResetRange(thr, (uptr)&fddesc, (uptr)p, size);
|
||||
if (atomic_compare_exchange_strong(pl1, &l1, (uptr)p, memory_order_acq_rel))
|
||||
l1 = (uptr)p;
|
||||
else
|
||||
internal_free(p);
|
||||
}
|
||||
return &((FdDesc**)l1)[fd % kTableSizeL2]; // NOLINT
|
||||
return &((FdDesc*)l1)[fd % kTableSizeL2]; // NOLINT
|
||||
}
|
||||
|
||||
// pd must be already ref'ed.
|
||||
static void init(ThreadState *thr, uptr pc, int fd, FdDesc *d) {
|
||||
FdDesc **pd = fdaddr(thr, pc, fd);
|
||||
static void init(ThreadState *thr, uptr pc, int fd, FdSync *s) {
|
||||
FdDesc *d = fddesc(thr, pc, fd);
|
||||
// As a matter of fact, we don't intercept all close calls.
|
||||
// See e.g. libc __res_iclose().
|
||||
if (*pd)
|
||||
unref(thr, pc, *pd);
|
||||
*pd = d;
|
||||
if (d->sync)
|
||||
unref(thr, pc, d->sync);
|
||||
d->sync = s;
|
||||
d->creation_tid = thr->tid;
|
||||
d->creation_stack = CurrentStackId(thr, pc);
|
||||
// To catch races between fd usage and open.
|
||||
MemoryRangeImitateWrite(thr, pc, (uptr)pd, 8);
|
||||
MemoryRangeImitateWrite(thr, pc, (uptr)d, 8);
|
||||
}
|
||||
|
||||
void FdInit() {
|
||||
atomic_store(&fdctx.globdesc.rc, (u64)-1, memory_order_relaxed);
|
||||
atomic_store(&fdctx.filedesc.rc, (u64)-1, memory_order_relaxed);
|
||||
atomic_store(&fdctx.sockdesc.rc, (u64)-1, memory_order_relaxed);
|
||||
atomic_store(&fdctx.globsync.rc, (u64)-1, memory_order_relaxed);
|
||||
atomic_store(&fdctx.filesync.rc, (u64)-1, memory_order_relaxed);
|
||||
atomic_store(&fdctx.socksync.rc, (u64)-1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool FdLocation(uptr addr, int *fd, int *tid, u32 *stack) {
|
||||
for (int l1 = 0; l1 < kTableSizeL1; l1++) {
|
||||
FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed);
|
||||
if (tab == 0)
|
||||
break;
|
||||
if (addr >= (uptr)tab && addr < (uptr)(tab + kTableSizeL2)) {
|
||||
int l2 = (addr - (uptr)tab) / sizeof(FdDesc);
|
||||
FdDesc *d = &tab[l2];
|
||||
*fd = l1 * kTableSizeL1 + l2;
|
||||
*tid = d->creation_tid;
|
||||
*stack = d->creation_stack;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FdAcquire(ThreadState *thr, uptr pc, int fd) {
|
||||
FdDesc **pd = fdaddr(thr, pc, fd);
|
||||
FdDesc *d = *pd;
|
||||
DPrintf("#%d: FdAcquire(%d) -> %p\n", thr->tid, fd, d);
|
||||
MemoryRead8Byte(thr, pc, (uptr)pd);
|
||||
if (d)
|
||||
Acquire(thr, pc, (uptr)d);
|
||||
FdDesc *d = fddesc(thr, pc, fd);
|
||||
FdSync *s = d->sync;
|
||||
DPrintf("#%d: FdAcquire(%d) -> %p\n", thr->tid, fd, s);
|
||||
MemoryRead8Byte(thr, pc, (uptr)d);
|
||||
if (s)
|
||||
Acquire(thr, pc, (uptr)s);
|
||||
}
|
||||
|
||||
void FdRelease(ThreadState *thr, uptr pc, int fd) {
|
||||
FdDesc **pd = fdaddr(thr, pc, fd);
|
||||
FdDesc *d = *pd;
|
||||
DPrintf("#%d: FdRelease(%d) -> %p\n", thr->tid, fd, d);
|
||||
if (d)
|
||||
Release(thr, pc, (uptr)d);
|
||||
MemoryRead8Byte(thr, pc, (uptr)pd);
|
||||
FdDesc *d = fddesc(thr, pc, fd);
|
||||
FdSync *s = d->sync;
|
||||
DPrintf("#%d: FdRelease(%d) -> %p\n", thr->tid, fd, s);
|
||||
if (s)
|
||||
Release(thr, pc, (uptr)s);
|
||||
MemoryRead8Byte(thr, pc, (uptr)d);
|
||||
}
|
||||
|
||||
void FdClose(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdClose(%d)\n", thr->tid, fd);
|
||||
FdDesc **pd = fdaddr(thr, pc, fd);
|
||||
FdDesc *d = fddesc(thr, pc, fd);
|
||||
// To catch races between fd usage and close.
|
||||
MemoryWrite8Byte(thr, pc, (uptr)pd);
|
||||
MemoryWrite8Byte(thr, pc, (uptr)d);
|
||||
// We need to clear it, because if we do not intercept any call out there
|
||||
// that creates fd, we will hit false postives.
|
||||
MemoryResetRange(thr, pc, (uptr)pd, 8);
|
||||
unref(thr, pc, *pd);
|
||||
*pd = 0;
|
||||
MemoryResetRange(thr, pc, (uptr)d, 8);
|
||||
unref(thr, pc, d->sync);
|
||||
d->sync = 0;
|
||||
d->creation_tid = 0;
|
||||
d->creation_stack = 0;
|
||||
}
|
||||
|
||||
void FdFileCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdFileCreate(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, &fdctx.filedesc);
|
||||
init(thr, pc, fd, &fdctx.filesync);
|
||||
}
|
||||
|
||||
void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd) {
|
||||
DPrintf("#%d: FdDup(%d, %d)\n", thr->tid, oldfd, newfd);
|
||||
// Ignore the case when user dups not yet connected socket.
|
||||
FdDesc **opd = fdaddr(thr, pc, oldfd);
|
||||
MemoryRead8Byte(thr, pc, (uptr)opd);
|
||||
FdDesc *od = fddesc(thr, pc, oldfd);
|
||||
MemoryRead8Byte(thr, pc, (uptr)od);
|
||||
FdClose(thr, pc, newfd);
|
||||
init(thr, pc, newfd, ref(*opd));
|
||||
init(thr, pc, newfd, ref(od->sync));
|
||||
}
|
||||
|
||||
void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd) {
|
||||
DPrintf("#%d: FdCreatePipe(%d, %d)\n", thr->tid, rfd, wfd);
|
||||
FdDesc *d = allocdesc();
|
||||
init(thr, pc, rfd, d);
|
||||
init(thr, pc, wfd, ref(d));
|
||||
FdSync *s = allocsync();
|
||||
init(thr, pc, rfd, s);
|
||||
init(thr, pc, wfd, ref(s));
|
||||
}
|
||||
|
||||
void FdEventCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdEventCreate(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, allocdesc());
|
||||
init(thr, pc, fd, allocsync());
|
||||
}
|
||||
|
||||
void FdPollCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdPollCreate(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, allocdesc());
|
||||
init(thr, pc, fd, allocsync());
|
||||
}
|
||||
|
||||
void FdSocketCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdSocketCreate(%d)\n", thr->tid, fd);
|
||||
// It can be a UDP socket.
|
||||
init(thr, pc, fd, &fdctx.sockdesc);
|
||||
init(thr, pc, fd, &fdctx.socksync);
|
||||
}
|
||||
|
||||
void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd) {
|
||||
DPrintf("#%d: FdSocketAccept(%d, %d)\n", thr->tid, fd, newfd);
|
||||
// Synchronize connect->accept.
|
||||
Acquire(thr, pc, (uptr)&fdctx.connectsync);
|
||||
init(thr, pc, newfd, &fdctx.sockdesc);
|
||||
init(thr, pc, newfd, &fdctx.socksync);
|
||||
}
|
||||
|
||||
void FdSocketConnecting(ThreadState *thr, uptr pc, int fd) {
|
||||
|
@ -179,7 +206,7 @@ void FdSocketConnecting(ThreadState *thr, uptr pc, int fd) {
|
|||
|
||||
void FdSocketConnect(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdSocketConnect(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, &fdctx.sockdesc);
|
||||
init(thr, pc, fd, &fdctx.socksync);
|
||||
}
|
||||
|
||||
uptr File2addr(char *path) {
|
||||
|
|
|
@ -51,6 +51,7 @@ void FdSocketCreate(ThreadState *thr, uptr pc, int fd);
|
|||
void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd);
|
||||
void FdSocketConnecting(ThreadState *thr, uptr pc, int fd);
|
||||
void FdSocketConnect(ThreadState *thr, uptr pc, int fd);
|
||||
bool FdLocation(uptr addr, int *fd, int *tid, u32 *stack);
|
||||
|
||||
uptr File2addr(char *path);
|
||||
uptr Dir2addr(char *path);
|
||||
|
|
|
@ -102,6 +102,7 @@ static void PrintMop(const ReportMop *mop, bool first) {
|
|||
}
|
||||
|
||||
static void PrintLocation(const ReportLocation *loc) {
|
||||
char thrbuf[kThreadBufSize];
|
||||
if (loc->type == ReportLocationGlobal) {
|
||||
Printf(" Location is global '%s' of size %zu at %zx %s:%d (%s+%p)\n\n",
|
||||
loc->name, loc->size, loc->addr, loc->file, loc->line,
|
||||
|
@ -112,7 +113,11 @@ static void PrintLocation(const ReportLocation *loc) {
|
|||
loc->size, loc->addr, thread_name(thrbuf, loc->tid));
|
||||
PrintStack(loc->stack);
|
||||
} else if (loc->type == ReportLocationStack) {
|
||||
Printf(" Location is stack of thread T%d:\n\n", loc->tid);
|
||||
Printf(" Location is stack of %s\n\n", thread_name(thrbuf, loc->tid));
|
||||
} else if (loc->type == ReportLocationFD) {
|
||||
Printf(" Location is file descriptor %d created by %s at:\n",
|
||||
loc->fd, thread_name(thrbuf, loc->tid));
|
||||
PrintStack(loc->stack);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,8 @@ struct ReportMop {
|
|||
enum ReportLocationType {
|
||||
ReportLocationGlobal,
|
||||
ReportLocationHeap,
|
||||
ReportLocationStack
|
||||
ReportLocationStack,
|
||||
ReportLocationFD
|
||||
};
|
||||
|
||||
struct ReportLocation {
|
||||
|
@ -67,6 +68,7 @@ struct ReportLocation {
|
|||
char *module;
|
||||
uptr offset;
|
||||
int tid;
|
||||
int fd;
|
||||
char *name;
|
||||
char *file;
|
||||
int line;
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "tsan_sync.h"
|
||||
#include "tsan_mman.h"
|
||||
#include "tsan_flags.h"
|
||||
#include "tsan_fd.h"
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
|
@ -227,6 +228,29 @@ void ScopedReport::AddLocation(uptr addr, uptr size) {
|
|||
if (addr == 0)
|
||||
return;
|
||||
#ifndef TSAN_GO
|
||||
int fd = -1;
|
||||
int creat_tid = -1;
|
||||
u32 creat_stack = 0;
|
||||
if (FdLocation(addr, &fd, &creat_tid, &creat_stack)
|
||||
|| FdLocation(AlternativeAddress(addr), &fd, &creat_tid, &creat_stack)) {
|
||||
void *mem = internal_alloc(MBlockReportLoc, sizeof(ReportLocation));
|
||||
ReportLocation *loc = new(mem) ReportLocation();
|
||||
rep_->locs.PushBack(loc);
|
||||
loc->type = ReportLocationFD;
|
||||
loc->fd = fd;
|
||||
loc->tid = creat_tid;
|
||||
uptr ssz = 0;
|
||||
const uptr *stack = StackDepotGet(creat_stack, &ssz);
|
||||
if (stack) {
|
||||
StackTrace trace;
|
||||
trace.Init(stack, ssz);
|
||||
loc->stack = SymbolizeStack(trace);
|
||||
}
|
||||
ThreadContext *tctx = FindThread(creat_tid);
|
||||
if (tctx)
|
||||
AddThread(tctx);
|
||||
return;
|
||||
}
|
||||
if (allocator()->PointerIsMine((void*)addr)) {
|
||||
MBlock *b = user_mblock(0, (void*)addr);
|
||||
ThreadContext *tctx = FindThread(b->alloc_tid);
|
||||
|
|
Loading…
Reference in New Issue