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