// 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