michael@0: // Copyright (c) 2006, Google Inc. michael@0: // All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions are michael@0: // met: michael@0: // michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above michael@0: // copyright notice, this list of conditions and the following disclaimer michael@0: // in the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google Inc. nor the names of its michael@0: // contributors may be used to endorse or promote products derived from michael@0: // this software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "common/windows/string_utils-inl.h" michael@0: michael@0: #include "client/windows/common/ipc_protocol.h" michael@0: #include "client/windows/handler/exception_handler.h" michael@0: #include "common/windows/guid_string.h" michael@0: michael@0: namespace google_breakpad { michael@0: michael@0: static const int kWaitForHandlerThreadMs = 60000; michael@0: static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024; michael@0: michael@0: // As documented on MSDN, on failure SuspendThread returns (DWORD) -1 michael@0: static const DWORD kFailedToSuspendThread = static_cast(-1); michael@0: michael@0: // This is passed as the context to the MinidumpWriteDump callback. michael@0: typedef struct { michael@0: AppMemoryList::const_iterator iter; michael@0: AppMemoryList::const_iterator end; michael@0: } MinidumpCallbackContext; michael@0: michael@0: vector* ExceptionHandler::handler_stack_ = NULL; michael@0: LONG ExceptionHandler::handler_stack_index_ = 0; michael@0: CRITICAL_SECTION ExceptionHandler::handler_stack_critical_section_; michael@0: volatile LONG ExceptionHandler::instance_count_ = 0; michael@0: michael@0: ExceptionHandler::ExceptionHandler(const wstring& dump_path, michael@0: FilterCallback filter, michael@0: MinidumpCallback callback, michael@0: void* callback_context, michael@0: int handler_types, michael@0: MINIDUMP_TYPE dump_type, michael@0: const wchar_t* pipe_name, michael@0: const CustomClientInfo* custom_info) { michael@0: Initialize(dump_path, michael@0: filter, michael@0: callback, michael@0: callback_context, michael@0: handler_types, michael@0: dump_type, michael@0: pipe_name, michael@0: NULL, michael@0: custom_info); michael@0: } michael@0: michael@0: ExceptionHandler::ExceptionHandler(const wstring& dump_path, michael@0: FilterCallback filter, michael@0: MinidumpCallback callback, michael@0: void* callback_context, michael@0: int handler_types, michael@0: MINIDUMP_TYPE dump_type, michael@0: HANDLE pipe_handle, michael@0: const CustomClientInfo* custom_info) { michael@0: Initialize(dump_path, michael@0: filter, michael@0: callback, michael@0: callback_context, michael@0: handler_types, michael@0: dump_type, michael@0: NULL, michael@0: pipe_handle, michael@0: custom_info); michael@0: } michael@0: michael@0: ExceptionHandler::ExceptionHandler(const wstring &dump_path, michael@0: FilterCallback filter, michael@0: MinidumpCallback callback, michael@0: void* callback_context, michael@0: int handler_types) { michael@0: Initialize(dump_path, michael@0: filter, michael@0: callback, michael@0: callback_context, michael@0: handler_types, michael@0: MiniDumpNormal, michael@0: NULL, michael@0: NULL, michael@0: NULL); michael@0: } michael@0: michael@0: void ExceptionHandler::Initialize(const wstring& dump_path, michael@0: FilterCallback filter, michael@0: MinidumpCallback callback, michael@0: void* callback_context, michael@0: int handler_types, michael@0: MINIDUMP_TYPE dump_type, michael@0: const wchar_t* pipe_name, michael@0: HANDLE pipe_handle, michael@0: const CustomClientInfo* custom_info) { michael@0: LONG instance_count = InterlockedIncrement(&instance_count_); michael@0: filter_ = filter; michael@0: callback_ = callback; michael@0: callback_context_ = callback_context; michael@0: dump_path_c_ = NULL; michael@0: next_minidump_id_c_ = NULL; michael@0: next_minidump_path_c_ = NULL; michael@0: dbghelp_module_ = NULL; michael@0: minidump_write_dump_ = NULL; michael@0: dump_type_ = dump_type; michael@0: rpcrt4_module_ = NULL; michael@0: uuid_create_ = NULL; michael@0: handler_types_ = handler_types; michael@0: previous_filter_ = NULL; michael@0: #if _MSC_VER >= 1400 // MSVC 2005/8 michael@0: previous_iph_ = NULL; michael@0: #endif // _MSC_VER >= 1400 michael@0: previous_pch_ = NULL; michael@0: handler_thread_ = NULL; michael@0: is_shutdown_ = false; michael@0: handler_start_semaphore_ = NULL; michael@0: handler_finish_semaphore_ = NULL; michael@0: requesting_thread_id_ = 0; michael@0: exception_info_ = NULL; michael@0: assertion_ = NULL; michael@0: handler_return_value_ = false; michael@0: handle_debug_exceptions_ = false; michael@0: michael@0: // Attempt to use out-of-process if user has specified a pipe. michael@0: if (pipe_name != NULL || pipe_handle != NULL) { michael@0: assert(!(pipe_name && pipe_handle)); michael@0: michael@0: scoped_ptr client; michael@0: if (pipe_name) { michael@0: client.reset( michael@0: new CrashGenerationClient(pipe_name, michael@0: dump_type_, michael@0: custom_info)); michael@0: } else { michael@0: client.reset( michael@0: new CrashGenerationClient(pipe_handle, michael@0: dump_type_, michael@0: custom_info)); michael@0: } michael@0: michael@0: // If successful in registering with the monitoring process, michael@0: // there is no need to setup in-process crash generation. michael@0: if (client->Register()) { michael@0: crash_generation_client_.reset(client.release()); michael@0: } michael@0: } michael@0: michael@0: if (!IsOutOfProcess()) { michael@0: // Either client did not ask for out-of-process crash generation michael@0: // or registration with the server process failed. In either case, michael@0: // setup to do in-process crash generation. michael@0: michael@0: // Set synchronization primitives and the handler thread. Each michael@0: // ExceptionHandler object gets its own handler thread because that's the michael@0: // only way to reliably guarantee sufficient stack space in an exception, michael@0: // and it allows an easy way to get a snapshot of the requesting thread's michael@0: // context outside of an exception. michael@0: InitializeCriticalSection(&handler_critical_section_); michael@0: handler_start_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL); michael@0: assert(handler_start_semaphore_ != NULL); michael@0: michael@0: handler_finish_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL); michael@0: assert(handler_finish_semaphore_ != NULL); michael@0: michael@0: // Don't attempt to create the thread if we could not create the semaphores. michael@0: if (handler_finish_semaphore_ != NULL && handler_start_semaphore_ != NULL) { michael@0: DWORD thread_id; michael@0: handler_thread_ = CreateThread(NULL, // lpThreadAttributes michael@0: kExceptionHandlerThreadInitialStackSize, michael@0: ExceptionHandlerThreadMain, michael@0: this, // lpParameter michael@0: 0, // dwCreationFlags michael@0: &thread_id); michael@0: assert(handler_thread_ != NULL); michael@0: } michael@0: michael@0: dbghelp_module_ = LoadLibrary(L"dbghelp.dll"); michael@0: if (dbghelp_module_) { michael@0: minidump_write_dump_ = reinterpret_cast( michael@0: GetProcAddress(dbghelp_module_, "MiniDumpWriteDump")); michael@0: } michael@0: michael@0: // Load this library dynamically to not affect existing projects. Most michael@0: // projects don't link against this directly, it's usually dynamically michael@0: // loaded by dependent code. michael@0: rpcrt4_module_ = LoadLibrary(L"rpcrt4.dll"); michael@0: if (rpcrt4_module_) { michael@0: uuid_create_ = reinterpret_cast( michael@0: GetProcAddress(rpcrt4_module_, "UuidCreate")); michael@0: } michael@0: michael@0: // set_dump_path calls UpdateNextID. This sets up all of the path and id michael@0: // strings, and their equivalent c_str pointers. michael@0: set_dump_path(dump_path); michael@0: } michael@0: michael@0: // Reserve one element for the instruction memory michael@0: AppMemory instruction_memory; michael@0: instruction_memory.ptr = NULL; michael@0: instruction_memory.length = 0; michael@0: app_memory_info_.push_back(instruction_memory); michael@0: michael@0: // There is a race condition here. If the first instance has not yet michael@0: // initialized the critical section, the second (and later) instances may michael@0: // try to use uninitialized critical section object. The feature of multiple michael@0: // instances in one module is not used much, so leave it as is for now. michael@0: // One way to solve this in the current design (that is, keeping the static michael@0: // handler stack) is to use spin locks with volatile bools to synchronize michael@0: // the handler stack. This works only if the compiler guarantees to generate michael@0: // cache coherent code for volatile. michael@0: // TODO(munjal): Fix this in a better way by changing the design if possible. michael@0: michael@0: // Lazy initialization of the handler_stack_critical_section_ michael@0: if (instance_count == 1) { michael@0: InitializeCriticalSection(&handler_stack_critical_section_); michael@0: } michael@0: michael@0: if (handler_types != HANDLER_NONE) { michael@0: EnterCriticalSection(&handler_stack_critical_section_); michael@0: michael@0: // The first time an ExceptionHandler that installs a handler is michael@0: // created, set up the handler stack. michael@0: if (!handler_stack_) { michael@0: handler_stack_ = new vector(); michael@0: } michael@0: handler_stack_->push_back(this); michael@0: michael@0: if (handler_types & HANDLER_EXCEPTION) michael@0: previous_filter_ = SetUnhandledExceptionFilter(HandleException); michael@0: michael@0: #if _MSC_VER >= 1400 // MSVC 2005/8 michael@0: if (handler_types & HANDLER_INVALID_PARAMETER) michael@0: previous_iph_ = _set_invalid_parameter_handler(HandleInvalidParameter); michael@0: #endif // _MSC_VER >= 1400 michael@0: michael@0: if (handler_types & HANDLER_PURECALL) michael@0: previous_pch_ = _set_purecall_handler(HandlePureVirtualCall); michael@0: michael@0: LeaveCriticalSection(&handler_stack_critical_section_); michael@0: } michael@0: } michael@0: michael@0: ExceptionHandler::~ExceptionHandler() { michael@0: if (dbghelp_module_) { michael@0: FreeLibrary(dbghelp_module_); michael@0: } michael@0: michael@0: if (rpcrt4_module_) { michael@0: FreeLibrary(rpcrt4_module_); michael@0: } michael@0: michael@0: if (handler_types_ != HANDLER_NONE) { michael@0: EnterCriticalSection(&handler_stack_critical_section_); michael@0: michael@0: if (handler_types_ & HANDLER_EXCEPTION) michael@0: SetUnhandledExceptionFilter(previous_filter_); michael@0: michael@0: #if _MSC_VER >= 1400 // MSVC 2005/8 michael@0: if (handler_types_ & HANDLER_INVALID_PARAMETER) michael@0: _set_invalid_parameter_handler(previous_iph_); michael@0: #endif // _MSC_VER >= 1400 michael@0: michael@0: if (handler_types_ & HANDLER_PURECALL) michael@0: _set_purecall_handler(previous_pch_); michael@0: michael@0: if (handler_stack_->back() == this) { michael@0: handler_stack_->pop_back(); michael@0: } else { michael@0: // TODO(mmentovai): use advapi32!ReportEvent to log the warning to the michael@0: // system's application event log. michael@0: fprintf(stderr, "warning: removing Breakpad handler out of order\n"); michael@0: vector::iterator iterator = handler_stack_->begin(); michael@0: while (iterator != handler_stack_->end()) { michael@0: if (*iterator == this) { michael@0: iterator = handler_stack_->erase(iterator); michael@0: } else { michael@0: ++iterator; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (handler_stack_->empty()) { michael@0: // When destroying the last ExceptionHandler that installed a handler, michael@0: // clean up the handler stack. michael@0: delete handler_stack_; michael@0: handler_stack_ = NULL; michael@0: } michael@0: michael@0: LeaveCriticalSection(&handler_stack_critical_section_); michael@0: } michael@0: michael@0: // Some of the objects were only initialized if out of process michael@0: // registration was not done. michael@0: if (!IsOutOfProcess()) { michael@0: #ifdef BREAKPAD_NO_TERMINATE_THREAD michael@0: // Clean up the handler thread and synchronization primitives. The handler michael@0: // thread is either waiting on the semaphore to handle a crash or it is michael@0: // handling a crash. Coming out of the wait is fast but wait more in the michael@0: // eventuality a crash is handled. This compilation option results in a michael@0: // deadlock if the exception handler is destroyed while executing code michael@0: // inside DllMain. michael@0: is_shutdown_ = true; michael@0: ReleaseSemaphore(handler_start_semaphore_, 1, NULL); michael@0: WaitForSingleObject(handler_thread_, kWaitForHandlerThreadMs); michael@0: #else michael@0: TerminateThread(handler_thread_, 1); michael@0: #endif // BREAKPAD_NO_TERMINATE_THREAD michael@0: michael@0: CloseHandle(handler_thread_); michael@0: handler_thread_ = NULL; michael@0: DeleteCriticalSection(&handler_critical_section_); michael@0: CloseHandle(handler_start_semaphore_); michael@0: CloseHandle(handler_finish_semaphore_); michael@0: } michael@0: michael@0: // There is a race condition in the code below: if this instance is michael@0: // deleting the static critical section and a new instance of the class michael@0: // is created, then there is a possibility that the critical section be michael@0: // initialized while the same critical section is being deleted. Given the michael@0: // usage pattern for the code, this race condition is unlikely to hit, but it michael@0: // is a race condition nonetheless. michael@0: if (InterlockedDecrement(&instance_count_) == 0) { michael@0: DeleteCriticalSection(&handler_stack_critical_section_); michael@0: } michael@0: } michael@0: michael@0: bool ExceptionHandler::RequestUpload(DWORD crash_id) { michael@0: return crash_generation_client_->RequestUpload(crash_id); michael@0: } michael@0: michael@0: // static michael@0: DWORD ExceptionHandler::ExceptionHandlerThreadMain(void* lpParameter) { michael@0: ExceptionHandler* self = reinterpret_cast(lpParameter); michael@0: assert(self); michael@0: assert(self->handler_start_semaphore_ != NULL); michael@0: assert(self->handler_finish_semaphore_ != NULL); michael@0: michael@0: while (true) { michael@0: if (WaitForSingleObject(self->handler_start_semaphore_, INFINITE) == michael@0: WAIT_OBJECT_0) { michael@0: // Perform the requested action. michael@0: if (self->is_shutdown_) { michael@0: // The instance of the exception handler is being destroyed. michael@0: break; michael@0: } else { michael@0: self->handler_return_value_ = michael@0: self->WriteMinidumpWithException(self->requesting_thread_id_, michael@0: self->exception_info_, michael@0: self->assertion_); michael@0: } michael@0: michael@0: // Allow the requesting thread to proceed. michael@0: ReleaseSemaphore(self->handler_finish_semaphore_, 1, NULL); michael@0: } michael@0: } michael@0: michael@0: // This statement is not reached when the thread is unconditionally michael@0: // terminated by the ExceptionHandler destructor. michael@0: return 0; michael@0: } michael@0: michael@0: // HandleException and HandleInvalidParameter must create an michael@0: // AutoExceptionHandler object to maintain static state and to determine which michael@0: // ExceptionHandler instance to use. The constructor locates the correct michael@0: // instance, and makes it available through get_handler(). The destructor michael@0: // restores the state in effect prior to allocating the AutoExceptionHandler. michael@0: class AutoExceptionHandler { michael@0: public: michael@0: AutoExceptionHandler() { michael@0: // Increment handler_stack_index_ so that if another Breakpad handler is michael@0: // registered using this same HandleException function, and it needs to be michael@0: // called while this handler is running (either because this handler michael@0: // declines to handle the exception, or an exception occurs during michael@0: // handling), HandleException will find the appropriate ExceptionHandler michael@0: // object in handler_stack_ to deliver the exception to. michael@0: // michael@0: // Because handler_stack_ is addressed in reverse (as |size - index|), michael@0: // preincrementing handler_stack_index_ avoids needing to subtract 1 from michael@0: // the argument to |at|. michael@0: // michael@0: // The index is maintained instead of popping elements off of the handler michael@0: // stack and pushing them at the end of this method. This avoids ruining michael@0: // the order of elements in the stack in the event that some other thread michael@0: // decides to manipulate the handler stack (such as creating a new michael@0: // ExceptionHandler object) while an exception is being handled. michael@0: EnterCriticalSection(&ExceptionHandler::handler_stack_critical_section_); michael@0: handler_ = ExceptionHandler::handler_stack_->at( michael@0: ExceptionHandler::handler_stack_->size() - michael@0: ++ExceptionHandler::handler_stack_index_); michael@0: michael@0: // In case another exception occurs while this handler is doing its thing, michael@0: // it should be delivered to the previous filter. michael@0: SetUnhandledExceptionFilter(handler_->previous_filter_); michael@0: #if _MSC_VER >= 1400 // MSVC 2005/8 michael@0: _set_invalid_parameter_handler(handler_->previous_iph_); michael@0: #endif // _MSC_VER >= 1400 michael@0: _set_purecall_handler(handler_->previous_pch_); michael@0: } michael@0: michael@0: ~AutoExceptionHandler() { michael@0: // Put things back the way they were before entering this handler. michael@0: SetUnhandledExceptionFilter(ExceptionHandler::HandleException); michael@0: #if _MSC_VER >= 1400 // MSVC 2005/8 michael@0: _set_invalid_parameter_handler(ExceptionHandler::HandleInvalidParameter); michael@0: #endif // _MSC_VER >= 1400 michael@0: _set_purecall_handler(ExceptionHandler::HandlePureVirtualCall); michael@0: michael@0: --ExceptionHandler::handler_stack_index_; michael@0: LeaveCriticalSection(&ExceptionHandler::handler_stack_critical_section_); michael@0: } michael@0: michael@0: ExceptionHandler* get_handler() const { return handler_; } michael@0: michael@0: private: michael@0: ExceptionHandler* handler_; michael@0: }; michael@0: michael@0: // static michael@0: LONG ExceptionHandler::HandleException(EXCEPTION_POINTERS* exinfo) { michael@0: AutoExceptionHandler auto_exception_handler; michael@0: ExceptionHandler* current_handler = auto_exception_handler.get_handler(); michael@0: michael@0: // Ignore EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP exceptions. This michael@0: // logic will short-circuit before calling WriteMinidumpOnHandlerThread, michael@0: // allowing something else to handle the breakpoint without incurring the michael@0: // overhead transitioning to and from the handler thread. This behavior michael@0: // can be overridden by calling ExceptionHandler::set_handle_debug_exceptions. michael@0: DWORD code = exinfo->ExceptionRecord->ExceptionCode; michael@0: LONG action; michael@0: bool is_debug_exception = (code == EXCEPTION_BREAKPOINT) || michael@0: (code == EXCEPTION_SINGLE_STEP); michael@0: michael@0: bool success = false; michael@0: michael@0: if (!is_debug_exception || michael@0: current_handler->get_handle_debug_exceptions()) { michael@0: // If out-of-proc crash handler client is available, we have to use that michael@0: // to generate dump and we cannot fall back on in-proc dump generation michael@0: // because we never prepared for an in-proc dump generation michael@0: michael@0: // In case of out-of-process dump generation, directly call michael@0: // WriteMinidumpWithException since there is no separate thread running. michael@0: if (current_handler->IsOutOfProcess()) { michael@0: success = current_handler->WriteMinidumpWithException( michael@0: GetCurrentThreadId(), michael@0: exinfo, michael@0: NULL); michael@0: } else { michael@0: success = current_handler->WriteMinidumpOnHandlerThread(exinfo, NULL); michael@0: } michael@0: } michael@0: michael@0: // The handler fully handled the exception. Returning michael@0: // EXCEPTION_EXECUTE_HANDLER indicates this to the system, and usually michael@0: // results in the application being terminated. michael@0: // michael@0: // Note: If the application was launched from within the Cygwin michael@0: // environment, returning EXCEPTION_EXECUTE_HANDLER seems to cause the michael@0: // application to be restarted. michael@0: if (success) { michael@0: action = EXCEPTION_EXECUTE_HANDLER; michael@0: } else { michael@0: // There was an exception, it was a breakpoint or something else ignored michael@0: // above, or it was passed to the handler, which decided not to handle it. michael@0: // This could be because the filter callback didn't want it, because michael@0: // minidump writing failed for some reason, or because the post-minidump michael@0: // callback function indicated failure. Give the previous handler a michael@0: // chance to do something with the exception. If there is no previous michael@0: // handler, return EXCEPTION_CONTINUE_SEARCH, which will allow a debugger michael@0: // or native "crashed" dialog to handle the exception. michael@0: if (current_handler->previous_filter_) { michael@0: action = current_handler->previous_filter_(exinfo); michael@0: } else { michael@0: action = EXCEPTION_CONTINUE_SEARCH; michael@0: } michael@0: } michael@0: michael@0: return action; michael@0: } michael@0: michael@0: #if _MSC_VER >= 1400 // MSVC 2005/8 michael@0: // static michael@0: void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression, michael@0: const wchar_t* function, michael@0: const wchar_t* file, michael@0: unsigned int line, michael@0: uintptr_t reserved) { michael@0: // This is an invalid parameter, not an exception. It's safe to play with michael@0: // sprintf here. michael@0: AutoExceptionHandler auto_exception_handler; michael@0: ExceptionHandler* current_handler = auto_exception_handler.get_handler(); michael@0: michael@0: MDRawAssertionInfo assertion; michael@0: memset(&assertion, 0, sizeof(assertion)); michael@0: _snwprintf_s(reinterpret_cast(assertion.expression), michael@0: sizeof(assertion.expression) / sizeof(assertion.expression[0]), michael@0: _TRUNCATE, L"%s", expression); michael@0: _snwprintf_s(reinterpret_cast(assertion.function), michael@0: sizeof(assertion.function) / sizeof(assertion.function[0]), michael@0: _TRUNCATE, L"%s", function); michael@0: _snwprintf_s(reinterpret_cast(assertion.file), michael@0: sizeof(assertion.file) / sizeof(assertion.file[0]), michael@0: _TRUNCATE, L"%s", file); michael@0: assertion.line = line; michael@0: assertion.type = MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER; michael@0: michael@0: // Make up an exception record for the current thread and CPU context michael@0: // to make it possible for the crash processor to classify these michael@0: // as do regular crashes, and to make it humane for developers to michael@0: // analyze them. michael@0: EXCEPTION_RECORD exception_record = {}; michael@0: CONTEXT exception_context = {}; michael@0: EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; michael@0: michael@0: ::RtlCaptureContext(&exception_context); michael@0: michael@0: exception_record.ExceptionCode = STATUS_INVALID_PARAMETER; michael@0: michael@0: // We store pointers to the the expression and function strings, michael@0: // and the line as exception parameters to make them easy to michael@0: // access by the developer on the far side. michael@0: exception_record.NumberParameters = 3; michael@0: exception_record.ExceptionInformation[0] = michael@0: reinterpret_cast(&assertion.expression); michael@0: exception_record.ExceptionInformation[1] = michael@0: reinterpret_cast(&assertion.file); michael@0: exception_record.ExceptionInformation[2] = assertion.line; michael@0: michael@0: bool success = false; michael@0: // In case of out-of-process dump generation, directly call michael@0: // WriteMinidumpWithException since there is no separate thread running. michael@0: if (current_handler->IsOutOfProcess()) { michael@0: success = current_handler->WriteMinidumpWithException( michael@0: GetCurrentThreadId(), michael@0: &exception_ptrs, michael@0: &assertion); michael@0: } else { michael@0: success = current_handler->WriteMinidumpOnHandlerThread(&exception_ptrs, michael@0: &assertion); michael@0: } michael@0: michael@0: if (!success) { michael@0: if (current_handler->previous_iph_) { michael@0: // The handler didn't fully handle the exception. Give it to the michael@0: // previous invalid parameter handler. michael@0: current_handler->previous_iph_(expression, michael@0: function, michael@0: file, michael@0: line, michael@0: reserved); michael@0: } else { michael@0: // If there's no previous handler, pass the exception back in to the michael@0: // invalid parameter handler's core. That's the routine that called this michael@0: // function, but now, since this function is no longer registered (and in michael@0: // fact, no function at all is registered), this will result in the michael@0: // default code path being taken: _CRT_DEBUGGER_HOOK and _invoke_watson. michael@0: // Use _invalid_parameter where it exists (in _DEBUG builds) as it passes michael@0: // more information through. In non-debug builds, it is not available, michael@0: // so fall back to using _invalid_parameter_noinfo. See invarg.c in the michael@0: // CRT source. michael@0: #ifdef _DEBUG michael@0: _invalid_parameter(expression, function, file, line, reserved); michael@0: #else // _DEBUG michael@0: _invalid_parameter_noinfo(); michael@0: #endif // _DEBUG michael@0: } michael@0: } michael@0: michael@0: // The handler either took care of the invalid parameter problem itself, michael@0: // or passed it on to another handler. "Swallow" it by exiting, paralleling michael@0: // the behavior of "swallowing" exceptions. michael@0: exit(0); michael@0: } michael@0: #endif // _MSC_VER >= 1400 michael@0: michael@0: // static michael@0: void ExceptionHandler::HandlePureVirtualCall() { michael@0: // This is an pure virtual function call, not an exception. It's safe to michael@0: // play with sprintf here. michael@0: AutoExceptionHandler auto_exception_handler; michael@0: ExceptionHandler* current_handler = auto_exception_handler.get_handler(); michael@0: michael@0: MDRawAssertionInfo assertion; michael@0: memset(&assertion, 0, sizeof(assertion)); michael@0: assertion.type = MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL; michael@0: michael@0: // Make up an exception record for the current thread and CPU context michael@0: // to make it possible for the crash processor to classify these michael@0: // as do regular crashes, and to make it humane for developers to michael@0: // analyze them. michael@0: EXCEPTION_RECORD exception_record = {}; michael@0: CONTEXT exception_context = {}; michael@0: EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; michael@0: michael@0: ::RtlCaptureContext(&exception_context); michael@0: michael@0: exception_record.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; michael@0: michael@0: // We store pointers to the the expression and function strings, michael@0: // and the line as exception parameters to make them easy to michael@0: // access by the developer on the far side. michael@0: exception_record.NumberParameters = 3; michael@0: exception_record.ExceptionInformation[0] = michael@0: reinterpret_cast(&assertion.expression); michael@0: exception_record.ExceptionInformation[1] = michael@0: reinterpret_cast(&assertion.file); michael@0: exception_record.ExceptionInformation[2] = assertion.line; michael@0: michael@0: bool success = false; michael@0: // In case of out-of-process dump generation, directly call michael@0: // WriteMinidumpWithException since there is no separate thread running. michael@0: michael@0: if (current_handler->IsOutOfProcess()) { michael@0: success = current_handler->WriteMinidumpWithException( michael@0: GetCurrentThreadId(), michael@0: &exception_ptrs, michael@0: &assertion); michael@0: } else { michael@0: success = current_handler->WriteMinidumpOnHandlerThread(&exception_ptrs, michael@0: &assertion); michael@0: } michael@0: michael@0: if (!success) { michael@0: if (current_handler->previous_pch_) { michael@0: // The handler didn't fully handle the exception. Give it to the michael@0: // previous purecall handler. michael@0: current_handler->previous_pch_(); michael@0: } else { michael@0: // If there's no previous handler, return and let _purecall handle it. michael@0: // This will just put up an assertion dialog. michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // The handler either took care of the invalid parameter problem itself, michael@0: // or passed it on to another handler. "Swallow" it by exiting, paralleling michael@0: // the behavior of "swallowing" exceptions. michael@0: exit(0); michael@0: } michael@0: michael@0: bool ExceptionHandler::WriteMinidumpOnHandlerThread( michael@0: EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion) { michael@0: EnterCriticalSection(&handler_critical_section_); michael@0: michael@0: // There isn't much we can do if the handler thread michael@0: // was not successfully created. michael@0: if (handler_thread_ == NULL) { michael@0: LeaveCriticalSection(&handler_critical_section_); michael@0: return false; michael@0: } michael@0: michael@0: // The handler thread should only be created when the semaphores are valid. michael@0: assert(handler_start_semaphore_ != NULL); michael@0: assert(handler_finish_semaphore_ != NULL); michael@0: michael@0: // Set up data to be passed in to the handler thread. michael@0: requesting_thread_id_ = GetCurrentThreadId(); michael@0: exception_info_ = exinfo; michael@0: assertion_ = assertion; michael@0: michael@0: // This causes the handler thread to call WriteMinidumpWithException. michael@0: ReleaseSemaphore(handler_start_semaphore_, 1, NULL); michael@0: michael@0: // Wait until WriteMinidumpWithException is done and collect its return value. michael@0: WaitForSingleObject(handler_finish_semaphore_, INFINITE); michael@0: bool status = handler_return_value_; michael@0: michael@0: // Clean up. michael@0: requesting_thread_id_ = 0; michael@0: exception_info_ = NULL; michael@0: assertion_ = NULL; michael@0: michael@0: LeaveCriticalSection(&handler_critical_section_); michael@0: michael@0: return status; michael@0: } michael@0: michael@0: bool ExceptionHandler::WriteMinidump() { michael@0: // Make up an exception record for the current thread and CPU context michael@0: // to make it possible for the crash processor to classify these michael@0: // as do regular crashes, and to make it humane for developers to michael@0: // analyze them. michael@0: EXCEPTION_RECORD exception_record = {}; michael@0: CONTEXT exception_context = {}; michael@0: EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; michael@0: michael@0: ::RtlCaptureContext(&exception_context); michael@0: exception_record.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; michael@0: michael@0: return WriteMinidumpForException(&exception_ptrs); michael@0: } michael@0: michael@0: bool ExceptionHandler::WriteMinidumpForException(EXCEPTION_POINTERS* exinfo) { michael@0: // In case of out-of-process dump generation, directly call michael@0: // WriteMinidumpWithException since there is no separate thread running. michael@0: if (IsOutOfProcess()) { michael@0: return WriteMinidumpWithException(GetCurrentThreadId(), michael@0: exinfo, michael@0: NULL); michael@0: } michael@0: michael@0: bool success = WriteMinidumpOnHandlerThread(exinfo, NULL); michael@0: UpdateNextID(); michael@0: return success; michael@0: } michael@0: michael@0: // static michael@0: bool ExceptionHandler::WriteMinidump(const wstring &dump_path, michael@0: MinidumpCallback callback, michael@0: void* callback_context) { michael@0: ExceptionHandler handler(dump_path, NULL, callback, callback_context, michael@0: HANDLER_NONE); michael@0: return handler.WriteMinidump(); michael@0: } michael@0: michael@0: // static michael@0: bool ExceptionHandler::WriteMinidumpForChild(HANDLE child, michael@0: DWORD child_blamed_thread, michael@0: const wstring& dump_path, michael@0: MinidumpCallback callback, michael@0: void* callback_context) { michael@0: EXCEPTION_RECORD ex; michael@0: CONTEXT ctx; michael@0: EXCEPTION_POINTERS exinfo = { NULL, NULL }; michael@0: DWORD last_suspend_count = kFailedToSuspendThread; michael@0: HANDLE child_thread_handle = OpenThread(THREAD_GET_CONTEXT | michael@0: THREAD_QUERY_INFORMATION | michael@0: THREAD_SUSPEND_RESUME, michael@0: FALSE, michael@0: child_blamed_thread); michael@0: // This thread may have died already, so not opening the handle is a michael@0: // non-fatal error. michael@0: if (child_thread_handle != NULL) { michael@0: last_suspend_count = SuspendThread(child_thread_handle); michael@0: if (last_suspend_count != kFailedToSuspendThread) { michael@0: ctx.ContextFlags = CONTEXT_ALL; michael@0: if (GetThreadContext(child_thread_handle, &ctx)) { michael@0: memset(&ex, 0, sizeof(ex)); michael@0: ex.ExceptionCode = EXCEPTION_BREAKPOINT; michael@0: #if defined(_M_IX86) michael@0: ex.ExceptionAddress = reinterpret_cast(ctx.Eip); michael@0: #elif defined(_M_X64) michael@0: ex.ExceptionAddress = reinterpret_cast(ctx.Rip); michael@0: #endif michael@0: exinfo.ExceptionRecord = &ex; michael@0: exinfo.ContextRecord = &ctx; michael@0: } michael@0: } michael@0: } michael@0: michael@0: ExceptionHandler handler(dump_path, NULL, callback, callback_context, michael@0: HANDLER_NONE); michael@0: bool success = handler.WriteMinidumpWithExceptionForProcess( michael@0: child_blamed_thread, michael@0: exinfo.ExceptionRecord ? &exinfo : NULL, michael@0: NULL, child, false); michael@0: michael@0: if (last_suspend_count != kFailedToSuspendThread) { michael@0: ResumeThread(child_thread_handle); michael@0: } michael@0: michael@0: CloseHandle(child_thread_handle); michael@0: michael@0: if (callback) { michael@0: success = callback(handler.dump_path_c_, handler.next_minidump_id_c_, michael@0: callback_context, NULL, NULL, success); michael@0: } michael@0: michael@0: return success; michael@0: } michael@0: michael@0: bool ExceptionHandler::WriteMinidumpWithException( michael@0: DWORD requesting_thread_id, michael@0: EXCEPTION_POINTERS* exinfo, michael@0: MDRawAssertionInfo* assertion) { michael@0: // Give user code a chance to approve or prevent writing a minidump. If the michael@0: // filter returns false, don't handle the exception at all. If this method michael@0: // was called as a result of an exception, returning false will cause michael@0: // HandleException to call any previous handler or return michael@0: // EXCEPTION_CONTINUE_SEARCH on the exception thread, allowing it to appear michael@0: // as though this handler were not present at all. michael@0: if (filter_ && !filter_(callback_context_, exinfo, assertion)) { michael@0: return false; michael@0: } michael@0: michael@0: bool success = false; michael@0: if (IsOutOfProcess()) { michael@0: success = crash_generation_client_->RequestDump(exinfo, assertion); michael@0: } else { michael@0: success = WriteMinidumpWithExceptionForProcess(requesting_thread_id, michael@0: exinfo, michael@0: assertion, michael@0: GetCurrentProcess(), michael@0: true); michael@0: } michael@0: michael@0: if (callback_) { michael@0: // TODO(munjal): In case of out-of-process dump generation, both michael@0: // dump_path_c_ and next_minidump_id_ will be NULL. For out-of-process michael@0: // scenario, the server process ends up creating the dump path and dump michael@0: // id so they are not known to the client. michael@0: success = callback_(dump_path_c_, next_minidump_id_c_, callback_context_, michael@0: exinfo, assertion, success); michael@0: } michael@0: michael@0: return success; michael@0: } michael@0: michael@0: // static michael@0: BOOL CALLBACK ExceptionHandler::MinidumpWriteDumpCallback( michael@0: PVOID context, michael@0: const PMINIDUMP_CALLBACK_INPUT callback_input, michael@0: PMINIDUMP_CALLBACK_OUTPUT callback_output) { michael@0: switch (callback_input->CallbackType) { michael@0: case MemoryCallback: { michael@0: MinidumpCallbackContext* callback_context = michael@0: reinterpret_cast(context); michael@0: if (callback_context->iter == callback_context->end) michael@0: return FALSE; michael@0: michael@0: // Include the specified memory region. michael@0: callback_output->MemoryBase = callback_context->iter->ptr; michael@0: callback_output->MemorySize = callback_context->iter->length; michael@0: callback_context->iter++; michael@0: return TRUE; michael@0: } michael@0: michael@0: // Include all modules. michael@0: case IncludeModuleCallback: michael@0: case ModuleCallback: michael@0: return TRUE; michael@0: michael@0: // Include all threads. michael@0: case IncludeThreadCallback: michael@0: case ThreadCallback: michael@0: return TRUE; michael@0: michael@0: // Stop receiving cancel callbacks. michael@0: case CancelCallback: michael@0: callback_output->CheckCancel = FALSE; michael@0: callback_output->Cancel = FALSE; michael@0: return TRUE; michael@0: } michael@0: // Ignore other callback types. michael@0: return FALSE; michael@0: } michael@0: michael@0: bool ExceptionHandler::WriteMinidumpWithExceptionForProcess( michael@0: DWORD requesting_thread_id, michael@0: EXCEPTION_POINTERS* exinfo, michael@0: MDRawAssertionInfo* assertion, michael@0: HANDLE process, michael@0: bool write_requester_stream) { michael@0: bool success = false; michael@0: if (minidump_write_dump_) { michael@0: HANDLE dump_file = CreateFile(next_minidump_path_c_, michael@0: GENERIC_WRITE, michael@0: 0, // no sharing michael@0: NULL, michael@0: CREATE_NEW, // fail if exists michael@0: FILE_ATTRIBUTE_NORMAL, michael@0: NULL); michael@0: if (dump_file != INVALID_HANDLE_VALUE) { michael@0: MINIDUMP_EXCEPTION_INFORMATION except_info; michael@0: except_info.ThreadId = requesting_thread_id; michael@0: except_info.ExceptionPointers = exinfo; michael@0: except_info.ClientPointers = FALSE; michael@0: michael@0: // Leave room in user_stream_array for possible breakpad and michael@0: // assertion info streams. michael@0: MINIDUMP_USER_STREAM user_stream_array[2]; michael@0: MINIDUMP_USER_STREAM_INFORMATION user_streams; michael@0: user_streams.UserStreamCount = 0; michael@0: user_streams.UserStreamArray = user_stream_array; michael@0: michael@0: if (write_requester_stream) { michael@0: // Add an MDRawBreakpadInfo stream to the minidump, to provide michael@0: // additional information about the exception handler to the Breakpad michael@0: // processor. The information will help the processor determine which michael@0: // threads are relevant. The Breakpad processor does not require this michael@0: // information but can function better with Breakpad-generated dumps michael@0: // when it is present. The native debugger is not harmed by the michael@0: // presence of this information. michael@0: MDRawBreakpadInfo breakpad_info; michael@0: breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | michael@0: MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; michael@0: breakpad_info.dump_thread_id = GetCurrentThreadId(); michael@0: breakpad_info.requesting_thread_id = requesting_thread_id; michael@0: michael@0: int index = user_streams.UserStreamCount; michael@0: user_stream_array[index].Type = MD_BREAKPAD_INFO_STREAM; michael@0: user_stream_array[index].BufferSize = sizeof(breakpad_info); michael@0: user_stream_array[index].Buffer = &breakpad_info; michael@0: ++user_streams.UserStreamCount; michael@0: } michael@0: michael@0: if (assertion) { michael@0: int index = user_streams.UserStreamCount; michael@0: user_stream_array[index].Type = MD_ASSERTION_INFO_STREAM; michael@0: user_stream_array[index].BufferSize = sizeof(MDRawAssertionInfo); michael@0: user_stream_array[index].Buffer = assertion; michael@0: ++user_streams.UserStreamCount; michael@0: } michael@0: michael@0: // Older versions of DbgHelp.dll don't correctly put the memory around michael@0: // the faulting instruction pointer into the minidump. This michael@0: // callback will ensure that it gets included. michael@0: if (exinfo) { michael@0: // Find a memory region of 256 bytes centered on the michael@0: // faulting instruction pointer. michael@0: const ULONG64 instruction_pointer = michael@0: #if defined(_M_IX86) michael@0: exinfo->ContextRecord->Eip; michael@0: #elif defined(_M_AMD64) michael@0: exinfo->ContextRecord->Rip; michael@0: #else michael@0: #error Unsupported platform michael@0: #endif michael@0: michael@0: MEMORY_BASIC_INFORMATION info; michael@0: if (VirtualQueryEx(process, michael@0: reinterpret_cast(instruction_pointer), michael@0: &info, michael@0: sizeof(MEMORY_BASIC_INFORMATION)) != 0 && michael@0: info.State == MEM_COMMIT) { michael@0: // Attempt to get 128 bytes before and after the instruction michael@0: // pointer, but settle for whatever's available up to the michael@0: // boundaries of the memory region. michael@0: const ULONG64 kIPMemorySize = 256; michael@0: ULONG64 base = michael@0: (std::max)(reinterpret_cast(info.BaseAddress), michael@0: instruction_pointer - (kIPMemorySize / 2)); michael@0: ULONG64 end_of_range = michael@0: (std::min)(instruction_pointer + (kIPMemorySize / 2), michael@0: reinterpret_cast(info.BaseAddress) michael@0: + info.RegionSize); michael@0: ULONG size = static_cast(end_of_range - base); michael@0: michael@0: AppMemory& elt = app_memory_info_.front(); michael@0: elt.ptr = base; michael@0: elt.length = size; michael@0: } michael@0: } michael@0: michael@0: MinidumpCallbackContext context; michael@0: context.iter = app_memory_info_.begin(); michael@0: context.end = app_memory_info_.end(); michael@0: michael@0: // Skip the reserved element if there was no instruction memory michael@0: if (context.iter->ptr == 0) { michael@0: context.iter++; michael@0: } michael@0: michael@0: MINIDUMP_CALLBACK_INFORMATION callback; michael@0: callback.CallbackRoutine = MinidumpWriteDumpCallback; michael@0: callback.CallbackParam = reinterpret_cast(&context); michael@0: michael@0: // The explicit comparison to TRUE avoids a warning (C4800). michael@0: success = (minidump_write_dump_(process, michael@0: GetProcessId(process), michael@0: dump_file, michael@0: dump_type_, michael@0: exinfo ? &except_info : NULL, michael@0: &user_streams, michael@0: &callback) == TRUE); michael@0: michael@0: CloseHandle(dump_file); michael@0: } michael@0: } michael@0: michael@0: return success; michael@0: } michael@0: michael@0: void ExceptionHandler::UpdateNextID() { michael@0: assert(uuid_create_); michael@0: UUID id = {0}; michael@0: if (uuid_create_) { michael@0: uuid_create_(&id); michael@0: } michael@0: next_minidump_id_ = GUIDString::GUIDToWString(&id); michael@0: next_minidump_id_c_ = next_minidump_id_.c_str(); michael@0: michael@0: wchar_t minidump_path[MAX_PATH]; michael@0: swprintf(minidump_path, MAX_PATH, L"%s\\%s.dmp", michael@0: dump_path_c_, next_minidump_id_c_); michael@0: michael@0: // remove when VC++7.1 is no longer supported michael@0: minidump_path[MAX_PATH - 1] = L'\0'; michael@0: michael@0: next_minidump_path_ = minidump_path; michael@0: next_minidump_path_c_ = next_minidump_path_.c_str(); michael@0: } michael@0: michael@0: void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) { michael@0: AppMemoryList::iterator iter = michael@0: std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr); michael@0: if (iter != app_memory_info_.end()) { michael@0: // Don't allow registering the same pointer twice. michael@0: return; michael@0: } michael@0: michael@0: AppMemory app_memory; michael@0: app_memory.ptr = reinterpret_cast(ptr); michael@0: app_memory.length = static_cast(length); michael@0: app_memory_info_.push_back(app_memory); michael@0: } michael@0: michael@0: void ExceptionHandler::UnregisterAppMemory(void* ptr) { michael@0: AppMemoryList::iterator iter = michael@0: std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr); michael@0: if (iter != app_memory_info_.end()) { michael@0: app_memory_info_.erase(iter); michael@0: } michael@0: } michael@0: michael@0: } // namespace google_breakpad