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 re | |
19 import subprocess | |
18 import sys | 20 import sys |
21 import tempfile | |
22 | |
23 g_temp_dirs = [] | |
24 | |
25 | |
26 def MakeTempDir(): | |
27 global g_temp_dirs | |
28 new_dir = tempfile.mkdtemp() | |
29 g_temp_dirs.append(new_dir) | |
30 return new_dir | |
31 | |
32 | |
33 def CleanUpTempDirs(): | |
34 global g_temp_dirs | |
35 for d in g_temp_dirs: | |
36 subprocess.call(['rmdir', '/s', '/q', d], shell=True) | |
19 | 37 |
20 | 38 |
21 def FindInstalledWindowsApplication(app_path): | 39 def FindInstalledWindowsApplication(app_path): |
22 search_paths = [os.getenv('PROGRAMFILES(X86)'), | 40 search_paths = [os.getenv('PROGRAMFILES(X86)'), os.getenv('PROGRAMFILES'), |
Mark Mentovai
2015/10/09 18:34:04
I liked this better before, one element per line.
scottmg
2015/10/09 18:49:28
Yeah, I started using https://github.com/google/ya
| |
23 os.getenv('PROGRAMFILES'), | |
24 os.getenv('LOCALAPPDATA')] | 41 os.getenv('LOCALAPPDATA')] |
25 search_paths += os.getenv('PATH', '').split(os.pathsep) | 42 search_paths += os.getenv('PATH', '').split(os.pathsep) |
26 | 43 |
27 for search_path in search_paths: | 44 for search_path in search_paths: |
28 if not search_path: | 45 if not search_path: |
29 continue | 46 continue |
30 path = os.path.join(search_path, app_path) | 47 path = os.path.join(search_path, app_path) |
31 if os.path.isfile(path): | 48 if os.path.isfile(path): |
32 return path | 49 return path |
33 | 50 |
34 return None | 51 return None |
35 | 52 |
36 | 53 |
37 def GetCdbPath(): | 54 def GetCdbPath(): |
55 """Search in some reasonable places to find cdb.exe. Searches x64 before x86 | |
56 and newer versions before older versions. | |
57 """ | |
38 possible_paths = ( | 58 possible_paths = ( |
39 os.path.join('Windows Kits', '10', 'Debuggers', 'x64'), | 59 os.path.join('Windows Kits', '10', 'Debuggers', 'x64'), |
40 os.path.join('Windows Kits', '10', 'Debuggers', 'x86'), | 60 os.path.join('Windows Kits', '10', 'Debuggers', 'x86'), |
41 os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'), | 61 os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'), |
42 os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'), | 62 os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'), |
43 os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'), | 63 os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'), |
44 os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'), | 64 os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'), |
45 'Debugging Tools For Windows (x64)', | 65 'Debugging Tools For Windows (x64)', 'Debugging Tools For Windows (x86)', |
46 'Debugging Tools For Windows (x86)', | 66 'Debugging Tools For Windows',) |
47 '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: | 67 for possible_path in possible_paths: |
54 app_path = os.path.join(possible_path, 'cdb.exe') | 68 app_path = os.path.join(possible_path, 'cdb.exe') |
55 app_path = FindInstalledWindowsApplication(app_path) | 69 app_path = FindInstalledWindowsApplication(app_path) |
56 if app_path: | 70 if app_path: |
57 return app_path | 71 return app_path |
58 return None | 72 return None |
59 | 73 |
60 | 74 |
75 def GetDumpFromCrashyProgram(out_dir): | |
76 """Initialize a crash database, run crashpad_handler, run crashy_program | |
77 connecting to the crash_handler. Returns the minidump generated by | |
78 crash_handler for further testing. | |
79 """ | |
80 pipe_name = r'\\.\pipe\end-to-end' | |
Mark Mentovai
2015/10/09 18:34:05
Should this be a little more random? pid + 64 rand
scottmg
2015/10/09 18:49:28
Done.
| |
81 | |
82 test_database = MakeTempDir() | |
83 | |
84 try: | |
85 if subprocess.call( | |
86 [os.path.join(out_dir, 'crashpad_database_util.exe'), '--create', | |
87 '--database=' + test_database]) != 0: | |
88 print 'could not initialize report database' | |
89 return None | |
90 | |
91 handler = subprocess.Popen([ | |
92 os.path.join(out_dir, 'crashpad_handler.exe'), '--pipe-name=' + | |
93 pipe_name, '--database=' + test_database | |
Mark Mentovai
2015/10/09 18:34:05
'pipe_name=' and pipe_name should at least be on t
scottmg
2015/10/09 18:49:28
Done.
| |
94 ]) | |
95 | |
96 subprocess.call([os.path.join(out_dir, 'crashy_program.exe'), pipe_name]) | |
97 | |
98 out = subprocess.check_output([ | |
99 os.path.join(out_dir, 'crashpad_database_util.exe'), | |
100 '--database=' + test_database, | |
101 '--show-completed-reports', | |
102 '--show-all-report-info', | |
103 ]) | |
104 for line in out.splitlines(): | |
105 if line.strip().startswith('Path:'): | |
106 return line.partition(':')[2].strip() | |
107 | |
108 finally: | |
109 if handler: | |
110 handler.kill() | |
111 | |
112 | |
113 class CdbRun(object): | |
114 """Run cdb.exe passing it the given commands and capturing the output. | |
115 `Check()` searchs for regex patterns in sequence allowing verification of | |
Mark Mentovai
2015/10/09 18:34:05
searches
scottmg
2015/10/09 18:49:28
Done.
| |
116 expected output. | |
117 """ | |
118 def __init__(self, cdb_path, dump_path, *commands): | |
119 # Run a command line that loads the dump, runs the specified commands, and | |
120 # then quits, and capture the stdout. | |
121 self.out = subprocess.check_output([cdb_path, '-z', dump_path, '-c', | |
122 ';'.join(commands) + ';q']) | |
Mark Mentovai
2015/10/09 18:34:05
Dunno about how cdb command lines should be escape
Mark Mentovai
2015/10/09 18:34:05
'-c' next to this, since this whole thing applies
scottmg
2015/10/09 18:49:28
Switched to just 'command'.
scottmg
2015/10/09 18:49:28
Done.
| |
123 | |
124 def Check(self, pattern, message): | |
125 print ' ... ' + message, | |
126 sys.stdout.flush() | |
127 match_obj = re.search(pattern, self.out) | |
128 if match_obj: | |
129 # Matched. Consume up to end of match. | |
130 self.out = self.out[match_obj.end(0):] | |
131 print '\rok - ' | |
Mark Mentovai
2015/10/09 18:34:04
'\rok - '
Not really sure what you’re going for h
scottmg
2015/10/09 18:49:28
I wanted the "ok"s before the names so they're eas
| |
132 sys.stdout.flush() | |
133 else: | |
134 print | |
135 print '-'*80 | |
Mark Mentovai
2015/10/09 18:34:05
Errors to stderr?
scottmg
2015/10/09 18:49:28
Done.
| |
136 print 'did not match:\n %s' % pattern | |
137 print '-'*80 | |
138 print 'remaining output was:\n %s' % self.out | |
139 print '-'*80 | |
140 sys.exit(1) | |
141 | |
142 | |
143 def RunTests(cdb_path, dump_path): | |
144 """Runs various tests in sequence. Runs a new cdb instance on the dump for | |
145 each block of tests to reduce the chances that output from one command is | |
146 confused for output from another. | |
147 """ | |
148 out = CdbRun(cdb_path, dump_path, '.ecxr') | |
149 out.Check('This dump file has an exception of interest stored in it', | |
150 'captured exception') | |
151 out.Check( | |
152 'crashy_program!crashpad::`anonymous namespace\'::SomeCrashyFunction', | |
153 'exception at correct location') | |
154 | |
155 out = CdbRun(cdb_path, dump_path, '!peb') | |
156 out.Check(r'PEB at', 'found the PEB') | |
157 out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved') | |
158 out.Check(r'Base TimeStamp Module', 'module list present') | |
159 out.Check(r'CommandLine: *\'.*crashy_program.exe *\\\\.\\pipe\\end-to-end', | |
160 'some PEB data is correct') | |
161 out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured') | |
162 | |
163 out = CdbRun(cdb_path, dump_path, '!teb') | |
164 out.Check(r'TEB at', 'found the TEB') | |
165 out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data') | |
166 out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue') | |
167 | |
168 out = CdbRun(cdb_path, dump_path, '!gle') | |
169 out.Check('LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the ' | |
170 'file specified.', '!gle gets last error') | |
171 out.Check('LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found} The ' | |
172 'file %hs does not exist.', '!gle gets last ntstatus') | |
173 | |
174 # Locks. | |
175 out = CdbRun(cdb_path, dump_path, '!locks') | |
176 out.Check(r'CritSec crashy_program!crashpad::`anonymous namespace\'::' | |
177 r'g_test_critical_section', 'lock was captured') | |
178 out.Check(r'\*\*\* Locked', 'lock debug info was captured, and is locked') | |
179 | |
180 | |
61 def main(args): | 181 def main(args): |
62 cdb_path = GetCdbPath() | 182 try: |
63 print 'cdb_path:', cdb_path | 183 if len(args) != 1: |
64 return 0 | 184 print 'must supply out dir' |
Mark Mentovai
2015/10/09 18:34:05
stderr for these
scottmg
2015/10/09 18:49:28
Done.
| |
185 return 1 | |
186 | |
187 cdb_path = GetCdbPath() | |
188 if not cdb_path: | |
189 print 'could not find cdb' | |
190 return 1 | |
191 | |
192 dump_path = GetDumpFromCrashyProgram(args[0]) | |
193 if not dump_path: | |
194 return 1 | |
195 | |
196 RunTests(cdb_path, dump_path) | |
197 | |
198 return 0 | |
199 finally: | |
200 CleanUpTempDirs() | |
65 | 201 |
66 | 202 |
67 if __name__ == '__main__': | 203 if __name__ == '__main__': |
68 sys.exit(main(sys.argv[1:])) | 204 sys.exit(main(sys.argv[1:])) |
OLD | NEW |