| //===-- 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" |
| |
| 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)); |
| 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 { |
| 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) |
| continue; |
| |
| bool suspended_thread = false; |
| for (const auto thread_id : suspended_threads_list.threadIds) { |
| if (thread_id == thread_entry.th32ThreadID) { |
| suspended_thread = true; |
| break; |
| } |
| } |
| |
| // Skip the Thread if it was already suspended |
| if (suspended_thread) |
| continue; |
| |
| const HANDLE thread = |
| OpenThread(THREAD_ALL_ACCESS, FALSE, thread_entry.th32ThreadID); |
| CHECK(thread); |
| |
| if (SuspendThread(thread) == (DWORD)-1) { |
| 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); |
| new_thread_found = true; |
| } while (Thread32Next(threads, &thread_entry)); |
| |
| 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. |
| } 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 |