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