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