// 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 #include #include "base/macros.h" #include "base/memory/ptr_util.h" namespace base { // Win32UnwindFunctions ------------------------------------------------------- const HMODULE ModuleHandleTraits::kNonNullModuleForTesting = reinterpret_cast(static_cast(-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(program_counter), &module_handle)) { const DWORD error = ::GetLastError(); DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast(error)); } return ScopedModuleHandle(module_handle); } } // namespace // Win32StackFrameUnwinder ---------------------------------------------------- Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {} Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {} Win32StackFrameUnwinder::Win32StackFrameUnwinder() : Win32StackFrameUnwinder(WrapUnique(new Win32UnwindFunctions)) {} Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {} bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context, ScopedModuleHandle* module) { #ifdef _WIN64 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 { 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) so the return address is at RSP. context->Rip = *reinterpret_cast(context->Rsp); context->Rsp += 8; } 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 unwind_functions) : at_top_frame_(true), unwind_functions_(std::move(unwind_functions)) {} } // namespace base