| # 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.iteritems()} |
| 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 |