From d6853c36971bdcc19411a59df484d90e184d75af Mon Sep 17 00:00:00 2001 From: "vegorov@chromium.org" Date: Wed, 26 Sep 2012 12:51:46 +0000 Subject: [PATCH] Extend grokdump.py with simple BreakPad symbol files support. R=mstarzinger@chromium.org BUG= Review URL: https://codereview.chromium.org/10923003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12620 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- tools/grokdump.py | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 133 insertions(+), 6 deletions(-) diff --git a/tools/grokdump.py b/tools/grokdump.py index 5d9a053..46ead5e 100755 --- a/tools/grokdump.py +++ b/tools/grokdump.py @@ -27,17 +27,18 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import bisect import cmd +import codecs import ctypes +import disasm import mmap import optparse import os -import disasm -import sys -import types -import codecs import re import struct +import sys +import types USAGE="""usage: %prog [OPTIONS] [DUMP-FILE] @@ -180,6 +181,11 @@ MINIDUMP_LOCATION_DESCRIPTOR = Descriptor([ ("rva", ctypes.c_uint32) ]) +MINIDUMP_STRING = Descriptor([ + ("length", ctypes.c_uint32), + ("buffer", lambda t: ctypes.c_uint8 * (t.length + 2)) +]) + MINIDUMP_DIRECTORY = Descriptor([ ("stream_type", ctypes.c_uint32), ("location", MINIDUMP_LOCATION_DESCRIPTOR.ctype) @@ -400,6 +406,24 @@ MINIDUMP_THREAD_LIST = Descriptor([ ("threads", lambda t: MINIDUMP_THREAD.ctype * t.thread_count) ]) +MINIDUMP_RAW_MODULE = Descriptor([ + ("base_of_image", ctypes.c_uint64), + ("size_of_image", ctypes.c_uint32), + ("checksum", ctypes.c_uint32), + ("time_date_stamp", ctypes.c_uint32), + ("module_name_rva", ctypes.c_uint32), + ("version_info", ctypes.c_uint32 * 13), + ("cv_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype), + ("misc_record", MINIDUMP_LOCATION_DESCRIPTOR.ctype), + ("reserved0", ctypes.c_uint32 * 2), + ("reserved1", ctypes.c_uint32 * 2) +]) + +MINIDUMP_MODULE_LIST = Descriptor([ + ("number_of_modules", ctypes.c_uint32), + ("modules", lambda t: MINIDUMP_RAW_MODULE.ctype * t.number_of_modules) +]) + MINIDUMP_RAW_SYSTEM_INFO = Descriptor([ ("processor_architecture", ctypes.c_uint16) ]) @@ -407,6 +431,20 @@ MINIDUMP_RAW_SYSTEM_INFO = Descriptor([ MD_CPU_ARCHITECTURE_X86 = 0 MD_CPU_ARCHITECTURE_AMD64 = 9 +class FuncSymbol: + def __init__(self, start, size, name): + self.start = start + self.end = self.start + size + self.name = name + + def __cmp__(self, other): + if isinstance(other, FuncSymbol): + return self.start - other.start + return self.start - other + + def Covers(self, addr): + return (self.start <= addr) and (addr < self.end) + class MinidumpReader(object): """Minidump (.dmp) reader.""" @@ -430,8 +468,13 @@ class MinidumpReader(object): self.exception_context = None self.memory_list = None self.memory_list64 = None + self.module_list = None self.thread_map = {} + self.symdir = options.symdir + self.modules_with_symbols = [] + self.symbols = [] + # Find MDRawSystemInfo stream and determine arch. for d in directories: if d.stream_type == MD_SYSTEM_INFO_STREAM: @@ -461,6 +504,11 @@ class MinidumpReader(object): for thread in thread_list.threads: DebugPrint(thread) self.thread_map[thread.id] = thread + elif d.stream_type == MD_MODULE_LIST_STREAM: + assert self.module_list is None + self.module_list = MINIDUMP_MODULE_LIST.Read( + self.minidump, d.location.rva) + assert ctypes.sizeof(self.module_list) == d.location.data_size elif d.stream_type == MD_MEMORY_LIST_STREAM: print >>sys.stderr, "Warning: This is not a full minidump!" assert self.memory_list is None @@ -644,6 +692,66 @@ class MinidumpReader(object): def Register(self, name): return self.exception_context.__getattribute__(name) + def ReadMinidumpString(self, rva): + string = bytearray(MINIDUMP_STRING.Read(self.minidump, rva).buffer) + string = string.decode("utf16") + return string[0:len(string) - 1] + + # Load FUNC records from a BreakPad symbol file + # + # http://code.google.com/p/google-breakpad/wiki/SymbolFiles + # + def _LoadSymbolsFrom(self, symfile, baseaddr): + print "Loading symbols from %s" % (symfile) + funcs = [] + with open(symfile) as f: + for line in f: + result = re.match( + r"^FUNC ([a-f0-9]+) ([a-f0-9]+) ([a-f0-9]+) (.*)$", line) + if result is not None: + start = int(result.group(1), 16) + size = int(result.group(2), 16) + name = result.group(4).rstrip() + bisect.insort_left(self.symbols, + FuncSymbol(baseaddr + start, size, name)) + print " ... done" + + def TryLoadSymbolsFor(self, modulename, module): + try: + symfile = os.path.join(self.symdir, + modulename.replace('.', '_') + ".pdb.sym") + self._LoadSymbolsFrom(symfile, module.base_of_image) + self.modules_with_symbols.append(module) + except Exception as e: + print " ... failure (%s)" % (e) + + # Returns true if address is covered by some module that has loaded symbols. + def _IsInModuleWithSymbols(self, addr): + for module in self.modules_with_symbols: + start = module.base_of_image + end = start + module.size_of_image + if (start <= addr) and (addr < end): + return True + return False + + # Find symbol covering the given address and return its name in format + # + + def FindSymbol(self, addr): + if not self._IsInModuleWithSymbols(addr): + return None + + i = bisect.bisect_left(self.symbols, addr) + symbol = None + if (0 < i) and self.symbols[i - 1].Covers(addr): + symbol = self.symbols[i - 1] + elif (i < len(self.symbols)) and self.symbols[i].Covers(addr): + symbol = self.symbols[i] + else: + return None + diff = addr - symbol.start + return "%s+0x%x" % (symbol.name, diff) + + # List of V8 instance types. Obtained by adding the code below to any .cc file. # @@ -1639,6 +1747,11 @@ CONTEXT_FOR_ARCH = { ['eax', 'ebx', 'ecx', 'edx', 'edi', 'esi', 'ebp', 'esp', 'eip'] } +KNOWN_MODULES = {'chrome.exe', 'chrome.dll'} + +def GetModuleName(reader, module): + name = reader.ReadMinidumpString(module.module_name_rva) + return str(os.path.basename(str(name).replace("\\", "/"))) def AnalyzeMinidump(options, minidump_name): reader = MinidumpReader(options, minidump_name) @@ -1657,6 +1770,13 @@ def AnalyzeMinidump(options, minidump_name): # TODO(vitalyr): decode eflags. print " eflags: %s" % bin(reader.exception_context.eflags)[2:] print + print " modules:" + for module in reader.module_list.modules: + name = GetModuleName(reader, module) + if name in KNOWN_MODULES: + print " %s at %08X" % (name, module.base_of_image) + reader.TryLoadSymbolsFor(name, module) + print stack_top = reader.ExceptionSP() stack_bottom = exception_thread.stack.start + \ @@ -1669,6 +1789,9 @@ def AnalyzeMinidump(options, minidump_name): heap = V8Heap(reader, stack_map) print "Disassembly around exception.eip:" + eip_symbol = reader.FindSymbol(reader.ExceptionIP()) + if eip_symbol is not None: + print eip_symbol disasm_start = reader.ExceptionIP() - EIP_PROXIMITY disasm_bytes = 2 * EIP_PROXIMITY if (options.full): @@ -1697,8 +1820,10 @@ def AnalyzeMinidump(options, minidump_name): for slot in xrange(stack_top, stack_bottom, reader.PointerSize()): maybe_address = reader.ReadUIntPtr(slot) heap_object = heap.FindObject(maybe_address) - print "%s: %s" % (reader.FormatIntPtr(slot), - reader.FormatIntPtr(maybe_address)) + maybe_symbol = reader.FindSymbol(maybe_address) + print "%s: %s %s" % (reader.FormatIntPtr(slot), + reader.FormatIntPtr(maybe_address), + maybe_symbol or "") if heap_object: heap_object.Print(Printer()) print @@ -1712,6 +1837,8 @@ if __name__ == "__main__": help="start an interactive inspector shell") parser.add_option("-f", "--full", dest="full", action="store_true", help="dump all information contained in the minidump") + parser.add_option("--symdir", dest="symdir", default=".", + help="directory containing *.pdb.sym file with symbols") options, args = parser.parse_args() if len(args) != 1: parser.print_help() -- 2.7.4