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