OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 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 import multiprocessing | |
7 import optparse | |
8 import os | |
9 import shutil | |
10 import subprocess | |
11 import sys | |
12 | |
13 | |
14 SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) | |
15 BLACKLIST = set(( | |
16 'sql.dll', | |
M-A Ruel
2012/11/21 09:06:32
Keep ordered
iannucci
2012/11/21 18:27:42
Done.
| |
17 'gpu.dll', | |
18 'crnss.dll', | |
19 'icuuc.dll' | |
M-A Ruel
2012/11/21 09:06:32
Comma on all items.
iannucci
2012/11/21 18:27:42
Done.
| |
20 )) | |
21 | |
22 | |
23 ### Multiprocessing functions | |
24 OPTIONS = None | |
25 STOPPED = None | |
26 def _InitializeASANitizer(options, stopped): | |
27 global OPTIONS, STOPPED | |
28 OPTIONS = options | |
29 STOPPED = stopped | |
30 | |
31 | |
32 def _ASANitize(job): | |
33 retval = 0 | |
34 stdout = '' | |
35 pe_image, pdb = job | |
36 | |
37 try: | |
38 if not STOPPED.is_set(): | |
39 out_pe = AddExtensionComponent(pe_image, 'asan') | |
40 out_pdb = AddExtensionComponent(pdb, 'asan') | |
41 | |
42 # Note that instrument.exe requires --foo=bar format (including the '=') | |
43 command = [OPTIONS.instrument_exe, '--mode=ASAN', | |
44 '--input-image=%s' % pe_image, | |
45 '--output-image=%s' % out_pe, | |
46 '--output-pdb=%s' % out_pdb, | |
47 '2>&1' # Combine stderr+stdout so that they're in order | |
48 ] | |
49 | |
50 for fname in filter(os.path.exists, (out_pe, out_pdb)): | |
51 os.remove(fname) | |
52 | |
53 proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) | |
M-A Ruel
2012/11/21 09:06:32
Creating a process just to start a process is a ta
iannucci
2012/11/21 18:27:42
Yeah I started down that path, but I felt that mul
| |
54 stdout, _ = proc.communicate() | |
55 retval = proc.returncode | |
56 | |
57 return (retval, stdout, pe_image) | |
58 except Exception: | |
59 import traceback | |
60 return (1, stdout+'\n'+traceback.format_exc(), pe_image) | |
61 | |
62 | |
63 ### Normal functions | |
64 | |
65 def AddExtensionComponent(path, new_ext): | |
66 """ | |
67 Prepends new_ext to the existing extension | |
68 >>> ChangeExtension('hello.foo.dll', 'asan') | |
69 'hello.asan.foo.dll' | |
70 """ | |
71 # Don't use os.path.splitext, because it will split on the rightmost dot | |
72 # instead of the leftmost dot. | |
73 base, ext = path.split('.', 1) | |
74 return base+'.'+new_ext+'.'+ext | |
75 | |
76 | |
77 def UpdateAsanRuntime(options): | |
78 """Updates the ASAN runtime dll in the build directory, if it exists.""" | |
79 runtime = os.path.join(options.full_directory, | |
80 os.path.basename(options.runtime_path)) | |
81 | |
82 if os.path.exists(runtime): | |
83 print('Removing', runtime) | |
84 os.remove(runtime) | |
85 | |
86 print 'Copying %s -> %s' % (options.runtime_path, runtime) | |
87 shutil.copy2(options.runtime_path, runtime) | |
88 | |
89 fname = os.path.basename(options.runtime_path) | |
90 print 'Blacklisting %s' % fname | |
91 BLACKLIST.add(fname) | |
92 | |
93 | |
94 def GetCompatiblePDB(pe_image): | |
95 """Returns <path to pdb> or None (if no good pdb exists).""" | |
96 # TODO(iannucci): Use PE header to look up pdb name. | |
97 # for now, assume that the pdb is always just PE.pdb | |
98 pdb_path = pe_image+'.pdb' | |
99 return pdb_path if os.path.exists(pdb_path) else None | |
100 | |
101 | |
102 def FindFilesToAsan(directory): | |
103 """Finds eligible PE images in given directory. A PE image is eligible if it | |
104 has a corresponding pdb and doesn't already have ASAN applied to it. Skips | |
105 files which have an extra extension (like foo.orig.exe).""" | |
106 ret = [] | |
107 | |
108 def GoodExeOrDll(fname): | |
109 return ( | |
110 '.' in fname and | |
111 fname not in BLACKLIST and | |
112 fname.split('.', 1)[-1] in ('exe', 'dll')) | |
113 | |
114 for root, _, files in os.walk(directory): | |
115 for pe_image in (os.path.join(root, f) for f in files if GoodExeOrDll(f)): | |
116 pdb = GetCompatiblePDB(pe_image) | |
117 if not pdb: | |
118 print >> sys.stderr, 'PDB for "%s" does not exist.' % pe_image | |
119 continue | |
120 | |
121 ret.append((pe_image, pdb)) | |
122 return ret | |
123 | |
124 | |
125 def ApplyAsanToBuild(options): | |
126 """Applies ASAN to all exe's/dll's in the build directory.""" | |
127 to_asan = FindFilesToAsan(options.full_directory) | |
128 | |
129 if not to_asan: | |
130 print >> sys.stderr, 'No files to ASAN!' | |
131 return 1 | |
132 | |
133 stopped = multiprocessing.Event() | |
134 pool = multiprocessing.Pool(options.jobs, initializer=_InitializeASANitizer, | |
135 initargs=(options, stopped)) | |
M-A Ruel
2012/11/21 09:06:32
Is this code I/O bound or CPU bound? I'd guess I/O
iannucci
2012/11/21 18:27:42
That's why jobs is tweakable.
| |
136 | |
137 ret = 0 | |
138 try: | |
139 generator = pool.imap_unordered(_ASANitize, to_asan) | |
140 for retval, stdout, failed_image in generator: | |
141 ostream = (sys.stderr if retval else sys.stdout) | |
142 print >> ostream, stdout | |
143 sys.stdout.flush() | |
144 sys.stderr.flush() | |
145 if retval: | |
146 print 'Failed to ASAN %s. Stopping remaining jobs.' % failed_image | |
147 ret = retval | |
148 stopped.set() | |
149 except KeyboardInterrupt: | |
150 stopped.set() | |
151 pool.close() | |
152 pool.join() | |
153 | |
154 return ret | |
155 | |
156 | |
157 def main(): | |
158 default_asan_dir = os.path.join(os.pardir, | |
159 'third_party', 'syzygy', 'binaries', 'exe') | |
160 default_instrument_exe = os.path.join(default_asan_dir, 'instrument.exe') | |
161 default_runtime_path = os.path.join(default_asan_dir, 'asan_rtl.dll') | |
162 | |
163 parser = optparse.OptionParser() | |
164 parser.add_option('--build_directory', | |
M-A Ruel
2012/11/21 09:06:32
alignment is wrong, either all +4 or not.
iannucci
2012/11/21 18:27:42
Done.
| |
165 help='Path to the build directory to asan (required).') | |
166 parser.add_option('--target', | |
167 help='The target in the build directory to asan (required).') | |
168 parser.add_option('--jobs', type='int', default=multiprocessing.cpu_count(), | |
169 help='Specify the number of sub-tasks to use (%default).') | |
170 parser.add_option('--instrument_exe', default=default_instrument_exe, | |
171 help='Specify the path to the ASAN instrument.exe relative to ' | |
172 'build_directory (%default).') | |
173 parser.add_option('--runtime_path', default=default_runtime_path, | |
174 help='Specify the path to the ASAN runtime DLL relative to ' | |
175 'build_directory (%default).') | |
176 options, args = parser.parse_args() | |
177 | |
178 if options.build_directory is None: | |
M-A Ruel
2012/11/21 09:06:32
if not options.build_directory:
iannucci
2012/11/21 18:27:42
Done.
| |
179 parser.error('Must specify --build_directory') | |
180 | |
181 if options.target is None: | |
182 parser.error('Must specify --target') | |
183 | |
184 options.full_directory = os.path.join(options.build_directory, options.target) | |
185 if not os.path.exists(options.full_directory): | |
186 parser.error('Could not find directory: %s' % options.full_directory) | |
187 | |
188 options.instrument_exe = os.path.abspath( | |
189 os.path.join(options.build_directory, options.instrument_exe)) | |
190 if not os.path.exists(options.instrument_exe): | |
191 parser.error('Could not find instrument_exe: %s' % options.instrument_exe) | |
192 | |
193 options.runtime_path = os.path.abspath( | |
194 os.path.join(options.build_directory, options.runtime_path)) | |
195 if not os.path.exists(options.runtime_path): | |
196 parser.error('Could not find runtime_path: %s' % options.runtime_path) | |
197 | |
198 if args: | |
199 parser.error('Not expecting additional arguments') | |
200 | |
201 print 'Default BLACKLIST is: %r' % BLACKLIST | |
202 | |
203 UpdateAsanRuntime(options) | |
204 return ApplyAsanToBuild(options) | |
205 | |
M-A Ruel
2012/11/21 09:06:32
2 lines
iannucci
2012/11/21 18:27:42
Done.
| |
206 if __name__ == '__main__': | |
207 sys.exit(main()) | |
OLD | NEW |