Commit f21df890 authored by Mike Wittman's avatar Mike Wittman Committed by Commit Bot

[Sampling profiler] Create cross-platform sampler implementation

Refactors the cross-platform parts of the stack sampling into
the new StackSamplerImpl implementation of the StackSampler
interface. Platform-specific aspects of stack sampling are refactored
into the ThreadDelegate/ThreadDelegateWin interface/implementation.

Refactoring the Mac sampler to make use of StackSamplerImpl will be
addressed in the next change.

Bug: 931418
Change-Id: I204257fec31b23fc20c7beceddb81b3a38a9466d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1530009
Auto-Submit: Mike Wittman <wittman@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: default avatarCharlie Andrews <charliea@chromium.org>
Cr-Commit-Position: refs/heads/master@{#644448}
parent f720763f
...@@ -614,12 +614,18 @@ jumbo_component("base") { ...@@ -614,12 +614,18 @@ jumbo_component("base") {
"process/process_win.cc", "process/process_win.cc",
"profiler/profile_builder.cc", "profiler/profile_builder.cc",
"profiler/profile_builder.h", "profiler/profile_builder.h",
"profiler/register_context.h",
"profiler/stack_sampler.cc", "profiler/stack_sampler.cc",
"profiler/stack_sampler.h", "profiler/stack_sampler.h",
"profiler/stack_sampler_impl.cc",
"profiler/stack_sampler_impl.h",
"profiler/stack_sampler_mac.cc", "profiler/stack_sampler_mac.cc",
"profiler/stack_sampler_win.cc", "profiler/stack_sampler_win.cc",
"profiler/stack_sampling_profiler.cc", "profiler/stack_sampling_profiler.cc",
"profiler/stack_sampling_profiler.h", "profiler/stack_sampling_profiler.h",
"profiler/thread_delegate.h",
"profiler/thread_delegate_win.cc",
"profiler/thread_delegate_win.h",
"profiler/unwind_result.h", "profiler/unwind_result.h",
"rand_util.cc", "rand_util.cc",
"rand_util.h", "rand_util.h",
...@@ -2486,6 +2492,7 @@ test("base_unittests") { ...@@ -2486,6 +2492,7 @@ test("base_unittests") {
"process/process_metrics_unittest.cc", "process/process_metrics_unittest.cc",
"process/process_unittest.cc", "process/process_unittest.cc",
"process/process_util_unittest.cc", "process/process_util_unittest.cc",
"profiler/stack_sampler_impl_unittest.cc",
"profiler/stack_sampling_profiler_unittest.cc", "profiler/stack_sampling_profiler_unittest.cc",
"rand_util_unittest.cc", "rand_util_unittest.cc",
"run_loop_unittest.cc", "run_loop_unittest.cc",
......
// 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.
//
// This file provides the RegisterContext cross-platform typedef that represents
// the native register context for the platform, plus functions that provide
// access to key registers in the context.
#ifndef BASE_PROFILER_REGISTER_CONTEXT_H_
#define BASE_PROFILER_REGISTER_CONTEXT_H_
#include "build/build_config.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
#if defined(OS_WIN)
using RegisterContext = ::CONTEXT;
inline uintptr_t& RegisterContextStackPointer(::CONTEXT* context) {
#if defined(ARCH_CPU_X86_64)
return context->Rsp;
#elif defined(ARCH_CPU_ARM64)
return context->Sp;
#else
// The reinterpret_cast accounts for the fact that Esp is a DWORD, which is an
// unsigned long, while uintptr_t is an unsigned int. The two types have the
// same representation on Windows, but C++ treats them as different.
return *reinterpret_cast<uintptr_t*>(&context->Esp);
#endif
}
inline uintptr_t& RegisterContextFramePointer(::CONTEXT* context) {
#if defined(ARCH_CPU_X86_64)
return context->Rbp;
#elif defined(ARCH_CPU_ARM64)
return context->Fp;
#else
// The reinterpret_cast accounts for the fact that Ebp is a DWORD, which is an
// unsigned long, while uintptr_t is an unsigned int. The two types have the
// same representation on Windows, but C++ treats them as different.
return *reinterpret_cast<uintptr_t*>(&context->Ebp);
#endif
}
#else // #if defined(OS_WIN)
// Placeholders for other platforms.
struct RegisterContext {
uintptr_t stack_pointer;
uintptr_t frame_pointer;
};
inline uintptr_t& RegisterContextStackPointer(RegisterContext* context) {
return context->stack_pointer;
}
inline uintptr_t& RegisterContextFramePointer(RegisterContext* context) {
return context->frame_pointer;
}
#endif // #if defined(OS_WIN)
#endif // BASE_PROFILER_REGISTER_CONTEXT_H_
...@@ -20,11 +20,11 @@ class StackSamplerTestDelegate; ...@@ -20,11 +20,11 @@ class StackSamplerTestDelegate;
// StackSampler is an implementation detail of StackSamplingProfiler. It // StackSampler is an implementation detail of StackSamplingProfiler. It
// abstracts the native implementation required to record a set of stack frames // abstracts the native implementation required to record a set of stack frames
// for a given thread. // for a given thread.
class StackSampler { class BASE_EXPORT StackSampler {
public: public:
// This class contains a buffer for stack copies that can be shared across // This class contains a buffer for stack copies that can be shared across
// multiple instances of StackSampler. // multiple instances of StackSampler.
class StackBuffer { class BASE_EXPORT StackBuffer {
public: public:
StackBuffer(size_t buffer_size); StackBuffer(size_t buffer_size);
~StackBuffer(); ~StackBuffer();
......
// 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/stack_sampler_impl.h"
#include <utility>
#include "base/logging.h"
#include "base/profiler/thread_delegate.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 {
// If the value at |pointer| points to the original stack, rewrite it to point
// to the corresponding location in the copied stack. NO HEAP ALLOCATIONS.
// static
uintptr_t RewritePointerIfInOriginalStack(
const uintptr_t* original_stack_bottom,
const uintptr_t* original_stack_top,
const uintptr_t* stack_copy_bottom,
uintptr_t pointer) {
auto original_stack_bottom_uint =
reinterpret_cast<uintptr_t>(original_stack_bottom);
auto original_stack_top_uint =
reinterpret_cast<uintptr_t>(original_stack_top);
auto stack_copy_bottom_uint = reinterpret_cast<uintptr_t>(stack_copy_bottom);
if (pointer < original_stack_bottom_uint ||
pointer >= original_stack_top_uint)
return pointer;
return stack_copy_bottom_uint + (pointer - original_stack_bottom_uint);
}
// Copies the stack to a buffer while rewriting possible pointers to locations
// within the stack to point to the corresponding locations in the copy. This is
// necessary to handle stack frames with dynamic stack allocation, where a
// pointer to the beginning of the dynamic allocation area is stored on the
// stack and/or in a non-volatile register.
//
// Eager rewriting of anything that looks like a pointer to the stack, as done
// in this function, does not adversely affect the stack unwinding. The only
// other values on the stack the unwinding depends on are return addresses,
// which should not point within the stack memory. The rewriting is guaranteed
// to catch all pointers because the stacks are guaranteed by the ABI to be
// sizeof(uintptr_t*) aligned.
//
// NO HEAP ALLOCATIONS.
//
// static
void CopyStackContentsAndRewritePointers(const uintptr_t* original_stack_bottom,
const uintptr_t* original_stack_top,
uintptr_t* stack_copy_bottom)
NO_SANITIZE("address") {
const uintptr_t* src = original_stack_bottom;
uintptr_t* dst = stack_copy_bottom;
for (; src < original_stack_top; ++src, ++dst) {
*dst = RewritePointerIfInOriginalStack(
original_stack_bottom, original_stack_top, stack_copy_bottom, *src);
}
}
} // namespace
StackSamplerImpl::StackSamplerImpl(
std::unique_ptr<ThreadDelegate> thread_delegate,
ModuleCache* module_cache,
StackSamplerTestDelegate* test_delegate)
: thread_delegate_(std::move(thread_delegate)),
module_cache_(module_cache),
test_delegate_(test_delegate) {}
StackSamplerImpl::~StackSamplerImpl() = default;
void StackSamplerImpl::RecordStackFrames(StackBuffer* stack_buffer,
ProfileBuilder* profile_builder) {
DCHECK(stack_buffer);
RegisterContext thread_context;
bool success = CopyStack(stack_buffer, profile_builder, &thread_context);
if (!success)
return;
if (test_delegate_)
test_delegate_->OnPreStackWalk();
profile_builder->OnSampleCompleted(WalkStack(&thread_context));
}
// Suspends the thread, copies its stack and register context, and records the
// current metadata, then resumes the thread. Returns true on success, and
// returns the copied state via the params. NO HEAP ALLOCATIONS within the
// ScopedSuspendThread scope.
bool StackSamplerImpl::CopyStack(StackBuffer* stack_buffer,
ProfileBuilder* profile_builder,
RegisterContext* thread_context) {
const uintptr_t top = thread_delegate_->GetStackBaseAddress();
uintptr_t bottom = 0;
{
// Allocation of the ScopedSuspendThread object itself is OK since it
// necessarily occurs before the thread is suspended by the object.
std::unique_ptr<ThreadDelegate::ScopedSuspendThread> suspend_thread =
thread_delegate_->CreateScopedSuspendThread();
if (!suspend_thread->WasSuccessful())
return false;
if (!thread_delegate_->GetThreadContext(thread_context))
return false;
bottom = RegisterContextStackPointer(thread_context);
// The StackBuffer allocation is expected to be at least as large as the
// largest stack region allocation on the platform, but check just in case
// it isn't *and* the actual stack itself exceeds the buffer allocation
// size.
if ((top - bottom) > stack_buffer->size())
return false;
if (!thread_delegate_->CanCopyStack(bottom))
return false;
profile_builder->RecordMetadata();
CopyStackContentsAndRewritePointers(reinterpret_cast<uintptr_t*>(bottom),
reinterpret_cast<uintptr_t*>(top),
stack_buffer->buffer());
}
for (uintptr_t* reg :
thread_delegate_->GetRegistersToRewrite(thread_context)) {
*reg = RewritePointerIfInOriginalStack(reinterpret_cast<uintptr_t*>(bottom),
reinterpret_cast<uintptr_t*>(top),
stack_buffer->buffer(), *reg);
}
return true;
}
// Walks the stack represented by |thread_context|, recording and returning the
// frames.
std::vector<ProfileBuilder::Frame> StackSamplerImpl::WalkStack(
RegisterContext* thread_context) {
std::vector<ProfileBuilder::Frame> stack;
// Reserve enough memory for most stacks, to avoid repeated
// allocations. Approximately 99.9% of recorded stacks are 128 frames or
// fewer.
stack.reserve(128);
thread_delegate_->WalkNativeFrames(thread_context, module_cache_, &stack);
return stack;
}
} // namespace base
// 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.
#ifndef BASE_PROFILER_STACK_SAMPLER_IMPL_H_
#define BASE_PROFILER_STACK_SAMPLER_IMPL_H_
#include <memory>
#include "base/base_export.h"
#include "base/profiler/profile_builder.h"
#include "base/profiler/register_context.h"
#include "base/profiler/stack_sampler.h"
namespace base {
class ThreadDelegate;
// Cross-platform stack sampler implementation. Delegates to ThreadDelegate for
// platform-specific implementation.
class BASE_EXPORT StackSamplerImpl : public StackSampler {
public:
StackSamplerImpl(std::unique_ptr<ThreadDelegate> delegate,
ModuleCache* module_cache,
StackSamplerTestDelegate* test_delegate = nullptr);
~StackSamplerImpl() override;
StackSamplerImpl(const StackSamplerImpl&) = delete;
StackSamplerImpl& operator=(const StackSamplerImpl&) = delete;
// Records a set of frames and returns them.
void RecordStackFrames(StackBuffer* stack_buffer,
ProfileBuilder* profile_builder) override;
private:
bool CopyStack(StackBuffer* stack_buffer,
ProfileBuilder* profile_builder,
RegisterContext* thread_context);
std::vector<ProfileBuilder::Frame> WalkStack(RegisterContext* thread_context);
const std::unique_ptr<ThreadDelegate> thread_delegate_;
ModuleCache* const module_cache_;
StackSamplerTestDelegate* const test_delegate_;
};
} // namespace base
#endif // BASE_PROFILER_STACK_SAMPLER_IMPL_H_
// 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 <memory>
#include <utility>
#include "base/profiler/profile_builder.h"
#include "base/profiler/stack_sampler_impl.h"
#include "base/profiler/thread_delegate.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
using ::testing::ElementsAre;
class TestProfileBuilder : public ProfileBuilder {
public:
TestProfileBuilder(ModuleCache* module_cache) : module_cache_(module_cache) {}
TestProfileBuilder(const TestProfileBuilder&) = delete;
TestProfileBuilder& operator=(const TestProfileBuilder&) = delete;
// ProfileBuilder
ModuleCache* GetModuleCache() override { return module_cache_; }
void RecordMetadata() override {}
void OnSampleCompleted(std::vector<Frame> frames) override {}
void OnProfileCompleted(TimeDelta profile_duration,
TimeDelta sampling_period) override {}
private:
ModuleCache* module_cache_;
};
// A thread delegate for use in tests that provides the expected behavior when
// operating on the supplied fake stack.
class TestThreadDelegate : public ThreadDelegate {
public:
class TestScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
public:
TestScopedSuspendThread() = default;
TestScopedSuspendThread(const TestScopedSuspendThread&) = delete;
TestScopedSuspendThread& operator=(const TestScopedSuspendThread&) = delete;
bool WasSuccessful() const override { return true; }
};
TestThreadDelegate(const std::vector<uintptr_t>& fake_stack,
// Vector to fill in with the copied stack.
std::vector<uintptr_t>* stack_copy = nullptr,
// Variable to fill in with the bottom address of the
// copied stack. This will be different than
// &(*stack_copy)[0] because |stack_copy| is a copy of the
// copy so does not share memory with the actual copy.
uintptr_t* stack_copy_bottom = nullptr,
// The register context will be initialized to
// *|thread_context| if non-null.
RegisterContext* thread_context = nullptr)
: fake_stack_(fake_stack),
stack_copy_(stack_copy),
stack_copy_bottom_(stack_copy_bottom),
thread_context_(thread_context) {}
TestThreadDelegate(const TestThreadDelegate&) = delete;
TestThreadDelegate& operator=(const TestThreadDelegate&) = delete;
std::unique_ptr<ScopedSuspendThread> CreateScopedSuspendThread() override {
return std::make_unique<TestScopedSuspendThread>();
}
bool GetThreadContext(RegisterContext* thread_context) override {
if (thread_context_)
*thread_context = *thread_context_;
// Set the stack pointer to be consistent with the provided fake stack.
RegisterContextStackPointer(thread_context) =
reinterpret_cast<uintptr_t>(&fake_stack_[0]);
return true;
}
uintptr_t GetStackBaseAddress() const override {
return reinterpret_cast<uintptr_t>(&fake_stack_[0] + fake_stack_.size());
}
bool CanCopyStack(uintptr_t stack_pointer) override { return true; }
std::vector<uintptr_t*> GetRegistersToRewrite(
RegisterContext* thread_context) override {
return {&RegisterContextFramePointer(thread_context)};
}
UnwindResult WalkNativeFrames(
RegisterContext* thread_context,
ModuleCache* module_cache,
std::vector<ProfileBuilder::Frame>* stack) override {
if (stack_copy_) {
auto* bottom = reinterpret_cast<uintptr_t*>(
RegisterContextStackPointer(thread_context));
auto* top = bottom + fake_stack_.size();
*stack_copy_ = std::vector<uintptr_t>(bottom, top);
}
if (stack_copy_bottom_)
*stack_copy_bottom_ = RegisterContextStackPointer(thread_context);
return UnwindResult::COMPLETED;
}
private:
// Must be a reference to retain the underlying allocation from the vector
// passed to the constructor.
const std::vector<uintptr_t>& fake_stack_;
std::vector<uintptr_t>* stack_copy_;
uintptr_t* stack_copy_bottom_;
RegisterContext* thread_context_;
};
} // namespace
TEST(StackSamplerImplTest, CopyStack) {
ModuleCache module_cache;
const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
std::vector<uintptr_t> stack_copy;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack, &stack_copy), &module_cache);
std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
std::make_unique<StackSampler::StackBuffer>(stack.size() *
sizeof(uintptr_t));
TestProfileBuilder profile_builder(&module_cache);
stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
EXPECT_EQ(stack, stack_copy);
}
TEST(StackSamplerImplTest, CopyStackBufferTooSmall) {
ModuleCache module_cache;
std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
std::vector<uintptr_t> stack_copy;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack, &stack_copy), &module_cache);
std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
std::make_unique<StackSampler::StackBuffer>((stack.size() - 1) *
sizeof(uintptr_t));
// Make the buffer different than the input stack.
stack_buffer->buffer()[0] = 100;
TestProfileBuilder profile_builder(&module_cache);
stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
// Use the buffer not being overwritten as a proxy for the unwind being
// aborted.
EXPECT_NE(stack, stack_copy);
}
TEST(StackSamplerImplTest, CopyStackAndRewritePointers) {
ModuleCache module_cache;
// Allocate space for the stack, then make its elements point to themselves.
std::vector<uintptr_t> stack(2);
stack[0] = reinterpret_cast<uintptr_t>(&stack[0]);
stack[1] = reinterpret_cast<uintptr_t>(&stack[1]);
std::vector<uintptr_t> stack_copy;
uintptr_t stack_copy_bottom;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack, &stack_copy,
&stack_copy_bottom),
&module_cache);
std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
std::make_unique<StackSampler::StackBuffer>(stack.size() *
sizeof(uintptr_t));
TestProfileBuilder profile_builder(&module_cache);
stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
EXPECT_THAT(stack_copy, ElementsAre(stack_copy_bottom,
stack_copy_bottom + sizeof(uintptr_t)));
}
TEST(StackSamplerImplTest, RewriteRegisters) {
ModuleCache module_cache;
std::vector<uintptr_t> stack(3);
uintptr_t stack_copy_bottom;
RegisterContext thread_context;
RegisterContextFramePointer(&thread_context) =
reinterpret_cast<uintptr_t>(&stack[1]);
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack, nullptr, &stack_copy_bottom,
&thread_context),
&module_cache);
std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
std::make_unique<StackSampler::StackBuffer>(stack.size() *
sizeof(uintptr_t));
TestProfileBuilder profile_builder(&module_cache);
stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
EXPECT_EQ(stack_copy_bottom + sizeof(uintptr_t),
RegisterContextFramePointer(&thread_context));
}
} // namespace base
This diff is collapsed.
// 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.
#ifndef BASE_PROFILER_THREAD_DELEGATE_H_
#define BASE_PROFILER_THREAD_DELEGATE_H_
#include <vector>
#include "base/base_export.h"
#include "base/profiler/profile_builder.h"
#include "base/profiler/register_context.h"
#include "base/profiler/unwind_result.h"
namespace base {
// Platform-specific thread and stack manipulation delegate, for use by the
// platform-independent stack copying/walking implementation in
// StackSamplerImpl.
//
// IMPORTANT NOTE: Most methods in this interface are invoked while the target
// thread is suspended so must not do any allocation from the heap, including
// indirectly via use of DCHECK/CHECK or other logging statements. Otherwise the
// implementation can deadlock on heap locks acquired by the target thread
// before it was suspended. These functions are commented with "NO HEAP
// ALLOCATIONS".
class BASE_EXPORT ThreadDelegate {
public:
// Implementations of this interface should suspend the thread for the
// object's lifetime. NO HEAP ALLOCATIONS between the time the thread is
// suspended and resumed.
class BASE_EXPORT ScopedSuspendThread {
public:
ScopedSuspendThread() = default;
virtual ~ScopedSuspendThread() = default;
ScopedSuspendThread(const ScopedSuspendThread&) = delete;
ScopedSuspendThread& operator=(const ScopedSuspendThread&) = delete;
virtual bool WasSuccessful() const = 0;
};
ThreadDelegate() = default;
virtual ~ThreadDelegate() = default;
ThreadDelegate(const ThreadDelegate&) = delete;
ThreadDelegate& operator=(const ThreadDelegate&) = delete;
// Creates an object that holds the thread suspended for its lifetime.
virtual std::unique_ptr<ScopedSuspendThread> CreateScopedSuspendThread() = 0;
// Gets the register context for the thread.
// NO HEAP ALLOCATIONS.
virtual bool GetThreadContext(RegisterContext* thread_context) = 0;
// Gets the base address of the thread's stack.
virtual uintptr_t GetStackBaseAddress() const = 0;
// Returns true if the thread's stack can be copied, where the bottom address
// of the thread is at |stack_pointer|.
// NO HEAP ALLOCATIONS.
virtual bool CanCopyStack(uintptr_t stack_pointer) = 0;
// Returns a list of registers that should be rewritten to point into the
// stack copy, if they originally pointed into the original stack.
// May heap allocate.
virtual std::vector<uintptr_t*> GetRegistersToRewrite(
RegisterContext* thread_context) = 0;
// Walks the native frames on the stack pointed to by the stack pointer in
// |thread_context|, appending the frames to |stack|.
// TODO(wittman): Move the unwinding support into a separate UnwindDelegate.
virtual UnwindResult WalkNativeFrames(
RegisterContext* thread_context,
ModuleCache* module_cache,
std::vector<ProfileBuilder::Frame>* stack) = 0;
};
} // namespace base
#endif // BASE_PROFILER_THREAD_DELEGATE_H_
// 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/thread_delegate_win.h"
#include <windows.h>
#include <winternl.h>
#include "base/profiler/win32_stack_frame_unwinder.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.
};
// 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.
ThreadDelegateWin::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.
ThreadDelegateWin::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 ThreadDelegateWin::ScopedSuspendThread::WasSuccessful() const {
return was_successful_;
}
// ThreadDelegateWin ----------------------------------------------------------
ThreadDelegateWin::ThreadDelegateWin(PlatformThreadId thread_id)
: thread_handle_(::OpenThread(
THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION,
FALSE,
thread_id)),
thread_stack_base_address_(reinterpret_cast<uintptr_t>(
GetThreadEnvironmentBlock(thread_handle_.Get())->Tib.StackBase)) {}
ThreadDelegateWin::~ThreadDelegateWin() = default;
std::unique_ptr<ThreadDelegate::ScopedSuspendThread>
ThreadDelegateWin::CreateScopedSuspendThread() {
return std::make_unique<ScopedSuspendThread>(thread_handle_.Get());
}
// NO HEAP ALLOCATIONS.
bool ThreadDelegateWin::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 ThreadDelegateWin::GetStackBaseAddress() const {
return thread_stack_base_address_;
}
// Tests whether |stack_pointer| points to a location in the guard page. NO HEAP
// ALLOCATIONS.
bool ThreadDelegateWin::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*> ThreadDelegateWin::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
};
}
UnwindResult ThreadDelegateWin::WalkNativeFrames(
CONTEXT* thread_context,
ModuleCache* module_cache,
std::vector<ProfileBuilder::Frame>* stack) {
Win32StackFrameUnwinder frame_unwinder;
while (ContextPC(thread_context)) {
const ModuleCache::Module* const module =
module_cache->GetModuleForAddress(ContextPC(thread_context));
if (!module) {
// There's no loaded module containing the instruction pointer. This can
// be due to executing code that is not in a module. For example, V8
// generated code or runtime-generated code associated with third-party
// injected DLLs. It can also be due to the the module having been
// unloaded since we recorded the stack. In the latter case the function
// unwind information was part of the unloaded module, so it's not
// possible to unwind further.
//
// If a module was found, it's still theoretically possible for the
// detected module module to be different than the one that was loaded
// when the stack was copied (i.e. if the module was unloaded and a
// different module loaded in overlapping memory). This likely would cause
// a crash, but has not been observed in practice.
//
// We return UNRECOGNIZED_FRAME on the optimistic assumption that this may
// be a frame the AuxUnwinder knows how to handle (e.g. a frame in V8
// generated code).
return UnwindResult::UNRECOGNIZED_FRAME;
}
// Record the current frame.
stack->emplace_back(ContextPC(thread_context), module);
if (!frame_unwinder.TryUnwind(thread_context, module))
return UnwindResult::ABORTED;
}
return UnwindResult::COMPLETED;
}
} // namespace base
// 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.
#ifndef BASE_PROFILER_THREAD_DELEGATE_WIN_H_
#define BASE_PROFILER_THREAD_DELEGATE_WIN_H_
#include <windows.h>
#include "base/base_export.h"
#include "base/profiler/thread_delegate.h"
#include "base/threading/platform_thread.h"
#include "base/win/scoped_handle.h"
namespace base {
// Platform- and thread-specific implementation in support of stack sampling on
// Windows.
class BASE_EXPORT ThreadDelegateWin : public ThreadDelegate {
public:
class ScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
public:
explicit ScopedSuspendThread(HANDLE thread_handle);
~ScopedSuspendThread() override;
bool WasSuccessful() const override;
private:
HANDLE thread_handle_;
bool was_successful_;
DISALLOW_COPY_AND_ASSIGN(ScopedSuspendThread);
};
explicit ThreadDelegateWin(PlatformThreadId thread_id);
~ThreadDelegateWin() override;
ThreadDelegateWin(const ThreadDelegateWin&) = delete;
ThreadDelegateWin& operator=(const ThreadDelegateWin&) = delete;
// ThreadDelegate
std::unique_ptr<ThreadDelegate::ScopedSuspendThread>
CreateScopedSuspendThread() override;
bool GetThreadContext(CONTEXT* thread_context) override;
uintptr_t GetStackBaseAddress() const override;
bool CanCopyStack(uintptr_t stack_pointer) override;
std::vector<uintptr_t*> GetRegistersToRewrite(
CONTEXT* thread_context) override;
UnwindResult WalkNativeFrames(
CONTEXT* thread_context,
ModuleCache* module_cache,
std::vector<ProfileBuilder::Frame>* stack) override;
private:
win::ScopedHandle thread_handle_;
const uintptr_t thread_stack_base_address_;
};
} // namespace base
#endif // BASE_PROFILER_THREAD_DELEGATE_WIN_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment