blob: 439d7cb3af870d6b5258a66618dbd8f7305dc0a6 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2011 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 builds.
"""
import optparse
import os
import sys
from common import chromium_utils
from recipes import build_directory
# Deprecated magic return code that buildbot used to interpret as "warnings"
# instead of "error". Kept for backwards compatibility purposes, but essentially
# useless.
RETCODE_WARNINGS = 88
# Environment variables that could point to the location of Program Files.
PROGRAM_FILES_ENV_VARS = [
'PROGRAMFILES',
'PROGRAMFILES(X86)',
'PROGRAMW6432',
]
# Places relative to Program Files that the debugger could be located.
DEBUGGER_DIRS = [
'Debugging Tools For Windows',
'Debugging Tools For Windows (x86)',
'Debugging Tools For Windows (x64)',
'Windows Kits\\8.0\\Debuggers\\x86',
'Windows Kits\\8.1\\Debuggers\\x64',
'Windows Kits\\10\\Debuggers\\x64',
]
def GetCrashDumpDir():
"""Returns the default crash dump directory used by chromium. """
local_app_data = os.environ.get('LOCALAPPDATA')
if not local_app_data:
user_profile = os.environ.get('USERPROFILE')
if not user_profile:
sys.exit(0)
local_app_data = '%s\\Local Settings\\Application Data' % user_profile
return '%s\\Chromium\\User Data\\Crash Reports' % local_app_data
def CdbExistsAtLocation(candidate_dir):
return (
os.path.exists(candidate_dir) and
os.path.isfile(os.path.join(candidate_dir, "cdb.exe"))
)
def ProbeDebuggerDir():
"""Probes the debugger installed path and returns the path."""
for env_var in PROGRAM_FILES_ENV_VARS:
program_files = os.environ.get(env_var)
if program_files:
for debugger_dir in DEBUGGER_DIRS:
debugger_dir = os.path.join(program_files, debugger_dir)
if CdbExistsAtLocation(debugger_dir):
return debugger_dir
return None
def GetStackTrace(debugger_dir, symbol_path, dump_file):
"""Gets and prints the stack trace from a crash dump file.
Args:
debugger_dir: the directory where the debugger is installed.
symbol_path: symbol path for debugger.
dump_file: the path to dump file.
Returns:
A string representing the stack trace.
"""
# Run debugger to analyze crash dump.
cmd = '%s\\cdb.exe -y "%s" -c ".ecxr;k30;q" -z "%s"' % (
debugger_dir, symbol_path, dump_file
)
try:
output = chromium_utils.GetCommandOutput(cmd)
except chromium_utils.ExternalError:
return 'Cannot get stack trace.'
# Retrieve stack trace from debugger output.
stack_start = output.find('ChildEBP')
stack_end = output.find('quit:')
return output[stack_start:stack_end]
def main():
parser = optparse.OptionParser()
parser.add_option(
'--dump-dir',
type='string',
default='',
help='The directory where dump files are stored.'
)
parser.add_option(
'--debugger-dir',
type='string',
default='',
help='The directory where the debugger is installed.'
'The debugger is used to get stack trace from dumps.'
)
parser.add_option('--build-dir', help='ignored')
parser.add_option(
'--target', default='Release', help='build target (Debug or Release)'
)
parser.add_option(
'--archive-dir',
type='string',
default='',
help='If specified, save dump files to the archive'
'directory.'
)
options, args = parser.parse_args()
options.build_dir = build_directory.GetBuildOutputDirectory()
if args:
parser.error('Unknown args "%s"' % ' '.join(args))
return 1
debugger_dir = options.debugger_dir
if not os.path.exists(debugger_dir):
debugger_dir = ProbeDebuggerDir()
if not debugger_dir:
print 'Cannot find debugger.'
return RETCODE_WARNINGS
print 'Debugger directory: %s' % debugger_dir
symbol_path = os.path.join(options.build_dir, options.target)
dll_path = os.path.join(symbol_path, 'chrome.dll')
if not os.path.exists(dll_path):
print 'Cannot find chrome.dll.'
return 0
dll_time = os.path.getmtime(dll_path)
dump_dir = options.dump_dir
if not dump_dir:
dump_dir = GetCrashDumpDir()
if not os.path.exists(dump_dir):
print 'The crash dump directory does not exist'
return 0
dump_count = 0
for dump_basename in os.listdir(dump_dir):
dump_file = os.path.join(dump_dir, dump_basename)
file_time = os.path.getmtime(dump_file)
if file_time < dll_time and os.path.isfile(dump_file):
# Ignore dumps older than dll file.
print 'Deleting old dump: %s' % dump_file
os.remove(dump_file)
elif dump_basename.endswith('.dmp'):
print '-------------------------'
print dump_basename
stack = GetStackTrace(debugger_dir, symbol_path, dump_file)
print stack
dump_count += 1
print '%s dumps found' % dump_count
# TODO(huanr): add the functionality of archiving dumps.
return 0
if '__main__' == __name__:
sys.exit(main())