blob: 74075cbec5f0e0bfb41d692e621b5382b2b81659 [file] [log] [blame]
// 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 <windows.h>
#include <utility>
#include "base/containers/hash_tables.h"
#include "base/memory/singleton.h"
#include "base/stl_util.h"
namespace base {
// Win32UnwindFunctions -------------------------------------------------------
const HMODULE ModuleHandleTraits::kNonNullModuleForTesting =
reinterpret_cast<HMODULE>(static_cast<uintptr_t>(-1));
// static
bool ModuleHandleTraits::CloseHandle(HMODULE handle) {
if (handle == kNonNullModuleForTesting)
return true;
return ::FreeLibrary(handle) != 0;
}
// static
bool ModuleHandleTraits::IsHandleValid(HMODULE handle) {
return handle != nullptr;
}
// static
HMODULE ModuleHandleTraits::NullHandle() {
return nullptr;
}
namespace {
// Implements the UnwindFunctions interface for the corresponding Win32
// functions.
class Win32UnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
public:
Win32UnwindFunctions();
~Win32UnwindFunctions() override;
PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
PDWORD64 image_base) override;
void VirtualUnwind(DWORD64 image_base,
DWORD64 program_counter,
PRUNTIME_FUNCTION runtime_function,
CONTEXT* context) override;
ScopedModuleHandle GetModuleForProgramCounter(
DWORD64 program_counter) override;
private:
DISALLOW_COPY_AND_ASSIGN(Win32UnwindFunctions);
};
Win32UnwindFunctions::Win32UnwindFunctions() {}
Win32UnwindFunctions::~Win32UnwindFunctions() {}
PRUNTIME_FUNCTION Win32UnwindFunctions::LookupFunctionEntry(
DWORD64 program_counter,
PDWORD64 image_base) {
#ifdef _WIN64
return RtlLookupFunctionEntry(program_counter, image_base, nullptr);
#else
NOTREACHED();
return nullptr;
#endif
}
void 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(UNW_FLAG_NHANDLER, image_base, program_counter,
runtime_function, context, &handler_data,
&establisher_frame, &nvcontext);
#else
NOTREACHED();
#endif
}
ScopedModuleHandle Win32UnwindFunctions::GetModuleForProgramCounter(
DWORD64 program_counter) {
HMODULE module_handle = nullptr;
// GetModuleHandleEx() increments the module reference count, which is then
// managed and ultimately decremented by ScopedModuleHandle.
if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast<LPCTSTR>(program_counter),
&module_handle)) {
const DWORD error = ::GetLastError();
DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
}
return ScopedModuleHandle(module_handle);
}
// LeafUnwindBlacklist --------------------------------------------------------
// 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();
// Returns true if |module| has been blacklisted.
bool IsBlacklisted(const void* module) const;
// Records |module| for blacklisting.
void BlacklistModule(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 performance reasons.
return Singleton<LeafUnwindBlacklist,
LeakySingletonTraits<LeafUnwindBlacklist>>::get();
}
bool LeafUnwindBlacklist::IsBlacklisted(const void* module) const {
return ContainsKey(blacklisted_modules_, module);
}
void LeafUnwindBlacklist::BlacklistModule(const void* module) {
CHECK(module);
blacklisted_modules_.insert(module);
}
LeafUnwindBlacklist::LeafUnwindBlacklist() {}
LeafUnwindBlacklist::~LeafUnwindBlacklist() {}
} // namespace
// Win32StackFrameUnwinder ----------------------------------------------------
Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
Win32StackFrameUnwinder::Win32StackFrameUnwinder()
: Win32StackFrameUnwinder(make_scoped_ptr(new Win32UnwindFunctions)) {
}
Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
ScopedModuleHandle* module) {
#ifdef _WIN64
CHECK(!at_top_frame_ || unwind_info_present_for_all_frames_);
ScopedModuleHandle frame_module =
unwind_functions_->GetModuleForProgramCounter(context->Rip);
if (!frame_module.IsValid()) {
// There's no loaded module containing the instruction pointer. This can be
// due to executing code that is not in a module. In particular,
// runtime-generated code associated with third-party injected DLLs
// typically is not in a module. 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.
return false;
}
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 (!image_base) {
// A null image_base means that the the last unwind produced an invalid
// instruction pointer. This has been observed where unwind information
// was present for a function but was inconsistent with the actual
// function code, in particular in BoringSSL. See
// https://crbug.com/542919.
} else 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.
LeafUnwindBlacklist::GetInstance()->BlacklistModule(
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;
}
}
module->Set(frame_module.Take());
return true;
#else
NOTREACHED();
return false;
#endif
}
Win32StackFrameUnwinder::Win32StackFrameUnwinder(
scoped_ptr<UnwindFunctions> unwind_functions)
: at_top_frame_(true),
unwind_info_present_for_all_frames_(true),
unwind_functions_(std::move(unwind_functions)) {}
} // namespace base