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> 2590 <a href="info.html?%(query_dump)s">Dump info</a> 2591 <a href="modules.html?%(query_dump)s">Modules</a> 2592 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 2599 2600 2601 <input type="search" name="val"> 2602 <input type="submit" name="disasm" value="Disasm"> 2603 2604 2605 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>: %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&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&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> %s</td>" % self.format_address(start + size)) 2876 f.write("<td> %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> ") 2889 f.write("base: %s" % self.reader.FormatIntPtr(module.base_of_image)) 2890 f.write("<br> ") 2891 f.write(" end: %s" % self.reader.FormatIntPtr(module.base_of_image + 2892 module.size_of_image)) 2893 f.write("<br> ") 2894 f.write(" file version: %s" % file_version) 2895 f.write("<br> ") 2896 f.write(" product version: %s" % product_version) 2897 f.write("<br> ") 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(" 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(" 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&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 </td>" 3032 if slot == highlight_address: 3033 f.write("<tr class=highlight-line>") 3034 address_fmt = "<a id=highlight></a>%s </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 </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(": %s </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&val=0x%x&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: " % address) 3091 if address < start_address: 3092 f.write(" ") 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("·") 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 = "&exact=off" 3147 expand = ("(<a href=\"disasm.html?%s%s" 3148 "&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>: %s </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> ") 3479 f.write(datetime.datetime.fromtimestamp(mtime)) 3480 f.write("</td>") 3481 f.write("<td> ") 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