Changeset View
Standalone View
compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_win.cpp
- This file was added.
//===-- sanitizer_stoptheworld_win.cpp ------------------------------------===// | |||||
// | |||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |||||
// See https://llvm.org/LICENSE.txt for license information. | |||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |||||
// | |||||
//===----------------------------------------------------------------------===// | |||||
// | |||||
// See sanitizer_stoptheworld.h for details. | |||||
// | |||||
//===----------------------------------------------------------------------===// | |||||
#include "sanitizer_platform.h" | |||||
#if SANITIZER_WINDOWS | |||||
# define WIN32_LEAN_AND_MEAN | |||||
# include <windows.h> | |||||
// windows.h needs to be included before tlhelp32.h | |||||
# include <tlhelp32.h> | |||||
# include "sanitizer_stoptheworld.h" | |||||
mstorsjo: This broke building on MinGW. When building in MinGW configurations, it's built with `… | |||||
namespace __sanitizer { | |||||
namespace { | |||||
struct SuspendedThreadsListWindows final : public SuspendedThreadsList { | |||||
InternalMmapVector<HANDLE> threadHandles; | |||||
InternalMmapVector<DWORD> threadIds; | |||||
SuspendedThreadsListWindows() { | |||||
threadIds.reserve(1024); | |||||
threadHandles.reserve(1024); | |||||
} | |||||
PtraceRegistersStatus GetRegistersAndSP(uptr index, | |||||
InternalMmapVector<uptr> *buffer, | |||||
uptr *sp) const override; | |||||
tid_t GetThreadID(uptr index) const override; | |||||
uptr ThreadCount() const override; | |||||
}; | |||||
// Stack Pointer register names on different architectures | |||||
# if SANITIZER_X64 | |||||
# define SP_REG Rsp | |||||
# elif SANITIZER_I386 | |||||
# define SP_REG Esp | |||||
# elif SANITIZER_ARM | SANITIZER_ARM64 | |||||
# define SP_REG Sp | |||||
# else | |||||
# error Architecture not supported! | |||||
# endif | |||||
PtraceRegistersStatus SuspendedThreadsListWindows::GetRegistersAndSP( | |||||
uptr index, InternalMmapVector<uptr> *buffer, uptr *sp) const { | |||||
CHECK_LT(index, threadHandles.size()); | |||||
buffer->resize(RoundUpTo(sizeof(CONTEXT), sizeof(uptr)) / sizeof(uptr)); | |||||
This fails to build on i386 (where things build successfully before), as you'd have to use .Esp instead of .Rsp there. mstorsjo: This fails to build on i386 (where things build successfully before), as you'd have to use `. | |||||
CONTEXT *thread_context = reinterpret_cast<CONTEXT *>(buffer->data()); | |||||
thread_context->ContextFlags = CONTEXT_ALL; | |||||
CHECK(GetThreadContext(threadHandles[index], thread_context)); | |||||
*sp = thread_context->SP_REG; | |||||
return REGISTERS_AVAILABLE; | |||||
} | |||||
tid_t SuspendedThreadsListWindows::GetThreadID(uptr index) const { | |||||
CHECK_LT(index, threadIds.size()); | |||||
return threadIds[index]; | |||||
} | |||||
uptr SuspendedThreadsListWindows::ThreadCount() const { | |||||
return threadIds.size(); | |||||
} | |||||
struct RunThreadArgs { | |||||
I reviewed this code, it looks reasonable to me, but I don't have a lot of time to do a careful review. In the future, we should identify a new Windows sanitizer code owner. rnk: I reviewed this code, it looks reasonable to me, but I don't have a lot of time to do a careful… | |||||
StopTheWorldCallback callback; | |||||
void *argument; | |||||
}; | |||||
DWORD WINAPI RunThread(void *argument) { | |||||
RunThreadArgs *run_args = (RunThreadArgs *)argument; | |||||
const DWORD this_thread = GetCurrentThreadId(); | |||||
const DWORD this_process = GetCurrentProcessId(); | |||||
SuspendedThreadsListWindows suspended_threads_list; | |||||
bool new_thread_found; | |||||
do { | |||||
// Take a snapshot of all Threads | |||||
const HANDLE threads = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); | |||||
CHECK(threads != INVALID_HANDLE_VALUE); | |||||
THREADENTRY32 thread_entry; | |||||
thread_entry.dwSize = sizeof(thread_entry); | |||||
new_thread_found = false; | |||||
if (!Thread32First(threads, &thread_entry)) | |||||
break; | |||||
do { | |||||
if (thread_entry.th32ThreadID == this_thread || | |||||
thread_entry.th32OwnerProcessID != this_process) | |||||
This is the one thing that fails to build when leaving out <algorithm>. mstorsjo: This is the one thing that fails to build when leaving out `<algorithm>`. | |||||
yep, we don't use STL in sanitizer runtimes vitalybuka: yep, we don't use STL in sanitizer runtimes | |||||
continue; | |||||
// Skip the Thread if it was already suspended | |||||
for (const auto thread_id : suspended_threads_list.threadIds) { | |||||
if (thread_id == thread_entry.th32ThreadID) { | |||||
continue; | |||||
mstorsjoUnsubmitted This continue will only affect the current for loop, which probably isn’t what you want mstorsjo: This continue will only affect the current for loop, which probably isn’t what you want | |||||
clemenswasserAuthorUnsubmitted Good catch, thanks! clemenswasser: Good catch, thanks! | |||||
} | |||||
} | |||||
const HANDLE thread = | |||||
OpenThread(THREAD_ALL_ACCESS, FALSE, thread_entry.th32ThreadID); | |||||
CHECK(thread); | |||||
if (SuspendThread(thread) == -1) { | |||||
what if SuspendThread failed? vitalybuka: what if SuspendThread failed? | |||||
DWORD last_error = GetLastError(); | |||||
VPrintf(1, "Could not suspend thread %lu (error %lu)", | |||||
thread_entry.th32ThreadID, last_error); | |||||
continue; | |||||
} | |||||
suspended_threads_list.threadIds.push_back(thread_entry.th32ThreadID); | |||||
suspended_threads_list.threadHandles.push_back(thread); | |||||
Why do we crash here? vitalybuka: Why do we crash here? | |||||
What do you mean by that. I don't crash in the __except block? Did I miss something? clemenswasser: What do you mean by that. I don't crash in the `__except` block? Did I miss something? | |||||
Why w have exceptions here? What do we need to catch with __try? vitalybuka: Why w have exceptions here? What do we need to catch with __try? | |||||
We catch for example the STATUS_ACCESS_VIOLATION hardware exception and more, which gets thrown in the SegvInCallback test. clemenswasser: We catch for example the `STATUS_ACCESS_VIOLATION` hardware exception and [more](https://docs. | |||||
My reading of the comments suggests that we don't need to catch exceptions to make that test pass. On Linux, the suspender thread blocks most signals, which presumably interacts really badly with SIGSEGV: the suspender thread (or process? unclear) gets killed, but the user threads remain suspended. This ends up looking like a timeout, which is bad. I think the test exists to ensure that crashes during StopTheWorld don't hang, not to show that the process recovers. Exiting the process with an error is fine. If that just happens naturally, I suggest disabling the SegvInCallback test on Windows. rnk: My reading of the comments suggests that we don't need to catch exceptions to make that test… | |||||
new_thread_found = true; | |||||
} while (Thread32Next(threads, &thread_entry)); | |||||
VPrintf(1, vitalybuka: VPrintf(1, | |||||
CloseHandle(threads); | |||||
// Between the call to `CreateToolhelp32Snapshot` and suspending the | |||||
// relevant Threads, new Threads could have potentially been created. So | |||||
// continue to find and suspend new Threads until we don't find any. | |||||
check return ResumeThread? vitalybuka: check return ResumeThread? | |||||
} while (new_thread_found); | |||||
// Now all Threads of this Process except of this Thread should be suspended. | |||||
// Execute the callback function. | |||||
run_args->callback(suspended_threads_list, run_args->argument); | |||||
// Resume all Threads | |||||
for (const auto suspended_thread_handle : | |||||
suspended_threads_list.threadHandles) { | |||||
CHECK_NE(ResumeThread(suspended_thread_handle), -1); | |||||
CloseHandle(suspended_thread_handle); | |||||
} | |||||
return 0; | |||||
} | |||||
} // namespace | |||||
void StopTheWorld(StopTheWorldCallback callback, void *argument) { | |||||
struct RunThreadArgs arg = {callback, argument}; | |||||
DWORD trace_thread_id; | |||||
auto trace_thread = | |||||
CreateThread(nullptr, 0, RunThread, &arg, 0, &trace_thread_id); | |||||
CHECK(trace_thread); | |||||
WaitForSingleObject(trace_thread, INFINITE); | |||||
CloseHandle(trace_thread); | |||||
} | |||||
} // namespace __sanitizer | |||||
#endif // SANITIZER_WINDOWS |
This broke building on MinGW. When building in MinGW configurations, it's built with -nostdinc++ (to make sure that sanitizers don't end up depending on the C++ standard I presume?), which makes <algorithm> not be found.
Can you make the code not rely on that header?