|
#!/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]) |