From bbd00c3a91fac9a25a0351337602e6477987a7b7 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Fri, 9 Oct 2015 13:39:39 -0700 Subject: [PATCH] win: Test some basic ! windbg commands R=mark@chromium.org BUG=crashpad:20, crashpad:46, crashpad:52 Review URL: https://codereview.chromium.org/1397833004 . --- build/run_tests.py | 2 +- snapshot/win/end_to_end_test.py | 162 ++++++++++++++++++++++++++++++-- 2 files changed, 154 insertions(+), 10 deletions(-) diff --git a/build/run_tests.py b/build/run_tests.py index 9d8935ef..43bb3158 100755 --- a/build/run_tests.py +++ b/build/run_tests.py @@ -64,7 +64,7 @@ def main(args): print name print '-' * 80 subprocess.check_call( - [sys.executable, os.path.join(crashpad_dir, name), args[0]]) + [sys.executable, os.path.join(crashpad_dir, name), out_dir]) return 0 diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index 0a13befd..64de3b21 100644 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -15,7 +15,26 @@ # limitations under the License. import os +import random +import re +import subprocess import sys +import tempfile + +g_temp_dirs = [] + + +def MakeTempDir(): + global g_temp_dirs + new_dir = tempfile.mkdtemp() + g_temp_dirs.append(new_dir) + return new_dir + + +def CleanUpTempDirs(): + global g_temp_dirs + for d in g_temp_dirs: + subprocess.call(['rmdir', '/s', '/q', d], shell=True) def FindInstalledWindowsApplication(app_path): @@ -35,6 +54,9 @@ def FindInstalledWindowsApplication(app_path): def GetCdbPath(): + """Search in some reasonable places to find cdb.exe. Searches x64 before x86 + and newer versions before older versions. + """ possible_paths = ( os.path.join('Windows Kits', '10', 'Debuggers', 'x64'), os.path.join('Windows Kits', '10', 'Debuggers', 'x86'), @@ -44,12 +66,7 @@ def GetCdbPath(): os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'), 'Debugging Tools For Windows (x64)', 'Debugging Tools For Windows (x86)', - 'Debugging Tools For Windows', - os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers', - 'x64'), - os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers', - 'x86'), - ) + 'Debugging Tools For Windows',) for possible_path in possible_paths: app_path = os.path.join(possible_path, 'cdb.exe') app_path = FindInstalledWindowsApplication(app_path) @@ -58,10 +75,137 @@ def GetCdbPath(): return None +def GetDumpFromCrashyProgram(out_dir, pipe_name): + """Initialize a crash database, run crashpad_handler, run crashy_program + connecting to the crash_handler. Returns the minidump generated by + crash_handler for further testing. + """ + test_database = MakeTempDir() + + try: + if subprocess.call( + [os.path.join(out_dir, 'crashpad_database_util.exe'), '--create', + '--database=' + test_database]) != 0: + print 'could not initialize report database' + return None + + handler = subprocess.Popen([ + os.path.join(out_dir, 'crashpad_handler.exe'), + '--pipe-name=' + pipe_name, + '--database=' + test_database + ]) + + subprocess.call([os.path.join(out_dir, 'crashy_program.exe'), pipe_name]) + + out = subprocess.check_output([ + os.path.join(out_dir, 'crashpad_database_util.exe'), + '--database=' + test_database, + '--show-completed-reports', + '--show-all-report-info', + ]) + for line in out.splitlines(): + if line.strip().startswith('Path:'): + return line.partition(':')[2].strip() + + finally: + if handler: + handler.kill() + + +class CdbRun(object): + """Run cdb.exe passing it a cdb command and capturing the output. + `Check()` searches for regex patterns in sequence allowing verification of + expected output. + """ + + def __init__(self, cdb_path, dump_path, command): + # Run a command line that loads the dump, runs the specified cdb command, + # and then quits, and capturing stdout. + self.out = subprocess.check_output([ + cdb_path, + '-z', dump_path, + '-c', command + ';q' + ]) + + def Check(self, pattern, message): + match_obj = re.search(pattern, self.out) + if match_obj: + # Matched. Consume up to end of match. + self.out = self.out[match_obj.end(0):] + print 'ok - %s' % message + else: + print >>sys.stderr, '-' * 80 + print >>sys.stderr, 'FAILED - %s' % message + print >>sys.stderr, '-' * 80 + print >>sys.stderr, 'did not match:\n %s' % pattern + print >>sys.stderr, '-' * 80 + print >>sys.stderr, 'remaining output was:\n %s' % self.out + print >>sys.stderr, '-' * 80 + sys.exit(1) + + +def RunTests(cdb_path, dump_path, pipe_name): + """Runs various tests in sequence. Runs a new cdb instance on the dump for + each block of tests to reduce the chances that output from one command is + confused for output from another. + """ + out = CdbRun(cdb_path, dump_path, '.ecxr') + out.Check('This dump file has an exception of interest stored in it', + 'captured exception') + out.Check( + 'crashy_program!crashpad::`anonymous namespace\'::SomeCrashyFunction', + 'exception at correct location') + + out = CdbRun(cdb_path, dump_path, '!peb') + out.Check(r'PEB at', 'found the PEB') + out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved') + out.Check(r'Base TimeStamp Module', 'module list present') + pipe_name_escaped = pipe_name.replace('\\', '\\\\') + out.Check(r'CommandLine: *\'.*crashy_program.exe *' + pipe_name_escaped, + 'some PEB data is correct') + out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured') + + out = CdbRun(cdb_path, dump_path, '!teb') + out.Check(r'TEB at', 'found the TEB') + out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data') + out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue') + + out = CdbRun(cdb_path, dump_path, '!gle') + out.Check('LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the ' + 'file specified.', '!gle gets last error') + out.Check('LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found} The ' + 'file %hs does not exist.', '!gle gets last ntstatus') + + # Locks. + out = CdbRun(cdb_path, dump_path, '!locks') + out.Check(r'CritSec crashy_program!crashpad::`anonymous namespace\'::' + r'g_test_critical_section', 'lock was captured') + out.Check(r'\*\*\* Locked', 'lock debug info was captured, and is locked') + + def main(args): - cdb_path = GetCdbPath() - print 'cdb_path:', cdb_path - return 0 + try: + if len(args) != 1: + print >>sys.stderr, 'must supply out dir' + return 1 + + cdb_path = GetCdbPath() + if not cdb_path: + print >>sys.stderr, 'could not find cdb' + return 1 + + pipe_name = r'\\.\pipe\end-to-end_%s_%s' % ( + os.getpid(), str(random.getrandbits(64))) + + dump_path = GetDumpFromCrashyProgram(args[0], pipe_name) + if not dump_path: + return 1 + + RunTests(cdb_path, dump_path, pipe_name) + + return 0 + finally: + CleanUpTempDirs() if __name__ == '__main__':