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