Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(362)

Side by Side Diff: base/profiler/win32_stack_frame_unwinder.cc

Issue 1503273003: Stack sampling profiler: fix leaf function unwinding and remove blacklisting (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@lkcr
Patch Set: Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | base/profiler/win32_stack_frame_unwinder_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/profiler/win32_stack_frame_unwinder.h" 5 #include "base/profiler/win32_stack_frame_unwinder.h"
6 6
7 #include <windows.h> 7 #include <windows.h>
8 #include <utility> 8 #include <utility>
9 9
10 #include "base/containers/hash_tables.h"
11 #include "base/memory/singleton.h"
12 #include "base/stl_util.h"
13
14 namespace base { 10 namespace base {
15 11
16 // Win32UnwindFunctions ------------------------------------------------------- 12 // Win32UnwindFunctions -------------------------------------------------------
17 13
18 const HMODULE ModuleHandleTraits::kNonNullModuleForTesting = 14 const HMODULE ModuleHandleTraits::kNonNullModuleForTesting =
19 reinterpret_cast<HMODULE>(static_cast<uintptr_t>(-1)); 15 reinterpret_cast<HMODULE>(static_cast<uintptr_t>(-1));
20 16
21 // static 17 // static
22 bool ModuleHandleTraits::CloseHandle(HMODULE handle) { 18 bool ModuleHandleTraits::CloseHandle(HMODULE handle) {
23 if (handle == kNonNullModuleForTesting) 19 if (handle == kNonNullModuleForTesting)
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
97 // managed and ultimately decremented by ScopedModuleHandle. 93 // managed and ultimately decremented by ScopedModuleHandle.
98 if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, 94 if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
99 reinterpret_cast<LPCTSTR>(program_counter), 95 reinterpret_cast<LPCTSTR>(program_counter),
100 &module_handle)) { 96 &module_handle)) {
101 const DWORD error = ::GetLastError(); 97 const DWORD error = ::GetLastError();
102 DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error)); 98 DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
103 } 99 }
104 return ScopedModuleHandle(module_handle); 100 return ScopedModuleHandle(module_handle);
105 } 101 }
106 102
107 // LeafUnwindBlacklist --------------------------------------------------------
108
109 // Records modules that are known to have functions that violate the Microsoft
110 // x64 calling convention and would be dangerous to manually unwind if
111 // encountered as the last frame on the call stack. Functions like these have
112 // been observed in injected third party modules that either do not provide
113 // function unwind information, or do not provide the required function prologue
114 // and epilogue. The former case was observed in several AV products and the
115 // latter in a WndProc function associated with Actual Window
116 // Manager/aimemb64.dll. See https://crbug.com/476422.
117 class LeafUnwindBlacklist {
118 public:
119 static LeafUnwindBlacklist* GetInstance();
120
121 // Returns true if |module| has been blacklisted.
122 bool IsBlacklisted(const void* module) const;
123
124 // Records |module| for blacklisting.
125 void BlacklistModule(const void* module);
126
127 private:
128 friend struct DefaultSingletonTraits<LeafUnwindBlacklist>;
129
130 LeafUnwindBlacklist();
131 ~LeafUnwindBlacklist();
132
133 // The set of modules known to have functions that violate the Microsoft x64
134 // calling convention.
135 base::hash_set<const void*> blacklisted_modules_;
136
137 DISALLOW_COPY_AND_ASSIGN(LeafUnwindBlacklist);
138 };
139
140 // static
141 LeafUnwindBlacklist* LeafUnwindBlacklist::GetInstance() {
142 // Leaky for performance reasons.
143 return Singleton<LeafUnwindBlacklist,
144 LeakySingletonTraits<LeafUnwindBlacklist>>::get();
145 }
146
147 bool LeafUnwindBlacklist::IsBlacklisted(const void* module) const {
148 return ContainsKey(blacklisted_modules_, module);
149 }
150
151 void LeafUnwindBlacklist::BlacklistModule(const void* module) {
152 CHECK(module);
153 blacklisted_modules_.insert(module);
154 }
155
156 LeafUnwindBlacklist::LeafUnwindBlacklist() {}
157 LeafUnwindBlacklist::~LeafUnwindBlacklist() {}
158
159 } // namespace 103 } // namespace
160 104
161 // Win32StackFrameUnwinder ---------------------------------------------------- 105 // Win32StackFrameUnwinder ----------------------------------------------------
162 106
163 Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {} 107 Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
164 Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {} 108 Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
165 109
166 Win32StackFrameUnwinder::Win32StackFrameUnwinder() 110 Win32StackFrameUnwinder::Win32StackFrameUnwinder()
167 : Win32StackFrameUnwinder(make_scoped_ptr(new Win32UnwindFunctions)) { 111 : Win32StackFrameUnwinder(make_scoped_ptr(new Win32UnwindFunctions)) {
168 } 112 }
169 113
170 Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {} 114 Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
171 115
172 bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context, 116 bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
173 ScopedModuleHandle* module) { 117 ScopedModuleHandle* module) {
174 #ifdef _WIN64 118 #ifdef _WIN64
175 CHECK(!at_top_frame_ || unwind_info_present_for_all_frames_);
176
177 ScopedModuleHandle frame_module = 119 ScopedModuleHandle frame_module =
178 unwind_functions_->GetModuleForProgramCounter(context->Rip); 120 unwind_functions_->GetModuleForProgramCounter(context->Rip);
179 if (!frame_module.IsValid()) { 121 if (!frame_module.IsValid()) {
180 // There's no loaded module containing the instruction pointer. This can be 122 // There's no loaded module containing the instruction pointer. This can be
181 // due to executing code that is not in a module. In particular, 123 // due to executing code that is not in a module. In particular,
182 // runtime-generated code associated with third-party injected DLLs 124 // runtime-generated code associated with third-party injected DLLs
183 // typically is not in a module. It can also be due to the the module having 125 // typically is not in a module. It can also be due to the the module having
184 // been unloaded since we recorded the stack. In the latter case the 126 // been unloaded since we recorded the stack. In the latter case the
185 // function unwind information was part of the unloaded module, so it's not 127 // function unwind information was part of the unloaded module, so it's not
186 // possible to unwind further. 128 // possible to unwind further.
187 // 129 //
188 // If a module was found, it's still theoretically possible for the detected 130 // If a module was found, it's still theoretically possible for the detected
189 // module module to be different than the one that was loaded when the stack 131 // module module to be different than the one that was loaded when the stack
190 // was copied (i.e. if the module was unloaded and a different module loaded 132 // was copied (i.e. if the module was unloaded and a different module loaded
191 // in overlapping memory). This likely would cause a crash, but has not been 133 // in overlapping memory). This likely would cause a crash, but has not been
192 // observed in practice. 134 // observed in practice.
193 return false; 135 return false;
194 } 136 }
195 137
196 ULONG64 image_base; 138 ULONG64 image_base;
197 // Try to look up unwind metadata for the current function. 139 // Try to look up unwind metadata for the current function.
198 PRUNTIME_FUNCTION runtime_function = 140 PRUNTIME_FUNCTION runtime_function =
199 unwind_functions_->LookupFunctionEntry(context->Rip, &image_base); 141 unwind_functions_->LookupFunctionEntry(context->Rip, &image_base);
200 142
201 if (runtime_function) { 143 if (runtime_function) {
202 unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function, 144 unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function,
203 context); 145 context);
204 at_top_frame_ = false; 146 at_top_frame_ = false;
205 } else { 147 } else {
206 // RtlLookupFunctionEntry didn't find unwind information. This could mean
207 // the code at the instruction pointer is in:
208 //
209 // 1. a true leaf function (i.e. a function that neither calls a function,
210 // nor allocates any stack space itself) in which case the return
211 // address is at RSP, or
212 //
213 // 2. a function that doesn't adhere to the Microsoft x64 calling
214 // convention, either by not providing the required unwind information,
215 // or by not having the prologue or epilogue required for unwinding;
216 // this case has been observed in crash data in injected third party
217 // DLLs.
218 //
219 // In valid code, case 1 can only occur (by definition) as the last frame
220 // on the stack. This happens in about 5% of observed stacks and can
221 // easily be unwound by popping RSP and using it as the next frame's
222 // instruction pointer.
223 //
224 // Case 2 can occur anywhere on the stack, and attempting to unwind the
225 // stack will result in treating whatever value happens to be on the stack
226 // at RSP as the next frame's instruction pointer. This is certainly wrong
227 // and very likely to lead to crashing by deferencing invalid pointers in
228 // the next RtlVirtualUnwind call.
229 //
230 // If we see case 2 at a location not the last frame, and all the previous
231 // frame had valid unwind information, then this is definitely bad code.
232 // We blacklist the module as untrustable for unwinding if we encounter a
233 // function in it that doesn't have unwind information.
234
235 if (at_top_frame_) { 148 if (at_top_frame_) {
236 at_top_frame_ = false; 149 at_top_frame_ = false;
237 150
238 // We are at the end of the stack. It's very likely that we're in case 1 151 // This is a leaf function (i.e. a function that neither calls a function,
239 // since the vast majority of code adheres to the Microsoft x64 calling 152 // nor allocates any stack space itself) so the return address is at RSP.
240 // convention. But there's a small chance we might be unlucky and be in 153 context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
241 // case 2. If this module is known to have bad code according to the
242 // leaf unwind blacklist, stop here, otherwise manually unwind.
243 if (LeafUnwindBlacklist::GetInstance()->IsBlacklisted(
244 reinterpret_cast<const void*>(image_base))) {
245 return false;
246 }
247
248 context->Rip = context->Rsp;
249 context->Rsp += 8; 154 context->Rsp += 8;
250 unwind_info_present_for_all_frames_ = false;
251 } else { 155 } else {
252 // We're not at the end of the stack. This frame is untrustworthy and we 156 // In theory we shouldn't get here, as it means we've encountered a
253 // can't safely unwind from here. 157 // function without unwind information below the top of the stack, which
254 if (!image_base) { 158 // is forbidden by the Microsoft x64 calling convention.
255 // A null image_base means that the the last unwind produced an invalid 159 //
256 // instruction pointer. This has been observed where unwind information 160 // The one known case in Chrome code that executes this path occurs
257 // was present for a function but was inconsistent with the actual 161 // because of BoringSSL unwind information inconsistent with the actual
258 // function code, in particular in BoringSSL. See 162 // function code. See https://crbug.com/542919.
259 // https://crbug.com/542919. 163 //
260 } else if (unwind_info_present_for_all_frames_) { 164 // Note that dodgy third-party generated code that otherwise would enter
261 // Unwind information was present for all previous frames, so we can 165 // this path should be caught by the module check above, since the code
262 // be confident this is case 2. Record the module to be blacklisted. 166 // typically is located outside of a module.
263 LeafUnwindBlacklist::GetInstance()->BlacklistModule(
264 reinterpret_cast<const void *>(image_base));
265 } else {
266 // We started off on a function without unwind information. It's very
267 // likely that all frames up to this point have been good, and this
268 // frame is case 2. But it's possible that the initial frame was case
269 // 2 but hadn't been blacklisted yet, and we've started to go off into
270 // the weeds. Since we can't be sure, just bail out without
271 // blacklisting the module; chances are we'll later encounter the same
272 // function on a stack with full unwind information.
273 }
274 return false; 167 return false;
275 } 168 }
276 } 169 }
277 170
278 module->Set(frame_module.Take()); 171 module->Set(frame_module.Take());
279 return true; 172 return true;
280 #else 173 #else
281 NOTREACHED(); 174 NOTREACHED();
282 return false; 175 return false;
283 #endif 176 #endif
284 } 177 }
285 178
286 Win32StackFrameUnwinder::Win32StackFrameUnwinder( 179 Win32StackFrameUnwinder::Win32StackFrameUnwinder(
287 scoped_ptr<UnwindFunctions> unwind_functions) 180 scoped_ptr<UnwindFunctions> unwind_functions)
288 : at_top_frame_(true), 181 : at_top_frame_(true),
289 unwind_info_present_for_all_frames_(true),
290 unwind_functions_(std::move(unwind_functions)) {} 182 unwind_functions_(std::move(unwind_functions)) {}
291 183
292 } // namespace base 184 } // namespace base
OLDNEW
« no previous file with comments | « no previous file | base/profiler/win32_stack_frame_unwinder_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698