Skip to content

Commit 9b4f179

Browse files
clemenswasservitalybuka
authored andcommitted
Port __sanitizer::StopTheWorld to Windows
This also makes the sanitizer_stoptheworld_test cross-platform by using the STL, rather than pthread. Reviewed By: vitalybuka Differential Revision: https://reviews.llvm.org/D115204
1 parent cb63ad8 commit 9b4f179

File tree

3 files changed

+184
-2
lines changed

3 files changed

+184
-2
lines changed

compiler-rt/lib/sanitizer_common/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ set(SANITIZER_SOURCES_NOTERMINATION
3434
sanitizer_solaris.cpp
3535
sanitizer_stoptheworld_fuchsia.cpp
3636
sanitizer_stoptheworld_mac.cpp
37+
sanitizer_stoptheworld_win.cpp
3738
sanitizer_suppressions.cpp
3839
sanitizer_tls_get_addr.cpp
3940
sanitizer_thread_registry.cpp
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//===-- sanitizer_stoptheworld_win.cpp ------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// See sanitizer_stoptheworld.h for details.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "sanitizer_platform.h"
14+
15+
#if SANITIZER_WINDOWS
16+
17+
# define WIN32_LEAN_AND_MEAN
18+
# include <windows.h>
19+
// windows.h needs to be included before tlhelp32.h
20+
# include <tlhelp32.h>
21+
22+
# include "sanitizer_stoptheworld.h"
23+
24+
namespace __sanitizer {
25+
26+
namespace {
27+
28+
struct SuspendedThreadsListWindows final : public SuspendedThreadsList {
29+
InternalMmapVector<HANDLE> threadHandles;
30+
InternalMmapVector<DWORD> threadIds;
31+
32+
SuspendedThreadsListWindows() {
33+
threadIds.reserve(1024);
34+
threadHandles.reserve(1024);
35+
}
36+
37+
PtraceRegistersStatus GetRegistersAndSP(uptr index,
38+
InternalMmapVector<uptr> *buffer,
39+
uptr *sp) const override;
40+
41+
tid_t GetThreadID(uptr index) const override;
42+
uptr ThreadCount() const override;
43+
};
44+
45+
// Stack Pointer register names on different architectures
46+
# if SANITIZER_X64
47+
# define SP_REG Rsp
48+
# elif SANITIZER_I386
49+
# define SP_REG Esp
50+
# elif SANITIZER_ARM | SANITIZER_ARM64
51+
# define SP_REG Sp
52+
# else
53+
# error Architecture not supported!
54+
# endif
55+
56+
PtraceRegistersStatus SuspendedThreadsListWindows::GetRegistersAndSP(
57+
uptr index, InternalMmapVector<uptr> *buffer, uptr *sp) const {
58+
CHECK_LT(index, threadHandles.size());
59+
60+
buffer->resize(RoundUpTo(sizeof(CONTEXT), sizeof(uptr)) / sizeof(uptr));
61+
CONTEXT *thread_context = reinterpret_cast<CONTEXT *>(buffer->data());
62+
thread_context->ContextFlags = CONTEXT_ALL;
63+
CHECK(GetThreadContext(threadHandles[index], thread_context));
64+
*sp = thread_context->SP_REG;
65+
66+
return REGISTERS_AVAILABLE;
67+
}
68+
69+
tid_t SuspendedThreadsListWindows::GetThreadID(uptr index) const {
70+
CHECK_LT(index, threadIds.size());
71+
return threadIds[index];
72+
}
73+
74+
uptr SuspendedThreadsListWindows::ThreadCount() const {
75+
return threadIds.size();
76+
}
77+
78+
struct RunThreadArgs {
79+
StopTheWorldCallback callback;
80+
void *argument;
81+
};
82+
83+
DWORD WINAPI RunThread(void *argument) {
84+
RunThreadArgs *run_args = (RunThreadArgs *)argument;
85+
86+
const DWORD this_thread = GetCurrentThreadId();
87+
const DWORD this_process = GetCurrentProcessId();
88+
89+
SuspendedThreadsListWindows suspended_threads_list;
90+
bool new_thread_found;
91+
92+
do {
93+
// Take a snapshot of all Threads
94+
const HANDLE threads = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
95+
CHECK(threads != INVALID_HANDLE_VALUE);
96+
97+
THREADENTRY32 thread_entry;
98+
thread_entry.dwSize = sizeof(thread_entry);
99+
new_thread_found = false;
100+
101+
if (!Thread32First(threads, &thread_entry))
102+
break;
103+
104+
do {
105+
if (thread_entry.th32ThreadID == this_thread ||
106+
thread_entry.th32OwnerProcessID != this_process)
107+
continue;
108+
109+
bool suspended_thread = false;
110+
for (const auto thread_id : suspended_threads_list.threadIds) {
111+
if (thread_id == thread_entry.th32ThreadID) {
112+
suspended_thread = true;
113+
break;
114+
}
115+
}
116+
117+
// Skip the Thread if it was already suspended
118+
if (suspended_thread)
119+
continue;
120+
121+
const HANDLE thread =
122+
OpenThread(THREAD_ALL_ACCESS, FALSE, thread_entry.th32ThreadID);
123+
CHECK(thread);
124+
125+
if (SuspendThread(thread) == -1) {
126+
DWORD last_error = GetLastError();
127+
128+
VPrintf(1, "Could not suspend thread %lu (error %lu)",
129+
thread_entry.th32ThreadID, last_error);
130+
continue;
131+
}
132+
133+
suspended_threads_list.threadIds.push_back(thread_entry.th32ThreadID);
134+
suspended_threads_list.threadHandles.push_back(thread);
135+
new_thread_found = true;
136+
} while (Thread32Next(threads, &thread_entry));
137+
138+
CloseHandle(threads);
139+
140+
// Between the call to `CreateToolhelp32Snapshot` and suspending the
141+
// relevant Threads, new Threads could have potentially been created. So
142+
// continue to find and suspend new Threads until we don't find any.
143+
} while (new_thread_found);
144+
145+
// Now all Threads of this Process except of this Thread should be suspended.
146+
// Execute the callback function.
147+
run_args->callback(suspended_threads_list, run_args->argument);
148+
149+
// Resume all Threads
150+
for (const auto suspended_thread_handle :
151+
suspended_threads_list.threadHandles) {
152+
CHECK_NE(ResumeThread(suspended_thread_handle), -1);
153+
CloseHandle(suspended_thread_handle);
154+
}
155+
156+
return 0;
157+
}
158+
159+
} // namespace
160+
161+
void StopTheWorld(StopTheWorldCallback callback, void *argument) {
162+
struct RunThreadArgs arg = {callback, argument};
163+
DWORD trace_thread_id;
164+
165+
auto trace_thread =
166+
CreateThread(nullptr, 0, RunThread, &arg, 0, &trace_thread_id);
167+
CHECK(trace_thread);
168+
169+
WaitForSingleObject(trace_thread, INFINITE);
170+
CloseHandle(trace_thread);
171+
}
172+
173+
} // namespace __sanitizer
174+
175+
#endif // SANITIZER_WINDOWS

compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#include "sanitizer_common/sanitizer_stoptheworld.h"
1414

1515
#include "sanitizer_common/sanitizer_platform.h"
16-
#if SANITIZER_LINUX && defined(__x86_64__)
16+
#if (SANITIZER_LINUX || SANITIZER_WINDOWS) && defined(__x86_64__)
1717

1818
# include <atomic>
1919
# include <mutex>
@@ -155,7 +155,13 @@ static void SegvCallback(const SuspendedThreadsList &suspended_threads_list,
155155
*(volatile int *)0x1234 = 0;
156156
}
157157

158-
TEST(StopTheWorld, SegvInCallback) {
158+
# if SANITIZER_WINDOWS
159+
# define MAYBE_SegvInCallback DISABLED_SegvInCallback
160+
# else
161+
# define MAYBE_SegvInCallback SegvInCallback
162+
# endif
163+
164+
TEST(StopTheWorld, MAYBE_SegvInCallback) {
159165
// Test that tracer thread catches SIGSEGV.
160166
StopTheWorld(&SegvCallback, NULL);
161167
}

0 commit comments

Comments
 (0)