OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright 2012 the V8 project authors. All rights reserved. | 3 # Copyright 2012 the V8 project authors. All rights reserved. |
4 # Redistribution and use in source and binary forms, with or without | 4 # Redistribution and use in source and binary forms, with or without |
5 # modification, are permitted provided that the following conditions are | 5 # modification, are permitted provided that the following conditions are |
6 # met: | 6 # met: |
7 # | 7 # |
8 # * Redistributions of source code must retain the above copyright | 8 # * Redistributions of source code must retain the above copyright |
9 # notice, this list of conditions and the following disclaimer. | 9 # notice, this list of conditions and the following disclaimer. |
10 # * Redistributions in binary form must reproduce the above | 10 # * Redistributions in binary form must reproduce the above |
(...skipping 13 matching lines...) Expand all Loading... |
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | 29 |
30 import bisect | 30 import bisect |
31 import cmd | 31 import cmd |
32 import codecs | 32 import codecs |
33 import ctypes | 33 import ctypes |
| 34 import datetime |
34 import disasm | 35 import disasm |
35 import mmap | 36 import mmap |
36 import optparse | 37 import optparse |
37 import os | 38 import os |
38 import re | 39 import re |
39 import struct | 40 import struct |
40 import sys | 41 import sys |
41 import types | 42 import types |
42 | 43 |
43 | |
44 USAGE="""usage: %prog [OPTIONS] [DUMP-FILE] | 44 USAGE="""usage: %prog [OPTIONS] [DUMP-FILE] |
45 | 45 |
46 Minidump analyzer. | 46 Minidump analyzer. |
47 | 47 |
48 Shows the processor state at the point of exception including the | 48 Shows the processor state at the point of exception including the |
49 stack of the active thread and the referenced objects in the V8 | 49 stack of the active thread and the referenced objects in the V8 |
50 heap. Code objects are disassembled and the addresses linked from the | 50 heap. Code objects are disassembled and the addresses linked from the |
51 stack (e.g. pushed return addresses) are marked with "=>". | 51 stack (e.g. pushed return addresses) are marked with "=>". |
52 | 52 |
53 Examples: | 53 Examples: |
(...skipping 381 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
435 ("ted", ctypes.c_uint64), | 435 ("ted", ctypes.c_uint64), |
436 ("stack", MINIDUMP_MEMORY_DESCRIPTOR.ctype), | 436 ("stack", MINIDUMP_MEMORY_DESCRIPTOR.ctype), |
437 ("context", MINIDUMP_LOCATION_DESCRIPTOR.ctype) | 437 ("context", MINIDUMP_LOCATION_DESCRIPTOR.ctype) |
438 ]) | 438 ]) |
439 | 439 |
440 MINIDUMP_THREAD_LIST = Descriptor([ | 440 MINIDUMP_THREAD_LIST = Descriptor([ |
441 ("thread_count", ctypes.c_uint32), | 441 ("thread_count", ctypes.c_uint32), |
442 ("threads", lambda t: MINIDUMP_THREAD.ctype * t.thread_count) | 442 ("threads", lambda t: MINIDUMP_THREAD.ctype * t.thread_count) |
443 ]) | 443 ]) |
444 | 444 |
| 445 MINIDUMP_VS_FIXEDFILEINFO = Descriptor([ |
| 446 ("dwSignature", ctypes.c_uint32), |
| 447 ("dwStrucVersion", ctypes.c_uint32), |
| 448 ("dwFileVersionMS", ctypes.c_uint32), |
| 449 ("dwFileVersionLS", ctypes.c_uint32), |
| 450 ("dwProductVersionMS", ctypes.c_uint32), |
| 451 ("dwProductVersionLS", ctypes.c_uint32), |
| 452 ("dwFileFlagsMask", ctypes.c_uint32), |
| 453 ("dwFileFlags", ctypes.c_uint32), |
| 454 ("dwFileOS", ctypes.c_uint32), |
| 455 ("dwFileType", ctypes.c_uint32), |
| 456 ("dwFileSubtype", ctypes.c_uint32), |
| 457 ("dwFileDateMS", ctypes.c_uint32), |
| 458 ("dwFileDateLS", ctypes.c_uint32) |
| 459 ]) |
| 460 |
445 MINIDUMP_RAW_MODULE = Descriptor([ | 461 MINIDUMP_RAW_MODULE = Descriptor([ |
446 ("base_of_image", ctypes.c_uint64), | 462 ("base_of_image", ctypes.c_uint64), |
447 ("size_of_image", ctypes.c_uint32), | 463 ("size_of_image", ctypes.c_uint32), |
448 ("checksum", ctypes.c_uint32), | 464 ("checksum", ctypes.c_uint32), |
449 ("time_date_stamp", ctypes.c_uint32), | 465 ("time_date_stamp", ctypes.c_uint32), |
450 ("module_name_rva", ctypes.c_uint32), | 466 ("module_name_rva", ctypes.c_uint32), |
451 ("version_info", ctypes.c_uint32 * 13), | 467 ("version_info", MINIDUMP_VS_FIXEDFILEINFO.ctype), |
452 ("cv_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype), | 468 ("cv_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype), |
453 ("misc_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype), | 469 ("misc_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype), |
454 ("reserved0", ctypes.c_uint32 * 2), | 470 ("reserved0", ctypes.c_uint32 * 2), |
455 ("reserved1", ctypes.c_uint32 * 2) | 471 ("reserved1", ctypes.c_uint32 * 2) |
456 ]) | 472 ]) |
457 | 473 |
458 MINIDUMP_MODULE_LIST = Descriptor([ | 474 MINIDUMP_MODULE_LIST = Descriptor([ |
459 ("number_of_modules", ctypes.c_uint32), | 475 ("number_of_modules", ctypes.c_uint32), |
460 ("modules", lambda t: MINIDUMP_RAW_MODULE.ctype * t.number_of_modules) | 476 ("modules", lambda t: MINIDUMP_RAW_MODULE.ctype * t.number_of_modules) |
461 ]) | 477 ]) |
(...skipping 316 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
778 size = int(result.group(2), 16) | 794 size = int(result.group(2), 16) |
779 name = result.group(4).rstrip() | 795 name = result.group(4).rstrip() |
780 bisect.insort_left(self.symbols, | 796 bisect.insort_left(self.symbols, |
781 FuncSymbol(baseaddr + start, size, name)) | 797 FuncSymbol(baseaddr + start, size, name)) |
782 print " ... done" | 798 print " ... done" |
783 | 799 |
784 def TryLoadSymbolsFor(self, modulename, module): | 800 def TryLoadSymbolsFor(self, modulename, module): |
785 try: | 801 try: |
786 symfile = os.path.join(self.symdir, | 802 symfile = os.path.join(self.symdir, |
787 modulename.replace('.', '_') + ".pdb.sym") | 803 modulename.replace('.', '_') + ".pdb.sym") |
788 self._LoadSymbolsFrom(symfile, module.base_of_image) | 804 if os.path.isfile(symfile): |
789 self.modules_with_symbols.append(module) | 805 self._LoadSymbolsFrom(symfile, module.base_of_image) |
| 806 self.modules_with_symbols.append(module) |
790 except Exception as e: | 807 except Exception as e: |
791 print " ... failure (%s)" % (e) | 808 print " ... failure (%s)" % (e) |
792 | 809 |
793 # Returns true if address is covered by some module that has loaded symbols. | 810 # Returns true if address is covered by some module that has loaded symbols. |
794 def _IsInModuleWithSymbols(self, addr): | 811 def _IsInModuleWithSymbols(self, addr): |
795 for module in self.modules_with_symbols: | 812 for module in self.modules_with_symbols: |
796 start = module.base_of_image | 813 start = module.base_of_image |
797 end = start + module.size_of_image | 814 end = start + module.size_of_image |
798 if (start <= addr) and (addr < end): | 815 if (start <= addr) and (addr < end): |
799 return True | 816 return True |
(...skipping 1204 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2004 """ | 2021 """ |
2005 List all available memory regions. | 2022 List all available memory regions. |
2006 """ | 2023 """ |
2007 def print_region(reader, start, size, location): | 2024 def print_region(reader, start, size, location): |
2008 print " %s - %s (%d bytes)" % (reader.FormatIntPtr(start), | 2025 print " %s - %s (%d bytes)" % (reader.FormatIntPtr(start), |
2009 reader.FormatIntPtr(start + size), | 2026 reader.FormatIntPtr(start + size), |
2010 size) | 2027 size) |
2011 print "Available memory regions:" | 2028 print "Available memory regions:" |
2012 self.reader.ForEachMemoryRegion(print_region) | 2029 self.reader.ForEachMemoryRegion(print_region) |
2013 | 2030 |
| 2031 def do_lm(self, arg): |
| 2032 """ |
| 2033 List details for all loaded modules in the minidump. An argument can |
| 2034 be passed to limit the output to only those modules that contain the |
| 2035 argument as a substring (case insensitive match). |
| 2036 """ |
| 2037 for module in self.reader.module_list.modules: |
| 2038 if arg: |
| 2039 name = GetModuleName(self.reader, module).lower() |
| 2040 if name.find(arg.lower()) >= 0: |
| 2041 PrintModuleDetails(self.reader, module) |
| 2042 else: |
| 2043 PrintModuleDetails(self.reader, module) |
| 2044 print |
| 2045 |
2014 def do_s(self, word): | 2046 def do_s(self, word): |
2015 """ | 2047 """ |
2016 Search for a given word in available memory regions. The given word | 2048 Search for a given word in available memory regions. The given word |
2017 is expanded to full pointer size and searched at aligned as well as | 2049 is expanded to full pointer size and searched at aligned as well as |
2018 un-aligned memory locations. Use 'sa' to search aligned locations | 2050 un-aligned memory locations. Use 'sa' to search aligned locations |
2019 only. | 2051 only. |
2020 """ | 2052 """ |
2021 try: | 2053 try: |
2022 word = int(word, 0) | 2054 word = int(word, 0) |
2023 except ValueError: | 2055 except ValueError: |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2062 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15'], | 2094 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15'], |
2063 MD_CPU_ARCHITECTURE_ARM: | 2095 MD_CPU_ARCHITECTURE_ARM: |
2064 ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', | 2096 ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', |
2065 'r10', 'r11', 'r12', 'sp', 'lr', 'pc'], | 2097 'r10', 'r11', 'r12', 'sp', 'lr', 'pc'], |
2066 MD_CPU_ARCHITECTURE_X86: | 2098 MD_CPU_ARCHITECTURE_X86: |
2067 ['eax', 'ebx', 'ecx', 'edx', 'edi', 'esi', 'ebp', 'esp', 'eip'] | 2099 ['eax', 'ebx', 'ecx', 'edx', 'edi', 'esi', 'ebp', 'esp', 'eip'] |
2068 } | 2100 } |
2069 | 2101 |
2070 KNOWN_MODULES = {'chrome.exe', 'chrome.dll'} | 2102 KNOWN_MODULES = {'chrome.exe', 'chrome.dll'} |
2071 | 2103 |
| 2104 def GetVersionString(ms, ls): |
| 2105 return "%d.%d.%d.%d" % (ms >> 16, ms & 0xffff, ls >> 16, ls & 0xffff) |
| 2106 |
| 2107 |
2072 def GetModuleName(reader, module): | 2108 def GetModuleName(reader, module): |
2073 name = reader.ReadMinidumpString(module.module_name_rva) | 2109 name = reader.ReadMinidumpString(module.module_name_rva) |
| 2110 # simplify for path manipulation |
| 2111 name = name.encode('utf-8') |
2074 return str(os.path.basename(str(name).replace("\\", "/"))) | 2112 return str(os.path.basename(str(name).replace("\\", "/"))) |
2075 | 2113 |
| 2114 |
| 2115 def PrintModuleDetails(reader, module): |
| 2116 print "%s" % GetModuleName(reader, module) |
| 2117 file_version = GetVersionString(module.version_info.dwFileVersionMS, |
| 2118 module.version_info.dwFileVersionLS) |
| 2119 product_version = GetVersionString(module.version_info.dwProductVersionMS, |
| 2120 module.version_info.dwProductVersionLS) |
| 2121 print " base: %s" % reader.FormatIntPtr(module.base_of_image) |
| 2122 print " end: %s" % reader.FormatIntPtr(module.base_of_image + |
| 2123 module.size_of_image) |
| 2124 print " file version: %s" % file_version |
| 2125 print " product version: %s" % product_version |
| 2126 time_date_stamp = datetime.datetime.fromtimestamp(module.time_date_stamp) |
| 2127 print " timestamp: %s" % time_date_stamp |
| 2128 |
| 2129 |
2076 def AnalyzeMinidump(options, minidump_name): | 2130 def AnalyzeMinidump(options, minidump_name): |
2077 reader = MinidumpReader(options, minidump_name) | 2131 reader = MinidumpReader(options, minidump_name) |
2078 heap = None | 2132 heap = None |
2079 DebugPrint("========================================") | 2133 DebugPrint("========================================") |
2080 if reader.exception is None: | 2134 if reader.exception is None: |
2081 print "Minidump has no exception info" | 2135 print "Minidump has no exception info" |
2082 else: | 2136 else: |
2083 print "Exception info:" | 2137 print "Exception info:" |
2084 exception_thread = reader.thread_map[reader.exception.thread_id] | 2138 exception_thread = reader.thread_map[reader.exception.thread_id] |
2085 print " thread id: %d" % exception_thread.id | 2139 print " thread id: %d" % exception_thread.id |
2086 print " code: %08X" % reader.exception.exception.code | 2140 print " code: %08X" % reader.exception.exception.code |
2087 print " context:" | 2141 print " context:" |
2088 for r in CONTEXT_FOR_ARCH[reader.arch]: | 2142 for r in CONTEXT_FOR_ARCH[reader.arch]: |
2089 print " %s: %s" % (r, reader.FormatIntPtr(reader.Register(r))) | 2143 print " %s: %s" % (r, reader.FormatIntPtr(reader.Register(r))) |
2090 # TODO(vitalyr): decode eflags. | 2144 # TODO(vitalyr): decode eflags. |
2091 if reader.arch == MD_CPU_ARCHITECTURE_ARM: | 2145 if reader.arch == MD_CPU_ARCHITECTURE_ARM: |
2092 print " cpsr: %s" % bin(reader.exception_context.cpsr)[2:] | 2146 print " cpsr: %s" % bin(reader.exception_context.cpsr)[2:] |
2093 else: | 2147 else: |
2094 print " eflags: %s" % bin(reader.exception_context.eflags)[2:] | 2148 print " eflags: %s" % bin(reader.exception_context.eflags)[2:] |
2095 | 2149 |
2096 # TODO(mstarzinger): Disabled because broken, needs investigation. | 2150 print |
2097 #print | 2151 print " modules:" |
2098 #print " modules:" | 2152 for module in reader.module_list.modules: |
2099 #for module in reader.module_list.modules: | 2153 name = GetModuleName(reader, module) |
2100 # name = GetModuleName(reader, module) | 2154 if name in KNOWN_MODULES: |
2101 # if name in KNOWN_MODULES: | 2155 print " %s at %08X" % (name, module.base_of_image) |
2102 # print " %s at %08X" % (name, module.base_of_image) | 2156 reader.TryLoadSymbolsFor(name, module) |
2103 # reader.TryLoadSymbolsFor(name, module) | |
2104 print | 2157 print |
2105 | 2158 |
2106 stack_top = reader.ExceptionSP() | 2159 stack_top = reader.ExceptionSP() |
2107 stack_bottom = exception_thread.stack.start + \ | 2160 stack_bottom = exception_thread.stack.start + \ |
2108 exception_thread.stack.memory.data_size | 2161 exception_thread.stack.memory.data_size |
2109 stack_map = {reader.ExceptionIP(): -1} | 2162 stack_map = {reader.ExceptionIP(): -1} |
2110 for slot in xrange(stack_top, stack_bottom, reader.PointerSize()): | 2163 for slot in xrange(stack_top, stack_bottom, reader.PointerSize()): |
2111 maybe_address = reader.ReadUIntPtr(slot) | 2164 maybe_address = reader.ReadUIntPtr(slot) |
2112 if not maybe_address in stack_map: | 2165 if not maybe_address in stack_map: |
2113 stack_map[maybe_address] = slot | 2166 stack_map[maybe_address] = slot |
(...skipping 16 matching lines...) Expand all Loading... |
2130 for line in lines: | 2183 for line in lines: |
2131 print FormatDisasmLine(disasm_start, heap, line) | 2184 print FormatDisasmLine(disasm_start, heap, line) |
2132 print | 2185 print |
2133 | 2186 |
2134 if heap is None: | 2187 if heap is None: |
2135 heap = V8Heap(reader, None) | 2188 heap = V8Heap(reader, None) |
2136 | 2189 |
2137 if options.full: | 2190 if options.full: |
2138 FullDump(reader, heap) | 2191 FullDump(reader, heap) |
2139 | 2192 |
| 2193 if options.command: |
| 2194 InspectionShell(reader, heap).onecmd(options.command) |
| 2195 |
2140 if options.shell: | 2196 if options.shell: |
2141 try: | 2197 try: |
2142 InspectionShell(reader, heap).cmdloop("type help to get help") | 2198 InspectionShell(reader, heap).cmdloop("type help to get help") |
2143 except KeyboardInterrupt: | 2199 except KeyboardInterrupt: |
2144 print "Kthxbye." | 2200 print "Kthxbye." |
2145 else: | 2201 elif not options.command: |
2146 if reader.exception is not None: | 2202 if reader.exception is not None: |
2147 print "Annotated stack (from exception.esp to bottom):" | 2203 print "Annotated stack (from exception.esp to bottom):" |
2148 for slot in xrange(stack_top, stack_bottom, reader.PointerSize()): | 2204 for slot in xrange(stack_top, stack_bottom, reader.PointerSize()): |
2149 maybe_address = reader.ReadUIntPtr(slot) | 2205 maybe_address = reader.ReadUIntPtr(slot) |
2150 heap_object = heap.FindObject(maybe_address) | 2206 heap_object = heap.FindObject(maybe_address) |
2151 maybe_symbol = reader.FindSymbol(maybe_address) | 2207 maybe_symbol = reader.FindSymbol(maybe_address) |
2152 print "%s: %s %s" % (reader.FormatIntPtr(slot), | 2208 print "%s: %s %s" % (reader.FormatIntPtr(slot), |
2153 reader.FormatIntPtr(maybe_address), | 2209 reader.FormatIntPtr(maybe_address), |
2154 maybe_symbol or "") | 2210 maybe_symbol or "") |
2155 if heap_object: | 2211 if heap_object: |
2156 heap_object.Print(Printer()) | 2212 heap_object.Print(Printer()) |
2157 print | 2213 print |
2158 | 2214 |
2159 reader.Dispose() | 2215 reader.Dispose() |
2160 | 2216 |
2161 | 2217 |
2162 if __name__ == "__main__": | 2218 if __name__ == "__main__": |
2163 parser = optparse.OptionParser(USAGE) | 2219 parser = optparse.OptionParser(USAGE) |
2164 parser.add_option("-s", "--shell", dest="shell", action="store_true", | 2220 parser.add_option("-s", "--shell", dest="shell", action="store_true", |
2165 help="start an interactive inspector shell") | 2221 help="start an interactive inspector shell") |
| 2222 parser.add_option("-c", "--command", dest="command", default="", |
| 2223 help="run an interactive inspector shell command and exit") |
2166 parser.add_option("-f", "--full", dest="full", action="store_true", | 2224 parser.add_option("-f", "--full", dest="full", action="store_true", |
2167 help="dump all information contained in the minidump") | 2225 help="dump all information contained in the minidump") |
2168 parser.add_option("--symdir", dest="symdir", default=".", | 2226 parser.add_option("--symdir", dest="symdir", default=".", |
2169 help="directory containing *.pdb.sym file with symbols") | 2227 help="directory containing *.pdb.sym file with symbols") |
2170 parser.add_option("--objdump", | 2228 parser.add_option("--objdump", |
2171 default="/usr/bin/objdump", | 2229 default="/usr/bin/objdump", |
2172 help="objdump tool to use [default: %default]") | 2230 help="objdump tool to use [default: %default]") |
2173 options, args = parser.parse_args() | 2231 options, args = parser.parse_args() |
2174 if os.path.exists(options.objdump): | 2232 if os.path.exists(options.objdump): |
2175 disasm.OBJDUMP_BIN = options.objdump | 2233 disasm.OBJDUMP_BIN = options.objdump |
2176 OBJDUMP_BIN = options.objdump | 2234 OBJDUMP_BIN = options.objdump |
2177 else: | 2235 else: |
2178 print "Cannot find %s, falling back to default objdump" % options.objdump | 2236 print "Cannot find %s, falling back to default objdump" % options.objdump |
2179 if len(args) != 1: | 2237 if len(args) != 1: |
2180 parser.print_help() | 2238 parser.print_help() |
2181 sys.exit(1) | 2239 sys.exit(1) |
2182 AnalyzeMinidump(options, args[0]) | 2240 AnalyzeMinidump(options, args[0]) |
OLD | NEW |