123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- // Copyright 2019 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "base/profiler/suspendable_thread_delegate_win.h"
- #include <windows.h>
- #include <winternl.h>
- #include <vector>
- #include "base/check.h"
- #include "base/debug/alias.h"
- #include "base/memory/raw_ptr.h"
- #include "base/profiler/native_unwinder_win.h"
- #include "build/build_config.h"
- // IMPORTANT NOTE: Some functions within this implementation are invoked while
- // the target thread is suspended so it must not do any allocation from the
- // heap, including indirectly via use of DCHECK/CHECK or other logging
- // statements. Otherwise this code can deadlock on heap locks acquired by the
- // target thread before it was suspended. These functions are commented with "NO
- // HEAP ALLOCATIONS".
- namespace base {
- namespace {
- // The thread environment block internal type.
- struct TEB {
- NT_TIB Tib;
- // Rest of struct is ignored.
- };
- win::ScopedHandle GetCurrentThreadHandle() {
- HANDLE thread;
- CHECK(::DuplicateHandle(::GetCurrentProcess(), ::GetCurrentThread(),
- ::GetCurrentProcess(), &thread, 0, FALSE,
- DUPLICATE_SAME_ACCESS));
- return win::ScopedHandle(thread);
- }
- win::ScopedHandle GetThreadHandle(PlatformThreadId thread_id) {
- // TODO(https://crbug.com/947459): Move this logic to
- // GetSamplingProfilerCurrentThreadToken() and pass the handle in
- // SamplingProfilerThreadToken.
- if (thread_id == ::GetCurrentThreadId())
- return GetCurrentThreadHandle();
- // TODO(http://crbug.com/947459): Remove the test_handle* CHECKs once we
- // understand which flag is triggering the failure.
- DWORD flags = 0;
- base::debug::Alias(&flags);
- flags |= THREAD_GET_CONTEXT;
- win::ScopedHandle test_handle1(::OpenThread(flags, FALSE, thread_id));
- CHECK(test_handle1.is_valid());
- flags |= THREAD_QUERY_INFORMATION;
- win::ScopedHandle test_handle2(::OpenThread(flags, FALSE, thread_id));
- CHECK(test_handle2.is_valid());
- flags |= THREAD_SUSPEND_RESUME;
- win::ScopedHandle handle(::OpenThread(flags, FALSE, thread_id));
- CHECK(handle.is_valid());
- return handle;
- }
- // Returns the thread environment block pointer for |thread_handle|.
- const TEB* GetThreadEnvironmentBlock(PlatformThreadId thread_id,
- HANDLE thread_handle) {
- // TODO(https://crbug.com/947459): Move this logic to
- // GetSamplingProfilerCurrentThreadToken() and pass the TEB* in
- // SamplingProfilerThreadToken.
- if (thread_id == ::GetCurrentThreadId())
- return reinterpret_cast<TEB*>(NtCurrentTeb());
- // Define the internal types we need to invoke NtQueryInformationThread.
- enum THREAD_INFORMATION_CLASS { ThreadBasicInformation };
- struct CLIENT_ID {
- HANDLE UniqueProcess;
- HANDLE UniqueThread;
- };
- struct THREAD_BASIC_INFORMATION {
- NTSTATUS ExitStatus;
- raw_ptr<TEB> Teb;
- CLIENT_ID ClientId;
- KAFFINITY AffinityMask;
- LONG Priority;
- LONG BasePriority;
- };
- using NtQueryInformationThreadFunction =
- NTSTATUS(WINAPI*)(HANDLE, THREAD_INFORMATION_CLASS, PVOID, ULONG, PULONG);
- static const auto nt_query_information_thread =
- reinterpret_cast<NtQueryInformationThreadFunction>(::GetProcAddress(
- ::GetModuleHandle(L"ntdll.dll"), "NtQueryInformationThread"));
- if (!nt_query_information_thread)
- return nullptr;
- THREAD_BASIC_INFORMATION basic_info = {0};
- NTSTATUS status = nt_query_information_thread(
- thread_handle, ThreadBasicInformation, &basic_info,
- sizeof(THREAD_BASIC_INFORMATION), nullptr);
- if (status != 0)
- return nullptr;
- return basic_info.Teb;
- }
- // Tests whether |stack_pointer| points to a location in the guard page. NO HEAP
- // ALLOCATIONS.
- bool PointsToGuardPage(uintptr_t stack_pointer) {
- MEMORY_BASIC_INFORMATION memory_info;
- SIZE_T result = ::VirtualQuery(reinterpret_cast<LPCVOID>(stack_pointer),
- &memory_info, sizeof(memory_info));
- return result != 0 && (memory_info.Protect & PAGE_GUARD);
- }
- // ScopedDisablePriorityBoost -------------------------------------------------
- // Disables priority boost on a thread for the lifetime of the object.
- class ScopedDisablePriorityBoost {
- public:
- ScopedDisablePriorityBoost(HANDLE thread_handle);
- ScopedDisablePriorityBoost(const ScopedDisablePriorityBoost&) = delete;
- ScopedDisablePriorityBoost& operator=(const ScopedDisablePriorityBoost&) =
- delete;
- ~ScopedDisablePriorityBoost();
- private:
- HANDLE thread_handle_;
- BOOL got_previous_boost_state_;
- BOOL boost_state_was_disabled_;
- };
- // NO HEAP ALLOCATIONS.
- ScopedDisablePriorityBoost::ScopedDisablePriorityBoost(HANDLE thread_handle)
- : thread_handle_(thread_handle),
- got_previous_boost_state_(false),
- boost_state_was_disabled_(false) {
- got_previous_boost_state_ =
- ::GetThreadPriorityBoost(thread_handle_, &boost_state_was_disabled_);
- if (got_previous_boost_state_) {
- // Confusingly, TRUE disables priority boost.
- ::SetThreadPriorityBoost(thread_handle_, TRUE);
- }
- }
- ScopedDisablePriorityBoost::~ScopedDisablePriorityBoost() {
- if (got_previous_boost_state_)
- ::SetThreadPriorityBoost(thread_handle_, boost_state_was_disabled_);
- }
- } // namespace
- // ScopedSuspendThread --------------------------------------------------------
- // NO HEAP ALLOCATIONS after ::SuspendThread.
- SuspendableThreadDelegateWin::ScopedSuspendThread::ScopedSuspendThread(
- HANDLE thread_handle)
- : thread_handle_(thread_handle),
- was_successful_(::SuspendThread(thread_handle) !=
- static_cast<DWORD>(-1)) {}
- // NO HEAP ALLOCATIONS. The CHECK is OK because it provides a more noisy failure
- // mode than deadlocking.
- SuspendableThreadDelegateWin::ScopedSuspendThread::~ScopedSuspendThread() {
- if (!was_successful_)
- return;
- // Disable the priority boost that the thread would otherwise receive on
- // resume. We do this to avoid artificially altering the dynamics of the
- // executing application any more than we already are by suspending and
- // resuming the thread.
- //
- // Note that this can racily disable a priority boost that otherwise would
- // have been given to the thread, if the thread is waiting on other wait
- // conditions at the time of SuspendThread and those conditions are satisfied
- // before priority boost is reenabled. The measured length of this window is
- // ~100us, so this should occur fairly rarely.
- ScopedDisablePriorityBoost disable_priority_boost(thread_handle_);
- bool resume_thread_succeeded =
- ::ResumeThread(thread_handle_) != static_cast<DWORD>(-1);
- CHECK(resume_thread_succeeded) << "ResumeThread failed: " << GetLastError();
- }
- bool SuspendableThreadDelegateWin::ScopedSuspendThread::WasSuccessful() const {
- return was_successful_;
- }
- // SuspendableThreadDelegateWin
- // ----------------------------------------------------------
- SuspendableThreadDelegateWin::SuspendableThreadDelegateWin(
- SamplingProfilerThreadToken thread_token)
- : thread_id_(thread_token.id),
- thread_handle_(GetThreadHandle(thread_token.id)),
- thread_stack_base_address_(reinterpret_cast<uintptr_t>(
- GetThreadEnvironmentBlock(thread_token.id, thread_handle_.get())
- ->Tib.StackBase)) {}
- SuspendableThreadDelegateWin::~SuspendableThreadDelegateWin() = default;
- std::unique_ptr<SuspendableThreadDelegate::ScopedSuspendThread>
- SuspendableThreadDelegateWin::CreateScopedSuspendThread() {
- return std::make_unique<ScopedSuspendThread>(thread_handle_.get());
- }
- PlatformThreadId SuspendableThreadDelegateWin::GetThreadId() const {
- return thread_id_;
- }
- // NO HEAP ALLOCATIONS.
- bool SuspendableThreadDelegateWin::GetThreadContext(CONTEXT* thread_context) {
- *thread_context = {0};
- thread_context->ContextFlags = CONTEXT_FULL;
- return ::GetThreadContext(thread_handle_.get(), thread_context) != 0;
- }
- // NO HEAP ALLOCATIONS.
- uintptr_t SuspendableThreadDelegateWin::GetStackBaseAddress() const {
- return thread_stack_base_address_;
- }
- // Tests whether |stack_pointer| points to a location in the guard page. NO HEAP
- // ALLOCATIONS.
- bool SuspendableThreadDelegateWin::CanCopyStack(uintptr_t stack_pointer) {
- // Dereferencing a pointer in the guard page in a thread that doesn't own the
- // stack results in a STATUS_GUARD_PAGE_VIOLATION exception and a crash. This
- // occurs very rarely, but reliably over the population.
- return !PointsToGuardPage(stack_pointer);
- }
- std::vector<uintptr_t*> SuspendableThreadDelegateWin::GetRegistersToRewrite(
- CONTEXT* thread_context) {
- // Return the set of non-volatile registers.
- return {
- #if defined(ARCH_CPU_X86_64)
- &thread_context->R12, &thread_context->R13, &thread_context->R14,
- &thread_context->R15, &thread_context->Rdi, &thread_context->Rsi,
- &thread_context->Rbx, &thread_context->Rbp, &thread_context->Rsp
- #elif defined(ARCH_CPU_ARM64)
- &thread_context->X19, &thread_context->X20, &thread_context->X21,
- &thread_context->X22, &thread_context->X23, &thread_context->X24,
- &thread_context->X25, &thread_context->X26, &thread_context->X27,
- &thread_context->X28, &thread_context->Fp, &thread_context->Lr,
- &thread_context->Sp
- #endif
- };
- }
- } // namespace base
|