 Chromium Code Reviews
 Chromium Code Reviews Issue 1397833004:
  win: Test some basic ! windbg commands  (Closed) 
  Base URL: https://chromium.googlesource.com/crashpad/crashpad@add-pexpect
    
  
    Issue 1397833004:
  win: Test some basic ! windbg commands  (Closed) 
  Base URL: https://chromium.googlesource.com/crashpad/crashpad@add-pexpect| 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 |