summaryrefslogtreecommitdiffstats
path: root/chrome/tools
diff options
context:
space:
mode:
authorthestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-16 18:35:08 +0000
committerthestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-16 18:35:08 +0000
commit4576ba04a833ea439f6dd034eeced33b24c898e9 (patch)
tree5ccd55e19106f06605e27dea96a3e3e98c1df1dc /chrome/tools
parent43919ac90f426c807da507d52c163972634c771e (diff)
downloadchromium_src-4576ba04a833ea439f6dd034eeced33b24c898e9.zip
chromium_src-4576ba04a833ea439f6dd034eeced33b24c898e9.tar.gz
chromium_src-4576ba04a833ea439f6dd034eeced33b24c898e9.tar.bz2
Add a script to process dumps on Linux.
BUG=22265 TEST=none Review URL: http://codereview.chromium.org/267123 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29298 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/tools')
-rwxr-xr-xchrome/tools/process_dumps_linux.py278
1 files changed, 278 insertions, 0 deletions
diff --git a/chrome/tools/process_dumps_linux.py b/chrome/tools/process_dumps_linux.py
new file mode 100755
index 0000000..6e35640
--- /dev/null
+++ b/chrome/tools/process_dumps_linux.py
@@ -0,0 +1,278 @@
+#!/usr/bin/python
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A tool to collect crash signatures for Chrome builds on Linux."""
+
+import fnmatch
+import optparse
+import os
+import shutil
+import subprocess
+import struct
+import sys
+import tempfile
+
+
+def VerifySymbolAndCopyToTempDir(symbol_file, temp_dir):
+ """Verify the symbol file looks correct and copy it to the right place
+ in temp_dir.
+
+ Args:
+ symbol_file: the path to the symbol file.
+ temp_dir: the base of the temp directory where the symbol file will reside.
+ Returns:
+ True on success.
+ """
+ symbol = open(symbol_file)
+ signature_line = symbol.readline().strip().split()
+ symbol.close()
+ # signature_line should look like:
+ # MODULE Linux x86 28D8A79A426807B5462CBA24F56746750 chrome
+ if (len(signature_line) == 5 and signature_line[0] == 'MODULE' and
+ signature_line[1] == 'Linux' and signature_line[4] == 'chrome' and
+ len(signature_line[3]) == 33):
+ dest = os.path.join(temp_dir, signature_line[4], signature_line[3])
+ os.makedirs(dest)
+ dest_file = os.path.join(dest, '%s.sym' % signature_line[4])
+ shutil.copyfile(symbol_file, dest_file)
+ return True
+ return False
+
+
+def GetCommandOutput(command):
+ """Runs the command list, returning its output.
+
+ Prints the given command (which should be a list of one or more strings),
+ then runs it and returns its output (stdout and stderr) as a string.
+
+ If the command exits with an error, raises OSError.
+
+ From chromium_utils.
+ """
+ proc = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT, bufsize=1)
+ output = proc.communicate()[0]
+ if proc.returncode:
+ raise OSError('%s: %s' % (subprocess.list2cmdline(command), output))
+ return output
+
+
+def GetCrashDumpDir():
+ """Returns the default crash dump directory used by Chromium."""
+ config_home = os.environ.get('XDG_CONFIG_HOME')
+ if not config_home:
+ home = os.path.expanduser('~')
+ if not home:
+ return ''
+ config_home = os.path.join(home, '.config')
+ return os.path.join(config_home, 'chromium', 'Crash Reports')
+
+
+def GetStackTrace(processor_bin, symbol_path, dump_file):
+ """Gets and prints the stack trace from a crash dump file.
+
+ Args:
+ processor_bin: the path to the processor.
+ symbol_path: root dir for the symbols.
+ dump_file: the path to the dump file.
+ Returns:
+ A string representing the stack trace.
+ """
+ # Run processor to analyze crash dump.
+ cmd = [processor_bin, '-m', dump_file, symbol_path]
+
+ try:
+ output = GetCommandOutput(cmd)
+ except OSError:
+ return 'Cannot get stack trace.'
+
+ # Retrieve stack trace from processor output. Processor output looks like:
+ # ----------------
+ # Debug output
+ # ...
+ # Debug output
+ # Module ...
+ # ...
+ # Module ...
+ #
+ # N|... <--+
+ # ... |--- crashed thread stack trace
+ # N|... <--+
+ # M|...
+ # ...
+ # ----------------
+ # where each line of the stack trace looks like:
+ # ThreadNumber|FrameNumber|ExeName|Function|SourceFile|LineNo|Offset
+
+ stack_trace_frames = []
+ idx = output.find('\nModule')
+ if idx >= 0:
+ output = output[idx+1:]
+ idx = output.find('\n\n')
+ if idx >= 0:
+ output = output[idx+2:].splitlines()
+ if output:
+ first_line = output[0].split('|')
+ if first_line:
+ crashed_thread = first_line[0]
+ for line in output:
+ line_split = line.split('|')
+ if not line_split:
+ break
+ if line_split[0] != crashed_thread:
+ break
+ stack_trace_frames.append(line_split)
+ if not stack_trace_frames:
+ return 'Cannot get stack trace.'
+ stack_trace = []
+ for frame in stack_trace_frames:
+ if len(frame) != 7:
+ continue
+ (exe, func, source, line, offset) = frame[2:]
+ if not exe or not source or not line or not offset:
+ continue
+ idx = func.find('(')
+ if idx >= 0:
+ func = func[:idx]
+ if not func:
+ continue
+ frame_output = '%s!%s+%s [%s @ %s]' % (exe, func, offset, source, line)
+ stack_trace.append(frame_output)
+ return '\n'.join(stack_trace)
+
+
+def LocateFiles(pattern, root=os.curdir):
+ """Yields files matching pattern found in root and its subdirectories.
+
+ An exception is thrown if root doesn't exist.
+
+ From chromium_utils."""
+ root = os.path.expanduser(root)
+ for path, dirs, files in os.walk(os.path.abspath(root)):
+ for filename in fnmatch.filter(files, pattern):
+ yield os.path.join(path, filename)
+
+
+def ProcessDump(dump_file, temp_dir):
+ """Extracts the part of the dump file that minidump_stackwalk can read.
+
+ Args:
+ dump_file: the dump file that needs to be processed.
+ temp_dir: the temp directory to put the dump file in.
+ Returns:
+ path of the processed dump file.
+ """
+ dump = open(dump_file, 'rb')
+ dump_data = dump.read()
+ dump.close()
+ idx = dump_data.find('MDMP')
+ if idx < 0:
+ return ''
+
+ dump_data = dump_data[idx:]
+ if not dump_data:
+ return ''
+ (dump_fd, dump_name) = tempfile.mkstemp(suffix='chromedump', dir=temp_dir)
+ os.write(dump_fd, dump_data)
+ os.close(dump_fd)
+ return dump_name
+
+
+def main_linux(options, args):
+ # minidump_stackwalk is part of Google Breakpad. You may need to checkout
+ # the code and build your own copy. http://google-breakpad.googlecode.com/
+ LINUX_PROCESSOR = 'minidump_stackwalk'
+ processor_bin = None
+ if options.processor_dir:
+ bin = os.path.join(os.path.expanduser(options.processor_dir),
+ LINUX_PROCESSOR)
+ if os.access(bin, os.X_OK):
+ processor_bin = bin
+ else:
+ for path in os.environ['PATH'].split(':'):
+ bin = os.path.join(path, LINUX_PROCESSOR)
+ if os.access(bin, os.X_OK):
+ processor_bin = bin
+ break
+ if not processor_bin:
+ print 'Cannot find minidump_stackwalk.'
+ return 1
+
+ if options.architecture:
+ bits = options.architecture
+ else:
+ bits = struct.calcsize('P') * 8
+ if bits == 32:
+ symbol_file = 'chrome.breakpad.ia32'
+ elif bits == 64:
+ symbol_file = 'chrome.breakpad.ia64'
+ else:
+ print 'Unknown architecture'
+ return 1
+
+ symbol_dir = options.symbol_dir
+ if not options.symbol_dir:
+ symbol_dir = os.curdir
+ symbol_dir = os.path.abspath(os.path.expanduser(symbol_dir))
+ symbol_file = os.path.join(symbol_dir, symbol_file)
+ if not os.path.exists(symbol_file):
+ print 'Cannot find symbols.'
+ return 1
+ symbol_time = os.path.getmtime(symbol_file)
+
+ dump_dir = options.dump_dir
+ if not dump_dir:
+ dump_dir = GetCrashDumpDir()
+ if not dump_dir:
+ print 'Cannot find dump dir.'
+ return 1
+
+ temp_dir = tempfile.mkdtemp(suffix='chromedump')
+ if not VerifySymbolAndCopyToTempDir(symbol_file, temp_dir):
+ print 'Cannot parse symbols.'
+ shutil.rmtree(temp_dir)
+ return 1
+
+ dump_count = 0
+ for dump_file in LocateFiles(pattern='*.dmp', root=dump_dir):
+ file_time = os.path.getmtime(dump_file)
+ if file_time < symbol_time:
+ # Ignore dumps older than symbol file.
+ continue
+ processed_dump_file = ProcessDump(dump_file, temp_dir)
+ if not processed_dump_file:
+ continue
+ print '-------------------------'
+ print GetStackTrace(processor_bin, temp_dir, processed_dump_file)
+ print
+ os.remove(processed_dump_file)
+ dump_count += 1
+
+ shutil.rmtree(temp_dir)
+ print '%s dumps found' % dump_count
+ return 0
+
+
+if '__main__' == __name__:
+ parser = optparse.OptionParser()
+ parser.add_option('', '--processor-dir', type='string', default='',
+ help='The directory where the processor is installed. '
+ 'The processor is used to get stack trace from dumps. '
+ 'Searches $PATH by default')
+ parser.add_option('', '--dump-dir', type='string', default='',
+ help='The directory where dump files are stored. '
+ 'Searches the Chromium crash directory by default.')
+ parser.add_option('', '--symbol-dir', default='',
+ help='The directory with the symbols file. [Required]')
+ parser.add_option('', '--architecture', type='int', default=None,
+ help='Override automatic x86/x86-64 detection. '
+ 'Valid values are 32 and 64')
+
+ (options, args) = parser.parse_args()
+
+ if sys.platform == 'linux2':
+ sys.exit(main_linux(options, args))
+ else:
+ sys.exit(1)