1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/profiler/win32_stack_frame_unwinder.h"
6
7 #include <windows.h>
8
9 #include <utility>
10
11 #include "base/macros.h"
12 #include "base/memory/ptr_util.h"
13
14 namespace base {
15
16 // Win32UnwindFunctions -------------------------------------------------------
17
18 const HMODULE ModuleHandleTraits::kNonNullModuleForTesting =
19 reinterpret_cast<HMODULE>(static_cast<uintptr_t>(-1));
20
21 // static
CloseHandle(HMODULE handle)22 bool ModuleHandleTraits::CloseHandle(HMODULE handle) {
23 if (handle == kNonNullModuleForTesting)
24 return true;
25
26 return ::FreeLibrary(handle) != 0;
27 }
28
29 // static
IsHandleValid(HMODULE handle)30 bool ModuleHandleTraits::IsHandleValid(HMODULE handle) {
31 return handle != nullptr;
32 }
33
34 // static
NullHandle()35 HMODULE ModuleHandleTraits::NullHandle() {
36 return nullptr;
37 }
38
39 namespace {
40
41 // Implements the UnwindFunctions interface for the corresponding Win32
42 // functions.
43 class Win32UnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
44 public:
45 Win32UnwindFunctions();
46 ~Win32UnwindFunctions() override;
47
48 PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
49 PDWORD64 image_base) override;
50
51 void VirtualUnwind(DWORD64 image_base,
52 DWORD64 program_counter,
53 PRUNTIME_FUNCTION runtime_function,
54 CONTEXT* context) override;
55
56 ScopedModuleHandle GetModuleForProgramCounter(
57 DWORD64 program_counter) override;
58
59 private:
60 DISALLOW_COPY_AND_ASSIGN(Win32UnwindFunctions);
61 };
62
Win32UnwindFunctions()63 Win32UnwindFunctions::Win32UnwindFunctions() {}
~Win32UnwindFunctions()64 Win32UnwindFunctions::~Win32UnwindFunctions() {}
65
LookupFunctionEntry(DWORD64 program_counter,PDWORD64 image_base)66 PRUNTIME_FUNCTION Win32UnwindFunctions::LookupFunctionEntry(
67 DWORD64 program_counter,
68 PDWORD64 image_base) {
69 #ifdef _WIN64
70 return ::RtlLookupFunctionEntry(program_counter, image_base, nullptr);
71 #else
72 NOTREACHED();
73 return nullptr;
74 #endif
75 }
76
VirtualUnwind(DWORD64 image_base,DWORD64 program_counter,PRUNTIME_FUNCTION runtime_function,CONTEXT * context)77 void Win32UnwindFunctions::VirtualUnwind(DWORD64 image_base,
78 DWORD64 program_counter,
79 PRUNTIME_FUNCTION runtime_function,
80 CONTEXT* context) {
81 #ifdef _WIN64
82 void* handler_data;
83 ULONG64 establisher_frame;
84 KNONVOLATILE_CONTEXT_POINTERS nvcontext = {};
85 ::RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, program_counter,
86 runtime_function, context, &handler_data,
87 &establisher_frame, &nvcontext);
88 #else
89 NOTREACHED();
90 #endif
91 }
92
GetModuleForProgramCounter(DWORD64 program_counter)93 ScopedModuleHandle Win32UnwindFunctions::GetModuleForProgramCounter(
94 DWORD64 program_counter) {
95 HMODULE module_handle = nullptr;
96 // GetModuleHandleEx() increments the module reference count, which is then
97 // managed and ultimately decremented by ScopedModuleHandle.
98 if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
99 reinterpret_cast<LPCTSTR>(program_counter),
100 &module_handle)) {
101 const DWORD error = ::GetLastError();
102 DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
103 }
104 return ScopedModuleHandle(module_handle);
105 }
106
107 } // namespace
108
109 // Win32StackFrameUnwinder ----------------------------------------------------
110
~UnwindFunctions()111 Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
UnwindFunctions()112 Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
113
Win32StackFrameUnwinder()114 Win32StackFrameUnwinder::Win32StackFrameUnwinder()
115 : Win32StackFrameUnwinder(WrapUnique(new Win32UnwindFunctions)) {}
116
~Win32StackFrameUnwinder()117 Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
118
TryUnwind(CONTEXT * context,ScopedModuleHandle * module)119 bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
120 ScopedModuleHandle* module) {
121 #ifdef _WIN64
122 ScopedModuleHandle frame_module =
123 unwind_functions_->GetModuleForProgramCounter(context->Rip);
124 if (!frame_module.IsValid()) {
125 // There's no loaded module containing the instruction pointer. This can be
126 // due to executing code that is not in a module. In particular,
127 // runtime-generated code associated with third-party injected DLLs
128 // typically is not in a module. It can also be due to the the module having
129 // been unloaded since we recorded the stack. In the latter case the
130 // function unwind information was part of the unloaded module, so it's not
131 // possible to unwind further.
132 //
133 // If a module was found, it's still theoretically possible for the detected
134 // module module to be different than the one that was loaded when the stack
135 // was copied (i.e. if the module was unloaded and a different module loaded
136 // in overlapping memory). This likely would cause a crash, but has not been
137 // observed in practice.
138 return false;
139 }
140
141 ULONG64 image_base;
142 // Try to look up unwind metadata for the current function.
143 PRUNTIME_FUNCTION runtime_function =
144 unwind_functions_->LookupFunctionEntry(context->Rip, &image_base);
145
146 if (runtime_function) {
147 unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function,
148 context);
149 at_top_frame_ = false;
150 } else {
151 if (at_top_frame_) {
152 at_top_frame_ = false;
153
154 // This is a leaf function (i.e. a function that neither calls a function,
155 // nor allocates any stack space itself) so the return address is at RSP.
156 context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
157 context->Rsp += 8;
158 } else {
159 // In theory we shouldn't get here, as it means we've encountered a
160 // function without unwind information below the top of the stack, which
161 // is forbidden by the Microsoft x64 calling convention.
162 //
163 // The one known case in Chrome code that executes this path occurs
164 // because of BoringSSL unwind information inconsistent with the actual
165 // function code. See https://crbug.com/542919.
166 //
167 // Note that dodgy third-party generated code that otherwise would enter
168 // this path should be caught by the module check above, since the code
169 // typically is located outside of a module.
170 return false;
171 }
172 }
173
174 module->Set(frame_module.Take());
175 return true;
176 #else
177 NOTREACHED();
178 return false;
179 #endif
180 }
181
Win32StackFrameUnwinder(std::unique_ptr<UnwindFunctions> unwind_functions)182 Win32StackFrameUnwinder::Win32StackFrameUnwinder(
183 std::unique_ptr<UnwindFunctions> unwind_functions)
184 : at_top_frame_(true), unwind_functions_(std::move(unwind_functions)) {}
185
186 } // namespace base
187