| // Copyright 2019 The Chromium Authors |
| // 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_exclusion.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 types not in winternl.h needed to invoke NtQueryInformationThread(). |
| constexpr auto ThreadBasicInformation = static_cast<THREADINFOCLASS>(0); |
| struct THREAD_BASIC_INFORMATION { |
| NTSTATUS ExitStatus; |
| RAW_PTR_EXCLUSION TEB* Teb; // Filled in by the OS so cannot use raw_ptr<>. |
| CLIENT_ID ClientId; |
| KAFFINITY AffinityMask; |
| LONG Priority; |
| LONG BasePriority; |
| }; |
| |
| THREAD_BASIC_INFORMATION basic_info = {0}; |
| NTSTATUS status = ::NtQueryInformationThread( |
| 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 |