OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Takes the same arguments as Windows link.exe, and a definition of libraries | 5 """Takes the same arguments as Windows link.exe, and a definition of libraries |
6 to split into subcomponents. Does multiple passes of link.exe invocation to | 6 to split into subcomponents. Does multiple passes of link.exe invocation to |
7 determine exports between parts and generates .def and import libraries to | 7 determine exports between parts and generates .def and import libraries to |
8 cause symbols to be available to other parts.""" | 8 cause symbols to be available to other parts.""" |
9 | 9 |
10 import _winreg | 10 import _winreg |
11 import ctypes | 11 import ctypes |
12 import os | 12 import os |
13 import re | 13 import re |
14 import subprocess | 14 import subprocess |
15 import sys | 15 import sys |
| 16 import tempfile |
16 | 17 |
17 | 18 |
18 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 19 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
19 | 20 |
20 | 21 |
21 def Log(message): | 22 def Log(message): |
22 print 'split_link:', message | 23 print 'split_link:', message |
23 | 24 |
24 | 25 |
25 def GetFlagsAndInputs(argv): | 26 def GetFlagsAndInputs(argv): |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
172 print >> f, '/OUT:' + output_name | 173 print >> f, '/OUT:' + output_name |
173 print >> f, '/MANIFESTFILE:' + manifest_name | 174 print >> f, '/MANIFESTFILE:' + manifest_name |
174 print >> f, '/PDB:' + PdbNameForIndex(index) | 175 print >> f, '/PDB:' + PdbNameForIndex(index) |
175 # Log('[[[\n' + open(rspfile).read() + '\n]]]') | 176 # Log('[[[\n' + open(rspfile).read() + '\n]]]') |
176 link_exe = GetOriginalLinkerPath() | 177 link_exe = GetOriginalLinkerPath() |
177 popen = subprocess.Popen([link_exe, '@' + rspfile], stdout=subprocess.PIPE) | 178 popen = subprocess.Popen([link_exe, '@' + rspfile], stdout=subprocess.PIPE) |
178 stdout, _ = popen.communicate() | 179 stdout, _ = popen.communicate() |
179 return stdout, popen.returncode, output_name | 180 return stdout, popen.returncode, output_name |
180 | 181 |
181 | 182 |
| 183 def GetLibObjList(lib): |
| 184 """Gets the list of object files contained in a .lib.""" |
| 185 link_exe = GetOriginalLinkerPath() |
| 186 popen = subprocess.Popen( |
| 187 [link_exe, '/lib', '/nologo', '/list', lib], stdout=subprocess.PIPE) |
| 188 stdout, _ = popen.communicate() |
| 189 return stdout.splitlines() |
| 190 |
| 191 |
| 192 def ExtractObjFromLib(lib, obj): |
| 193 """Extracts a .obj file contained in a .lib file. Returns the absolute path |
| 194 a temp file.""" |
| 195 link_exe = GetOriginalLinkerPath() |
| 196 temp = tempfile.NamedTemporaryFile( |
| 197 prefix='split_link_', suffix='.obj', delete=False) |
| 198 temp.close() |
| 199 subprocess.check_call([ |
| 200 link_exe, '/lib', '/nologo', '/extract:' + obj, lib, '/out:' + temp.name]) |
| 201 return temp.name |
| 202 |
| 203 |
| 204 def Unmangle(export): |
| 205 "Returns the human-presentable name of a mangled symbol.""" |
| 206 # Use dbghelp.dll to demangle the name. |
| 207 # TODO(scottmg): Perhaps a simple cache? Seems pretty fast though. |
| 208 UnDecorateSymbolName = ctypes.windll.dbghelp.UnDecorateSymbolName |
| 209 buffer_size = 2048 |
| 210 output_string = ctypes.create_string_buffer(buffer_size) |
| 211 if not UnDecorateSymbolName( |
| 212 export, ctypes.byref(output_string), buffer_size, 0): |
| 213 raise ctypes.WinError() |
| 214 return output_string.value |
| 215 |
| 216 |
| 217 def IsDataDefinition(export): |
| 218 """Determines if a given name is data rather than a function. Always returns |
| 219 False for C-style (as opposed to C++-style names).""" |
| 220 if export[0] != '?': |
| 221 return False |
| 222 |
| 223 # If it contains a '(' we assume it's a function. |
| 224 return '(' not in Unmangle(export) |
| 225 |
| 226 |
182 def GenerateDefFiles(unresolved_by_part): | 227 def GenerateDefFiles(unresolved_by_part): |
183 """Given a list of unresolved externals, generates a .def file that will | 228 """Given a list of unresolved externals, generates a .def file that will |
184 cause all those symbols to be exported.""" | 229 cause all those symbols to be exported.""" |
185 deffiles = [] | 230 deffiles = [] |
186 Log('generating .def files') | 231 Log('generating .def files') |
187 for i, part in enumerate(unresolved_by_part): | 232 for i, part in enumerate(unresolved_by_part): |
188 deffile = 'part%d.def' % i | 233 deffile = 'part%d.def' % i |
189 with open(deffile, 'w') as f: | 234 with open(deffile, 'w') as f: |
190 print >> f, 'LIBRARY %s' % OutputNameForIndex(i) | 235 print >> f, 'LIBRARY %s' % OutputNameForIndex(i) |
191 print >> f, 'EXPORTS' | 236 print >> f, 'EXPORTS' |
192 for j, part in enumerate(unresolved_by_part): | 237 for j, part in enumerate(unresolved_by_part): |
193 if i == j: | 238 if i == j: |
194 continue | 239 continue |
195 print >> f, '\n'.join(' ' + export for export in part) | 240 is_data = [' DATA' if IsDataDefinition(export) else '' |
| 241 for export in part] |
| 242 print >> f, '\n'.join(' ' + export + data |
| 243 for export, data in zip(part, is_data)) |
196 deffiles.append(deffile) | 244 deffiles.append(deffile) |
197 return deffiles | 245 return deffiles |
198 | 246 |
199 | 247 |
200 def BuildImportLibs(flags, inputs_by_part, deffiles): | 248 def BuildImportLibs(flags, inputs_by_part, deffiles): |
201 """Runs the linker to generate an import library.""" | 249 """Runs the linker to generate an import library.""" |
202 import_libs = [] | 250 import_libs = [] |
203 Log('building import libs') | 251 Log('building import libs') |
204 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)): | 252 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)): |
205 libfile = 'part%d.lib' % i | 253 libfile = 'part%d.lib' % i |
(...skipping 25 matching lines...) Expand all Loading... |
231 all_succeeded = False | 279 all_succeeded = False |
232 new_externals.append(ParseOutExternals(stdout)) | 280 new_externals.append(ParseOutExternals(stdout)) |
233 else: | 281 else: |
234 new_externals.append([]) | 282 new_externals.append([]) |
235 dlls.append(output) | 283 dlls.append(output) |
236 combined_externals = [sorted(set(prev) | set(new)) | 284 combined_externals = [sorted(set(prev) | set(new)) |
237 for prev, new in zip(unresolved_by_part, new_externals)] | 285 for prev, new in zip(unresolved_by_part, new_externals)] |
238 return all_succeeded, dlls, combined_externals | 286 return all_succeeded, dlls, combined_externals |
239 | 287 |
240 | 288 |
| 289 def ExtractSubObjsTargetedAtAll( |
| 290 inputs, |
| 291 num_parts, |
| 292 description_parts, |
| 293 description_all, |
| 294 description_all_from_libs): |
| 295 """For (lib, obj) tuples in the all_from_libs section, extract the obj out of |
| 296 the lib and added it to inputs. Returns a list of lists for which part the |
| 297 extracted obj belongs in (which is whichever the .lib isn't in).""" |
| 298 by_parts = [[] for _ in range(num_parts)] |
| 299 for lib_spec, obj_spec in description_all_from_libs: |
| 300 for input_file in inputs: |
| 301 if re.search(lib_spec, input_file): |
| 302 objs = GetLibObjList(input_file) |
| 303 match_count = 0 |
| 304 for obj in objs: |
| 305 if re.search(obj_spec, obj, re.I): |
| 306 extracted_obj = ExtractObjFromLib(input_file, obj) |
| 307 #Log('extracted %s (%s %s)' % (extracted_obj, input_file, obj)) |
| 308 i = PartFor(input_file, description_parts, description_all) |
| 309 if i == -1: |
| 310 raise SystemExit( |
| 311 '%s is already in all parts, but matched ' |
| 312 '%s in all_from_libs' % (input_file, obj)) |
| 313 # See note in main(). |
| 314 assert num_parts == 2, "Can't handle > 2 dlls currently" |
| 315 by_parts[1 - i].append(obj) |
| 316 match_count += 1 |
| 317 if match_count == 0: |
| 318 raise SystemExit( |
| 319 '%s, %s matched a lib, but no objs' % (lib_spec, obj_spec)) |
| 320 return by_parts |
| 321 |
| 322 |
241 def main(): | 323 def main(): |
242 flags, inputs = GetFlagsAndInputs(sys.argv[1:]) | 324 flags, inputs = GetFlagsAndInputs(sys.argv[1:]) |
243 partition_file = os.path.normpath( | 325 partition_file = os.path.normpath( |
244 os.path.join(BASE_DIR, '../../../build/split_link_partition.py')) | 326 os.path.join(BASE_DIR, '../../../build/split_link_partition.py')) |
245 with open(partition_file) as partition: | 327 with open(partition_file) as partition: |
246 description = eval(partition.read()) | 328 description = eval(partition.read()) |
247 inputs_by_part = [] | 329 inputs_by_part = [] |
248 description_parts = description['parts'] | 330 description_parts = description['parts'] |
249 # We currently assume that if a symbols isn't in dll 0, then it's in dll 1 | 331 # We currently assume that if a symbol isn't in dll 0, then it's in dll 1 |
250 # when generating def files. Otherwise, we'd need to do more complex things | 332 # when generating def files. Otherwise, we'd need to do more complex things |
251 # to figure out where each symbol actually is to assign it to the correct | 333 # to figure out where each symbol actually is to assign it to the correct |
252 # .def file. | 334 # .def file. |
253 num_parts = len(description_parts) | 335 num_parts = len(description_parts) |
254 assert num_parts == 2, "Can't handle > 2 dlls currently" | 336 assert num_parts == 2, "Can't handle > 2 dlls currently" |
255 description_parts.reverse() | 337 description_parts.reverse() |
| 338 objs_from_libs = ExtractSubObjsTargetedAtAll( |
| 339 inputs, |
| 340 num_parts, |
| 341 description_parts, |
| 342 description['all'], |
| 343 description['all_from_libs']) |
| 344 objs_from_libs.reverse() |
256 inputs_by_part = [[] for _ in range(num_parts)] | 345 inputs_by_part = [[] for _ in range(num_parts)] |
257 for input_file in inputs: | 346 for input_file in inputs: |
258 i = PartFor(input_file, description_parts, description['all']) | 347 i = PartFor(input_file, description_parts, description['all']) |
259 if i == -1: | 348 if i == -1: |
260 for part in inputs_by_part: | 349 for part in inputs_by_part: |
261 part.append(input_file) | 350 part.append(input_file) |
262 else: | 351 else: |
263 inputs_by_part[i].append(input_file) | 352 inputs_by_part[i].append(input_file) |
264 inputs_by_part.reverse() | 353 inputs_by_part.reverse() |
265 | 354 |
| 355 # Put the subobjs on to the main list. |
| 356 for i, part in enumerate(objs_from_libs): |
| 357 Log('%d sub .objs added to part %d' % (len(part), i)) |
| 358 inputs_by_part[i].extend(part) |
| 359 |
266 unresolved_by_part = [[] for _ in range(num_parts)] | 360 unresolved_by_part = [[] for _ in range(num_parts)] |
267 import_libs = [None] * num_parts | 361 import_libs = [None] * num_parts |
268 deffiles = [None] * num_parts | 362 deffiles = [None] * num_parts |
269 | 363 |
| 364 data_exports = 0 |
270 for i in range(5): | 365 for i in range(5): |
271 Log('--- starting pass %d' % i) | 366 Log('--- starting pass %d' % i) |
272 ok, dlls, unresolved_by_part = AttemptLink( | 367 ok, dlls, unresolved_by_part = AttemptLink( |
273 flags, inputs_by_part, unresolved_by_part, deffiles, import_libs) | 368 flags, inputs_by_part, unresolved_by_part, deffiles, import_libs) |
274 if ok: | 369 if ok: |
275 break | 370 break |
| 371 data_exports = 0 |
| 372 for i, part in enumerate(unresolved_by_part): |
| 373 for export in part: |
| 374 if IsDataDefinition(export): |
| 375 print 'part %d contains data export: %s (aka %s)' % ( |
| 376 i, Unmangle(export), export) |
| 377 data_exports += 1 |
276 deffiles = GenerateDefFiles(unresolved_by_part) | 378 deffiles = GenerateDefFiles(unresolved_by_part) |
277 import_libs = BuildImportLibs(flags, inputs_by_part, deffiles) | 379 import_libs = BuildImportLibs(flags, inputs_by_part, deffiles) |
278 else: | 380 else: |
279 return 1 | 381 return 1 |
280 | 382 |
| 383 if data_exports: |
| 384 print 'Data exports found, see report above.' |
| 385 print('These cannot be exported, and must be either duplicated to the ' |
| 386 'target DLL, or wrapped in a function.') |
| 387 return 1 |
| 388 |
281 mt_exe = GetMtPath() | 389 mt_exe = GetMtPath() |
282 for i, dll in enumerate(dlls): | 390 for i, dll in enumerate(dlls): |
283 Log('embedding manifest in %s' % dll) | 391 Log('embedding manifest in %s' % dll) |
284 args = [mt_exe, '-nologo', '-manifest'] | 392 args = [mt_exe, '-nologo', '-manifest'] |
285 args.append(ManifestNameForIndex(i)) | 393 args.append(ManifestNameForIndex(i)) |
286 args.append(description['manifest']) | 394 args.append(description['manifest']) |
287 args.append('-outputresource:%s;2' % dll) | 395 args.append('-outputresource:%s;2' % dll) |
288 subprocess.check_call(args) | 396 subprocess.check_call(args) |
289 | 397 |
290 Log('built %r' % dlls) | 398 Log('built %r' % dlls) |
291 | 399 |
292 return 0 | 400 return 0 |
293 | 401 |
294 | 402 |
295 if __name__ == '__main__': | 403 if __name__ == '__main__': |
296 sys.exit(main()) | 404 sys.exit(main()) |
OLD | NEW |