| // Copyright (c) 2008, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "client/windows/crash_generation/minidump_generator.h" |
| |
| #include <assert.h> |
| #include <avrfsdk.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <list> |
| #include <vector> |
| |
| #include "client/windows/common/auto_critical_section.h" |
| #include "common/scoped_ptr.h" |
| #include "common/windows/guid_string.h" |
| |
| using std::wstring; |
| |
| namespace { |
| |
| // A helper class used to collect handle operations data. Unlike |
| // |MiniDumpWithHandleData| it records the operations for a single handle value |
| // only, making it possible to include this information to a minidump. |
| class HandleTraceData { |
| public: |
| HandleTraceData(); |
| ~HandleTraceData(); |
| |
| // Collects the handle operations data and formats a user stream to be added |
| // to the minidump. |
| bool CollectHandleData(HANDLE process_handle, |
| EXCEPTION_POINTERS* exception_pointers); |
| |
| // Fills the user dump entry with a pointer to the collected handle operations |
| // data. Returns |true| if the entry was initialized successfully, or |false| |
| // if no trace data is available. |
| bool GetUserStream(MINIDUMP_USER_STREAM* user_stream); |
| |
| private: |
| // Reads the exception code from the client process's address space. |
| // This routine assumes that the client process's pointer width matches ours. |
| static bool ReadExceptionCode(HANDLE process_handle, |
| EXCEPTION_POINTERS* exception_pointers, |
| DWORD* exception_code); |
| |
| // Stores handle operations retrieved by VerifierEnumerateResource(). |
| static ULONG CALLBACK RecordHandleOperations(void* resource_description, |
| void* enumeration_context, |
| ULONG* enumeration_level); |
| |
| // Function pointer type for VerifierEnumerateResource, which is looked up |
| // dynamically. |
| typedef BOOL (WINAPI* VerifierEnumerateResourceType)( |
| HANDLE Process, |
| ULONG Flags, |
| ULONG ResourceType, |
| AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback, |
| PVOID EnumerationContext); |
| |
| // Handle to dynamically loaded verifier.dll. |
| HMODULE verifier_module_; |
| |
| // Pointer to the VerifierEnumerateResource function. |
| VerifierEnumerateResourceType enumerate_resource_; |
| |
| // Handle value to look for. |
| ULONG64 handle_; |
| |
| // List of handle operations for |handle_|. |
| std::list<AVRF_HANDLE_OPERATION> operations_; |
| |
| // Minidump stream data. |
| std::vector<char> stream_; |
| }; |
| |
| HandleTraceData::HandleTraceData() |
| : verifier_module_(NULL), |
| enumerate_resource_(NULL), |
| handle_(NULL) { |
| } |
| |
| HandleTraceData::~HandleTraceData() { |
| if (verifier_module_) { |
| FreeLibrary(verifier_module_); |
| } |
| } |
| |
| bool HandleTraceData::CollectHandleData( |
| HANDLE process_handle, |
| EXCEPTION_POINTERS* exception_pointers) { |
| DWORD exception_code; |
| if (!ReadExceptionCode(process_handle, exception_pointers, &exception_code)) { |
| return false; |
| } |
| |
| // Verify whether the execption is STATUS_INVALID_HANDLE. Do not record any |
| // handle information if it is a different exception to keep the minidump |
| // small. |
| if (exception_code != STATUS_INVALID_HANDLE) { |
| return true; |
| } |
| |
| // Load verifier!VerifierEnumerateResource() dynamically. |
| verifier_module_ = LoadLibrary(TEXT("verifier.dll")); |
| if (!verifier_module_) { |
| return false; |
| } |
| |
| enumerate_resource_ = reinterpret_cast<VerifierEnumerateResourceType>( |
| GetProcAddress(verifier_module_, "VerifierEnumerateResource")); |
| if (!enumerate_resource_) { |
| return false; |
| } |
| |
| // STATUS_INVALID_HANDLE does not provide the offending handle value in |
| // the exception parameters so we have to guess. At the moment we scan |
| // the handle operations trace looking for the last invalid handle operation |
| // and record only the operations for that handle value. |
| if (enumerate_resource_(process_handle, |
| 0, |
| AvrfResourceHandleTrace, |
| &RecordHandleOperations, |
| this) != ERROR_SUCCESS) { |
| // The handle tracing must have not been enabled. |
| return true; |
| } |
| |
| // Now that |handle_| is initialized, purge all irrelevant operations. |
| std::list<AVRF_HANDLE_OPERATION>::iterator i = operations_.begin(); |
| std::list<AVRF_HANDLE_OPERATION>::iterator i_end = operations_.end(); |
| while (i != i_end) { |
| if (i->Handle == handle_) { |
| ++i; |
| } else { |
| i = operations_.erase(i); |
| } |
| } |
| |
| // Convert the list of recorded operations to a minidump stream. |
| stream_.resize(sizeof(MINIDUMP_HANDLE_OPERATION_LIST) + |
| sizeof(AVRF_HANDLE_OPERATION) * operations_.size()); |
| |
| MINIDUMP_HANDLE_OPERATION_LIST* stream_data = |
| reinterpret_cast<MINIDUMP_HANDLE_OPERATION_LIST*>( |
| &stream_.front()); |
| stream_data->SizeOfHeader = sizeof(MINIDUMP_HANDLE_OPERATION_LIST); |
| stream_data->SizeOfEntry = sizeof(AVRF_HANDLE_OPERATION); |
| stream_data->NumberOfEntries = static_cast<ULONG32>(operations_.size()); |
| stream_data->Reserved = 0; |
| std::copy(operations_.begin(), |
| operations_.end(), |
| #ifdef _MSC_VER |
| stdext::checked_array_iterator<AVRF_HANDLE_OPERATION*>( |
| reinterpret_cast<AVRF_HANDLE_OPERATION*>(stream_data + 1), |
| operations_.size()) |
| #else |
| reinterpret_cast<AVRF_HANDLE_OPERATION*>(stream_data + 1) |
| #endif |
| ); |
| |
| return true; |
| } |
| |
| bool HandleTraceData::GetUserStream(MINIDUMP_USER_STREAM* user_stream) { |
| if (stream_.empty()) { |
| return false; |
| } else { |
| user_stream->Type = HandleOperationListStream; |
| user_stream->BufferSize = static_cast<ULONG>(stream_.size()); |
| user_stream->Buffer = &stream_.front(); |
| return true; |
| } |
| } |
| |
| bool HandleTraceData::ReadExceptionCode( |
| HANDLE process_handle, |
| EXCEPTION_POINTERS* exception_pointers, |
| DWORD* exception_code) { |
| EXCEPTION_POINTERS pointers; |
| if (!ReadProcessMemory(process_handle, |
| exception_pointers, |
| &pointers, |
| sizeof(pointers), |
| NULL)) { |
| return false; |
| } |
| |
| if (!ReadProcessMemory(process_handle, |
| pointers.ExceptionRecord, |
| exception_code, |
| sizeof(*exception_code), |
| NULL)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| ULONG CALLBACK HandleTraceData::RecordHandleOperations( |
| void* resource_description, |
| void* enumeration_context, |
| ULONG* enumeration_level) { |
| AVRF_HANDLE_OPERATION* description = |
| reinterpret_cast<AVRF_HANDLE_OPERATION*>(resource_description); |
| HandleTraceData* self = |
| reinterpret_cast<HandleTraceData*>(enumeration_context); |
| |
| // Remember the last invalid handle operation. |
| if (description->OperationType == OperationDbBADREF) { |
| self->handle_ = description->Handle; |
| } |
| |
| // Record all handle operations. |
| self->operations_.push_back(*description); |
| |
| *enumeration_level = HeapEnumerationEverything; |
| return ERROR_SUCCESS; |
| } |
| |
| } // namespace |
| |
| namespace google_breakpad { |
| |
| MinidumpGenerator::MinidumpGenerator( |
| const std::wstring& dump_path, |
| const HANDLE process_handle, |
| const DWORD process_id, |
| const DWORD thread_id, |
| const DWORD requesting_thread_id, |
| EXCEPTION_POINTERS* exception_pointers, |
| MDRawAssertionInfo* assert_info, |
| const MINIDUMP_TYPE dump_type, |
| const bool is_client_pointers) |
| : dbghelp_module_(NULL), |
| rpcrt4_module_(NULL), |
| dump_path_(dump_path), |
| process_handle_(process_handle), |
| process_id_(process_id), |
| thread_id_(thread_id), |
| requesting_thread_id_(requesting_thread_id), |
| exception_pointers_(exception_pointers), |
| assert_info_(assert_info), |
| dump_type_(dump_type), |
| is_client_pointers_(is_client_pointers), |
| dump_file_(INVALID_HANDLE_VALUE), |
| full_dump_file_(INVALID_HANDLE_VALUE), |
| dump_file_is_internal_(false), |
| full_dump_file_is_internal_(false), |
| additional_streams_(NULL), |
| callback_info_(NULL), |
| write_dump_(NULL), |
| create_uuid_(NULL) { |
| InitializeCriticalSection(&module_load_sync_); |
| InitializeCriticalSection(&get_proc_address_sync_); |
| } |
| |
| MinidumpGenerator::~MinidumpGenerator() { |
| if (dump_file_is_internal_ && dump_file_ != INVALID_HANDLE_VALUE) { |
| CloseHandle(dump_file_); |
| } |
| |
| if (full_dump_file_is_internal_ && full_dump_file_ != INVALID_HANDLE_VALUE) { |
| CloseHandle(full_dump_file_); |
| } |
| |
| if (dbghelp_module_) { |
| FreeLibrary(dbghelp_module_); |
| } |
| |
| if (rpcrt4_module_) { |
| FreeLibrary(rpcrt4_module_); |
| } |
| |
| DeleteCriticalSection(&get_proc_address_sync_); |
| DeleteCriticalSection(&module_load_sync_); |
| } |
| |
| bool MinidumpGenerator::WriteMinidump() { |
| bool full_memory_dump = (dump_type_ & MiniDumpWithFullMemory) != 0; |
| if (dump_file_ == INVALID_HANDLE_VALUE || |
| (full_memory_dump && full_dump_file_ == INVALID_HANDLE_VALUE)) { |
| return false; |
| } |
| |
| MiniDumpWriteDumpType write_dump = GetWriteDump(); |
| if (!write_dump) { |
| return false; |
| } |
| |
| MINIDUMP_EXCEPTION_INFORMATION* dump_exception_pointers = NULL; |
| MINIDUMP_EXCEPTION_INFORMATION dump_exception_info; |
| |
| // Setup the exception information object only if it's a dump |
| // due to an exception. |
| if (exception_pointers_) { |
| dump_exception_pointers = &dump_exception_info; |
| dump_exception_info.ThreadId = thread_id_; |
| dump_exception_info.ExceptionPointers = exception_pointers_; |
| dump_exception_info.ClientPointers = is_client_pointers_; |
| } |
| |
| // Add an MDRawBreakpadInfo stream to the minidump, to provide additional |
| // information about the exception handler to the Breakpad processor. |
| // The information will help the processor determine which threads are |
| // relevant. The Breakpad processor does not require this information but |
| // can function better with Breakpad-generated dumps when it is present. |
| // The native debugger is not harmed by the presence of this information. |
| MDRawBreakpadInfo breakpad_info = {0}; |
| if (!is_client_pointers_) { |
| // Set the dump thread id and requesting thread id only in case of |
| // in-process dump generation. |
| breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | |
| MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; |
| breakpad_info.dump_thread_id = thread_id_; |
| breakpad_info.requesting_thread_id = requesting_thread_id_; |
| } |
| |
| int additional_streams_count = additional_streams_ ? |
| additional_streams_->UserStreamCount : 0; |
| scoped_array<MINIDUMP_USER_STREAM> user_stream_array( |
| new MINIDUMP_USER_STREAM[3 + additional_streams_count]); |
| user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM; |
| user_stream_array[0].BufferSize = sizeof(breakpad_info); |
| user_stream_array[0].Buffer = &breakpad_info; |
| |
| MINIDUMP_USER_STREAM_INFORMATION user_streams; |
| user_streams.UserStreamCount = 1; |
| user_streams.UserStreamArray = user_stream_array.get(); |
| |
| MDRawAssertionInfo* actual_assert_info = assert_info_; |
| MDRawAssertionInfo client_assert_info = {{0}}; |
| |
| if (assert_info_) { |
| // If the assertion info object lives in the client process, |
| // read the memory of the client process. |
| if (is_client_pointers_) { |
| SIZE_T bytes_read = 0; |
| if (!ReadProcessMemory(process_handle_, |
| assert_info_, |
| &client_assert_info, |
| sizeof(client_assert_info), |
| &bytes_read)) { |
| if (dump_file_is_internal_) |
| CloseHandle(dump_file_); |
| if (full_dump_file_is_internal_ && |
| full_dump_file_ != INVALID_HANDLE_VALUE) |
| CloseHandle(full_dump_file_); |
| return false; |
| } |
| |
| if (bytes_read != sizeof(client_assert_info)) { |
| if (dump_file_is_internal_) |
| CloseHandle(dump_file_); |
| if (full_dump_file_is_internal_ && |
| full_dump_file_ != INVALID_HANDLE_VALUE) |
| CloseHandle(full_dump_file_); |
| return false; |
| } |
| |
| actual_assert_info = &client_assert_info; |
| } |
| |
| user_stream_array[1].Type = MD_ASSERTION_INFO_STREAM; |
| user_stream_array[1].BufferSize = sizeof(MDRawAssertionInfo); |
| user_stream_array[1].Buffer = actual_assert_info; |
| ++user_streams.UserStreamCount; |
| } |
| |
| if (additional_streams_) { |
| for (size_t i = 0; |
| i < additional_streams_->UserStreamCount; |
| i++, user_streams.UserStreamCount++) { |
| user_stream_array[user_streams.UserStreamCount].Type = |
| additional_streams_->UserStreamArray[i].Type; |
| user_stream_array[user_streams.UserStreamCount].BufferSize = |
| additional_streams_->UserStreamArray[i].BufferSize; |
| user_stream_array[user_streams.UserStreamCount].Buffer = |
| additional_streams_->UserStreamArray[i].Buffer; |
| } |
| } |
| |
| // If the process is terminated by STATUS_INVALID_HANDLE exception store |
| // the trace of operations for the offending handle value. Do nothing special |
| // if the client already requested the handle trace to be stored in the dump. |
| HandleTraceData handle_trace_data; |
| if (exception_pointers_ && (dump_type_ & MiniDumpWithHandleData) == 0) { |
| if (!handle_trace_data.CollectHandleData(process_handle_, |
| exception_pointers_)) { |
| if (dump_file_is_internal_) |
| CloseHandle(dump_file_); |
| if (full_dump_file_is_internal_ && |
| full_dump_file_ != INVALID_HANDLE_VALUE) |
| CloseHandle(full_dump_file_); |
| return false; |
| } |
| } |
| |
| bool result_full_memory = true; |
| if (full_memory_dump) { |
| result_full_memory = write_dump( |
| process_handle_, |
| process_id_, |
| full_dump_file_, |
| static_cast<MINIDUMP_TYPE>((dump_type_ & (~MiniDumpNormal)) |
| | MiniDumpWithHandleData), |
| exception_pointers_ ? &dump_exception_info : NULL, |
| &user_streams, |
| NULL) != FALSE; |
| } |
| |
| // Add handle operations trace stream to the minidump if it was collected. |
| if (handle_trace_data.GetUserStream( |
| &user_stream_array[user_streams.UserStreamCount])) { |
| ++user_streams.UserStreamCount; |
| } |
| |
| bool result_minidump = write_dump( |
| process_handle_, |
| process_id_, |
| dump_file_, |
| static_cast<MINIDUMP_TYPE>((dump_type_ & (~MiniDumpWithFullMemory)) |
| | MiniDumpNormal), |
| exception_pointers_ ? &dump_exception_info : NULL, |
| &user_streams, |
| callback_info_) != FALSE; |
| |
| return result_minidump && result_full_memory; |
| } |
| |
| bool MinidumpGenerator::GenerateDumpFile(wstring* dump_path) { |
| // The dump file was already set by handle or this function was previously |
| // called. |
| if (dump_file_ != INVALID_HANDLE_VALUE) { |
| return false; |
| } |
| |
| wstring dump_file_path; |
| if (!GenerateDumpFilePath(&dump_file_path)) { |
| return false; |
| } |
| |
| dump_file_ = CreateFile(dump_file_path.c_str(), |
| GENERIC_WRITE, |
| 0, |
| NULL, |
| CREATE_NEW, |
| FILE_ATTRIBUTE_NORMAL, |
| NULL); |
| if (dump_file_ == INVALID_HANDLE_VALUE) { |
| return false; |
| } |
| |
| dump_file_is_internal_ = true; |
| *dump_path = dump_file_path; |
| return true; |
| } |
| |
| bool MinidumpGenerator::GenerateFullDumpFile(wstring* full_dump_path) { |
| // A full minidump was not requested. |
| if ((dump_type_ & MiniDumpWithFullMemory) == 0) { |
| return false; |
| } |
| |
| // The dump file was already set by handle or this function was previously |
| // called. |
| if (full_dump_file_ != INVALID_HANDLE_VALUE) { |
| return false; |
| } |
| |
| wstring full_dump_file_path; |
| if (!GenerateDumpFilePath(&full_dump_file_path)) { |
| return false; |
| } |
| full_dump_file_path.resize(full_dump_file_path.size() - 4); // strip .dmp |
| full_dump_file_path.append(TEXT("-full.dmp")); |
| |
| full_dump_file_ = CreateFile(full_dump_file_path.c_str(), |
| GENERIC_WRITE, |
| 0, |
| NULL, |
| CREATE_NEW, |
| FILE_ATTRIBUTE_NORMAL, |
| NULL); |
| if (full_dump_file_ == INVALID_HANDLE_VALUE) { |
| return false; |
| } |
| |
| full_dump_file_is_internal_ = true; |
| *full_dump_path = full_dump_file_path; |
| return true; |
| } |
| |
| HMODULE MinidumpGenerator::GetDbghelpModule() { |
| AutoCriticalSection lock(&module_load_sync_); |
| if (!dbghelp_module_) { |
| dbghelp_module_ = LoadLibrary(TEXT("dbghelp.dll")); |
| } |
| |
| return dbghelp_module_; |
| } |
| |
| MinidumpGenerator::MiniDumpWriteDumpType MinidumpGenerator::GetWriteDump() { |
| AutoCriticalSection lock(&get_proc_address_sync_); |
| if (!write_dump_) { |
| HMODULE module = GetDbghelpModule(); |
| if (module) { |
| FARPROC proc = GetProcAddress(module, "MiniDumpWriteDump"); |
| write_dump_ = reinterpret_cast<MiniDumpWriteDumpType>(proc); |
| } |
| } |
| |
| return write_dump_; |
| } |
| |
| HMODULE MinidumpGenerator::GetRpcrt4Module() { |
| AutoCriticalSection lock(&module_load_sync_); |
| if (!rpcrt4_module_) { |
| rpcrt4_module_ = LoadLibrary(TEXT("rpcrt4.dll")); |
| } |
| |
| return rpcrt4_module_; |
| } |
| |
| MinidumpGenerator::UuidCreateType MinidumpGenerator::GetCreateUuid() { |
| AutoCriticalSection lock(&module_load_sync_); |
| if (!create_uuid_) { |
| HMODULE module = GetRpcrt4Module(); |
| if (module) { |
| FARPROC proc = GetProcAddress(module, "UuidCreate"); |
| create_uuid_ = reinterpret_cast<UuidCreateType>(proc); |
| } |
| } |
| |
| return create_uuid_; |
| } |
| |
| bool MinidumpGenerator::GenerateDumpFilePath(wstring* file_path) { |
| UUID id = {0}; |
| |
| UuidCreateType create_uuid = GetCreateUuid(); |
| if (!create_uuid) { |
| return false; |
| } |
| |
| create_uuid(&id); |
| wstring id_str = GUIDString::GUIDToWString(&id); |
| |
| *file_path = dump_path_ + TEXT("\\") + id_str + TEXT(".dmp"); |
| return true; |
| } |
| |
| } // namespace google_breakpad |