Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | // Copyright (c) 2006, Google Inc. |
michael@0 | 2 | // All rights reserved. |
michael@0 | 3 | // |
michael@0 | 4 | // Redistribution and use in source and binary forms, with or without |
michael@0 | 5 | // modification, are permitted provided that the following conditions are |
michael@0 | 6 | // met: |
michael@0 | 7 | // |
michael@0 | 8 | // * Redistributions of source code must retain the above copyright |
michael@0 | 9 | // notice, this list of conditions and the following disclaimer. |
michael@0 | 10 | // * Redistributions in binary form must reproduce the above |
michael@0 | 11 | // copyright notice, this list of conditions and the following disclaimer |
michael@0 | 12 | // in the documentation and/or other materials provided with the |
michael@0 | 13 | // distribution. |
michael@0 | 14 | // * Neither the name of Google Inc. nor the names of its |
michael@0 | 15 | // contributors may be used to endorse or promote products derived from |
michael@0 | 16 | // this software without specific prior written permission. |
michael@0 | 17 | // |
michael@0 | 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
michael@0 | 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
michael@0 | 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
michael@0 | 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
michael@0 | 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
michael@0 | 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
michael@0 | 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
michael@0 | 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
michael@0 | 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
michael@0 | 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
michael@0 | 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
michael@0 | 29 | |
michael@0 | 30 | #include <ObjBase.h> |
michael@0 | 31 | |
michael@0 | 32 | #include <algorithm> |
michael@0 | 33 | #include <cassert> |
michael@0 | 34 | #include <cstdio> |
michael@0 | 35 | |
michael@0 | 36 | #include "common/windows/string_utils-inl.h" |
michael@0 | 37 | |
michael@0 | 38 | #include "client/windows/common/ipc_protocol.h" |
michael@0 | 39 | #include "client/windows/handler/exception_handler.h" |
michael@0 | 40 | #include "common/windows/guid_string.h" |
michael@0 | 41 | |
michael@0 | 42 | namespace google_breakpad { |
michael@0 | 43 | |
michael@0 | 44 | static const int kWaitForHandlerThreadMs = 60000; |
michael@0 | 45 | static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024; |
michael@0 | 46 | |
michael@0 | 47 | // As documented on MSDN, on failure SuspendThread returns (DWORD) -1 |
michael@0 | 48 | static const DWORD kFailedToSuspendThread = static_cast<DWORD>(-1); |
michael@0 | 49 | |
michael@0 | 50 | // This is passed as the context to the MinidumpWriteDump callback. |
michael@0 | 51 | typedef struct { |
michael@0 | 52 | AppMemoryList::const_iterator iter; |
michael@0 | 53 | AppMemoryList::const_iterator end; |
michael@0 | 54 | } MinidumpCallbackContext; |
michael@0 | 55 | |
michael@0 | 56 | vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL; |
michael@0 | 57 | LONG ExceptionHandler::handler_stack_index_ = 0; |
michael@0 | 58 | CRITICAL_SECTION ExceptionHandler::handler_stack_critical_section_; |
michael@0 | 59 | volatile LONG ExceptionHandler::instance_count_ = 0; |
michael@0 | 60 | |
michael@0 | 61 | ExceptionHandler::ExceptionHandler(const wstring& dump_path, |
michael@0 | 62 | FilterCallback filter, |
michael@0 | 63 | MinidumpCallback callback, |
michael@0 | 64 | void* callback_context, |
michael@0 | 65 | int handler_types, |
michael@0 | 66 | MINIDUMP_TYPE dump_type, |
michael@0 | 67 | const wchar_t* pipe_name, |
michael@0 | 68 | const CustomClientInfo* custom_info) { |
michael@0 | 69 | Initialize(dump_path, |
michael@0 | 70 | filter, |
michael@0 | 71 | callback, |
michael@0 | 72 | callback_context, |
michael@0 | 73 | handler_types, |
michael@0 | 74 | dump_type, |
michael@0 | 75 | pipe_name, |
michael@0 | 76 | NULL, |
michael@0 | 77 | custom_info); |
michael@0 | 78 | } |
michael@0 | 79 | |
michael@0 | 80 | ExceptionHandler::ExceptionHandler(const wstring& dump_path, |
michael@0 | 81 | FilterCallback filter, |
michael@0 | 82 | MinidumpCallback callback, |
michael@0 | 83 | void* callback_context, |
michael@0 | 84 | int handler_types, |
michael@0 | 85 | MINIDUMP_TYPE dump_type, |
michael@0 | 86 | HANDLE pipe_handle, |
michael@0 | 87 | const CustomClientInfo* custom_info) { |
michael@0 | 88 | Initialize(dump_path, |
michael@0 | 89 | filter, |
michael@0 | 90 | callback, |
michael@0 | 91 | callback_context, |
michael@0 | 92 | handler_types, |
michael@0 | 93 | dump_type, |
michael@0 | 94 | NULL, |
michael@0 | 95 | pipe_handle, |
michael@0 | 96 | custom_info); |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | ExceptionHandler::ExceptionHandler(const wstring &dump_path, |
michael@0 | 100 | FilterCallback filter, |
michael@0 | 101 | MinidumpCallback callback, |
michael@0 | 102 | void* callback_context, |
michael@0 | 103 | int handler_types) { |
michael@0 | 104 | Initialize(dump_path, |
michael@0 | 105 | filter, |
michael@0 | 106 | callback, |
michael@0 | 107 | callback_context, |
michael@0 | 108 | handler_types, |
michael@0 | 109 | MiniDumpNormal, |
michael@0 | 110 | NULL, |
michael@0 | 111 | NULL, |
michael@0 | 112 | NULL); |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | void ExceptionHandler::Initialize(const wstring& dump_path, |
michael@0 | 116 | FilterCallback filter, |
michael@0 | 117 | MinidumpCallback callback, |
michael@0 | 118 | void* callback_context, |
michael@0 | 119 | int handler_types, |
michael@0 | 120 | MINIDUMP_TYPE dump_type, |
michael@0 | 121 | const wchar_t* pipe_name, |
michael@0 | 122 | HANDLE pipe_handle, |
michael@0 | 123 | const CustomClientInfo* custom_info) { |
michael@0 | 124 | LONG instance_count = InterlockedIncrement(&instance_count_); |
michael@0 | 125 | filter_ = filter; |
michael@0 | 126 | callback_ = callback; |
michael@0 | 127 | callback_context_ = callback_context; |
michael@0 | 128 | dump_path_c_ = NULL; |
michael@0 | 129 | next_minidump_id_c_ = NULL; |
michael@0 | 130 | next_minidump_path_c_ = NULL; |
michael@0 | 131 | dbghelp_module_ = NULL; |
michael@0 | 132 | minidump_write_dump_ = NULL; |
michael@0 | 133 | dump_type_ = dump_type; |
michael@0 | 134 | rpcrt4_module_ = NULL; |
michael@0 | 135 | uuid_create_ = NULL; |
michael@0 | 136 | handler_types_ = handler_types; |
michael@0 | 137 | previous_filter_ = NULL; |
michael@0 | 138 | #if _MSC_VER >= 1400 // MSVC 2005/8 |
michael@0 | 139 | previous_iph_ = NULL; |
michael@0 | 140 | #endif // _MSC_VER >= 1400 |
michael@0 | 141 | previous_pch_ = NULL; |
michael@0 | 142 | handler_thread_ = NULL; |
michael@0 | 143 | is_shutdown_ = false; |
michael@0 | 144 | handler_start_semaphore_ = NULL; |
michael@0 | 145 | handler_finish_semaphore_ = NULL; |
michael@0 | 146 | requesting_thread_id_ = 0; |
michael@0 | 147 | exception_info_ = NULL; |
michael@0 | 148 | assertion_ = NULL; |
michael@0 | 149 | handler_return_value_ = false; |
michael@0 | 150 | handle_debug_exceptions_ = false; |
michael@0 | 151 | |
michael@0 | 152 | // Attempt to use out-of-process if user has specified a pipe. |
michael@0 | 153 | if (pipe_name != NULL || pipe_handle != NULL) { |
michael@0 | 154 | assert(!(pipe_name && pipe_handle)); |
michael@0 | 155 | |
michael@0 | 156 | scoped_ptr<CrashGenerationClient> client; |
michael@0 | 157 | if (pipe_name) { |
michael@0 | 158 | client.reset( |
michael@0 | 159 | new CrashGenerationClient(pipe_name, |
michael@0 | 160 | dump_type_, |
michael@0 | 161 | custom_info)); |
michael@0 | 162 | } else { |
michael@0 | 163 | client.reset( |
michael@0 | 164 | new CrashGenerationClient(pipe_handle, |
michael@0 | 165 | dump_type_, |
michael@0 | 166 | custom_info)); |
michael@0 | 167 | } |
michael@0 | 168 | |
michael@0 | 169 | // If successful in registering with the monitoring process, |
michael@0 | 170 | // there is no need to setup in-process crash generation. |
michael@0 | 171 | if (client->Register()) { |
michael@0 | 172 | crash_generation_client_.reset(client.release()); |
michael@0 | 173 | } |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | if (!IsOutOfProcess()) { |
michael@0 | 177 | // Either client did not ask for out-of-process crash generation |
michael@0 | 178 | // or registration with the server process failed. In either case, |
michael@0 | 179 | // setup to do in-process crash generation. |
michael@0 | 180 | |
michael@0 | 181 | // Set synchronization primitives and the handler thread. Each |
michael@0 | 182 | // ExceptionHandler object gets its own handler thread because that's the |
michael@0 | 183 | // only way to reliably guarantee sufficient stack space in an exception, |
michael@0 | 184 | // and it allows an easy way to get a snapshot of the requesting thread's |
michael@0 | 185 | // context outside of an exception. |
michael@0 | 186 | InitializeCriticalSection(&handler_critical_section_); |
michael@0 | 187 | handler_start_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL); |
michael@0 | 188 | assert(handler_start_semaphore_ != NULL); |
michael@0 | 189 | |
michael@0 | 190 | handler_finish_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL); |
michael@0 | 191 | assert(handler_finish_semaphore_ != NULL); |
michael@0 | 192 | |
michael@0 | 193 | // Don't attempt to create the thread if we could not create the semaphores. |
michael@0 | 194 | if (handler_finish_semaphore_ != NULL && handler_start_semaphore_ != NULL) { |
michael@0 | 195 | DWORD thread_id; |
michael@0 | 196 | handler_thread_ = CreateThread(NULL, // lpThreadAttributes |
michael@0 | 197 | kExceptionHandlerThreadInitialStackSize, |
michael@0 | 198 | ExceptionHandlerThreadMain, |
michael@0 | 199 | this, // lpParameter |
michael@0 | 200 | 0, // dwCreationFlags |
michael@0 | 201 | &thread_id); |
michael@0 | 202 | assert(handler_thread_ != NULL); |
michael@0 | 203 | } |
michael@0 | 204 | |
michael@0 | 205 | dbghelp_module_ = LoadLibrary(L"dbghelp.dll"); |
michael@0 | 206 | if (dbghelp_module_) { |
michael@0 | 207 | minidump_write_dump_ = reinterpret_cast<MiniDumpWriteDump_type>( |
michael@0 | 208 | GetProcAddress(dbghelp_module_, "MiniDumpWriteDump")); |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | // Load this library dynamically to not affect existing projects. Most |
michael@0 | 212 | // projects don't link against this directly, it's usually dynamically |
michael@0 | 213 | // loaded by dependent code. |
michael@0 | 214 | rpcrt4_module_ = LoadLibrary(L"rpcrt4.dll"); |
michael@0 | 215 | if (rpcrt4_module_) { |
michael@0 | 216 | uuid_create_ = reinterpret_cast<UuidCreate_type>( |
michael@0 | 217 | GetProcAddress(rpcrt4_module_, "UuidCreate")); |
michael@0 | 218 | } |
michael@0 | 219 | |
michael@0 | 220 | // set_dump_path calls UpdateNextID. This sets up all of the path and id |
michael@0 | 221 | // strings, and their equivalent c_str pointers. |
michael@0 | 222 | set_dump_path(dump_path); |
michael@0 | 223 | } |
michael@0 | 224 | |
michael@0 | 225 | // Reserve one element for the instruction memory |
michael@0 | 226 | AppMemory instruction_memory; |
michael@0 | 227 | instruction_memory.ptr = NULL; |
michael@0 | 228 | instruction_memory.length = 0; |
michael@0 | 229 | app_memory_info_.push_back(instruction_memory); |
michael@0 | 230 | |
michael@0 | 231 | // There is a race condition here. If the first instance has not yet |
michael@0 | 232 | // initialized the critical section, the second (and later) instances may |
michael@0 | 233 | // try to use uninitialized critical section object. The feature of multiple |
michael@0 | 234 | // instances in one module is not used much, so leave it as is for now. |
michael@0 | 235 | // One way to solve this in the current design (that is, keeping the static |
michael@0 | 236 | // handler stack) is to use spin locks with volatile bools to synchronize |
michael@0 | 237 | // the handler stack. This works only if the compiler guarantees to generate |
michael@0 | 238 | // cache coherent code for volatile. |
michael@0 | 239 | // TODO(munjal): Fix this in a better way by changing the design if possible. |
michael@0 | 240 | |
michael@0 | 241 | // Lazy initialization of the handler_stack_critical_section_ |
michael@0 | 242 | if (instance_count == 1) { |
michael@0 | 243 | InitializeCriticalSection(&handler_stack_critical_section_); |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | if (handler_types != HANDLER_NONE) { |
michael@0 | 247 | EnterCriticalSection(&handler_stack_critical_section_); |
michael@0 | 248 | |
michael@0 | 249 | // The first time an ExceptionHandler that installs a handler is |
michael@0 | 250 | // created, set up the handler stack. |
michael@0 | 251 | if (!handler_stack_) { |
michael@0 | 252 | handler_stack_ = new vector<ExceptionHandler*>(); |
michael@0 | 253 | } |
michael@0 | 254 | handler_stack_->push_back(this); |
michael@0 | 255 | |
michael@0 | 256 | if (handler_types & HANDLER_EXCEPTION) |
michael@0 | 257 | previous_filter_ = SetUnhandledExceptionFilter(HandleException); |
michael@0 | 258 | |
michael@0 | 259 | #if _MSC_VER >= 1400 // MSVC 2005/8 |
michael@0 | 260 | if (handler_types & HANDLER_INVALID_PARAMETER) |
michael@0 | 261 | previous_iph_ = _set_invalid_parameter_handler(HandleInvalidParameter); |
michael@0 | 262 | #endif // _MSC_VER >= 1400 |
michael@0 | 263 | |
michael@0 | 264 | if (handler_types & HANDLER_PURECALL) |
michael@0 | 265 | previous_pch_ = _set_purecall_handler(HandlePureVirtualCall); |
michael@0 | 266 | |
michael@0 | 267 | LeaveCriticalSection(&handler_stack_critical_section_); |
michael@0 | 268 | } |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | ExceptionHandler::~ExceptionHandler() { |
michael@0 | 272 | if (dbghelp_module_) { |
michael@0 | 273 | FreeLibrary(dbghelp_module_); |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | if (rpcrt4_module_) { |
michael@0 | 277 | FreeLibrary(rpcrt4_module_); |
michael@0 | 278 | } |
michael@0 | 279 | |
michael@0 | 280 | if (handler_types_ != HANDLER_NONE) { |
michael@0 | 281 | EnterCriticalSection(&handler_stack_critical_section_); |
michael@0 | 282 | |
michael@0 | 283 | if (handler_types_ & HANDLER_EXCEPTION) |
michael@0 | 284 | SetUnhandledExceptionFilter(previous_filter_); |
michael@0 | 285 | |
michael@0 | 286 | #if _MSC_VER >= 1400 // MSVC 2005/8 |
michael@0 | 287 | if (handler_types_ & HANDLER_INVALID_PARAMETER) |
michael@0 | 288 | _set_invalid_parameter_handler(previous_iph_); |
michael@0 | 289 | #endif // _MSC_VER >= 1400 |
michael@0 | 290 | |
michael@0 | 291 | if (handler_types_ & HANDLER_PURECALL) |
michael@0 | 292 | _set_purecall_handler(previous_pch_); |
michael@0 | 293 | |
michael@0 | 294 | if (handler_stack_->back() == this) { |
michael@0 | 295 | handler_stack_->pop_back(); |
michael@0 | 296 | } else { |
michael@0 | 297 | // TODO(mmentovai): use advapi32!ReportEvent to log the warning to the |
michael@0 | 298 | // system's application event log. |
michael@0 | 299 | fprintf(stderr, "warning: removing Breakpad handler out of order\n"); |
michael@0 | 300 | vector<ExceptionHandler*>::iterator iterator = handler_stack_->begin(); |
michael@0 | 301 | while (iterator != handler_stack_->end()) { |
michael@0 | 302 | if (*iterator == this) { |
michael@0 | 303 | iterator = handler_stack_->erase(iterator); |
michael@0 | 304 | } else { |
michael@0 | 305 | ++iterator; |
michael@0 | 306 | } |
michael@0 | 307 | } |
michael@0 | 308 | } |
michael@0 | 309 | |
michael@0 | 310 | if (handler_stack_->empty()) { |
michael@0 | 311 | // When destroying the last ExceptionHandler that installed a handler, |
michael@0 | 312 | // clean up the handler stack. |
michael@0 | 313 | delete handler_stack_; |
michael@0 | 314 | handler_stack_ = NULL; |
michael@0 | 315 | } |
michael@0 | 316 | |
michael@0 | 317 | LeaveCriticalSection(&handler_stack_critical_section_); |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | // Some of the objects were only initialized if out of process |
michael@0 | 321 | // registration was not done. |
michael@0 | 322 | if (!IsOutOfProcess()) { |
michael@0 | 323 | #ifdef BREAKPAD_NO_TERMINATE_THREAD |
michael@0 | 324 | // Clean up the handler thread and synchronization primitives. The handler |
michael@0 | 325 | // thread is either waiting on the semaphore to handle a crash or it is |
michael@0 | 326 | // handling a crash. Coming out of the wait is fast but wait more in the |
michael@0 | 327 | // eventuality a crash is handled. This compilation option results in a |
michael@0 | 328 | // deadlock if the exception handler is destroyed while executing code |
michael@0 | 329 | // inside DllMain. |
michael@0 | 330 | is_shutdown_ = true; |
michael@0 | 331 | ReleaseSemaphore(handler_start_semaphore_, 1, NULL); |
michael@0 | 332 | WaitForSingleObject(handler_thread_, kWaitForHandlerThreadMs); |
michael@0 | 333 | #else |
michael@0 | 334 | TerminateThread(handler_thread_, 1); |
michael@0 | 335 | #endif // BREAKPAD_NO_TERMINATE_THREAD |
michael@0 | 336 | |
michael@0 | 337 | CloseHandle(handler_thread_); |
michael@0 | 338 | handler_thread_ = NULL; |
michael@0 | 339 | DeleteCriticalSection(&handler_critical_section_); |
michael@0 | 340 | CloseHandle(handler_start_semaphore_); |
michael@0 | 341 | CloseHandle(handler_finish_semaphore_); |
michael@0 | 342 | } |
michael@0 | 343 | |
michael@0 | 344 | // There is a race condition in the code below: if this instance is |
michael@0 | 345 | // deleting the static critical section and a new instance of the class |
michael@0 | 346 | // is created, then there is a possibility that the critical section be |
michael@0 | 347 | // initialized while the same critical section is being deleted. Given the |
michael@0 | 348 | // usage pattern for the code, this race condition is unlikely to hit, but it |
michael@0 | 349 | // is a race condition nonetheless. |
michael@0 | 350 | if (InterlockedDecrement(&instance_count_) == 0) { |
michael@0 | 351 | DeleteCriticalSection(&handler_stack_critical_section_); |
michael@0 | 352 | } |
michael@0 | 353 | } |
michael@0 | 354 | |
michael@0 | 355 | bool ExceptionHandler::RequestUpload(DWORD crash_id) { |
michael@0 | 356 | return crash_generation_client_->RequestUpload(crash_id); |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | // static |
michael@0 | 360 | DWORD ExceptionHandler::ExceptionHandlerThreadMain(void* lpParameter) { |
michael@0 | 361 | ExceptionHandler* self = reinterpret_cast<ExceptionHandler *>(lpParameter); |
michael@0 | 362 | assert(self); |
michael@0 | 363 | assert(self->handler_start_semaphore_ != NULL); |
michael@0 | 364 | assert(self->handler_finish_semaphore_ != NULL); |
michael@0 | 365 | |
michael@0 | 366 | while (true) { |
michael@0 | 367 | if (WaitForSingleObject(self->handler_start_semaphore_, INFINITE) == |
michael@0 | 368 | WAIT_OBJECT_0) { |
michael@0 | 369 | // Perform the requested action. |
michael@0 | 370 | if (self->is_shutdown_) { |
michael@0 | 371 | // The instance of the exception handler is being destroyed. |
michael@0 | 372 | break; |
michael@0 | 373 | } else { |
michael@0 | 374 | self->handler_return_value_ = |
michael@0 | 375 | self->WriteMinidumpWithException(self->requesting_thread_id_, |
michael@0 | 376 | self->exception_info_, |
michael@0 | 377 | self->assertion_); |
michael@0 | 378 | } |
michael@0 | 379 | |
michael@0 | 380 | // Allow the requesting thread to proceed. |
michael@0 | 381 | ReleaseSemaphore(self->handler_finish_semaphore_, 1, NULL); |
michael@0 | 382 | } |
michael@0 | 383 | } |
michael@0 | 384 | |
michael@0 | 385 | // This statement is not reached when the thread is unconditionally |
michael@0 | 386 | // terminated by the ExceptionHandler destructor. |
michael@0 | 387 | return 0; |
michael@0 | 388 | } |
michael@0 | 389 | |
michael@0 | 390 | // HandleException and HandleInvalidParameter must create an |
michael@0 | 391 | // AutoExceptionHandler object to maintain static state and to determine which |
michael@0 | 392 | // ExceptionHandler instance to use. The constructor locates the correct |
michael@0 | 393 | // instance, and makes it available through get_handler(). The destructor |
michael@0 | 394 | // restores the state in effect prior to allocating the AutoExceptionHandler. |
michael@0 | 395 | class AutoExceptionHandler { |
michael@0 | 396 | public: |
michael@0 | 397 | AutoExceptionHandler() { |
michael@0 | 398 | // Increment handler_stack_index_ so that if another Breakpad handler is |
michael@0 | 399 | // registered using this same HandleException function, and it needs to be |
michael@0 | 400 | // called while this handler is running (either because this handler |
michael@0 | 401 | // declines to handle the exception, or an exception occurs during |
michael@0 | 402 | // handling), HandleException will find the appropriate ExceptionHandler |
michael@0 | 403 | // object in handler_stack_ to deliver the exception to. |
michael@0 | 404 | // |
michael@0 | 405 | // Because handler_stack_ is addressed in reverse (as |size - index|), |
michael@0 | 406 | // preincrementing handler_stack_index_ avoids needing to subtract 1 from |
michael@0 | 407 | // the argument to |at|. |
michael@0 | 408 | // |
michael@0 | 409 | // The index is maintained instead of popping elements off of the handler |
michael@0 | 410 | // stack and pushing them at the end of this method. This avoids ruining |
michael@0 | 411 | // the order of elements in the stack in the event that some other thread |
michael@0 | 412 | // decides to manipulate the handler stack (such as creating a new |
michael@0 | 413 | // ExceptionHandler object) while an exception is being handled. |
michael@0 | 414 | EnterCriticalSection(&ExceptionHandler::handler_stack_critical_section_); |
michael@0 | 415 | handler_ = ExceptionHandler::handler_stack_->at( |
michael@0 | 416 | ExceptionHandler::handler_stack_->size() - |
michael@0 | 417 | ++ExceptionHandler::handler_stack_index_); |
michael@0 | 418 | |
michael@0 | 419 | // In case another exception occurs while this handler is doing its thing, |
michael@0 | 420 | // it should be delivered to the previous filter. |
michael@0 | 421 | SetUnhandledExceptionFilter(handler_->previous_filter_); |
michael@0 | 422 | #if _MSC_VER >= 1400 // MSVC 2005/8 |
michael@0 | 423 | _set_invalid_parameter_handler(handler_->previous_iph_); |
michael@0 | 424 | #endif // _MSC_VER >= 1400 |
michael@0 | 425 | _set_purecall_handler(handler_->previous_pch_); |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | ~AutoExceptionHandler() { |
michael@0 | 429 | // Put things back the way they were before entering this handler. |
michael@0 | 430 | SetUnhandledExceptionFilter(ExceptionHandler::HandleException); |
michael@0 | 431 | #if _MSC_VER >= 1400 // MSVC 2005/8 |
michael@0 | 432 | _set_invalid_parameter_handler(ExceptionHandler::HandleInvalidParameter); |
michael@0 | 433 | #endif // _MSC_VER >= 1400 |
michael@0 | 434 | _set_purecall_handler(ExceptionHandler::HandlePureVirtualCall); |
michael@0 | 435 | |
michael@0 | 436 | --ExceptionHandler::handler_stack_index_; |
michael@0 | 437 | LeaveCriticalSection(&ExceptionHandler::handler_stack_critical_section_); |
michael@0 | 438 | } |
michael@0 | 439 | |
michael@0 | 440 | ExceptionHandler* get_handler() const { return handler_; } |
michael@0 | 441 | |
michael@0 | 442 | private: |
michael@0 | 443 | ExceptionHandler* handler_; |
michael@0 | 444 | }; |
michael@0 | 445 | |
michael@0 | 446 | // static |
michael@0 | 447 | LONG ExceptionHandler::HandleException(EXCEPTION_POINTERS* exinfo) { |
michael@0 | 448 | AutoExceptionHandler auto_exception_handler; |
michael@0 | 449 | ExceptionHandler* current_handler = auto_exception_handler.get_handler(); |
michael@0 | 450 | |
michael@0 | 451 | // Ignore EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP exceptions. This |
michael@0 | 452 | // logic will short-circuit before calling WriteMinidumpOnHandlerThread, |
michael@0 | 453 | // allowing something else to handle the breakpoint without incurring the |
michael@0 | 454 | // overhead transitioning to and from the handler thread. This behavior |
michael@0 | 455 | // can be overridden by calling ExceptionHandler::set_handle_debug_exceptions. |
michael@0 | 456 | DWORD code = exinfo->ExceptionRecord->ExceptionCode; |
michael@0 | 457 | LONG action; |
michael@0 | 458 | bool is_debug_exception = (code == EXCEPTION_BREAKPOINT) || |
michael@0 | 459 | (code == EXCEPTION_SINGLE_STEP); |
michael@0 | 460 | |
michael@0 | 461 | bool success = false; |
michael@0 | 462 | |
michael@0 | 463 | if (!is_debug_exception || |
michael@0 | 464 | current_handler->get_handle_debug_exceptions()) { |
michael@0 | 465 | // If out-of-proc crash handler client is available, we have to use that |
michael@0 | 466 | // to generate dump and we cannot fall back on in-proc dump generation |
michael@0 | 467 | // because we never prepared for an in-proc dump generation |
michael@0 | 468 | |
michael@0 | 469 | // In case of out-of-process dump generation, directly call |
michael@0 | 470 | // WriteMinidumpWithException since there is no separate thread running. |
michael@0 | 471 | if (current_handler->IsOutOfProcess()) { |
michael@0 | 472 | success = current_handler->WriteMinidumpWithException( |
michael@0 | 473 | GetCurrentThreadId(), |
michael@0 | 474 | exinfo, |
michael@0 | 475 | NULL); |
michael@0 | 476 | } else { |
michael@0 | 477 | success = current_handler->WriteMinidumpOnHandlerThread(exinfo, NULL); |
michael@0 | 478 | } |
michael@0 | 479 | } |
michael@0 | 480 | |
michael@0 | 481 | // The handler fully handled the exception. Returning |
michael@0 | 482 | // EXCEPTION_EXECUTE_HANDLER indicates this to the system, and usually |
michael@0 | 483 | // results in the application being terminated. |
michael@0 | 484 | // |
michael@0 | 485 | // Note: If the application was launched from within the Cygwin |
michael@0 | 486 | // environment, returning EXCEPTION_EXECUTE_HANDLER seems to cause the |
michael@0 | 487 | // application to be restarted. |
michael@0 | 488 | if (success) { |
michael@0 | 489 | action = EXCEPTION_EXECUTE_HANDLER; |
michael@0 | 490 | } else { |
michael@0 | 491 | // There was an exception, it was a breakpoint or something else ignored |
michael@0 | 492 | // above, or it was passed to the handler, which decided not to handle it. |
michael@0 | 493 | // This could be because the filter callback didn't want it, because |
michael@0 | 494 | // minidump writing failed for some reason, or because the post-minidump |
michael@0 | 495 | // callback function indicated failure. Give the previous handler a |
michael@0 | 496 | // chance to do something with the exception. If there is no previous |
michael@0 | 497 | // handler, return EXCEPTION_CONTINUE_SEARCH, which will allow a debugger |
michael@0 | 498 | // or native "crashed" dialog to handle the exception. |
michael@0 | 499 | if (current_handler->previous_filter_) { |
michael@0 | 500 | action = current_handler->previous_filter_(exinfo); |
michael@0 | 501 | } else { |
michael@0 | 502 | action = EXCEPTION_CONTINUE_SEARCH; |
michael@0 | 503 | } |
michael@0 | 504 | } |
michael@0 | 505 | |
michael@0 | 506 | return action; |
michael@0 | 507 | } |
michael@0 | 508 | |
michael@0 | 509 | #if _MSC_VER >= 1400 // MSVC 2005/8 |
michael@0 | 510 | // static |
michael@0 | 511 | void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression, |
michael@0 | 512 | const wchar_t* function, |
michael@0 | 513 | const wchar_t* file, |
michael@0 | 514 | unsigned int line, |
michael@0 | 515 | uintptr_t reserved) { |
michael@0 | 516 | // This is an invalid parameter, not an exception. It's safe to play with |
michael@0 | 517 | // sprintf here. |
michael@0 | 518 | AutoExceptionHandler auto_exception_handler; |
michael@0 | 519 | ExceptionHandler* current_handler = auto_exception_handler.get_handler(); |
michael@0 | 520 | |
michael@0 | 521 | MDRawAssertionInfo assertion; |
michael@0 | 522 | memset(&assertion, 0, sizeof(assertion)); |
michael@0 | 523 | _snwprintf_s(reinterpret_cast<wchar_t*>(assertion.expression), |
michael@0 | 524 | sizeof(assertion.expression) / sizeof(assertion.expression[0]), |
michael@0 | 525 | _TRUNCATE, L"%s", expression); |
michael@0 | 526 | _snwprintf_s(reinterpret_cast<wchar_t*>(assertion.function), |
michael@0 | 527 | sizeof(assertion.function) / sizeof(assertion.function[0]), |
michael@0 | 528 | _TRUNCATE, L"%s", function); |
michael@0 | 529 | _snwprintf_s(reinterpret_cast<wchar_t*>(assertion.file), |
michael@0 | 530 | sizeof(assertion.file) / sizeof(assertion.file[0]), |
michael@0 | 531 | _TRUNCATE, L"%s", file); |
michael@0 | 532 | assertion.line = line; |
michael@0 | 533 | assertion.type = MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER; |
michael@0 | 534 | |
michael@0 | 535 | // Make up an exception record for the current thread and CPU context |
michael@0 | 536 | // to make it possible for the crash processor to classify these |
michael@0 | 537 | // as do regular crashes, and to make it humane for developers to |
michael@0 | 538 | // analyze them. |
michael@0 | 539 | EXCEPTION_RECORD exception_record = {}; |
michael@0 | 540 | CONTEXT exception_context = {}; |
michael@0 | 541 | EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; |
michael@0 | 542 | |
michael@0 | 543 | ::RtlCaptureContext(&exception_context); |
michael@0 | 544 | |
michael@0 | 545 | exception_record.ExceptionCode = STATUS_INVALID_PARAMETER; |
michael@0 | 546 | |
michael@0 | 547 | // We store pointers to the the expression and function strings, |
michael@0 | 548 | // and the line as exception parameters to make them easy to |
michael@0 | 549 | // access by the developer on the far side. |
michael@0 | 550 | exception_record.NumberParameters = 3; |
michael@0 | 551 | exception_record.ExceptionInformation[0] = |
michael@0 | 552 | reinterpret_cast<ULONG_PTR>(&assertion.expression); |
michael@0 | 553 | exception_record.ExceptionInformation[1] = |
michael@0 | 554 | reinterpret_cast<ULONG_PTR>(&assertion.file); |
michael@0 | 555 | exception_record.ExceptionInformation[2] = assertion.line; |
michael@0 | 556 | |
michael@0 | 557 | bool success = false; |
michael@0 | 558 | // In case of out-of-process dump generation, directly call |
michael@0 | 559 | // WriteMinidumpWithException since there is no separate thread running. |
michael@0 | 560 | if (current_handler->IsOutOfProcess()) { |
michael@0 | 561 | success = current_handler->WriteMinidumpWithException( |
michael@0 | 562 | GetCurrentThreadId(), |
michael@0 | 563 | &exception_ptrs, |
michael@0 | 564 | &assertion); |
michael@0 | 565 | } else { |
michael@0 | 566 | success = current_handler->WriteMinidumpOnHandlerThread(&exception_ptrs, |
michael@0 | 567 | &assertion); |
michael@0 | 568 | } |
michael@0 | 569 | |
michael@0 | 570 | if (!success) { |
michael@0 | 571 | if (current_handler->previous_iph_) { |
michael@0 | 572 | // The handler didn't fully handle the exception. Give it to the |
michael@0 | 573 | // previous invalid parameter handler. |
michael@0 | 574 | current_handler->previous_iph_(expression, |
michael@0 | 575 | function, |
michael@0 | 576 | file, |
michael@0 | 577 | line, |
michael@0 | 578 | reserved); |
michael@0 | 579 | } else { |
michael@0 | 580 | // If there's no previous handler, pass the exception back in to the |
michael@0 | 581 | // invalid parameter handler's core. That's the routine that called this |
michael@0 | 582 | // function, but now, since this function is no longer registered (and in |
michael@0 | 583 | // fact, no function at all is registered), this will result in the |
michael@0 | 584 | // default code path being taken: _CRT_DEBUGGER_HOOK and _invoke_watson. |
michael@0 | 585 | // Use _invalid_parameter where it exists (in _DEBUG builds) as it passes |
michael@0 | 586 | // more information through. In non-debug builds, it is not available, |
michael@0 | 587 | // so fall back to using _invalid_parameter_noinfo. See invarg.c in the |
michael@0 | 588 | // CRT source. |
michael@0 | 589 | #ifdef _DEBUG |
michael@0 | 590 | _invalid_parameter(expression, function, file, line, reserved); |
michael@0 | 591 | #else // _DEBUG |
michael@0 | 592 | _invalid_parameter_noinfo(); |
michael@0 | 593 | #endif // _DEBUG |
michael@0 | 594 | } |
michael@0 | 595 | } |
michael@0 | 596 | |
michael@0 | 597 | // The handler either took care of the invalid parameter problem itself, |
michael@0 | 598 | // or passed it on to another handler. "Swallow" it by exiting, paralleling |
michael@0 | 599 | // the behavior of "swallowing" exceptions. |
michael@0 | 600 | exit(0); |
michael@0 | 601 | } |
michael@0 | 602 | #endif // _MSC_VER >= 1400 |
michael@0 | 603 | |
michael@0 | 604 | // static |
michael@0 | 605 | void ExceptionHandler::HandlePureVirtualCall() { |
michael@0 | 606 | // This is an pure virtual function call, not an exception. It's safe to |
michael@0 | 607 | // play with sprintf here. |
michael@0 | 608 | AutoExceptionHandler auto_exception_handler; |
michael@0 | 609 | ExceptionHandler* current_handler = auto_exception_handler.get_handler(); |
michael@0 | 610 | |
michael@0 | 611 | MDRawAssertionInfo assertion; |
michael@0 | 612 | memset(&assertion, 0, sizeof(assertion)); |
michael@0 | 613 | assertion.type = MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL; |
michael@0 | 614 | |
michael@0 | 615 | // Make up an exception record for the current thread and CPU context |
michael@0 | 616 | // to make it possible for the crash processor to classify these |
michael@0 | 617 | // as do regular crashes, and to make it humane for developers to |
michael@0 | 618 | // analyze them. |
michael@0 | 619 | EXCEPTION_RECORD exception_record = {}; |
michael@0 | 620 | CONTEXT exception_context = {}; |
michael@0 | 621 | EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; |
michael@0 | 622 | |
michael@0 | 623 | ::RtlCaptureContext(&exception_context); |
michael@0 | 624 | |
michael@0 | 625 | exception_record.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; |
michael@0 | 626 | |
michael@0 | 627 | // We store pointers to the the expression and function strings, |
michael@0 | 628 | // and the line as exception parameters to make them easy to |
michael@0 | 629 | // access by the developer on the far side. |
michael@0 | 630 | exception_record.NumberParameters = 3; |
michael@0 | 631 | exception_record.ExceptionInformation[0] = |
michael@0 | 632 | reinterpret_cast<ULONG_PTR>(&assertion.expression); |
michael@0 | 633 | exception_record.ExceptionInformation[1] = |
michael@0 | 634 | reinterpret_cast<ULONG_PTR>(&assertion.file); |
michael@0 | 635 | exception_record.ExceptionInformation[2] = assertion.line; |
michael@0 | 636 | |
michael@0 | 637 | bool success = false; |
michael@0 | 638 | // In case of out-of-process dump generation, directly call |
michael@0 | 639 | // WriteMinidumpWithException since there is no separate thread running. |
michael@0 | 640 | |
michael@0 | 641 | if (current_handler->IsOutOfProcess()) { |
michael@0 | 642 | success = current_handler->WriteMinidumpWithException( |
michael@0 | 643 | GetCurrentThreadId(), |
michael@0 | 644 | &exception_ptrs, |
michael@0 | 645 | &assertion); |
michael@0 | 646 | } else { |
michael@0 | 647 | success = current_handler->WriteMinidumpOnHandlerThread(&exception_ptrs, |
michael@0 | 648 | &assertion); |
michael@0 | 649 | } |
michael@0 | 650 | |
michael@0 | 651 | if (!success) { |
michael@0 | 652 | if (current_handler->previous_pch_) { |
michael@0 | 653 | // The handler didn't fully handle the exception. Give it to the |
michael@0 | 654 | // previous purecall handler. |
michael@0 | 655 | current_handler->previous_pch_(); |
michael@0 | 656 | } else { |
michael@0 | 657 | // If there's no previous handler, return and let _purecall handle it. |
michael@0 | 658 | // This will just put up an assertion dialog. |
michael@0 | 659 | return; |
michael@0 | 660 | } |
michael@0 | 661 | } |
michael@0 | 662 | |
michael@0 | 663 | // The handler either took care of the invalid parameter problem itself, |
michael@0 | 664 | // or passed it on to another handler. "Swallow" it by exiting, paralleling |
michael@0 | 665 | // the behavior of "swallowing" exceptions. |
michael@0 | 666 | exit(0); |
michael@0 | 667 | } |
michael@0 | 668 | |
michael@0 | 669 | bool ExceptionHandler::WriteMinidumpOnHandlerThread( |
michael@0 | 670 | EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion) { |
michael@0 | 671 | EnterCriticalSection(&handler_critical_section_); |
michael@0 | 672 | |
michael@0 | 673 | // There isn't much we can do if the handler thread |
michael@0 | 674 | // was not successfully created. |
michael@0 | 675 | if (handler_thread_ == NULL) { |
michael@0 | 676 | LeaveCriticalSection(&handler_critical_section_); |
michael@0 | 677 | return false; |
michael@0 | 678 | } |
michael@0 | 679 | |
michael@0 | 680 | // The handler thread should only be created when the semaphores are valid. |
michael@0 | 681 | assert(handler_start_semaphore_ != NULL); |
michael@0 | 682 | assert(handler_finish_semaphore_ != NULL); |
michael@0 | 683 | |
michael@0 | 684 | // Set up data to be passed in to the handler thread. |
michael@0 | 685 | requesting_thread_id_ = GetCurrentThreadId(); |
michael@0 | 686 | exception_info_ = exinfo; |
michael@0 | 687 | assertion_ = assertion; |
michael@0 | 688 | |
michael@0 | 689 | // This causes the handler thread to call WriteMinidumpWithException. |
michael@0 | 690 | ReleaseSemaphore(handler_start_semaphore_, 1, NULL); |
michael@0 | 691 | |
michael@0 | 692 | // Wait until WriteMinidumpWithException is done and collect its return value. |
michael@0 | 693 | WaitForSingleObject(handler_finish_semaphore_, INFINITE); |
michael@0 | 694 | bool status = handler_return_value_; |
michael@0 | 695 | |
michael@0 | 696 | // Clean up. |
michael@0 | 697 | requesting_thread_id_ = 0; |
michael@0 | 698 | exception_info_ = NULL; |
michael@0 | 699 | assertion_ = NULL; |
michael@0 | 700 | |
michael@0 | 701 | LeaveCriticalSection(&handler_critical_section_); |
michael@0 | 702 | |
michael@0 | 703 | return status; |
michael@0 | 704 | } |
michael@0 | 705 | |
michael@0 | 706 | bool ExceptionHandler::WriteMinidump() { |
michael@0 | 707 | // Make up an exception record for the current thread and CPU context |
michael@0 | 708 | // to make it possible for the crash processor to classify these |
michael@0 | 709 | // as do regular crashes, and to make it humane for developers to |
michael@0 | 710 | // analyze them. |
michael@0 | 711 | EXCEPTION_RECORD exception_record = {}; |
michael@0 | 712 | CONTEXT exception_context = {}; |
michael@0 | 713 | EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; |
michael@0 | 714 | |
michael@0 | 715 | ::RtlCaptureContext(&exception_context); |
michael@0 | 716 | exception_record.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; |
michael@0 | 717 | |
michael@0 | 718 | return WriteMinidumpForException(&exception_ptrs); |
michael@0 | 719 | } |
michael@0 | 720 | |
michael@0 | 721 | bool ExceptionHandler::WriteMinidumpForException(EXCEPTION_POINTERS* exinfo) { |
michael@0 | 722 | // In case of out-of-process dump generation, directly call |
michael@0 | 723 | // WriteMinidumpWithException since there is no separate thread running. |
michael@0 | 724 | if (IsOutOfProcess()) { |
michael@0 | 725 | return WriteMinidumpWithException(GetCurrentThreadId(), |
michael@0 | 726 | exinfo, |
michael@0 | 727 | NULL); |
michael@0 | 728 | } |
michael@0 | 729 | |
michael@0 | 730 | bool success = WriteMinidumpOnHandlerThread(exinfo, NULL); |
michael@0 | 731 | UpdateNextID(); |
michael@0 | 732 | return success; |
michael@0 | 733 | } |
michael@0 | 734 | |
michael@0 | 735 | // static |
michael@0 | 736 | bool ExceptionHandler::WriteMinidump(const wstring &dump_path, |
michael@0 | 737 | MinidumpCallback callback, |
michael@0 | 738 | void* callback_context) { |
michael@0 | 739 | ExceptionHandler handler(dump_path, NULL, callback, callback_context, |
michael@0 | 740 | HANDLER_NONE); |
michael@0 | 741 | return handler.WriteMinidump(); |
michael@0 | 742 | } |
michael@0 | 743 | |
michael@0 | 744 | // static |
michael@0 | 745 | bool ExceptionHandler::WriteMinidumpForChild(HANDLE child, |
michael@0 | 746 | DWORD child_blamed_thread, |
michael@0 | 747 | const wstring& dump_path, |
michael@0 | 748 | MinidumpCallback callback, |
michael@0 | 749 | void* callback_context) { |
michael@0 | 750 | EXCEPTION_RECORD ex; |
michael@0 | 751 | CONTEXT ctx; |
michael@0 | 752 | EXCEPTION_POINTERS exinfo = { NULL, NULL }; |
michael@0 | 753 | DWORD last_suspend_count = kFailedToSuspendThread; |
michael@0 | 754 | HANDLE child_thread_handle = OpenThread(THREAD_GET_CONTEXT | |
michael@0 | 755 | THREAD_QUERY_INFORMATION | |
michael@0 | 756 | THREAD_SUSPEND_RESUME, |
michael@0 | 757 | FALSE, |
michael@0 | 758 | child_blamed_thread); |
michael@0 | 759 | // This thread may have died already, so not opening the handle is a |
michael@0 | 760 | // non-fatal error. |
michael@0 | 761 | if (child_thread_handle != NULL) { |
michael@0 | 762 | last_suspend_count = SuspendThread(child_thread_handle); |
michael@0 | 763 | if (last_suspend_count != kFailedToSuspendThread) { |
michael@0 | 764 | ctx.ContextFlags = CONTEXT_ALL; |
michael@0 | 765 | if (GetThreadContext(child_thread_handle, &ctx)) { |
michael@0 | 766 | memset(&ex, 0, sizeof(ex)); |
michael@0 | 767 | ex.ExceptionCode = EXCEPTION_BREAKPOINT; |
michael@0 | 768 | #if defined(_M_IX86) |
michael@0 | 769 | ex.ExceptionAddress = reinterpret_cast<PVOID>(ctx.Eip); |
michael@0 | 770 | #elif defined(_M_X64) |
michael@0 | 771 | ex.ExceptionAddress = reinterpret_cast<PVOID>(ctx.Rip); |
michael@0 | 772 | #endif |
michael@0 | 773 | exinfo.ExceptionRecord = &ex; |
michael@0 | 774 | exinfo.ContextRecord = &ctx; |
michael@0 | 775 | } |
michael@0 | 776 | } |
michael@0 | 777 | } |
michael@0 | 778 | |
michael@0 | 779 | ExceptionHandler handler(dump_path, NULL, callback, callback_context, |
michael@0 | 780 | HANDLER_NONE); |
michael@0 | 781 | bool success = handler.WriteMinidumpWithExceptionForProcess( |
michael@0 | 782 | child_blamed_thread, |
michael@0 | 783 | exinfo.ExceptionRecord ? &exinfo : NULL, |
michael@0 | 784 | NULL, child, false); |
michael@0 | 785 | |
michael@0 | 786 | if (last_suspend_count != kFailedToSuspendThread) { |
michael@0 | 787 | ResumeThread(child_thread_handle); |
michael@0 | 788 | } |
michael@0 | 789 | |
michael@0 | 790 | CloseHandle(child_thread_handle); |
michael@0 | 791 | |
michael@0 | 792 | if (callback) { |
michael@0 | 793 | success = callback(handler.dump_path_c_, handler.next_minidump_id_c_, |
michael@0 | 794 | callback_context, NULL, NULL, success); |
michael@0 | 795 | } |
michael@0 | 796 | |
michael@0 | 797 | return success; |
michael@0 | 798 | } |
michael@0 | 799 | |
michael@0 | 800 | bool ExceptionHandler::WriteMinidumpWithException( |
michael@0 | 801 | DWORD requesting_thread_id, |
michael@0 | 802 | EXCEPTION_POINTERS* exinfo, |
michael@0 | 803 | MDRawAssertionInfo* assertion) { |
michael@0 | 804 | // Give user code a chance to approve or prevent writing a minidump. If the |
michael@0 | 805 | // filter returns false, don't handle the exception at all. If this method |
michael@0 | 806 | // was called as a result of an exception, returning false will cause |
michael@0 | 807 | // HandleException to call any previous handler or return |
michael@0 | 808 | // EXCEPTION_CONTINUE_SEARCH on the exception thread, allowing it to appear |
michael@0 | 809 | // as though this handler were not present at all. |
michael@0 | 810 | if (filter_ && !filter_(callback_context_, exinfo, assertion)) { |
michael@0 | 811 | return false; |
michael@0 | 812 | } |
michael@0 | 813 | |
michael@0 | 814 | bool success = false; |
michael@0 | 815 | if (IsOutOfProcess()) { |
michael@0 | 816 | success = crash_generation_client_->RequestDump(exinfo, assertion); |
michael@0 | 817 | } else { |
michael@0 | 818 | success = WriteMinidumpWithExceptionForProcess(requesting_thread_id, |
michael@0 | 819 | exinfo, |
michael@0 | 820 | assertion, |
michael@0 | 821 | GetCurrentProcess(), |
michael@0 | 822 | true); |
michael@0 | 823 | } |
michael@0 | 824 | |
michael@0 | 825 | if (callback_) { |
michael@0 | 826 | // TODO(munjal): In case of out-of-process dump generation, both |
michael@0 | 827 | // dump_path_c_ and next_minidump_id_ will be NULL. For out-of-process |
michael@0 | 828 | // scenario, the server process ends up creating the dump path and dump |
michael@0 | 829 | // id so they are not known to the client. |
michael@0 | 830 | success = callback_(dump_path_c_, next_minidump_id_c_, callback_context_, |
michael@0 | 831 | exinfo, assertion, success); |
michael@0 | 832 | } |
michael@0 | 833 | |
michael@0 | 834 | return success; |
michael@0 | 835 | } |
michael@0 | 836 | |
michael@0 | 837 | // static |
michael@0 | 838 | BOOL CALLBACK ExceptionHandler::MinidumpWriteDumpCallback( |
michael@0 | 839 | PVOID context, |
michael@0 | 840 | const PMINIDUMP_CALLBACK_INPUT callback_input, |
michael@0 | 841 | PMINIDUMP_CALLBACK_OUTPUT callback_output) { |
michael@0 | 842 | switch (callback_input->CallbackType) { |
michael@0 | 843 | case MemoryCallback: { |
michael@0 | 844 | MinidumpCallbackContext* callback_context = |
michael@0 | 845 | reinterpret_cast<MinidumpCallbackContext*>(context); |
michael@0 | 846 | if (callback_context->iter == callback_context->end) |
michael@0 | 847 | return FALSE; |
michael@0 | 848 | |
michael@0 | 849 | // Include the specified memory region. |
michael@0 | 850 | callback_output->MemoryBase = callback_context->iter->ptr; |
michael@0 | 851 | callback_output->MemorySize = callback_context->iter->length; |
michael@0 | 852 | callback_context->iter++; |
michael@0 | 853 | return TRUE; |
michael@0 | 854 | } |
michael@0 | 855 | |
michael@0 | 856 | // Include all modules. |
michael@0 | 857 | case IncludeModuleCallback: |
michael@0 | 858 | case ModuleCallback: |
michael@0 | 859 | return TRUE; |
michael@0 | 860 | |
michael@0 | 861 | // Include all threads. |
michael@0 | 862 | case IncludeThreadCallback: |
michael@0 | 863 | case ThreadCallback: |
michael@0 | 864 | return TRUE; |
michael@0 | 865 | |
michael@0 | 866 | // Stop receiving cancel callbacks. |
michael@0 | 867 | case CancelCallback: |
michael@0 | 868 | callback_output->CheckCancel = FALSE; |
michael@0 | 869 | callback_output->Cancel = FALSE; |
michael@0 | 870 | return TRUE; |
michael@0 | 871 | } |
michael@0 | 872 | // Ignore other callback types. |
michael@0 | 873 | return FALSE; |
michael@0 | 874 | } |
michael@0 | 875 | |
michael@0 | 876 | bool ExceptionHandler::WriteMinidumpWithExceptionForProcess( |
michael@0 | 877 | DWORD requesting_thread_id, |
michael@0 | 878 | EXCEPTION_POINTERS* exinfo, |
michael@0 | 879 | MDRawAssertionInfo* assertion, |
michael@0 | 880 | HANDLE process, |
michael@0 | 881 | bool write_requester_stream) { |
michael@0 | 882 | bool success = false; |
michael@0 | 883 | if (minidump_write_dump_) { |
michael@0 | 884 | HANDLE dump_file = CreateFile(next_minidump_path_c_, |
michael@0 | 885 | GENERIC_WRITE, |
michael@0 | 886 | 0, // no sharing |
michael@0 | 887 | NULL, |
michael@0 | 888 | CREATE_NEW, // fail if exists |
michael@0 | 889 | FILE_ATTRIBUTE_NORMAL, |
michael@0 | 890 | NULL); |
michael@0 | 891 | if (dump_file != INVALID_HANDLE_VALUE) { |
michael@0 | 892 | MINIDUMP_EXCEPTION_INFORMATION except_info; |
michael@0 | 893 | except_info.ThreadId = requesting_thread_id; |
michael@0 | 894 | except_info.ExceptionPointers = exinfo; |
michael@0 | 895 | except_info.ClientPointers = FALSE; |
michael@0 | 896 | |
michael@0 | 897 | // Leave room in user_stream_array for possible breakpad and |
michael@0 | 898 | // assertion info streams. |
michael@0 | 899 | MINIDUMP_USER_STREAM user_stream_array[2]; |
michael@0 | 900 | MINIDUMP_USER_STREAM_INFORMATION user_streams; |
michael@0 | 901 | user_streams.UserStreamCount = 0; |
michael@0 | 902 | user_streams.UserStreamArray = user_stream_array; |
michael@0 | 903 | |
michael@0 | 904 | if (write_requester_stream) { |
michael@0 | 905 | // Add an MDRawBreakpadInfo stream to the minidump, to provide |
michael@0 | 906 | // additional information about the exception handler to the Breakpad |
michael@0 | 907 | // processor. The information will help the processor determine which |
michael@0 | 908 | // threads are relevant. The Breakpad processor does not require this |
michael@0 | 909 | // information but can function better with Breakpad-generated dumps |
michael@0 | 910 | // when it is present. The native debugger is not harmed by the |
michael@0 | 911 | // presence of this information. |
michael@0 | 912 | MDRawBreakpadInfo breakpad_info; |
michael@0 | 913 | breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | |
michael@0 | 914 | MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; |
michael@0 | 915 | breakpad_info.dump_thread_id = GetCurrentThreadId(); |
michael@0 | 916 | breakpad_info.requesting_thread_id = requesting_thread_id; |
michael@0 | 917 | |
michael@0 | 918 | int index = user_streams.UserStreamCount; |
michael@0 | 919 | user_stream_array[index].Type = MD_BREAKPAD_INFO_STREAM; |
michael@0 | 920 | user_stream_array[index].BufferSize = sizeof(breakpad_info); |
michael@0 | 921 | user_stream_array[index].Buffer = &breakpad_info; |
michael@0 | 922 | ++user_streams.UserStreamCount; |
michael@0 | 923 | } |
michael@0 | 924 | |
michael@0 | 925 | if (assertion) { |
michael@0 | 926 | int index = user_streams.UserStreamCount; |
michael@0 | 927 | user_stream_array[index].Type = MD_ASSERTION_INFO_STREAM; |
michael@0 | 928 | user_stream_array[index].BufferSize = sizeof(MDRawAssertionInfo); |
michael@0 | 929 | user_stream_array[index].Buffer = assertion; |
michael@0 | 930 | ++user_streams.UserStreamCount; |
michael@0 | 931 | } |
michael@0 | 932 | |
michael@0 | 933 | // Older versions of DbgHelp.dll don't correctly put the memory around |
michael@0 | 934 | // the faulting instruction pointer into the minidump. This |
michael@0 | 935 | // callback will ensure that it gets included. |
michael@0 | 936 | if (exinfo) { |
michael@0 | 937 | // Find a memory region of 256 bytes centered on the |
michael@0 | 938 | // faulting instruction pointer. |
michael@0 | 939 | const ULONG64 instruction_pointer = |
michael@0 | 940 | #if defined(_M_IX86) |
michael@0 | 941 | exinfo->ContextRecord->Eip; |
michael@0 | 942 | #elif defined(_M_AMD64) |
michael@0 | 943 | exinfo->ContextRecord->Rip; |
michael@0 | 944 | #else |
michael@0 | 945 | #error Unsupported platform |
michael@0 | 946 | #endif |
michael@0 | 947 | |
michael@0 | 948 | MEMORY_BASIC_INFORMATION info; |
michael@0 | 949 | if (VirtualQueryEx(process, |
michael@0 | 950 | reinterpret_cast<LPCVOID>(instruction_pointer), |
michael@0 | 951 | &info, |
michael@0 | 952 | sizeof(MEMORY_BASIC_INFORMATION)) != 0 && |
michael@0 | 953 | info.State == MEM_COMMIT) { |
michael@0 | 954 | // Attempt to get 128 bytes before and after the instruction |
michael@0 | 955 | // pointer, but settle for whatever's available up to the |
michael@0 | 956 | // boundaries of the memory region. |
michael@0 | 957 | const ULONG64 kIPMemorySize = 256; |
michael@0 | 958 | ULONG64 base = |
michael@0 | 959 | (std::max)(reinterpret_cast<ULONG64>(info.BaseAddress), |
michael@0 | 960 | instruction_pointer - (kIPMemorySize / 2)); |
michael@0 | 961 | ULONG64 end_of_range = |
michael@0 | 962 | (std::min)(instruction_pointer + (kIPMemorySize / 2), |
michael@0 | 963 | reinterpret_cast<ULONG64>(info.BaseAddress) |
michael@0 | 964 | + info.RegionSize); |
michael@0 | 965 | ULONG size = static_cast<ULONG>(end_of_range - base); |
michael@0 | 966 | |
michael@0 | 967 | AppMemory& elt = app_memory_info_.front(); |
michael@0 | 968 | elt.ptr = base; |
michael@0 | 969 | elt.length = size; |
michael@0 | 970 | } |
michael@0 | 971 | } |
michael@0 | 972 | |
michael@0 | 973 | MinidumpCallbackContext context; |
michael@0 | 974 | context.iter = app_memory_info_.begin(); |
michael@0 | 975 | context.end = app_memory_info_.end(); |
michael@0 | 976 | |
michael@0 | 977 | // Skip the reserved element if there was no instruction memory |
michael@0 | 978 | if (context.iter->ptr == 0) { |
michael@0 | 979 | context.iter++; |
michael@0 | 980 | } |
michael@0 | 981 | |
michael@0 | 982 | MINIDUMP_CALLBACK_INFORMATION callback; |
michael@0 | 983 | callback.CallbackRoutine = MinidumpWriteDumpCallback; |
michael@0 | 984 | callback.CallbackParam = reinterpret_cast<void*>(&context); |
michael@0 | 985 | |
michael@0 | 986 | // The explicit comparison to TRUE avoids a warning (C4800). |
michael@0 | 987 | success = (minidump_write_dump_(process, |
michael@0 | 988 | GetProcessId(process), |
michael@0 | 989 | dump_file, |
michael@0 | 990 | dump_type_, |
michael@0 | 991 | exinfo ? &except_info : NULL, |
michael@0 | 992 | &user_streams, |
michael@0 | 993 | &callback) == TRUE); |
michael@0 | 994 | |
michael@0 | 995 | CloseHandle(dump_file); |
michael@0 | 996 | } |
michael@0 | 997 | } |
michael@0 | 998 | |
michael@0 | 999 | return success; |
michael@0 | 1000 | } |
michael@0 | 1001 | |
michael@0 | 1002 | void ExceptionHandler::UpdateNextID() { |
michael@0 | 1003 | assert(uuid_create_); |
michael@0 | 1004 | UUID id = {0}; |
michael@0 | 1005 | if (uuid_create_) { |
michael@0 | 1006 | uuid_create_(&id); |
michael@0 | 1007 | } |
michael@0 | 1008 | next_minidump_id_ = GUIDString::GUIDToWString(&id); |
michael@0 | 1009 | next_minidump_id_c_ = next_minidump_id_.c_str(); |
michael@0 | 1010 | |
michael@0 | 1011 | wchar_t minidump_path[MAX_PATH]; |
michael@0 | 1012 | swprintf(minidump_path, MAX_PATH, L"%s\\%s.dmp", |
michael@0 | 1013 | dump_path_c_, next_minidump_id_c_); |
michael@0 | 1014 | |
michael@0 | 1015 | // remove when VC++7.1 is no longer supported |
michael@0 | 1016 | minidump_path[MAX_PATH - 1] = L'\0'; |
michael@0 | 1017 | |
michael@0 | 1018 | next_minidump_path_ = minidump_path; |
michael@0 | 1019 | next_minidump_path_c_ = next_minidump_path_.c_str(); |
michael@0 | 1020 | } |
michael@0 | 1021 | |
michael@0 | 1022 | void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) { |
michael@0 | 1023 | AppMemoryList::iterator iter = |
michael@0 | 1024 | std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr); |
michael@0 | 1025 | if (iter != app_memory_info_.end()) { |
michael@0 | 1026 | // Don't allow registering the same pointer twice. |
michael@0 | 1027 | return; |
michael@0 | 1028 | } |
michael@0 | 1029 | |
michael@0 | 1030 | AppMemory app_memory; |
michael@0 | 1031 | app_memory.ptr = reinterpret_cast<ULONG64>(ptr); |
michael@0 | 1032 | app_memory.length = static_cast<ULONG>(length); |
michael@0 | 1033 | app_memory_info_.push_back(app_memory); |
michael@0 | 1034 | } |
michael@0 | 1035 | |
michael@0 | 1036 | void ExceptionHandler::UnregisterAppMemory(void* ptr) { |
michael@0 | 1037 | AppMemoryList::iterator iter = |
michael@0 | 1038 | std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr); |
michael@0 | 1039 | if (iter != app_memory_info_.end()) { |
michael@0 | 1040 | app_memory_info_.erase(iter); |
michael@0 | 1041 | } |
michael@0 | 1042 | } |
michael@0 | 1043 | |
michael@0 | 1044 | } // namespace google_breakpad |