| # Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | |
| # for details. All rights reserved. Use of this source code is governed by a | |
| # BSD-style license that can be found in the LICENSE file. | |
| # This file contains a set of utilities for parsing minidumps. | |
| import ctypes | |
| import mmap | |
| import os | |
| import sys | |
| class Enum(object): | |
| def __init__(self, type, name2value): | |
| self.name2value = name2value | |
| self.value2name = {v: k for k, v in name2value.items()} | |
| self.type = type | |
| def from_raw(self, v): | |
| if v not in self.value2name: | |
| return 'Unknown(' + str(v) + ')' | |
| return self.value2name[v] | |
| def to_raw(self, v): | |
| return self.name2value[v] | |
| class Descriptor(object): | |
| """A handy wrapper over ctypes.Structure""" | |
| def __init__(self, fields): | |
| self.fields = fields | |
| self.ctype = Descriptor._GetCtype(fields) | |
| self.size = ctypes.sizeof(self.ctype) | |
| def Read(self, address): | |
| return self.ctype.from_address(address) | |
| @staticmethod | |
| def _GetCtype(fields): | |
| raw_fields = [] | |
| wrappers = {} | |
| for field in fields: | |
| (name, type) = field | |
| if isinstance(type, Enum): | |
| raw_fields.append(('_raw_' + name, type.type)) | |
| wrappers[name] = type | |
| else: | |
| raw_fields.append(field) | |
| class Raw(ctypes.Structure): | |
| _fields_ = raw_fields | |
| _pack_ = 1 | |
| def __getattribute__(self, name): | |
| if name in wrappers: | |
| return wrappers[name].from_raw( | |
| getattr(self, '_raw_' + name)) | |
| else: | |
| return ctypes.Structure.__getattribute__(self, name) | |
| def __repr__(self): | |
| return '{' + ', '.join( | |
| '%s: %s' % (field, self.__getattribute__(field)) | |
| for field, _ in fields) + '}' | |
| return Raw | |
| # Structures below are based on the information in the MSDN pages and | |
| # Breakpad/Crashpad sources. | |
| MINIDUMP_HEADER = Descriptor([('signature', ctypes.c_uint32), | |
| ('version', ctypes.c_uint32), | |
| ('stream_count', ctypes.c_uint32), | |
| ('stream_directories_rva', ctypes.c_uint32), | |
| ('checksum', ctypes.c_uint32), | |
| ('time_date_stampt', ctypes.c_uint32), | |
| ('flags', ctypes.c_uint64)]) | |
| MINIDUMP_LOCATION_DESCRIPTOR = Descriptor([('data_size', ctypes.c_uint32), | |
| ('rva', ctypes.c_uint32)]) | |
| MINIDUMP_STREAM_TYPE = { | |
| 'MD_UNUSED_STREAM': 0, | |
| 'MD_RESERVED_STREAM_0': 1, | |
| 'MD_RESERVED_STREAM_1': 2, | |
| 'MD_THREAD_LIST_STREAM': 3, | |
| 'MD_MODULE_LIST_STREAM': 4, | |
| 'MD_MEMORY_LIST_STREAM': 5, | |
| 'MD_EXCEPTION_STREAM': 6, | |
| 'MD_SYSTEM_INFO_STREAM': 7, | |
| 'MD_THREAD_EX_LIST_STREAM': 8, | |
| 'MD_MEMORY_64_LIST_STREAM': 9, | |
| 'MD_COMMENT_STREAM_A': 10, | |
| 'MD_COMMENT_STREAM_W': 11, | |
| 'MD_HANDLE_DATA_STREAM': 12, | |
| 'MD_FUNCTION_TABLE_STREAM': 13, | |
| 'MD_UNLOADED_MODULE_LIST_STREAM': 14, | |
| 'MD_MISC_INFO_STREAM': 15, | |
| 'MD_MEMORY_INFO_LIST_STREAM': 16, | |
| 'MD_THREAD_INFO_LIST_STREAM': 17, | |
| 'MD_HANDLE_OPERATION_LIST_STREAM': 18, | |
| } | |
| MINIDUMP_DIRECTORY = Descriptor([('stream_type', | |
| Enum(ctypes.c_uint32, MINIDUMP_STREAM_TYPE)), | |
| ('location', | |
| MINIDUMP_LOCATION_DESCRIPTOR.ctype)]) | |
| MINIDUMP_MISC_INFO_2 = Descriptor([ | |
| ('SizeOfInfo', ctypes.c_uint32), | |
| ('Flags1', ctypes.c_uint32), | |
| ('ProcessId', ctypes.c_uint32), | |
| ('ProcessCreateTime', ctypes.c_uint32), | |
| ('ProcessUserTime', ctypes.c_uint32), | |
| ('ProcessKernelTime', ctypes.c_uint32), | |
| ('ProcessorMaxMhz', ctypes.c_uint32), | |
| ('ProcessorCurrentMhz', ctypes.c_uint32), | |
| ('ProcessorMhzLimit', ctypes.c_uint32), | |
| ('ProcessorMaxIdleState', ctypes.c_uint32), | |
| ('ProcessorCurrentIdleState', ctypes.c_uint32), | |
| ]) | |
| MINIDUMP_MISC1_PROCESS_ID = 0x00000001 | |
| # A helper to get a raw address of the memory mapped buffer returned by | |
| # mmap. | |
| def BufferToAddress(buf): | |
| obj = ctypes.py_object(buf) | |
| address = ctypes.c_void_p() | |
| length = ctypes.c_ssize_t() | |
| ctypes.pythonapi.PyObject_AsReadBuffer(obj, ctypes.byref(address), | |
| ctypes.byref(length)) | |
| return address.value | |
| class MinidumpFile(object): | |
| """Class for reading minidump files.""" | |
| _HEADER_MAGIC = 0x504d444d | |
| def __init__(self, minidump_name): | |
| self.minidump_name = minidump_name | |
| self.minidump_file = open(minidump_name, 'r') | |
| self.minidump = mmap.mmap( | |
| self.minidump_file.fileno(), 0, access=mmap.ACCESS_READ) | |
| self.minidump_address = BufferToAddress(self.minidump) | |
| self.header = self.Read(MINIDUMP_HEADER, 0) | |
| if self.header.signature != MinidumpFile._HEADER_MAGIC: | |
| raise Exception('Unsupported minidump header magic') | |
| self.directories = [] | |
| offset = self.header.stream_directories_rva | |
| for _ in range(self.header.stream_count): | |
| self.directories.append(self.Read(MINIDUMP_DIRECTORY, offset)) | |
| offset += MINIDUMP_DIRECTORY.size | |
| def GetProcessId(self): | |
| for dir in self.directories: | |
| if dir.stream_type == 'MD_MISC_INFO_STREAM': | |
| info = self.Read(MINIDUMP_MISC_INFO_2, dir.location.rva) | |
| if info.Flags1 & MINIDUMP_MISC1_PROCESS_ID != 0: | |
| return info.ProcessId | |
| return -1 | |
| def Read(self, what, offset): | |
| return what.Read(self.minidump_address + offset) | |
| def __enter__(self): | |
| return self | |
| def __exit__(self, type, value, traceback): | |
| self.minidump.close() | |
| self.minidump_file.close() | |
| # Returns process id of the crashed process recorded in the given minidump. | |
| def GetProcessIdFromDump(path): | |
| try: | |
| with MinidumpFile(path) as f: | |
| return int(f.GetProcessId()) | |
| except: | |
| return -1 |