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
30import BaseHTTPServer
31import bisect
32import cgi
33import cmd
34import codecs
35import ctypes
36import datetime
37import disasm
38import mmap
39import optparse
40import os
41import re
42import sys
43import types
44import urllib
45import urlparse
46import v8heapconst
47import webbrowser
48
49PORT_NUMBER = 8081
50
51
52USAGE="""usage: %prog [OPTIONS] [DUMP-FILE]
53
54Minidump analyzer.
55
56Shows the processor state at the point of exception including the
57stack of the active thread and the referenced objects in the V8
58heap. Code objects are disassembled and the addresses linked from the
59stack (e.g. pushed return addresses) are marked with "=>".
60
61Examples:
62  $ %prog 12345678-1234-1234-1234-123456789abcd-full.dmp"""
63
64
65DEBUG=False
66
67
68def DebugPrint(s):
69  if not DEBUG: return
70  print s
71
72
73class 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
118def 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.
175INSTANCE_TYPES = v8heapconst.INSTANCE_TYPES
176KNOWN_MAPS = v8heapconst.KNOWN_MAPS
177KNOWN_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
182MINIDUMP_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
192MINIDUMP_LOCATION_DESCRIPTOR = Descriptor([
193  ("data_size", ctypes.c_uint32),
194  ("rva", ctypes.c_uint32)
195])
196
197MINIDUMP_STRING = Descriptor([
198  ("length", ctypes.c_uint32),
199  ("buffer", lambda t: ctypes.c_uint8 * (t.length + 2))
200])
201
202MINIDUMP_DIRECTORY = Descriptor([
203  ("stream_type", ctypes.c_uint32),
204  ("location", MINIDUMP_LOCATION_DESCRIPTOR.ctype)
205])
206
207MD_EXCEPTION_MAXIMUM_PARAMETERS = 15
208
209MINIDUMP_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
219MINIDUMP_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.
227MD_UNUSED_STREAM = 0
228MD_RESERVED_STREAM_0 = 1
229MD_RESERVED_STREAM_1 = 2
230MD_THREAD_LIST_STREAM = 3
231MD_MODULE_LIST_STREAM = 4
232MD_MEMORY_LIST_STREAM = 5
233MD_EXCEPTION_STREAM = 6
234MD_SYSTEM_INFO_STREAM = 7
235MD_THREAD_EX_LIST_STREAM = 8
236MD_MEMORY_64_LIST_STREAM = 9
237MD_COMMENT_STREAM_A = 10
238MD_COMMENT_STREAM_W = 11
239MD_HANDLE_DATA_STREAM = 12
240MD_FUNCTION_TABLE_STREAM = 13
241MD_UNLOADED_MODULE_LIST_STREAM = 14
242MD_MISC_INFO_STREAM = 15
243MD_MEMORY_INFO_LIST_STREAM = 16
244MD_THREAD_INFO_LIST_STREAM = 17
245MD_HANDLE_OPERATION_LIST_STREAM = 18
246
247MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE = 80
248
249MINIDUMP_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
261MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE = 512
262
263# Context flags.
264MD_CONTEXT_X86 = 0x00010000
265MD_CONTEXT_X86_CONTROL = (MD_CONTEXT_X86 | 0x00000001)
266MD_CONTEXT_X86_INTEGER = (MD_CONTEXT_X86 | 0x00000002)
267MD_CONTEXT_X86_SEGMENTS = (MD_CONTEXT_X86 | 0x00000004)
268MD_CONTEXT_X86_FLOATING_POINT = (MD_CONTEXT_X86 | 0x00000008)
269MD_CONTEXT_X86_DEBUG_REGISTERS = (MD_CONTEXT_X86 | 0x00000010)
270MD_CONTEXT_X86_EXTENDED_REGISTERS = (MD_CONTEXT_X86 | 0x00000020)
271
272def EnableOnFlag(type, flag):
273  return lambda o: [None, type][int((o.context_flags & flag) != 0)]
274
275MINIDUMP_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
312MD_CONTEXT_ARM = 0x40000000
313MD_CONTEXT_ARM_INTEGER = (MD_CONTEXT_ARM | 0x00000002)
314MD_CONTEXT_ARM_FLOATING_POINT = (MD_CONTEXT_ARM | 0x00000004)
315MD_FLOATINGSAVEAREA_ARM_FPR_COUNT = 32
316MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT = 8
317
318MINIDUMP_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
324MINIDUMP_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
348MD_CONTEXT_AMD64 = 0x00100000
349MD_CONTEXT_AMD64_CONTROL = (MD_CONTEXT_AMD64 | 0x00000001)
350MD_CONTEXT_AMD64_INTEGER = (MD_CONTEXT_AMD64 | 0x00000002)
351MD_CONTEXT_AMD64_SEGMENTS = (MD_CONTEXT_AMD64 | 0x00000004)
352MD_CONTEXT_AMD64_FLOATING_POINT = (MD_CONTEXT_AMD64 | 0x00000008)
353MD_CONTEXT_AMD64_DEBUG_REGISTERS = (MD_CONTEXT_AMD64 | 0x00000010)
354
355MINIDUMP_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
422MINIDUMP_MEMORY_DESCRIPTOR = Descriptor([
423  ("start", ctypes.c_uint64),
424  ("memory", MINIDUMP_LOCATION_DESCRIPTOR.ctype)
425])
426
427MINIDUMP_MEMORY_DESCRIPTOR64 = Descriptor([
428  ("start", ctypes.c_uint64),
429  ("size", ctypes.c_uint64)
430])
431
432MINIDUMP_MEMORY_LIST = Descriptor([
433  ("range_count", ctypes.c_uint32),
434  ("ranges", lambda m: MINIDUMP_MEMORY_DESCRIPTOR.ctype * m.range_count)
435])
436
437MINIDUMP_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
443MINIDUMP_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
453MINIDUMP_THREAD_LIST = Descriptor([
454  ("thread_count", ctypes.c_uint32),
455  ("threads", lambda t: MINIDUMP_THREAD.ctype * t.thread_count)
456])
457
458MINIDUMP_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
474MINIDUMP_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
487MINIDUMP_MODULE_LIST = Descriptor([
488  ("number_of_modules", ctypes.c_uint32),
489  ("modules", lambda t: MINIDUMP_RAW_MODULE.ctype * t.number_of_modules)
490])
491
492MINIDUMP_RAW_SYSTEM_INFO = Descriptor([
493  ("processor_architecture", ctypes.c_uint16)
494])
495
496MD_CPU_ARCHITECTURE_X86 = 0
497MD_CPU_ARCHITECTURE_ARM = 5
498MD_CPU_ARCHITECTURE_AMD64 = 9
499
500class 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
514class 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
872class 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
895ADDRESS_RE = re.compile(r"0x[0-9a-fA-F]+")
896
897
898def 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
921def 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
932class 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
959class 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
1065class 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
1084class 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
1097class 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
1127class 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
1146class 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
1185class 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
1223class 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
1290class 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
1328class 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
1378class 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
1417class 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
1430class 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
1451class 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
1482class V8Heap(object):
1483  CLASS_MAP = {
1484    "SYMBOL_TYPE": SeqString,
1485    "ONE_BYTE_SYMBOL_TYPE": SeqString,
1486    "CONS_SYMBOL_TYPE": ConsString,
1487    "CONS_ONE_BYTE_SYMBOL_TYPE": ConsString,
1488    "EXTERNAL_SYMBOL_TYPE": ExternalString,
1489    "EXTERNAL_SYMBOL_WITH_ONE_BYTE_DATA_TYPE": ExternalString,
1490    "EXTERNAL_ONE_BYTE_SYMBOL_TYPE": ExternalString,
1491    "SHORT_EXTERNAL_SYMBOL_TYPE": ExternalString,
1492    "SHORT_EXTERNAL_SYMBOL_WITH_ONE_BYTE_DATA_TYPE": ExternalString,
1493    "SHORT_EXTERNAL_ONE_BYTE_SYMBOL_TYPE": ExternalString,
1494    "STRING_TYPE": SeqString,
1495    "ONE_BYTE_STRING_TYPE": SeqString,
1496    "CONS_STRING_TYPE": ConsString,
1497    "CONS_ONE_BYTE_STRING_TYPE": ConsString,
1498    "EXTERNAL_STRING_TYPE": ExternalString,
1499    "EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE": ExternalString,
1500    "EXTERNAL_ONE_BYTE_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
1572class 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
1581class 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
1591COMMENT_RE = re.compile(r"^C (0x[0-9a-fA-F]+) (.*)$")
1592PAGEADDRESS_RE = re.compile(
1593    r"^P (mappage|pointerpage|datapage) (0x[0-9a-fA-F]+)$")
1594
1595
1596class 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
1664class 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
1756WEB_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
1797tr.highlight-line {
1798  background-color : yellow;
1799}
1800
1801.highlight {
1802  background-color : magenta;
1803}
1804
1805tr.inexact-highlight-line {
1806  background-color : pink;
1807}
1808
1809input {
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
1843a.nodump:visited {
1844  color : black;
1845  text-decoration : none;
1846}
1847
1848a.nodump:link {
1849  color : black;
1850  text-decoration : none;
1851}
1852
1853a:visited {
1854  color : blueviolet;
1855}
1856
1857a:link {
1858  color : blue;
1859}
1860
1861.disasmcomment {
1862  color : DarkGreen;
1863}
1864
1865</style>
1866
1867<script type="application/javascript">
1868
1869var address_str = "address-";
1870var address_len = address_str.length;
1871
1872function 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
1880function 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
1890var dump_str = "dump-";
1891var dump_len = dump_str.length;
1892
1893function 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
1901function 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
1911function 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
1959WEB_FOOTER = """
1960</body>
1961</html>
1962"""
1963
1964
1965class WebParameterError(Exception):
1966  def __init__(self, message):
1967    Exception.__init__(self, message)
1968
1969
1970class 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
2069HTML_REG_FORMAT = "<span class=\"register\"><b>%s</b>:&nbsp;%s</span>\n"
2070
2071
2072class 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
2640WEB_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
2662var dump_str = "dump-";
2663var dump_len = dump_str.length;
2664
2665function 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
2673function 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
2691WEB_DUMPS_FOOTER = """
2692</body>
2693</html>
2694"""
2695
2696DUMP_FILE_RE = re.compile(r"[-_0-9a-zA-Z][-\._0-9a-zA-Z]*\.dmp$")
2697
2698
2699class 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
2784class 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
2989EIP_PROXIMITY = 64
2990
2991CONTEXT_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
3002KNOWN_MODULES = {'chrome.exe', 'chrome.dll'}
3003
3004def GetVersionString(ms, ls):
3005  return "%d.%d.%d.%d" % (ms >> 16, ms & 0xffff, ls >> 16, ls & 0xffff)
3006
3007
3008def 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
3015def 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
3030def 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        ascii_content = [c if c >= '\x20' and c <  '\x7f' else '.'
3107                         for c in reader.ReadBytes(slot, reader.PointerSize())]
3108        maybe_address = reader.ReadUIntPtr(slot)
3109        heap_object = heap.FindObject(maybe_address)
3110        maybe_symbol = reader.FindSymbol(maybe_address)
3111        if slot == frame_pointer:
3112          maybe_symbol = "<---- frame pointer"
3113          frame_pointer = maybe_address
3114        print "%s: %s %s %s" % (reader.FormatIntPtr(slot),
3115                                reader.FormatIntPtr(maybe_address),
3116                                "".join(ascii_content),
3117                                maybe_symbol or "")
3118        if heap_object:
3119          heap_object.Print(Printer())
3120          print
3121
3122  reader.Dispose()
3123
3124
3125if __name__ == "__main__":
3126  parser = optparse.OptionParser(USAGE)
3127  parser.add_option("-s", "--shell", dest="shell", action="store_true",
3128                    help="start an interactive inspector shell")
3129  parser.add_option("-w", "--web", dest="web", action="store_true",
3130                    help="start a web server on localhost:%i" % PORT_NUMBER)
3131  parser.add_option("-c", "--command", dest="command", default="",
3132                    help="run an interactive inspector shell command and exit")
3133  parser.add_option("-f", "--full", dest="full", action="store_true",
3134                    help="dump all information contained in the minidump")
3135  parser.add_option("--symdir", dest="symdir", default=".",
3136                    help="directory containing *.pdb.sym file with symbols")
3137  parser.add_option("--objdump",
3138                    default="/usr/bin/objdump",
3139                    help="objdump tool to use [default: %default]")
3140  options, args = parser.parse_args()
3141  if os.path.exists(options.objdump):
3142    disasm.OBJDUMP_BIN = options.objdump
3143    OBJDUMP_BIN = options.objdump
3144  else:
3145    print "Cannot find %s, falling back to default objdump" % options.objdump
3146  if len(args) != 1:
3147    parser.print_help()
3148    sys.exit(1)
3149  if options.web:
3150    try:
3151      server = InspectionWebServer(PORT_NUMBER, options, args[0])
3152      print 'Started httpserver on port ' , PORT_NUMBER
3153      webbrowser.open('http://localhost:%i/summary.html' % PORT_NUMBER)
3154      server.serve_forever()
3155    except KeyboardInterrupt:
3156      print '^C received, shutting down the web server'
3157      server.socket.close()
3158  else:
3159    AnalyzeMinidump(options, args[0])
3160