Extend grokdump.py with simple BreakPad symbol files support.
authorvegorov@chromium.org <vegorov@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 26 Sep 2012 12:51:46 +0000 (12:51 +0000)
committervegorov@chromium.org <vegorov@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 26 Sep 2012 12:51:46 +0000 (12:51 +0000)
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

index 5d9a053..46ead5e 100755 (executable)
 # (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
+  #     <symbol name>+<offset from the start>
+  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()