Index: tools/win/split_link/split_link.py |
diff --git a/tools/win/split_link/split_link.py b/tools/win/split_link/split_link.py |
index 2b035c5418b2a70389ee903fb30820d3a1c4d469..72b6c47290d77c88f004f0db687a4f28229b9e96 100644 |
--- a/tools/win/split_link/split_link.py |
+++ b/tools/win/split_link/split_link.py |
@@ -13,6 +13,7 @@ import os |
import re |
import subprocess |
import sys |
+import tempfile |
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
@@ -179,6 +180,50 @@ def RunLinker(flags, index, inputs, phase): |
return stdout, popen.returncode, output_name |
+def GetLibObjList(lib): |
+ """Gets the list of object files contained in a .lib.""" |
+ link_exe = GetOriginalLinkerPath() |
+ popen = subprocess.Popen( |
+ [link_exe, '/lib', '/nologo', '/list', lib], stdout=subprocess.PIPE) |
+ stdout, _ = popen.communicate() |
+ return stdout.splitlines() |
+ |
+ |
+def ExtractObjFromLib(lib, obj): |
+ """Extracts a .obj file contained in a .lib file. Returns the absolute path |
+ a temp file.""" |
+ link_exe = GetOriginalLinkerPath() |
+ temp = tempfile.NamedTemporaryFile( |
+ prefix='split_link_', suffix='.obj', delete=False) |
+ temp.close() |
+ subprocess.check_call([ |
+ link_exe, '/lib', '/nologo', '/extract:' + obj, lib, '/out:' + temp.name]) |
+ return temp.name |
+ |
+ |
+def Unmangle(export): |
+ "Returns the human-presentable name of a mangled symbol.""" |
+ # Use dbghelp.dll to demangle the name. |
+ # TODO(scottmg): Perhaps a simple cache? Seems pretty fast though. |
+ UnDecorateSymbolName = ctypes.windll.dbghelp.UnDecorateSymbolName |
+ buffer_size = 2048 |
+ output_string = ctypes.create_string_buffer(buffer_size) |
+ if not UnDecorateSymbolName( |
+ export, ctypes.byref(output_string), buffer_size, 0): |
+ raise ctypes.WinError() |
+ return output_string.value |
+ |
+ |
+def IsDataDefinition(export): |
+ """Determines if a given name is data rather than a function. Always returns |
+ False for C-style (as opposed to C++-style names).""" |
+ if export[0] != '?': |
+ return False |
+ |
+ # If it contains a '(' we assume it's a function. |
+ return '(' not in Unmangle(export) |
+ |
+ |
def GenerateDefFiles(unresolved_by_part): |
"""Given a list of unresolved externals, generates a .def file that will |
cause all those symbols to be exported.""" |
@@ -192,7 +237,10 @@ def GenerateDefFiles(unresolved_by_part): |
for j, part in enumerate(unresolved_by_part): |
if i == j: |
continue |
- print >> f, '\n'.join(' ' + export for export in part) |
+ is_data = [' DATA' if IsDataDefinition(export) else '' |
+ for export in part] |
+ print >> f, '\n'.join(' ' + export + data |
+ for export, data in zip(part, is_data)) |
deffiles.append(deffile) |
return deffiles |
@@ -238,6 +286,40 @@ def AttemptLink(flags, inputs_by_part, unresolved_by_part, deffiles, |
return all_succeeded, dlls, combined_externals |
+def ExtractSubObjsTargetedAtAll( |
+ inputs, |
+ num_parts, |
+ description_parts, |
+ description_all, |
+ description_all_from_libs): |
+ """For (lib, obj) tuples in the all_from_libs section, extract the obj out of |
+ the lib and added it to inputs. Returns a list of lists for which part the |
+ extracted obj belongs in (which is whichever the .lib isn't in).""" |
+ by_parts = [[] for _ in range(num_parts)] |
+ for lib_spec, obj_spec in description_all_from_libs: |
+ for input_file in inputs: |
+ if re.search(lib_spec, input_file): |
+ objs = GetLibObjList(input_file) |
+ match_count = 0 |
+ for obj in objs: |
+ if re.search(obj_spec, obj, re.I): |
+ extracted_obj = ExtractObjFromLib(input_file, obj) |
+ #Log('extracted %s (%s %s)' % (extracted_obj, input_file, obj)) |
+ i = PartFor(input_file, description_parts, description_all) |
+ if i == -1: |
+ raise SystemExit( |
+ '%s is already in all parts, but matched ' |
+ '%s in all_from_libs' % (input_file, obj)) |
+ # See note in main(). |
+ assert num_parts == 2, "Can't handle > 2 dlls currently" |
+ by_parts[1 - i].append(obj) |
+ match_count += 1 |
+ if match_count == 0: |
+ raise SystemExit( |
+ '%s, %s matched a lib, but no objs' % (lib_spec, obj_spec)) |
+ return by_parts |
+ |
+ |
def main(): |
flags, inputs = GetFlagsAndInputs(sys.argv[1:]) |
partition_file = os.path.normpath( |
@@ -246,13 +328,20 @@ def main(): |
description = eval(partition.read()) |
inputs_by_part = [] |
description_parts = description['parts'] |
- # We currently assume that if a symbols isn't in dll 0, then it's in dll 1 |
+ # We currently assume that if a symbol isn't in dll 0, then it's in dll 1 |
# when generating def files. Otherwise, we'd need to do more complex things |
# to figure out where each symbol actually is to assign it to the correct |
# .def file. |
num_parts = len(description_parts) |
assert num_parts == 2, "Can't handle > 2 dlls currently" |
description_parts.reverse() |
+ objs_from_libs = ExtractSubObjsTargetedAtAll( |
+ inputs, |
+ num_parts, |
+ description_parts, |
+ description['all'], |
+ description['all_from_libs']) |
+ objs_from_libs.reverse() |
inputs_by_part = [[] for _ in range(num_parts)] |
for input_file in inputs: |
i = PartFor(input_file, description_parts, description['all']) |
@@ -263,21 +352,40 @@ def main(): |
inputs_by_part[i].append(input_file) |
inputs_by_part.reverse() |
+ # Put the subobjs on to the main list. |
+ for i, part in enumerate(objs_from_libs): |
+ Log('%d sub .objs added to part %d' % (len(part), i)) |
+ inputs_by_part[i].extend(part) |
+ |
unresolved_by_part = [[] for _ in range(num_parts)] |
import_libs = [None] * num_parts |
deffiles = [None] * num_parts |
+ data_exports = 0 |
for i in range(5): |
Log('--- starting pass %d' % i) |
ok, dlls, unresolved_by_part = AttemptLink( |
flags, inputs_by_part, unresolved_by_part, deffiles, import_libs) |
if ok: |
break |
+ data_exports = 0 |
+ for i, part in enumerate(unresolved_by_part): |
+ for export in part: |
+ if IsDataDefinition(export): |
+ print 'part %d contains data export: %s (aka %s)' % ( |
+ i, Unmangle(export), export) |
+ data_exports += 1 |
deffiles = GenerateDefFiles(unresolved_by_part) |
import_libs = BuildImportLibs(flags, inputs_by_part, deffiles) |
else: |
return 1 |
+ if data_exports: |
+ print 'Data exports found, see report above.' |
+ print('These cannot be exported, and must be either duplicated to the ' |
+ 'target DLL, or wrapped in a function.') |
+ return 1 |
+ |
mt_exe = GetMtPath() |
for i, dll in enumerate(dlls): |
Log('embedding manifest in %s' % dll) |