OLD | NEW |
| (Empty) |
1 // Copyright (c) 2009 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 #ifndef CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_ | |
6 #define CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_ | |
7 | |
8 #include "base/logging.h" | |
9 #include "chrome_frame/crash_reporting/vectored_handler.h" | |
10 #include "chrome_frame/crash_reporting/nt_loader.h" | |
11 | |
12 #if !defined(_M_IX86) | |
13 #error only x86 is supported for now. | |
14 #endif | |
15 | |
16 #ifndef EXCEPTION_CHAIN_END | |
17 #define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD*)-1) | |
18 #if !defined(_WIN32_WINNT_WIN8) | |
19 typedef struct _EXCEPTION_REGISTRATION_RECORD { | |
20 struct _EXCEPTION_REGISTRATION_RECORD* Next; | |
21 PVOID Handler; | |
22 } EXCEPTION_REGISTRATION_RECORD; | |
23 // VEH handler flags settings. | |
24 // These are grabbed from winnt.h for PocketPC. | |
25 // Only EXCEPTION_NONCONTINUABLE in defined in "regular" winnt.h | |
26 // #define EXCEPTION_NONCONTINUABLE 0x1 // Noncontinuable exception | |
27 #define EXCEPTION_UNWINDING 0x2 // Unwind is in progress | |
28 #define EXCEPTION_EXIT_UNWIND 0x4 // Exit unwind is in progress | |
29 #define EXCEPTION_STACK_INVALID 0x8 // Stack out of limits or unaligned | |
30 #define EXCEPTION_NESTED_CALL 0x10 // Nested exception handler call | |
31 #define EXCEPTION_TARGET_UNWIND 0x20 // Target unwind in progress | |
32 #define EXCEPTION_COLLIDED_UNWIND 0x40 // Collided exception handler call | |
33 | |
34 #define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | \ | |
35 EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND) | |
36 | |
37 #define IS_UNWINDING(Flag) (((Flag) & EXCEPTION_UNWIND) != 0) | |
38 #define IS_DISPATCHING(Flag) (((Flag) & EXCEPTION_UNWIND) == 0) | |
39 #define IS_TARGET_UNWIND(Flag) ((Flag) & EXCEPTION_TARGET_UNWIND) | |
40 #endif // !defined(_WIN32_WINNT_WIN8) | |
41 #endif // EXCEPTION_CHAIN_END | |
42 | |
43 template <typename E> | |
44 VectoredHandlerT<E>::VectoredHandlerT(E* api) : exceptions_seen_(0), api_(api) { | |
45 } | |
46 | |
47 template <typename E> | |
48 VectoredHandlerT<E>::~VectoredHandlerT() { | |
49 } | |
50 | |
51 template <typename E> | |
52 LONG VectoredHandlerT<E>::Handler(EXCEPTION_POINTERS* exceptionInfo) { | |
53 // TODO(stoyan): Consider reentrancy | |
54 const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode; | |
55 | |
56 // Not interested in non-error exceptions. In this category falls exceptions | |
57 // like: | |
58 // 0x40010006 - OutputDebugStringA. Seen when no debugger is attached | |
59 // (otherwise debugger swallows the exception and prints | |
60 // the string). | |
61 // 0x406D1388 - DebuggerProbe. Used by debug CRT - for example see source | |
62 // code of isatty(). Used to name a thread as well. | |
63 // RPC_E_DISCONNECTED and Co. - COM IPC non-fatal warnings | |
64 // STATUS_BREAKPOINT and Co. - Debugger related breakpoints | |
65 if ((exceptionCode & ERROR_SEVERITY_ERROR) != ERROR_SEVERITY_ERROR) { | |
66 return ExceptionContinueSearch; | |
67 } | |
68 | |
69 // Ignore custom exception codes. | |
70 // MSXML likes to raise 0xE0000001 while parsing. | |
71 // Note the C++ SEH (0xE06D7363) also fails in that range. | |
72 if (exceptionCode & APPLICATION_ERROR_MASK) { | |
73 return ExceptionContinueSearch; | |
74 } | |
75 | |
76 exceptions_seen_++; | |
77 | |
78 // If the exception code is STATUS_STACK_OVERFLOW then proceed as usual - | |
79 // we want to report it. Otherwise check whether guard page in stack is gone - | |
80 // i.e. stack overflow has been already observed and most probably we are | |
81 // seeing the follow-up STATUS_ACCESS_VIOLATION(s). See bug 32441. | |
82 if (exceptionCode != STATUS_STACK_OVERFLOW && api_->CheckForStackOverflow()) { | |
83 return ExceptionContinueSearch; | |
84 } | |
85 | |
86 // Check whether exception will be handled by the module that cuased it. | |
87 // This should automatically handle the case of IsBadReadPtr and family. | |
88 const EXCEPTION_REGISTRATION_RECORD* seh = api_->RtlpGetExceptionList(); | |
89 if (api_->ShouldIgnoreException(exceptionInfo, seh)) { | |
90 return ExceptionContinueSearch; | |
91 } | |
92 | |
93 const DWORD exceptionFlags = exceptionInfo->ExceptionRecord->ExceptionFlags; | |
94 // I don't think VEH is called on unwind. Just to be safe. | |
95 if (IS_DISPATCHING(exceptionFlags)) { | |
96 if (ModuleHasInstalledSEHFilter()) | |
97 return ExceptionContinueSearch; | |
98 | |
99 if (api_->IsOurModule(exceptionInfo->ExceptionRecord->ExceptionAddress)) { | |
100 api_->WriteDump(exceptionInfo); | |
101 return ExceptionContinueSearch; | |
102 } | |
103 | |
104 // See whether our module is somewhere in the call stack. | |
105 void* back_trace[E::max_back_trace] = {0}; | |
106 DWORD captured = api_->RtlCaptureStackBackTrace(0, api_->max_back_trace, | |
107 &back_trace[0], NULL); | |
108 for (DWORD i = 0; i < captured; ++i) { | |
109 if (api_->IsOurModule(back_trace[i])) { | |
110 api_->WriteDump(exceptionInfo); | |
111 return ExceptionContinueSearch; | |
112 } | |
113 } | |
114 } | |
115 | |
116 return ExceptionContinueSearch; | |
117 } | |
118 | |
119 template <typename E> | |
120 bool VectoredHandlerT<E>::ModuleHasInstalledSEHFilter() { | |
121 const EXCEPTION_REGISTRATION_RECORD* RegistrationFrame = | |
122 api_->RtlpGetExceptionList(); | |
123 // TODO(stoyan): Add the stack limits check and some sanity checks like | |
124 // decreasing addresses of registration records | |
125 while (RegistrationFrame != EXCEPTION_CHAIN_END) { | |
126 if (api_->IsOurModule(RegistrationFrame->Handler)) { | |
127 return true; | |
128 } | |
129 | |
130 RegistrationFrame = RegistrationFrame->Next; | |
131 } | |
132 | |
133 return false; | |
134 } | |
135 | |
136 | |
137 // Here comes the default Windows 32-bit implementation. | |
138 namespace { | |
139 __declspec(naked) | |
140 static EXCEPTION_REGISTRATION_RECORD* InternalRtlpGetExceptionList() { | |
141 __asm { | |
142 mov eax, fs:0 | |
143 ret | |
144 } | |
145 } | |
146 | |
147 __declspec(naked) | |
148 static char* GetStackTopLimit() { | |
149 __asm { | |
150 mov eax, fs:8 | |
151 ret | |
152 } | |
153 } | |
154 } // end of namespace | |
155 | |
156 // Class which methods simply forwards to Win32 API. | |
157 // Used as template (external interface) of VectoredHandlerT<E>. | |
158 class Win32VEHTraits { | |
159 public: | |
160 enum {max_back_trace = 62}; | |
161 | |
162 static inline | |
163 EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() { | |
164 return InternalRtlpGetExceptionList(); | |
165 } | |
166 | |
167 static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip, | |
168 DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) { | |
169 return ::RtlCaptureStackBackTrace(FramesToSkip, FramesToCapture, | |
170 BackTrace, BackTraceHash); | |
171 } | |
172 | |
173 static bool ShouldIgnoreException(const EXCEPTION_POINTERS* exceptionInfo, | |
174 const EXCEPTION_REGISTRATION_RECORD* seh_record) { | |
175 const void* address = exceptionInfo->ExceptionRecord->ExceptionAddress; | |
176 const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | | |
177 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS; | |
178 HMODULE crashing_module = GetModuleHandleFromAddress(address); | |
179 if (!crashing_module) | |
180 return false; | |
181 | |
182 HMODULE top_seh_module = NULL; | |
183 if (EXCEPTION_CHAIN_END != seh_record) | |
184 top_seh_module = GetModuleHandleFromAddress(seh_record->Handler); | |
185 | |
186 // check if the crash address belongs in a module that's expecting | |
187 // and handling it. This should address cases like kernel32!IsBadXXX | |
188 // and urlmon!IsValidInterface | |
189 if (crashing_module == top_seh_module) | |
190 return true; | |
191 | |
192 // We don't want to report exceptions that occur during DLL loading, | |
193 // as those are captured and ignored by the NT loader. If this thread | |
194 // is holding the loader's lock, there's a possiblity that the crash | |
195 // is occurring in a loading DLL, in which case we resolve the | |
196 // crash address to a module and check on the module's status. | |
197 if (nt_loader::OwnsLoaderLock()) { | |
198 nt_loader::LDR_DATA_TABLE_ENTRY* entry = | |
199 nt_loader::GetLoaderEntry(crashing_module); | |
200 | |
201 // If: | |
202 // 1. we found the entry in question, and | |
203 // 2. the entry is a DLL (has the IMAGE_DLL flag set), and | |
204 // 3. the DLL has a non-null entrypoint, and | |
205 // 4. the loader has not tagged it with the process attached called flag | |
206 // we conclude that the crash is most likely during the loading of the | |
207 // module and bail on reporting the crash to avoid false positives | |
208 // during crashes that occur during module loads, such as e.g. when | |
209 // the hook manager attempts to load buggy window hook DLLs. | |
210 if (entry && | |
211 (entry->Flags & LDRP_IMAGE_DLL) != 0 && | |
212 (entry->EntryPoint != NULL) && | |
213 (entry->Flags & LDRP_PROCESS_ATTACH_CALLED) == 0) | |
214 return true; | |
215 } | |
216 | |
217 return false; | |
218 } | |
219 | |
220 static bool CheckForStackOverflow() { | |
221 MEMORY_BASIC_INFORMATION mi = {0}; | |
222 const DWORD kPageSize = 0x1000; | |
223 void* stack_top = GetStackTopLimit() - kPageSize; | |
224 ::VirtualQuery(stack_top, &mi, sizeof(mi)); | |
225 // The above call may result in moving the top of the stack. | |
226 // Check once more. | |
227 void* stack_top2 = GetStackTopLimit() - kPageSize; | |
228 if (stack_top2 != stack_top) | |
229 ::VirtualQuery(stack_top2, &mi, sizeof(mi)); | |
230 return !(mi.Protect & PAGE_GUARD); | |
231 } | |
232 | |
233 private: | |
234 static inline const void* CodeOffset(const void* code, int offset) { | |
235 return reinterpret_cast<const char*>(code) + offset; | |
236 } | |
237 | |
238 static HMODULE GetModuleHandleFromAddress(const void* p) { | |
239 HMODULE module_at_address = NULL; | |
240 MEMORY_BASIC_INFORMATION mi = {0}; | |
241 if (::VirtualQuery(p, &mi, sizeof(mi)) && (mi.Type & MEM_IMAGE)) { | |
242 module_at_address = reinterpret_cast<HMODULE>(mi.AllocationBase); | |
243 } | |
244 | |
245 return module_at_address; | |
246 } | |
247 }; | |
248 | |
249 | |
250 // Use Win32 API; checks for single (current) module. Will call a specified | |
251 // CrashHandlerTraits::DumpHandler when taking a dump. | |
252 class CrashHandlerTraits : public Win32VEHTraits, | |
253 public ModuleOfInterestWithExcludedRegion { | |
254 public: | |
255 | |
256 typedef bool (*DumpHandler)(EXCEPTION_POINTERS* p); | |
257 | |
258 CrashHandlerTraits() : dump_handler_(NULL) {} | |
259 | |
260 // Note that breakpad_lock must be held when this is called. | |
261 void Init(const void* veh_segment_start, const void* veh_segment_end, | |
262 DumpHandler dump_handler) { | |
263 DCHECK(dump_handler); | |
264 dump_handler_ = dump_handler; | |
265 ModuleOfInterestWithExcludedRegion::SetCurrentModule(); | |
266 // Pointers to static (non-extern) functions take the address of the | |
267 // function's first byte, as opposed to an entry in the compiler generated | |
268 // JMP table. In release builds /OPT:REF wipes away the JMP table, but debug | |
269 // builds are not so lucky. | |
270 ModuleOfInterestWithExcludedRegion::SetExcludedRegion(veh_segment_start, | |
271 veh_segment_end); | |
272 } | |
273 | |
274 void Shutdown() { | |
275 } | |
276 | |
277 inline bool WriteDump(EXCEPTION_POINTERS* p) { | |
278 if (dump_handler_) { | |
279 return dump_handler_(p); | |
280 } else { | |
281 return false; | |
282 } | |
283 } | |
284 | |
285 private: | |
286 DumpHandler dump_handler_; | |
287 }; | |
288 | |
289 #endif // CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_ | |
OLD | NEW |