OLD | NEW |
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 <utility> | 7 #include <utility> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/compiler_specific.h" | 10 #include "base/compiler_specific.h" |
(...skipping 13 matching lines...) Expand all Loading... |
24 void VirtualUnwind(DWORD64 image_base, | 24 void VirtualUnwind(DWORD64 image_base, |
25 DWORD64 program_counter, | 25 DWORD64 program_counter, |
26 PRUNTIME_FUNCTION runtime_function, | 26 PRUNTIME_FUNCTION runtime_function, |
27 CONTEXT* context) override; | 27 CONTEXT* context) override; |
28 ScopedModuleHandle GetModuleForProgramCounter( | 28 ScopedModuleHandle GetModuleForProgramCounter( |
29 DWORD64 program_counter) override; | 29 DWORD64 program_counter) override; |
30 | 30 |
31 // Instructs GetModuleForProgramCounter to return null on the next call. | 31 // Instructs GetModuleForProgramCounter to return null on the next call. |
32 void SetUnloadedModule(); | 32 void SetUnloadedModule(); |
33 | 33 |
34 // These functions set whether the next frame will have a RUNTIME_FUNCTION, | 34 // These functions set whether the next frame will have a RUNTIME_FUNCTION. |
35 // and allow specification of a custom image_base. | |
36 void SetHasRuntimeFunction(CONTEXT* context); | 35 void SetHasRuntimeFunction(CONTEXT* context); |
37 void SetHasRuntimeFunction(DWORD64 image_base, CONTEXT* context); | |
38 void SetHasRuntimeFunction(DWORD64 image_base, | |
39 const RUNTIME_FUNCTION& runtime_function, | |
40 DWORD program_counter_offset, | |
41 CONTEXT* context); | |
42 void SetNoRuntimeFunction(CONTEXT* context); | 36 void SetNoRuntimeFunction(CONTEXT* context); |
43 void SetNoRuntimeFunction(DWORD64 image_base, CONTEXT* context); | |
44 | 37 |
45 private: | 38 private: |
46 enum RuntimeFunctionState { NO_RUNTIME_FUNCTION, HAS_RUNTIME_FUNCTION }; | |
47 | |
48 enum { kImageBaseIncrement = 1 << 20 }; | 39 enum { kImageBaseIncrement = 1 << 20 }; |
49 | 40 |
50 // Sets whether the next frame should have a RUNTIME_FUNCTION, and allows | |
51 // specification of a custom image_base. | |
52 void SetNextFrameState(RuntimeFunctionState runtime_function_state, | |
53 DWORD64 image_base, | |
54 CONTEXT* context); | |
55 | |
56 static RUNTIME_FUNCTION* const kInvalidRuntimeFunction; | 41 static RUNTIME_FUNCTION* const kInvalidRuntimeFunction; |
57 | 42 |
58 bool module_is_loaded_; | 43 bool module_is_loaded_; |
59 DWORD64 expected_program_counter_; | 44 DWORD64 expected_program_counter_; |
60 DWORD64 custom_image_base_; | |
61 DWORD64 next_image_base_; | 45 DWORD64 next_image_base_; |
62 DWORD64 expected_image_base_; | 46 DWORD64 expected_image_base_; |
63 RUNTIME_FUNCTION* next_runtime_function_; | 47 RUNTIME_FUNCTION* next_runtime_function_; |
64 std::vector<RUNTIME_FUNCTION> runtime_functions_; | 48 std::vector<RUNTIME_FUNCTION> runtime_functions_; |
65 | 49 |
66 DISALLOW_COPY_AND_ASSIGN(TestUnwindFunctions); | 50 DISALLOW_COPY_AND_ASSIGN(TestUnwindFunctions); |
67 }; | 51 }; |
68 | 52 |
69 RUNTIME_FUNCTION* const TestUnwindFunctions::kInvalidRuntimeFunction = | 53 RUNTIME_FUNCTION* const TestUnwindFunctions::kInvalidRuntimeFunction = |
70 reinterpret_cast<RUNTIME_FUNCTION*>(static_cast<uintptr_t>(-1)); | 54 reinterpret_cast<RUNTIME_FUNCTION*>(static_cast<uintptr_t>(-1)); |
71 | 55 |
72 TestUnwindFunctions::TestUnwindFunctions() | 56 TestUnwindFunctions::TestUnwindFunctions() |
73 : module_is_loaded_(true), | 57 : module_is_loaded_(true), |
74 expected_program_counter_(0), | 58 expected_program_counter_(0), |
75 custom_image_base_(0), | |
76 next_image_base_(kImageBaseIncrement), | 59 next_image_base_(kImageBaseIncrement), |
77 expected_image_base_(0), | 60 expected_image_base_(0), |
78 next_runtime_function_(kInvalidRuntimeFunction) { | 61 next_runtime_function_(kInvalidRuntimeFunction) { |
79 } | 62 } |
80 | 63 |
81 PRUNTIME_FUNCTION TestUnwindFunctions::LookupFunctionEntry( | 64 PRUNTIME_FUNCTION TestUnwindFunctions::LookupFunctionEntry( |
82 DWORD64 program_counter, | 65 DWORD64 program_counter, |
83 PDWORD64 image_base) { | 66 PDWORD64 image_base) { |
84 EXPECT_EQ(expected_program_counter_, program_counter); | 67 EXPECT_EQ(expected_program_counter_, program_counter); |
85 if (custom_image_base_) { | 68 *image_base = expected_image_base_ = next_image_base_; |
86 *image_base = expected_image_base_ = custom_image_base_; | 69 next_image_base_ += kImageBaseIncrement; |
87 custom_image_base_ = 0; | |
88 } else { | |
89 *image_base = expected_image_base_ = next_image_base_; | |
90 next_image_base_ += kImageBaseIncrement; | |
91 } | |
92 RUNTIME_FUNCTION* return_value = next_runtime_function_; | 70 RUNTIME_FUNCTION* return_value = next_runtime_function_; |
93 next_runtime_function_ = kInvalidRuntimeFunction; | 71 next_runtime_function_ = kInvalidRuntimeFunction; |
94 return return_value; | 72 return return_value; |
95 } | 73 } |
96 | 74 |
97 void TestUnwindFunctions::VirtualUnwind(DWORD64 image_base, | 75 void TestUnwindFunctions::VirtualUnwind(DWORD64 image_base, |
98 DWORD64 program_counter, | 76 DWORD64 program_counter, |
99 PRUNTIME_FUNCTION runtime_function, | 77 PRUNTIME_FUNCTION runtime_function, |
100 CONTEXT* context) { | 78 CONTEXT* context) { |
101 ASSERT_NE(kInvalidRuntimeFunction, runtime_function) | 79 ASSERT_NE(kInvalidRuntimeFunction, runtime_function) |
(...skipping 15 matching lines...) Expand all Loading... |
117 return ScopedModuleHandle(return_non_null_value ? | 95 return ScopedModuleHandle(return_non_null_value ? |
118 ModuleHandleTraits::kNonNullModuleForTesting : | 96 ModuleHandleTraits::kNonNullModuleForTesting : |
119 nullptr); | 97 nullptr); |
120 } | 98 } |
121 | 99 |
122 void TestUnwindFunctions::SetUnloadedModule() { | 100 void TestUnwindFunctions::SetUnloadedModule() { |
123 module_is_loaded_ = false; | 101 module_is_loaded_ = false; |
124 } | 102 } |
125 | 103 |
126 void TestUnwindFunctions::SetHasRuntimeFunction(CONTEXT* context) { | 104 void TestUnwindFunctions::SetHasRuntimeFunction(CONTEXT* context) { |
127 SetNextFrameState(HAS_RUNTIME_FUNCTION, 0, context); | 105 RUNTIME_FUNCTION runtime_function = {}; |
128 } | 106 runtime_function.BeginAddress = 16; |
129 | 107 runtime_function.EndAddress = runtime_function.BeginAddress + 256; |
130 void TestUnwindFunctions::SetHasRuntimeFunction(DWORD64 image_base, | |
131 CONTEXT* context) { | |
132 SetNextFrameState(HAS_RUNTIME_FUNCTION, image_base, context); | |
133 } | |
134 | |
135 void TestUnwindFunctions::SetHasRuntimeFunction( | |
136 DWORD64 image_base, | |
137 const RUNTIME_FUNCTION& runtime_function, | |
138 DWORD program_counter_offset, | |
139 CONTEXT* context) { | |
140 custom_image_base_ = image_base; | |
141 runtime_functions_.push_back(runtime_function); | 108 runtime_functions_.push_back(runtime_function); |
142 next_runtime_function_ = &runtime_functions_.back(); | 109 next_runtime_function_ = &runtime_functions_.back(); |
| 110 |
143 expected_program_counter_ = context->Rip = | 111 expected_program_counter_ = context->Rip = |
144 image_base + program_counter_offset; | 112 next_image_base_ + runtime_function.BeginAddress + 8; |
145 } | 113 } |
146 | 114 |
147 void TestUnwindFunctions::SetNoRuntimeFunction(CONTEXT* context) { | 115 void TestUnwindFunctions::SetNoRuntimeFunction(CONTEXT* context) { |
148 SetNextFrameState(NO_RUNTIME_FUNCTION, 0, context); | 116 expected_program_counter_ = context->Rip = 100; |
149 } | 117 next_runtime_function_ = nullptr; |
150 | |
151 void TestUnwindFunctions::SetNoRuntimeFunction(DWORD64 image_base, | |
152 CONTEXT* context) { | |
153 SetNextFrameState(NO_RUNTIME_FUNCTION, image_base, context); | |
154 } | |
155 | |
156 | |
157 void TestUnwindFunctions::SetNextFrameState( | |
158 RuntimeFunctionState runtime_function_state, | |
159 DWORD64 image_base, | |
160 CONTEXT* context) { | |
161 if (image_base) | |
162 custom_image_base_ = image_base; | |
163 | |
164 if (runtime_function_state == HAS_RUNTIME_FUNCTION) { | |
165 RUNTIME_FUNCTION runtime_function = {}; | |
166 runtime_function.BeginAddress = 16; | |
167 runtime_function.EndAddress = runtime_function.BeginAddress + 256; | |
168 runtime_functions_.push_back(runtime_function); | |
169 next_runtime_function_ = &runtime_functions_.back(); | |
170 | |
171 DWORD64 image_base = custom_image_base_ ? custom_image_base_ : | |
172 next_image_base_; | |
173 expected_program_counter_ = context->Rip = | |
174 image_base + runtime_function.BeginAddress + 8; | |
175 } else { | |
176 expected_program_counter_ = context->Rip = 100; | |
177 next_runtime_function_ = nullptr; | |
178 } | |
179 } | 118 } |
180 | 119 |
181 } // namespace | 120 } // namespace |
182 | 121 |
183 class Win32StackFrameUnwinderTest : public testing::Test { | 122 class Win32StackFrameUnwinderTest : public testing::Test { |
184 protected: | 123 protected: |
185 Win32StackFrameUnwinderTest() {} | 124 Win32StackFrameUnwinderTest() {} |
186 | 125 |
187 // This exists so that Win32StackFrameUnwinder's constructor can be private | 126 // This exists so that Win32StackFrameUnwinder's constructor can be private |
188 // with a single friend declaration of this test fixture. | 127 // with a single friend declaration of this test fixture. |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
233 unwind_functions_->SetUnloadedModule(); | 172 unwind_functions_->SetUnloadedModule(); |
234 EXPECT_FALSE(unwinder->TryUnwind(&context, &module)); | 173 EXPECT_FALSE(unwinder->TryUnwind(&context, &module)); |
235 } | 174 } |
236 | 175 |
237 // Checks that the CONTEXT's stack pointer gets popped when the top frame has no | 176 // Checks that the CONTEXT's stack pointer gets popped when the top frame has no |
238 // unwind information. | 177 // unwind information. |
239 TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) { | 178 TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) { |
240 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); | 179 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
241 CONTEXT context = {0}; | 180 CONTEXT context = {0}; |
242 ScopedModuleHandle module; | 181 ScopedModuleHandle module; |
243 const DWORD64 original_rsp = 128; | 182 DWORD64 next_ip = 0x0123456789abcdef; |
| 183 DWORD64 original_rsp = reinterpret_cast<DWORD64>(&next_ip); |
244 context.Rsp = original_rsp; | 184 context.Rsp = original_rsp; |
245 | 185 |
246 unwind_functions_->SetNoRuntimeFunction(&context); | 186 unwind_functions_->SetNoRuntimeFunction(&context); |
247 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | 187 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); |
248 EXPECT_EQ(original_rsp, context.Rip); | 188 EXPECT_EQ(next_ip, context.Rip); |
249 EXPECT_EQ(original_rsp + 8, context.Rsp); | 189 EXPECT_EQ(original_rsp + 8, context.Rsp); |
250 EXPECT_TRUE(module.IsValid()); | 190 EXPECT_TRUE(module.IsValid()); |
251 | 191 |
252 unwind_functions_->SetHasRuntimeFunction(&context); | 192 unwind_functions_->SetHasRuntimeFunction(&context); |
253 module.Set(nullptr); | 193 module.Set(nullptr); |
254 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | 194 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); |
255 EXPECT_TRUE(module.IsValid()); | 195 EXPECT_TRUE(module.IsValid()); |
256 | 196 |
257 unwind_functions_->SetHasRuntimeFunction(&context); | 197 unwind_functions_->SetHasRuntimeFunction(&context); |
258 module.Set(nullptr); | 198 module.Set(nullptr); |
259 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | 199 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); |
260 EXPECT_TRUE(module.IsValid()); | 200 EXPECT_TRUE(module.IsValid()); |
261 } | 201 } |
262 | 202 |
263 // Checks that a frame below the top of the stack with missing unwind info | 203 // Checks that a frame below the top of the stack with missing unwind info |
264 // results in blacklisting the module. | 204 // terminates the unwinding. |
265 TEST_F(Win32StackFrameUnwinderTest, BlacklistedModule) { | 205 TEST_F(Win32StackFrameUnwinderTest, FrameBelowTopWithoutUnwindInfo) { |
266 const DWORD64 image_base_for_module_with_bad_function = 1024; | |
267 { | 206 { |
268 // First stack, with a bad function below the top of the stack. | 207 // First stack, with a bad function below the top of the stack. |
269 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); | 208 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
270 CONTEXT context = {0}; | 209 CONTEXT context = {0}; |
271 ScopedModuleHandle module; | 210 ScopedModuleHandle module; |
272 unwind_functions_->SetHasRuntimeFunction(&context); | 211 unwind_functions_->SetHasRuntimeFunction(&context); |
273 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | 212 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); |
274 EXPECT_TRUE(module.IsValid()); | 213 EXPECT_TRUE(module.IsValid()); |
275 | 214 |
276 unwind_functions_->SetNoRuntimeFunction( | 215 unwind_functions_->SetNoRuntimeFunction(&context); |
277 image_base_for_module_with_bad_function, | |
278 &context); | |
279 EXPECT_FALSE(unwinder->TryUnwind(&context, &module)); | 216 EXPECT_FALSE(unwinder->TryUnwind(&context, &module)); |
280 } | 217 } |
281 | |
282 { | |
283 // Second stack; check that a function at the top of the stack without | |
284 // unwind info from the previously-seen module is blacklisted. | |
285 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); | |
286 CONTEXT context = {0}; | |
287 ScopedModuleHandle module; | |
288 unwind_functions_->SetNoRuntimeFunction( | |
289 image_base_for_module_with_bad_function, | |
290 &context); | |
291 EXPECT_FALSE(unwinder->TryUnwind(&context, &module)); | |
292 } | |
293 | |
294 { | |
295 // Third stack; check that a function at the top of the stack *with* unwind | |
296 // info from the previously-seen module is not blacklisted. Then check that | |
297 // functions below the top of the stack with unwind info are not | |
298 // blacklisted, regardless of whether they are in the previously-seen | |
299 // module. | |
300 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); | |
301 CONTEXT context = {0}; | |
302 ScopedModuleHandle module; | |
303 unwind_functions_->SetHasRuntimeFunction( | |
304 image_base_for_module_with_bad_function, | |
305 &context); | |
306 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | |
307 EXPECT_TRUE(module.IsValid()); | |
308 | |
309 unwind_functions_->SetHasRuntimeFunction(&context); | |
310 module.Set(nullptr); | |
311 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | |
312 EXPECT_TRUE(module.IsValid()); | |
313 | |
314 unwind_functions_->SetHasRuntimeFunction( | |
315 image_base_for_module_with_bad_function, | |
316 &context); | |
317 module.Set(nullptr); | |
318 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | |
319 EXPECT_TRUE(module.IsValid()); | |
320 } | |
321 | |
322 { | |
323 // Fourth stack; check that a function at the top of the stack without | |
324 // unwind info and not from the previously-seen module is not | |
325 // blacklisted. Then check that functions below the top of the stack with | |
326 // unwind info are not blacklisted, regardless of whether they are in the | |
327 // previously-seen module. | |
328 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); | |
329 CONTEXT context = {0}; | |
330 ScopedModuleHandle module; | |
331 unwind_functions_->SetNoRuntimeFunction(&context); | |
332 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | |
333 EXPECT_TRUE(module.IsValid()); | |
334 | |
335 unwind_functions_->SetHasRuntimeFunction(&context); | |
336 module.Set(nullptr); | |
337 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | |
338 EXPECT_TRUE(module.IsValid()); | |
339 | |
340 unwind_functions_->SetHasRuntimeFunction( | |
341 image_base_for_module_with_bad_function, | |
342 &context); | |
343 module.Set(nullptr); | |
344 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | |
345 EXPECT_TRUE(module.IsValid()); | |
346 } | |
347 } | |
348 | |
349 // Checks that a frame below the top of the stack with missing unwind info does | |
350 // not result in blacklisting the module if the first frame also was missing | |
351 // unwind info. This ensures we don't blacklist an innocent module because the | |
352 // first frame was bad but we didn't know it at the time. | |
353 TEST_F(Win32StackFrameUnwinderTest, ModuleFromQuestionableFrameNotBlacklisted) { | |
354 const DWORD64 image_base_for_questionable_module = 2048; | |
355 { | |
356 // First stack, with both the first and second frames missing unwind info. | |
357 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); | |
358 CONTEXT context = {0}; | |
359 ScopedModuleHandle module; | |
360 unwind_functions_->SetNoRuntimeFunction(&context); | |
361 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | |
362 EXPECT_TRUE(module.IsValid()); | |
363 | |
364 unwind_functions_->SetNoRuntimeFunction(image_base_for_questionable_module, | |
365 &context); | |
366 EXPECT_FALSE(unwinder->TryUnwind(&context, &module)); | |
367 } | |
368 | |
369 { | |
370 // Second stack; check that the questionable module was not blacklisted. | |
371 scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); | |
372 CONTEXT context = {0}; | |
373 ScopedModuleHandle module; | |
374 unwind_functions_->SetNoRuntimeFunction(image_base_for_questionable_module, | |
375 &context); | |
376 EXPECT_TRUE(unwinder->TryUnwind(&context, &module)); | |
377 EXPECT_TRUE(module.IsValid()); | |
378 } | |
379 } | 218 } |
380 | 219 |
381 } // namespace base | 220 } // namespace base |
OLD | NEW |