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

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

Issue 2621363002: Remove Kasko! (Closed)
Patch Set: Rebase Created 3 years, 10 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') | chrome/tools/build/win/FILES.cfg » ('j') | 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 syzyasan=1 win_z7=0 chromium_win_pch=0
20 - build the release Chrome binaries:
21 ninja -C out\Release chrome.exe chromedriver.exe
22 - run the test:
23 python chrome/test/kasko/syzyasan_integration_test.py
24 """
25
26 import logging
27 import os
28 import optparse
29 import re
30 import shutil
31 import subprocess
32 import sys
33
34 # Bring in the Kasko module.
35 KASKO_DIR = os.path.join(os.path.dirname(__file__), 'py')
36 sys.path.append(KASKO_DIR)
37 import kasko
38
39
40 _LOGGER = logging.getLogger(os.path.basename(__file__))
41 _CHROME_DLL = 'chrome.dll'
42 _INSTRUMENT = 'instrument.exe'
43 _SYZYASAN_RTL = 'syzyasan_rtl.dll'
44
45
46 def _ParseCommandLine():
47 self_dir = os.path.dirname(__file__)
48 src_dir = os.path.abspath(os.path.join(self_dir, '..', '..', '..'))
49
50 option_parser = kasko.config.GenerateOptionParser()
51 option_parser.add_option('--instrumented-dir', dest='instrumented_dir',
52 type='string',
53 help='Path where instrumented binaries will be placed. If instrumented '
54 'binaries already exist here they will be reused.')
55 option_parser.add_option('--skip-instrumentation',
56 dest='skip_instrumentation', action='store_true', default=False,
57 help='Skips instrumentation if specified. To be used when testing '
58 'against an already instrumented build of Chrome.')
59 option_parser.add_option('--syzygy-dir', dest='syzygy_dir', type='string',
60 default=os.path.join(src_dir, 'third_party', 'syzygy', 'binaries', 'exe'),
61 help='Path to Syzygy binaries. By default will look in third_party.')
62 options = kasko.config.ParseCommandLine(option_parser)
63
64 if not os.path.isdir(options.syzygy_dir):
65 option_parser.error('Invalid syzygy directory.')
66 for basename in [_INSTRUMENT, _SYZYASAN_RTL]:
67 path = os.path.join(options.syzygy_dir, basename)
68 if not os.path.isfile(path):
69 option_parser.error('Missing syzygy binary: %s' % path)
70
71 _LOGGER.debug('Using syzygy path: %s', options.syzygy_dir)
72
73 return options
74
75
76 def _DecorateFilename(name, deco):
77 """Decorates a filename, transforming 'foo.baz.bar' to 'foo.dec.baz.bar'."""
78 d = os.path.dirname(name)
79 b = os.path.basename(name)
80 b = b.split('.', 1)
81 b.insert(1, deco)
82 return os.path.join(d, '.'.join(b))
83
84
85 def _BackupFile(path, dst_dir):
86 """Creates a backup of a file in the specified directory."""
87 bak = os.path.abspath(os.path.join(dst_dir, os.path.basename(path)))
88 if os.path.exists(bak):
89 os.remove(bak)
90 # Copy the file, with its permissions and timestamps, etc.
91 _LOGGER.debug('Copying "%s" to "%s".' % (path, bak))
92 shutil.copyfile(path, bak)
93 shutil.copystat(path, bak)
94 return bak
95
96
97 def _RestoreFile(path, backup):
98 """Restores a file from its backup. Leaves the backup file."""
99 if not os.path.exists(backup):
100 raise Exception('Backup does not exist: %s' % backup)
101 if os.path.exists(path):
102 os.remove(path)
103 _LOGGER.debug('Restoring "%s" from "%s".' % (path, backup))
104 shutil.copyfile(backup, path)
105 shutil.copystat(backup, path)
106
107
108 class _ScopedInstrumentedChrome(object):
109 """SyzyAsan Instruments a Chrome installation in-place."""
110
111 def __init__(self, chrome_dir, syzygy_dir, temp_dir, instrumented_dir=None,
112 verbose=False, skip_instrumentation=False):
113 self.chrome_dir_ = chrome_dir
114 self.syzygy_dir_ = syzygy_dir
115 self.temp_dir_ = temp_dir
116 self.instrumented_dir_ = instrumented_dir
117 self.verbose_ = verbose
118 self.skip_instrumentation_ = skip_instrumentation
119
120 def _ProduceInstrumentedBinaries(self):
121 # Generate the instrumentation command-line. This will place the
122 # instrumented binaries in the temp directory.
123 instrument = os.path.abspath(os.path.join(self.syzygy_dir_, _INSTRUMENT))
124 cmd = [instrument,
125 '--mode=asan',
126 '--input-image=%s' % self.chrome_dll_bak_,
127 '--input-pdb=%s' % self.chrome_dll_pdb_bak_,
128 '--output-image=%s' % self.chrome_dll_inst_,
129 '--output-pdb=%s' % self.chrome_dll_pdb_inst_,
130 '--no-augment-pdb']
131
132 _LOGGER.debug('Instrumenting Chrome binaries.')
133
134 # If in verbose mode then let the instrumentation produce output directly.
135 if self.verbose_:
136 result = subprocess.call(cmd)
137 else:
138 # Otherwise run the command with all output suppressed.
139 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
140 stderr=subprocess.PIPE)
141 stdout, stderr = proc.communicate()
142 result = proc.returncode
143 if result != 0:
144 sys.stdout.write(stdout)
145 sys.stderr.write(stderr)
146
147 if result != 0:
148 raise Exception('Failed to instrument: %s' % self.chrome_dll_)
149
150 return
151
152 def __enter__(self):
153 """In-place instruments a Chrome installation with SyzyAsan."""
154 # Do nothing if instrumentation is to be skipped entirely.
155 if self.skip_instrumentation_:
156 _LOGGER.debug('Assuming binaries already instrumented.')
157 return self
158
159 # Build paths to the original Chrome binaries.
160 self.chrome_dll_ = os.path.abspath(os.path.join(
161 self.chrome_dir_, _CHROME_DLL))
162 self.chrome_dll_pdb_ = self.chrome_dll_ + '.pdb'
163
164 # Backup the original Chrome binaries to the temp directory.
165 orig_dir = os.path.join(self.temp_dir_, 'orig')
166 os.makedirs(orig_dir)
167 self.chrome_dll_bak_ = _BackupFile(self.chrome_dll_, orig_dir)
168 self.chrome_dll_pdb_bak_ = _BackupFile(self.chrome_dll_pdb_, orig_dir)
169
170 # Generate the path to the instrumented binaries.
171 inst_dir = os.path.join(self.temp_dir_, 'inst')
172 if self.instrumented_dir_:
173 inst_dir = self.instrumented_dir_
174 if not os.path.isdir(inst_dir):
175 os.makedirs(inst_dir)
176 self.chrome_dll_inst_ = os.path.abspath(os.path.join(
177 inst_dir, _DecorateFilename(_CHROME_DLL, 'inst')))
178 self.chrome_dll_pdb_inst_ = os.path.abspath(os.path.join(
179 inst_dir, _DecorateFilename(_CHROME_DLL + '.pdb', 'inst')))
180
181 # Only generate the instrumented binaries if they don't exist.
182 if (os.path.isfile(self.chrome_dll_inst_) and
183 os.path.isfile(self.chrome_dll_pdb_inst_)):
184 _LOGGER.debug('Using existing instrumented binaries.')
185 else:
186 self._ProduceInstrumentedBinaries()
187
188 # Replace the original chrome binaries with the instrumented versions.
189 _RestoreFile(self.chrome_dll_, self.chrome_dll_inst_)
190 _RestoreFile(self.chrome_dll_pdb_, self.chrome_dll_pdb_inst_)
191
192 # Copy the runtime library into the Chrome directory.
193 syzyasan_rtl = os.path.abspath(os.path.join(self.syzygy_dir_,
194 _SYZYASAN_RTL))
195 self.syzyasan_rtl_ = os.path.abspath(os.path.join(self.chrome_dir_,
196 _SYZYASAN_RTL))
197 _RestoreFile(self.syzyasan_rtl_, syzyasan_rtl)
198
199 return self
200
201 def __exit__(self, *args, **kwargs):
202 # Do nothing if instrumentation is to be skipped entirely.
203 if self.skip_instrumentation_:
204 return
205
206 # Remove the RTL and restore the original Chrome binaries.
207 os.remove(self.syzyasan_rtl_)
208 _RestoreFile(self.chrome_dll_, self.chrome_dll_bak_)
209 _RestoreFile(self.chrome_dll_pdb_, self.chrome_dll_pdb_bak_)
210
211
212 def Main():
213 options = _ParseCommandLine()
214
215 # Generate a temporary directory for use in the tests.
216 with kasko.util.ScopedTempDir() as temp_dir:
217 try:
218 # Prevent the temporary directory from self cleaning if requested.
219 if options.keep_temp_dirs:
220 temp_dir_path = temp_dir.release()
221 else:
222 temp_dir_path = temp_dir.path
223
224 # Use the specified user data directory if requested.
225 if options.user_data_dir:
226 user_data_dir = options.user_data_dir
227 else:
228 user_data_dir = os.path.join(temp_dir_path, 'user-data-dir')
229
230 kasko_dir = os.path.join(temp_dir_path, 'kasko')
231 os.makedirs(kasko_dir)
232
233 # Launch the test server.
234 server = kasko.crash_server.CrashServer()
235 with kasko.util.ScopedStartStop(server):
236 _LOGGER.info('Started server on port %d', server.port)
237
238 # Configure the environment so Chrome can find the test crash server.
239 os.environ['KASKO_CRASH_SERVER_URL'] = (
240 'http://127.0.0.1:%d/crash' % server.port)
241
242 # Configure the environment to disable feature randomization, which can
243 # result in Kasko being randomly disabled. Append to any existing
244 # options.
245 k = 'SYZYGY_ASAN_OPTIONS'
246 v = '--disable_feature_randomization'
247 if k in os.environ:
248 os.environ[k] += ' ' + v
249 else:
250 os.environ[k] = v
251
252 # SyzyAsan instrument the Chrome installation.
253 chrome_dir = os.path.dirname(options.chrome)
254 with _ScopedInstrumentedChrome(chrome_dir, options.syzygy_dir,
255 temp_dir_path, instrumented_dir=options.instrumented_dir,
256 verbose=(options.log_level == logging.DEBUG),
257 skip_instrumentation=options.skip_instrumentation) as asan_chrome:
258 # Launch Chrome and navigate it to the test URL.
259 chrome = kasko.process.ChromeInstance(options.chromedriver,
260 options.chrome, user_data_dir)
261 with kasko.util.ScopedStartStop(chrome):
262 _LOGGER.info('Navigating to SyzyAsan debug URL')
263 chrome.navigate_to('chrome://crash/browser-use-after-free')
264
265 _LOGGER.info('Waiting for Kasko report')
266 if not server.wait_for_report(10):
267 raise Exception('No Kasko report received.')
268
269 report = server.crash(0)
270 kasko.report.LogCrashKeys(report)
271 kasko.report.ValidateCrashReport(report, {'asan-error-type': 'SyzyAsan'})
272
273 _LOGGER.info('Test passed successfully!')
274 except Exception as e:
275 _LOGGER.error(e)
276 return 1
277
278 return 0
279
280
281 if __name__ == '__main__':
282 sys.exit(Main())
OLDNEW
« no previous file with comments | « chrome/test/kasko/py/kasko/util.py ('k') | chrome/tools/build/win/FILES.cfg » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698