Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(252)

Side by Side Diff: chrome/test/kasko/syzyasan_integration_test.py

Issue 1582613002: [win] Create a SyzyAsan/Chrome/Kasko/Crashpad integration test. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Slight refactor to use pre-instrumented binaries. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/test/kasko/py/kasko/util.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2016 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """A Windows-only end-to-end integration test for Kasko and SyzyAsan.
7
8 This test ensures that the interface between SyzyAsan, Kasko and Chrome works
9 as expected. The test causes a crash that should be detected by SyzyAsan and
10 delivered via Kasko to a locally hosted test crash server.
11
12 Note that this test only works against non-component Release and Official builds
13 of Chrome with Chrome branding, and attempting to use it with anything else will
14 most likely lead to constant failures.
15
16 Typical usage (assuming in root 'src' directory):
17
18 - generate project files with the following GYP variables:
19 branding=Chrome syzyasan=1 win_z7=0 chromium_win_pch=0
Sébastien Marchand 2016/01/13 15:47:19 buildtype=Official too ? not sure that we need the
chrisha 2016/01/13 18:55:14 Removed that comment.
20 - build the release Chrome binaries:
21 ninja -C out\Release chrome.exe
22 - run the test:
23 python chrome/test/kasko/syzyasan_integration_test.py
24 --chrome-dir=out/Release
25
26 Many of the components in this test could be reused in other end-to-end crash
27 testing. Feel free to open them up for reuse, but please CC chrisha@chromium.org
28 on any associated reviews or bugs!
29 """
30
31 import logging
32 import os
33 import optparse
34 import re
35 import shutil
36 import subprocess
37 import sys
38
39 # Bring in the Kasko module.
40 KASKO_DIR = os.path.join(os.path.dirname(__file__), 'py')
41 sys.path.append(KASKO_DIR)
42 import kasko
43
44
45 _LOGGER = logging.getLogger(os.path.basename(__file__))
46 _CHROME_DLL = 'chrome.dll'
47 _INSTRUMENT = 'instrument.exe'
48 _SYZYASAN_RTL = 'syzyasan_rtl.dll'
49
50
51 def _ParseCommandLine():
52 self_dir = os.path.dirname(__file__)
53 src_dir = os.path.abspath(os.path.join(self_dir, '..', '..', '..'))
54
55 option_parser = kasko.config.GenerateOptionParser()
56 option_parser.add_option('--instrumented-dir', dest='instrumented_dir',
57 type='string',
58 help='Path where instrumented binaries will be placed. If instrumented '
59 'binaries already exist here they will be reused.')
60 option_parser.add_option('--skip-instrumentation',
61 dest='skip_instrumentation', action='store_true', default=False,
62 help='Skips instrumentation if specified. To be used when testing '
63 'against an already instrumented build of Chrome.')
64 option_parser.add_option('--syzygy-dir', dest='syzygy_dir', type='string',
65 default=os.path.join(src_dir, 'third_party', 'syzygy', 'binaries', 'exe'),
66 help='Path to Syzygy binaries. By default will look in third_party.')
67 options = kasko.config.ParseCommandLine(option_parser)
68
69 if not os.path.isdir(options.syzygy_dir):
70 option_parser.error('Invalid syzygy directory.')
71 for basename in [_INSTRUMENT, _SYZYASAN_RTL]:
72 path = os.path.join(options.syzygy_dir, basename)
73 if not os.path.isfile(path):
74 option_parser.error('Missing syzygy binary: %s' % path)
75
76 _LOGGER.debug('Using syzygy path: %s', options.syzygy_dir)
77
78 return options
79
80
81 def _DecorateFilename(name, deco):
82 """Decorates a filename, transforming 'foo.baz.bar' to 'foo.dec.baz.bar'."""
83 d = os.path.dirname(name)
84 b = os.path.basename(name)
85 b = b.split('.', 1)
86 b.insert(1, deco)
87 return os.path.join(d, '.'.join(b))
88
89
90 def _BackupFile(path, dst_dir):
91 """Creates a backup of a file in the specified directory."""
92 bak = os.path.abspath(os.path.join(dst_dir, os.path.basename(path)))
93 if os.path.exists(bak):
94 os.remove(bak)
95 # Copy the file, with its permissions and timestamps, etc.
96 _LOGGER.debug('Copying "%s" to "%s".' % (path, bak))
97 shutil.copyfile(path, bak)
98 shutil.copystat(path, bak)
99 return bak
100
101
102 def _RestoreFile(path, backup):
103 """Restores a file from its backup. Leaves the backup file."""
104 if not os.path.exists(backup):
105 raise Exception('Backup does not exist: %s' % backup)
106 if os.path.exists(path):
107 os.remove(path)
108 _LOGGER.debug('Restoring "%s" from "%s".' % (path, backup))
109 shutil.copyfile(backup, path)
110 shutil.copystat(backup, path)
111
112
113 class _ScopedInstrumentedChrome(object):
114 """SyzyAsan Instruments a Chrome installation in-place."""
115
116 def __init__(self, chrome_dir, syzygy_dir, temp_dir, instrumented_dir=None,
117 verbose=False, skip_instrumentation=False):
118 self.chrome_dir_ = chrome_dir
119 self.syzygy_dir_ = syzygy_dir
120 self.temp_dir_ = temp_dir
121 self.instrumented_dir_ = instrumented_dir
122 self.verbose_ = verbose
123 self.skip_instrumentation_ = skip_instrumentation
124
125 def _ProduceInstrumentedBinaries(self):
126 # Generate the instrumentation command-line. This will place the
127 # instrumented binaries in the temp directory.
128 instrument = os.path.abspath(os.path.join(self.syzygy_dir_, _INSTRUMENT))
129 cmd = [instrument,
130 '--mode=asan',
131 '--input-image=%s' % self.chrome_dll_bak_,
132 '--input-pdb=%s' % self.chrome_dll_pdb_bak_,
133 '--output-image=%s' % self.chrome_dll_inst_,
134 '--output-pdb=%s' % self.chrome_dll_pdb_inst_,
135 '--no-augment-pdb']
136
137 _LOGGER.debug('Instrumenting Chrome binaries.')
138
139 # If in verbose mode then let the instrumentation produce output directly.
140 if self.verbose_:
141 result = subprocess.call(cmd)
142 else:
143 # Otherwise run the command with all output suppressed.
144 proc = subprocess.call(cmd, stdout=subprocess.PIPE,
145 stderr=subprocess.PIPE)
146 stdout, stderr = proc.communicate()
147 result = proc.returncode
148 if result != 0:
149 sys.stdout.write(stdout)
150 sys.stderr.write(stderr)
151
152 if result != 0:
153 raise Exception('Failed to instrument: %s' % chrome_dll)
154
155 return
156
157 def __enter__(self):
158 """In-place instruments a Chrome installation with SyzyAsan."""
159 # Do nothing if instrumentation is to be skipped entirely.
160 if self.skip_instrumentation_:
161 _LOGGER.debug('Assuming binaries already instrumented.')
162 return self
163
164 # Build paths to the original Chrome binaries.
165 self.chrome_dll_ = os.path.abspath(os.path.join(
166 self.chrome_dir_, _CHROME_DLL))
167 self.chrome_dll_pdb_ = self.chrome_dll_ + '.pdb'
168
169 # Backup the original Chrome binaries to the temp directory.
170 orig_dir = os.path.join(self.temp_dir_, 'orig')
171 os.makedirs(orig_dir)
172 self.chrome_dll_bak_ = _BackupFile(self.chrome_dll_, orig_dir)
173 self.chrome_dll_pdb_bak_ = _BackupFile(self.chrome_dll_pdb_, orig_dir)
174
175 # Generate the path to the instrumented binaries.
176 inst_dir = os.path.join(self.temp_dir_, 'inst')
177 if self.instrumented_dir_:
178 inst_dir = self.instrumented_dir_
179 if not os.path.isdir(inst_dir):
180 os.makedirs(inst_dir)
181 self.chrome_dll_inst_ = os.path.abspath(os.path.join(
182 inst_dir, _DecorateFilename(_CHROME_DLL, 'inst')))
183 self.chrome_dll_pdb_inst_ = os.path.abspath(os.path.join(
184 inst_dir, _DecorateFilename(_CHROME_DLL + '.pdb', 'inst')))
185
186 # Only generate the instrumented binaries if they don't exist.
187 if (os.path.isfile(self.chrome_dll_inst_) and
188 os.path.isfile(self.chrome_dll_pdb_inst_)):
189 _LOGGER.debug('Using existing instrumented binaries.')
190 else:
191 self._ProduceInstrumentedBinaries()
192
193 # Replace the original chrome binaries with the instrumented versions.
194 _RestoreFile(self.chrome_dll_, self.chrome_dll_inst_)
195 _RestoreFile(self.chrome_dll_pdb_, self.chrome_dll_pdb_inst_)
196
197 # Copy the runtime library into the Chrome directory.
198 syzyasan_rtl = os.path.abspath(os.path.join(self.syzygy_dir_,
199 _SYZYASAN_RTL))
200 self.syzyasan_rtl_ = os.path.abspath(os.path.join(self.chrome_dir_,
201 _SYZYASAN_RTL))
202 _RestoreFile(self.syzyasan_rtl_, syzyasan_rtl)
203
204 return self
205
206 def __exit__(self, *args, **kwargs):
207 # Do nothing if instrumentation is to be skipped entirely.
208 if self.skip_instrumentation_:
209 return
210
211 # Remove the RTL and restore the original Chrome binaries.
212 os.remove(self.syzyasan_rtl_)
213 _RestoreFile(self.chrome_dll_, self.chrome_dll_bak_)
214 _RestoreFile(self.chrome_dll_pdb_, self.chrome_dll_pdb_bak_)
215
216
217 def Main():
218 options = _ParseCommandLine()
219
220 # Generate a temporary directory for use in the tests.
221 with kasko.util.ScopedTempDir() as temp_dir:
222 # Prevent the temporary directory from self cleaning if requested.
223 if options.keep_temp_dirs:
224 temp_dir_path = temp_dir.release()
225 else:
226 temp_dir_path = temp_dir.path
227
228 # Use the specified user data directory if requested.
229 if options.user_data_dir:
230 user_data_dir = options.user_data_dir
231 else:
232 user_data_dir = os.path.join(temp_dir_path, 'user-data-dir')
233
234 kasko_dir = os.path.join(temp_dir_path, 'kasko')
235 os.makedirs(kasko_dir)
236
237 # Launch the test server.
238 server = kasko.crash_server.CrashServer()
239 with kasko.util.ScopedStartStop(server):
240 _LOGGER.info('Started server on port %d', server.port)
241
242 # Configure the environment so Chrome can find the test crash server.
243 os.environ['KASKO_CRASH_SERVER_URL'] = (
244 'http://127.0.0.1:%d/crash' % server.port)
245
246 # SyzyAsan instrument the Chrome installation.
247 chrome_dir = os.path.dirname(options.chrome)
248 with _ScopedInstrumentedChrome(chrome_dir, options.syzygy_dir,
249 temp_dir_path, instrumented_dir=options.instrumented_dir,
250 verbose=(options.log_level == logging.DEBUG),
251 skip_instrumentation=options.skip_instrumentation) as asan_chrome:
252 # Launch Chrome and navigate it to the test URL.
253 chrome = kasko.process.ChromeInstance(options.chromedriver,
254 options.chrome, user_data_dir)
255 with kasko.util.ScopedStartStop(chrome):
256 _LOGGER.info('Navigating to SyzyAsan debug URL')
257 chrome.navigate_to('chrome://crash/browser-use-after-free')
258
259 _LOGGER.info('Waiting for Kasko report')
260 if not server.wait_for_report(10):
261 raise Exception('No Kasko report received.')
262
263 report = server.crash(0)
264 kasko.report.LogCrashKeys(report)
265 kasko.report.ValidateCrashReport(report, {'asan-error-type': 'SyzyAsan'})
266
267 return 0
268
269
270 if __name__ == '__main__':
271 sys.exit(Main())
OLDNEW
« no previous file with comments | « chrome/test/kasko/py/kasko/util.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698