mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 00:52:29 +00:00

This commit removes the simple/test-event-emitter-memory-leak test for being unreliable with the new garbage collector: the memory pressure exerted by the test case is too low for the garbage collector to kick in. It can be made to work again by limiting the heap size with the --max_old_space_size=x flag but that won't be very reliable across platforms and architectures.
2005 lines
68 KiB
Python
Executable File
2005 lines
68 KiB
Python
Executable File
#!/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
|
|
import v8heapconst
|
|
|
|
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)
|
|
|
|
# Heap constants generated by 'make grokdump' in v8heapconst module.
|
|
INSTANCE_TYPES = v8heapconst.INSTANCE_TYPES
|
|
KNOWN_MAPS = v8heapconst.KNOWN_MAPS
|
|
KNOWN_OBJECTS = v8heapconst.KNOWN_OBJECTS
|
|
|
|
# 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 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_X86:
|
|
return self.exception_context.ebp
|
|
|
|
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)
|
|
|
|
|
|
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):
|
|
# 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(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:
|
|
frame_pointer = reader.ExceptionFP()
|
|
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)
|
|
if slot == frame_pointer:
|
|
maybe_symbol = "<---- frame pointer"
|
|
frame_pointer = 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])
|