OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 | 2 |
3 # Copyright 2015 The Crashpad Authors. All rights reserved. | 3 # Copyright 2015 The Crashpad Authors. All rights reserved. |
4 # | 4 # |
5 # Licensed under the Apache License, Version 2.0 (the "License"); | 5 # Licensed under the Apache License, Version 2.0 (the "License"); |
6 # you may not use this file except in compliance with the License. | 6 # you may not use this file except in compliance with the License. |
7 # You may obtain a copy of the License at | 7 # You may obtain a copy of the License at |
8 # | 8 # |
9 # http://www.apache.org/licenses/LICENSE-2.0 | 9 # http://www.apache.org/licenses/LICENSE-2.0 |
10 # | 10 # |
11 # Unless required by applicable law or agreed to in writing, software | 11 # Unless required by applicable law or agreed to in writing, software |
12 # distributed under the License is distributed on an "AS IS" BASIS, | 12 # distributed under the License is distributed on an "AS IS" BASIS, |
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 # See the License for the specific language governing permissions and | 14 # See the License for the specific language governing permissions and |
15 # limitations under the License. | 15 # limitations under the License. |
16 | 16 |
17 import os | 17 import os |
| 18 import random |
| 19 import re |
| 20 import subprocess |
18 import sys | 21 import sys |
| 22 import tempfile |
| 23 |
| 24 g_temp_dirs = [] |
| 25 |
| 26 |
| 27 def MakeTempDir(): |
| 28 global g_temp_dirs |
| 29 new_dir = tempfile.mkdtemp() |
| 30 g_temp_dirs.append(new_dir) |
| 31 return new_dir |
| 32 |
| 33 |
| 34 def CleanUpTempDirs(): |
| 35 global g_temp_dirs |
| 36 for d in g_temp_dirs: |
| 37 subprocess.call(['rmdir', '/s', '/q', d], shell=True) |
19 | 38 |
20 | 39 |
21 def FindInstalledWindowsApplication(app_path): | 40 def FindInstalledWindowsApplication(app_path): |
22 search_paths = [os.getenv('PROGRAMFILES(X86)'), | 41 search_paths = [os.getenv('PROGRAMFILES(X86)'), |
23 os.getenv('PROGRAMFILES'), | 42 os.getenv('PROGRAMFILES'), |
24 os.getenv('LOCALAPPDATA')] | 43 os.getenv('LOCALAPPDATA')] |
25 search_paths += os.getenv('PATH', '').split(os.pathsep) | 44 search_paths += os.getenv('PATH', '').split(os.pathsep) |
26 | 45 |
27 for search_path in search_paths: | 46 for search_path in search_paths: |
28 if not search_path: | 47 if not search_path: |
29 continue | 48 continue |
30 path = os.path.join(search_path, app_path) | 49 path = os.path.join(search_path, app_path) |
31 if os.path.isfile(path): | 50 if os.path.isfile(path): |
32 return path | 51 return path |
33 | 52 |
34 return None | 53 return None |
35 | 54 |
36 | 55 |
37 def GetCdbPath(): | 56 def GetCdbPath(): |
| 57 """Search in some reasonable places to find cdb.exe. Searches x64 before x86 |
| 58 and newer versions before older versions. |
| 59 """ |
38 possible_paths = ( | 60 possible_paths = ( |
39 os.path.join('Windows Kits', '10', 'Debuggers', 'x64'), | 61 os.path.join('Windows Kits', '10', 'Debuggers', 'x64'), |
40 os.path.join('Windows Kits', '10', 'Debuggers', 'x86'), | 62 os.path.join('Windows Kits', '10', 'Debuggers', 'x86'), |
41 os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'), | 63 os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'), |
42 os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'), | 64 os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'), |
43 os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'), | 65 os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'), |
44 os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'), | 66 os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'), |
45 'Debugging Tools For Windows (x64)', | 67 'Debugging Tools For Windows (x64)', |
46 'Debugging Tools For Windows (x86)', | 68 'Debugging Tools For Windows (x86)', |
47 'Debugging Tools For Windows', | 69 'Debugging Tools For Windows',) |
48 os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers', | |
49 'x64'), | |
50 os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers', | |
51 'x86'), | |
52 ) | |
53 for possible_path in possible_paths: | 70 for possible_path in possible_paths: |
54 app_path = os.path.join(possible_path, 'cdb.exe') | 71 app_path = os.path.join(possible_path, 'cdb.exe') |
55 app_path = FindInstalledWindowsApplication(app_path) | 72 app_path = FindInstalledWindowsApplication(app_path) |
56 if app_path: | 73 if app_path: |
57 return app_path | 74 return app_path |
58 return None | 75 return None |
59 | 76 |
60 | 77 |
| 78 def GetDumpFromCrashyProgram(out_dir, pipe_name): |
| 79 """Initialize a crash database, run crashpad_handler, run crashy_program |
| 80 connecting to the crash_handler. Returns the minidump generated by |
| 81 crash_handler for further testing. |
| 82 """ |
| 83 test_database = MakeTempDir() |
| 84 |
| 85 try: |
| 86 if subprocess.call( |
| 87 [os.path.join(out_dir, 'crashpad_database_util.exe'), '--create', |
| 88 '--database=' + test_database]) != 0: |
| 89 print 'could not initialize report database' |
| 90 return None |
| 91 |
| 92 handler = subprocess.Popen([ |
| 93 os.path.join(out_dir, 'crashpad_handler.exe'), |
| 94 '--pipe-name=' + pipe_name, |
| 95 '--database=' + test_database |
| 96 ]) |
| 97 |
| 98 subprocess.call([os.path.join(out_dir, 'crashy_program.exe'), pipe_name]) |
| 99 |
| 100 out = subprocess.check_output([ |
| 101 os.path.join(out_dir, 'crashpad_database_util.exe'), |
| 102 '--database=' + test_database, |
| 103 '--show-completed-reports', |
| 104 '--show-all-report-info', |
| 105 ]) |
| 106 for line in out.splitlines(): |
| 107 if line.strip().startswith('Path:'): |
| 108 return line.partition(':')[2].strip() |
| 109 |
| 110 finally: |
| 111 if handler: |
| 112 handler.kill() |
| 113 |
| 114 |
| 115 class CdbRun(object): |
| 116 """Run cdb.exe passing it a cdb command and capturing the output. |
| 117 `Check()` searches for regex patterns in sequence allowing verification of |
| 118 expected output. |
| 119 """ |
| 120 |
| 121 def __init__(self, cdb_path, dump_path, command): |
| 122 # Run a command line that loads the dump, runs the specified cdb command, |
| 123 # and then quits, and capturing stdout. |
| 124 self.out = subprocess.check_output([ |
| 125 cdb_path, |
| 126 '-z', dump_path, |
| 127 '-c', command + ';q' |
| 128 ]) |
| 129 |
| 130 def Check(self, pattern, message): |
| 131 match_obj = re.search(pattern, self.out) |
| 132 if match_obj: |
| 133 # Matched. Consume up to end of match. |
| 134 self.out = self.out[match_obj.end(0):] |
| 135 print 'ok - %s' % message |
| 136 else: |
| 137 print >>sys.stderr, '-' * 80 |
| 138 print >>sys.stderr, 'FAILED - %s' % message |
| 139 print >>sys.stderr, '-' * 80 |
| 140 print >>sys.stderr, 'did not match:\n %s' % pattern |
| 141 print >>sys.stderr, '-' * 80 |
| 142 print >>sys.stderr, 'remaining output was:\n %s' % self.out |
| 143 print >>sys.stderr, '-' * 80 |
| 144 sys.exit(1) |
| 145 |
| 146 |
| 147 def RunTests(cdb_path, dump_path, pipe_name): |
| 148 """Runs various tests in sequence. Runs a new cdb instance on the dump for |
| 149 each block of tests to reduce the chances that output from one command is |
| 150 confused for output from another. |
| 151 """ |
| 152 out = CdbRun(cdb_path, dump_path, '.ecxr') |
| 153 out.Check('This dump file has an exception of interest stored in it', |
| 154 'captured exception') |
| 155 out.Check( |
| 156 'crashy_program!crashpad::`anonymous namespace\'::SomeCrashyFunction', |
| 157 'exception at correct location') |
| 158 |
| 159 out = CdbRun(cdb_path, dump_path, '!peb') |
| 160 out.Check(r'PEB at', 'found the PEB') |
| 161 out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved') |
| 162 out.Check(r'Base TimeStamp Module', 'module list present') |
| 163 pipe_name_escaped = pipe_name.replace('\\', '\\\\') |
| 164 out.Check(r'CommandLine: *\'.*crashy_program.exe *' + pipe_name_escaped, |
| 165 'some PEB data is correct') |
| 166 out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured') |
| 167 |
| 168 out = CdbRun(cdb_path, dump_path, '!teb') |
| 169 out.Check(r'TEB at', 'found the TEB') |
| 170 out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data') |
| 171 out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue') |
| 172 |
| 173 out = CdbRun(cdb_path, dump_path, '!gle') |
| 174 out.Check('LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the ' |
| 175 'file specified.', '!gle gets last error') |
| 176 out.Check('LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found} The ' |
| 177 'file %hs does not exist.', '!gle gets last ntstatus') |
| 178 |
| 179 # Locks. |
| 180 out = CdbRun(cdb_path, dump_path, '!locks') |
| 181 out.Check(r'CritSec crashy_program!crashpad::`anonymous namespace\'::' |
| 182 r'g_test_critical_section', 'lock was captured') |
| 183 out.Check(r'\*\*\* Locked', 'lock debug info was captured, and is locked') |
| 184 |
| 185 |
61 def main(args): | 186 def main(args): |
62 cdb_path = GetCdbPath() | 187 try: |
63 print 'cdb_path:', cdb_path | 188 if len(args) != 1: |
64 return 0 | 189 print >>sys.stderr, 'must supply out dir' |
| 190 return 1 |
| 191 |
| 192 cdb_path = GetCdbPath() |
| 193 if not cdb_path: |
| 194 print >>sys.stderr, 'could not find cdb' |
| 195 return 1 |
| 196 |
| 197 pipe_name = r'\\.\pipe\end-to-end_%s_%s' % ( |
| 198 os.getpid(), str(random.getrandbits(64))) |
| 199 |
| 200 dump_path = GetDumpFromCrashyProgram(args[0], pipe_name) |
| 201 if not dump_path: |
| 202 return 1 |
| 203 |
| 204 RunTests(cdb_path, dump_path, pipe_name) |
| 205 |
| 206 return 0 |
| 207 finally: |
| 208 CleanUpTempDirs() |
65 | 209 |
66 | 210 |
67 if __name__ == '__main__': | 211 if __name__ == '__main__': |
68 sys.exit(main(sys.argv[1:])) | 212 sys.exit(main(sys.argv[1:])) |
OLD | NEW |