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