blob: c2afb25a6f4300e850599ba5e10f3bc3e88a8f8a [file] [log] [blame]
// 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 "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);
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();
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<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(
PlatformThreadId thread_id)
: thread_handle_(GetThreadHandle(thread_id)),
thread_stack_base_address_(reinterpret_cast<uintptr_t>(
GetThreadEnvironmentBlock(thread_handle_.Get())->Tib.StackBase)) {}
SuspendableThreadDelegateWin::~SuspendableThreadDelegateWin() = default;
std::unique_ptr<SuspendableThreadDelegate::ScopedSuspendThread>
SuspendableThreadDelegateWin::CreateScopedSuspendThread() {
return std::make_unique<ScopedSuspendThread>(thread_handle_.Get());
}
// 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
#endif
};
}
} // namespace base