[ASan/Win] Fix a CHECK failure when an exception is thrown from a callback passed to BindIoCompletionCallback
This also simplifies how we handle QueueUserWorkItem llvm-svn: 232499
This commit is contained in:
parent
0b16859805
commit
d58230b9dc
|
@ -102,61 +102,51 @@ INTERCEPTOR_WINAPI(DWORD, CreateThread,
|
|||
}
|
||||
|
||||
namespace {
|
||||
struct UserWorkItemInfo {
|
||||
DWORD (__stdcall *function)(void *arg);
|
||||
void *arg;
|
||||
u32 parent_tid;
|
||||
};
|
||||
|
||||
BlockingMutex mu_for_thread_tracking(LINKER_INITIALIZED);
|
||||
|
||||
// QueueUserWorkItem may silently create a thread we should keep track of.
|
||||
// We achieve this by wrapping the user-supplied work items with our function.
|
||||
DWORD __stdcall QueueUserWorkItemWrapper(void *arg) {
|
||||
UserWorkItemInfo *item = (UserWorkItemInfo *)arg;
|
||||
void EnsureWorkerThreadRegistered() {
|
||||
// FIXME: GetCurrentThread relies on TSD, which might not play well with
|
||||
// system thread pools. We might want to use something like reference
|
||||
// counting to zero out GetCurrentThread() underlying storage when the last
|
||||
// work item finishes? Or can we disable reclaiming of threads in the pool?
|
||||
BlockingMutexLock l(&mu_for_thread_tracking);
|
||||
if (__asan::GetCurrentThread())
|
||||
return;
|
||||
|
||||
{
|
||||
// FIXME: GetCurrentThread relies on TSD, which might not play well with
|
||||
// system thread pools. We might want to use something like reference
|
||||
// counting to zero out GetCurrentThread() underlying storage when the last
|
||||
// work item finishes? Or can we disable reclaiming of threads in the pool?
|
||||
BlockingMutexLock l(&mu_for_thread_tracking);
|
||||
AsanThread *t = __asan::GetCurrentThread();
|
||||
if (!t) {
|
||||
GET_STACK_TRACE_THREAD;
|
||||
t = AsanThread::Create(/* start_routine */ nullptr, /* arg */ nullptr,
|
||||
item->parent_tid, &stack, /* detached */ true);
|
||||
t->Init();
|
||||
asanThreadRegistry().StartThread(t->tid(), 0, 0);
|
||||
SetCurrentThread(t);
|
||||
}
|
||||
}
|
||||
|
||||
DWORD ret = item->function(item->arg);
|
||||
delete item;
|
||||
return ret;
|
||||
AsanThread *t = AsanThread::Create(
|
||||
/* start_routine */ nullptr, /* arg */ nullptr,
|
||||
/* parent_tid */ -1, /* stack */ nullptr, /* detached */ true);
|
||||
t->Init();
|
||||
asanThreadRegistry().StartThread(t->tid(), 0, 0);
|
||||
SetCurrentThread(t);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
INTERCEPTOR_WINAPI(BOOL, QueueUserWorkItem, LPTHREAD_START_ROUTINE function,
|
||||
PVOID arg, ULONG flags) {
|
||||
UserWorkItemInfo *work_item_info = new UserWorkItemInfo;
|
||||
work_item_info->function = function;
|
||||
work_item_info->arg = arg;
|
||||
work_item_info->parent_tid = GetCurrentTidOrInvalid();
|
||||
return REAL(QueueUserWorkItem)(QueueUserWorkItemWrapper,
|
||||
work_item_info, flags);
|
||||
INTERCEPTOR_WINAPI(DWORD, NtWaitForWorkViaWorkerFactory, DWORD a, DWORD b) {
|
||||
// NtWaitForWorkViaWorkerFactory is called from system worker pool threads to
|
||||
// query work scheduled by BindIoCompletionCallback, QueueUserWorkItem, etc.
|
||||
// System worker pool threads are created at arbitraty point in time and
|
||||
// without using CreateThread, so we wrap NtWaitForWorkViaWorkerFactory
|
||||
// instead and don't register a specific parent_tid/stack.
|
||||
EnsureWorkerThreadRegistered();
|
||||
return REAL(NtWaitForWorkViaWorkerFactory)(a, b);
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
namespace __asan {
|
||||
|
||||
void InitializePlatformInterceptors() {
|
||||
ASAN_INTERCEPT_FUNC(CreateThread);
|
||||
ASAN_INTERCEPT_FUNC(QueueUserWorkItem);
|
||||
ASAN_INTERCEPT_FUNC(RaiseException);
|
||||
ASAN_INTERCEPT_FUNC(_except_handler3);
|
||||
ASAN_INTERCEPT_FUNC(_except_handler4);
|
||||
|
||||
// NtWaitForWorkViaWorkerFactory is always linked dynamically.
|
||||
CHECK(::__interception::OverrideFunction(
|
||||
"NtWaitForWorkViaWorkerFactory",
|
||||
(uptr)WRAP(NtWaitForWorkViaWorkerFactory),
|
||||
(uptr *)&REAL(NtWaitForWorkViaWorkerFactory)));
|
||||
}
|
||||
|
||||
// ---------------------- TSD ---------------- {{{
|
||||
|
|
|
@ -84,6 +84,7 @@ static size_t RoundUpToInstrBoundary(size_t size, char *code) {
|
|||
cursor += 2;
|
||||
continue;
|
||||
case '\xE9': // E9 XX YY ZZ WW = jmp WWZZYYXX
|
||||
case '\xB8': // B8 XX YY ZZ WW = mov eax, WWZZYYXX
|
||||
cursor += 5;
|
||||
continue;
|
||||
}
|
||||
|
@ -182,10 +183,14 @@ bool OverrideFunction(uptr old_func, uptr new_func, uptr *orig_old_func) {
|
|||
}
|
||||
|
||||
static const void **InterestingDLLsAvailable() {
|
||||
const char *InterestingDLLs[] = {"kernel32.dll",
|
||||
"msvcr110.dll", // VS2012
|
||||
"msvcr120.dll", // VS2013
|
||||
NULL};
|
||||
const char *InterestingDLLs[] = {
|
||||
"kernel32.dll",
|
||||
"msvcr110.dll", // VS2012
|
||||
"msvcr120.dll", // VS2013
|
||||
// NTDLL should go last as it exports some functions that we should override
|
||||
// in the CRT [presumably only used internally].
|
||||
"ntdll.dll", NULL
|
||||
};
|
||||
static void *result[ARRAY_SIZE(InterestingDLLs)] = { 0 };
|
||||
if (!result[0]) {
|
||||
for (size_t i = 0, j = 0; InterestingDLLs[i]; ++i) {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
// Make sure we can throw exceptions from work items executed via
|
||||
// BindIoCompletionCallback.
|
||||
//
|
||||
// Clang doesn't support exceptions on Windows yet, so for the time being we
|
||||
// build this program in two parts: the code with exceptions is built with CL,
|
||||
// the rest is built with Clang. This represents the typical scenario when we
|
||||
// build a large project using "clang-cl -fallback -fsanitize=address".
|
||||
//
|
||||
// RUN: cl -c %s -Fo%t.obj
|
||||
// RUN: %clangxx_asan -o %t.exe %s %t.obj
|
||||
// RUN: %run %t.exe 2>&1 | FileCheck %s
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void ThrowAndCatch();
|
||||
|
||||
#if !defined(__clang__)
|
||||
__declspec(noinline)
|
||||
void Throw() {
|
||||
fprintf(stderr, "Throw\n");
|
||||
// CHECK: Throw
|
||||
throw 1;
|
||||
}
|
||||
|
||||
void ThrowAndCatch() {
|
||||
int local;
|
||||
try {
|
||||
Throw();
|
||||
} catch(...) {
|
||||
fprintf(stderr, "Catch\n");
|
||||
// CHECK: Catch
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
char buffer[65536];
|
||||
HANDLE done;
|
||||
OVERLAPPED ov;
|
||||
|
||||
void CALLBACK completion_callback(DWORD error, DWORD bytesRead,
|
||||
LPOVERLAPPED pov) {
|
||||
ThrowAndCatch();
|
||||
SetEvent(done);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
done = CreateEvent(0, false, false, "job is done");
|
||||
if (!done)
|
||||
return 1;
|
||||
HANDLE file = CreateFile(
|
||||
argv[0], GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
|
||||
NULL);
|
||||
if (!file)
|
||||
return 2;
|
||||
if (!BindIoCompletionCallback(file, completion_callback, 0))
|
||||
return 3;
|
||||
|
||||
if (!ReadFile(file, buffer, sizeof(buffer), NULL, &ov) &&
|
||||
GetLastError() != ERROR_IO_PENDING)
|
||||
return 4;
|
||||
|
||||
if (WAIT_OBJECT_0 != WaitForSingleObject(done, INFINITE))
|
||||
return 5;
|
||||
fprintf(stderr, "Done!\n");
|
||||
// CHECK: Done!
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,29 @@
|
|||
// RUN: %clang_cl_asan -O0 %s -Fe%t
|
||||
// RUN: not %run %t 2>&1 | FileCheck %s
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
HANDLE done;
|
||||
|
||||
DWORD CALLBACK work_item(LPVOID) {
|
||||
int subscript = -1;
|
||||
volatile char stack_buffer[42];
|
||||
stack_buffer[subscript] = 42;
|
||||
// CHECK: AddressSanitizer: stack-buffer-underflow on address [[ADDR:0x[0-9a-f]+]]
|
||||
// CHECK: WRITE of size 1 at [[ADDR]] thread T1
|
||||
// CHECK: {{#0 .* work_item .*queue_user_work_item_report.cc}}:[[@LINE-3]]
|
||||
// CHECK: Address [[ADDR]] is located in stack of thread T1 at offset {{.*}} in frame
|
||||
// CHECK: work_item
|
||||
SetEvent(done);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
done = CreateEvent(0, false, false, "job is done");
|
||||
if (!done)
|
||||
return 1;
|
||||
// CHECK-NOT: Thread T1 created
|
||||
QueueUserWorkItem(&work_item, nullptr, 0);
|
||||
if (WAIT_OBJECT_0 != WaitForSingleObject(done, INFINITE))
|
||||
return 2;
|
||||
}
|
Loading…
Reference in New Issue