// 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 #include #include "base/debug/alias.h" #include "base/logging.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 GetThreadHandle(PlatformThreadId thread_id) { // 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.IsValid()); flags |= THREAD_QUERY_INFORMATION; win::ScopedHandle test_handle2(::OpenThread(flags, FALSE, thread_id)); CHECK(test_handle2.IsValid()); flags |= THREAD_SUSPEND_RESUME; win::ScopedHandle handle(::OpenThread(flags, FALSE, thread_id)); CHECK(handle.IsValid()); return handle; } // Returns the thread environment block pointer for |thread_handle|. const TEB* GetThreadEnvironmentBlock(HANDLE thread_handle) { // 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; 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(::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(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(); private: HANDLE thread_handle_; BOOL got_previous_boost_state_; BOOL boost_state_was_disabled_; DISALLOW_COPY_AND_ASSIGN(ScopedDisablePriorityBoost); }; // 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(-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(-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( GetThreadEnvironmentBlock(thread_handle_.Get())->Tib.StackBase)) {} SuspendableThreadDelegateWin::~SuspendableThreadDelegateWin() = default; std::unique_ptr SuspendableThreadDelegateWin::CreateScopedSuspendThread() { return std::make_unique(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 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