Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright 2012 the V8 project authors. All rights reserved.
      4 # Redistribution and use in source and binary forms, with or without
      5 # modification, are permitted provided that the following conditions are
      6 # met:
      7 #
      8 #     * Redistributions of source code must retain the above copyright
      9 #       notice, this list of conditions and the following disclaimer.
     10 #     * Redistributions in binary form must reproduce the above
     11 #       copyright notice, this list of conditions and the following
     12 #       disclaimer in the documentation and/or other materials provided
     13 #       with the distribution.
     14 #     * Neither the name of Google Inc. nor the names of its
     15 #       contributors may be used to endorse or promote products derived
     16 #       from this software without specific prior written permission.
     17 #
     18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 
     30 import BaseHTTPServer
     31 import bisect
     32 import cgi
     33 import cmd
     34 import codecs
     35 import ctypes
     36 import datetime
     37 import disasm
     38 import mmap
     39 import optparse
     40 import os
     41 import re
     42 import sys
     43 import types
     44 import urllib
     45 import urlparse
     46 import v8heapconst
     47 import webbrowser
     48 
     49 PORT_NUMBER = 8081
     50 
     51 
     52 USAGE="""usage: %prog [OPTIONS] [DUMP-FILE]
     53 
     54 Minidump analyzer.
     55 
     56 Shows the processor state at the point of exception including the
     57 stack of the active thread and the referenced objects in the V8
     58 heap. Code objects are disassembled and the addresses linked from the
     59 stack (e.g. pushed return addresses) are marked with "=>".
     60 
     61 Examples:
     62   $ %prog 12345678-1234-1234-1234-123456789abcd-full.dmp"""
     63 
     64 
     65 DEBUG=False
     66 
     67 
     68 def DebugPrint(s):
     69   if not DEBUG: return
     70   print s
     71 
     72 
     73 class Descriptor(object):
     74   """Descriptor of a structure in a memory."""
     75 
     76   def __init__(self, fields):
     77     self.fields = fields
     78     self.is_flexible = False
     79     for _, type_or_func in fields:
     80       if isinstance(type_or_func, types.FunctionType):
     81         self.is_flexible = True
     82         break
     83     if not self.is_flexible:
     84       self.ctype = Descriptor._GetCtype(fields)
     85       self.size = ctypes.sizeof(self.ctype)
     86 
     87   def Read(self, memory, offset):
     88     if self.is_flexible:
     89       fields_copy = self.fields[:]
     90       last = 0
     91       for name, type_or_func in fields_copy:
     92         if isinstance(type_or_func, types.FunctionType):
     93           partial_ctype = Descriptor._GetCtype(fields_copy[:last])
     94           partial_object = partial_ctype.from_buffer(memory, offset)
     95           type = type_or_func(partial_object)
     96           if type is not None:
     97             fields_copy[last] = (name, type)
     98             last += 1
     99         else:
    100           last += 1
    101       complete_ctype = Descriptor._GetCtype(fields_copy[:last])
    102     else:
    103       complete_ctype = self.ctype
    104     return complete_ctype.from_buffer(memory, offset)
    105 
    106   @staticmethod
    107   def _GetCtype(fields):
    108     class Raw(ctypes.Structure):
    109       _fields_ = fields
    110       _pack_ = 1
    111 
    112       def __str__(self):
    113         return "{" + ", ".join("%s: %s" % (field, self.__getattribute__(field))
    114                                for field, _ in Raw._fields_) + "}"
    115     return Raw
    116 
    117 
    118 def FullDump(reader, heap):
    119   """Dump all available memory regions."""
    120   def dump_region(reader, start, size, location):
    121     print
    122     while start & 3 != 0:
    123       start += 1
    124       size -= 1
    125       location += 1
    126     is_executable = reader.IsProbableExecutableRegion(location, size)
    127     is_ascii = reader.IsProbableASCIIRegion(location, size)
    128 
    129     if is_executable is not False:
    130       lines = reader.GetDisasmLines(start, size)
    131       for line in lines:
    132         print FormatDisasmLine(start, heap, line)
    133       print
    134 
    135     if is_ascii is not False:
    136       # Output in the same format as the Unix hd command
    137       addr = start
    138       for slot in xrange(location, location + size, 16):
    139         hex_line = ""
    140         asc_line = ""
    141         for i in xrange(0, 16):
    142           if slot + i < location + size:
    143             byte = ctypes.c_uint8.from_buffer(reader.minidump, slot + i).value
    144             if byte >= 0x20 and byte < 0x7f:
    145               asc_line += chr(byte)
    146             else:
    147               asc_line += "."
    148             hex_line += " %02x" % (byte)
    149           else:
    150             hex_line += "   "
    151           if i == 7:
    152             hex_line += " "
    153         print "%s  %s |%s|" % (reader.FormatIntPtr(addr),
    154                                hex_line,
    155                                asc_line)
    156         addr += 16
    157 
    158     if is_executable is not True and is_ascii is not True:
    159       print "%s - %s" % (reader.FormatIntPtr(start),
    160                          reader.FormatIntPtr(start + size))
    161       for slot in xrange(start,
    162                          start + size,
    163                          reader.PointerSize()):
    164         maybe_address = reader.ReadUIntPtr(slot)
    165         heap_object = heap.FindObject(maybe_address)
    166         print "%s: %s" % (reader.FormatIntPtr(slot),
    167                           reader.FormatIntPtr(maybe_address))
    168         if heap_object:
    169           heap_object.Print(Printer())
    170           print
    171 
    172   reader.ForEachMemoryRegion(dump_region)
    173 
    174 # Heap constants generated by 'make grokdump' in v8heapconst module.
    175 INSTANCE_TYPES = v8heapconst.INSTANCE_TYPES
    176 KNOWN_MAPS = v8heapconst.KNOWN_MAPS
    177 KNOWN_OBJECTS = v8heapconst.KNOWN_OBJECTS
    178 
    179 # Set of structures and constants that describe the layout of minidump
    180 # files. Based on MSDN and Google Breakpad.
    181 
    182 MINIDUMP_HEADER = Descriptor([
    183   ("signature", ctypes.c_uint32),
    184   ("version", ctypes.c_uint32),
    185   ("stream_count", ctypes.c_uint32),
    186   ("stream_directories_rva", ctypes.c_uint32),
    187   ("checksum", ctypes.c_uint32),
    188   ("time_date_stampt", ctypes.c_uint32),
    189   ("flags", ctypes.c_uint64)
    190 ])
    191 
    192 MINIDUMP_LOCATION_DESCRIPTOR = Descriptor([
    193   ("data_size", ctypes.c_uint32),
    194   ("rva", ctypes.c_uint32)
    195 ])
    196 
    197 MINIDUMP_STRING = Descriptor([
    198   ("length", ctypes.c_uint32),
    199   ("buffer", lambda t: ctypes.c_uint8 * (t.length + 2))
    200 ])
    201 
    202 MINIDUMP_DIRECTORY = Descriptor([
    203   ("stream_type", ctypes.c_uint32),
    204   ("location", MINIDUMP_LOCATION_DESCRIPTOR.ctype)
    205 ])
    206 
    207 MD_EXCEPTION_MAXIMUM_PARAMETERS = 15
    208 
    209 MINIDUMP_EXCEPTION = Descriptor([
    210   ("code", ctypes.c_uint32),
    211   ("flags", ctypes.c_uint32),
    212   ("record", ctypes.c_uint64),
    213   ("address", ctypes.c_uint64),
    214   ("parameter_count", ctypes.c_uint32),
    215   ("unused_alignment", ctypes.c_uint32),
    216   ("information", ctypes.c_uint64 * MD_EXCEPTION_MAXIMUM_PARAMETERS)
    217 ])
    218 
    219 MINIDUMP_EXCEPTION_STREAM = Descriptor([
    220   ("thread_id", ctypes.c_uint32),
    221   ("unused_alignment", ctypes.c_uint32),
    222   ("exception", MINIDUMP_EXCEPTION.ctype),
    223   ("thread_context", MINIDUMP_LOCATION_DESCRIPTOR.ctype)
    224 ])
    225 
    226 # Stream types.
    227 MD_UNUSED_STREAM = 0
    228 MD_RESERVED_STREAM_0 = 1
    229 MD_RESERVED_STREAM_1 = 2
    230 MD_THREAD_LIST_STREAM = 3
    231 MD_MODULE_LIST_STREAM = 4
    232 MD_MEMORY_LIST_STREAM = 5
    233 MD_EXCEPTION_STREAM = 6
    234 MD_SYSTEM_INFO_STREAM = 7
    235 MD_THREAD_EX_LIST_STREAM = 8
    236 MD_MEMORY_64_LIST_STREAM = 9
    237 MD_COMMENT_STREAM_A = 10
    238 MD_COMMENT_STREAM_W = 11
    239 MD_HANDLE_DATA_STREAM = 12
    240 MD_FUNCTION_TABLE_STREAM = 13
    241 MD_UNLOADED_MODULE_LIST_STREAM = 14
    242 MD_MISC_INFO_STREAM = 15
    243 MD_MEMORY_INFO_LIST_STREAM = 16
    244 MD_THREAD_INFO_LIST_STREAM = 17
    245 MD_HANDLE_OPERATION_LIST_STREAM = 18
    246 
    247 MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE = 80
    248 
    249 MINIDUMP_FLOATING_SAVE_AREA_X86 = Descriptor([
    250   ("control_word", ctypes.c_uint32),
    251   ("status_word", ctypes.c_uint32),
    252   ("tag_word", ctypes.c_uint32),
    253   ("error_offset", ctypes.c_uint32),
    254   ("error_selector", ctypes.c_uint32),
    255   ("data_offset", ctypes.c_uint32),
    256   ("data_selector", ctypes.c_uint32),
    257   ("register_area", ctypes.c_uint8 * MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE),
    258   ("cr0_npx_state", ctypes.c_uint32)
    259 ])
    260 
    261 MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE = 512
    262 
    263 # Context flags.
    264 MD_CONTEXT_X86 = 0x00010000
    265 MD_CONTEXT_X86_CONTROL = (MD_CONTEXT_X86 | 0x00000001)
    266 MD_CONTEXT_X86_INTEGER = (MD_CONTEXT_X86 | 0x00000002)
    267 MD_CONTEXT_X86_SEGMENTS = (MD_CONTEXT_X86 | 0x00000004)
    268 MD_CONTEXT_X86_FLOATING_POINT = (MD_CONTEXT_X86 | 0x00000008)
    269 MD_CONTEXT_X86_DEBUG_REGISTERS = (MD_CONTEXT_X86 | 0x00000010)
    270 MD_CONTEXT_X86_EXTENDED_REGISTERS = (MD_CONTEXT_X86 | 0x00000020)
    271 
    272 def EnableOnFlag(type, flag):
    273   return lambda o: [None, type][int((o.context_flags & flag) != 0)]
    274 
    275 MINIDUMP_CONTEXT_X86 = Descriptor([
    276   ("context_flags", ctypes.c_uint32),
    277   # MD_CONTEXT_X86_DEBUG_REGISTERS.
    278   ("dr0", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)),
    279   ("dr1", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)),
    280   ("dr2", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)),
    281   ("dr3", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)),
    282   ("dr6", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)),
    283   ("dr7", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)),
    284   # MD_CONTEXT_X86_FLOATING_POINT.
    285   ("float_save", EnableOnFlag(MINIDUMP_FLOATING_SAVE_AREA_X86.ctype,
    286                               MD_CONTEXT_X86_FLOATING_POINT)),
    287   # MD_CONTEXT_X86_SEGMENTS.
    288   ("gs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)),
    289   ("fs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)),
    290   ("es", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)),
    291   ("ds", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)),
    292   # MD_CONTEXT_X86_INTEGER.
    293   ("edi", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)),
    294   ("esi", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)),
    295   ("ebx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)),
    296   ("edx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)),
    297   ("ecx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)),
    298   ("eax", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)),
    299   # MD_CONTEXT_X86_CONTROL.
    300   ("ebp", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)),
    301   ("eip", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)),
    302   ("cs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)),
    303   ("eflags", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)),
    304   ("esp", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)),
    305   ("ss", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)),
    306   # MD_CONTEXT_X86_EXTENDED_REGISTERS.
    307   ("extended_registers",
    308    EnableOnFlag(ctypes.c_uint8 * MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE,
    309                 MD_CONTEXT_X86_EXTENDED_REGISTERS))
    310 ])
    311 
    312 MD_CONTEXT_ARM = 0x40000000
    313 MD_CONTEXT_ARM_INTEGER = (MD_CONTEXT_ARM | 0x00000002)
    314 MD_CONTEXT_ARM_FLOATING_POINT = (MD_CONTEXT_ARM | 0x00000004)
    315 MD_FLOATINGSAVEAREA_ARM_FPR_COUNT = 32
    316 MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT = 8
    317 
    318 MINIDUMP_FLOATING_SAVE_AREA_ARM = Descriptor([
    319   ("fpscr", ctypes.c_uint64),
    320   ("regs", ctypes.c_uint64 * MD_FLOATINGSAVEAREA_ARM_FPR_COUNT),
    321   ("extra", ctypes.c_uint64 * MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT)
    322 ])
    323 
    324 MINIDUMP_CONTEXT_ARM = Descriptor([
    325   ("context_flags", ctypes.c_uint32),
    326   # MD_CONTEXT_ARM_INTEGER.
    327   ("r0", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    328   ("r1", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    329   ("r2", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    330   ("r3", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    331   ("r4", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    332   ("r5", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    333   ("r6", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    334   ("r7", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    335   ("r8", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    336   ("r9", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    337   ("r10", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    338   ("r11", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    339   ("r12", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    340   ("sp", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    341   ("lr", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    342   ("pc", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_ARM_INTEGER)),
    343   ("cpsr", ctypes.c_uint32),
    344   ("float_save", EnableOnFlag(MINIDUMP_FLOATING_SAVE_AREA_ARM.ctype,
    345                               MD_CONTEXT_ARM_FLOATING_POINT))
    346 ])
    347 
    348 MD_CONTEXT_AMD64 = 0x00100000
    349 MD_CONTEXT_AMD64_CONTROL = (MD_CONTEXT_AMD64 | 0x00000001)
    350 MD_CONTEXT_AMD64_INTEGER = (MD_CONTEXT_AMD64 | 0x00000002)
    351 MD_CONTEXT_AMD64_SEGMENTS = (MD_CONTEXT_AMD64 | 0x00000004)
    352 MD_CONTEXT_AMD64_FLOATING_POINT = (MD_CONTEXT_AMD64 | 0x00000008)
    353 MD_CONTEXT_AMD64_DEBUG_REGISTERS = (MD_CONTEXT_AMD64 | 0x00000010)
    354 
    355 MINIDUMP_CONTEXT_AMD64 = Descriptor([
    356   ("p1_home", ctypes.c_uint64),
    357   ("p2_home", ctypes.c_uint64),
    358   ("p3_home", ctypes.c_uint64),
    359   ("p4_home", ctypes.c_uint64),
    360   ("p5_home", ctypes.c_uint64),
    361   ("p6_home", ctypes.c_uint64),
    362   ("context_flags", ctypes.c_uint32),
    363   ("mx_csr", ctypes.c_uint32),
    364   # MD_CONTEXT_AMD64_CONTROL.
    365   ("cs", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_CONTROL)),
    366   # MD_CONTEXT_AMD64_SEGMENTS
    367   ("ds", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_SEGMENTS)),
    368   ("es", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_SEGMENTS)),
    369   ("fs", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_SEGMENTS)),
    370   ("gs", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_SEGMENTS)),
    371   # MD_CONTEXT_AMD64_CONTROL.
    372   ("ss", EnableOnFlag(ctypes.c_uint16, MD_CONTEXT_AMD64_CONTROL)),
    373   ("eflags", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_AMD64_CONTROL)),
    374   # MD_CONTEXT_AMD64_DEBUG_REGISTERS.
    375   ("dr0", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    376   ("dr1", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    377   ("dr2", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    378   ("dr3", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    379   ("dr6", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    380   ("dr7", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    381   # MD_CONTEXT_AMD64_INTEGER.
    382   ("rax", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    383   ("rcx", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    384   ("rdx", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    385   ("rbx", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    386   # MD_CONTEXT_AMD64_CONTROL.
    387   ("rsp", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_CONTROL)),
    388   # MD_CONTEXT_AMD64_INTEGER.
    389   ("rbp", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    390   ("rsi", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    391   ("rdi", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    392   ("r8", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    393   ("r9", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    394   ("r10", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    395   ("r11", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    396   ("r12", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    397   ("r13", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    398   ("r14", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    399   ("r15", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_INTEGER)),
    400   # MD_CONTEXT_AMD64_CONTROL.
    401   ("rip", EnableOnFlag(ctypes.c_uint64, MD_CONTEXT_AMD64_CONTROL)),
    402   # MD_CONTEXT_AMD64_FLOATING_POINT
    403   ("sse_registers", EnableOnFlag(ctypes.c_uint8 * (16 * 26),
    404                                  MD_CONTEXT_AMD64_FLOATING_POINT)),
    405   ("vector_registers", EnableOnFlag(ctypes.c_uint8 * (16 * 26),
    406                                     MD_CONTEXT_AMD64_FLOATING_POINT)),
    407   ("vector_control", EnableOnFlag(ctypes.c_uint64,
    408                                   MD_CONTEXT_AMD64_FLOATING_POINT)),
    409   # MD_CONTEXT_AMD64_DEBUG_REGISTERS.
    410   ("debug_control", EnableOnFlag(ctypes.c_uint64,
    411                                  MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    412   ("last_branch_to_rip", EnableOnFlag(ctypes.c_uint64,
    413                                       MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    414   ("last_branch_from_rip", EnableOnFlag(ctypes.c_uint64,
    415                                         MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    416   ("last_exception_to_rip", EnableOnFlag(ctypes.c_uint64,
    417                                          MD_CONTEXT_AMD64_DEBUG_REGISTERS)),
    418   ("last_exception_from_rip", EnableOnFlag(ctypes.c_uint64,
    419                                            MD_CONTEXT_AMD64_DEBUG_REGISTERS))
    420 ])
    421 
    422 MINIDUMP_MEMORY_DESCRIPTOR = Descriptor([
    423   ("start", ctypes.c_uint64),
    424   ("memory", MINIDUMP_LOCATION_DESCRIPTOR.ctype)
    425 ])
    426 
    427 MINIDUMP_MEMORY_DESCRIPTOR64 = Descriptor([
    428   ("start", ctypes.c_uint64),
    429   ("size", ctypes.c_uint64)
    430 ])
    431 
    432 MINIDUMP_MEMORY_LIST = Descriptor([
    433   ("range_count", ctypes.c_uint32),
    434   ("ranges", lambda m: MINIDUMP_MEMORY_DESCRIPTOR.ctype * m.range_count)
    435 ])
    436 
    437 MINIDUMP_MEMORY_LIST64 = Descriptor([
    438   ("range_count", ctypes.c_uint64),
    439   ("base_rva", ctypes.c_uint64),
    440   ("ranges", lambda m: MINIDUMP_MEMORY_DESCRIPTOR64.ctype * m.range_count)
    441 ])
    442 
    443 MINIDUMP_THREAD = Descriptor([
    444   ("id", ctypes.c_uint32),
    445   ("suspend_count", ctypes.c_uint32),
    446   ("priority_class", ctypes.c_uint32),
    447   ("priority", ctypes.c_uint32),
    448   ("ted", ctypes.c_uint64),
    449   ("stack", MINIDUMP_MEMORY_DESCRIPTOR.ctype),
    450   ("context", MINIDUMP_LOCATION_DESCRIPTOR.ctype)
    451 ])
    452 
    453 MINIDUMP_THREAD_LIST = Descriptor([
    454   ("thread_count", ctypes.c_uint32),
    455   ("threads", lambda t: MINIDUMP_THREAD.ctype * t.thread_count)
    456 ])
    457 
    458 MINIDUMP_VS_FIXEDFILEINFO = Descriptor([
    459   ("dwSignature", ctypes.c_uint32),
    460   ("dwStrucVersion", ctypes.c_uint32),
    461   ("dwFileVersionMS", ctypes.c_uint32),
    462   ("dwFileVersionLS", ctypes.c_uint32),
    463   ("dwProductVersionMS", ctypes.c_uint32),
    464   ("dwProductVersionLS", ctypes.c_uint32),
    465   ("dwFileFlagsMask", ctypes.c_uint32),
    466   ("dwFileFlags", ctypes.c_uint32),
    467   ("dwFileOS", ctypes.c_uint32),
    468   ("dwFileType", ctypes.c_uint32),
    469   ("dwFileSubtype", ctypes.c_uint32),
    470   ("dwFileDateMS", ctypes.c_uint32),
    471   ("dwFileDateLS", ctypes.c_uint32)
    472 ])
    473 
    474 MINIDUMP_RAW_MODULE = Descriptor([
    475   ("base_of_image", ctypes.c_uint64),
    476   ("size_of_image", ctypes.c_uint32),
    477   ("checksum", ctypes.c_uint32),
    478   ("time_date_stamp", ctypes.c_uint32),
    479   ("module_name_rva", ctypes.c_uint32),
    480   ("version_info", MINIDUMP_VS_FIXEDFILEINFO.ctype),
    481   ("cv_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype),
    482   ("misc_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype),
    483   ("reserved0", ctypes.c_uint32 * 2),
    484   ("reserved1", ctypes.c_uint32 * 2)
    485 ])
    486 
    487 MINIDUMP_MODULE_LIST = Descriptor([
    488   ("number_of_modules", ctypes.c_uint32),
    489   ("modules", lambda t: MINIDUMP_RAW_MODULE.ctype * t.number_of_modules)
    490 ])
    491 
    492 MINIDUMP_RAW_SYSTEM_INFO = Descriptor([
    493   ("processor_architecture", ctypes.c_uint16)
    494 ])
    495 
    496 MD_CPU_ARCHITECTURE_X86 = 0
    497 MD_CPU_ARCHITECTURE_ARM = 5
    498 MD_CPU_ARCHITECTURE_AMD64 = 9
    499 
    500 class FuncSymbol:
    501   def __init__(self, start, size, name):
    502     self.start = start
    503     self.end = self.start + size
    504     self.name = name
    505 
    506   def __cmp__(self, other):
    507     if isinstance(other, FuncSymbol):
    508       return self.start - other.start
    509     return self.start - other
    510 
    511   def Covers(self, addr):
    512     return (self.start <= addr) and (addr < self.end)
    513 
    514 class MinidumpReader(object):
    515   """Minidump (.dmp) reader."""
    516 
    517   _HEADER_MAGIC = 0x504d444d
    518 
    519   def __init__(self, options, minidump_name):
    520     self.minidump_name = minidump_name
    521     self.minidump_file = open(minidump_name, "r")
    522     self.minidump = mmap.mmap(self.minidump_file.fileno(), 0, mmap.MAP_PRIVATE)
    523     self.header = MINIDUMP_HEADER.Read(self.minidump, 0)
    524     if self.header.signature != MinidumpReader._HEADER_MAGIC:
    525       print >>sys.stderr, "Warning: Unsupported minidump header magic!"
    526     DebugPrint(self.header)
    527     directories = []
    528     offset = self.header.stream_directories_rva
    529     for _ in xrange(self.header.stream_count):
    530       directories.append(MINIDUMP_DIRECTORY.Read(self.minidump, offset))
    531       offset += MINIDUMP_DIRECTORY.size
    532     self.arch = None
    533     self.exception = None
    534     self.exception_context = None
    535     self.memory_list = None
    536     self.memory_list64 = None
    537     self.module_list = None
    538     self.thread_map = {}
    539 
    540     self.symdir = options.symdir
    541     self.modules_with_symbols = []
    542     self.symbols = []
    543 
    544     # Find MDRawSystemInfo stream and determine arch.
    545     for d in directories:
    546       if d.stream_type == MD_SYSTEM_INFO_STREAM:
    547         system_info = MINIDUMP_RAW_SYSTEM_INFO.Read(
    548             self.minidump, d.location.rva)
    549         self.arch = system_info.processor_architecture
    550         assert self.arch in [MD_CPU_ARCHITECTURE_AMD64,
    551                              MD_CPU_ARCHITECTURE_ARM,
    552                              MD_CPU_ARCHITECTURE_X86]
    553     assert not self.arch is None
    554 
    555     for d in directories:
    556       DebugPrint(d)
    557       if d.stream_type == MD_EXCEPTION_STREAM:
    558         self.exception = MINIDUMP_EXCEPTION_STREAM.Read(
    559           self.minidump, d.location.rva)
    560         DebugPrint(self.exception)
    561         if self.arch == MD_CPU_ARCHITECTURE_X86:
    562           self.exception_context = MINIDUMP_CONTEXT_X86.Read(
    563               self.minidump, self.exception.thread_context.rva)
    564         elif self.arch == MD_CPU_ARCHITECTURE_AMD64:
    565           self.exception_context = MINIDUMP_CONTEXT_AMD64.Read(
    566               self.minidump, self.exception.thread_context.rva)
    567         elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    568           self.exception_context = MINIDUMP_CONTEXT_ARM.Read(
    569               self.minidump, self.exception.thread_context.rva)
    570         DebugPrint(self.exception_context)
    571       elif d.stream_type == MD_THREAD_LIST_STREAM:
    572         thread_list = MINIDUMP_THREAD_LIST.Read(self.minidump, d.location.rva)
    573         assert ctypes.sizeof(thread_list) == d.location.data_size
    574         DebugPrint(thread_list)
    575         for thread in thread_list.threads:
    576           DebugPrint(thread)
    577           self.thread_map[thread.id] = thread
    578       elif d.stream_type == MD_MODULE_LIST_STREAM:
    579         assert self.module_list is None
    580         self.module_list = MINIDUMP_MODULE_LIST.Read(
    581           self.minidump, d.location.rva)
    582         assert ctypes.sizeof(self.module_list) == d.location.data_size
    583       elif d.stream_type == MD_MEMORY_LIST_STREAM:
    584         print >>sys.stderr, "Warning: This is not a full minidump!"
    585         assert self.memory_list is None
    586         self.memory_list = MINIDUMP_MEMORY_LIST.Read(
    587           self.minidump, d.location.rva)
    588         assert ctypes.sizeof(self.memory_list) == d.location.data_size
    589         DebugPrint(self.memory_list)
    590       elif d.stream_type == MD_MEMORY_64_LIST_STREAM:
    591         assert self.memory_list64 is None
    592         self.memory_list64 = MINIDUMP_MEMORY_LIST64.Read(
    593           self.minidump, d.location.rva)
    594         assert ctypes.sizeof(self.memory_list64) == d.location.data_size
    595         DebugPrint(self.memory_list64)
    596 
    597   def IsValidAddress(self, address):
    598     return self.FindLocation(address) is not None
    599 
    600   def ReadU8(self, address):
    601     location = self.FindLocation(address)
    602     return ctypes.c_uint8.from_buffer(self.minidump, location).value
    603 
    604   def ReadU32(self, address):
    605     location = self.FindLocation(address)
    606     return ctypes.c_uint32.from_buffer(self.minidump, location).value
    607 
    608   def ReadU64(self, address):
    609     location = self.FindLocation(address)
    610     return ctypes.c_uint64.from_buffer(self.minidump, location).value
    611 
    612   def ReadUIntPtr(self, address):
    613     if self.arch == MD_CPU_ARCHITECTURE_AMD64:
    614       return self.ReadU64(address)
    615     elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    616       return self.ReadU32(address)
    617     elif self.arch == MD_CPU_ARCHITECTURE_X86:
    618       return self.ReadU32(address)
    619 
    620   def ReadBytes(self, address, size):
    621     location = self.FindLocation(address)
    622     return self.minidump[location:location + size]
    623 
    624   def _ReadWord(self, location):
    625     if self.arch == MD_CPU_ARCHITECTURE_AMD64:
    626       return ctypes.c_uint64.from_buffer(self.minidump, location).value
    627     elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    628       return ctypes.c_uint32.from_buffer(self.minidump, location).value
    629     elif self.arch == MD_CPU_ARCHITECTURE_X86:
    630       return ctypes.c_uint32.from_buffer(self.minidump, location).value
    631 
    632   def IsProbableASCIIRegion(self, location, length):
    633     ascii_bytes = 0
    634     non_ascii_bytes = 0
    635     for loc in xrange(location, location + length):
    636       byte = ctypes.c_uint8.from_buffer(self.minidump, loc).value
    637       if byte >= 0x7f:
    638         non_ascii_bytes += 1
    639       if byte < 0x20 and byte != 0:
    640         non_ascii_bytes += 1
    641       if byte < 0x7f and byte >= 0x20:
    642         ascii_bytes += 1
    643       if byte == 0xa:  # newline
    644         ascii_bytes += 1
    645     if ascii_bytes * 10 <= length:
    646       return False
    647     if length > 0 and ascii_bytes > non_ascii_bytes * 7:
    648       return True
    649     if ascii_bytes > non_ascii_bytes * 3:
    650       return None  # Maybe
    651     return False
    652 
    653   def IsProbableExecutableRegion(self, location, length):
    654     opcode_bytes = 0
    655     sixty_four = self.arch == MD_CPU_ARCHITECTURE_AMD64
    656     for loc in xrange(location, location + length):
    657       byte = ctypes.c_uint8.from_buffer(self.minidump, loc).value
    658       if (byte == 0x8b or           # mov
    659           byte == 0x89 or           # mov reg-reg
    660           (byte & 0xf0) == 0x50 or  # push/pop
    661           (sixty_four and (byte & 0xf0) == 0x40) or  # rex prefix
    662           byte == 0xc3 or           # return
    663           byte == 0x74 or           # jeq
    664           byte == 0x84 or           # jeq far
    665           byte == 0x75 or           # jne
    666           byte == 0x85 or           # jne far
    667           byte == 0xe8 or           # call
    668           byte == 0xe9 or           # jmp far
    669           byte == 0xeb):            # jmp near
    670         opcode_bytes += 1
    671     opcode_percent = (opcode_bytes * 100) / length
    672     threshold = 20
    673     if opcode_percent > threshold + 2:
    674       return True
    675     if opcode_percent > threshold - 2:
    676       return None  # Maybe
    677     return False
    678 
    679   def FindRegion(self, addr):
    680     answer = [-1, -1]
    681     def is_in(reader, start, size, location):
    682       if addr >= start and addr < start + size:
    683         answer[0] = start
    684         answer[1] = size
    685     self.ForEachMemoryRegion(is_in)
    686     if answer[0] == -1:
    687       return None
    688     return answer
    689 
    690   def ForEachMemoryRegion(self, cb):
    691     if self.memory_list64 is not None:
    692       for r in self.memory_list64.ranges:
    693         location = self.memory_list64.base_rva + offset
    694         cb(self, r.start, r.size, location)
    695         offset += r.size
    696 
    697     if self.memory_list is not None:
    698       for r in self.memory_list.ranges:
    699         cb(self, r.start, r.memory.data_size, r.memory.rva)
    700 
    701   def FindWord(self, word, alignment=0):
    702     def search_inside_region(reader, start, size, location):
    703       location = (location + alignment) & ~alignment
    704       for loc in xrange(location, location + size - self.PointerSize()):
    705         if reader._ReadWord(loc) == word:
    706           slot = start + (loc - location)
    707           print "%s: %s" % (reader.FormatIntPtr(slot),
    708                             reader.FormatIntPtr(word))
    709     self.ForEachMemoryRegion(search_inside_region)
    710 
    711   def FindWordList(self, word):
    712     aligned_res = []
    713     unaligned_res = []
    714     def search_inside_region(reader, start, size, location):
    715       for loc in xrange(location, location + size - self.PointerSize()):
    716         if reader._ReadWord(loc) == word:
    717           slot = start + (loc - location)
    718           if slot % self.PointerSize() == 0:
    719             aligned_res.append(slot)
    720           else:
    721             unaligned_res.append(slot)
    722     self.ForEachMemoryRegion(search_inside_region)
    723     return (aligned_res, unaligned_res)
    724 
    725   def FindLocation(self, address):
    726     offset = 0
    727     if self.memory_list64 is not None:
    728       for r in self.memory_list64.ranges:
    729         if r.start <= address < r.start + r.size:
    730           return self.memory_list64.base_rva + offset + address - r.start
    731         offset += r.size
    732     if self.memory_list is not None:
    733       for r in self.memory_list.ranges:
    734         if r.start <= address < r.start + r.memory.data_size:
    735           return r.memory.rva + address - r.start
    736     return None
    737 
    738   def GetDisasmLines(self, address, size):
    739     def CountUndefinedInstructions(lines):
    740       pattern = "<UNDEFINED>"
    741       return sum([line.count(pattern) for (ignore, line) in lines])
    742 
    743     location = self.FindLocation(address)
    744     if location is None: return []
    745     arch = None
    746     possible_objdump_flags = [""]
    747     if self.arch == MD_CPU_ARCHITECTURE_X86:
    748       arch = "ia32"
    749     elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    750       arch = "arm"
    751       possible_objdump_flags = ["", "--disassembler-options=force-thumb"]
    752     elif self.arch == MD_CPU_ARCHITECTURE_AMD64:
    753       arch = "x64"
    754     results = [ disasm.GetDisasmLines(self.minidump_name,
    755                                      location,
    756                                      size,
    757                                      arch,
    758                                      False,
    759                                      objdump_flags)
    760                 for objdump_flags in possible_objdump_flags ]
    761     return min(results, key=CountUndefinedInstructions)
    762 
    763 
    764   def Dispose(self):
    765     self.minidump.close()
    766     self.minidump_file.close()
    767 
    768   def ExceptionIP(self):
    769     if self.arch == MD_CPU_ARCHITECTURE_AMD64:
    770       return self.exception_context.rip
    771     elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    772       return self.exception_context.pc
    773     elif self.arch == MD_CPU_ARCHITECTURE_X86:
    774       return self.exception_context.eip
    775 
    776   def ExceptionSP(self):
    777     if self.arch == MD_CPU_ARCHITECTURE_AMD64:
    778       return self.exception_context.rsp
    779     elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    780       return self.exception_context.sp
    781     elif self.arch == MD_CPU_ARCHITECTURE_X86:
    782       return self.exception_context.esp
    783 
    784   def ExceptionFP(self):
    785     if self.arch == MD_CPU_ARCHITECTURE_AMD64:
    786       return self.exception_context.rbp
    787     elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    788       return None
    789     elif self.arch == MD_CPU_ARCHITECTURE_X86:
    790       return self.exception_context.ebp
    791 
    792   def FormatIntPtr(self, value):
    793     if self.arch == MD_CPU_ARCHITECTURE_AMD64:
    794       return "%016x" % value
    795     elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    796       return "%08x" % value
    797     elif self.arch == MD_CPU_ARCHITECTURE_X86:
    798       return "%08x" % value
    799 
    800   def PointerSize(self):
    801     if self.arch == MD_CPU_ARCHITECTURE_AMD64:
    802       return 8
    803     elif self.arch == MD_CPU_ARCHITECTURE_ARM:
    804       return 4
    805     elif self.arch == MD_CPU_ARCHITECTURE_X86:
    806       return 4
    807 
    808   def Register(self, name):
    809     return self.exception_context.__getattribute__(name)
    810 
    811   def ReadMinidumpString(self, rva):
    812     string = bytearray(MINIDUMP_STRING.Read(self.minidump, rva).buffer)
    813     string = string.decode("utf16")
    814     return string[0:len(string) - 1]
    815 
    816   # Load FUNC records from a BreakPad symbol file
    817   #
    818   #    http://code.google.com/p/google-breakpad/wiki/SymbolFiles
    819   #
    820   def _LoadSymbolsFrom(self, symfile, baseaddr):
    821     print "Loading symbols from %s" % (symfile)
    822     funcs = []
    823     with open(symfile) as f:
    824       for line in f:
    825         result = re.match(
    826             r"^FUNC ([a-f0-9]+) ([a-f0-9]+) ([a-f0-9]+) (.*)$", line)
    827         if result is not None:
    828           start = int(result.group(1), 16)
    829           size = int(result.group(2), 16)
    830           name = result.group(4).rstrip()
    831           bisect.insort_left(self.symbols,
    832                              FuncSymbol(baseaddr + start, size, name))
    833     print " ... done"
    834 
    835   def TryLoadSymbolsFor(self, modulename, module):
    836     try:
    837       symfile = os.path.join(self.symdir,
    838                              modulename.replace('.', '_') + ".pdb.sym")
    839       if os.path.isfile(symfile):
    840         self._LoadSymbolsFrom(symfile, module.base_of_image)
    841         self.modules_with_symbols.append(module)
    842     except Exception as e:
    843       print "  ... failure (%s)" % (e)
    844 
    845   # Returns true if address is covered by some module that has loaded symbols.
    846   def _IsInModuleWithSymbols(self, addr):
    847     for module in self.modules_with_symbols:
    848       start = module.base_of_image
    849       end = start + module.size_of_image
    850       if (start <= addr) and (addr < end):
    851         return True
    852     return False
    853 
    854   # Find symbol covering the given address and return its name in format
    855   #     <symbol name>+<offset from the start>
    856   def FindSymbol(self, addr):
    857     if not self._IsInModuleWithSymbols(addr):
    858       return None
    859 
    860     i = bisect.bisect_left(self.symbols, addr)
    861     symbol = None
    862     if (0 < i) and self.symbols[i - 1].Covers(addr):
    863       symbol = self.symbols[i - 1]
    864     elif (i < len(self.symbols)) and self.symbols[i].Covers(addr):
    865       symbol = self.symbols[i]
    866     else:
    867       return None
    868     diff = addr - symbol.start
    869     return "%s+0x%x" % (symbol.name, diff)
    870 
    871 
    872 class Printer(object):
    873   """Printer with indentation support."""
    874 
    875   def __init__(self):
    876     self.indent = 0
    877 
    878   def Indent(self):
    879     self.indent += 2
    880 
    881   def Dedent(self):
    882     self.indent -= 2
    883 
    884   def Print(self, string):
    885     print "%s%s" % (self._IndentString(), string)
    886 
    887   def PrintLines(self, lines):
    888     indent = self._IndentString()
    889     print "\n".join("%s%s" % (indent, line) for line in lines)
    890 
    891   def _IndentString(self):
    892     return self.indent * " "
    893 
    894 
    895 ADDRESS_RE = re.compile(r"0x[0-9a-fA-F]+")
    896 
    897 
    898 def FormatDisasmLine(start, heap, line):
    899   line_address = start + line[0]
    900   stack_slot = heap.stack_map.get(line_address)
    901   marker = "  "
    902   if stack_slot:
    903     marker = "=>"
    904   code = AnnotateAddresses(heap, line[1])
    905 
    906   # Compute the actual call target which the disassembler is too stupid
    907   # to figure out (it adds the call offset to the disassembly offset rather
    908   # than the absolute instruction address).
    909   if heap.reader.arch == MD_CPU_ARCHITECTURE_X86:
    910     if code.startswith("e8"):
    911       words = code.split()
    912       if len(words) > 6 and words[5] == "call":
    913         offset = int(words[4] + words[3] + words[2] + words[1], 16)
    914         target = (line_address + offset + 5) & 0xFFFFFFFF
    915         code = code.replace(words[6], "0x%08x" % target)
    916   # TODO(jkummerow): port this hack to ARM and x64.
    917 
    918   return "%s%08x %08x: %s" % (marker, line_address, line[0], code)
    919 
    920 
    921 def AnnotateAddresses(heap, line):
    922   extra = []
    923   for m in ADDRESS_RE.finditer(line):
    924     maybe_address = int(m.group(0), 16)
    925     object = heap.FindObject(maybe_address)
    926     if not object: continue
    927     extra.append(str(object))
    928   if len(extra) == 0: return line
    929   return "%s  ;; %s" % (line, ", ".join(extra))
    930 
    931 
    932 class HeapObject(object):
    933   def __init__(self, heap, map, address):
    934     self.heap = heap
    935     self.map = map
    936     self.address = address
    937 
    938   def Is(self, cls):
    939     return isinstance(self, cls)
    940 
    941   def Print(self, p):
    942     p.Print(str(self))
    943 
    944   def __str__(self):
    945     return "HeapObject(%s, %s)" % (self.heap.reader.FormatIntPtr(self.address),
    946                                    INSTANCE_TYPES[self.map.instance_type])
    947 
    948   def ObjectField(self, offset):
    949     field_value = self.heap.reader.ReadUIntPtr(self.address + offset)
    950     return self.heap.FindObjectOrSmi(field_value)
    951 
    952   def SmiField(self, offset):
    953     field_value = self.heap.reader.ReadUIntPtr(self.address + offset)
    954     if (field_value & 1) == 0:
    955       return field_value / 2
    956     return None
    957 
    958 
    959 class Map(HeapObject):
    960   def Decode(self, offset, size, value):
    961     return (value >> offset) & ((1 << size) - 1)
    962 
    963   # Instance Sizes
    964   def InstanceSizesOffset(self):
    965     return self.heap.PointerSize()
    966 
    967   def InstanceSizeOffset(self):
    968     return self.InstanceSizesOffset()
    969 
    970   def InObjectProperties(self):
    971     return self.InstanceSizeOffset() + 1
    972 
    973   def PreAllocatedPropertyFields(self):
    974     return self.InObjectProperties() + 1
    975 
    976   def VisitorId(self):
    977     return self.PreAllocatedPropertyFields() + 1
    978 
    979   # Instance Attributes
    980   def InstanceAttributesOffset(self):
    981     return self.InstanceSizesOffset() + self.heap.IntSize()
    982 
    983   def InstanceTypeOffset(self):
    984     return self.InstanceAttributesOffset()
    985 
    986   def UnusedPropertyFieldsOffset(self):
    987     return self.InstanceTypeOffset() + 1
    988 
    989   def BitFieldOffset(self):
    990     return self.UnusedPropertyFieldsOffset() + 1
    991 
    992   def BitField2Offset(self):
    993     return self.BitFieldOffset() + 1
    994 
    995   # Other fields
    996   def PrototypeOffset(self):
    997     return self.InstanceAttributesOffset() + self.heap.IntSize()
    998 
    999   def ConstructorOffset(self):
   1000     return self.PrototypeOffset() + self.heap.PointerSize()
   1001 
   1002   def TransitionsOrBackPointerOffset(self):
   1003     return self.ConstructorOffset() + self.heap.PointerSize()
   1004 
   1005   def DescriptorsOffset(self):
   1006     return self.TransitionsOrBackPointerOffset() + self.heap.PointerSize()
   1007 
   1008   def CodeCacheOffset(self):
   1009     return self.DescriptorsOffset() + self.heap.PointerSize()
   1010 
   1011   def DependentCodeOffset(self):
   1012     return self.CodeCacheOffset() + self.heap.PointerSize()
   1013 
   1014   def BitField3Offset(self):
   1015     return self.DependentCodeOffset() + self.heap.PointerSize()
   1016 
   1017   def ReadByte(self, offset):
   1018     return self.heap.reader.ReadU8(self.address + offset)
   1019 
   1020   def Print(self, p):
   1021     p.Print("Map(%08x)" % (self.address))
   1022     p.Print("- size: %d, inobject: %d, preallocated: %d, visitor: %d" % (
   1023         self.ReadByte(self.InstanceSizeOffset()),
   1024         self.ReadByte(self.InObjectProperties()),
   1025         self.ReadByte(self.PreAllocatedPropertyFields()),
   1026         self.VisitorId()))
   1027 
   1028     bitfield = self.ReadByte(self.BitFieldOffset())
   1029     bitfield2 = self.ReadByte(self.BitField2Offset())
   1030     p.Print("- %s, unused: %d, bf: %d, bf2: %d" % (
   1031         INSTANCE_TYPES[self.ReadByte(self.InstanceTypeOffset())],
   1032         self.ReadByte(self.UnusedPropertyFieldsOffset()),
   1033         bitfield, bitfield2))
   1034 
   1035     p.Print("- kind: %s" % (self.Decode(3, 5, bitfield2)))
   1036 
   1037     bitfield3 = self.ObjectField(self.BitField3Offset())
   1038     p.Print(
   1039         "- EnumLength: %d NumberOfOwnDescriptors: %d OwnsDescriptors: %s" % (
   1040             self.Decode(0, 11, bitfield3),
   1041             self.Decode(11, 11, bitfield3),
   1042             self.Decode(25, 1, bitfield3)))
   1043     p.Print("- IsShared: %s" % (self.Decode(22, 1, bitfield3)))
   1044     p.Print("- FunctionWithPrototype: %s" % (self.Decode(23, 1, bitfield3)))
   1045     p.Print("- DictionaryMap: %s" % (self.Decode(24, 1, bitfield3)))
   1046 
   1047     descriptors = self.ObjectField(self.DescriptorsOffset())
   1048     if descriptors.__class__ == FixedArray:
   1049       DescriptorArray(descriptors).Print(p)
   1050     else:
   1051       p.Print("Descriptors: %s" % (descriptors))
   1052 
   1053     transitions = self.ObjectField(self.TransitionsOrBackPointerOffset())
   1054     if transitions.__class__ == FixedArray:
   1055       TransitionArray(transitions).Print(p)
   1056     else:
   1057       p.Print("TransitionsOrBackPointer: %s" % (transitions))
   1058 
   1059   def __init__(self, heap, map, address):
   1060     HeapObject.__init__(self, heap, map, address)
   1061     self.instance_type = \
   1062         heap.reader.ReadU8(self.address + self.InstanceTypeOffset())
   1063 
   1064 
   1065 class String(HeapObject):
   1066   def LengthOffset(self):
   1067     # First word after the map is the hash, the second is the length.
   1068     return self.heap.PointerSize() * 2
   1069 
   1070   def __init__(self, heap, map, address):
   1071     HeapObject.__init__(self, heap, map, address)
   1072     self.length = self.SmiField(self.LengthOffset())
   1073 
   1074   def GetChars(self):
   1075     return "?string?"
   1076 
   1077   def Print(self, p):
   1078     p.Print(str(self))
   1079 
   1080   def __str__(self):
   1081     return "\"%s\"" % self.GetChars()
   1082 
   1083 
   1084 class SeqString(String):
   1085   def CharsOffset(self):
   1086     return self.heap.PointerSize() * 3
   1087 
   1088   def __init__(self, heap, map, address):
   1089     String.__init__(self, heap, map, address)
   1090     self.chars = heap.reader.ReadBytes(self.address + self.CharsOffset(),
   1091                                        self.length)
   1092 
   1093   def GetChars(self):
   1094     return self.chars
   1095 
   1096 
   1097 class ExternalString(String):
   1098   # TODO(vegorov) fix ExternalString for X64 architecture
   1099   RESOURCE_OFFSET = 12
   1100 
   1101   WEBKIT_RESOUCE_STRING_IMPL_OFFSET = 4
   1102   WEBKIT_STRING_IMPL_CHARS_OFFSET = 8
   1103 
   1104   def __init__(self, heap, map, address):
   1105     String.__init__(self, heap, map, address)
   1106     reader = heap.reader
   1107     self.resource = \
   1108         reader.ReadU32(self.address + ExternalString.RESOURCE_OFFSET)
   1109     self.chars = "?external string?"
   1110     if not reader.IsValidAddress(self.resource): return
   1111     string_impl_address = self.resource + \
   1112         ExternalString.WEBKIT_RESOUCE_STRING_IMPL_OFFSET
   1113     if not reader.IsValidAddress(string_impl_address): return
   1114     string_impl = reader.ReadU32(string_impl_address)
   1115     chars_ptr_address = string_impl + \
   1116         ExternalString.WEBKIT_STRING_IMPL_CHARS_OFFSET
   1117     if not reader.IsValidAddress(chars_ptr_address): return
   1118     chars_ptr = reader.ReadU32(chars_ptr_address)
   1119     if not reader.IsValidAddress(chars_ptr): return
   1120     raw_chars = reader.ReadBytes(chars_ptr, 2 * self.length)
   1121     self.chars = codecs.getdecoder("utf16")(raw_chars)[0]
   1122 
   1123   def GetChars(self):
   1124     return self.chars
   1125 
   1126 
   1127 class ConsString(String):
   1128   def LeftOffset(self):
   1129     return self.heap.PointerSize() * 3
   1130 
   1131   def RightOffset(self):
   1132     return self.heap.PointerSize() * 4
   1133 
   1134   def __init__(self, heap, map, address):
   1135     String.__init__(self, heap, map, address)
   1136     self.left = self.ObjectField(self.LeftOffset())
   1137     self.right = self.ObjectField(self.RightOffset())
   1138 
   1139   def GetChars(self):
   1140     try:
   1141       return self.left.GetChars() + self.right.GetChars()
   1142     except:
   1143       return "***CAUGHT EXCEPTION IN GROKDUMP***"
   1144 
   1145 
   1146 class Oddball(HeapObject):
   1147   # Should match declarations in objects.h
   1148   KINDS = [
   1149     "False",
   1150     "True",
   1151     "TheHole",
   1152     "Null",
   1153     "ArgumentMarker",
   1154     "Undefined",
   1155     "Other"
   1156   ]
   1157 
   1158   def ToStringOffset(self):
   1159     return self.heap.PointerSize()
   1160 
   1161   def ToNumberOffset(self):
   1162     return self.ToStringOffset() + self.heap.PointerSize()
   1163 
   1164   def KindOffset(self):
   1165     return self.ToNumberOffset() + self.heap.PointerSize()
   1166 
   1167   def __init__(self, heap, map, address):
   1168     HeapObject.__init__(self, heap, map, address)
   1169     self.to_string = self.ObjectField(self.ToStringOffset())
   1170     self.kind = self.SmiField(self.KindOffset())
   1171 
   1172   def Print(self, p):
   1173     p.Print(str(self))
   1174 
   1175   def __str__(self):
   1176     if self.to_string:
   1177       return "Oddball(%08x, <%s>)" % (self.address, str(self.to_string))
   1178     else:
   1179       kind = "???"
   1180       if 0 <= self.kind < len(Oddball.KINDS):
   1181         kind = Oddball.KINDS[self.kind]
   1182       return "Oddball(%08x, kind=%s)" % (self.address, kind)
   1183 
   1184 
   1185 class FixedArray(HeapObject):
   1186   def LengthOffset(self):
   1187     return self.heap.PointerSize()
   1188 
   1189   def ElementsOffset(self):
   1190     return self.heap.PointerSize() * 2
   1191 
   1192   def MemberOffset(self, i):
   1193     return self.ElementsOffset() + self.heap.PointerSize() * i
   1194 
   1195   def Get(self, i):
   1196     return self.ObjectField(self.MemberOffset(i))
   1197 
   1198   def __init__(self, heap, map, address):
   1199     HeapObject.__init__(self, heap, map, address)
   1200     self.length = self.SmiField(self.LengthOffset())
   1201 
   1202   def Print(self, p):
   1203     p.Print("FixedArray(%s) {" % self.heap.reader.FormatIntPtr(self.address))
   1204     p.Indent()
   1205     p.Print("length: %d" % self.length)
   1206     base_offset = self.ElementsOffset()
   1207     for i in xrange(self.length):
   1208       offset = base_offset + 4 * i
   1209       try:
   1210         p.Print("[%08d] = %s" % (i, self.ObjectField(offset)))
   1211       except TypeError:
   1212         p.Dedent()
   1213         p.Print("...")
   1214         p.Print("}")
   1215         return
   1216     p.Dedent()
   1217     p.Print("}")
   1218 
   1219   def __str__(self):
   1220     return "FixedArray(%08x, length=%d)" % (self.address, self.length)
   1221 
   1222 
   1223 class DescriptorArray(object):
   1224   def __init__(self, array):
   1225     self.array = array
   1226 
   1227   def Length(self):
   1228     return self.array.Get(0)
   1229 
   1230   def Decode(self, offset, size, value):
   1231     return (value >> offset) & ((1 << size) - 1)
   1232 
   1233   TYPES = [
   1234       "normal",
   1235       "field",
   1236       "function",
   1237       "callbacks"
   1238   ]
   1239 
   1240   def Type(self, value):
   1241     return DescriptorArray.TYPES[self.Decode(0, 3, value)]
   1242 
   1243   def Attributes(self, value):
   1244     attributes = self.Decode(3, 3, value)
   1245     result = []
   1246     if (attributes & 0): result += ["ReadOnly"]
   1247     if (attributes & 1): result += ["DontEnum"]
   1248     if (attributes & 2): result += ["DontDelete"]
   1249     return "[" + (",".join(result)) + "]"
   1250 
   1251   def Deleted(self, value):
   1252     return self.Decode(6, 1, value) == 1
   1253 
   1254   def FieldIndex(self, value):
   1255     return self.Decode(20, 11, value)
   1256 
   1257   def Pointer(self, value):
   1258     return self.Decode(6, 11, value)
   1259 
   1260   def Details(self, di, value):
   1261     return (
   1262         di,
   1263         self.Type(value),
   1264         self.Attributes(value),
   1265         self.FieldIndex(value),
   1266         self.Pointer(value)
   1267     )
   1268 
   1269 
   1270   def Print(self, p):
   1271     length = self.Length()
   1272     array = self.array
   1273 
   1274     p.Print("Descriptors(%08x, length=%d)" % (array.address, length))
   1275     p.Print("[et] %s" % (array.Get(1)))
   1276 
   1277     for di in xrange(length):
   1278       i = 2 + di * 3
   1279       p.Print("0x%x" % (array.address + array.MemberOffset(i)))
   1280       p.Print("[%i] name:    %s" % (di, array.Get(i + 0)))
   1281       p.Print("[%i] details: %s %s field-index %i pointer %i" % \
   1282               self.Details(di, array.Get(i + 1)))
   1283       p.Print("[%i] value:   %s" % (di, array.Get(i + 2)))
   1284 
   1285     end = self.array.length // 3
   1286     if length != end:
   1287       p.Print("[%i-%i] slack descriptors" % (length, end))
   1288 
   1289 
   1290 class TransitionArray(object):
   1291   def __init__(self, array):
   1292     self.array = array
   1293 
   1294   def IsSimpleTransition(self):
   1295     return self.array.length <= 2
   1296 
   1297   def Length(self):
   1298     # SimpleTransition cases
   1299     if self.IsSimpleTransition():
   1300       return self.array.length - 1
   1301     return (self.array.length - 3) // 2
   1302 
   1303   def Print(self, p):
   1304     length = self.Length()
   1305     array = self.array
   1306 
   1307     p.Print("Transitions(%08x, length=%d)" % (array.address, length))
   1308     p.Print("[backpointer] %s" % (array.Get(0)))
   1309     if self.IsSimpleTransition():
   1310       if length == 1:
   1311         p.Print("[simple target] %s" % (array.Get(1)))
   1312       return
   1313 
   1314     elements = array.Get(1)
   1315     if elements is not None:
   1316       p.Print("[elements   ] %s" % (elements))
   1317 
   1318     prototype = array.Get(2)
   1319     if prototype is not None:
   1320       p.Print("[prototype  ] %s" % (prototype))
   1321 
   1322     for di in xrange(length):
   1323       i = 3 + di * 2
   1324       p.Print("[%i] symbol: %s" % (di, array.Get(i + 0)))
   1325       p.Print("[%i] target: %s" % (di, array.Get(i + 1)))
   1326 
   1327 
   1328 class JSFunction(HeapObject):
   1329   def CodeEntryOffset(self):
   1330     return 3 * self.heap.PointerSize()
   1331 
   1332   def SharedOffset(self):
   1333     return 5 * self.heap.PointerSize()
   1334 
   1335   def __init__(self, heap, map, address):
   1336     HeapObject.__init__(self, heap, map, address)
   1337     code_entry = \
   1338         heap.reader.ReadU32(self.address + self.CodeEntryOffset())
   1339     self.code = heap.FindObject(code_entry - Code.HeaderSize(heap) + 1)
   1340     self.shared = self.ObjectField(self.SharedOffset())
   1341 
   1342   def Print(self, p):
   1343     source = "\n".join("  %s" % line for line in self._GetSource().split("\n"))
   1344     p.Print("JSFunction(%s) {" % self.heap.reader.FormatIntPtr(self.address))
   1345     p.Indent()
   1346     p.Print("inferred name: %s" % self.shared.inferred_name)
   1347     if self.shared.script.Is(Script) and self.shared.script.name.Is(String):
   1348       p.Print("script name: %s" % self.shared.script.name)
   1349     p.Print("source:")
   1350     p.PrintLines(self._GetSource().split("\n"))
   1351     p.Print("code:")
   1352     self.code.Print(p)
   1353     if self.code != self.shared.code:
   1354       p.Print("unoptimized code:")
   1355       self.shared.code.Print(p)
   1356     p.Dedent()
   1357     p.Print("}")
   1358 
   1359   def __str__(self):
   1360     inferred_name = ""
   1361     if self.shared.Is(SharedFunctionInfo):
   1362       inferred_name = self.shared.inferred_name
   1363     return "JSFunction(%s, %s)" % \
   1364           (self.heap.reader.FormatIntPtr(self.address), inferred_name)
   1365 
   1366   def _GetSource(self):
   1367     source = "?source?"
   1368     start = self.shared.start_position
   1369     end = self.shared.end_position
   1370     if not self.shared.script.Is(Script): return source
   1371     script_source = self.shared.script.source
   1372     if not script_source.Is(String): return source
   1373     if start and end:
   1374       source = script_source.GetChars()[start:end]
   1375     return source
   1376 
   1377 
   1378 class SharedFunctionInfo(HeapObject):
   1379   def CodeOffset(self):
   1380     return 2 * self.heap.PointerSize()
   1381 
   1382   def ScriptOffset(self):
   1383     return 7 * self.heap.PointerSize()
   1384 
   1385   def InferredNameOffset(self):
   1386     return 9 * self.heap.PointerSize()
   1387 
   1388   def EndPositionOffset(self):
   1389     return 12 * self.heap.PointerSize() + 4 * self.heap.IntSize()
   1390 
   1391   def StartPositionAndTypeOffset(self):
   1392     return 12 * self.heap.PointerSize() + 5 * self.heap.IntSize()
   1393 
   1394   def __init__(self, heap, map, address):
   1395     HeapObject.__init__(self, heap, map, address)
   1396     self.code = self.ObjectField(self.CodeOffset())
   1397     self.script = self.ObjectField(self.ScriptOffset())
   1398     self.inferred_name = self.ObjectField(self.InferredNameOffset())
   1399     if heap.PointerSize() == 8:
   1400       start_position_and_type = \
   1401           heap.reader.ReadU32(self.StartPositionAndTypeOffset())
   1402       self.start_position = start_position_and_type >> 2
   1403       pseudo_smi_end_position = \
   1404           heap.reader.ReadU32(self.EndPositionOffset())
   1405       self.end_position = pseudo_smi_end_position >> 2
   1406     else:
   1407       start_position_and_type = \
   1408           self.SmiField(self.StartPositionAndTypeOffset())
   1409       if start_position_and_type:
   1410         self.start_position = start_position_and_type >> 2
   1411       else:
   1412         self.start_position = None
   1413       self.end_position = \
   1414           self.SmiField(self.EndPositionOffset())
   1415 
   1416 
   1417 class Script(HeapObject):
   1418   def SourceOffset(self):
   1419     return self.heap.PointerSize()
   1420 
   1421   def NameOffset(self):
   1422     return self.SourceOffset() + self.heap.PointerSize()
   1423 
   1424   def __init__(self, heap, map, address):
   1425     HeapObject.__init__(self, heap, map, address)
   1426     self.source = self.ObjectField(self.SourceOffset())
   1427     self.name = self.ObjectField(self.NameOffset())
   1428 
   1429 
   1430 class CodeCache(HeapObject):
   1431   def DefaultCacheOffset(self):
   1432     return self.heap.PointerSize()
   1433 
   1434   def NormalTypeCacheOffset(self):
   1435     return self.DefaultCacheOffset() + self.heap.PointerSize()
   1436 
   1437   def __init__(self, heap, map, address):
   1438     HeapObject.__init__(self, heap, map, address)
   1439     self.default_cache = self.ObjectField(self.DefaultCacheOffset())
   1440     self.normal_type_cache = self.ObjectField(self.NormalTypeCacheOffset())
   1441 
   1442   def Print(self, p):
   1443     p.Print("CodeCache(%s) {" % self.heap.reader.FormatIntPtr(self.address))
   1444     p.Indent()
   1445     p.Print("default cache: %s" % self.default_cache)
   1446     p.Print("normal type cache: %s" % self.normal_type_cache)
   1447     p.Dedent()
   1448     p.Print("}")
   1449 
   1450 
   1451 class Code(HeapObject):
   1452   CODE_ALIGNMENT_MASK = (1 << 5) - 1
   1453 
   1454   def InstructionSizeOffset(self):
   1455     return self.heap.PointerSize()
   1456 
   1457   @staticmethod
   1458   def HeaderSize(heap):
   1459     return (heap.PointerSize() + heap.IntSize() + \
   1460         4 * heap.PointerSize() + 3 * heap.IntSize() + \
   1461         Code.CODE_ALIGNMENT_MASK) & ~Code.CODE_ALIGNMENT_MASK
   1462 
   1463   def __init__(self, heap, map, address):
   1464     HeapObject.__init__(self, heap, map, address)
   1465     self.entry = self.address + Code.HeaderSize(heap)
   1466     self.instruction_size = \
   1467         heap.reader.ReadU32(self.address + self.InstructionSizeOffset())
   1468 
   1469   def Print(self, p):
   1470     lines = self.heap.reader.GetDisasmLines(self.entry, self.instruction_size)
   1471     p.Print("Code(%s) {" % self.heap.reader.FormatIntPtr(self.address))
   1472     p.Indent()
   1473     p.Print("instruction_size: %d" % self.instruction_size)
   1474     p.PrintLines(self._FormatLine(line) for line in lines)
   1475     p.Dedent()
   1476     p.Print("}")
   1477 
   1478   def _FormatLine(self, line):
   1479     return FormatDisasmLine(self.entry, self.heap, line)
   1480 
   1481 
   1482 class V8Heap(object):
   1483   CLASS_MAP = {
   1484     "SYMBOL_TYPE": SeqString,
   1485     "ASCII_SYMBOL_TYPE": SeqString,
   1486     "CONS_SYMBOL_TYPE": ConsString,
   1487     "CONS_ASCII_SYMBOL_TYPE": ConsString,
   1488     "EXTERNAL_SYMBOL_TYPE": ExternalString,
   1489     "EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE": ExternalString,
   1490     "EXTERNAL_ASCII_SYMBOL_TYPE": ExternalString,
   1491     "SHORT_EXTERNAL_SYMBOL_TYPE": ExternalString,
   1492     "SHORT_EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE": ExternalString,
   1493     "SHORT_EXTERNAL_ASCII_SYMBOL_TYPE": ExternalString,
   1494     "STRING_TYPE": SeqString,
   1495     "ASCII_STRING_TYPE": SeqString,
   1496     "CONS_STRING_TYPE": ConsString,
   1497     "CONS_ASCII_STRING_TYPE": ConsString,
   1498     "EXTERNAL_STRING_TYPE": ExternalString,
   1499     "EXTERNAL_STRING_WITH_ASCII_DATA_TYPE": ExternalString,
   1500     "EXTERNAL_ASCII_STRING_TYPE": ExternalString,
   1501     "MAP_TYPE": Map,
   1502     "ODDBALL_TYPE": Oddball,
   1503     "FIXED_ARRAY_TYPE": FixedArray,
   1504     "JS_FUNCTION_TYPE": JSFunction,
   1505     "SHARED_FUNCTION_INFO_TYPE": SharedFunctionInfo,
   1506     "SCRIPT_TYPE": Script,
   1507     "CODE_CACHE_TYPE": CodeCache,
   1508     "CODE_TYPE": Code,
   1509   }
   1510 
   1511   def __init__(self, reader, stack_map):
   1512     self.reader = reader
   1513     self.stack_map = stack_map
   1514     self.objects = {}
   1515 
   1516   def FindObjectOrSmi(self, tagged_address):
   1517     if (tagged_address & 1) == 0: return tagged_address / 2
   1518     return self.FindObject(tagged_address)
   1519 
   1520   def FindObject(self, tagged_address):
   1521     if tagged_address in self.objects:
   1522       return self.objects[tagged_address]
   1523     if (tagged_address & self.ObjectAlignmentMask()) != 1: return None
   1524     address = tagged_address - 1
   1525     if not self.reader.IsValidAddress(address): return None
   1526     map_tagged_address = self.reader.ReadUIntPtr(address)
   1527     if tagged_address == map_tagged_address:
   1528       # Meta map?
   1529       meta_map = Map(self, None, address)
   1530       instance_type_name = INSTANCE_TYPES.get(meta_map.instance_type)
   1531       if instance_type_name != "MAP_TYPE": return None
   1532       meta_map.map = meta_map
   1533       object = meta_map
   1534     else:
   1535       map = self.FindMap(map_tagged_address)
   1536       if map is None: return None
   1537       instance_type_name = INSTANCE_TYPES.get(map.instance_type)
   1538       if instance_type_name is None: return None
   1539       cls = V8Heap.CLASS_MAP.get(instance_type_name, HeapObject)
   1540       object = cls(self, map, address)
   1541     self.objects[tagged_address] = object
   1542     return object
   1543 
   1544   def FindMap(self, tagged_address):
   1545     if (tagged_address & self.MapAlignmentMask()) != 1: return None
   1546     address = tagged_address - 1
   1547     if not self.reader.IsValidAddress(address): return None
   1548     object = Map(self, None, address)
   1549     return object
   1550 
   1551   def IntSize(self):
   1552     return 4
   1553 
   1554   def PointerSize(self):
   1555     return self.reader.PointerSize()
   1556 
   1557   def ObjectAlignmentMask(self):
   1558     return self.PointerSize() - 1
   1559 
   1560   def MapAlignmentMask(self):
   1561     if self.reader.arch == MD_CPU_ARCHITECTURE_AMD64:
   1562       return (1 << 4) - 1
   1563     elif self.reader.arch == MD_CPU_ARCHITECTURE_ARM:
   1564       return (1 << 4) - 1
   1565     elif self.reader.arch == MD_CPU_ARCHITECTURE_X86:
   1566       return (1 << 5) - 1
   1567 
   1568   def PageAlignmentMask(self):
   1569     return (1 << 20) - 1
   1570 
   1571 
   1572 class KnownObject(HeapObject):
   1573   def __init__(self, heap, known_name):
   1574     HeapObject.__init__(self, heap, None, None)
   1575     self.known_name = known_name
   1576 
   1577   def __str__(self):
   1578     return "<%s>" % self.known_name
   1579 
   1580 
   1581 class KnownMap(HeapObject):
   1582   def __init__(self, heap, known_name, instance_type):
   1583     HeapObject.__init__(self, heap, None, None)
   1584     self.instance_type = instance_type
   1585     self.known_name = known_name
   1586 
   1587   def __str__(self):
   1588     return "<%s>" % self.known_name
   1589 
   1590 
   1591 COMMENT_RE = re.compile(r"^C (0x[0-9a-fA-F]+) (.*)$")
   1592 PAGEADDRESS_RE = re.compile(
   1593     r"^P (mappage|pointerpage|datapage) (0x[0-9a-fA-F]+)$")
   1594 
   1595 
   1596 class InspectionInfo(object):
   1597   def __init__(self, minidump_name, reader):
   1598     self.comment_file = minidump_name + ".comments"
   1599     self.address_comments = {}
   1600     self.page_address = {}
   1601     if os.path.exists(self.comment_file):
   1602       with open(self.comment_file, "r") as f:
   1603         lines = f.readlines()
   1604         f.close()
   1605 
   1606         for l in lines:
   1607           m = COMMENT_RE.match(l)
   1608           if m:
   1609             self.address_comments[int(m.group(1), 0)] = m.group(2)
   1610           m = PAGEADDRESS_RE.match(l)
   1611           if m:
   1612             self.page_address[m.group(1)] = int(m.group(2), 0)
   1613     self.reader = reader
   1614     self.styles = {}
   1615     self.color_addresses()
   1616     return
   1617 
   1618   def get_page_address(self, page_kind):
   1619     return self.page_address.get(page_kind, 0)
   1620 
   1621   def save_page_address(self, page_kind, address):
   1622     with open(self.comment_file, "a") as f:
   1623       f.write("P %s 0x%x\n" % (page_kind, address))
   1624       f.close()
   1625 
   1626   def color_addresses(self):
   1627     # Color all stack addresses.
   1628     exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
   1629     stack_top = self.reader.ExceptionSP()
   1630     stack_bottom = exception_thread.stack.start + \
   1631         exception_thread.stack.memory.data_size
   1632     frame_pointer = self.reader.ExceptionFP()
   1633     self.styles[frame_pointer] = "frame"
   1634     for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
   1635       self.styles[slot] = "stackaddress"
   1636     for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
   1637       maybe_address = self.reader.ReadUIntPtr(slot)
   1638       self.styles[maybe_address] = "stackval"
   1639       if slot == frame_pointer:
   1640         self.styles[slot] = "frame"
   1641         frame_pointer = maybe_address
   1642     self.styles[self.reader.ExceptionIP()] = "pc"
   1643 
   1644   def get_style_class(self, address):
   1645     return self.styles.get(address, None)
   1646 
   1647   def get_style_class_string(self, address):
   1648     style = self.get_style_class(address)
   1649     if style != None:
   1650       return " class=\"%s\" " % style
   1651     else:
   1652       return ""
   1653 
   1654   def set_comment(self, address, comment):
   1655     self.address_comments[address] = comment
   1656     with open(self.comment_file, "a") as f:
   1657       f.write("C 0x%x %s\n" % (address, comment))
   1658       f.close()
   1659 
   1660   def get_comment(self, address):
   1661     return self.address_comments.get(address, "")
   1662 
   1663 
   1664 class InspectionPadawan(object):
   1665   """The padawan can improve annotations by sensing well-known objects."""
   1666   def __init__(self, reader, heap):
   1667     self.reader = reader
   1668     self.heap = heap
   1669     self.known_first_map_page = 0
   1670     self.known_first_data_page = 0
   1671     self.known_first_pointer_page = 0
   1672 
   1673   def __getattr__(self, name):
   1674     """An InspectionPadawan can be used instead of V8Heap, even though
   1675        it does not inherit from V8Heap (aka. mixin)."""
   1676     return getattr(self.heap, name)
   1677 
   1678   def GetPageOffset(self, tagged_address):
   1679     return tagged_address & self.heap.PageAlignmentMask()
   1680 
   1681   def IsInKnownMapSpace(self, tagged_address):
   1682     page_address = tagged_address & ~self.heap.PageAlignmentMask()
   1683     return page_address == self.known_first_map_page
   1684 
   1685   def IsInKnownOldSpace(self, tagged_address):
   1686     page_address = tagged_address & ~self.heap.PageAlignmentMask()
   1687     return page_address in [self.known_first_data_page,
   1688                             self.known_first_pointer_page]
   1689 
   1690   def ContainingKnownOldSpaceName(self, tagged_address):
   1691     page_address = tagged_address & ~self.heap.PageAlignmentMask()
   1692     if page_address == self.known_first_data_page: return "OLD_DATA_SPACE"
   1693     if page_address == self.known_first_pointer_page: return "OLD_POINTER_SPACE"
   1694     return None
   1695 
   1696   def SenseObject(self, tagged_address):
   1697     if self.IsInKnownOldSpace(tagged_address):
   1698       offset = self.GetPageOffset(tagged_address)
   1699       lookup_key = (self.ContainingKnownOldSpaceName(tagged_address), offset)
   1700       known_obj_name = KNOWN_OBJECTS.get(lookup_key)
   1701       if known_obj_name:
   1702         return KnownObject(self, known_obj_name)
   1703     if self.IsInKnownMapSpace(tagged_address):
   1704       known_map = self.SenseMap(tagged_address)
   1705       if known_map:
   1706         return known_map
   1707     found_obj = self.heap.FindObject(tagged_address)
   1708     if found_obj: return found_obj
   1709     address = tagged_address - 1
   1710     if self.reader.IsValidAddress(address):
   1711       map_tagged_address = self.reader.ReadUIntPtr(address)
   1712       map = self.SenseMap(map_tagged_address)
   1713       if map is None: return None
   1714       instance_type_name = INSTANCE_TYPES.get(map.instance_type)
   1715       if instance_type_name is None: return None
   1716       cls = V8Heap.CLASS_MAP.get(instance_type_name, HeapObject)
   1717       return cls(self, map, address)
   1718     return None
   1719 
   1720   def SenseMap(self, tagged_address):
   1721     if self.IsInKnownMapSpace(tagged_address):
   1722       offset = self.GetPageOffset(tagged_address)
   1723       known_map_info = KNOWN_MAPS.get(offset)
   1724       if known_map_info:
   1725         known_map_type, known_map_name = known_map_info
   1726         return KnownMap(self, known_map_name, known_map_type)
   1727     found_map = self.heap.FindMap(tagged_address)
   1728     if found_map: return found_map
   1729     return None
   1730 
   1731   def FindObjectOrSmi(self, tagged_address):
   1732     """When used as a mixin in place of V8Heap."""
   1733     found_obj = self.SenseObject(tagged_address)
   1734     if found_obj: return found_obj
   1735     if (tagged_address & 1) == 0:
   1736       return "Smi(%d)" % (tagged_address / 2)
   1737     else:
   1738       return "Unknown(%s)" % self.reader.FormatIntPtr(tagged_address)
   1739 
   1740   def FindObject(self, tagged_address):
   1741     """When used as a mixin in place of V8Heap."""
   1742     raise NotImplementedError
   1743 
   1744   def FindMap(self, tagged_address):
   1745     """When used as a mixin in place of V8Heap."""
   1746     raise NotImplementedError
   1747 
   1748   def PrintKnowledge(self):
   1749     print "  known_first_map_page = %s\n"\
   1750           "  known_first_data_page = %s\n"\
   1751           "  known_first_pointer_page = %s" % (
   1752           self.reader.FormatIntPtr(self.known_first_map_page),
   1753           self.reader.FormatIntPtr(self.known_first_data_page),
   1754           self.reader.FormatIntPtr(self.known_first_pointer_page))
   1755 
   1756 WEB_HEADER = """
   1757 <!DOCTYPE html>
   1758 <html>
   1759 <head>
   1760 <meta content="text/html; charset=utf-8" http-equiv="content-type">
   1761 <style media="screen" type="text/css">
   1762 
   1763 .code {
   1764   font-family: monospace;
   1765 }
   1766 
   1767 .dmptable {
   1768   border-collapse : collapse;
   1769   border-spacing : 0px;
   1770 }
   1771 
   1772 .codedump {
   1773   border-collapse : collapse;
   1774   border-spacing : 0px;
   1775 }
   1776 
   1777 .addrcomments {
   1778   border : 0px;
   1779 }
   1780 
   1781 .register {
   1782   padding-right : 1em;
   1783 }
   1784 
   1785 .header {
   1786   clear : both;
   1787 }
   1788 
   1789 .header .navigation {
   1790   float : left;
   1791 }
   1792 
   1793 .header .dumpname {
   1794   float : right;
   1795 }
   1796 
   1797 tr.highlight-line {
   1798   background-color : yellow;
   1799 }
   1800 
   1801 .highlight {
   1802   background-color : magenta;
   1803 }
   1804 
   1805 tr.inexact-highlight-line {
   1806   background-color : pink;
   1807 }
   1808 
   1809 input {
   1810   background-color: inherit;
   1811   border: 1px solid LightGray;
   1812 }
   1813 
   1814 .dumpcomments {
   1815   border : 1px solid LightGray;
   1816   width : 32em;
   1817 }
   1818 
   1819 .regions td {
   1820   padding:0 15px 0 15px;
   1821 }
   1822 
   1823 .stackframe td {
   1824   background-color : cyan;
   1825 }
   1826 
   1827 .stackaddress {
   1828   background-color : LightGray;
   1829 }
   1830 
   1831 .stackval {
   1832   background-color : LightCyan;
   1833 }
   1834 
   1835 .frame {
   1836   background-color : cyan;
   1837 }
   1838 
   1839 .commentinput {
   1840   width : 20em;
   1841 }
   1842 
   1843 a.nodump:visited {
   1844   color : black;
   1845   text-decoration : none;
   1846 }
   1847 
   1848 a.nodump:link {
   1849   color : black;
   1850   text-decoration : none;
   1851 }
   1852 
   1853 a:visited {
   1854   color : blueviolet;
   1855 }
   1856 
   1857 a:link {
   1858   color : blue;
   1859 }
   1860 
   1861 .disasmcomment {
   1862   color : DarkGreen;
   1863 }
   1864 
   1865 </style>
   1866 
   1867 <script type="application/javascript">
   1868 
   1869 var address_str = "address-";
   1870 var address_len = address_str.length;
   1871 
   1872 function comment() {
   1873   var s = event.srcElement.id;
   1874   var index = s.indexOf(address_str);
   1875   if (index >= 0) {
   1876     send_comment(s.substring(index + address_len), event.srcElement.value);
   1877   }
   1878 }
   1879 
   1880 function send_comment(address, comment) {
   1881   xmlhttp = new XMLHttpRequest();
   1882   address = encodeURIComponent(address)
   1883   comment = encodeURIComponent(comment)
   1884   xmlhttp.open("GET",
   1885       "setcomment?%(query_dump)s&address=" + address +
   1886       "&comment=" + comment, true);
   1887   xmlhttp.send();
   1888 }
   1889 
   1890 var dump_str = "dump-";
   1891 var dump_len = dump_str.length;
   1892 
   1893 function dump_comment() {
   1894   var s = event.srcElement.id;
   1895   var index = s.indexOf(dump_str);
   1896   if (index >= 0) {
   1897     send_dump_desc(s.substring(index + dump_len), event.srcElement.value);
   1898   }
   1899 }
   1900 
   1901 function send_dump_desc(name, desc) {
   1902   xmlhttp = new XMLHttpRequest();
   1903   name = encodeURIComponent(name)
   1904   desc = encodeURIComponent(desc)
   1905   xmlhttp.open("GET",
   1906       "setdumpdesc?dump=" + name +
   1907       "&description=" + desc, true);
   1908   xmlhttp.send();
   1909 }
   1910 
   1911 function onpage(kind, address) {
   1912   xmlhttp = new XMLHttpRequest();
   1913   kind = encodeURIComponent(kind)
   1914   address = encodeURIComponent(address)
   1915   xmlhttp.onreadystatechange = function() {
   1916     if (xmlhttp.readyState==4 && xmlhttp.status==200) {
   1917       location.reload(true)
   1918     }
   1919   };
   1920   xmlhttp.open("GET",
   1921       "setpageaddress?%(query_dump)s&kind=" + kind +
   1922       "&address=" + address);
   1923   xmlhttp.send();
   1924 }
   1925 
   1926 </script>
   1927 
   1928 <title>Dump %(dump_name)s</title>
   1929 </head>
   1930 
   1931 <body>
   1932   <div class="header">
   1933     <form class="navigation" action="search.html">
   1934       <a href="summary.html?%(query_dump)s">Context info</a>&nbsp;&nbsp;&nbsp;
   1935       <a href="info.html?%(query_dump)s">Dump info</a>&nbsp;&nbsp;&nbsp;
   1936       <a href="modules.html?%(query_dump)s">Modules</a>&nbsp;&nbsp;&nbsp;
   1937       &nbsp;
   1938       <input type="search" name="val">
   1939       <input type="submit" name="search" value="Search">
   1940       <input type="hidden" name="dump" value="%(dump_name)s">
   1941     </form>
   1942     <form class="navigation" action="disasm.html#highlight">
   1943       &nbsp;
   1944       &nbsp;
   1945       &nbsp;
   1946       <input type="search" name="val">
   1947       <input type="submit" name="disasm" value="Disasm">
   1948       &nbsp;
   1949       &nbsp;
   1950       &nbsp;
   1951       <a href="dumps.html">Dumps...</a>
   1952     </form>
   1953   </div>
   1954   <br>
   1955   <hr>
   1956 """
   1957 
   1958 
   1959 WEB_FOOTER = """
   1960 </body>
   1961 </html>
   1962 """
   1963 
   1964 
   1965 class WebParameterError(Exception):
   1966   def __init__(self, message):
   1967     Exception.__init__(self, message)
   1968 
   1969 
   1970 class InspectionWebHandler(BaseHTTPServer.BaseHTTPRequestHandler):
   1971   def formatter(self, query_components):
   1972     name = query_components.get("dump", [None])[0]
   1973     return self.server.get_dump_formatter(name)
   1974 
   1975   def send_success_html_headers(self):
   1976     self.send_response(200)
   1977     self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
   1978     self.send_header("Pragma", "no-cache")
   1979     self.send_header("Expires", "0")
   1980     self.send_header('Content-type','text/html')
   1981     self.end_headers()
   1982     return
   1983 
   1984   def do_GET(self):
   1985     try:
   1986       parsedurl = urlparse.urlparse(self.path)
   1987       query_components = urlparse.parse_qs(parsedurl.query)
   1988       if parsedurl.path == "/dumps.html":
   1989         self.send_success_html_headers()
   1990         self.server.output_dumps(self.wfile)
   1991       elif parsedurl.path == "/summary.html":
   1992         self.send_success_html_headers()
   1993         self.formatter(query_components).output_summary(self.wfile)
   1994       elif parsedurl.path == "/info.html":
   1995         self.send_success_html_headers()
   1996         self.formatter(query_components).output_info(self.wfile)
   1997       elif parsedurl.path == "/modules.html":
   1998         self.send_success_html_headers()
   1999         self.formatter(query_components).output_modules(self.wfile)
   2000       elif parsedurl.path == "/search.html":
   2001         address = query_components.get("val", [])
   2002         if len(address) != 1:
   2003           self.send_error(404, "Invalid params")
   2004           return
   2005         self.send_success_html_headers()
   2006         self.formatter(query_components).output_search_res(
   2007             self.wfile, address[0])
   2008       elif parsedurl.path == "/disasm.html":
   2009         address = query_components.get("val", [])
   2010         exact = query_components.get("exact", ["on"])
   2011         if len(address) != 1:
   2012           self.send_error(404, "Invalid params")
   2013           return
   2014         self.send_success_html_headers()
   2015         self.formatter(query_components).output_disasm(
   2016             self.wfile, address[0], exact[0])
   2017       elif parsedurl.path == "/data.html":
   2018         address = query_components.get("val", [])
   2019         datakind = query_components.get("type", ["address"])
   2020         if len(address) == 1 and len(datakind) == 1:
   2021           self.send_success_html_headers()
   2022           self.formatter(query_components).output_data(
   2023               self.wfile, address[0], datakind[0])
   2024         else:
   2025           self.send_error(404,'Invalid params')
   2026       elif parsedurl.path == "/setdumpdesc":
   2027         name = query_components.get("dump", [""])
   2028         description = query_components.get("description", [""])
   2029         if len(name) == 1 and len(description) == 1:
   2030           name = name[0]
   2031           description = description[0]
   2032           if self.server.set_dump_desc(name, description):
   2033             self.send_success_html_headers()
   2034             self.wfile.write("OK")
   2035             return
   2036         self.send_error(404,'Invalid params')
   2037       elif parsedurl.path == "/setcomment":
   2038         address = query_components.get("address", [])
   2039         comment = query_components.get("comment", [""])
   2040         if len(address) == 1 and len(comment) == 1:
   2041           address = address[0]
   2042           comment = comment[0]
   2043           self.formatter(query_components).set_comment(address, comment)
   2044           self.send_success_html_headers()
   2045           self.wfile.write("OK")
   2046         else:
   2047           self.send_error(404,'Invalid params')
   2048       elif parsedurl.path == "/setpageaddress":
   2049         kind = query_components.get("kind", [])
   2050         address = query_components.get("address", [""])
   2051         if len(kind) == 1 and len(address) == 1:
   2052           kind = kind[0]
   2053           address = address[0]
   2054           self.formatter(query_components).set_page_address(kind, address)
   2055           self.send_success_html_headers()
   2056           self.wfile.write("OK")
   2057         else:
   2058           self.send_error(404,'Invalid params')
   2059       else:
   2060         self.send_error(404,'File Not Found: %s' % self.path)
   2061 
   2062     except IOError:
   2063       self.send_error(404,'File Not Found: %s' % self.path)
   2064 
   2065     except WebParameterError as e:
   2066       self.send_error(404, 'Web parameter error: %s' % e.message)
   2067 
   2068 
   2069 HTML_REG_FORMAT = "<span class=\"register\"><b>%s</b>:&nbsp;%s</span>\n"
   2070 
   2071 
   2072 class InspectionWebFormatter(object):
   2073   CONTEXT_FULL = 0
   2074   CONTEXT_SHORT = 1
   2075 
   2076   def __init__(self, switches, minidump_name, http_server):
   2077     self.dumpfilename = os.path.split(minidump_name)[1]
   2078     self.encfilename = urllib.urlencode({ 'dump' : self.dumpfilename })
   2079     self.reader = MinidumpReader(switches, minidump_name)
   2080     self.server = http_server
   2081 
   2082     # Set up the heap
   2083     exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
   2084     stack_top = self.reader.ExceptionSP()
   2085     stack_bottom = exception_thread.stack.start + \
   2086         exception_thread.stack.memory.data_size
   2087     stack_map = {self.reader.ExceptionIP(): -1}
   2088     for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
   2089       maybe_address = self.reader.ReadUIntPtr(slot)
   2090       if not maybe_address in stack_map:
   2091         stack_map[maybe_address] = slot
   2092     self.heap = V8Heap(self.reader, stack_map)
   2093 
   2094     self.padawan = InspectionPadawan(self.reader, self.heap)
   2095     self.comments = InspectionInfo(minidump_name, self.reader)
   2096     self.padawan.known_first_data_page = (
   2097         self.comments.get_page_address("datapage"))
   2098     self.padawan.known_first_map_page = (
   2099         self.comments.get_page_address("mappage"))
   2100     self.padawan.known_first_pointer_page = (
   2101         self.comments.get_page_address("pointerpage"))
   2102 
   2103   def set_comment(self, straddress, comment):
   2104     try:
   2105       address = int(straddress, 0)
   2106       self.comments.set_comment(address, comment)
   2107     except ValueError:
   2108       print "Invalid address"
   2109 
   2110   def set_page_address(self, kind, straddress):
   2111     try:
   2112       address = int(straddress, 0)
   2113       if kind == "datapage":
   2114         self.padawan.known_first_data_page = address
   2115       elif kind == "mappage":
   2116         self.padawan.known_first_map_page = address
   2117       elif kind == "pointerpage":
   2118         self.padawan.known_first_pointer_page = address
   2119       self.comments.save_page_address(kind, address)
   2120     except ValueError:
   2121       print "Invalid address"
   2122 
   2123   def td_from_address(self, f, address):
   2124     f.write("<td %s>" % self.comments.get_style_class_string(address))
   2125 
   2126   def format_address(self, maybeaddress, straddress = None):
   2127     if maybeaddress is None:
   2128       return "not in dump"
   2129     else:
   2130       if straddress is None:
   2131         straddress = "0x" + self.reader.FormatIntPtr(maybeaddress)
   2132       style_class = ""
   2133       if not self.reader.IsValidAddress(maybeaddress):
   2134         style_class = " class=\"nodump\""
   2135       return ("<a %s href=\"search.html?%s&amp;val=%s\">%s</a>" %
   2136               (style_class, self.encfilename, straddress, straddress))
   2137 
   2138   def output_header(self, f):
   2139     f.write(WEB_HEADER %
   2140         { "query_dump" : self.encfilename,
   2141           "dump_name"  : cgi.escape(self.dumpfilename) })
   2142 
   2143   def output_footer(self, f):
   2144     f.write(WEB_FOOTER)
   2145 
   2146   MAX_CONTEXT_STACK = 4096
   2147 
   2148   def output_summary(self, f):
   2149     self.output_header(f)
   2150     f.write('<div class="code">')
   2151     self.output_context(f, InspectionWebFormatter.CONTEXT_SHORT)
   2152     self.output_disasm_pc(f)
   2153 
   2154     # Output stack
   2155     exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
   2156     stack_bottom = exception_thread.stack.start + \
   2157         min(exception_thread.stack.memory.data_size, self.MAX_CONTEXT_STACK)
   2158     stack_top = self.reader.ExceptionSP()
   2159     self.output_words(f, stack_top - 16, stack_bottom, stack_top, "Stack")
   2160 
   2161     f.write('</div>')
   2162     self.output_footer(f)
   2163     return
   2164 
   2165   def output_info(self, f):
   2166     self.output_header(f)
   2167     f.write("<h3>Dump info</h3>\n")
   2168     f.write("Description: ")
   2169     self.server.output_dump_desc_field(f, self.dumpfilename)
   2170     f.write("<br>\n")
   2171     f.write("Filename: ")
   2172     f.write("<span class=\"code\">%s</span><br>\n" % (self.dumpfilename))
   2173     dt = datetime.datetime.fromtimestamp(self.reader.header.time_date_stampt)
   2174     f.write("Timestamp: %s<br>\n" % dt.strftime('%Y-%m-%d %H:%M:%S'))
   2175     self.output_context(f, InspectionWebFormatter.CONTEXT_FULL)
   2176     self.output_address_ranges(f)
   2177     self.output_footer(f)
   2178     return
   2179 
   2180   def output_address_ranges(self, f):
   2181     regions = {}
   2182     def print_region(_reader, start, size, _location):
   2183       regions[start] = size
   2184     self.reader.ForEachMemoryRegion(print_region)
   2185     f.write("<h3>Available memory regions</h3>\n")
   2186     f.write('<div class="code">')
   2187     f.write("<table class=\"regions\">\n")
   2188     f.write("<thead><tr>")
   2189     f.write("<th>Start address</th>")
   2190     f.write("<th>End address</th>")
   2191     f.write("<th>Number of bytes</th>")
   2192     f.write("</tr></thead>\n")
   2193     for start in sorted(regions):
   2194       size = regions[start]
   2195       f.write("<tr>")
   2196       f.write("<td>%s</td>" % self.format_address(start))
   2197       f.write("<td>&nbsp;%s</td>" % self.format_address(start + size))
   2198       f.write("<td>&nbsp;%d</td>" % size)
   2199       f.write("</tr>\n")
   2200     f.write("</table>\n")
   2201     f.write('</div>')
   2202     return
   2203 
   2204   def output_module_details(self, f, module):
   2205     f.write("<b>%s</b>" % GetModuleName(self.reader, module))
   2206     file_version = GetVersionString(module.version_info.dwFileVersionMS,
   2207                                     module.version_info.dwFileVersionLS)
   2208     product_version = GetVersionString(module.version_info.dwProductVersionMS,
   2209                                        module.version_info.dwProductVersionLS)
   2210     f.write("<br>&nbsp;&nbsp;\n")
   2211     f.write("base: %s" % self.reader.FormatIntPtr(module.base_of_image))
   2212     f.write("<br>&nbsp;&nbsp;\n")
   2213     f.write("  end: %s" % self.reader.FormatIntPtr(module.base_of_image +
   2214                                             module.size_of_image))
   2215     f.write("<br>&nbsp;&nbsp;\n")
   2216     f.write("  file version: %s" % file_version)
   2217     f.write("<br>&nbsp;&nbsp;\n")
   2218     f.write("  product version: %s" % product_version)
   2219     f.write("<br>&nbsp;&nbsp;\n")
   2220     time_date_stamp = datetime.datetime.fromtimestamp(module.time_date_stamp)
   2221     f.write("  timestamp: %s" % time_date_stamp)
   2222     f.write("<br>\n");
   2223 
   2224   def output_modules(self, f):
   2225     self.output_header(f)
   2226     f.write('<div class="code">')
   2227     for module in self.reader.module_list.modules:
   2228       self.output_module_details(f, module)
   2229     f.write("</div>")
   2230     self.output_footer(f)
   2231     return
   2232 
   2233   def output_context(self, f, details):
   2234     exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
   2235     f.write("<h3>Exception context</h3>")
   2236     f.write('<div class="code">\n')
   2237     f.write("Thread id: %d" % exception_thread.id)
   2238     f.write("&nbsp;&nbsp; Exception code: %08X\n" %
   2239             self.reader.exception.exception.code)
   2240     if details == InspectionWebFormatter.CONTEXT_FULL:
   2241       if self.reader.exception.exception.parameter_count > 0:
   2242         f.write("&nbsp;&nbsp; Exception parameters: \n")
   2243         for i in xrange(0, self.reader.exception.exception.parameter_count):
   2244           f.write("%08x" % self.reader.exception.exception.information[i])
   2245         f.write("<br><br>\n")
   2246 
   2247     for r in CONTEXT_FOR_ARCH[self.reader.arch]:
   2248       f.write(HTML_REG_FORMAT %
   2249               (r, self.format_address(self.reader.Register(r))))
   2250     # TODO(vitalyr): decode eflags.
   2251     if self.reader.arch == MD_CPU_ARCHITECTURE_ARM:
   2252       f.write("<b>cpsr</b>: %s" % bin(self.reader.exception_context.cpsr)[2:])
   2253     else:
   2254       f.write("<b>eflags</b>: %s" %
   2255               bin(self.reader.exception_context.eflags)[2:])
   2256     f.write('</div>\n')
   2257     return
   2258 
   2259   def align_down(self, a, size):
   2260     alignment_correction = a % size
   2261     return a - alignment_correction
   2262 
   2263   def align_up(self, a, size):
   2264     alignment_correction = (size - 1) - ((a + size - 1) % size)
   2265     return a + alignment_correction
   2266 
   2267   def format_object(self, address):
   2268     heap_object = self.padawan.SenseObject(address)
   2269     return cgi.escape(str(heap_object or ""))
   2270 
   2271   def output_data(self, f, straddress, datakind):
   2272     try:
   2273       self.output_header(f)
   2274       address = int(straddress, 0)
   2275       if not self.reader.IsValidAddress(address):
   2276         f.write("<h3>Address 0x%x not found in the dump.</h3>" % address)
   2277         return
   2278       region = self.reader.FindRegion(address)
   2279       if datakind == "address":
   2280         self.output_words(f, region[0], region[0] + region[1], address, "Dump")
   2281       elif datakind == "ascii":
   2282         self.output_ascii(f, region[0], region[0] + region[1], address)
   2283       self.output_footer(f)
   2284 
   2285     except ValueError:
   2286       f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
   2287     return
   2288 
   2289   def output_words(self, f, start_address, end_address,
   2290                    highlight_address, desc):
   2291     region = self.reader.FindRegion(highlight_address)
   2292     if region is None:
   2293       f.write("<h3>Address 0x%x not found in the dump.</h3>\n" %
   2294               (highlight_address))
   2295       return
   2296     size = self.heap.PointerSize()
   2297     start_address = self.align_down(start_address, size)
   2298     low = self.align_down(region[0], size)
   2299     high = self.align_up(region[0] + region[1], size)
   2300     if start_address < low:
   2301       start_address = low
   2302     end_address = self.align_up(end_address, size)
   2303     if end_address > high:
   2304       end_address = high
   2305 
   2306     expand = ""
   2307     if start_address != low or end_address != high:
   2308       expand = ("(<a href=\"data.html?%s&amp;val=0x%x#highlight\">"
   2309                 " more..."
   2310                 " </a>)" %
   2311                 (self.encfilename, highlight_address))
   2312 
   2313     f.write("<h3>%s 0x%x - 0x%x, "
   2314             "highlighting <a href=\"#highlight\">0x%x</a> %s</h3>\n" %
   2315             (desc, start_address, end_address, highlight_address, expand))
   2316     f.write('<div class="code">')
   2317     f.write("<table class=\"codedump\">\n")
   2318 
   2319     for slot in xrange(start_address, end_address, size):
   2320       heap_object = ""
   2321       maybe_address = None
   2322       end_region = region[0] + region[1]
   2323       if slot < region[0] or slot + size > end_region:
   2324         straddress = "0x"
   2325         for i in xrange(end_region, slot + size):
   2326           straddress += "??"
   2327         for i in reversed(
   2328             xrange(max(slot, region[0]), min(slot + size, end_region))):
   2329           straddress += "%02x" % self.reader.ReadU8(i)
   2330         for i in xrange(slot, region[0]):
   2331           straddress += "??"
   2332       else:
   2333         maybe_address = self.reader.ReadUIntPtr(slot)
   2334         straddress = self.format_address(maybe_address)
   2335         if maybe_address:
   2336           heap_object = self.format_object(maybe_address)
   2337 
   2338       address_fmt = "%s&nbsp;</td>\n"
   2339       if slot == highlight_address:
   2340         f.write("<tr class=\"highlight-line\">\n")
   2341         address_fmt = "<a id=\"highlight\"></a>%s&nbsp;</td>\n"
   2342       elif slot < highlight_address and highlight_address < slot + size:
   2343         f.write("<tr class=\"inexact-highlight-line\">\n")
   2344         address_fmt = "<a id=\"highlight\"></a>%s&nbsp;</td>\n"
   2345       else:
   2346         f.write("<tr>\n")
   2347 
   2348       f.write("  <td>")
   2349       self.output_comment_box(f, "da-", slot)
   2350       f.write("</td>\n")
   2351       f.write("  ")
   2352       self.td_from_address(f, slot)
   2353       f.write(address_fmt % self.format_address(slot))
   2354       f.write("  ")
   2355       self.td_from_address(f, maybe_address)
   2356       f.write(":&nbsp; %s &nbsp;</td>\n" % straddress)
   2357       f.write("  <td>")
   2358       if maybe_address != None:
   2359         self.output_comment_box(
   2360             f, "sv-" + self.reader.FormatIntPtr(slot), maybe_address)
   2361       f.write("  </td>\n")
   2362       f.write("  <td>%s</td>\n" % (heap_object or ''))
   2363       f.write("</tr>\n")
   2364     f.write("</table>\n")
   2365     f.write("</div>")
   2366     return
   2367 
   2368   def output_ascii(self, f, start_address, end_address, highlight_address):
   2369     region = self.reader.FindRegion(highlight_address)
   2370     if region is None:
   2371       f.write("<h3>Address %x not found in the dump.</h3>" %
   2372           highlight_address)
   2373       return
   2374     if start_address < region[0]:
   2375       start_address = region[0]
   2376     if end_address > region[0] + region[1]:
   2377       end_address = region[0] + region[1]
   2378 
   2379     expand = ""
   2380     if start_address != region[0] or end_address != region[0] + region[1]:
   2381       link = ("data.html?%s&amp;val=0x%x&amp;type=ascii#highlight" %
   2382               (self.encfilename, highlight_address))
   2383       expand = "(<a href=\"%s\">more...</a>)" % link
   2384 
   2385     f.write("<h3>ASCII dump 0x%x - 0x%x, highlighting 0x%x %s</h3>" %
   2386             (start_address, end_address, highlight_address, expand))
   2387 
   2388     line_width = 64
   2389 
   2390     f.write('<div class="code">')
   2391 
   2392     start = self.align_down(start_address, line_width)
   2393 
   2394     for address in xrange(start, end_address):
   2395       if address % 64 == 0:
   2396         if address != start:
   2397           f.write("<br>")
   2398         f.write("0x%08x:&nbsp;" % address)
   2399       if address < start_address:
   2400         f.write("&nbsp;")
   2401       else:
   2402         if address == highlight_address:
   2403           f.write("<span class=\"highlight\">")
   2404         code = self.reader.ReadU8(address)
   2405         if code < 127 and code >= 32:
   2406           f.write("&#")
   2407           f.write(str(code))
   2408           f.write(";")
   2409         else:
   2410           f.write("&middot;")
   2411         if address == highlight_address:
   2412           f.write("</span>")
   2413     f.write("</div>")
   2414     return
   2415 
   2416   def output_disasm(self, f, straddress, strexact):
   2417     try:
   2418       self.output_header(f)
   2419       address = int(straddress, 0)
   2420       if not self.reader.IsValidAddress(address):
   2421         f.write("<h3>Address 0x%x not found in the dump.</h3>" % address)
   2422         return
   2423       region = self.reader.FindRegion(address)
   2424       self.output_disasm_range(
   2425           f, region[0], region[0] + region[1], address, strexact == "on")
   2426       self.output_footer(f)
   2427     except ValueError:
   2428       f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
   2429     return
   2430 
   2431   def output_disasm_range(
   2432       self, f, start_address, end_address, highlight_address, exact):
   2433     region = self.reader.FindRegion(highlight_address)
   2434     if start_address < region[0]:
   2435       start_address = region[0]
   2436     if end_address > region[0] + region[1]:
   2437       end_address = region[0] + region[1]
   2438     count = end_address - start_address
   2439     lines = self.reader.GetDisasmLines(start_address, count)
   2440     found = False
   2441     if exact:
   2442       for line in lines:
   2443         if line[0] + start_address == highlight_address:
   2444           found = True
   2445           break
   2446       if not found:
   2447         start_address = highlight_address
   2448         count = end_address - start_address
   2449         lines = self.reader.GetDisasmLines(highlight_address, count)
   2450     expand = ""
   2451     if start_address != region[0] or end_address != region[0] + region[1]:
   2452       exactness = ""
   2453       if exact and not found and end_address == region[0] + region[1]:
   2454         exactness = "&amp;exact=off"
   2455       expand = ("(<a href=\"disasm.html?%s%s"
   2456                 "&amp;val=0x%x#highlight\">more...</a>)" %
   2457                 (self.encfilename, exactness, highlight_address))
   2458 
   2459     f.write("<h3>Disassembling 0x%x - 0x%x, highlighting 0x%x %s</h3>" %
   2460             (start_address, end_address, highlight_address, expand))
   2461     f.write('<div class="code">')
   2462     f.write("<table class=\"codedump\">\n");
   2463     for i in xrange(0, len(lines)):
   2464       line = lines[i]
   2465       next_address = count
   2466       if i + 1 < len(lines):
   2467         next_line = lines[i + 1]
   2468         next_address = next_line[0]
   2469       self.format_disasm_line(
   2470           f, start_address, line, next_address, highlight_address)
   2471     f.write("</table>\n")
   2472     f.write("</div>")
   2473     return
   2474 
   2475   def annotate_disasm_addresses(self, line):
   2476     extra = []
   2477     for m in ADDRESS_RE.finditer(line):
   2478       maybe_address = int(m.group(0), 16)
   2479       formatted_address = self.format_address(maybe_address, m.group(0))
   2480       line = line.replace(m.group(0), formatted_address)
   2481       object_info = self.padawan.SenseObject(maybe_address)
   2482       if not object_info:
   2483         continue
   2484       extra.append(cgi.escape(str(object_info)))
   2485     if len(extra) == 0:
   2486       return line
   2487     return ("%s <span class=\"disasmcomment\">;; %s</span>" %
   2488             (line, ", ".join(extra)))
   2489 
   2490   def format_disasm_line(
   2491       self, f, start, line, next_address, highlight_address):
   2492     line_address = start + line[0]
   2493     address_fmt = "  <td>%s</td>\n"
   2494     if line_address == highlight_address:
   2495       f.write("<tr class=\"highlight-line\">\n")
   2496       address_fmt = "  <td><a id=\"highlight\">%s</a></td>\n"
   2497     elif (line_address < highlight_address and
   2498           highlight_address < next_address + start):
   2499       f.write("<tr class=\"inexact-highlight-line\">\n")
   2500       address_fmt = "  <td><a id=\"highlight\">%s</a></td>\n"
   2501     else:
   2502       f.write("<tr>\n")
   2503     num_bytes = next_address - line[0]
   2504     stack_slot = self.heap.stack_map.get(line_address)
   2505     marker = ""
   2506     if stack_slot:
   2507       marker = "=>"
   2508     op_offset = 3 * num_bytes - 1
   2509 
   2510     code = line[1]
   2511     # Compute the actual call target which the disassembler is too stupid
   2512     # to figure out (it adds the call offset to the disassembly offset rather
   2513     # than the absolute instruction address).
   2514     if self.heap.reader.arch == MD_CPU_ARCHITECTURE_X86:
   2515       if code.startswith("e8"):
   2516         words = code.split()
   2517         if len(words) > 6 and words[5] == "call":
   2518           offset = int(words[4] + words[3] + words[2] + words[1], 16)
   2519           target = (line_address + offset + 5) & 0xFFFFFFFF
   2520           code = code.replace(words[6], "0x%08x" % target)
   2521     # TODO(jkummerow): port this hack to ARM and x64.
   2522 
   2523     opcodes = code[:op_offset]
   2524     code = self.annotate_disasm_addresses(code[op_offset:])
   2525     f.write("  <td>")
   2526     self.output_comment_box(f, "codel-", line_address)
   2527     f.write("</td>\n")
   2528     f.write(address_fmt % marker)
   2529     f.write("  ")
   2530     self.td_from_address(f, line_address)
   2531     f.write("%s (+0x%x)</td>\n" %
   2532             (self.format_address(line_address), line[0]))
   2533     f.write("  <td>:&nbsp;%s&nbsp;</td>\n" % opcodes)
   2534     f.write("  <td>%s</td>\n" % code)
   2535     f.write("</tr>\n")
   2536 
   2537   def output_comment_box(self, f, prefix, address):
   2538     f.write("<input type=\"text\" class=\"commentinput\" "
   2539             "id=\"%s-address-0x%s\" onchange=\"comment()\" value=\"%s\">" %
   2540             (prefix,
   2541              self.reader.FormatIntPtr(address),
   2542              cgi.escape(self.comments.get_comment(address)) or ""))
   2543 
   2544   MAX_FOUND_RESULTS = 100
   2545 
   2546   def output_find_results(self, f, results):
   2547     f.write("Addresses")
   2548     toomany = len(results) > self.MAX_FOUND_RESULTS
   2549     if toomany:
   2550       f.write("(found %i results, displaying only first %i)" %
   2551               (len(results), self.MAX_FOUND_RESULTS))
   2552     f.write(": \n")
   2553     results = sorted(results)
   2554     results = results[:min(len(results), self.MAX_FOUND_RESULTS)]
   2555     for address in results:
   2556       f.write("<span %s>%s</span>\n" %
   2557               (self.comments.get_style_class_string(address),
   2558                self.format_address(address)))
   2559     if toomany:
   2560       f.write("...\n")
   2561 
   2562 
   2563   def output_page_info(self, f, page_kind, page_address, my_page_address):
   2564     if my_page_address == page_address and page_address != 0:
   2565       f.write("Marked first %s page.\n" % page_kind)
   2566     else:
   2567       f.write("<span id=\"%spage\" style=\"display:none\">" % page_kind)
   2568       f.write("Marked first %s page." % page_kind)
   2569       f.write("</span>\n")
   2570       f.write("<button onclick=\"onpage('%spage', '0x%x')\">" %
   2571               (page_kind, my_page_address))
   2572       f.write("Mark as first %s page</button>\n" % page_kind)
   2573     return
   2574 
   2575   def output_search_res(self, f, straddress):
   2576     try:
   2577       self.output_header(f)
   2578       f.write("<h3>Search results for %s</h3>" % straddress)
   2579 
   2580       address = int(straddress, 0)
   2581 
   2582       f.write("Comment: ")
   2583       self.output_comment_box(f, "search-", address)
   2584       f.write("<br>\n")
   2585 
   2586       page_address = address & ~self.heap.PageAlignmentMask()
   2587 
   2588       f.write("Page info: \n")
   2589       self.output_page_info(f, "data", self.padawan.known_first_data_page, \
   2590                             page_address)
   2591       self.output_page_info(f, "map", self.padawan.known_first_map_page, \
   2592                             page_address)
   2593       self.output_page_info(f, "pointer", \
   2594                             self.padawan.known_first_pointer_page, \
   2595                             page_address)
   2596 
   2597       if not self.reader.IsValidAddress(address):
   2598         f.write("<h3>The contents at address %s not found in the dump.</h3>" % \
   2599                 straddress)
   2600       else:
   2601         # Print as words
   2602         self.output_words(f, address - 8, address + 32, address, "Dump")
   2603 
   2604         # Print as ASCII
   2605         f.write("<hr>\n")
   2606         self.output_ascii(f, address, address + 256, address)
   2607 
   2608         # Print as code
   2609         f.write("<hr>\n")
   2610         self.output_disasm_range(f, address - 16, address + 16, address, True)
   2611 
   2612       aligned_res, unaligned_res = self.reader.FindWordList(address)
   2613 
   2614       if len(aligned_res) > 0:
   2615         f.write("<h3>Occurrences of 0x%x at aligned addresses</h3>\n" %
   2616                 address)
   2617         self.output_find_results(f, aligned_res)
   2618 
   2619       if len(unaligned_res) > 0:
   2620         f.write("<h3>Occurrences of 0x%x at unaligned addresses</h3>\n" % \
   2621                 address)
   2622         self.output_find_results(f, unaligned_res)
   2623 
   2624       if len(aligned_res) + len(unaligned_res) == 0:
   2625         f.write("<h3>No occurences of 0x%x found in the dump</h3>\n" % address)
   2626 
   2627       self.output_footer(f)
   2628 
   2629     except ValueError:
   2630       f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
   2631     return
   2632 
   2633   def output_disasm_pc(self, f):
   2634     address = self.reader.ExceptionIP()
   2635     if not self.reader.IsValidAddress(address):
   2636       return
   2637     self.output_disasm_range(f, address - 16, address + 16, address, True)
   2638 
   2639 
   2640 WEB_DUMPS_HEADER = """
   2641 <!DOCTYPE html>
   2642 <html>
   2643 <head>
   2644 <meta content="text/html; charset=utf-8" http-equiv="content-type">
   2645 <style media="screen" type="text/css">
   2646 
   2647 .dumplist {
   2648   border-collapse : collapse;
   2649   border-spacing : 0px;
   2650   font-family: monospace;
   2651 }
   2652 
   2653 .dumpcomments {
   2654   border : 1px solid LightGray;
   2655   width : 32em;
   2656 }
   2657 
   2658 </style>
   2659 
   2660 <script type="application/javascript">
   2661 
   2662 var dump_str = "dump-";
   2663 var dump_len = dump_str.length;
   2664 
   2665 function dump_comment() {
   2666   var s = event.srcElement.id;
   2667   var index = s.indexOf(dump_str);
   2668   if (index >= 0) {
   2669     send_dump_desc(s.substring(index + dump_len), event.srcElement.value);
   2670   }
   2671 }
   2672 
   2673 function send_dump_desc(name, desc) {
   2674   xmlhttp = new XMLHttpRequest();
   2675   name = encodeURIComponent(name)
   2676   desc = encodeURIComponent(desc)
   2677   xmlhttp.open("GET",
   2678       "setdumpdesc?dump=" + name +
   2679       "&description=" + desc, true);
   2680   xmlhttp.send();
   2681 }
   2682 
   2683 </script>
   2684 
   2685 <title>Dump list</title>
   2686 </head>
   2687 
   2688 <body>
   2689 """
   2690 
   2691 WEB_DUMPS_FOOTER = """
   2692 </body>
   2693 </html>
   2694 """
   2695 
   2696 DUMP_FILE_RE = re.compile(r"[-_0-9a-zA-Z][-\._0-9a-zA-Z]*\.dmp$")
   2697 
   2698 
   2699 class InspectionWebServer(BaseHTTPServer.HTTPServer):
   2700   def __init__(self, port_number, switches, minidump_name):
   2701     BaseHTTPServer.HTTPServer.__init__(
   2702         self, ('', port_number), InspectionWebHandler)
   2703     splitpath = os.path.split(minidump_name)
   2704     self.dumppath = splitpath[0]
   2705     self.dumpfilename = splitpath[1]
   2706     self.default_formatter = InspectionWebFormatter(
   2707         switches, minidump_name, self)
   2708     self.formatters = { self.dumpfilename : self.default_formatter }
   2709     self.switches = switches
   2710 
   2711   def output_dump_desc_field(self, f, name):
   2712     try:
   2713       descfile = open(os.path.join(self.dumppath, name + ".desc"), "r")
   2714       desc = descfile.readline()
   2715       descfile.close()
   2716     except IOError:
   2717       desc = ""
   2718     f.write("<input type=\"text\" class=\"dumpcomments\" "
   2719             "id=\"dump-%s\" onchange=\"dump_comment()\" value=\"%s\">\n" %
   2720             (cgi.escape(name), desc))
   2721 
   2722   def set_dump_desc(self, name, description):
   2723     if not DUMP_FILE_RE.match(name):
   2724       return False
   2725     fname = os.path.join(self.dumppath, name)
   2726     if not os.path.isfile(fname):
   2727       return False
   2728     fname = fname + ".desc"
   2729     descfile = open(fname, "w")
   2730     descfile.write(description)
   2731     descfile.close()
   2732     return True
   2733 
   2734   def get_dump_formatter(self, name):
   2735     if name is None:
   2736       return self.default_formatter
   2737     else:
   2738       if not DUMP_FILE_RE.match(name):
   2739         raise WebParameterError("Invalid name '%s'" % name)
   2740       formatter = self.formatters.get(name, None)
   2741       if formatter is None:
   2742         try:
   2743           formatter = InspectionWebFormatter(
   2744               self.switches, os.path.join(self.dumppath, name), self)
   2745           self.formatters[name] = formatter
   2746         except IOError:
   2747           raise WebParameterError("Could not open dump '%s'" % name)
   2748       return formatter
   2749 
   2750   def output_dumps(self, f):
   2751     f.write(WEB_DUMPS_HEADER)
   2752     f.write("<h3>List of available dumps</h3>")
   2753     f.write("<table class=\"dumplist\">\n")
   2754     f.write("<thead><tr>")
   2755     f.write("<th>Name</th>")
   2756     f.write("<th>File time</th>")
   2757     f.write("<th>Comment</th>")
   2758     f.write("</tr></thead>")
   2759     dumps_by_time = {}
   2760     for fname in os.listdir(self.dumppath):
   2761       if DUMP_FILE_RE.match(fname):
   2762         mtime = os.stat(os.path.join(self.dumppath, fname)).st_mtime
   2763         fnames = dumps_by_time.get(mtime, [])
   2764         fnames.append(fname)
   2765         dumps_by_time[mtime] = fnames
   2766 
   2767     for mtime in sorted(dumps_by_time, reverse=True):
   2768       fnames = dumps_by_time[mtime]
   2769       for fname in fnames:
   2770         f.write("<tr>\n")
   2771         f.write("<td><a href=\"summary.html?%s\">%s</a></td>\n" % (
   2772             (urllib.urlencode({ 'dump' : fname }), fname)))
   2773         f.write("<td>&nbsp;&nbsp;&nbsp;")
   2774         f.write(datetime.datetime.fromtimestamp(mtime))
   2775         f.write("</td>")
   2776         f.write("<td>&nbsp;&nbsp;&nbsp;")
   2777         self.output_dump_desc_field(f, fname)
   2778         f.write("</td>")
   2779         f.write("</tr>\n")
   2780     f.write("</table>\n")
   2781     f.write(WEB_DUMPS_FOOTER)
   2782     return
   2783 
   2784 class InspectionShell(cmd.Cmd):
   2785   def __init__(self, reader, heap):
   2786     cmd.Cmd.__init__(self)
   2787     self.reader = reader
   2788     self.heap = heap
   2789     self.padawan = InspectionPadawan(reader, heap)
   2790     self.prompt = "(grok) "
   2791 
   2792   def do_da(self, address):
   2793     """
   2794      Print ASCII string starting at specified address.
   2795     """
   2796     address = int(address, 16)
   2797     string = ""
   2798     while self.reader.IsValidAddress(address):
   2799       code = self.reader.ReadU8(address)
   2800       if code < 128:
   2801         string += chr(code)
   2802       else:
   2803         break
   2804       address += 1
   2805     if string == "":
   2806       print "Not an ASCII string at %s" % self.reader.FormatIntPtr(address)
   2807     else:
   2808       print "%s\n" % string
   2809 
   2810   def do_dd(self, address):
   2811     """
   2812      Interpret memory at the given address (if available) as a sequence
   2813      of words. Automatic alignment is not performed.
   2814     """
   2815     start = int(address, 16)
   2816     if (start & self.heap.ObjectAlignmentMask()) != 0:
   2817       print "Warning: Dumping un-aligned memory, is this what you had in mind?"
   2818     for slot in xrange(start,
   2819                        start + self.reader.PointerSize() * 10,
   2820                        self.reader.PointerSize()):
   2821       if not self.reader.IsValidAddress(slot):
   2822         print "Address is not contained within the minidump!"
   2823         return
   2824       maybe_address = self.reader.ReadUIntPtr(slot)
   2825       heap_object = self.padawan.SenseObject(maybe_address)
   2826       print "%s: %s %s" % (self.reader.FormatIntPtr(slot),
   2827                            self.reader.FormatIntPtr(maybe_address),
   2828                            heap_object or '')
   2829 
   2830   def do_do(self, address):
   2831     """
   2832      Interpret memory at the given address as a V8 object. Automatic
   2833      alignment makes sure that you can pass tagged as well as un-tagged
   2834      addresses.
   2835     """
   2836     address = int(address, 16)
   2837     if (address & self.heap.ObjectAlignmentMask()) == 0:
   2838       address = address + 1
   2839     elif (address & self.heap.ObjectAlignmentMask()) != 1:
   2840       print "Address doesn't look like a valid pointer!"
   2841       return
   2842     heap_object = self.padawan.SenseObject(address)
   2843     if heap_object:
   2844       heap_object.Print(Printer())
   2845     else:
   2846       print "Address cannot be interpreted as object!"
   2847 
   2848   def do_do_desc(self, address):
   2849     """
   2850       Print a descriptor array in a readable format.
   2851     """
   2852     start = int(address, 16)
   2853     if ((start & 1) == 1): start = start - 1
   2854     DescriptorArray(FixedArray(self.heap, None, start)).Print(Printer())
   2855 
   2856   def do_do_map(self, address):
   2857     """
   2858       Print a descriptor array in a readable format.
   2859     """
   2860     start = int(address, 16)
   2861     if ((start & 1) == 1): start = start - 1
   2862     Map(self.heap, None, start).Print(Printer())
   2863 
   2864   def do_do_trans(self, address):
   2865     """
   2866       Print a transition array in a readable format.
   2867     """
   2868     start = int(address, 16)
   2869     if ((start & 1) == 1): start = start - 1
   2870     TransitionArray(FixedArray(self.heap, None, start)).Print(Printer())
   2871 
   2872   def do_dp(self, address):
   2873     """
   2874      Interpret memory at the given address as being on a V8 heap page
   2875      and print information about the page header (if available).
   2876     """
   2877     address = int(address, 16)
   2878     page_address = address & ~self.heap.PageAlignmentMask()
   2879     if self.reader.IsValidAddress(page_address):
   2880       raise NotImplementedError
   2881     else:
   2882       print "Page header is not available!"
   2883 
   2884   def do_k(self, arguments):
   2885     """
   2886      Teach V8 heap layout information to the inspector. This increases
   2887      the amount of annotations the inspector can produce while dumping
   2888      data. The first page of each heap space is of particular interest
   2889      because it contains known objects that do not move.
   2890     """
   2891     self.padawan.PrintKnowledge()
   2892 
   2893   def do_kd(self, address):
   2894     """
   2895      Teach V8 heap layout information to the inspector. Set the first
   2896      data-space page by passing any pointer into that page.
   2897     """
   2898     address = int(address, 16)
   2899     page_address = address & ~self.heap.PageAlignmentMask()
   2900     self.padawan.known_first_data_page = page_address
   2901 
   2902   def do_km(self, address):
   2903     """
   2904      Teach V8 heap layout information to the inspector. Set the first
   2905      map-space page by passing any pointer into that page.
   2906     """
   2907     address = int(address, 16)
   2908     page_address = address & ~self.heap.PageAlignmentMask()
   2909     self.padawan.known_first_map_page = page_address
   2910 
   2911   def do_kp(self, address):
   2912     """
   2913      Teach V8 heap layout information to the inspector. Set the first
   2914      pointer-space page by passing any pointer into that page.
   2915     """
   2916     address = int(address, 16)
   2917     page_address = address & ~self.heap.PageAlignmentMask()
   2918     self.padawan.known_first_pointer_page = page_address
   2919 
   2920   def do_list(self, smth):
   2921     """
   2922      List all available memory regions.
   2923     """
   2924     def print_region(reader, start, size, location):
   2925       print "  %s - %s (%d bytes)" % (reader.FormatIntPtr(start),
   2926                                       reader.FormatIntPtr(start + size),
   2927                                       size)
   2928     print "Available memory regions:"
   2929     self.reader.ForEachMemoryRegion(print_region)
   2930 
   2931   def do_lm(self, arg):
   2932     """
   2933      List details for all loaded modules in the minidump. An argument can
   2934      be passed to limit the output to only those modules that contain the
   2935      argument as a substring (case insensitive match).
   2936     """
   2937     for module in self.reader.module_list.modules:
   2938       if arg:
   2939         name = GetModuleName(self.reader, module).lower()
   2940         if name.find(arg.lower()) >= 0:
   2941           PrintModuleDetails(self.reader, module)
   2942       else:
   2943         PrintModuleDetails(self.reader, module)
   2944     print
   2945 
   2946   def do_s(self, word):
   2947     """
   2948      Search for a given word in available memory regions. The given word
   2949      is expanded to full pointer size and searched at aligned as well as
   2950      un-aligned memory locations. Use 'sa' to search aligned locations
   2951      only.
   2952     """
   2953     try:
   2954       word = int(word, 0)
   2955     except ValueError:
   2956       print "Malformed word, prefix with '0x' to use hexadecimal format."
   2957       return
   2958     print "Searching for word %d/0x%s:" % (word, self.reader.FormatIntPtr(word))
   2959     self.reader.FindWord(word)
   2960 
   2961   def do_sh(self, none):
   2962     """
   2963      Search for the V8 Heap object in all available memory regions. You
   2964      might get lucky and find this rare treasure full of invaluable
   2965      information.
   2966     """
   2967     raise NotImplementedError
   2968 
   2969   def do_u(self, args):
   2970     """
   2971      Unassemble memory in the region [address, address + size). If the
   2972      size is not specified, a default value of 32 bytes is used.
   2973      Synopsis: u 0x<address> 0x<size>
   2974     """
   2975     args = args.split(' ')
   2976     start = int(args[0], 16)
   2977     size = int(args[1], 16) if len(args) > 1 else 0x20
   2978     if not self.reader.IsValidAddress(start):
   2979       print "Address is not contained within the minidump!"
   2980       return
   2981     lines = self.reader.GetDisasmLines(start, size)
   2982     for line in lines:
   2983       print FormatDisasmLine(start, self.heap, line)
   2984     print
   2985 
   2986   def do_EOF(self, none):
   2987     raise KeyboardInterrupt
   2988 
   2989 EIP_PROXIMITY = 64
   2990 
   2991 CONTEXT_FOR_ARCH = {
   2992     MD_CPU_ARCHITECTURE_AMD64:
   2993       ['rax', 'rbx', 'rcx', 'rdx', 'rdi', 'rsi', 'rbp', 'rsp', 'rip',
   2994        'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15'],
   2995     MD_CPU_ARCHITECTURE_ARM:
   2996       ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9',
   2997        'r10', 'r11', 'r12', 'sp', 'lr', 'pc'],
   2998     MD_CPU_ARCHITECTURE_X86:
   2999       ['eax', 'ebx', 'ecx', 'edx', 'edi', 'esi', 'ebp', 'esp', 'eip']
   3000 }
   3001 
   3002 KNOWN_MODULES = {'chrome.exe', 'chrome.dll'}
   3003 
   3004 def GetVersionString(ms, ls):
   3005   return "%d.%d.%d.%d" % (ms >> 16, ms & 0xffff, ls >> 16, ls & 0xffff)
   3006 
   3007 
   3008 def GetModuleName(reader, module):
   3009   name = reader.ReadMinidumpString(module.module_name_rva)
   3010   # simplify for path manipulation
   3011   name = name.encode('utf-8')
   3012   return str(os.path.basename(str(name).replace("\\", "/")))
   3013 
   3014 
   3015 def PrintModuleDetails(reader, module):
   3016   print "%s" % GetModuleName(reader, module)
   3017   file_version = GetVersionString(module.version_info.dwFileVersionMS,
   3018                                   module.version_info.dwFileVersionLS)
   3019   product_version = GetVersionString(module.version_info.dwProductVersionMS,
   3020                                      module.version_info.dwProductVersionLS)
   3021   print "  base: %s" % reader.FormatIntPtr(module.base_of_image)
   3022   print "  end: %s" % reader.FormatIntPtr(module.base_of_image +
   3023                                           module.size_of_image)
   3024   print "  file version: %s" % file_version
   3025   print "  product version: %s" % product_version
   3026   time_date_stamp = datetime.datetime.fromtimestamp(module.time_date_stamp)
   3027   print "  timestamp: %s" % time_date_stamp
   3028 
   3029 
   3030 def AnalyzeMinidump(options, minidump_name):
   3031   reader = MinidumpReader(options, minidump_name)
   3032   heap = None
   3033   DebugPrint("========================================")
   3034   if reader.exception is None:
   3035     print "Minidump has no exception info"
   3036   else:
   3037     print "Exception info:"
   3038     exception_thread = reader.thread_map[reader.exception.thread_id]
   3039     print "  thread id: %d" % exception_thread.id
   3040     print "  code: %08X" % reader.exception.exception.code
   3041     print "  context:"
   3042     for r in CONTEXT_FOR_ARCH[reader.arch]:
   3043       print "    %s: %s" % (r, reader.FormatIntPtr(reader.Register(r)))
   3044     # TODO(vitalyr): decode eflags.
   3045     if reader.arch == MD_CPU_ARCHITECTURE_ARM:
   3046       print "    cpsr: %s" % bin(reader.exception_context.cpsr)[2:]
   3047     else:
   3048       print "    eflags: %s" % bin(reader.exception_context.eflags)[2:]
   3049 
   3050     print
   3051     print "  modules:"
   3052     for module in reader.module_list.modules:
   3053       name = GetModuleName(reader, module)
   3054       if name in KNOWN_MODULES:
   3055         print "    %s at %08X" % (name, module.base_of_image)
   3056         reader.TryLoadSymbolsFor(name, module)
   3057     print
   3058 
   3059     stack_top = reader.ExceptionSP()
   3060     stack_bottom = exception_thread.stack.start + \
   3061         exception_thread.stack.memory.data_size
   3062     stack_map = {reader.ExceptionIP(): -1}
   3063     for slot in xrange(stack_top, stack_bottom, reader.PointerSize()):
   3064       maybe_address = reader.ReadUIntPtr(slot)
   3065       if not maybe_address in stack_map:
   3066         stack_map[maybe_address] = slot
   3067     heap = V8Heap(reader, stack_map)
   3068 
   3069     print "Disassembly around exception.eip:"
   3070     eip_symbol = reader.FindSymbol(reader.ExceptionIP())
   3071     if eip_symbol is not None:
   3072       print eip_symbol
   3073     disasm_start = reader.ExceptionIP() - EIP_PROXIMITY
   3074     disasm_bytes = 2 * EIP_PROXIMITY
   3075     if (options.full):
   3076       full_range = reader.FindRegion(reader.ExceptionIP())
   3077       if full_range is not None:
   3078         disasm_start = full_range[0]
   3079         disasm_bytes = full_range[1]
   3080 
   3081     lines = reader.GetDisasmLines(disasm_start, disasm_bytes)
   3082 
   3083     for line in lines:
   3084       print FormatDisasmLine(disasm_start, heap, line)
   3085     print
   3086 
   3087   if heap is None:
   3088     heap = V8Heap(reader, None)
   3089 
   3090   if options.full:
   3091     FullDump(reader, heap)
   3092 
   3093   if options.command:
   3094     InspectionShell(reader, heap).onecmd(options.command)
   3095 
   3096   if options.shell:
   3097     try:
   3098       InspectionShell(reader, heap).cmdloop("type help to get help")
   3099     except KeyboardInterrupt:
   3100       print "Kthxbye."
   3101   elif not options.command:
   3102     if reader.exception is not None:
   3103       frame_pointer = reader.ExceptionFP()
   3104       print "Annotated stack (from exception.esp to bottom):"
   3105       for slot in xrange(stack_top, stack_bottom, reader.PointerSize()):
   3106         maybe_address = reader.ReadUIntPtr(slot)
   3107         heap_object = heap.FindObject(maybe_address)
   3108         maybe_symbol = reader.FindSymbol(maybe_address)
   3109         if slot == frame_pointer:
   3110           maybe_symbol = "<---- frame pointer"
   3111           frame_pointer = maybe_address
   3112         print "%s: %s %s" % (reader.FormatIntPtr(slot),
   3113                              reader.FormatIntPtr(maybe_address),
   3114                              maybe_symbol or "")
   3115         if heap_object:
   3116           heap_object.Print(Printer())
   3117           print
   3118 
   3119   reader.Dispose()
   3120 
   3121 
   3122 if __name__ == "__main__":
   3123   parser = optparse.OptionParser(USAGE)
   3124   parser.add_option("-s", "--shell", dest="shell", action="store_true",
   3125                     help="start an interactive inspector shell")
   3126   parser.add_option("-w", "--web", dest="web", action="store_true",
   3127                     help="start a web server on localhost:%i" % PORT_NUMBER)
   3128   parser.add_option("-c", "--command", dest="command", default="",
   3129                     help="run an interactive inspector shell command and exit")
   3130   parser.add_option("-f", "--full", dest="full", action="store_true",
   3131                     help="dump all information contained in the minidump")
   3132   parser.add_option("--symdir", dest="symdir", default=".",
   3133                     help="directory containing *.pdb.sym file with symbols")
   3134   parser.add_option("--objdump",
   3135                     default="/usr/bin/objdump",
   3136                     help="objdump tool to use [default: %default]")
   3137   options, args = parser.parse_args()
   3138   if os.path.exists(options.objdump):
   3139     disasm.OBJDUMP_BIN = options.objdump
   3140     OBJDUMP_BIN = options.objdump
   3141   else:
   3142     print "Cannot find %s, falling back to default objdump" % options.objdump
   3143   if len(args) != 1:
   3144     parser.print_help()
   3145     sys.exit(1)
   3146   if options.web:
   3147     try:
   3148       server = InspectionWebServer(PORT_NUMBER, options, args[0])
   3149       print 'Started httpserver on port ' , PORT_NUMBER
   3150       webbrowser.open('http://localhost:%i/summary.html' % PORT_NUMBER)
   3151       server.serve_forever()
   3152     except KeyboardInterrupt:
   3153       print '^C received, shutting down the web server'
   3154       server.socket.close()
   3155   else:
   3156     AnalyzeMinidump(options, args[0])
   3157