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