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