diff options
author | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-16 18:35:08 +0000 |
---|---|---|
committer | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-16 18:35:08 +0000 |
commit | 4576ba04a833ea439f6dd034eeced33b24c898e9 (patch) | |
tree | 5ccd55e19106f06605e27dea96a3e3e98c1df1dc /chrome/tools | |
parent | 43919ac90f426c807da507d52c163972634c771e (diff) | |
download | chromium_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-x | chrome/tools/process_dumps_linux.py | 278 |
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) |