| Index: profiler/win32_stack_frame_unwinder.cc
|
| diff --git a/profiler/win32_stack_frame_unwinder.cc b/profiler/win32_stack_frame_unwinder.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c3809a622c6eea8646fbe68f39cfdc5beadb7cec
|
| --- /dev/null
|
| +++ b/profiler/win32_stack_frame_unwinder.cc
|
| @@ -0,0 +1,210 @@
|
| +// Copyright 2015 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/win32_stack_frame_unwinder.h"
|
| +
|
| +#include "base/containers/hash_tables.h"
|
| +#include "base/memory/singleton.h"
|
| +#include "base/stl_util.h"
|
| +
|
| +namespace base {
|
| +
|
| +// LeafUnwindBlacklist --------------------------------------------------------
|
| +
|
| +namespace {
|
| +
|
| +// Records modules that are known to have functions that violate the Microsoft
|
| +// x64 calling convention and would be dangerous to manually unwind if
|
| +// encountered as the last frame on the call stack. Functions like these have
|
| +// been observed in injected third party modules that either do not provide
|
| +// function unwind information, or do not provide the required function prologue
|
| +// and epilogue. The former case was observed in several AV products and the
|
| +// latter in a WndProc function associated with Actual Window
|
| +// Manager/aimemb64.dll. See https://crbug.com/476422.
|
| +class LeafUnwindBlacklist {
|
| + public:
|
| + static LeafUnwindBlacklist* GetInstance();
|
| +
|
| + // This function does not allocate memory and is safe to call between
|
| + // SuspendThread and ResumeThread.
|
| + bool IsBlacklisted(const void* module) const;
|
| +
|
| + // Allocates memory. Must be invoked only after ResumeThread, otherwise we
|
| + // risk deadlocking on a heap lock held by a suspended thread.
|
| + void AddModuleToBlacklist(const void* module);
|
| +
|
| + private:
|
| + friend struct DefaultSingletonTraits<LeafUnwindBlacklist>;
|
| +
|
| + LeafUnwindBlacklist();
|
| + ~LeafUnwindBlacklist();
|
| +
|
| + // The set of modules known to have functions that violate the Microsoft x64
|
| + // calling convention.
|
| + base::hash_set<const void*> blacklisted_modules_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(LeafUnwindBlacklist);
|
| +};
|
| +
|
| +// static
|
| +LeafUnwindBlacklist* LeafUnwindBlacklist::GetInstance() {
|
| + // Leaky for shutdown performance.
|
| + return Singleton<LeafUnwindBlacklist,
|
| + LeakySingletonTraits<LeafUnwindBlacklist>>::get();
|
| +}
|
| +
|
| +bool LeafUnwindBlacklist::IsBlacklisted(const void* module) const {
|
| + return ContainsKey(blacklisted_modules_, module);
|
| +}
|
| +
|
| +void LeafUnwindBlacklist::AddModuleToBlacklist(const void* module) {
|
| + CHECK(module);
|
| + blacklisted_modules_.insert(module);
|
| +}
|
| +
|
| +LeafUnwindBlacklist::LeafUnwindBlacklist() {}
|
| +LeafUnwindBlacklist::~LeafUnwindBlacklist() {}
|
| +
|
| +} // namespace
|
| +
|
| +// Win32StackFrameUnwinder ----------------------------------------------------
|
| +
|
| +Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
|
| +Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
|
| +
|
| +Win32StackFrameUnwinder::Win32UnwindFunctions::Win32UnwindFunctions() {}
|
| +
|
| +PRUNTIME_FUNCTION
|
| +Win32StackFrameUnwinder::Win32UnwindFunctions::LookupFunctionEntry(
|
| + DWORD64 program_counter,
|
| + PDWORD64 image_base) {
|
| +#ifdef _WIN64
|
| + return RtlLookupFunctionEntry(program_counter, image_base, nullptr);
|
| +#else
|
| + NOTREACHED();
|
| + return nullptr;
|
| +#endif
|
| +}
|
| +
|
| +void Win32StackFrameUnwinder::Win32UnwindFunctions::VirtualUnwind(
|
| + DWORD64 image_base,
|
| + DWORD64 program_counter,
|
| + PRUNTIME_FUNCTION runtime_function,
|
| + CONTEXT* context) {
|
| +#ifdef _WIN64
|
| + void* handler_data;
|
| + ULONG64 establisher_frame;
|
| + KNONVOLATILE_CONTEXT_POINTERS nvcontext = {};
|
| + RtlVirtualUnwind(0, image_base, program_counter, runtime_function, context,
|
| + &handler_data, &establisher_frame, &nvcontext);
|
| +#else
|
| + NOTREACHED();
|
| +#endif
|
| +}
|
| +
|
| +Win32StackFrameUnwinder::Win32StackFrameUnwinder()
|
| + : Win32StackFrameUnwinder(&win32_unwind_functions_) {}
|
| +
|
| +Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {
|
| + if (pending_blacklisted_module_) {
|
| + LeafUnwindBlacklist::GetInstance()->AddModuleToBlacklist(
|
| + pending_blacklisted_module_);
|
| + }
|
| +}
|
| +
|
| +bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context) {
|
| +#ifdef _WIN64
|
| + CHECK(!at_top_frame_ || unwind_info_present_for_all_frames_);
|
| + CHECK(!pending_blacklisted_module_);
|
| +
|
| + ULONG64 image_base;
|
| + // Try to look up unwind metadata for the current function.
|
| + PRUNTIME_FUNCTION runtime_function =
|
| + unwind_functions_->LookupFunctionEntry(context->Rip, &image_base);
|
| +
|
| + if (runtime_function) {
|
| + unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function,
|
| + context);
|
| + at_top_frame_ = false;
|
| + } else {
|
| + // RtlLookupFunctionEntry didn't find unwind information. This could mean
|
| + // the code at the instruction pointer is in:
|
| + //
|
| + // 1. a true leaf function (i.e. a function that neither calls a function,
|
| + // nor allocates any stack space itself) in which case the return
|
| + // address is at RSP, or
|
| + //
|
| + // 2. a function that doesn't adhere to the Microsoft x64 calling
|
| + // convention, either by not providing the required unwind information,
|
| + // or by not having the prologue or epilogue required for unwinding;
|
| + // this case has been observed in crash data in injected third party
|
| + // DLLs.
|
| + //
|
| + // In valid code, case 1 can only occur (by definition) as the last frame
|
| + // on the stack. This happens in about 5% of observed stacks and can
|
| + // easily be unwound by popping RSP and using it as the next frame's
|
| + // instruction pointer.
|
| + //
|
| + // Case 2 can occur anywhere on the stack, and attempting to unwind the
|
| + // stack will result in treating whatever value happens to be on the stack
|
| + // at RSP as the next frame's instruction pointer. This is certainly wrong
|
| + // and very likely to lead to crashing by deferencing invalid pointers in
|
| + // the next RtlVirtualUnwind call.
|
| + //
|
| + // If we see case 2 at a location not the last frame, and all the previous
|
| + // frame had valid unwind information, then this is definitely bad code.
|
| + // We blacklist the module as untrustable for unwinding if we encounter a
|
| + // function in it that doesn't have unwind information.
|
| +
|
| + if (at_top_frame_) {
|
| + at_top_frame_ = false;
|
| +
|
| + // We are at the end of the stack. It's very likely that we're in case 1
|
| + // since the vast majority of code adheres to the Microsoft x64 calling
|
| + // convention. But there's a small chance we might be unlucky and be in
|
| + // case 2. If this module is known to have bad code according to the
|
| + // leaf unwind blacklist, stop here, otherwise manually unwind.
|
| + if (LeafUnwindBlacklist::GetInstance()->IsBlacklisted(
|
| + reinterpret_cast<const void*>(image_base))) {
|
| + return false;
|
| + }
|
| +
|
| + context->Rip = context->Rsp;
|
| + context->Rsp += 8;
|
| + unwind_info_present_for_all_frames_ = false;
|
| + } else {
|
| + // We're not at the end of the stack. This frame is untrustworthy and we
|
| + // can't safely unwind from here.
|
| + if (unwind_info_present_for_all_frames_) {
|
| + // Unwind information was present for all previous frames, so we can
|
| + // be confident this is case 2. Record the module to be blacklisted.
|
| + pending_blacklisted_module_ = reinterpret_cast<const void*>(image_base);
|
| + } else {
|
| + // We started off on a function without unwind information. It's very
|
| + // likely that all frames up to this point have been good, and this
|
| + // frame is case 2. But it's possible that the initial frame was case
|
| + // 2 but hadn't been blacklisted yet, and we've started to go off into
|
| + // the weeds. Since we can't be sure, just bail out without
|
| + // blacklisting the module; chances are we'll later encounter the same
|
| + // function on a stack with full unwind information.
|
| + }
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +#else
|
| + NOTREACHED();
|
| + return false;
|
| +#endif
|
| +}
|
| +
|
| +Win32StackFrameUnwinder::Win32StackFrameUnwinder(
|
| + UnwindFunctions* unwind_functions)
|
| + : at_top_frame_(true),
|
| + unwind_info_present_for_all_frames_(true),
|
| + pending_blacklisted_module_(nullptr),
|
| + unwind_functions_(unwind_functions) {}
|
| +
|
| +} // namespace base
|
|
|