OLD | NEW |
---|---|
(Empty) | |
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 "base/containers/hash_tables.h" | |
8 #include "base/memory/singleton.h" | |
9 #include "base/stl_util.h" | |
10 | |
11 namespace base { | |
12 | |
13 // LeafUnwindBlacklist -------------------------------------------------------- | |
14 | |
15 namespace { | |
16 | |
17 // Records modules that are known to have functions that violate the Microsoft | |
18 // x64 calling convention and would be dangerous to manually unwind if | |
19 // encountered as the last frame on the call stack. | |
cpu_(ooo_6.6-7.5)
2015/08/19 19:07:24
can you add an example of this kind of function?
Mike Wittman
2015/08/19 19:54:41
Done.
| |
20 class LeafUnwindBlacklist { | |
21 public: | |
22 static LeafUnwindBlacklist* GetInstance(); | |
23 | |
24 // This function does not allocate memory and is safe to call between | |
25 // SuspendThread and ResumeThread. | |
26 bool IsBlacklisted(const void* module) const; | |
27 | |
28 // Allocates memory. Must be invoked only after ResumeThread, otherwise | |
29 // RecordStack will eventually deadlock on a heap lock. | |
cpu_(ooo_6.6-7.5)
2015/08/19 19:07:24
we can make RecoardStack allocate from a different
Mike Wittman
2015/08/19 19:54:41
Ah, I didn't mean for this comment to imply that R
| |
30 void AddModuleToBlacklist(const void* module); | |
31 | |
32 private: | |
33 friend struct DefaultSingletonTraits<LeafUnwindBlacklist>; | |
34 | |
35 LeafUnwindBlacklist(); | |
36 ~LeafUnwindBlacklist(); | |
37 | |
38 // The set of modules known to have functions that violate the Microsoft x64 | |
39 // calling convention. | |
40 base::hash_set<const void*> blacklisted_modules_; | |
41 | |
42 DISALLOW_COPY_AND_ASSIGN(LeafUnwindBlacklist); | |
43 }; | |
44 | |
45 // static | |
46 LeafUnwindBlacklist* LeafUnwindBlacklist::GetInstance() { | |
47 // Leaky for shutdown performance. | |
48 return Singleton<LeafUnwindBlacklist, | |
49 LeakySingletonTraits<LeafUnwindBlacklist>>::get(); | |
50 } | |
51 | |
52 bool LeafUnwindBlacklist::IsBlacklisted(const void* module) const { | |
53 return ContainsKey(blacklisted_modules_, module); | |
54 } | |
55 | |
56 void LeafUnwindBlacklist::AddModuleToBlacklist(const void* module) { | |
57 CHECK(module); | |
58 blacklisted_modules_.insert(module); | |
59 } | |
60 | |
61 LeafUnwindBlacklist::LeafUnwindBlacklist() {} | |
62 LeafUnwindBlacklist::~LeafUnwindBlacklist() {} | |
63 | |
64 } // namespace | |
65 | |
66 // Win32StackFrameUnwinder ---------------------------------------------------- | |
67 | |
68 Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {} | |
69 Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {} | |
70 | |
71 Win32StackFrameUnwinder::Win32UnwindFunctions::Win32UnwindFunctions() {} | |
72 | |
73 PRUNTIME_FUNCTION Win32StackFrameUnwinder::Win32UnwindFunctions:: | |
74 LookupFunctionEntry(DWORD64 program_counter, PDWORD64 image_base) { | |
75 #ifdef _WIN64 | |
76 return RtlLookupFunctionEntry(program_counter, image_base, nullptr); | |
77 #else | |
78 NOTREACHED(); | |
79 return nullptr; | |
80 #endif | |
81 } | |
82 | |
83 void Win32StackFrameUnwinder::Win32UnwindFunctions::VirtualUnwind( | |
84 DWORD64 image_base, | |
85 DWORD64 program_counter, | |
86 PRUNTIME_FUNCTION runtime_function, | |
87 CONTEXT* context) { | |
88 #ifdef _WIN64 | |
89 void* handler_data; | |
90 ULONG64 establisher_frame; | |
91 KNONVOLATILE_CONTEXT_POINTERS nvcontext = {}; | |
92 RtlVirtualUnwind(0, image_base, program_counter, runtime_function, | |
93 context, &handler_data, &establisher_frame, &nvcontext); | |
94 #else | |
95 NOTREACHED(); | |
96 #endif | |
97 } | |
98 | |
99 | |
100 Win32StackFrameUnwinder::Win32StackFrameUnwinder() | |
101 : Win32StackFrameUnwinder(&win32_unwind_functions_) { | |
102 } | |
103 | |
104 Win32StackFrameUnwinder::~Win32StackFrameUnwinder() { | |
105 if (pending_blacklisted_module_) { | |
106 LeafUnwindBlacklist::GetInstance()->AddModuleToBlacklist( | |
107 pending_blacklisted_module_); | |
108 } | |
109 } | |
110 | |
111 bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context) { | |
112 #ifdef _WIN64 | |
113 CHECK(!at_top_frame_ || unwind_info_present_for_all_frames_); | |
114 CHECK(!pending_blacklisted_module_); | |
115 | |
116 ULONG64 image_base; | |
117 // Try to look up unwind metadata for the current function. | |
118 PRUNTIME_FUNCTION runtime_function = | |
119 unwind_functions_->LookupFunctionEntry(context->Rip, &image_base); | |
120 | |
121 if (runtime_function) { | |
122 unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function, | |
123 context); | |
124 at_top_frame_ = false; | |
125 } else { | |
126 // RtlLookupFunctionEntry didn't find unwind information. This could mean | |
127 // the code at the instruction pointer is in: | |
128 // | |
129 // 1. a true leaf function (i.e. a function that neither calls a function, | |
130 // nor allocates any stack space itself) in which case the return | |
131 // address is at RSP, or | |
132 // | |
133 // 2. a function that doesn't adhere to the Microsoft x64 calling | |
134 // convention, either by not providing the required unwind information, | |
135 // or by not having the prologue or epilogue required for unwinding; | |
136 // this case has been observed in crash data in injected third party | |
137 // DLLs. | |
138 // | |
139 // In valid code, case 1 can only occur (by definition) as the last frame | |
140 // on the stack. This happens in about 5% of observed stacks and can | |
141 // easily be unwound by popping RSP and using it as the next frame's | |
142 // instruction pointer. | |
143 // | |
144 // Case 2 can occur anywhere on the stack, and attempting to unwind the | |
145 // stack will result in treating whatever value happens to be on the stack | |
146 // at RSP as the next frame's instruction pointer. This is certainly wrong | |
147 // and very likely to lead to crashing by deferencing invalid pointers in | |
148 // the next RtlVirtualUnwind call. | |
149 // | |
150 // If we see case 2 at a location not the last frame, and all the previous | |
151 // frame had valid unwind information, then this is definitely bad code. | |
152 // We blacklist the module as untrustable for unwinding if we encounter a | |
153 // function in it that doesn't have unwind information. | |
154 | |
155 if (at_top_frame_) { | |
156 at_top_frame_ = false; | |
157 | |
158 // We are at the end of the stack. It's very likely that we're in case 1 | |
159 // since the vast majority of code adheres to the Microsoft x64 calling | |
160 // convention. But there's a small chance we might be unlucky and be in | |
161 // case 2. If this module is known to have bad code according to the | |
162 // leaf unwind blacklist, stop here, otherwise manually unwind. | |
163 if (LeafUnwindBlacklist::GetInstance()->IsBlacklisted( | |
164 reinterpret_cast<const void*>(image_base))) { | |
165 return false; | |
166 } | |
167 | |
168 context->Rip = context->Rsp; | |
169 context->Rsp += 8; | |
170 unwind_info_present_for_all_frames_ = false; | |
171 } else { | |
172 // We're not at the end of the stack. This frame is untrustworthy and we | |
173 // can't safely unwind from here. | |
174 if (unwind_info_present_for_all_frames_) { | |
175 // Unwind information was present for all previous frames, so we can | |
176 // be confident this is case 2. Record the module to be blacklisted. | |
177 pending_blacklisted_module_ = | |
178 reinterpret_cast<const void *>(image_base); | |
179 } else { | |
180 // We started off on a function without unwind information. It's very | |
181 // likely that all frames up to this point have been good, and this | |
182 // frame is case 2. But it's possible that the initial frame was case | |
183 // 2 but hadn't been blacklisted yet, and we've started to go off into | |
184 // the weeds. Since we can't be sure, just bail out without | |
185 // blacklisting the module; chances are we'll later encounter the same | |
186 // function on a stack with full unwind information. | |
187 } | |
188 return false; | |
189 } | |
190 } | |
191 | |
192 return true; | |
193 #else | |
194 NOTREACHED(); | |
195 return false; | |
196 #endif | |
197 } | |
198 | |
199 Win32StackFrameUnwinder::Win32StackFrameUnwinder( | |
200 UnwindFunctions* unwind_functions) | |
201 : at_top_frame_(true), | |
202 unwind_info_present_for_all_frames_(true), | |
203 pending_blacklisted_module_(nullptr), | |
204 unwind_functions_(unwind_functions) { | |
205 } | |
206 | |
207 } // namespace base | |
OLD | NEW |