From a15a2a681196a2ccee6e25ab121678bdf2143cef Mon Sep 17 00:00:00 2001 From: "mark@chromium.org" Date: Tue, 3 Jul 2012 16:44:49 +0000 Subject: [PATCH] Allow the minidump writer to collect handle data so that resulting dump contains information about opened handles (!handle) and handle operations trace (!htrace). Depending on the dump type different amount of data is collected. For a full dump all handles and complete handle trace are included to the dump. In the case of a minidump the list of handle operations for a single handle value (the last handle value that caused STATUS_INVALID_HANDLE exception) is recorded. In either case the handle trace should be explicitly enabled by the client process (or by a debugger) in order to produce any handle trace data. BUG=131699 Patch by Alex Pakhunov Review URL: https://breakpad.appspot.com/410002/ git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@979 4c0a9323-5329-0410-9bdc-e9ce6186880e --- .../crash_generation/minidump_generator.cc | 257 +++++++++++++++++- 1 file changed, 242 insertions(+), 15 deletions(-) diff --git a/src/client/windows/crash_generation/minidump_generator.cc b/src/client/windows/crash_generation/minidump_generator.cc index 37bd55e4..84922940 100644 --- a/src/client/windows/crash_generation/minidump_generator.cc +++ b/src/client/windows/crash_generation/minidump_generator.cc @@ -28,12 +28,217 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "client/windows/crash_generation/minidump_generator.h" -#include + +#include +#include + +#include +#include +#include + #include "client/windows/common/auto_critical_section.h" #include "common/windows/guid_string.h" using std::wstring; +// Disable C4996: 'std::copy': Function call with parameters that may be unsafe. +#pragma warning( disable : 4996 ) + +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 operations_; + + // Minidump stream data. + std::vector 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( + 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) { + return false; + } + + // Now that |handle_| is initialized, purge all irrelevant operations. + std::list::iterator i = operations_.begin(); + std::list::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( + &stream_.front()); + stream_data->SizeOfHeader = sizeof(MINIDUMP_HANDLE_OPERATION_LIST); + stream_data->SizeOfEntry = sizeof(AVRF_HANDLE_OPERATION); + stream_data->NumberOfEntries = static_cast(operations_.size()); + stream_data->Reserved = 0; + std::copy(operations_.begin(), + operations_.end(), + reinterpret_cast(stream_data + 1)); + + 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(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(resource_description); + HandleTraceData* self = + reinterpret_cast(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 wstring& dump_path) @@ -162,8 +367,9 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle, breakpad_info.requesting_thread_id = requesting_thread_id; } - // Leave room in user_stream_array for a possible assertion info stream. - MINIDUMP_USER_STREAM user_stream_array[2]; + // Leave room in user_stream_array for possible assertion info and handle + // operations streams. + MINIDUMP_USER_STREAM user_stream_array[3]; user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM; user_stream_array[0].BufferSize = sizeof(breakpad_info); user_stream_array[0].Buffer = &breakpad_info; @@ -207,6 +413,39 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle, ++user_streams.UserStreamCount; } + // If the process is terminated by STATUS_INVALID_HANDLE exception store + // the trace of operatios 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)) { + CloseHandle(dump_file); + if (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((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, @@ -217,18 +456,6 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle, &user_streams, NULL) != FALSE; - bool result_full_memory = true; - if (full_memory_dump) { - result_full_memory = write_dump( - process_handle, - process_id, - full_dump_file, - static_cast(dump_type & (~MiniDumpNormal)), - exception_pointers ? &dump_exception_info : NULL, - &user_streams, - NULL) != FALSE; - } - bool result = result_minidump && result_full_memory; CloseHandle(dump_file);