| #!/usr/bin/env python |
| # |
| # Copyright 2012 the V8 project authors. 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. |
| |
| import BaseHTTPServer |
| import bisect |
| import cgi |
| import cmd |
| import codecs |
| import ctypes |
| import datetime |
| import disasm |
| import inspect |
| import mmap |
| import optparse |
| import os |
| import re |
| import StringIO |
| import sys |
| import types |
| import urllib |
| import urlparse |
| import v8heapconst |
| import webbrowser |
| |
| PORT_NUMBER = 8081 |
| |
| |
| USAGE="""usage: %prog [OPTIONS] [DUMP-FILE] |
| |
| Minidump analyzer. |
| |
| Shows the processor state at the point of exception including the |
| stack of the active thread and the referenced objects in the V8 |
| heap. Code objects are disassembled and the addresses linked from the |
| stack (e.g. pushed return addresses) are marked with "=>". |
| |
| Examples: |
| $ %prog 12345678-1234-1234-1234-123456789abcd-full.dmp""" |
| |
| |
| DEBUG=False |
| |
| |
| def DebugPrint(s): |
| if not DEBUG: return |
| print s |
| |
| |
| class Descriptor(object): |
| """Descriptor of a structure in a memory.""" |
| |
| def __init__(self, fields): |
| self.fields = fields |
| self.is_flexible = False |
| for _, type_or_func in fields: |
| if isinstance(type_or_func, types.FunctionType): |
| self.is_flexible = True |
| break |
| if not self.is_flexible: |
| self.ctype = Descriptor._GetCtype(fields) |
| self.size = ctypes.sizeof(self.ctype) |
| |
| def Read(self, memory, offset): |
| if self.is_flexible: |
| fields_copy = self.fields[:] |
| last = 0 |
| for name, type_or_func in fields_copy: |
| if isinstance(type_or_func, types.FunctionType): |
| partial_ctype = Descriptor._GetCtype(fields_copy[:last]) |
| partial_object = partial_ctype.from_buffer(memory, offset) |
| type = type_or_func(partial_object) |
| if type is not None: |
| fields_copy[last] = (name, type) |
| last += 1 |
| else: |
| last += 1 |
| complete_ctype = Descriptor._GetCtype(fields_copy[:last]) |
| else: |
| complete_ctype = self.ctype |
| return complete_ctype.from_buffer(memory, offset) |
| |
| @staticmethod |
| def _GetCtype(fields): |
| class Raw(ctypes.Structure): |
| _fields_ = fields |
| _pack_ = 1 |
| |
| def __str__(self): |
| return "{" + ", ".join("%s: %s" % (field, self.__getattribute__(field)) |
| for field, _ in Raw._fields_) + "}" |
| return Raw |
| |
| |
| def FullDump(reader, heap): |
| """Dump all available memory regions.""" |
| def dump_region(reader, start, size, location): |
| print |
| while start & 3 != 0: |
| start += 1 |
| size -= 1 |
| location += 1 |
| is_executable = reader.IsProbableExecutableRegion(location, size) |
| is_ascii = reader.IsProbableASCIIRegion(location, size) |
| |
| if is_executable is not False: |
| lines = reader.GetDisasmLines(start, size) |
| for line in lines: |
| print FormatDisasmLine(start, heap, line) |
| print |
| |
| if is_ascii is not False: |
| # Output in the same format as the Unix hd command |
| addr = start |
| for i in xrange(0, size, 16): |
| slot = i + location |
| hex_line = "" |
| asc_line = "" |
| for i in xrange(16): |
| if slot + i < location + size: |
| byte = ctypes.c_uint8.from_buffer(reader.minidump, slot + i).value |
| if byte >= 0x20 and byte < 0x7f: |
| asc_line += chr(byte) |
| else: |
| asc_line += "." |
| hex_line += " %02x" % (byte) |
| else: |
| hex_line += " " |
| if i == 7: |
| hex_line += " " |
| print "%s %s |%s|" % (reader.FormatIntPtr(addr), |
| hex_line, |
| asc_line) |
| addr += 16 |
| |
| if is_executable is not True and is_ascii is not True: |
| print "%s - %s" % (reader.FormatIntPtr(start), |
| reader.FormatIntPtr(start + size)) |
| print start + size + 1; |
| for i in xrange(0, size, reader.PointerSize()): |
| slot = start + i |
| maybe_address = reader.ReadUIntPtr(slot) |
| heap_object = heap.FindObject(maybe_address) |
| print "%s: %s" % (reader.FormatIntPtr(slot), |
| reader.FormatIntPtr(maybe_address)) |
| if heap_object: |
| heap_object.Print(Printer()) |
| print |
| |
| reader.ForEachMemoryRegion(dump_region) |
| |
| # Heap constants generated by 'make grokdump' in v8heapconst module. |
| INSTANCE_TYPES = v8heapconst.INSTANCE_TYPES |
| KNOWN_MAPS = v8heapconst.KNOWN_MAPS |
| KNOWN_OBJECTS = v8heapconst.KNOWN_OBJECTS |
| FRAME_MARKERS = v8heapconst.FRAME_MARKERS |
| |
| # Markers pushed on the stack by PushStackTraceAndDie |
| MAGIC_MARKER_PAIRS = ( |
| (0xbbbbbbbb, 0xbbbbbbbb), |
| (0xfefefefe, 0xfefefeff), |
| ) |
| |
| # Set of structures and constants that describe the layout of minidump |
| # files. Based on MSDN and Google Breakpad. |
| |
| 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_STRING = Descriptor([ |
| ("length", ctypes.c_uint32), |
| ("buffer", lambda t: ctypes.c_uint8 * (t.length + 2)) |
| ]) |
| |
| MINIDUMP_DIRECTORY = Descriptor([ |
| ("stream_type", ctypes.c_uint32), |
| ("location", MINIDUMP_LOCATION_DESCRIPTOR.ctype) |
| ]) |
| |
| MD_EXCEPTION_MAXIMUM_PARAMETERS = 15 |
| |
| MINIDUMP_EXCEPTION = Descriptor([ |
| ("code", ctypes.c_uint32), |
| ("flags", ctypes.c_uint32), |
| ("record", ctypes.c_uint64), |
| ("address", ctypes.c_uint64), |
| ("parameter_count", ctypes.c_uint32), |
| ("unused_alignment", ctypes.c_uint32), |
| ("information", ctypes.c_uint64 * MD_EXCEPTION_MAXIMUM_PARAMETERS) |
| ]) |
| |
| MINIDUMP_EXCEPTION_STREAM = Descriptor([ |
| ("thread_id", ctypes.c_uint32), |
| ("unused_alignment", ctypes.c_uint32), |
| ("exception", MINIDUMP_EXCEPTION.ctype), |
| ("thread_context", MINIDUMP_LOCATION_DESCRIPTOR.ctype) |
| ]) |
| |
| # Stream types. |
| 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 |
| |
| MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE = 80 |
| |
| MINIDUMP_FLOATING_SAVE_AREA_X86 = Descriptor([ |
| ("control_word", ctypes.c_uint32), |
| ("status_word", ctypes.c_uint32), |
| ("tag_word", ctypes.c_uint32), |
| ("error_offset", ctypes.c_uint32), |
| ("error_selector", ctypes.c_uint32), |
| ("data_offset", ctypes.c_uint32), |
| ("data_selector", ctypes.c_uint32), |
| ("register_area", ctypes.c_uint8 * MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE), |
| ("cr0_npx_state", ctypes.c_uint32) |
| ]) |
| |
| MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE = 512 |
| |
| # Context flags. |
| MD_CONTEXT_X86 = 0x00010000 |
| MD_CONTEXT_X86_CONTROL = (MD_CONTEXT_X86 | 0x00000001) |
| MD_CONTEXT_X86_INTEGER = (MD_CONTEXT_X86 | 0x00000002) |
| MD_CONTEXT_X86_SEGMENTS = (MD_CONTEXT_X86 | 0x00000004) |
| MD_CONTEXT_X86_FLOATING_POINT = (MD_CONTEXT_X86 | 0x00000008) |
| MD_CONTEXT_X86_DEBUG_REGISTERS = (MD_CONTEXT_X86 | 0x00000010) |
| MD_CONTEXT_X86_EXTENDED_REGISTERS = (MD_CONTEXT_X86 | 0x00000020) |
| |
| def EnableOnFlag(type, flag): |
| return lambda o: [None, type][int((o.context_flags & flag) != 0)] |
| |
| MINIDUMP_CONTEXT_X86 = Descriptor([ |
| ("context_flags", ctypes.c_uint32), |
| # MD_CONTEXT_X86_DEBUG_REGISTERS. |
| ("dr0", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), |
| ("dr1", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), |
| ("dr2", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), |
| ("dr3", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), |
| ("dr6", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), |
| ("dr7", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), |
| # MD_CONTEXT_X86_FLOATING_POINT. |
| ("float_save", EnableOnFlag(MINIDUMP_FLOATING_SAVE_AREA_X86.ctype, |
| MD_CONTEXT_X86_FLOATING_POINT)), |
| # MD_CONTEXT_X86_SEGMENTS. |
| ("gs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)), |
| ("fs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)), |
| ("es", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)), |
| ("ds", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)), |
| # MD_CONTEXT_X86_INTEGER. |
| ("edi", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), |
| ("esi", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), |
| ("ebx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), |
| ("edx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), |
| ("ecx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), |
| ("eax", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), |
| # MD_CONTEXT_X86_CONTROL. |
| ("ebp", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), |
| ("eip", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), |
| ("cs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), |
| ("eflags", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), |
| ("esp", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), |
| ("ss", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), |
| # MD_CONTEXT_X86_EXTENDED_REGISTERS. |
| ("extended_registers", |
| EnableOnFlag(ctypes.c_uint8 * MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE, |
| MD_CONTEXT_X86_EXTENDED_REGISTERS)) |
| ]) |
| |
| MD_CONTEXT_ARM = 0x40000000 |
| MD_CONTEXT_ARM_INTEGER = (MD_CONTEXT_ARM | 0x00000002) |
| MD_CONTEXT_ARM_FLOATING_POINT = (MD_CONTEXT_ARM | 0x00000004) |
| MD_FLOATINGSAVEAREA_ARM_FPR_COUNT = 32 |
| MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT = 8 |
| |
| MINIDUMP_FLOATING_SAVE_AREA_ARM = Descriptor([ |
| ("fpscr", ctypes.c_uint64), |
| ("regs", ctypes.c_uint64 * MD_FLOATINGSAVEAREA_ARM_FPR_COUNT), |
| ("extra", ctypes.c_uint64 * MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT) |
| ]) |
| |
| MINIDUMP_CONTEXT_ARM = Descriptor([ |
| ("context_flags", ctypes.c_uint32), |
| # MD_CONTEXT_ARM_INTEGER. |
| ("r0", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r1", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r2", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r3", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r4", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r5", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r6", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r7", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r8", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r9", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r10", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r11", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("r12", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("sp", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("lr", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("pc", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)), |
| ("cpsr", ctypes.c_uint32), |
| ("float_save", EnableOnFlag(MINIDUMP_FLOATING_SAVE_AREA_ARM.ctype, |
| MD_CONTEXT_ARM_FLOATING_POINT)) |
| ]) |
| |
| |
| MD_CONTEXT_ARM64 = 0x80000000 |
| MD_CONTEXT_ARM64_INTEGER = (MD_CONTEXT_ARM64 | 0x00000002) |
| MD_CONTEXT_ARM64_FLOATING_POINT = (MD_CONTEXT_ARM64 | 0x00000004) |
| MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT = 64 |
| |
| MINIDUMP_FLOATING_SAVE_AREA_ARM = Descriptor([ |
| ("fpscr", ctypes.c_uint64), |
| ("regs", ctypes.c_uint64 * MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT), |
| ]) |
| |
| MINIDUMP_CONTEXT_ARM64 = Descriptor([ |
| ("context_flags", ctypes.c_uint64), |
| # MD_CONTEXT_ARM64_INTEGER. |
| ("r0", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r1", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r2", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r3", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r4", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r5", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r6", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r7", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r8", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r9", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r10", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r11", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r12", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r13", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r14", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r15", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r16", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r17", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r18", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r19", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r20", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r21", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r22", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r23", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r24", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r25", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r26", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r27", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("r28", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("fp", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("lr", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("sp", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("pc", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_ARM64_INTEGER)), |
| ("cpsr", ctypes.c_uint32), |
| ("float_save", EnableOnFlag(MINIDUMP_FLOATING_SAVE_AREA_ARM.ctype, |
| MD_CONTEXT_ARM64_FLOATING_POINT)) |
| ]) |
| |
| |
| MD_CONTEXT_AMD64 = 0x00100000 |
| MD_CONTEXT_AMD64_CONTROL = (MD_CONTEXT_AMD64 | 0x00000001) |
| MD_CONTEXT_AMD64_INTEGER = (MD_CONTEXT_AMD64 | 0x00000002) |
| MD_CONTEXT_AMD64_SEGMENTS = (MD_CONTEXT_AMD64 | 0x00000004) |
| MD_CONTEXT_AMD64_FLOATING_POINT = (MD_CONTEXT_AMD64 | 0x00000008) |
| MD_CONTEXT_AMD64_DEBUG_REGISTERS = (MD_CONTEXT_AMD64 | 0x00000010) |
| |
| MINIDUMP_CONTEXT_AMD64 = Descriptor([ |
| ("p1_home", ctypes.c_uint64), |
| ("p2_home", ctypes.c_uint64), |
| ("p3_home", ctypes.c_uint64), |
| ("p4_home", ctypes.c_uint64), |
| ("p5_home", ctypes.c_uint64), |
| ("p6_home", ctypes.c_uint64), |
| ("context_flags", ctypes.c_uint32), |
| ("mx_csr", ctypes.c_uint32), |
| # MD_CONTEXT_AMD64_CONTROL. |
| ("cs", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_CONTROL)), |
| # MD_CONTEXT_AMD64_SEGMENTS |
| ("ds", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_SEGMENTS)), |
| ("es", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_SEGMENTS)), |
| ("fs", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_SEGMENTS)), |
| ("gs", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_SEGMENTS)), |
| # MD_CONTEXT_AMD64_CONTROL. |
| ("ss", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_CONTROL)), |
| ("eflags", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_AMD64_CONTROL)), |
| # MD_CONTEXT_AMD64_DEBUG_REGISTERS. |
| ("dr0", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("dr1", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("dr2", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("dr3", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("dr6", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("dr7", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| # MD_CONTEXT_AMD64_INTEGER. |
| ("rax", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("rcx", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("rdx", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("rbx", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| # MD_CONTEXT_AMD64_CONTROL. |
| ("rsp", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_CONTROL)), |
| # MD_CONTEXT_AMD64_INTEGER. |
| ("rbp", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("rsi", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("rdi", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("r8", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("r9", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("r10", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("r11", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("r12", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("r13", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("r14", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| ("r15", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)), |
| # MD_CONTEXT_AMD64_CONTROL. |
| ("rip", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_CONTROL)), |
| # MD_CONTEXT_AMD64_FLOATING_POINT |
| ("sse_registers", EnableOnFlag(ctypes.c_uint8 * (16 * 26), |
| MD_CONTEXT_AMD64_FLOATING_POINT)), |
| ("vector_registers", EnableOnFlag(ctypes.c_uint8 * (16 * 26), |
| MD_CONTEXT_AMD64_FLOATING_POINT)), |
| ("vector_control", EnableOnFlag(ctypes.c_uint64, |
| MD_CONTEXT_AMD64_FLOATING_POINT)), |
| # MD_CONTEXT_AMD64_DEBUG_REGISTERS. |
| ("debug_control", EnableOnFlag(ctypes.c_uint64, |
| MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("last_branch_to_rip", EnableOnFlag(ctypes.c_uint64, |
| MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("last_branch_from_rip", EnableOnFlag(ctypes.c_uint64, |
| MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("last_exception_to_rip", EnableOnFlag(ctypes.c_uint64, |
| MD_CONTEXT_AMD64_DEBUG_REGISTERS)), |
| ("last_exception_from_rip", EnableOnFlag(ctypes.c_uint64, |
| MD_CONTEXT_AMD64_DEBUG_REGISTERS)) |
| ]) |
| |
| MINIDUMP_MEMORY_DESCRIPTOR = Descriptor([ |
| ("start", ctypes.c_uint64), |
| ("memory", MINIDUMP_LOCATION_DESCRIPTOR.ctype) |
| ]) |
| |
| MINIDUMP_MEMORY_DESCRIPTOR64 = Descriptor([ |
| ("start", ctypes.c_uint64), |
| ("size", ctypes.c_uint64) |
| ]) |
| |
| MINIDUMP_MEMORY_LIST = Descriptor([ |
| ("range_count", ctypes.c_uint32), |
| ("ranges", lambda m: MINIDUMP_MEMORY_DESCRIPTOR.ctype * m.range_count) |
| ]) |
| |
| MINIDUMP_MEMORY_LIST_Mac = Descriptor([ |
| ("range_count", ctypes.c_uint32), |
| ("junk", ctypes.c_uint32), |
| ("ranges", lambda m: MINIDUMP_MEMORY_DESCRIPTOR.ctype * m.range_count) |
| ]) |
| |
| MINIDUMP_MEMORY_LIST64 = Descriptor([ |
| ("range_count", ctypes.c_uint64), |
| ("base_rva", ctypes.c_uint64), |
| ("ranges", lambda m: MINIDUMP_MEMORY_DESCRIPTOR64.ctype * m.range_count) |
| ]) |
| |
| MINIDUMP_THREAD = Descriptor([ |
| ("id", ctypes.c_uint32), |
| ("suspend_count", ctypes.c_uint32), |
| ("priority_class", ctypes.c_uint32), |
| ("priority", ctypes.c_uint32), |
| ("ted", ctypes.c_uint64), |
| ("stack", MINIDUMP_MEMORY_DESCRIPTOR.ctype), |
| ("context", MINIDUMP_LOCATION_DESCRIPTOR.ctype) |
| ]) |
| |
| MINIDUMP_THREAD_LIST = Descriptor([ |
| ("thread_count", ctypes.c_uint32), |
| ("threads", lambda t: MINIDUMP_THREAD.ctype * t.thread_count) |
| ]) |
| |
| MINIDUMP_THREAD_LIST_Mac = Descriptor([ |
| ("thread_count", ctypes.c_uint32), |
| ("junk", ctypes.c_uint32), |
| ("threads", lambda t: MINIDUMP_THREAD.ctype * t.thread_count) |
| ]) |
| |
| MINIDUMP_VS_FIXEDFILEINFO = Descriptor([ |
| ("dwSignature", ctypes.c_uint32), |
| ("dwStrucVersion", ctypes.c_uint32), |
| ("dwFileVersionMS", ctypes.c_uint32), |
| ("dwFileVersionLS", ctypes.c_uint32), |
| ("dwProductVersionMS", ctypes.c_uint32), |
| ("dwProductVersionLS", ctypes.c_uint32), |
| ("dwFileFlagsMask", ctypes.c_uint32), |
| ("dwFileFlags", ctypes.c_uint32), |
| ("dwFileOS", ctypes.c_uint32), |
| ("dwFileType", ctypes.c_uint32), |
| ("dwFileSubtype", ctypes.c_uint32), |
| ("dwFileDateMS", ctypes.c_uint32), |
| ("dwFileDateLS", ctypes.c_uint32) |
| ]) |
| |
| MINIDUMP_RAW_MODULE = Descriptor([ |
| ("base_of_image", ctypes.c_uint64), |
| ("size_of_image", ctypes.c_uint32), |
| ("checksum", ctypes.c_uint32), |
| ("time_date_stamp", ctypes.c_uint32), |
| ("module_name_rva", ctypes.c_uint32), |
| ("version_info", MINIDUMP_VS_FIXEDFILEINFO.ctype), |
| ("cv_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype), |
| ("misc_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype), |
| ("reserved0", ctypes.c_uint32 * 2), |
| ("reserved1", ctypes.c_uint32 * 2) |
| ]) |
| |
| MINIDUMP_MODULE_LIST = Descriptor([ |
| ("number_of_modules", ctypes.c_uint32), |
| ("modules", lambda t: MINIDUMP_RAW_MODULE.ctype * t.number_of_modules) |
| ]) |
| |
| MINIDUMP_MODULE_LIST_Mac = Descriptor([ |
| ("number_of_modules", ctypes.c_uint32), |
| ("junk", ctypes.c_uint32), |
| ("modules", lambda t: MINIDUMP_RAW_MODULE.ctype * t.number_of_modules) |
| ]) |
| |
| MINIDUMP_RAW_SYSTEM_INFO = Descriptor([ |
| ("processor_architecture", ctypes.c_uint16) |
| ]) |
| |
| MD_CPU_ARCHITECTURE_X86 = 0 |
| MD_CPU_ARCHITECTURE_ARM = 5 |
| MD_CPU_ARCHITECTURE_ARM64 = 0x8003 |
| MD_CPU_ARCHITECTURE_AMD64 = 9 |
| |
| OBJDUMP_BIN = None |
| DEFAULT_OBJDUMP_BIN = '/usr/bin/objdump' |
| |
| class FuncSymbol: |
| def __init__(self, start, size, name): |
| self.start = start |
| self.end = self.start + size |
| self.name = name |
| |
| def __cmp__(self, other): |
| if isinstance(other, FuncSymbol): |
| return self.start - other.start |
| return self.start - other |
| |
| def Covers(self, addr): |
| return (self.start <= addr) and (addr < self.end) |
| |
| class MinidumpReader(object): |
| """Minidump (.dmp) reader.""" |
| |
| _HEADER_MAGIC = 0x504d444d |
| |
| def __init__(self, options, minidump_name): |
| self.minidump_name = minidump_name |
| self.minidump_file = open(minidump_name, "r") |
| self.minidump = mmap.mmap(self.minidump_file.fileno(), 0, mmap.MAP_PRIVATE) |
| self.header = MINIDUMP_HEADER.Read(self.minidump, 0) |
| if self.header.signature != MinidumpReader._HEADER_MAGIC: |
| print >>sys.stderr, "Warning: Unsupported minidump header magic!" |
| DebugPrint(self.header) |
| directories = [] |
| offset = self.header.stream_directories_rva |
| for _ in xrange(self.header.stream_count): |
| directories.append(MINIDUMP_DIRECTORY.Read(self.minidump, offset)) |
| offset += MINIDUMP_DIRECTORY.size |
| self.arch = None |
| self.exception = None |
| self.exception_context = None |
| self.memory_list = None |
| self.memory_list64 = None |
| self.module_list = None |
| self.thread_map = {} |
| |
| self.symdir = options.symdir |
| self.modules_with_symbols = [] |
| self.symbols = [] |
| |
| self._ReadArchitecture(directories) |
| self._ReadDirectories(directories) |
| self._FindObjdump(options) |
| |
| def _ReadArchitecture(self, directories): |
| # Find MDRawSystemInfo stream and determine arch. |
| for d in directories: |
| if d.stream_type == MD_SYSTEM_INFO_STREAM: |
| system_info = MINIDUMP_RAW_SYSTEM_INFO.Read( |
| self.minidump, d.location.rva) |
| self.arch = system_info.processor_architecture |
| assert self.arch in [MD_CPU_ARCHITECTURE_AMD64, |
| MD_CPU_ARCHITECTURE_ARM, |
| MD_CPU_ARCHITECTURE_ARM64, |
| MD_CPU_ARCHITECTURE_X86] |
| assert not self.arch is None |
| |
| def _ReadDirectories(self, directories): |
| for d in directories: |
| DebugPrint(d) |
| if d.stream_type == MD_EXCEPTION_STREAM: |
| self.exception = MINIDUMP_EXCEPTION_STREAM.Read( |
| self.minidump, d.location.rva) |
| DebugPrint(self.exception) |
| self.exception_context = self.ContextDescriptor().Read( |
| self.minidump, self.exception.thread_context.rva) |
| DebugPrint(self.exception_context) |
| elif d.stream_type == MD_THREAD_LIST_STREAM: |
| thread_list = MINIDUMP_THREAD_LIST.Read(self.minidump, d.location.rva) |
| if ctypes.sizeof(thread_list) + 4 == d.location.data_size: |
| thread_list = MINIDUMP_THREAD_LIST_Mac.Read( |
| self.minidump, d.location.rva) |
| assert ctypes.sizeof(thread_list) == d.location.data_size |
| DebugPrint(thread_list) |
| for thread in thread_list.threads: |
| DebugPrint(thread) |
| self.thread_map[thread.id] = thread |
| elif d.stream_type == MD_MODULE_LIST_STREAM: |
| assert self.module_list is None |
| self.module_list = MINIDUMP_MODULE_LIST.Read( |
| self.minidump, d.location.rva) |
| if ctypes.sizeof(self.module_list) + 4 == d.location.data_size: |
| self.module_list = MINIDUMP_MODULE_LIST_Mac.Read( |
| self.minidump, d.location.rva) |
| assert ctypes.sizeof(self.module_list) == d.location.data_size |
| DebugPrint(self.module_list) |
| elif d.stream_type == MD_MEMORY_LIST_STREAM: |
| print >>sys.stderr, "Warning: This is not a full minidump!" |
| assert self.memory_list is None |
| self.memory_list = MINIDUMP_MEMORY_LIST.Read( |
| self.minidump, d.location.rva) |
| if ctypes.sizeof(self.memory_list) + 4 == d.location.data_size: |
| self.memory_list = MINIDUMP_MEMORY_LIST_Mac.Read( |
| self.minidump, d.location.rva) |
| assert ctypes.sizeof(self.memory_list) == d.location.data_size |
| DebugPrint(self.memory_list) |
| elif d.stream_type == MD_MEMORY_64_LIST_STREAM: |
| assert self.memory_list64 is None |
| self.memory_list64 = MINIDUMP_MEMORY_LIST64.Read( |
| self.minidump, d.location.rva) |
| assert ctypes.sizeof(self.memory_list64) == d.location.data_size |
| DebugPrint(self.memory_list64) |
| |
| def _FindObjdump(self, options): |
| if options.objdump: |
| objdump_bin = options.objdump |
| else: |
| objdump_bin = self._FindThirdPartyObjdump() |
| if not objdump_bin or not os.path.exists(objdump_bin): |
| print "# Cannot find '%s', falling back to default objdump '%s'" % ( |
| objdump_bin, DEFAULT_OBJDUMP_BIN) |
| objdump_bin = DEFAULT_OBJDUMP_BIN |
| global OBJDUMP_BIN |
| OBJDUMP_BIN = objdump_bin |
| disasm.OBJDUMP_BIN = objdump_bin |
| |
| def _FindThirdPartyObjdump(self): |
| # Try to find the platform specific objdump |
| third_party_dir = os.path.join( |
| os.path.dirname(os.path.dirname(__file__)), 'third_party') |
| objdumps = [] |
| for root, dirs, files in os.walk(third_party_dir): |
| for file in files: |
| if file.endswith("objdump"): |
| objdumps.append(os.path.join(root, file)) |
| if self.arch == MD_CPU_ARCHITECTURE_ARM: |
| platform_filter = 'arm-linux' |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM64: |
| platform_filter = 'aarch64' |
| else: |
| # use default otherwise |
| return None |
| print ("# Looking for platform specific (%s) objdump in " |
| "third_party directory.") % platform_filter |
| objdumps = filter(lambda file: platform_filter in file >= 0, objdumps) |
| if len(objdumps) == 0: |
| print "# Could not find platform specific objdump in third_party." |
| print "# Make sure you installed the correct SDK." |
| return None |
| return objdumps[0] |
| |
| def ContextDescriptor(self): |
| if self.arch == MD_CPU_ARCHITECTURE_X86: |
| return MINIDUMP_CONTEXT_X86 |
| elif self.arch == MD_CPU_ARCHITECTURE_AMD64: |
| return MINIDUMP_CONTEXT_AMD64 |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM: |
| return MINIDUMP_CONTEXT_ARM |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM64: |
| return MINIDUMP_CONTEXT_ARM64 |
| else: |
| return None |
| |
| def IsValidAlignedAddress(self, address): |
| return self.IsAlignedAddress(address) and self.IsValidAddress(address) |
| |
| def IsValidAddress(self, address): |
| return self.FindLocation(address) is not None |
| |
| def IsAlignedAddress(self, address): |
| return (address % self.PointerSize()) == 0 |
| |
| def IsExceptionStackAddress(self, address): |
| if not self.IsAlignedAddress(address): return False |
| return self.IsAnyExceptionStackAddress(address) |
| |
| def IsAnyExceptionStackAddress(self, address): |
| return self.StackTop() <= address <= self.StackBottom() |
| |
| def IsValidExceptionStackAddress(self, address): |
| if not self.IsValidAddress(address): return False |
| return self.isExceptionStackAddress(address) |
| |
| def IsModuleAddress(self, address): |
| return self.GetModuleForAddress(address) != None |
| |
| def GetModuleForAddress(self, address): |
| for module in self.module_list.modules: |
| start = module.base_of_image |
| end = start + module.size_of_image |
| if start <= address < end: return module |
| return None |
| |
| def ReadU8(self, address): |
| location = self.FindLocation(address) |
| return ctypes.c_uint8.from_buffer(self.minidump, location).value |
| |
| def ReadU32(self, address): |
| location = self.FindLocation(address) |
| return ctypes.c_uint32.from_buffer(self.minidump, location).value |
| |
| def ReadU64(self, address): |
| location = self.FindLocation(address) |
| return ctypes.c_uint64.from_buffer(self.minidump, location).value |
| |
| def Is64(self): |
| return (self.arch == MD_CPU_ARCHITECTURE_ARM64 or |
| self.arch == MD_CPU_ARCHITECTURE_AMD64) |
| |
| def ReadUIntPtr(self, address): |
| if self.Is64(): |
| return self.ReadU64(address) |
| return self.ReadU32(address) |
| |
| def ReadBytes(self, address, size): |
| location = self.FindLocation(address) |
| return self.minidump[location:location + size] |
| |
| def _ReadWord(self, location): |
| if self.Is64(): |
| return ctypes.c_uint64.from_buffer(self.minidump, location).value |
| return ctypes.c_uint32.from_buffer(self.minidump, location).value |
| |
| def ReadAsciiPtr(self, address): |
| ascii_content = [c if c >= '\x20' and c < '\x7f' else '.' |
| for c in self.ReadBytes(address, self.PointerSize())] |
| return ''.join(ascii_content) |
| |
| def ReadAsciiString(self, address): |
| string = "" |
| while self.IsValidAddress(address): |
| code = self.ReadU8(address) |
| if 0 < code < 128: |
| string += chr(code) |
| else: |
| break |
| address += 1 |
| return string |
| |
| def IsProbableASCIIRegion(self, location, length): |
| ascii_bytes = 0 |
| non_ascii_bytes = 0 |
| for i in xrange(length): |
| loc = location + i |
| byte = ctypes.c_uint8.from_buffer(self.minidump, loc).value |
| if byte >= 0x7f: |
| non_ascii_bytes += 1 |
| if byte < 0x20 and byte != 0: |
| non_ascii_bytes += 1 |
| if byte < 0x7f and byte >= 0x20: |
| ascii_bytes += 1 |
| if byte == 0xa: # newline |
| ascii_bytes += 1 |
| if ascii_bytes * 10 <= length: |
| return False |
| if length > 0 and ascii_bytes > non_ascii_bytes * 7: |
| return True |
| if ascii_bytes > non_ascii_bytes * 3: |
| return None # Maybe |
| return False |
| |
| def IsProbableExecutableRegion(self, location, length): |
| opcode_bytes = 0 |
| sixty_four = self.Is64() |
| for i in xrange(length): |
| loc = location + i |
| byte = ctypes.c_uint8.from_buffer(self.minidump, loc).value |
| if (byte == 0x8b or # mov |
| byte == 0x89 or # mov reg-reg |
| (byte & 0xf0) == 0x50 or # push/pop |
| (sixty_four and (byte & 0xf0) == 0x40) or # rex prefix |
| byte == 0xc3 or # return |
| byte == 0x74 or # jeq |
| byte == 0x84 or # jeq far |
| byte == 0x75 or # jne |
| byte == 0x85 or # jne far |
| byte == 0xe8 or # call |
| byte == 0xe9 or # jmp far |
| byte == 0xeb): # jmp near |
| opcode_bytes += 1 |
| opcode_percent = (opcode_bytes * 100) / length |
| threshold = 20 |
| if opcode_percent > threshold + 2: |
| return True |
| if opcode_percent > threshold - 2: |
| return None # Maybe |
| return False |
| |
| def FindRegion(self, addr): |
| answer = [-1, -1] |
| def is_in(reader, start, size, location): |
| if addr >= start and addr < start + size: |
| answer[0] = start |
| answer[1] = size |
| self.ForEachMemoryRegion(is_in) |
| if answer[0] == -1: |
| return None |
| return answer |
| |
| def ForEachMemoryRegion(self, cb): |
| if self.memory_list64 is not None: |
| for r in self.memory_list64.ranges: |
| location = self.memory_list64.base_rva + offset |
| cb(self, r.start, r.size, location) |
| offset += r.size |
| |
| if self.memory_list is not None: |
| for r in self.memory_list.ranges: |
| cb(self, r.start, r.memory.data_size, r.memory.rva) |
| |
| def FindWord(self, word, alignment=0): |
| def search_inside_region(reader, start, size, location): |
| location = (location + alignment) & ~alignment |
| for i in xrange(size - self.PointerSize()): |
| loc = location + i |
| if reader._ReadWord(loc) == word: |
| slot = start + (loc - location) |
| print "%s: %s" % (reader.FormatIntPtr(slot), |
| reader.FormatIntPtr(word)) |
| self.ForEachMemoryRegion(search_inside_region) |
| |
| def FindWordList(self, word): |
| aligned_res = [] |
| unaligned_res = [] |
| def search_inside_region(reader, start, size, location): |
| for i in xrange(size - self.PointerSize()): |
| loc = location + i |
| if reader._ReadWord(loc) == word: |
| slot = start + (loc - location) |
| if self.IsAlignedAddress(slot): |
| aligned_res.append(slot) |
| else: |
| unaligned_res.append(slot) |
| self.ForEachMemoryRegion(search_inside_region) |
| return (aligned_res, unaligned_res) |
| |
| def FindLocation(self, address): |
| offset = 0 |
| if self.memory_list64 is not None: |
| for r in self.memory_list64.ranges: |
| if r.start <= address < r.start + r.size: |
| return self.memory_list64.base_rva + offset + address - r.start |
| offset += r.size |
| if self.memory_list is not None: |
| for r in self.memory_list.ranges: |
| if r.start <= address < r.start + r.memory.data_size: |
| return r.memory.rva + address - r.start |
| return None |
| |
| def GetDisasmLines(self, address, size): |
| def CountUndefinedInstructions(lines): |
| pattern = "<UNDEFINED>" |
| return sum([line.count(pattern) for (ignore, line) in lines]) |
| |
| location = self.FindLocation(address) |
| if location is None: return [] |
| arch = None |
| possible_objdump_flags = [""] |
| if self.arch == MD_CPU_ARCHITECTURE_X86: |
| arch = "ia32" |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM: |
| arch = "arm" |
| possible_objdump_flags = ["", "--disassembler-options=force-thumb"] |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM64: |
| arch = "arm64" |
| possible_objdump_flags = ["", "--disassembler-options=force-thumb"] |
| elif self.arch == MD_CPU_ARCHITECTURE_AMD64: |
| arch = "x64" |
| results = [ disasm.GetDisasmLines(self.minidump_name, |
| location, |
| size, |
| arch, |
| False, |
| objdump_flags) |
| for objdump_flags in possible_objdump_flags ] |
| return min(results, key=CountUndefinedInstructions) |
| |
| |
| def Dispose(self): |
| self.minidump.close() |
| self.minidump_file.close() |
| |
| def ExceptionIP(self): |
| if self.arch == MD_CPU_ARCHITECTURE_AMD64: |
| return self.exception_context.rip |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM: |
| return self.exception_context.pc |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM64: |
| return self.exception_context.pc |
| elif self.arch == MD_CPU_ARCHITECTURE_X86: |
| return self.exception_context.eip |
| |
| def ExceptionSP(self): |
| if self.arch == MD_CPU_ARCHITECTURE_AMD64: |
| return self.exception_context.rsp |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM: |
| return self.exception_context.sp |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM64: |
| return self.exception_context.sp |
| elif self.arch == MD_CPU_ARCHITECTURE_X86: |
| return self.exception_context.esp |
| |
| def ExceptionFP(self): |
| if self.arch == MD_CPU_ARCHITECTURE_AMD64: |
| return self.exception_context.rbp |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM: |
| return None |
| elif self.arch == MD_CPU_ARCHITECTURE_ARM64: |
| return self.exception_context.fp |
| elif self.arch == MD_CPU_ARCHITECTURE_X86: |
| return self.exception_context.ebp |
| |
| def ExceptionThread(self): |
| return self.thread_map[self.exception.thread_id] |
| |
| def StackTop(self): |
| return self.ExceptionSP() |
| |
| def StackBottom(self): |
| exception_thread = self.ExceptionThread() |
| return exception_thread.stack.start + \ |
| exception_thread.stack.memory.data_size |
| |
| def FormatIntPtr(self, value): |
| if self.Is64(): |
| return "%016x" % value |
| return "%08x" % value |
| |
| def PointerSize(self): |
| if self.Is64(): |
| return 8 |
| return 4 |
| |
| def Register(self, name): |
| return self.exception_context.__getattribute__(name) |
| |
| def ReadMinidumpString(self, rva): |
| string = bytearray(MINIDUMP_STRING.Read(self.minidump, rva).buffer) |
| string = string.decode("utf16") |
| return string[0:len(string) - 1] |
| |
| # Load FUNC records from a BreakPad symbol file |
| # |
| # http://code.google.com/p/google-breakpad/wiki/SymbolFiles |
| # |
| def _LoadSymbolsFrom(self, symfile, baseaddr): |
| print "Loading symbols from %s" % (symfile) |
| funcs = [] |
| with open(symfile) as f: |
| for line in f: |
| result = re.match( |
| r"^FUNC ([a-f0-9]+) ([a-f0-9]+) ([a-f0-9]+) (.*)$", line) |
| if result is not None: |
| start = int(result.group(1), 16) |
| size = int(result.group(2), 16) |
| name = result.group(4).rstrip() |
| bisect.insort_left(self.symbols, |
| FuncSymbol(baseaddr + start, size, name)) |
| print " ... done" |
| |
| def TryLoadSymbolsFor(self, modulename, module): |
| try: |
| symfile = os.path.join(self.symdir, |
| modulename.replace('.', '_') + ".pdb.sym") |
| if os.path.isfile(symfile): |
| self._LoadSymbolsFrom(symfile, module.base_of_image) |
| self.modules_with_symbols.append(module) |
| except Exception as e: |
| print " ... failure (%s)" % (e) |
| |
| # Returns true if address is covered by some module that has loaded symbols. |
| def _IsInModuleWithSymbols(self, addr): |
| for module in self.modules_with_symbols: |
| start = module.base_of_image |
| end = start + module.size_of_image |
| if (start <= addr) and (addr < end): |
| return True |
| return False |
| |
| # Find symbol covering the given address and return its name in format |
| # <symbol name>+<offset from the start> |
| def FindSymbol(self, addr): |
| if not self._IsInModuleWithSymbols(addr): |
| return None |
| |
| i = bisect.bisect_left(self.symbols, addr) |
| symbol = None |
| if (0 < i) and self.symbols[i - 1].Covers(addr): |
| symbol = self.symbols[i - 1] |
| elif (i < len(self.symbols)) and self.symbols[i].Covers(addr): |
| symbol = self.symbols[i] |
| else: |
| return None |
| diff = addr - symbol.start |
| return "%s+0x%x" % (symbol.name, diff) |
| |
| |
| class Printer(object): |
| """Printer with indentation support.""" |
| |
| def __init__(self): |
| self.indent = 0 |
| |
| def Indent(self): |
| self.indent += 2 |
| |
| def Dedent(self): |
| self.indent -= 2 |
| |
| def Print(self, string): |
| print "%s%s" % (self._IndentString(), string) |
| |
| def PrintLines(self, lines): |
| indent = self._IndentString() |
| print "\n".join("%s%s" % (indent, line) for line in lines) |
| |
| def _IndentString(self): |
| return self.indent * " " |
| |
| |
| ADDRESS_RE = re.compile(r"0x[0-9a-fA-F]+") |
| |
| |
| def FormatDisasmLine(start, heap, line): |
| line_address = start + line[0] |
| stack_slot = heap.stack_map.get(line_address) |
| marker = " " |
| if stack_slot: |
| marker = "=>" |
| code = AnnotateAddresses(heap, line[1]) |
| |
| # Compute the actual call target which the disassembler is too stupid |
| # to figure out (it adds the call offset to the disassembly offset rather |
| # than the absolute instruction address). |
| if heap.reader.arch == MD_CPU_ARCHITECTURE_X86: |
| if code.startswith("e8"): |
| words = code.split() |
| if len(words) > 6 and words[5] == "call": |
| offset = int(words[4] + words[3] + words[2] + words[1], 16) |
| target = (line_address + offset + 5) & 0xFFFFFFFF |
| code = code.replace(words[6], "0x%08x" % target) |
| # TODO(jkummerow): port this hack to ARM and x64. |
| |
| return "%s%08x %08x: %s" % (marker, line_address, line[0], code) |
| |
| |
| def AnnotateAddresses(heap, line): |
| extra = [] |
| for m in ADDRESS_RE.finditer(line): |
| maybe_address = int(m.group(0), 16) |
| object = heap.FindObject(maybe_address) |
| if not object: continue |
| extra.append(str(object)) |
| if len(extra) == 0: return line |
| return "%s ;; %s" % (line, ", ".join(extra)) |
| |
| |
| class HeapObject(object): |
| def __init__(self, heap, map, address): |
| self.heap = heap |
| self.map = map |
| self.address = address |
| |
| def Is(self, cls): |
| return isinstance(self, cls) |
| |
| def Print(self, p): |
| p.Print(str(self)) |
| |
| def __str__(self): |
| instance_type = "???" |
| if self.map is not None: |
| instance_type = INSTANCE_TYPES[self.map.instance_type] |
| return "%s(%s, %s)" % (self.__class__.__name__, |
| self.heap.reader.FormatIntPtr(self.address), |
| instance_type) |
| |
| def ObjectField(self, offset): |
| field_value = self.heap.reader.ReadUIntPtr(self.address + offset) |
| return self.heap.FindObjectOrSmi(field_value) |
| |
| def SmiField(self, offset): |
| field_value = self.heap.reader.ReadUIntPtr(self.address + offset) |
| if self.heap.IsSmi(field_value): |
| return self.heap.SmiUntag(field_value) |
| return None |
| |
| |
| class Map(HeapObject): |
| def Decode(self, offset, size, value): |
| return (value >> offset) & ((1 << size) - 1) |
| |
| # Instance Sizes |
| def InstanceSizesOffset(self): |
| return self.heap.PointerSize() |
| |
| def InstanceSizeOffset(self): |
| return self.InstanceSizesOffset() |
| |
| def InObjectProperties(self): |
| return self.InstanceSizeOffset() + 1 |
| |
| def UnusedByte(self): |
| return self.InObjectProperties() + 1 |
| |
| def VisitorId(self): |
| return self.UnusedByte() + 1 |
| |
| # Instance Attributes |
| def InstanceAttributesOffset(self): |
| return self.InstanceSizesOffset() + self.heap.IntSize() |
| |
| def InstanceTypeOffset(self): |
| return self.InstanceAttributesOffset() |
| |
| def BitFieldOffset(self): |
| return self.InstanceTypeOffset() + 1 |
| |
| def BitField2Offset(self): |
| return self.BitFieldOffset() + 1 |
| |
| def UnusedPropertyFieldsOffset(self): |
| return self.BitField2Offset() + 1 |
| |
| # Other fields |
| def BitField3Offset(self): |
| return self.InstanceAttributesOffset() + self.heap.IntSize() |
| |
| def PrototypeOffset(self): |
| return self.BitField3Offset() + self.heap.PointerSize() |
| |
| def ConstructorOrBackPointerOffset(self): |
| return self.PrototypeOffset() + self.heap.PointerSize() |
| |
| def TransitionsOrPrototypeInfoOffset(self): |
| return self.ConstructorOrBackPointerOffset() + self.heap.PointerSize() |
| |
| def DescriptorsOffset(self): |
| return self.TransitionsOrPrototypeInfoOffset() + self.heap.PointerSize() |
| |
| def LayoutDescriptorOffset(self): |
| return self.DescriptorsOffset() + self.heap.PointerSize() |
| |
| def CodeCacheOffset(self): |
| if (self.heap.reader.Is64()): |
| return self.LayoutDescriptorOffset() + self.heap.PointerSize() |
| return self.DescriptorsOffset() + self.heap.PointerSize() |
| |
| def DependentCodeOffset(self): |
| return self.CodeCacheOffset() + self.heap.PointerSize() |
| |
| def WeakCellCacheOffset(self): |
| return self.DependentCodeOffset() + self.heap.PointerSize() |
| |
| def ReadByte(self, offset): |
| return self.heap.reader.ReadU8(self.address + offset) |
| |
| def ReadWord(self, offset): |
| return self.heap.reader.ReadUIntPtr(self.address + offset) |
| |
| def Print(self, p): |
| p.Print("Map(%08x)" % (self.address)) |
| p.Print(" - size: %d, inobject: %d, (unused: %d), visitor: %d" % ( |
| self.ReadByte(self.InstanceSizeOffset()), |
| self.ReadByte(self.InObjectProperties()), |
| self.ReadByte(self.UnusedByte()), |
| self.VisitorId())) |
| |
| instance_type = INSTANCE_TYPES[self.ReadByte(self.InstanceTypeOffset())] |
| bitfield = self.ReadByte(self.BitFieldOffset()) |
| bitfield2 = self.ReadByte(self.BitField2Offset()) |
| unused = self.ReadByte(self.UnusedPropertyFieldsOffset()) |
| p.Print(" - %s, bf: %d, bf2: %d, unused: %d" % ( |
| instance_type, bitfield, bitfield2, unused)) |
| |
| p.Print(" - kind: %s" % (self.Decode(3, 5, bitfield2))) |
| |
| bitfield3 = self.ReadWord(self.BitField3Offset()) |
| |
| p.Print( |
| " - EnumLength: %d NumberOfOwnDescriptors: %d OwnsDescriptors: %s" % ( |
| self.Decode(0, 10, bitfield3), |
| self.Decode(10, 10, bitfield3), |
| self.Decode(21, 1, bitfield3))) |
| p.Print(" - DictionaryMap: %s" % (self.Decode(20, 1, bitfield3))) |
| p.Print(" - Deprecated: %s" % (self.Decode(23, 1, bitfield3))) |
| p.Print(" - IsUnstable: %s" % (self.Decode(24, 1, bitfield3))) |
| p.Print(" - NewTargetIsBase: %s" % (self.Decode(27, 1, bitfield3))) |
| |
| descriptors = self.ObjectField(self.DescriptorsOffset()) |
| if descriptors.__class__ == FixedArray: |
| DescriptorArray(descriptors).Print(p) |
| else: |
| p.Print(" - Descriptors: %s" % (descriptors)) |
| |
| transitions = self.ObjectField(self.TransitionsOrPrototypeInfoOffset()) |
| if transitions.__class__ == FixedArray: |
| TransitionArray(transitions).Print(p) |
| else: |
| p.Print(" - TransitionsOrPrototypeInfo: %s" % (transitions)) |
| |
| p.Print(" - Prototype: %s" % self.ObjectField(self.PrototypeOffset())) |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| self.instance_type = \ |
| heap.reader.ReadU8(self.address + self.InstanceTypeOffset()) |
| |
| |
| class String(HeapObject): |
| def LengthOffset(self): |
| # First word after the map is the hash, the second is the length. |
| return self.heap.PointerSize() * 2 |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| self.length = self.SmiField(self.LengthOffset()) |
| |
| def GetChars(self): |
| return "?string?" |
| |
| def Print(self, p): |
| p.Print(str(self)) |
| |
| def __str__(self): |
| return "\"%s\"" % self.GetChars() |
| |
| |
| class SeqString(String): |
| def CharsOffset(self): |
| return self.heap.PointerSize() * 3 |
| |
| def __init__(self, heap, map, address): |
| String.__init__(self, heap, map, address) |
| self.chars = heap.reader.ReadBytes(self.address + self.CharsOffset(), |
| self.length) |
| |
| def GetChars(self): |
| return self.chars |
| |
| |
| class ExternalString(String): |
| # TODO(vegorov) fix ExternalString for X64 architecture |
| RESOURCE_OFFSET = 12 |
| |
| WEBKIT_RESOUCE_STRING_IMPL_OFFSET = 4 |
| WEBKIT_STRING_IMPL_CHARS_OFFSET = 8 |
| |
| def __init__(self, heap, map, address): |
| String.__init__(self, heap, map, address) |
| reader = heap.reader |
| self.resource = \ |
| reader.ReadU32(self.address + ExternalString.RESOURCE_OFFSET) |
| self.chars = "?external string?" |
| if not reader.IsValidAddress(self.resource): return |
| string_impl_address = self.resource + \ |
| ExternalString.WEBKIT_RESOUCE_STRING_IMPL_OFFSET |
| if not reader.IsValidAddress(string_impl_address): return |
| string_impl = reader.ReadU32(string_impl_address) |
| chars_ptr_address = string_impl + \ |
| ExternalString.WEBKIT_STRING_IMPL_CHARS_OFFSET |
| if not reader.IsValidAddress(chars_ptr_address): return |
| chars_ptr = reader.ReadU32(chars_ptr_address) |
| if not reader.IsValidAddress(chars_ptr): return |
| raw_chars = reader.ReadBytes(chars_ptr, 2 * self.length) |
| self.chars = codecs.getdecoder("utf16")(raw_chars)[0] |
| |
| def GetChars(self): |
| return self.chars |
| |
| |
| class ConsString(String): |
| def LeftOffset(self): |
| return self.heap.PointerSize() * 3 |
| |
| def RightOffset(self): |
| return self.heap.PointerSize() * 4 |
| |
| def __init__(self, heap, map, address): |
| String.__init__(self, heap, map, address) |
| self.left = self.ObjectField(self.LeftOffset()) |
| self.right = self.ObjectField(self.RightOffset()) |
| |
| def GetChars(self): |
| try: |
| return self.left.GetChars() + self.right.GetChars() |
| except: |
| return "***CAUGHT EXCEPTION IN GROKDUMP***" |
| |
| |
| class Oddball(HeapObject): |
| #Should match declarations in objects.h |
| KINDS = [ |
| "False", |
| "True", |
| "TheHole", |
| "Null", |
| "ArgumentMarker", |
| "Undefined", |
| "Other" |
| ] |
| |
| def ToStringOffset(self): |
| return self.heap.PointerSize() |
| |
| def ToNumberOffset(self): |
| return self.ToStringOffset() + self.heap.PointerSize() |
| |
| def KindOffset(self): |
| return self.ToNumberOffset() + self.heap.PointerSize() |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| self.to_string = self.ObjectField(self.ToStringOffset()) |
| self.kind = self.SmiField(self.KindOffset()) |
| |
| def Print(self, p): |
| p.Print(str(self)) |
| |
| def __str__(self): |
| if self.to_string: |
| return "Oddball(%08x, <%s>)" % (self.address, str(self.to_string)) |
| else: |
| kind = "???" |
| if 0 <= self.kind < len(Oddball.KINDS): |
| kind = Oddball.KINDS[self.kind] |
| return "Oddball(%08x, kind=%s)" % (self.address, kind) |
| |
| |
| class FixedArray(HeapObject): |
| def LengthOffset(self): |
| return self.heap.PointerSize() |
| |
| def ElementsOffset(self): |
| return self.heap.PointerSize() * 2 |
| |
| def MemberOffset(self, i): |
| return self.ElementsOffset() + self.heap.PointerSize() * i |
| |
| def Get(self, i): |
| return self.ObjectField(self.MemberOffset(i)) |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| self.length = self.SmiField(self.LengthOffset()) |
| |
| def Print(self, p): |
| p.Print("FixedArray(%s) {" % self.heap.reader.FormatIntPtr(self.address)) |
| p.Indent() |
| p.Print("length: %d" % self.length) |
| base_offset = self.ElementsOffset() |
| for i in xrange(self.length): |
| offset = base_offset + 4 * i |
| try: |
| p.Print("[%08d] = %s" % (i, self.ObjectField(offset))) |
| except TypeError: |
| p.Dedent() |
| p.Print("...") |
| p.Print("}") |
| return |
| p.Dedent() |
| p.Print("}") |
| |
| def __str__(self): |
| return "FixedArray(%08x, length=%d)" % (self.address, self.length) |
| |
| |
| class DescriptorArray(object): |
| def __init__(self, array): |
| self.array = array |
| |
| def Length(self): |
| return self.array.Get(0) |
| |
| def Decode(self, offset, size, value): |
| return (value >> offset) & ((1 << size) - 1) |
| |
| TYPES = [ |
| "normal", |
| "field", |
| "function", |
| "callbacks" |
| ] |
| |
| def Type(self, value): |
| return DescriptorArray.TYPES[self.Decode(0, 3, value)] |
| |
| def Attributes(self, value): |
| attributes = self.Decode(3, 3, value) |
| result = [] |
| if (attributes & 0): result += ["ReadOnly"] |
| if (attributes & 1): result += ["DontEnum"] |
| if (attributes & 2): result += ["DontDelete"] |
| return "[" + (",".join(result)) + "]" |
| |
| def Deleted(self, value): |
| return self.Decode(6, 1, value) == 1 |
| |
| def FieldIndex(self, value): |
| return self.Decode(20, 11, value) |
| |
| def Pointer(self, value): |
| return self.Decode(6, 11, value) |
| |
| def Details(self, di, value): |
| return ( |
| di, |
| self.Type(value), |
| self.Attributes(value), |
| self.FieldIndex(value), |
| self.Pointer(value) |
| ) |
| |
| |
| def Print(self, p): |
| length = self.Length() |
| array = self.array |
| |
| p.Print("Descriptors(%08x, length=%d)" % (array.address, length)) |
| p.Print("[et] %s" % (array.Get(1))) |
| |
| for di in xrange(length): |
| i = 2 + di * 3 |
| p.Print("0x%x" % (array.address + array.MemberOffset(i))) |
| p.Print("[%i] name: %s" % (di, array.Get(i + 0))) |
| p.Print("[%i] details: %s %s field-index %i pointer %i" % \ |
| self.Details(di, array.Get(i + 1))) |
| p.Print("[%i] value: %s" % (di, array.Get(i + 2))) |
| |
| end = self.array.length // 3 |
| if length != end: |
| p.Print("[%i-%i] slack descriptors" % (length, end)) |
| |
| |
| class TransitionArray(object): |
| def __init__(self, array): |
| self.array = array |
| |
| def IsSimpleTransition(self): |
| return self.array.length <= 2 |
| |
| def Length(self): |
| # SimpleTransition cases |
| if self.IsSimpleTransition(): |
| return self.array.length - 1 |
| return (self.array.length - 3) // 2 |
| |
| def Print(self, p): |
| length = self.Length() |
| array = self.array |
| |
| p.Print("Transitions(%08x, length=%d)" % (array.address, length)) |
| p.Print("[backpointer] %s" % (array.Get(0))) |
| if self.IsSimpleTransition(): |
| if length == 1: |
| p.Print("[simple target] %s" % (array.Get(1))) |
| return |
| |
| elements = array.Get(1) |
| if elements is not None: |
| p.Print("[elements ] %s" % (elements)) |
| |
| prototype = array.Get(2) |
| if prototype is not None: |
| p.Print("[prototype ] %s" % (prototype)) |
| |
| for di in xrange(length): |
| i = 3 + di * 2 |
| p.Print("[%i] symbol: %s" % (di, array.Get(i + 0))) |
| p.Print("[%i] target: %s" % (di, array.Get(i + 1))) |
| |
| |
| class JSFunction(HeapObject): |
| def CodeEntryOffset(self): |
| return 3 * self.heap.PointerSize() |
| |
| def SharedOffset(self): |
| return 5 * self.heap.PointerSize() |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| code_entry = \ |
| heap.reader.ReadU32(self.address + self.CodeEntryOffset()) |
| self.code = heap.FindObject(code_entry - Code.HeaderSize(heap) + 1) |
| self.shared = self.ObjectField(self.SharedOffset()) |
| |
| def Print(self, p): |
| source = "\n".join(" %s" % line for line in self._GetSource().split("\n")) |
| p.Print("JSFunction(%s) {" % self.heap.reader.FormatIntPtr(self.address)) |
| p.Indent() |
| p.Print("inferred name: %s" % self.shared.inferred_name) |
| if self.shared.script.Is(Script) and self.shared.script.name.Is(String): |
| p.Print("script name: %s" % self.shared.script.name) |
| p.Print("source:") |
| p.PrintLines(self._GetSource().split("\n")) |
| p.Print("code:") |
| self.code.Print(p) |
| if self.code != self.shared.code: |
| p.Print("unoptimized code:") |
| self.shared.code.Print(p) |
| p.Dedent() |
| p.Print("}") |
| |
| def __str__(self): |
| inferred_name = "" |
| if self.shared is not None and self.shared.Is(SharedFunctionInfo): |
| inferred_name = self.shared.inferred_name |
| return "JSFunction(%s, %s) " % \ |
| (self.heap.reader.FormatIntPtr(self.address), inferred_name) |
| |
| def _GetSource(self): |
| source = "?source?" |
| start = self.shared.start_position |
| end = self.shared.end_position |
| if not self.shared.script.Is(Script): return source |
| script_source = self.shared.script.source |
| if not script_source.Is(String): return source |
| if start and end: |
| source = script_source.GetChars()[start:end] |
| return source |
| |
| |
| class SharedFunctionInfo(HeapObject): |
| def CodeOffset(self): |
| return 2 * self.heap.PointerSize() |
| |
| def ScriptOffset(self): |
| return 7 * self.heap.PointerSize() |
| |
| def InferredNameOffset(self): |
| return 9 * self.heap.PointerSize() |
| |
| def EndPositionOffset(self): |
| return 12 * self.heap.PointerSize() + 4 * self.heap.IntSize() |
| |
| def StartPositionAndTypeOffset(self): |
| return 12 * self.heap.PointerSize() + 5 * self.heap.IntSize() |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| try: |
| self.code = self.ObjectField(self.CodeOffset()) |
| self.script = self.ObjectField(self.ScriptOffset()) |
| self.inferred_name = self.ObjectField(self.InferredNameOffset()) |
| if heap.PointerSize() == 8: |
| start_position_and_type = \ |
| heap.reader.ReadU32(self.StartPositionAndTypeOffset()) |
| self.start_position = start_position_and_type >> 2 |
| pseudo_smi_end_position = \ |
| heap.reader.ReadU32(self.EndPositionOffset()) |
| self.end_position = pseudo_smi_end_position >> 2 |
| else: |
| start_position_and_type = \ |
| self.SmiField(self.StartPositionAndTypeOffset()) |
| if start_position_and_type: |
| self.start_position = start_position_and_type >> 2 |
| else: |
| self.start_position = None |
| self.end_position = \ |
| self.SmiField(self.EndPositionOffset()) |
| except: |
| print("*** Error while reading SharedFunctionInfo") |
| |
| |
| class Script(HeapObject): |
| def SourceOffset(self): |
| return self.heap.PointerSize() |
| |
| def NameOffset(self): |
| return self.SourceOffset() + self.heap.PointerSize() |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| self.source = self.ObjectField(self.SourceOffset()) |
| self.name = self.ObjectField(self.NameOffset()) |
| |
| |
| class CodeCache(HeapObject): |
| def DefaultCacheOffset(self): |
| return self.heap.PointerSize() |
| |
| def NormalTypeCacheOffset(self): |
| return self.DefaultCacheOffset() + self.heap.PointerSize() |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| self.default_cache = self.ObjectField(self.DefaultCacheOffset()) |
| self.normal_type_cache = self.ObjectField(self.NormalTypeCacheOffset()) |
| |
| def Print(self, p): |
| p.Print("CodeCache(%s) {" % self.heap.reader.FormatIntPtr(self.address)) |
| p.Indent() |
| p.Print("default cache: %s" % self.default_cache) |
| p.Print("normal type cache: %s" % self.normal_type_cache) |
| p.Dedent() |
| p.Print("}") |
| |
| |
| class Code(HeapObject): |
| CODE_ALIGNMENT_MASK = (1 << 5) - 1 |
| |
| def InstructionSizeOffset(self): |
| return self.heap.PointerSize() |
| |
| @staticmethod |
| def HeaderSize(heap): |
| return (heap.PointerSize() + heap.IntSize() + \ |
| 4 * heap.PointerSize() + 3 * heap.IntSize() + \ |
| Code.CODE_ALIGNMENT_MASK) & ~Code.CODE_ALIGNMENT_MASK |
| |
| def __init__(self, heap, map, address): |
| HeapObject.__init__(self, heap, map, address) |
| self.entry = self.address + Code.HeaderSize(heap) |
| self.instruction_size = \ |
| heap.reader.ReadU32(self.address + self.InstructionSizeOffset()) |
| |
| def Print(self, p): |
| lines = self.heap.reader.GetDisasmLines(self.entry, self.instruction_size) |
| p.Print("Code(%s) {" % self.heap.reader.FormatIntPtr(self.address)) |
| p.Indent() |
| p.Print("instruction_size: %d" % self.instruction_size) |
| p.PrintLines(self._FormatLine(line) for line in lines) |
| p.Dedent() |
| p.Print("}") |
| |
| def _FormatLine(self, line): |
| return FormatDisasmLine(self.entry, self.heap, line) |
| |
| |
| class V8Heap(object): |
| CLASS_MAP = { |
| "SYMBOL_TYPE": SeqString, |
| "ONE_BYTE_SYMBOL_TYPE": SeqString, |
| "CONS_SYMBOL_TYPE": ConsString, |
| "CONS_ONE_BYTE_SYMBOL_TYPE": ConsString, |
| "EXTERNAL_SYMBOL_TYPE": ExternalString, |
| "EXTERNAL_SYMBOL_WITH_ONE_BYTE_DATA_TYPE": ExternalString, |
| "EXTERNAL_ONE_BYTE_SYMBOL_TYPE": ExternalString, |
| "SHORT_EXTERNAL_SYMBOL_TYPE": ExternalString, |
| "SHORT_EXTERNAL_SYMBOL_WITH_ONE_BYTE_DATA_TYPE": ExternalString, |
| "SHORT_EXTERNAL_ONE_BYTE_SYMBOL_TYPE": ExternalString, |
| "STRING_TYPE": SeqString, |
| "ONE_BYTE_STRING_TYPE": SeqString, |
| "CONS_STRING_TYPE": ConsString, |
| "CONS_ONE_BYTE_STRING_TYPE": ConsString, |
| "EXTERNAL_STRING_TYPE": ExternalString, |
| "EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE": ExternalString, |
| "EXTERNAL_ONE_BYTE_STRING_TYPE": ExternalString, |
| "MAP_TYPE": Map, |
| "ODDBALL_TYPE": Oddball, |
| "FIXED_ARRAY_TYPE": FixedArray, |
| "HASH_TABLE_TYPE": FixedArray, |
| "JS_FUNCTION_TYPE": JSFunction, |
| "SHARED_FUNCTION_INFO_TYPE": SharedFunctionInfo, |
| "SCRIPT_TYPE": Script, |
| "CODE_CACHE_TYPE": CodeCache, |
| "CODE_TYPE": Code, |
| } |
| |
| def __init__(self, reader, stack_map): |
| self.reader = reader |
| self.stack_map = stack_map |
| self.objects = {} |
| |
| def FindObjectOrSmi(self, tagged_address): |
| if self.IsSmi(tagged_address): return self.SmiUntag(tagged_address) |
| return self.FindObject(tagged_address) |
| |
| def FindObject(self, tagged_address): |
| if tagged_address in self.objects: |
| return self.objects[tagged_address] |
| if not self.IsTaggedObjectAddress(tagged_address): return None |
| address = tagged_address - 1 |
| if not self.reader.IsValidAddress(address): return None |
| map_tagged_address = self.reader.ReadUIntPtr(address) |
| if tagged_address == map_tagged_address: |
| # Meta map? |
| meta_map = Map(self, None, address) |
| instance_type_name = INSTANCE_TYPES.get(meta_map.instance_type) |
| if instance_type_name != "MAP_TYPE": return None |
| meta_map.map = meta_map |
| object = meta_map |
| else: |
| map = self.FindMap(map_tagged_address) |
| if map is None: return None |
| instance_type_name = INSTANCE_TYPES.get(map.instance_type) |
| if instance_type_name is None: return None |
| cls = V8Heap.CLASS_MAP.get(instance_type_name, HeapObject) |
| object = cls(self, map, address) |
| self.objects[tagged_address] = object |
| return object |
| |
| def FindMap(self, tagged_address): |
| address = self.FindMapAddress(tagged_address) |
| if not address: return None |
| object = Map(self, None, address) |
| return object |
| |
| def FindMapAddress(self, tagged_address): |
| if not self.IsTaggedMapAddress(tagged_address): return None |
| address = tagged_address - 1 |
| if not self.reader.IsValidAddress(address): return None |
| return address |
| |
| def IntSize(self): |
| return 4 |
| |
| def PointerSize(self): |
| return self.reader.PointerSize() |
| |
| def ObjectAlignmentMask(self): |
| return self.PointerSize() - 1 |
| |
| def IsTaggedObjectAddress(self, address): |
| return (address & self.ObjectAlignmentMask()) == 1 |
| |
| def IsValidTaggedObjectAddress(self, address): |
| if not self.IsTaggedObjectAddress(address): return False |
| return self.reader.IsValidAddress(address) |
| |
| def IsTaggedMapAddress(self, address): |
| return (address & self.MapAlignmentMask()) == 1 |
| |
| def MapAlignmentMask(self): |
| if self.reader.arch == MD_CPU_ARCHITECTURE_AMD64: |
| return (1 << 4) - 1 |
| elif self.reader.arch == MD_CPU_ARCHITECTURE_ARM: |
| return (1 << 4) - 1 |
| elif self.reader.arch == MD_CPU_ARCHITECTURE_ARM64: |
| return (1 << 4) - 1 |
| elif self.reader.arch == MD_CPU_ARCHITECTURE_X86: |
| return (1 << 5) - 1 |
| |
| def PageAlignmentMask(self): |
| return (1 << 19) - 1 |
| |
| def IsTaggedAddress(self, address): |
| return (address & self.ObjectAlignmentMask()) == 1 |
| |
| def IsSmi(self, tagged_address): |
| if self.reader.Is64(): |
| return (tagged_address & 0xFFFFFFFF) == 0 |
| return not self.IsTaggedAddress(tagged_address) |
| |
| def SmiUntag(self, tagged_address): |
| if self.reader.Is64(): return tagged_address >> 32 |
| return tagged_address >> 1 |
| |
| def AddressTypeMarker(self, address): |
| if not self.reader.IsValidAddress(address): return " " |
| if self.reader.IsExceptionStackAddress(address): return "S" |
| if self.reader.IsModuleAddress(address): return "C" |
| if self.IsTaggedAddress(address): |
| # Cannot have an tagged pointer into the stack |
| if self.reader.IsAnyExceptionStackAddress(address): return "s" |
| return "T" |
| return "*" |
| |
| def FormatIntPtr(self, address): |
| marker = self.AddressTypeMarker(address) |
| address = self.reader.FormatIntPtr(address) |
| if marker == " ": return address |
| return "%s %s" % (address, marker) |
| |
| def RelativeOffset(self, slot, address): |
| if not self.reader.IsValidAlignedAddress(slot): return None |
| if self.IsTaggedObjectAddress(address): |
| address -= 1 |
| if not self.reader.IsValidAlignedAddress(address): return None |
| offset = (address - slot) / self.PointerSize() |
| |
| lower_limit = -32 |
| upper_limit = 128 |
| if self.reader.IsExceptionStackAddress(address): |
| upper_limit = 0xFFFFFF |
| |
| if offset < lower_limit or upper_limit < offset: return None |
| target_address = self.reader.ReadUIntPtr(address) |
| return "[%+02d]=%s %s" % (offset, self.reader.FormatIntPtr(target_address), |
| self.AddressTypeMarker(target_address)) |
| |
| def FindObjectPointers(self, start=0, end=0): |
| objects = set() |
| def find_object_in_region(reader, start, size, location): |
| for slot in range(start, start+size, self.reader.PointerSize()): |
| if not self.reader.IsValidAddress(slot): break |
| # Collect only tagged pointers (object) to tagged pointers (map) |
| tagged_address = self.reader.ReadUIntPtr(slot) |
| if not self.IsValidTaggedObjectAddress(tagged_address): continue |
| map_address = self.reader.ReadUIntPtr(tagged_address - 1) |
| if not self.IsTaggedMapAddress(map_address): continue |
| objects.add(tagged_address) |
| |
| if not start and not end: |
| self.reader.ForEachMemoryRegion(find_object_in_region) |
| else: |
| find_object_in_region(self.reader, start, end-start, None) |
| |
| return objects |
| |
| class KnownObject(HeapObject): |
| def __init__(self, heap, known_name): |
| HeapObject.__init__(self, heap, None, None) |
| self.known_name = known_name |
| |
| def __str__(self): |
| return "<%s>" % self.known_name |
| |
| |
| class KnownMap(HeapObject): |
| def __init__(self, heap, known_name, instance_type): |
| HeapObject.__init__(self, heap, None, None) |
| self.instance_type = instance_type |
| self.known_name = known_name |
| |
| def __str__(self): |
| return "<%s>" % self.known_name |
| |
| |
| COMMENT_RE = re.compile(r"^C (0x[0-9a-fA-F]+) (.*)$") |
| PAGEADDRESS_RE = re.compile( |
| r"^P (mappage|oldpage) (0x[0-9a-fA-F]+)$") |
| |
| |
| class InspectionInfo(object): |
| def __init__(self, minidump_name, reader): |
| self.comment_file = minidump_name + ".comments" |
| self.address_comments = {} |
| self.page_address = {} |
| if os.path.exists(self.comment_file): |
| with open(self.comment_file, "r") as f: |
| lines = f.readlines() |
| f.close() |
| |
| for l in lines: |
| m = COMMENT_RE.match(l) |
| if m: |
| self.address_comments[int(m.group(1), 0)] = m.group(2) |
| m = PAGEADDRESS_RE.match(l) |
| if m: |
| self.page_address[m.group(1)] = int(m.group(2), 0) |
| self.reader = reader |
| self.styles = {} |
| self.color_addresses() |
| return |
| |
| def get_page_address(self, page_kind): |
| return self.page_address.get(page_kind, 0) |
| |
| def save_page_address(self, page_kind, address): |
| with open(self.comment_file, "a") as f: |
| f.write("P %s 0x%x\n" % (page_kind, address)) |
| f.close() |
| |
| def color_addresses(self): |
| # Color all stack addresses. |
| exception_thread = self.reader.thread_map[self.reader.exception.thread_id] |
| stack_top = self.reader.ExceptionSP() |
| stack_bottom = exception_thread.stack.start + \ |
| exception_thread.stack.memory.data_size |
| frame_pointer = self.reader.ExceptionFP() |
| self.styles[frame_pointer] = "frame" |
| for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()): |
| # stack address |
| self.styles[slot] = "sa" |
| for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()): |
| maybe_address = self.reader.ReadUIntPtr(slot) |
| # stack value |
| self.styles[maybe_address] = "sv" |
| if slot == frame_pointer: |
| self.styles[slot] = "frame" |
| frame_pointer = maybe_address |
| self.styles[self.reader.ExceptionIP()] = "pc" |
| |
| def get_style_class(self, address): |
| return self.styles.get(address, None) |
| |
| def get_style_class_string(self, address): |
| style = self.get_style_class(address) |
| if style != None: |
| return " class=%s " % style |
| else: |
| return "" |
| |
| def set_comment(self, address, comment): |
| self.address_comments[address] = comment |
| with open(self.comment_file, "a") as f: |
| f.write("C 0x%x %s\n" % (address, comment)) |
| f.close() |
| |
| def get_comment(self, address): |
| return self.address_comments.get(address, "") |
| |
| |
| class InspectionPadawan(object): |
| """The padawan can improve annotations by sensing well-known objects.""" |
| def __init__(self, reader, heap): |
| self.reader = reader |
| self.heap = heap |
| self.known_first_map_page = 0 |
| self.known_first_old_page = 0 |
| self.context = None |
| |
| def __getattr__(self, name): |
| """An InspectionPadawan can be used instead of V8Heap, even though |
| it does not inherit from V8Heap (aka. mixin).""" |
| return getattr(self.heap, name) |
| |
| def GetPageOffset(self, tagged_address): |
| return tagged_address & self.heap.PageAlignmentMask() |
| |
| def IsInKnownMapSpace(self, tagged_address): |
| page_address = tagged_address & ~self.heap.PageAlignmentMask() |
| return page_address == self.known_first_map_page |
| |
| def IsInKnownOldSpace(self, tagged_address): |
| page_address = tagged_address & ~self.heap.PageAlignmentMask() |
| return page_address == self.known_first_old_page |
| |
| def ContainingKnownOldSpaceName(self, tagged_address): |
| page_address = tagged_address & ~self.heap.PageAlignmentMask() |
| if page_address == self.known_first_old_page: return "OLD_SPACE" |
| return None |
| |
| def FrameMarkerName(self, value): |
| # The frame marker is Smi-tagged but not Smi encoded and 0 is not a valid |
| # frame type. |
| value = (value >> 1) - 1 |
| if 0 <= value < len(FRAME_MARKERS): |
| return "Possibly %s frame marker" % FRAME_MARKERS[value] |
| return None |
| |
| def IsFrameMarker(self, slot, address): |
| if not slot: return False |
| # Frame markers only occur directly after a frame pointer and only on the |
| # stack. |
| if not self.reader.IsExceptionStackAddress(slot): return False |
| next_slot = slot + self.reader.PointerSize() |
| if not self.reader.IsValidAddress(next_slot): return False |
| next_address = self.reader.ReadUIntPtr(next_slot) |
| return self.reader.IsExceptionStackAddress(next_address) |
| |
| def FormatSmi(self, address): |
| value = self.heap.SmiUntag(address) |
| # On 32-bit systems almost everything looks like a Smi. |
| if not self.reader.Is64() or value == 0: return None |
| return "Smi(%d)" % value |
| |
| def SenseObject(self, address, slot=None): |
| if self.IsFrameMarker(slot, address): |
| return self.FrameMarkerName(address) |
| if self.heap.IsSmi(address): |
| return self.FormatSmi(address) |
| if not self.heap.IsTaggedAddress(address): return None |
| tagged_address = address |
| if self.IsInKnownOldSpace(tagged_address): |
| offset = self.GetPageOffset(tagged_address) |
| lookup_key = (self.ContainingKnownOldSpaceName(tagged_address), offset) |
| known_obj_name = KNOWN_OBJECTS.get(lookup_key) |
| if known_obj_name: |
| return KnownObject(self, known_obj_name) |
| if self.IsInKnownMapSpace(tagged_address): |
| known_map = self.SenseMap(tagged_address) |
| if known_map: |
| return known_map |
| found_obj = self.heap.FindObject(tagged_address) |
| if found_obj: return found_obj |
| address = tagged_address - 1 |
| if self.reader.IsValidAddress(address): |
| map_tagged_address = self.reader.ReadUIntPtr(address) |
| map = self.SenseMap(map_tagged_address) |
| if map is None: return None |
| instance_type_name = INSTANCE_TYPES.get(map.instance_type) |
| if instance_type_name is None: return None |
| cls = V8Heap.CLASS_MAP.get(instance_type_name, HeapObject) |
| return cls(self, map, address) |
| return None |
| |
| def SenseMap(self, tagged_address): |
| if self.IsInKnownMapSpace(tagged_address): |
| offset = self.GetPageOffset(tagged_address) |
| known_map_info = KNOWN_MAPS.get(offset) |
| if known_map_info: |
| known_map_type, known_map_name = known_map_info |
| return KnownMap(self, known_map_name, known_map_type) |
| found_map = self.heap.FindMap(tagged_address) |
| if found_map: return found_map |
| return None |
| |
| def FindObjectOrSmi(self, tagged_address): |
| """When used as a mixin in place of V8Heap.""" |
| found_obj = self.SenseObject(tagged_address) |
| if found_obj: return found_obj |
| if self.IsSmi(tagged_address): |
| return self.FormatSmi(tagged_address) |
| else: |
| return "Unknown(%s)" % self.reader.FormatIntPtr(tagged_address) |
| |
| def FindObject(self, tagged_address): |
| """When used as a mixin in place of V8Heap.""" |
| raise NotImplementedError |
| |
| def FindMap(self, tagged_address): |
| """When used as a mixin in place of V8Heap.""" |
| raise NotImplementedError |
| |
| def PrintKnowledge(self): |
| print " known_first_map_page = %s\n"\ |
| " known_first_old_page = %s" % ( |
| self.reader.FormatIntPtr(self.known_first_map_page), |
| self.reader.FormatIntPtr(self.known_first_old_page)) |
| |
| def FindFirstAsciiString(self, start, end=None, min_length=32): |
| """ Walk the memory until we find a large string """ |
| if not end: end = start + 64 |
| for slot in xrange(start, end): |
| if not self.reader.IsValidAddress(slot): break |
| message = self.reader.ReadAsciiString(slot) |
| if len(message) > min_length: |
| return (slot, message) |
| return (None,None) |
| |
| def PrintStackTraceMessage(self, start=None, print_message=True): |
| """ |
| Try to print a possible message from PushStackTraceAndDie. |
| Returns the first address where the normal stack starts again. |
| """ |
| # Only look at the first 1k words on the stack |
| ptr_size = self.reader.PointerSize() |
| if start is None: |
| start = self.reader.ExceptionSP() |
| if not self.reader.IsValidAddress(start): return start |
| end = start + ptr_size * 1024 * 4 |
| message_start = 0 |
| magic1 = None |
| for slot in xrange(start, end, ptr_size): |
| if not self.reader.IsValidAddress(slot + ptr_size): break |
| magic1 = self.reader.ReadUIntPtr(slot) |
| magic2 = self.reader.ReadUIntPtr(slot + ptr_size) |
| pair = (magic1 & 0xFFFFFFFF, magic2 & 0xFFFFFFFF) |
| if pair in MAGIC_MARKER_PAIRS: |
| message_slot = slot + ptr_size * 4 |
| message_start = self.reader.ReadUIntPtr(message_slot) |
| break |
| if message_start == 0: |
| """ |
| On Mac we don't always get proper magic markers, so just try printing |
| the first long ascii string found on the stack. |
| """ |
| magic1 = None |
| magic2 = None |
| message_start, message = self.FindFirstAsciiString(start, end, 128) |
| if message_start is None: return start |
| else: |
| message = self.reader.ReadAsciiString(message_start) |
| stack_start = message_start + len(message) + 1 |
| # Make sure the address is word aligned |
| stack_start = stack_start - (stack_start % ptr_size) |
| if magic1 is None: |
| print "Stack Message:" |
| print " message start: %s" % self.heap.FormatIntPtr(message_start) |
| print " stack_start: %s" % self.heap.FormatIntPtr(stack_start ) |
| else: |
| ptr1 = self.reader.ReadUIntPtr(slot + ptr_size * 2) |
| ptr2 = self.reader.ReadUIntPtr(slot + ptr_size * 3) |
| print "Stack Message:" |
| print " magic1: %s" % self.heap.FormatIntPtr(magic1) |
| print " magic2: %s" % self.heap.FormatIntPtr(magic2) |
| print " ptr1: %s" % self.heap.FormatIntPtr(ptr1) |
| print " ptr2: %s" % self.heap.FormatIntPtr(ptr2) |
| print " message start: %s" % self.heap.FormatIntPtr(message_start) |
| print " stack_start: %s" % self.heap.FormatIntPtr(stack_start ) |
| print "" |
| if not print_message: |
| print " Use `dsa` to print the message with annotated addresses." |
| print "" |
| return stack_start |
| # Annotate all addresses in the dumped message |
| prog = re.compile("[0-9a-fA-F]{%s}" % ptr_size*2) |
| addresses = list(set(prog.findall(message))) |
| for i in range(len(addresses)): |
| address_org = addresses[i] |
| address = self.heap.FormatIntPtr(int(address_org, 16)) |
| if address_org != address: |
| message = message.replace(address_org, address) |
| print "Message:" |
| print "="*80 |
| print message |
| print "="*80 |
| print "" |
| return stack_start |
| |
| def TryInferFramePointer(self, slot, address): |
| """ Assume we have a framepointer if we find 4 consecutive links """ |
| for i in range(0, 4): |
| if not self.reader.IsExceptionStackAddress(address): return 0 |
| next_address = self.reader.ReadUIntPtr(address) |
| if next_address == address: return 0 |
| address = next_address |
| return slot |
| |
| def TryInferContext(self, address): |
| if self.context: return |
| ptr_size = self.reader.PointerSize() |
| possible_context = dict() |
| count = 0 |
| while self.reader.IsExceptionStackAddress(address): |
| prev_addr = self.reader.ReadUIntPtr(address-ptr_size) |
| if self.heap.IsTaggedObjectAddress(prev_addr): |
| if prev_addr in possible_context: |
| possible_context[prev_addr] += 1 |
| else: |
| possible_context[prev_addr] = 1 |
| address = self.reader.ReadUIntPtr(address) |
| count += 1 |
| if count <= 5 or len(possible_context) == 0: return |
| # Find entry with highest count |
| possible_context = possible_context.items() |
| possible_context.sort(key=lambda pair: pair[1]) |
| address,count = possible_context[-1] |
| if count <= 4: return |
| self.context = address |
| |
| def InterpretMemory(self, start, end): |
| # On 64 bit we omit frame pointers, so we have to do some more guesswork. |
| frame_pointer = 0 |
| if not self.reader.Is64(): |
| frame_pointer = self.reader.ExceptionFP() |
| # Follow the framepointer into the address range |
| while frame_pointer and frame_pointer < start: |
| frame_pointer = self.reader.ReadUIntPtr(frame_pointer) |
| if not self.reader.IsExceptionStackAddress(frame_pointer) or \ |
| not frame_pointer: |
| frame_pointer = 0 |
| break |
| in_oom_dump_area = False |
| is_stack = self.reader.IsExceptionStackAddress(start) |
| free_space_end = 0 |
| ptr_size = self.reader.PointerSize() |
| |
| for slot in xrange(start, end, ptr_size): |
| if not self.reader.IsValidAddress(slot): |
| print "%s: Address is not contained within the minidump!" % slot |
| return |
| maybe_address = self.reader.ReadUIntPtr(slot) |
| address_info = [] |
| # Mark continuous free space objects |
| if slot == free_space_end: |
| address_info.append("+") |
| elif slot <= free_space_end: |
| address_info.append("|") |
| else: |
| free_space_end = 0 |
| |
| heap_object = self.SenseObject(maybe_address, slot) |
| if heap_object: |
| # Detect Free-space ranges |
| if isinstance(heap_object, KnownMap) and \ |
| heap_object.known_name == "FreeSpaceMap": |
| # The free-space length is is stored as a Smi in the next slot. |
| length = self.reader.ReadUIntPtr(slot + ptr_size) |
| if self.heap.IsSmi(length): |
| length = self.heap.SmiUntag(length) |
| free_space_end = slot + length - ptr_size |
| address_info.append(str(heap_object)) |
| relative_offset = self.heap.RelativeOffset(slot, maybe_address) |
| if relative_offset: |
| address_info.append(relative_offset) |
| if maybe_address == self.context: |
| address_info.append("CONTEXT") |
| |
| maybe_address_contents = None |
| if is_stack: |
| if self.reader.IsExceptionStackAddress(maybe_address): |
| maybe_address_contents = \ |
| self.reader.ReadUIntPtr(maybe_address) & 0xFFFFFFFF |
| if maybe_address_contents == 0xdecade00: |
| in_oom_dump_area = True |
| if frame_pointer == 0: |
| frame_pointer = self.TryInferFramePointer(slot, maybe_address) |
| if frame_pointer != 0: |
| self.TryInferContext(slot) |
| maybe_symbol = self.reader.FindSymbol(maybe_address) |
| if in_oom_dump_area: |
| if maybe_address_contents == 0xdecade00: |
| address_info = ["<==== HeapStats start marker"] |
| elif maybe_address_contents == 0xdecade01: |
| address_info = ["<==== HeapStats end marker"] |
| elif maybe_address_contents is not None: |
| address_info = [" %d (%d Mbytes)" % (maybe_address_contents, |
| maybe_address_contents >> 20)] |
| if slot == frame_pointer: |
| if not self.reader.IsExceptionStackAddress(maybe_address): |
| address_info.append("<==== BAD frame pointer") |
| frame_pointer = 0 |
| else: |
| address_info.append("<==== Frame pointer") |
| frame_pointer = maybe_address |
| address_type_marker = self.heap.AddressTypeMarker(maybe_address) |
| string_value = self.reader.ReadAsciiPtr(slot) |
| print "%s: %s %s %s %s" % (self.reader.FormatIntPtr(slot), |
| self.reader.FormatIntPtr(maybe_address), |
| address_type_marker, |
| string_value, |
| ' | '.join(address_info)) |
| if maybe_address_contents == 0xdecade01: |
| in_oom_dump_area = False |
| heap_object = self.heap.FindObject(maybe_address) |
| if heap_object: |
| heap_object.Print(Printer()) |
| print "" |
| |
| WEB_HEADER = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta content="text/html; charset=utf-8" http-equiv="content-type"> |
| <style media="screen" type="text/css"> |
| |
| .code { |
| font-family: monospace; |
| } |
| |
| .dmptable { |
| border-collapse : collapse; |
| border-spacing : 0px; |
| table-layout: fixed; |
| } |
| |
| .codedump { |
| border-collapse : collapse; |
| border-spacing : 0px; |
| table-layout: fixed; |
| } |
| |
| .addrcomments { |
| border : 0px; |
| } |
| |
| .register { |
| padding-right : 1em; |
| } |
| |
| .header { |
| clear : both; |
| } |
| |
| .header .navigation { |
| float : left; |
| } |
| |
| .header .dumpname { |
| float : right; |
| } |
| |
| tr.highlight-line { |
| background-color : yellow; |
| } |
| |
| .highlight { |
| background-color : magenta; |
| } |
| |
| tr.inexact-highlight-line { |
| background-color : pink; |
| } |
| |
| input { |
| background-color: inherit; |
| border: 1px solid LightGray; |
| } |
| |
| .dumpcomments { |
| border : 1px solid LightGray; |
| width : 32em; |
| } |
| |
| .regions td { |
| padding:0 15px 0 15px; |
| } |
| |
| .stackframe td { |
| background-color : cyan; |
| } |
| |
| .stackaddress, .sa { |
| background-color : LightGray; |
| } |
| |
| .stackval, .sv { |
| background-color : LightCyan; |
| } |
| |
| .frame { |
| background-color : cyan; |
| } |
| |
| .commentinput, .ci { |
| width : 20em; |
| } |
| |
| /* a.nodump */ |
| a.nd:visited { |
| color : black; |
| text-decoration : none; |
| } |
| |
| a.nd:link { |
| color : black; |
| text-decoration : none; |
| } |
| |
| a:visited { |
| color : blueviolet; |
| } |
| |
| a:link { |
| color : blue; |
| } |
| |
| .disasmcomment { |
| color : DarkGreen; |
| } |
| |
| </style> |
| |
| <script type="application/javascript"> |
| |
| var address_str = "address-"; |
| var address_len = address_str.length; |
| |
| function comment() { |
| var s = event.srcElement.id; |
| var index = s.indexOf(address_str); |
| if (index >= 0) { |
| send_comment(s.substring(index + address_len), event.srcElement.value); |
| } |
| } |
| var c = comment; |
| |
| function send_comment(address, comment) { |
| xmlhttp = new XMLHttpRequest(); |
| address = encodeURIComponent(address) |
| comment = encodeURIComponent(comment) |
| xmlhttp.open("GET", |
| "setcomment?%(query_dump)s&address=" + address + |
| "&comment=" + comment, true); |
| xmlhttp.send(); |
| } |
| |
| var dump_str = "dump-"; |
| var dump_len = dump_str.length; |
| |
| function dump_comment() { |
| var s = event.srcElement.id; |
| var index = s.indexOf(dump_str); |
| if (index >= 0) { |
| send_dump_desc(s.substring(index + dump_len), event.srcElement.value); |
| } |
| } |
| |
| function send_dump_desc(name, desc) { |
| xmlhttp = new XMLHttpRequest(); |
| name = encodeURIComponent(name) |
| desc = encodeURIComponent(desc) |
| xmlhttp.open("GET", |
| "setdumpdesc?dump=" + name + |
| "&description=" + desc, true); |
| xmlhttp.send(); |
| } |
| |
| function onpage(kind, address) { |
| xmlhttp = new XMLHttpRequest(); |
| kind = encodeURIComponent(kind) |
| address = encodeURIComponent(address) |
| xmlhttp.onreadystatechange = function() { |
| if (xmlhttp.readyState==4 && xmlhttp.status==200) { |
| location.reload(true) |
| } |
| }; |
| xmlhttp.open("GET", |
| "setpageaddress?%(query_dump)s&kind=" + kind + |
| "&address=" + address); |
| xmlhttp.send(); |
| } |
| |
| </script> |
| |
| <title>Dump %(dump_name)s</title> |
| </head> |
| |
| <body> |
| <div class="header"> |
| <form class="navigation" action="search.html"> |
| <a href="summary.html?%(query_dump)s">Context info</a> |
| <a href="info.html?%(query_dump)s">Dump info</a> |
| <a href="modules.html?%(query_dump)s">Modules</a> |
| |
| <input type="search" name="val"> |
| <input type="submit" name="search" value="Search"> |
| <input type="hidden" name="dump" value="%(dump_name)s"> |
| </form> |
| <form class="navigation" action="disasm.html#highlight"> |
| |
| |
| |
| <input type="search" name="val"> |
| <input type="submit" name="disasm" value="Disasm"> |
| |
| |
| |
| <a href="dumps.html">Dumps...</a> |
| </form> |
| </div> |
| <br> |
| <hr> |
| """ |
| |
| |
| WEB_FOOTER = """ |
| </body> |
| </html> |
| """ |
| |
| |
| class WebParameterError(Exception): |
| def __init__(self, message): |
| Exception.__init__(self, message) |
| |
| |
| class InspectionWebHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
| def formatter(self, query_components): |
| name = query_components.get("dump", [None])[0] |
| return self.server.get_dump_formatter(name) |
| |
| def send_success_html_headers(self): |
| self.send_response(200) |
| self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") |
| self.send_header("Pragma", "no-cache") |
| self.send_header("Expires", "0") |
| self.send_header('Content-type','text/html') |
| self.end_headers() |
| return |
| |
| def do_GET(self): |
| try: |
| parsedurl = urlparse.urlparse(self.path) |
| query_components = urlparse.parse_qs(parsedurl.query) |
| if parsedurl.path == "/dumps.html": |
| self.send_success_html_headers() |
| out_buffer = StringIO.StringIO() |
| self.server.output_dumps(out_buffer) |
| self.wfile.write(out_buffer.getvalue()) |
| elif parsedurl.path == "/summary.html": |
| self.send_success_html_headers() |
| out_buffer = StringIO.StringIO() |
| self.formatter(query_components).output_summary(out_buffer) |
| self.wfile.write(out_buffer.getvalue()) |
| elif parsedurl.path == "/info.html": |
| self.send_success_html_headers() |
| out_buffer = StringIO.StringIO() |
| self.formatter(query_components).output_info(out_buffer) |
| self.wfile.write(out_buffer.getvalue()) |
| elif parsedurl.path == "/modules.html": |
| self.send_success_html_headers() |
| out_buffer = StringIO.StringIO() |
| self.formatter(query_components).output_modules(out_buffer) |
| self.wfile.write(out_buffer.getvalue()) |
| elif parsedurl.path == "/search.html" or parsedurl.path == "/s": |
| address = query_components.get("val", []) |
| if len(address) != 1: |
| self.send_error(404, "Invalid params") |
| return |
| self.send_success_html_headers() |
| out_buffer = StringIO.StringIO() |
| self.formatter(query_components).output_search_res( |
| out_buffer, address[0]) |
| self.wfile.write(out_buffer.getvalue()) |
| elif parsedurl.path == "/disasm.html": |
| address = query_components.get("val", []) |
| exact = query_components.get("exact", ["on"]) |
| if len(address) != 1: |
| self.send_error(404, "Invalid params") |
| return |
| self.send_success_html_headers() |
| out_buffer = StringIO.StringIO() |
| self.formatter(query_components).output_disasm( |
| out_buffer, address[0], exact[0]) |
| self.wfile.write(out_buffer.getvalue()) |
| elif parsedurl.path == "/data.html": |
| address = query_components.get("val", []) |
| datakind = query_components.get("type", ["address"]) |
| if len(address) == 1 and len(datakind) == 1: |
| self.send_success_html_headers() |
| out_buffer = StringIO.StringIO() |
| self.formatter(query_components).output_data( |
| out_buffer, address[0], datakind[0]) |
| self.wfile.write(out_buffer.getvalue()) |
| else: |
| self.send_error(404,'Invalid params') |
| elif parsedurl.path == "/setdumpdesc": |
| name = query_components.get("dump", [""]) |
| description = query_components.get("description", [""]) |
| if len(name) == 1 and len(description) == 1: |
| name = name[0] |
| description = description[0] |
| if self.server.set_dump_desc(name, description): |
| self.send_success_html_headers() |
| self.wfile.write("OK") |
| return |
| self.send_error(404,'Invalid params') |
| elif parsedurl.path == "/setcomment": |
| address = query_components.get("address", []) |
| comment = query_components.get("comment", [""]) |
| if len(address) == 1 and len(comment) == 1: |
| address = address[0] |
| comment = comment[0] |
| self.formatter(query_components).set_comment(address, comment) |
| self.send_success_html_headers() |
| self.wfile.write("OK") |
| else: |
| self.send_error(404,'Invalid params') |
| elif parsedurl.path == "/setpageaddress": |
| kind = query_components.get("kind", []) |
| address = query_components.get("address", [""]) |
| if len(kind) == 1 and len(address) == 1: |
| kind = kind[0] |
| address = address[0] |
| self.formatter(query_components).set_page_address(kind, address) |
| self.send_success_html_headers() |
| self.wfile.write("OK") |
| else: |
| self.send_error(404,'Invalid params') |
| else: |
| self.send_error(404,'File Not Found: %s' % self.path) |
| |
| except IOError: |
| self.send_error(404,'File Not Found: %s' % self.path) |
| |
| except WebParameterError as e: |
| self.send_error(404, 'Web parameter error: %s' % e.message) |
| |
| |
| HTML_REG_FORMAT = "<span class=\"register\"><b>%s</b>: %s</span><br/>\n" |
| |
| |
| class InspectionWebFormatter(object): |
| CONTEXT_FULL = 0 |
| CONTEXT_SHORT = 1 |
| |
| def __init__(self, switches, minidump_name, http_server): |
| self.dumpfilename = os.path.split(minidump_name)[1] |
| self.encfilename = urllib.urlencode({ 'dump' : self.dumpfilename }) |
| self.reader = MinidumpReader(switches, minidump_name) |
| self.server = http_server |
| |
| # Set up the heap |
| exception_thread = self.reader.thread_map[self.reader.exception.thread_id] |
| stack_top = self.reader.ExceptionSP() |
| stack_bottom = exception_thread.stack.start + \ |
| exception_thread.stack.memory.data_size |
| stack_map = {self.reader.ExceptionIP(): -1} |
| for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()): |
| maybe_address = self.reader.ReadUIntPtr(slot) |
| if not maybe_address in stack_map: |
| stack_map[maybe_address] = slot |
| self.heap = V8Heap(self.reader, stack_map) |
| |
| self.padawan = InspectionPadawan(self.reader, self.heap) |
| self.comments = InspectionInfo(minidump_name, self.reader) |
| self.padawan.known_first_old_page = ( |
| self.comments.get_page_address("oldpage")) |
| self.padawan.known_first_map_page = ( |
| self.comments.get_page_address("mappage")) |
| |
| def set_comment(self, straddress, comment): |
| try: |
| address = int(straddress, 0) |
| self.comments.set_comment(address, comment) |
| except ValueError: |
| print "Invalid address" |
| |
| def set_page_address(self, kind, straddress): |
| try: |
| address = int(straddress, 0) |
| if kind == "oldpage": |
| self.padawan.known_first_old_page = address |
| elif kind == "mappage": |
| self.padawan.known_first_map_page = address |
| self.comments.save_page_address(kind, address) |
| except ValueError: |
| print "Invalid address" |
| |
| def td_from_address(self, f, address): |
| f.write("<td %s>" % self.comments.get_style_class_string(address)) |
| |
| def format_address(self, maybeaddress, straddress = None): |
| if maybeaddress is None: |
| return "not in dump" |
| else: |
| if straddress is None: |
| straddress = "0x" + self.reader.FormatIntPtr(maybeaddress) |
| style_class = "" |
| if not self.reader.IsValidAddress(maybeaddress): |
| style_class = "class=nd" |
| return ("<a %s href=s?%s&val=%s>%s</a>" % |
| (style_class, self.encfilename, straddress, straddress)) |
| |
| def output_header(self, f): |
| f.write(WEB_HEADER % |
| { "query_dump" : self.encfilename, |
| "dump_name" : cgi.escape(self.dumpfilename) }) |
| |
| def output_footer(self, f): |
| f.write(WEB_FOOTER) |
| |
| MAX_CONTEXT_STACK = 2048 |
| |
| def output_summary(self, f): |
| self.output_header(f) |
| f.write('<div class="code">') |
| self.output_context(f, InspectionWebFormatter.CONTEXT_SHORT) |
| self.output_disasm_pc(f) |
| |
| # Output stack |
| exception_thread = self.reader.thread_map[self.reader.exception.thread_id] |
| stack_top = self.reader.ExceptionSP() |
| stack_bottom = min(exception_thread.stack.start + \ |
| exception_thread.stack.memory.data_size, |
| stack_top + self.MAX_CONTEXT_STACK) |
| self.output_words(f, stack_top - 16, stack_bottom, stack_top, "Stack") |
| |
| f.write('</div>') |
| self.output_footer(f) |
| return |
| |
| def output_info(self, f): |
| self.output_header(f) |
| f.write("<h3>Dump info</h3>") |
| f.write("Description: ") |
| self.server.output_dump_desc_field(f, self.dumpfilename) |
| f.write("<br>") |
| f.write("Filename: ") |
| f.write("<span class=\"code\">%s</span><br>" % (self.dumpfilename)) |
| dt = datetime.datetime.fromtimestamp(self.reader.header.time_date_stampt) |
| f.write("Timestamp: %s<br>" % dt.strftime('%Y-%m-%d %H:%M:%S')) |
| self.output_context(f, InspectionWebFormatter.CONTEXT_FULL) |
| self.output_address_ranges(f) |
| self.output_footer(f) |
| return |
| |
| def output_address_ranges(self, f): |
| regions = {} |
| def print_region(_reader, start, size, _location): |
| regions[start] = size |
| self.reader.ForEachMemoryRegion(print_region) |
| f.write("<h3>Available memory regions</h3>") |
| f.write('<div class="code">') |
| f.write("<table class=\"regions\">") |
| f.write("<thead><tr>") |
| f.write("<th>Start address</th>") |
| f.write("<th>End address</th>") |
| f.write("<th>Number of bytes</th>") |
| f.write("</tr></thead>") |
| for start in sorted(regions): |
| size = regions[start] |
| f.write("<tr>") |
| f.write("<td>%s</td>" % self.format_address(start)) |
| f.write("<td> %s</td>" % self.format_address(start + size)) |
| f.write("<td> %d</td>" % size) |
| f.write("</tr>") |
| f.write("</table>") |
| f.write('</div>') |
| return |
| |
| def output_module_details(self, f, module): |
| f.write("<b>%s</b>" % GetModuleName(self.reader, module)) |
| file_version = GetVersionString(module.version_info.dwFileVersionMS, |
| module.version_info.dwFileVersionLS) |
| product_version = GetVersionString(module.version_info.dwProductVersionMS, |
| module.version_info.dwProductVersionLS) |
| f.write("<br> ") |
| f.write("base: %s" % self.reader.FormatIntPtr(module.base_of_image)) |
| f.write("<br> ") |
| f.write(" end: %s" % self.reader.FormatIntPtr(module.base_of_image + |
| module.size_of_image)) |
| f.write("<br> ") |
| f.write(" file version: %s" % file_version) |
| f.write("<br> ") |
| f.write(" product version: %s" % product_version) |
| f.write("<br> ") |
| time_date_stamp = datetime.datetime.fromtimestamp(module.time_date_stamp) |
| f.write(" timestamp: %s" % time_date_stamp) |
| f.write("<br>"); |
| |
| def output_modules(self, f): |
| self.output_header(f) |
| f.write('<div class="code">') |
| for module in self.reader.module_list.modules: |
| self.output_module_details(f, module) |
| f.write("</div>") |
| self.output_footer(f) |
| return |
| |
| def output_context(self, f, details): |
| exception_thread = self.reader.thread_map[self.reader.exception.thread_id] |
| f.write("<h3>Exception context</h3>") |
| f.write('<div class="code">') |
| f.write("Thread id: %d" % exception_thread.id) |
| f.write(" Exception code: %08X<br/>" % |
| self.reader.exception.exception.code) |
| if details == InspectionWebFormatter.CONTEXT_FULL: |
| if self.reader.exception.exception.parameter_count > 0: |
| f.write(" Exception parameters: ") |
| for i in xrange(0, self.reader.exception.exception.parameter_count): |
| f.write("%08x" % self.reader.exception.exception.information[i]) |
| f.write("<br><br>") |
| |
| for r in CONTEXT_FOR_ARCH[self.reader.arch]: |
| f.write(HTML_REG_FORMAT % |
| (r, self.format_address(self.reader.Register(r)))) |
| # TODO(vitalyr): decode eflags. |
| if self.reader.arch in [MD_CPU_ARCHITECTURE_ARM, MD_CPU_ARCHITECTURE_ARM64]: |
| f.write("<b>cpsr</b>: %s" % bin(self.reader.exception_context.cpsr)[2:]) |
| else: |
| f.write("<b>eflags</b>: %s" % |
| bin(self.reader.exception_context.eflags)[2:]) |
| f.write('</div>') |
| return |
| |
| def align_down(self, a, size): |
| alignment_correction = a % size |
| return a - alignment_correction |
| |
| def align_up(self, a, size): |
| alignment_correction = (size - 1) - ((a + size - 1) % size) |
| return a + alignment_correction |
| |
| def format_object(self, address): |
| heap_object = self.padawan.SenseObject(address) |
| return cgi.escape(str(heap_object or "")) |
| |
| def output_data(self, f, straddress, datakind): |
| try: |
| self.output_header(f) |
| address = int(straddress, 0) |
| if not self.reader.IsValidAddress(address): |
| f.write("<h3>Address 0x%x not found in the dump.</h3>" % address) |
| return |
| region = self.reader.FindRegion(address) |
| if datakind == "address": |
| self.output_words(f, region[0], region[0] + region[1], address, "Dump") |
| elif datakind == "ascii": |
| self.output_ascii(f, region[0], region[0] + region[1], address) |
| self.output_footer(f) |
| |
| except ValueError: |
| f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress) |
| return |
| |
| def output_words(self, f, start_address, end_address, |
| highlight_address, desc): |
| region = self.reader.FindRegion(highlight_address) |
| if region is None: |
| f.write("<h3>Address 0x%x not found in the dump.</h3>" % |
| (highlight_address)) |
| return |
| size = self.heap.PointerSize() |
| start_address = self.align_down(start_address, size) |
| low = self.align_down(region[0], size) |
| high = self.align_up(region[0] + region[1], size) |
| if start_address < low: |
| start_address = low |
| end_address = self.align_up(end_address, size) |
| if end_address > high: |
| end_address = high |
| |
| expand = "" |
| if start_address != low or end_address != high: |
| expand = ("(<a href=\"data.html?%s&val=0x%x#highlight\">" |
| " more..." |
| " </a>)" % |
| (self.encfilename, highlight_address)) |
| |
| f.write("<h3>%s 0x%x - 0x%x, " |
| "highlighting <a href=\"#highlight\">0x%x</a> %s</h3>" % |
| (desc, start_address, end_address, highlight_address, expand)) |
| f.write('<div class="code">') |
| f.write("<table class=codedump>") |
| |
| for j in xrange(0, end_address - start_address, size): |
| slot = start_address + j |
| heap_object = "" |
| maybe_address = None |
| end_region = region[0] + region[1] |
| if slot < region[0] or slot + size > end_region: |
| straddress = "0x" |
| for i in xrange(end_region, slot + size): |
| straddress += "??" |
| for i in reversed( |
| xrange(max(slot, region[0]), min(slot + size, end_region))): |
| straddress += "%02x" % self.reader.ReadU8(i) |
| for i in xrange(slot, region[0]): |
| straddress += "??" |
| else: |
| maybe_address = self.reader.ReadUIntPtr(slot) |
| straddress = self.format_address(maybe_address) |
| if maybe_address: |
| heap_object = self.format_object(maybe_address) |
| |
| address_fmt = "%s </td>" |
| if slot == highlight_address: |
| f.write("<tr class=highlight-line>") |
| address_fmt = "<a id=highlight></a>%s </td>" |
| elif slot < highlight_address and highlight_address < slot + size: |
| f.write("<tr class=inexact-highlight-line>") |
| address_fmt = "<a id=highlight></a>%s </td>" |
| else: |
| f.write("<tr>") |
| |
| f.write("<td>") |
| self.output_comment_box(f, "da-", slot) |
| f.write("</td>") |
| self.td_from_address(f, slot) |
| f.write(address_fmt % self.format_address(slot)) |
| self.td_from_address(f, maybe_address) |
| f.write(": %s </td>" % straddress) |
| f.write("<td>") |
| if maybe_address != None: |
| self.output_comment_box( |
| f, "sv-" + self.reader.FormatIntPtr(slot), maybe_address) |
| f.write("</td>") |
| f.write("<td>%s</td>" % (heap_object or '')) |
| f.write("</tr>") |
| f.write("</table>") |
| f.write("</div>") |
| return |
| |
| def output_ascii(self, f, start_address, end_address, highlight_address): |
| region = self.reader.FindRegion(highlight_address) |
| if region is None: |
| f.write("<h3>Address %x not found in the dump.</h3>" % |
| highlight_address) |
| return |
| if start_address < region[0]: |
| start_address = region[0] |
| if end_address > region[0] + region[1]: |
| end_address = region[0] + region[1] |
| |
| expand = "" |
| if start_address != region[0] or end_address != region[0] + region[1]: |
| link = ("data.html?%s&val=0x%x&type=ascii#highlight" % |
| (self.encfilename, highlight_address)) |
| expand = "(<a href=\"%s\">more...</a>)" % link |
| |
| f.write("<h3>ASCII dump 0x%x - 0x%x, highlighting 0x%x %s</h3>" % |
| (start_address, end_address, highlight_address, expand)) |
| |
| line_width = 64 |
| |
| f.write('<div class="code">') |
| |
| start = self.align_down(start_address, line_width) |
| |
| for i in xrange(end_address - start): |
| address = start + i |
| if address % 64 == 0: |
| if address != start: |
| f.write("<br>") |
| f.write("0x%08x: " % address) |
| if address < start_address: |
| f.write(" ") |
| else: |
| if address == highlight_address: |
| f.write("<span class=\"highlight\">") |
| code = self.reader.ReadU8(address) |
| if code < 127 and code >= 32: |
| f.write("&#") |
| f.write(str(code)) |
| f.write(";") |
| else: |
| f.write("·") |
| if address == highlight_address: |
| f.write("</span>") |
| f.write("</div>") |
| return |
| |
| def output_disasm(self, f, straddress, strexact): |
| try: |
| self.output_header(f) |
| address = int(straddress, 0) |
| if not self.reader.IsValidAddress(address): |
| f.write("<h3>Address 0x%x not found in the dump.</h3>" % address) |
| return |
| region = self.reader.FindRegion(address) |
| self.output_disasm_range( |
| f, region[0], region[0] + region[1], address, strexact == "on") |
| self.output_footer(f) |
| except ValueError: |
| f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress) |
| return |
| |
| def output_disasm_range( |
| self, f, start_address, end_address, highlight_address, exact): |
| region = self.reader.FindRegion(highlight_address) |
| if start_address < region[0]: |
| start_address = region[0] |
| if end_address > region[0] + region[1]: |
| end_address = region[0] + region[1] |
| count = end_address - start_address |
| lines = self.reader.GetDisasmLines(start_address, count) |
| found = False |
| if exact: |
| for line in lines: |
| if line[0] + start_address == highlight_address: |
| found = True |
| break |
| if not found: |
| start_address = highlight_address |
| count = end_address - start_address |
| lines = self.reader.GetDisasmLines(highlight_address, count) |
| expand = "" |
| if start_address != region[0] or end_address != region[0] + region[1]: |
| exactness = "" |
| if exact and not found and end_address == region[0] + region[1]: |
| exactness = "&exact=off" |
| expand = ("(<a href=\"disasm.html?%s%s" |
| "&val=0x%x#highlight\">more...</a>)" % |
| (self.encfilename, exactness, highlight_address)) |
| |
| f.write("<h3>Disassembling 0x%x - 0x%x, highlighting 0x%x %s</h3>" % |
| (start_address, end_address, highlight_address, expand)) |
| f.write('<div class="code">') |
| f.write("<table class=\"codedump\">"); |
| for i in xrange(len(lines)): |
| line = lines[i] |
| next_address = count |
| if i + 1 < len(lines): |
| next_line = lines[i + 1] |
| next_address = next_line[0] |
| self.format_disasm_line( |
| f, start_address, line, next_address, highlight_address) |
| f.write("</table>") |
| f.write("</div>") |
| return |
| |
| def annotate_disasm_addresses(self, line): |
| extra = [] |
| for m in ADDRESS_RE.finditer(line): |
| maybe_address = int(m.group(0), 16) |
| formatted_address = self.format_address(maybe_address, m.group(0)) |
| line = line.replace(m.group(0), formatted_address) |
| object_info = self.padawan.SenseObject(maybe_address) |
| if not object_info: |
| continue |
| extra.append(cgi.escape(str(object_info))) |
| if len(extra) == 0: |
| return line |
| return ("%s <span class=disasmcomment>;; %s</span>" % |
| (line, ", ".join(extra))) |
| |
| def format_disasm_line( |
| self, f, start, line, next_address, highlight_address): |
| line_address = start + line[0] |
| address_fmt = " <td>%s</td>" |
| if line_address == highlight_address: |
| f.write("<tr class=highlight-line>") |
| address_fmt = " <td><a id=highlight>%s</a></td>" |
| elif (line_address < highlight_address and |
| highlight_address < next_address + start): |
| f.write("<tr class=inexact-highlight-line>") |
| address_fmt = " <td><a id=highlight>%s</a></td>" |
| else: |
| f.write("<tr>") |
| num_bytes = next_address - line[0] |
| stack_slot = self.heap.stack_map.get(line_address) |
| marker = "" |
| if stack_slot: |
| marker = "=>" |
| |
| code = line[1] |
| |
| # Some disassemblers insert spaces between each byte, |
| # while some do not. |
| if code[2] == " ": |
| op_offset = 3 * num_bytes - 1 |
| else: |
| op_offset = 2 * num_bytes |
| |
| # Compute the actual call target which the disassembler is too stupid |
| # to figure out (it adds the call offset to the disassembly offset rather |
| # than the absolute instruction address). |
| if self.heap.reader.arch == MD_CPU_ARCHITECTURE_X86: |
| if code.startswith("e8"): |
| words = code.split() |
| if len(words) > 6 and words[5] == "call": |
| offset = int(words[4] + words[3] + words[2] + words[1], 16) |
| target = (line_address + offset + 5) & 0xFFFFFFFF |
| code = code.replace(words[6], "0x%08x" % target) |
| # TODO(jkummerow): port this hack to ARM and x64. |
| |
| opcodes = code[:op_offset] |
| code = self.annotate_disasm_addresses(code[op_offset:]) |
| f.write(" <td>") |
| self.output_comment_box(f, "codel-", line_address) |
| f.write("</td>") |
| f.write(address_fmt % marker) |
| f.write(" ") |
| self.td_from_address(f, line_address) |
| f.write(self.format_address(line_address)) |
| f.write(" (+0x%x)</td>" % line[0]) |
| f.write("<td>: %s </td>" % opcodes) |
| f.write("<td>%s</td>" % code) |
| f.write("</tr>") |
| |
| def output_comment_box(self, f, prefix, address): |
| comment = self.comments.get_comment(address) |
| value = "" |
| if comment: |
| value = " value=\"%s\"" % cgi.escape(comment) |
| f.write("<input type=text class=ci " |
| "id=%s-address-0x%s onchange=c()%s>" % |
| (prefix, |
| self.reader.FormatIntPtr(address), |
| value)) |
| |
| MAX_FOUND_RESULTS = 100 |
| |
| def output_find_results(self, f, results): |
| f.write("Addresses") |
| toomany = len(results) > self.MAX_FOUND_RESULTS |
| if toomany: |
| f.write("(found %i results, displaying only first %i)" % |
| (len(results), self.MAX_FOUND_RESULTS)) |
| f.write(": ") |
| results = sorted(results) |
| results = results[:min(len(results), self.MAX_FOUND_RESULTS)] |
| for address in results: |
| f.write("<span %s>%s</span>" % |
| (self.comments.get_style_class_string(address), |
| self.format_address(address))) |
| if toomany: |
| f.write("...") |
| |
| |
| def output_page_info(self, f, page_kind, page_address, my_page_address): |
| if my_page_address == page_address and page_address != 0: |
| f.write("Marked first %s page." % page_kind) |
| else: |
| f.write("<span id=\"%spage\" style=\"display:none\">" % page_kind) |
| f.write("Marked first %s page." % page_kind) |
| f.write("</span>\n") |
| f.write("<button onclick=\"onpage('%spage', '0x%x')\">" % |
| (page_kind, my_page_address)) |
| f.write("Mark as first %s page</button>" % page_kind) |
| return |
| |
| def output_search_res(self, f, straddress): |
| try: |
| self.output_header(f) |
| f.write("<h3>Search results for %s</h3>" % straddress) |
| |
| address = int(straddress, 0) |
| |
| f.write("Comment: ") |
| self.output_comment_box(f, "search-", address) |
| f.write("<br>") |
| |
| page_address = address & ~self.heap.PageAlignmentMask() |
| |
| f.write("Page info: ") |
| self.output_page_info(f, "old", self.padawan.known_first_old_page, \ |
| page_address) |
| self.output_page_info(f, "map", self.padawan.known_first_map_page, \ |
| page_address) |
| |
| if not self.reader.IsValidAddress(address): |
| f.write("<h3>The contents at address %s not found in the dump.</h3>" % \ |
| straddress) |
| else: |
| # Print as words |
| self.output_words(f, address - 8, address + 32, address, "Dump") |
| |
| # Print as ASCII |
| f.write("<hr>") |
| self.output_ascii(f, address, address + 256, address) |
| |
| # Print as code |
| f.write("<hr>") |
| self.output_disasm_range(f, address - 16, address + 16, address, True) |
| |
| aligned_res, unaligned_res = self.reader.FindWordList(address) |
| |
| if len(aligned_res) > 0: |
| f.write("<h3>Occurrences of 0x%x at aligned addresses</h3>" % |
| address) |
| self.output_find_results(f, aligned_res) |
| |
| if len(unaligned_res) > 0: |
| f.write("<h3>Occurrences of 0x%x at unaligned addresses</h3>" % \ |
| address) |
| self.output_find_results(f, unaligned_res) |
| |
| if len(aligned_res) + len(unaligned_res) == 0: |
| f.write("<h3>No occurrences of 0x%x found in the dump</h3>" % address) |
| |
| self.output_footer(f) |
| |
| except ValueError: |
| f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress) |
| return |
| |
| def output_disasm_pc(self, f): |
| address = self.reader.ExceptionIP() |
| if not self.reader.IsValidAddress(address): |
| return |
| self.output_disasm_range(f, address - 16, address + 16, address, True) |
| |
| |
| WEB_DUMPS_HEADER = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta content="text/html; charset=utf-8" http-equiv="content-type"> |
| <style media="screen" type="text/css"> |
| |
| .dumplist { |
| border-collapse : collapse; |
| border-spacing : 0px; |
| font-family: monospace; |
| } |
| |
| .dumpcomments { |
| border : 1px solid LightGray; |
| width : 32em; |
| } |
| |
| </style> |
| |
| <script type="application/javascript"> |
| |
| var dump_str = "dump-"; |
| var dump_len = dump_str.length; |
| |
| function dump_comment() { |
| var s = event.srcElement.id; |
| var index = s.indexOf(dump_str); |
| if (index >= 0) { |
| send_dump_desc(s.substring(index + dump_len), event.srcElement.value); |
| } |
| } |
| |
| function send_dump_desc(name, desc) { |
| xmlhttp = new XMLHttpRequest(); |
| name = encodeURIComponent(name) |
| desc = encodeURIComponent(desc) |
| xmlhttp.open("GET", |
| "setdumpdesc?dump=" + name + |
| "&description=" + desc, true); |
| xmlhttp.send(); |
| } |
| |
| </script> |
| |
| <title>Dump list</title> |
| </head> |
| |
| <body> |
| """ |
| |
| WEB_DUMPS_FOOTER = """ |
| </body> |
| </html> |
| """ |
| |
| DUMP_FILE_RE = re.compile(r"[-_0-9a-zA-Z][-\._0-9a-zA-Z]*\.dmp$") |
| |
| |
| class InspectionWebServer(BaseHTTPServer.HTTPServer): |
| def __init__(self, port_number, switches, minidump_name): |
| BaseHTTPServer.HTTPServer.__init__( |
| self, ('', port_number), InspectionWebHandler) |
| splitpath = os.path.split(minidump_name) |
| self.dumppath = splitpath[0] |
| self.dumpfilename = splitpath[1] |
| self.default_formatter = InspectionWebFormatter( |
| switches, minidump_name, self) |
| self.formatters = { self.dumpfilename : self.default_formatter } |
| self.switches = switches |
| |
| def output_dump_desc_field(self, f, name): |
| try: |
| descfile = open(os.path.join(self.dumppath, name + ".desc"), "r") |
| desc = descfile.readline() |
| descfile.close() |
| except IOError: |
| desc = "" |
| f.write("<input type=\"text\" class=\"dumpcomments\" " |
| "id=\"dump-%s\" onchange=\"dump_comment()\" value=\"%s\">\n" % |
| (cgi.escape(name), desc)) |
| |
| def set_dump_desc(self, name, description): |
| if not DUMP_FILE_RE.match(name): |
| return False |
| fname = os.path.join(self.dumppath, name) |
| if not os.path.isfile(fname): |
| return False |
| fname = fname + ".desc" |
| descfile = open(fname, "w") |
| descfile.write(description) |
| descfile.close() |
| return True |
| |
| def get_dump_formatter(self, name): |
| if name is None: |
| return self.default_formatter |
| else: |
| if not DUMP_FILE_RE.match(name): |
| raise WebParameterError("Invalid name '%s'" % name) |
| formatter = self.formatters.get(name, None) |
| if formatter is None: |
| try: |
| formatter = InspectionWebFormatter( |
| self.switches, os.path.join(self.dumppath, name), self) |
| self.formatters[name] = formatter |
| except IOError: |
| raise WebParameterError("Could not open dump '%s'" % name) |
| return formatter |
| |
| def output_dumps(self, f): |
| f.write(WEB_DUMPS_HEADER) |
| f.write("<h3>List of available dumps</h3>") |
| f.write("<table class=\"dumplist\">\n") |
| f.write("<thead><tr>") |
| f.write("<th>Name</th>") |
| f.write("<th>File time</th>") |
| f.write("<th>Comment</th>") |
| f.write("</tr></thead>") |
| dumps_by_time = {} |
| for fname in os.listdir(self.dumppath): |
| if DUMP_FILE_RE.match(fname): |
| mtime = os.stat(os.path.join(self.dumppath, fname)).st_mtime |
| fnames = dumps_by_time.get(mtime, []) |
| fnames.append(fname) |
| dumps_by_time[mtime] = fnames |
| |
| for mtime in sorted(dumps_by_time, reverse=True): |
| fnames = dumps_by_time[mtime] |
| for fname in fnames: |
| f.write("<tr>\n") |
| f.write("<td><a href=\"summary.html?%s\">%s</a></td>\n" % ( |
| (urllib.urlencode({ 'dump' : fname }), fname))) |
| f.write("<td> ") |
| f.write(datetime.datetime.fromtimestamp(mtime)) |
| f.write("</td>") |
| f.write("<td> ") |
| self.output_dump_desc_field(f, fname) |
| f.write("</td>") |
| f.write("</tr>\n") |
| f.write("</table>\n") |
| f.write(WEB_DUMPS_FOOTER) |
| return |
| |
| class InspectionShell(cmd.Cmd): |
| def __init__(self, reader, heap): |
| cmd.Cmd.__init__(self) |
| self.reader = reader |
| self.heap = heap |
| self.padawan = InspectionPadawan(reader, heap) |
| self.prompt = "(grok) " |
| |
| self.dd_start = 0 |
| self.dd_num = 0x10 |
| self.u_start = 0 |
| self.u_num = 0 |
| |
| def EvalExpression(self, expr): |
| # Auto convert hex numbers to a python compatible format |
| if expr[:2] == "00": |
| expr = "0x"+expr |
| result = None |
| try: |
| # Ugly hack to patch in register values. |
| registers = [register |
| for register,value in self.reader.ContextDescriptor().fields] |
| registers.sort(key=lambda r: len(r)) |
| registers.reverse() |
| for register in registers: |
| expr = expr.replace("$"+register, str(self.reader.Register(register))) |
| result = eval(expr) |
| except Exception as e: |
| print("**** Could not evaluate '%s': %s" % (expr, e)) |
| raise e |
| return result |
| |
| def ParseAddressExpr(self, expr): |
| address = 0; |
| try: |
| result = self.EvalExpression(expr) |
| except: |
| return 0 |
| try: |
| address = int(result) |
| except Exception as e: |
| print("**** Could not convert '%s' => %s to valid address: %s" % ( |
| expr, result , e)) |
| return address |
| |
| def do_help(self, cmd=None): |
| if len(cmd) == 0: |
| print "Available commands" |
| print "=" * 79 |
| prefix = "do_" |
| methods = inspect.getmembers(InspectionShell, predicate=inspect.ismethod) |
| for name,method in methods: |
| if not name.startswith(prefix): continue |
| doc = inspect.getdoc(method) |
| if not doc: continue |
| name = prefix.join(name.split(prefix)[1:]) |
| description = doc.splitlines()[0] |
| print (name + ": ").ljust(16) + description |
| print "=" * 79 |
| else: |
| return super(InspectionShell, self).do_help(cmd) |
| |
| def do_p(self, cmd): |
| """ see print """ |
| return self.do_print(cmd) |
| |
| def do_print(self, cmd): |
| """ |
| Evaluate an arbitrary python command. |
| """ |
| try: |
| print(self.EvalExpression(cmd)) |
| except: |
| pass |
| |
| def do_da(self, address): |
| """ see display_ascii""" |
| return self.do_display_ascii(address) |
| |
| def do_display_ascii(self, address): |
| """ |
| Print ASCII string starting at specified address. |
| """ |
| address = self.ParseAddressExpr(address) |
| string = self.reader.ReadAsciiString(address) |
| if string == "": |
| print "Not an ASCII string at %s" % self.reader.FormatIntPtr(address) |
| else: |
| print "%s\n" % string |
| |
| def do_dsa(self, address): |
| """ see display_stack_ascii""" |
| return self.do_display_stack_ascii(address) |
| |
| def do_display_stack_ascii(self, address): |
| """ |
| Print ASCII stack error message. |
| """ |
| if self.reader.exception is None: |
| print "Minidump has no exception info" |
| return |
| if len(address) == 0: |
| address = None |
| else: |
| address = self.ParseAddressExpr(address) |
| self.padawan.PrintStackTraceMessage(address) |
| |
| def do_dd(self, args): |
| """ |
| Interpret memory in the given region [address, address + num * word_size) |
| |
| (if available) as a sequence of words. Automatic alignment is not performed. |
| If the num is not specified, a default value of 16 words is usif not self.Is |
| If no address is given, dd continues printing at the next word. |
| |
| Synopsis: dd 0x<address>|$register [0x<num>] |
| """ |
| if len(args) != 0: |
| args = args.split(' ') |
| self.dd_start = self.ParseAddressExpr(args[0]) |
| self.dd_num = int(args[1], 16) if len(args) > 1 else 0x10 |
| else: |
| self.dd_start += self.dd_num * self.reader.PointerSize() |
| if not self.reader.IsAlignedAddress(self.dd_start): |
| print "Warning: Dumping un-aligned memory, is this what you had in mind?" |
| end = self.dd_start + self.reader.PointerSize() * self.dd_num |
| self.padawan.InterpretMemory(self.dd_start, end) |
| |
| def do_do(self, address): |
| """ see display_object """ |
| return self.do_display_object(address) |
| |
| def do_display_object(self, address): |
| """ |
| Interpret memory at the given address as a V8 object. |
| |
| Automatic alignment makes sure that you can pass tagged as well as |
| un-tagged addresses. |
| """ |
| address = self.ParseAddressExpr(address) |
| if self.reader.IsAlignedAddress(address): |
| address = address + 1 |
| elif not self.heap.IsTaggedObjectAddress(address): |
| print "Address doesn't look like a valid pointer!" |
| return |
| heap_object = self.padawan.SenseObject(address) |
| if heap_object: |
| heap_object.Print(Printer()) |
| else: |
| print "Address cannot be interpreted as object!" |
| |
| def do_dso(self, args): |
| """ see display_stack_objects """ |
| return self.do_display_stack_objects(args) |
| |
| def do_display_stack_objects(self, args): |
| """ |
| Find and Print object pointers in the given range. |
| |
| Print all possible object pointers that are on the stack or in the given |
| address range. |
| |
| Usage: dso [START_ADDR,[END_ADDR]] |
| """ |
| start = self.reader.StackTop() |
| end = self.reader.StackBottom() |
| if len(args) != 0: |
| args = args.split(' ') |
| start = self.ParseAddressExpr(args[0]) |
| end = self.ParseAddressExpr(args[1]) if len(args) > 1 else end |
| objects = self.heap.FindObjectPointers(start, end) |
| for address in objects: |
| heap_object = self.padawan.SenseObject(address) |
| info = "" |
| if heap_object: |
| info = str(heap_object) |
| print("%s %s" % (self.padawan.FormatIntPtr(address), info)) |
| |
| def do_do_desc(self, address): |
| """ |
| Print a descriptor array in a readable format. |
| """ |
| start = self.ParseAddressExpr(address) |
| if ((start & 1) == 1): start = start - 1 |
| DescriptorArray(FixedArray(self.heap, None, start)).Print(Printer()) |
| |
| def do_do_map(self, address): |
| """ |
| Print a Map in a readable format. |
| """ |
| start = self.ParseAddressExpr(address) |
| if ((start & 1) == 1): start = start - 1 |
| Map(self.heap, None, start).Print(Printer()) |
| |
| def do_do_trans(self, address): |
| """ |
| Print a transition array in a readable format. |
| """ |
| start = self.ParseAddressExpr(address) |
| if ((start & 1) == 1): start = start - 1 |
| TransitionArray(FixedArray(self.heap, None, start)).Print(Printer()) |
| |
| def do_dp(self, address): |
| """ see display_page """ |
| return self.do_display_page(address) |
| |
| def do_display_page(self, address): |
| """ |
| Prints details about the V8 heap page of the given address. |
| |
| Interpret memory at the given address as being on a V8 heap page |
| and print information about the page header (if available). |
| """ |
| address = self.ParseAddressExpr(address) |
| page_address = address & ~self.heap.PageAlignmentMask() |
| if self.reader.IsValidAddress(page_address): |
| print "**** Not Implemented" |
| return |
| else: |
| print "Page header is not available!" |
| |
| def do_k(self, arguments): |
| """ |
| Teach V8 heap layout information to the inspector. |
| |
| This increases the amount of annotations the inspector can produce while |
| dumping data. The first page of each heap space is of particular interest |
| because it contains known objects that do not move. |
| """ |
| self.padawan.PrintKnowledge() |
| |
| def do_ko(self, address): |
| """ see known_oldspace """ |
| return self.do_known_oldspace(address) |
| |
| def do_known_oldspace(self, address): |
| """ |
| Teach V8 heap layout information to the inspector. |
| |
| Set the first old space page by passing any pointer into that page. |
| """ |
| address = self.ParseAddressExpr(address) |
| page_address = address & ~self.heap.PageAlignmentMask() |
| self.padawan.known_first_old_page = page_address |
| |
| def do_km(self, address): |
| """ see known_map """ |
| return self.do_known_map(address) |
| |
| def do_known_map(self, address): |
| """ |
| Teach V8 heap layout information to the inspector. |
| |
| Set the first map-space page by passing any pointer into that page. |
| """ |
| address = self.ParseAddressExpr(address) |
| page_address = address & ~self.heap.PageAlignmentMask() |
| self.padawan.known_first_map_page = page_address |
| |
| def do_list(self, smth): |
| """ |
| List all available memory regions. |
| """ |
| def print_region(reader, start, size, location): |
| print " %s - %s (%d bytes)" % (reader.FormatIntPtr(start), |
| reader.FormatIntPtr(start + size), |
| size) |
| print "Available memory regions:" |
| self.reader.ForEachMemoryRegion(print_region) |
| |
| def do_lm(self, arg): |
| """ see list_modules """ |
| return self.do_list_modules(arg) |
| |
| def do_list_modules(self, arg): |
| """ |
| List details for all loaded modules in the minidump. |
| |
| An argument can be passed to limit the output to only those modules that |
| contain the argument as a substring (case insensitive match). |
| """ |
| for module in self.reader.module_list.modules: |
| if arg: |
| name = GetModuleName(self.reader, module).lower() |
| if name.find(arg.lower()) >= 0: |
| PrintModuleDetails(self.reader, module) |
| else: |
| PrintModuleDetails(self.reader, module) |
| print |
| |
| def do_s(self, word): |
| """ see search """ |
| return self.do_search(word) |
| |
| def do_search(self, word): |
| """ |
| Search for a given word in available memory regions. |
| |
| The given word is expanded to full pointer size and searched at aligned |
| as well as un-aligned memory locations. Use 'sa' to search aligned locations |
| only. |
| """ |
| try: |
| word = self.ParseAddressExpr(word) |
| except ValueError: |
| print "Malformed word, prefix with '0x' to use hexadecimal format." |
| return |
| print "Searching for word %d/0x%s:" % (word, self.reader.FormatIntPtr(word)) |
| self.reader.FindWord(word) |
| |
| def do_sh(self, none): |
| """ |
| Search for the V8 Heap object in all available memory regions. |
| |
| You might get lucky and find this rare treasure full of invaluable |
| information. |
| """ |
| print "**** Not Implemented" |
| |
| def do_u(self, args): |
| """ see disassemble """ |
| return self.do_disassemble(args) |
| |
| def do_disassemble(self, args): |
| """ |
| Unassemble memory in the region [address, address + size). |
| |
| If the size is not specified, a default value of 32 bytes is used. |
| Synopsis: u 0x<address> 0x<size> |
| """ |
| if len(args) != 0: |
| args = args.split(' ') |
| self.u_start = self.ParseAddressExpr(args[0]) |
| self.u_size = self.ParseAddressExpr(args[1]) if len(args) > 1 else 0x20 |
| skip = False |
| else: |
| # Skip the first instruction if we reuse the last address. |
| skip = True |
| |
| if not self.reader.IsValidAddress(self.u_start): |
| print "Address %s is not contained within the minidump!" % ( |
| self.reader.FormatIntPtr(self.u_start)) |
| return |
| lines = self.reader.GetDisasmLines(self.u_start, self.u_size) |
| if len(lines) == 0: |
| print "Address %s could not be disassembled!" % ( |
| self.reader.FormatIntPtr(self.u_start)) |
| print " Could not disassemble using %s." % OBJDUMP_BIN |
| print " Pass path to architecture specific objdump via --objdump?" |
| return |
| for line in lines: |
| if skip: |
| skip = False |
| continue |
| print FormatDisasmLine(self.u_start, self.heap, line) |
| # Set the next start address = last line |
| self.u_start += lines[-1][0] |
| print |
| |
| def do_EOF(self, none): |
| raise KeyboardInterrupt |
| |
| EIP_PROXIMITY = 64 |
| |
| CONTEXT_FOR_ARCH = { |
| MD_CPU_ARCHITECTURE_AMD64: |
| ['rax', 'rbx', 'rcx', 'rdx', 'rdi', 'rsi', 'rbp', 'rsp', 'rip', |
| 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15'], |
| MD_CPU_ARCHITECTURE_ARM: |
| ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', |
| 'r10', 'r11', 'r12', 'sp', 'lr', 'pc'], |
| MD_CPU_ARCHITECTURE_ARM64: |
| ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', |
| 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', 'r16', 'r17', 'r18', 'r19', |
| 'r20', 'r21', 'r22', 'r23', 'r24', 'r25', 'r26', 'r27', 'r28', |
| 'fp', 'lr', 'sp', 'pc'], |
| MD_CPU_ARCHITECTURE_X86: |
| ['eax', 'ebx', 'ecx', 'edx', 'edi', 'esi', 'ebp', 'esp', 'eip'] |
| } |
| |
| KNOWN_MODULES = {'chrome.exe', 'chrome.dll'} |
| |
| def GetVersionString(ms, ls): |
| return "%d.%d.%d.%d" % (ms >> 16, ms & 0xffff, ls >> 16, ls & 0xffff) |
| |
| |
| def GetModuleName(reader, module): |
| name = reader.ReadMinidumpString(module.module_name_rva) |
| # simplify for path manipulation |
| name = name.encode('utf-8') |
| return str(os.path.basename(str(name).replace("\\", "/"))) |
| |
| |
| def PrintModuleDetails(reader, module): |
| print "%s" % GetModuleName(reader, module) |
| file_version = GetVersionString(module.version_info.dwFileVersionMS, |
| module.version_info.dwFileVersionLS); |
| product_version = GetVersionString(module.version_info.dwProductVersionMS, |
| module.version_info.dwProductVersionLS) |
| print " base: %s" % reader.FormatIntPtr(module.base_of_image) |
| print " end: %s" % reader.FormatIntPtr(module.base_of_image + |
| module.size_of_image) |
| print " file version: %s" % file_version |
| print " product version: %s" % product_version |
| time_date_stamp = datetime.datetime.fromtimestamp(module.time_date_stamp) |
| print " timestamp: %s" % time_date_stamp |
| |
| |
| def AnalyzeMinidump(options, minidump_name): |
| reader = MinidumpReader(options, minidump_name) |
| heap = None |
| |
| stack_top = reader.ExceptionSP() |
| stack_bottom = reader.StackBottom() |
| stack_map = {reader.ExceptionIP(): -1} |
| for slot in xrange(stack_top, stack_bottom, reader.PointerSize()): |
| maybe_address = reader.ReadUIntPtr(slot) |
| if not maybe_address in stack_map: |
| stack_map[maybe_address] = slot |
| |
| heap = V8Heap(reader, stack_map) |
| padawan = InspectionPadawan(reader, heap) |
| |
| DebugPrint("========================================") |
| if reader.exception is None: |
| print "Minidump has no exception info" |
| else: |
| print "Address markers:" |
| print " T = valid tagged pointer in the minidump" |
| print " S = address on the exception stack" |
| print " C = address in loaded C/C++ module" |
| print " * = address in the minidump" |
| print "" |
| print "Exception info:" |
| exception_thread = reader.ExceptionThread() |
| print " thread id: %d" % exception_thread.id |
| print " code: %08X" % reader.exception.exception.code |
| print " context:" |
| context = CONTEXT_FOR_ARCH[reader.arch] |
| maxWidth = max(map(lambda s: len(s), context)) |
| for r in context: |
| register_value = reader.Register(r) |
| print " %s: %s" % (r.rjust(maxWidth), |
| heap.FormatIntPtr(register_value)) |
| # TODO(vitalyr): decode eflags. |
| if reader.arch in [MD_CPU_ARCHITECTURE_ARM, MD_CPU_ARCHITECTURE_ARM64]: |
| print " cpsr: %s" % bin(reader.exception_context.cpsr)[2:] |
| else: |
| print " eflags: %s" % bin(reader.exception_context.eflags)[2:] |
| |
| print |
| print " modules:" |
| for module in reader.module_list.modules: |
| name = GetModuleName(reader, module) |
| if name in KNOWN_MODULES: |
| print " %s at %08X" % (name, module.base_of_image) |
| reader.TryLoadSymbolsFor(name, module) |
| print |
| |
| print " stack-top: %s" % heap.FormatIntPtr(reader.StackTop()) |
| print " stack-bottom: %s" % heap.FormatIntPtr(reader.StackBottom()) |
| print "" |
| |
| if options.shell: |
| padawan.PrintStackTraceMessage(print_message=False) |
| |
| print "Disassembly around exception.eip:" |
| eip_symbol = reader.FindSymbol(reader.ExceptionIP()) |
| if eip_symbol is not None: |
| print eip_symbol |
| disasm_start = reader.ExceptionIP() - EIP_PROXIMITY |
| disasm_bytes = 2 * EIP_PROXIMITY |
| if (options.full): |
| full_range = reader.FindRegion(reader.ExceptionIP()) |
| if full_range is not None: |
| disasm_start = full_range[0] |
| disasm_bytes = full_range[1] |
| |
| lines = reader.GetDisasmLines(disasm_start, disasm_bytes) |
| |
| if not lines: |
| print "Could not disassemble using %s." % OBJDUMP_BIN |
| print "Pass path to architecture specific objdump via --objdump?" |
| |
| for line in lines: |
| print FormatDisasmLine(disasm_start, heap, line) |
| print |
| |
| if heap is None: |
| heap = V8Heap(reader, None) |
| |
| if options.full: |
| FullDump(reader, heap) |
| |
| if options.command: |
| InspectionShell(reader, heap).onecmd(options.command) |
| |
| if options.shell: |
| try: |
| InspectionShell(reader, heap).cmdloop("type help to get help") |
| except KeyboardInterrupt: |
| print "Kthxbye." |
| elif not options.command: |
| if reader.exception is not None: |
| print "Annotated stack (from exception.esp to bottom):" |
| stack_start = padawan.PrintStackTraceMessage() |
| padawan.InterpretMemory(stack_start, stack_bottom) |
| reader.Dispose() |
| |
| |
| if __name__ == "__main__": |
| parser = optparse.OptionParser(USAGE) |
| parser.add_option("-s", "--shell", dest="shell", action="store_true", |
| help="start an interactive inspector shell") |
| parser.add_option("-w", "--web", dest="web", action="store_true", |
| help="start a web server on localhost:%i" % PORT_NUMBER) |
| parser.add_option("-c", "--command", dest="command", default="", |
| help="run an interactive inspector shell command and exit") |
| parser.add_option("-f", "--full", dest="full", action="store_true", |
| help="dump all information contained in the minidump") |
| parser.add_option("--symdir", dest="symdir", default=".", |
| help="directory containing *.pdb.sym file with symbols") |
| parser.add_option("--objdump", default="", |
| help="objdump tool to use [default: %s]" % ( |
| DEFAULT_OBJDUMP_BIN)) |
| options, args = parser.parse_args() |
| if len(args) != 1: |
| parser.print_help() |
| sys.exit(1) |
| if options.web: |
| try: |
| server = InspectionWebServer(PORT_NUMBER, options, args[0]) |
| print 'Started httpserver on port ' , PORT_NUMBER |
| webbrowser.open('http://localhost:%i/summary.html' % PORT_NUMBER) |
| server.serve_forever() |
| except KeyboardInterrupt: |
| print '^C received, shutting down the web server' |
| server.socket.close() |
| else: |
| AnalyzeMinidump(options, args[0]) |