v8/tools/grokdump.py

2242 lines
77 KiB
Python
Raw Normal View History

#!/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 bisect
import cmd
import codecs
import ctypes
import datetime
import disasm
import mmap
import optparse
import os
import re
import struct
import sys
import types
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 slot in xrange(location, location + size, 16):
hex_line = ""
asc_line = ""
for i in xrange(0, 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))
for slot in xrange(start,
start + size,
reader.PointerSize()):
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)
# 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_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_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_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_RAW_SYSTEM_INFO = Descriptor([
("processor_architecture", ctypes.c_uint16)
])
MD_CPU_ARCHITECTURE_X86 = 0
MD_CPU_ARCHITECTURE_ARM = 5
MD_CPU_ARCHITECTURE_AMD64 = 9
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 = []
# 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_X86]
assert not self.arch is None
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)
if self.arch == MD_CPU_ARCHITECTURE_X86:
self.exception_context = MINIDUMP_CONTEXT_X86.Read(
self.minidump, self.exception.thread_context.rva)
elif self.arch == MD_CPU_ARCHITECTURE_AMD64:
self.exception_context = MINIDUMP_CONTEXT_AMD64.Read(
self.minidump, self.exception.thread_context.rva)
elif self.arch == MD_CPU_ARCHITECTURE_ARM:
self.exception_context = MINIDUMP_CONTEXT_ARM.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)
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)
assert ctypes.sizeof(self.module_list) == d.location.data_size
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)
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 IsValidAddress(self, address):
return self.FindLocation(address) is not 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 ReadUIntPtr(self, address):
if self.arch == MD_CPU_ARCHITECTURE_AMD64:
return self.ReadU64(address)
elif self.arch == MD_CPU_ARCHITECTURE_ARM:
return self.ReadU32(address)
elif self.arch == MD_CPU_ARCHITECTURE_X86:
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.arch == MD_CPU_ARCHITECTURE_AMD64:
return ctypes.c_uint64.from_buffer(self.minidump, location).value
elif self.arch == MD_CPU_ARCHITECTURE_ARM:
return ctypes.c_uint32.from_buffer(self.minidump, location).value
elif self.arch == MD_CPU_ARCHITECTURE_X86:
return ctypes.c_uint32.from_buffer(self.minidump, location).value
def IsProbableASCIIRegion(self, location, length):
ascii_bytes = 0
non_ascii_bytes = 0
for loc in xrange(location, location + length):
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.arch == MD_CPU_ARCHITECTURE_AMD64
for loc in xrange(location, location + length):
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 loc in xrange(location, location + size - self.PointerSize()):
if reader._ReadWord(loc) == word:
slot = start + (loc - location)
print "%s: %s" % (reader.FormatIntPtr(slot),
reader.FormatIntPtr(word))
self.ForEachMemoryRegion(search_inside_region)
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_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_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_X86:
return self.exception_context.esp
def FormatIntPtr(self, value):
if self.arch == MD_CPU_ARCHITECTURE_AMD64:
return "%016x" % value
elif self.arch == MD_CPU_ARCHITECTURE_ARM:
return "%08x" % value
elif self.arch == MD_CPU_ARCHITECTURE_X86:
return "%08x" % value
def PointerSize(self):
if self.arch == MD_CPU_ARCHITECTURE_AMD64:
return 8
elif self.arch == MD_CPU_ARCHITECTURE_ARM:
return 4
elif self.arch == MD_CPU_ARCHITECTURE_X86:
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)
# List of V8 instance types. Obtained by adding the code below to any .cc file.
#
# #define DUMP_TYPE(T) printf(" %d: \"%s\",\n", T, #T);
# struct P {
# P() {
# printf("INSTANCE_TYPES = {\n");
# INSTANCE_TYPE_LIST(DUMP_TYPE)
# printf("}\n");
# }
# };
# static P p;
INSTANCE_TYPES = {
0: "STRING_TYPE",
4: "ASCII_STRING_TYPE",
1: "CONS_STRING_TYPE",
5: "CONS_ASCII_STRING_TYPE",
3: "SLICED_STRING_TYPE",
2: "EXTERNAL_STRING_TYPE",
6: "EXTERNAL_ASCII_STRING_TYPE",
10: "EXTERNAL_STRING_WITH_ASCII_DATA_TYPE",
18: "SHORT_EXTERNAL_STRING_TYPE",
22: "SHORT_EXTERNAL_ASCII_STRING_TYPE",
26: "SHORT_EXTERNAL_STRING_WITH_ASCII_DATA_TYPE",
64: "INTERNALIZED_STRING_TYPE",
68: "ASCII_INTERNALIZED_STRING_TYPE",
65: "CONS_INTERNALIZED_STRING_TYPE",
69: "CONS_ASCII_INTERNALIZED_STRING_TYPE",
66: "EXTERNAL_INTERNALIZED_STRING_TYPE",
70: "EXTERNAL_ASCII_INTERNALIZED_STRING_TYPE",
74: "EXTERNAL_INTERNALIZED_STRING_WITH_ASCII_DATA_TYPE",
82: "SHORT_EXTERNAL_INTERNALIZED_STRING_TYPE",
86: "SHORT_EXTERNAL_ASCII_INTERNALIZED_STRING_TYPE",
90: "SHORT_EXTERNAL_INTERNALIZED_STRING_WITH_ASCII_DATA_TYPE",
128: "SYMBOL_TYPE",
129: "MAP_TYPE",
130: "CODE_TYPE",
131: "ODDBALL_TYPE",
132: "JS_GLOBAL_PROPERTY_CELL_TYPE",
133: "HEAP_NUMBER_TYPE",
134: "FOREIGN_TYPE",
135: "BYTE_ARRAY_TYPE",
136: "FREE_SPACE_TYPE",
137: "EXTERNAL_BYTE_ARRAY_TYPE",
138: "EXTERNAL_UNSIGNED_BYTE_ARRAY_TYPE",
139: "EXTERNAL_SHORT_ARRAY_TYPE",
140: "EXTERNAL_UNSIGNED_SHORT_ARRAY_TYPE",
141: "EXTERNAL_INT_ARRAY_TYPE",
142: "EXTERNAL_UNSIGNED_INT_ARRAY_TYPE",
143: "EXTERNAL_FLOAT_ARRAY_TYPE",
145: "EXTERNAL_PIXEL_ARRAY_TYPE",
147: "FILLER_TYPE",
148: "DECLARED_ACCESSOR_DESCRIPTOR_TYPE",
149: "DECLARED_ACCESSOR_INFO_TYPE",
150: "EXECUTABLE_ACCESSOR_INFO_TYPE",
151: "ACCESSOR_PAIR_TYPE",
152: "ACCESS_CHECK_INFO_TYPE",
153: "INTERCEPTOR_INFO_TYPE",
154: "CALL_HANDLER_INFO_TYPE",
155: "FUNCTION_TEMPLATE_INFO_TYPE",
156: "OBJECT_TEMPLATE_INFO_TYPE",
157: "SIGNATURE_INFO_TYPE",
158: "TYPE_SWITCH_INFO_TYPE",
159: "ALLOCATION_SITE_INFO_TYPE",
160: "SCRIPT_TYPE",
161: "CODE_CACHE_TYPE",
162: "POLYMORPHIC_CODE_CACHE_TYPE",
163: "TYPE_FEEDBACK_INFO_TYPE",
164: "ALIASED_ARGUMENTS_ENTRY_TYPE",
167: "FIXED_ARRAY_TYPE",
146: "FIXED_DOUBLE_ARRAY_TYPE",
168: "SHARED_FUNCTION_INFO_TYPE",
169: "JS_MESSAGE_OBJECT_TYPE",
172: "JS_VALUE_TYPE",
173: "JS_DATE_TYPE",
174: "JS_OBJECT_TYPE",
175: "JS_CONTEXT_EXTENSION_OBJECT_TYPE",
176: "JS_MODULE_TYPE",
177: "JS_GLOBAL_OBJECT_TYPE",
178: "JS_BUILTINS_OBJECT_TYPE",
179: "JS_GLOBAL_PROXY_TYPE",
180: "JS_ARRAY_TYPE",
171: "JS_PROXY_TYPE",
183: "JS_WEAK_MAP_TYPE",
184: "JS_WEAK_SET_TYPE",
185: "JS_REGEXP_TYPE",
186: "JS_FUNCTION_TYPE",
170: "JS_FUNCTION_PROXY_TYPE",
165: "DEBUG_INFO_TYPE",
166: "BREAK_POINT_INFO_TYPE",
}
# List of known V8 maps. Used to determine the instance type and name
# for maps that are part of the root-set and hence on the first page of
# the map-space. Obtained by adding the code below to an IA32 release
# build with enabled snapshots to the end of the Isolate::Init method.
#
# #define ROOT_LIST_CASE(type, name, camel_name) \
# if (o == heap_.name()) n = #camel_name;
# #define STRUCT_LIST_CASE(upper_name, camel_name, name) \
# if (o == heap_.name##_map()) n = #camel_name "Map";
# HeapObjectIterator it(heap_.map_space());
# printf("KNOWN_MAPS = {\n");
# for (Object* o = it.Next(); o != NULL; o = it.Next()) {
# Map* m = Map::cast(o);
# const char* n = "";
# intptr_t p = reinterpret_cast<intptr_t>(m) & 0xfffff;
# int t = m->instance_type();
# ROOT_LIST(ROOT_LIST_CASE)
# STRUCT_LIST(STRUCT_LIST_CASE)
# printf(" 0x%05x: (%d, \"%s\"),\n", p, t, n);
# }
# printf("}\n");
KNOWN_MAPS = {
0x08081: (135, "ByteArrayMap"),
0x080a9: (129, "MetaMap"),
0x080d1: (131, "OddballMap"),
0x080f9: (68, "AsciiInternalizedStringMap"),
0x08121: (167, "FixedArrayMap"),
0x08149: (133, "HeapNumberMap"),
0x08171: (136, "FreeSpaceMap"),
0x08199: (147, "OnePointerFillerMap"),
0x081c1: (147, "TwoPointerFillerMap"),
0x081e9: (132, "GlobalPropertyCellMap"),
0x08211: (168, "SharedFunctionInfoMap"),
0x08239: (167, "NativeContextMap"),
0x08261: (130, "CodeMap"),
0x08289: (167, "ScopeInfoMap"),
0x082b1: (167, "FixedCOWArrayMap"),
0x082d9: (146, "FixedDoubleArrayMap"),
0x08301: (167, "HashTableMap"),
0x08329: (128, "SymbolMap"),
0x08351: (0, "StringMap"),
0x08379: (4, "AsciiStringMap"),
0x083a1: (1, "ConsStringMap"),
0x083c9: (5, "ConsAsciiStringMap"),
0x083f1: (3, "SlicedStringMap"),
0x08419: (7, "SlicedAsciiStringMap"),
0x08441: (2, "ExternalStringMap"),
0x08469: (10, "ExternalStringWithAsciiDataMap"),
0x08491: (6, "ExternalAsciiStringMap"),
0x084b9: (18, "ShortExternalStringMap"),
0x084e1: (26, "ShortExternalStringWithAsciiDataMap"),
0x08509: (64, "InternalizedStringMap"),
0x08531: (65, "ConsInternalizedStringMap"),
0x08559: (69, "ConsAsciiInternalizedStringMap"),
0x08581: (66, "ExternalInternalizedStringMap"),
0x085a9: (74, "ExternalInternalizedStringWithAsciiDataMap"),
0x085d1: (70, "ExternalAsciiInternalizedStringMap"),
0x085f9: (82, "ShortExternalInternalizedStringMap"),
0x08621: (90, "ShortExternalInternalizedStringWithAsciiDataMap"),
0x08649: (86, "ShortExternalAsciiInternalizedStringMap"),
0x08671: (22, "ShortExternalAsciiStringMap"),
0x08699: (0, "UndetectableStringMap"),
0x086c1: (4, "UndetectableAsciiStringMap"),
0x086e9: (145, "ExternalPixelArrayMap"),
0x08711: (137, "ExternalByteArrayMap"),
0x08739: (138, "ExternalUnsignedByteArrayMap"),
0x08761: (139, "ExternalShortArrayMap"),
0x08789: (140, "ExternalUnsignedShortArrayMap"),
0x087b1: (141, "ExternalIntArrayMap"),
0x087d9: (142, "ExternalUnsignedIntArrayMap"),
0x08801: (143, "ExternalFloatArrayMap"),
0x08829: (144, "ExternalDoubleArrayMap"),
0x08851: (167, "NonStrictArgumentsElementsMap"),
0x08879: (167, "FunctionContextMap"),
0x088a1: (167, "CatchContextMap"),
0x088c9: (167, "WithContextMap"),
0x088f1: (167, "BlockContextMap"),
0x08919: (167, "ModuleContextMap"),
0x08941: (167, "GlobalContextMap"),
0x08969: (169, "JSMessageObjectMap"),
0x08991: (134, "ForeignMap"),
0x089b9: (174, "NeanderMap"),
0x089e1: (159, "AllocationSiteInfoMap"),
0x08a09: (162, "PolymorphicCodeCacheMap"),
0x08a31: (160, "ScriptMap"),
0x08a59: (174, ""),
0x08a81: (174, "ExternalMap"),
0x08aa9: (148, "DeclaredAccessorDescriptorMap"),
0x08ad1: (149, "DeclaredAccessorInfoMap"),
0x08af9: (150, "ExecutableAccessorInfoMap"),
0x08b21: (151, "AccessorPairMap"),
0x08b49: (152, "AccessCheckInfoMap"),
0x08b71: (153, "InterceptorInfoMap"),
0x08b99: (154, "CallHandlerInfoMap"),
0x08bc1: (155, "FunctionTemplateInfoMap"),
0x08be9: (156, "ObjectTemplateInfoMap"),
0x08c11: (157, "SignatureInfoMap"),
0x08c39: (158, "TypeSwitchInfoMap"),
0x08c61: (161, "CodeCacheMap"),
0x08c89: (163, "TypeFeedbackInfoMap"),
0x08cb1: (164, "AliasedArgumentsEntryMap"),
0x08cd9: (165, "DebugInfoMap"),
0x08d01: (166, "BreakPointInfoMap"),
}
# List of known V8 objects. Used to determine name for objects that are
# part of the root-set and hence on the first page of various old-space
# paged. Obtained by adding the code below to an IA32 release build with
# enabled snapshots to the end of the Isolate::Init method.
#
# #define ROOT_LIST_CASE(type, name, camel_name) \
# if (o == heap_.name()) n = #camel_name;
# OldSpaces spit(heap());
# printf("KNOWN_OBJECTS = {\n");
# for (PagedSpace* s = spit.next(); s != NULL; s = spit.next()) {
# HeapObjectIterator it(s);
# const char* sname = AllocationSpaceName(s->identity());
# for (Object* o = it.Next(); o != NULL; o = it.Next()) {
# const char* n = NULL;
# intptr_t p = reinterpret_cast<intptr_t>(o) & 0xfffff;
# ROOT_LIST(ROOT_LIST_CASE)
# if (n != NULL) {
# printf(" (\"%s\", 0x%05x): \"%s\",\n", sname, p, n);
# }
# }
# }
# printf("}\n");
KNOWN_OBJECTS = {
("OLD_POINTER_SPACE", 0x08081): "NullValue",
("OLD_POINTER_SPACE", 0x08091): "UndefinedValue",
("OLD_POINTER_SPACE", 0x080a1): "InstanceofCacheMap",
("OLD_POINTER_SPACE", 0x080b1): "TrueValue",
("OLD_POINTER_SPACE", 0x080c1): "FalseValue",
("OLD_POINTER_SPACE", 0x080d1): "NoInterceptorResultSentinel",
("OLD_POINTER_SPACE", 0x080e1): "ArgumentsMarker",
("OLD_POINTER_SPACE", 0x080f1): "NumberStringCache",
("OLD_POINTER_SPACE", 0x088f9): "SingleCharacterStringCache",
("OLD_POINTER_SPACE", 0x08b01): "StringSplitCache",
("OLD_POINTER_SPACE", 0x08f09): "RegExpMultipleCache",
("OLD_POINTER_SPACE", 0x09311): "TerminationException",
("OLD_POINTER_SPACE", 0x09321): "MessageListeners",
("OLD_POINTER_SPACE", 0x0933d): "CodeStubs",
("OLD_POINTER_SPACE", 0x09fa5): "NonMonomorphicCache",
("OLD_POINTER_SPACE", 0x0a5b9): "PolymorphicCodeCache",
("OLD_POINTER_SPACE", 0x0a5c1): "NativesSourceCache",
("OLD_POINTER_SPACE", 0x0a601): "EmptyScript",
("OLD_POINTER_SPACE", 0x0a63d): "IntrinsicFunctionNames",
("OLD_POINTER_SPACE", 0x0d659): "ObservationState",
("OLD_POINTER_SPACE", 0x27415): "SymbolTable",
("OLD_DATA_SPACE", 0x08099): "EmptyDescriptorArray",
("OLD_DATA_SPACE", 0x080a1): "EmptyFixedArray",
("OLD_DATA_SPACE", 0x080a9): "NanValue",
("OLD_DATA_SPACE", 0x08125): "EmptyByteArray",
("OLD_DATA_SPACE", 0x0812d): "EmptyString",
("OLD_DATA_SPACE", 0x08259): "InfinityValue",
("OLD_DATA_SPACE", 0x08265): "MinusZeroValue",
("OLD_DATA_SPACE", 0x08271): "PrototypeAccessors",
("CODE_SPACE", 0x0aea1): "JsEntryCode",
("CODE_SPACE", 0x0b5c1): "JsConstructEntryCode",
}
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])
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):
return "HeapObject(%s, %s)" % (self.heap.reader.FormatIntPtr(self.address),
INSTANCE_TYPES[self.map.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)
assert (field_value & 1) == 0
return field_value / 2
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 PreAllocatedPropertyFields(self):
return self.InObjectProperties() + 1
def VisitorId(self):
return self.PreAllocatedPropertyFields() + 1
# Instance Attributes
def InstanceAttributesOffset(self):
return self.InstanceSizesOffset() + self.heap.IntSize()
def InstanceTypeOffset(self):
return self.InstanceAttributesOffset()
def UnusedPropertyFieldsOffset(self):
return self.InstanceTypeOffset() + 1
def BitFieldOffset(self):
return self.UnusedPropertyFieldsOffset() + 1
def BitField2Offset(self):
return self.BitFieldOffset() + 1
# Other fields
def PrototypeOffset(self):
return self.InstanceAttributesOffset() + self.heap.IntSize()
def ConstructorOffset(self):
return self.PrototypeOffset() + self.heap.PointerSize()
def TransitionsOrBackPointerOffset(self):
return self.ConstructorOffset() + self.heap.PointerSize()
def DescriptorsOffset(self):
return self.TransitionsOrBackPointerOffset() + self.heap.PointerSize()
def CodeCacheOffset(self):
return self.DescriptorsOffset() + self.heap.PointerSize()
def DependentCodeOffset(self):
return self.CodeCacheOffset() + self.heap.PointerSize()
def BitField3Offset(self):
return self.DependentCodeOffset() + self.heap.PointerSize()
def ReadByte(self, offset):
return self.heap.reader.ReadU8(self.address + offset)
def Print(self, p):
p.Print("Map(%08x)" % (self.address))
p.Print("- size: %d, inobject: %d, preallocated: %d, visitor: %d" % (
self.ReadByte(self.InstanceSizeOffset()),
self.ReadByte(self.InObjectProperties()),
self.ReadByte(self.PreAllocatedPropertyFields()),
self.VisitorId()))
bitfield = self.ReadByte(self.BitFieldOffset())
bitfield2 = self.ReadByte(self.BitField2Offset())
p.Print("- %s, unused: %d, bf: %d, bf2: %d" % (
INSTANCE_TYPES[self.ReadByte(self.InstanceTypeOffset())],
self.ReadByte(self.UnusedPropertyFieldsOffset()),
bitfield, bitfield2))
p.Print("- kind: %s" % (self.Decode(3, 5, bitfield2)))
bitfield3 = self.ObjectField(self.BitField3Offset())
p.Print(
"- EnumLength: %d NumberOfOwnDescriptors: %d OwnsDescriptors: %s" % (
self.Decode(0, 11, bitfield3),
self.Decode(11, 11, bitfield3),
self.Decode(25, 1, bitfield3)))
p.Print("- IsShared: %s" % (self.Decode(22, 1, bitfield3)))
p.Print("- FunctionWithPrototype: %s" % (self.Decode(23, 1, bitfield3)))
p.Print("- DictionaryMap: %s" % (self.Decode(24, 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.TransitionsOrBackPointerOffset())
if transitions.__class__ == FixedArray:
TransitionArray(transitions).Print(p)
else:
p.Print("TransitionsOrBackPointer: %s" % (transitions))
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):
return self.heap.PointerSize()
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, self.to_string.GetChars())
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 Storage(self, value):
return self.Decode(7, 11, value)
def Pointer(self, value):
return self.Decode(18, 11, value)
def Details(self, di, value):
return (
di,
self.Type(value),
self.Attributes(value),
self.Storage(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 enum %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(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
return script_source.GetChars()[start:end]
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)
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())
self.start_position = start_position_and_type >> 2
self.end_position = \
self.SmiField(self.EndPositionOffset())
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,
"ASCII_SYMBOL_TYPE": SeqString,
"CONS_SYMBOL_TYPE": ConsString,
"CONS_ASCII_SYMBOL_TYPE": ConsString,
"EXTERNAL_SYMBOL_TYPE": ExternalString,
"EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE": ExternalString,
"EXTERNAL_ASCII_SYMBOL_TYPE": ExternalString,
"SHORT_EXTERNAL_SYMBOL_TYPE": ExternalString,
"SHORT_EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE": ExternalString,
"SHORT_EXTERNAL_ASCII_SYMBOL_TYPE": ExternalString,
"STRING_TYPE": SeqString,
"ASCII_STRING_TYPE": SeqString,
"CONS_STRING_TYPE": ConsString,
"CONS_ASCII_STRING_TYPE": ConsString,
"EXTERNAL_STRING_TYPE": ExternalString,
"EXTERNAL_STRING_WITH_ASCII_DATA_TYPE": ExternalString,
"EXTERNAL_ASCII_STRING_TYPE": ExternalString,
"MAP_TYPE": Map,
"ODDBALL_TYPE": Oddball,
"FIXED_ARRAY_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 (tagged_address & 1) == 0: return tagged_address / 2
return self.FindObject(tagged_address)
def FindObject(self, tagged_address):
if tagged_address in self.objects:
return self.objects[tagged_address]
if (tagged_address & self.ObjectAlignmentMask()) != 1: 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):
if (tagged_address & self.MapAlignmentMask()) != 1: return None
address = tagged_address - 1
if not self.reader.IsValidAddress(address): return None
object = Map(self, None, address)
return object
def IntSize(self):
return 4
def PointerSize(self):
return self.reader.PointerSize()
def ObjectAlignmentMask(self):
return self.PointerSize() - 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_X86:
return (1 << 5) - 1
def PageAlignmentMask(self):
return (1 << 20) - 1
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
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_data_page = 0
self.known_first_pointer_page = 0
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 in [self.known_first_data_page,
self.known_first_pointer_page]
def ContainingKnownOldSpaceName(self, tagged_address):
page_address = tagged_address & ~self.heap.PageAlignmentMask()
if page_address == self.known_first_data_page: return "OLD_DATA_SPACE"
if page_address == self.known_first_pointer_page: return "OLD_POINTER_SPACE"
return None
def SenseObject(self, tagged_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 (tagged_address & 1) == 0:
return "Smi(%d)" % (tagged_address / 2)
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_data_page = %s\n"\
" known_first_pointer_page = %s" % (
self.reader.FormatIntPtr(self.known_first_map_page),
self.reader.FormatIntPtr(self.known_first_data_page),
self.reader.FormatIntPtr(self.known_first_pointer_page))
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) "
def do_da(self, address):
"""
Print ASCII string starting at specified address.
"""
address = int(address, 16)
string = ""
while self.reader.IsValidAddress(address):
code = self.reader.ReadU8(address)
if code < 128:
string += chr(code)
else:
break
address += 1
if string == "":
print "Not an ASCII string at %s" % self.reader.FormatIntPtr(address)
else:
print "%s\n" % string
def do_dd(self, address):
"""
Interpret memory at the given address (if available) as a sequence
of words. Automatic alignment is not performed.
"""
start = int(address, 16)
if (start & self.heap.ObjectAlignmentMask()) != 0:
print "Warning: Dumping un-aligned memory, is this what you had in mind?"
for slot in xrange(start,
start + self.reader.PointerSize() * 10,
self.reader.PointerSize()):
if not self.reader.IsValidAddress(slot):
print "Address is not contained within the minidump!"
return
maybe_address = self.reader.ReadUIntPtr(slot)
heap_object = self.padawan.SenseObject(maybe_address)
print "%s: %s %s" % (self.reader.FormatIntPtr(slot),
self.reader.FormatIntPtr(maybe_address),
heap_object or '')
def do_do(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 = int(address, 16)
if (address & self.heap.ObjectAlignmentMask()) == 0:
address = address + 1
elif (address & self.heap.ObjectAlignmentMask()) != 1:
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_do_desc(self, address):
"""
Print a descriptor array in a readable format.
"""
start = int(address, 16)
if ((start & 1) == 1): start = start - 1
DescriptorArray(FixedArray(self.heap, None, start)).Print(Printer())
def do_do_map(self, address):
"""
Print a descriptor array in a readable format.
"""
start = int(address, 16)
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 = int(address, 16)
if ((start & 1) == 1): start = start - 1
TransitionArray(FixedArray(self.heap, None, start)).Print(Printer())
def do_dp(self, address):
"""
Interpret memory at the given address as being on a V8 heap page
and print information about the page header (if available).
"""
address = int(address, 16)
page_address = address & ~self.heap.PageAlignmentMask()
if self.reader.IsValidAddress(page_address):
raise NotImplementedError
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_kd(self, address):
"""
Teach V8 heap layout information to the inspector. Set the first
data-space page by passing any pointer into that page.
"""
address = int(address, 16)
page_address = address & ~self.heap.PageAlignmentMask()
self.padawan.known_first_data_page = page_address
def do_km(self, address):
"""
Teach V8 heap layout information to the inspector. Set the first
map-space page by passing any pointer into that page.
"""
address = int(address, 16)
page_address = address & ~self.heap.PageAlignmentMask()
self.padawan.known_first_map_page = page_address
def do_kp(self, address):
"""
Teach V8 heap layout information to the inspector. Set the first
pointer-space page by passing any pointer into that page.
"""
address = int(address, 16)
page_address = address & ~self.heap.PageAlignmentMask()
self.padawan.known_first_pointer_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):
"""
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):
"""
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 = int(word, 0)
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.
"""
raise NotImplementedError
def do_u(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>
"""
args = args.split(' ')
start = int(args[0], 16)
size = int(args[1], 16) if len(args) > 1 else 0x20
if not self.reader.IsValidAddress(start):
print "Address is not contained within the minidump!"
return
lines = self.reader.GetDisasmLines(start, size)
for line in lines:
print FormatDisasmLine(start, self.heap, line)
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_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
DebugPrint("========================================")
if reader.exception is None:
print "Minidump has no exception info"
else:
print "Exception info:"
exception_thread = reader.thread_map[reader.exception.thread_id]
print " thread id: %d" % exception_thread.id
print " code: %08X" % reader.exception.exception.code
print " context:"
for r in CONTEXT_FOR_ARCH[reader.arch]:
print " %s: %s" % (r, reader.FormatIntPtr(reader.Register(r)))
# TODO(vitalyr): decode eflags.
if reader.arch == MD_CPU_ARCHITECTURE_ARM:
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
stack_top = reader.ExceptionSP()
stack_bottom = exception_thread.stack.start + \
exception_thread.stack.memory.data_size
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)
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)
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):"
for slot in xrange(stack_top, stack_bottom, reader.PointerSize()):
maybe_address = reader.ReadUIntPtr(slot)
heap_object = heap.FindObject(maybe_address)
maybe_symbol = reader.FindSymbol(maybe_address)
print "%s: %s %s" % (reader.FormatIntPtr(slot),
reader.FormatIntPtr(maybe_address),
maybe_symbol or "")
if heap_object:
heap_object.Print(Printer())
print
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("-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="/usr/bin/objdump",
help="objdump tool to use [default: %default]")
options, args = parser.parse_args()
if os.path.exists(options.objdump):
disasm.OBJDUMP_BIN = options.objdump
OBJDUMP_BIN = options.objdump
else:
print "Cannot find %s, falling back to default objdump" % options.objdump
if len(args) != 1:
parser.print_help()
sys.exit(1)
AnalyzeMinidump(options, args[0])