blob: 204b398377d2d37459fd5d145321f04dc1f13325 [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/macros.h"
#include "build/build_config.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);
}
} // namespace
// Win32StackFrameUnwinder ----------------------------------------------------
Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
Win32StackFrameUnwinder::Win32StackFrameUnwinder()
: Win32StackFrameUnwinder(std::make_unique<Win32UnwindFunctions>()) {}
Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
ScopedModuleHandle* module) {
#ifdef _WIN64
// TODO(wittman): update base::ModuleCache to return a ScopedModuleHandle and
// use it for this module lookup.
ScopedModuleHandle frame_module =
unwind_functions_->GetModuleForProgramCounter(ContextPC(context));
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(ContextPC(context), &image_base);
if (runtime_function) {
unwind_functions_->VirtualUnwind(image_base, ContextPC(context),
runtime_function, context);
at_top_frame_ = false;
} else {
if (at_top_frame_) {
at_top_frame_ = false;
// This is a leaf function (i.e. a function that neither calls a function,
// nor allocates any stack space itself).
#if defined(ARCH_CPU_X86_64)
// For X64, return address is at RSP.
context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
context->Rsp += 8;
#elif defined(ARCH_CPU_ARM64)
// For leaf function on Windows ARM64, return address is at LR(X30).
// Add CONTEXT_UNWOUND_TO_CALL flag to avoid unwind ambiguity for tailcall
// on ARM64, because padding after tailcall is not guaranteed.
context->Pc = context->Lr;
context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL;
#else
#error Unsupported Windows 64-bit Arch
#endif
} else {
// In theory we shouldn't get here, as it means we've encountered a
// function without unwind information below the top of the stack, which
// is forbidden by the Microsoft x64 calling convention.
//
// The one known case in Chrome code that executes this path occurs
// because of BoringSSL unwind information inconsistent with the actual
// function code. See https://crbug.com/542919.
//
// Note that dodgy third-party generated code that otherwise would enter
// this path should be caught by the module check above, since the code
// typically is located outside of a module.
return false;
}
}
module->Set(frame_module.Take());
return true;
#else
NOTREACHED();
return false;
#endif
}
Win32StackFrameUnwinder::Win32StackFrameUnwinder(
std::unique_ptr<UnwindFunctions> unwind_functions)
: at_top_frame_(true), unwind_functions_(std::move(unwind_functions)) {}
} // namespace base