PageRenderTime 50ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/win/split_link/split_link.py

https://gitlab.com/jonnialva90/iridium-browser
Python | 420 lines | 372 code | 29 blank | 19 comment | 31 complexity | da0e8f492dc32737440660184c0a40fb MD5 | raw file
  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
  3. # found in the LICENSE file.
  4. """Takes the same arguments as Windows link.exe, and a definition of libraries
  5. to split into subcomponents. Does multiple passes of link.exe invocation to
  6. determine exports between parts and generates .def and import libraries to
  7. cause symbols to be available to other parts."""
  8. import _winreg
  9. import ctypes
  10. import os
  11. import re
  12. import shutil
  13. import subprocess
  14. import sys
  15. import tempfile
  16. BASE_DIR = os.path.dirname(os.path.abspath(__file__))
  17. # This can be set to ignore data exports. The resulting DLLs will probably not
  18. # run, but at least they can be generated. The log of data exports will still
  19. # be output.
  20. IGNORE_DATA = 0
  21. def Log(message):
  22. print 'split_link:', message
  23. def GetFlagsAndInputs(argv):
  24. """Parses the command line intended for link.exe and return the flags and
  25. input files."""
  26. rsp_expanded = []
  27. for arg in argv:
  28. if arg[0] == '@':
  29. with open(arg[1:]) as rsp:
  30. rsp_expanded.extend(rsp.read().splitlines())
  31. else:
  32. rsp_expanded.append(arg)
  33. # Use CommandLineToArgvW so we match link.exe parsing.
  34. try:
  35. size = ctypes.c_int()
  36. ptr = ctypes.windll.shell32.CommandLineToArgvW(
  37. ctypes.create_unicode_buffer(' '.join(rsp_expanded)),
  38. ctypes.byref(size))
  39. ref = ctypes.c_wchar_p * size.value
  40. raw = ref.from_address(ptr)
  41. args = [arg for arg in raw]
  42. finally:
  43. ctypes.windll.kernel32.LocalFree(ptr)
  44. inputs = []
  45. flags = []
  46. intermediate_manifest = ''
  47. for arg in args:
  48. lower_arg = arg.lower()
  49. # We'll be replacing these ourselves.
  50. if lower_arg.startswith('/out:'):
  51. continue
  52. if lower_arg.startswith('/manifestfile:'):
  53. intermediate_manifest = arg[arg.index(':')+1:]
  54. continue
  55. if lower_arg.startswith('/pdb:'):
  56. continue
  57. if (not lower_arg.startswith('/') and
  58. lower_arg.endswith(('.obj', '.lib', '.res'))):
  59. inputs.append(arg)
  60. else:
  61. flags.append(arg)
  62. return flags, inputs, intermediate_manifest
  63. def GetRegistryValue(subkey):
  64. try:
  65. val = _winreg.QueryValue(_winreg.HKEY_CURRENT_USER,
  66. 'Software\\Chromium\\' + subkey)
  67. if os.path.exists(val):
  68. return val
  69. except WindowsError:
  70. pass
  71. raise SystemExit("Couldn't read from registry")
  72. def GetOriginalLinkerPath():
  73. return GetRegistryValue('split_link_installed')
  74. def GetMtPath():
  75. return GetRegistryValue('split_link_mt_path')
  76. def PartFor(input_file, description_parts, description_all):
  77. """Determines which part a given link input should be put into (or all)."""
  78. # Check if it should go in all parts.
  79. input_file = input_file.lower()
  80. if any(re.search(spec, input_file) for spec in description_all):
  81. return -1
  82. # Or pick which particular one it belongs in.
  83. for i, spec_list in enumerate(description_parts):
  84. if any(re.search(spec, input_file) for spec in spec_list):
  85. return i
  86. raise ValueError("couldn't find location for %s" % input_file)
  87. def ParseOutExternals(output):
  88. """Given the stdout of link.exe, parses the error messages to retrieve all
  89. symbols that are unresolved."""
  90. result = set()
  91. # Styles of messages for unresolved externals, and a boolean to indicate
  92. # whether the error message emits the symbols with or without a leading
  93. # underscore.
  94. unresolved_regexes = [
  95. (re.compile(r' : error LNK2019: unresolved external symbol ".*" \((.*)\)'
  96. r' referenced in function'),
  97. False),
  98. (re.compile(r' : error LNK2001: unresolved external symbol ".*" \((.*)\)$'),
  99. False),
  100. (re.compile(r' : error LNK2019: unresolved external symbol (.*)'
  101. r' referenced in function '),
  102. True),
  103. (re.compile(r' : error LNK2001: unresolved external symbol (.*)$'),
  104. True),
  105. ]
  106. for line in output.splitlines():
  107. line = line.strip()
  108. for regex, strip_leading_underscore in unresolved_regexes:
  109. mo = regex.search(line)
  110. if mo:
  111. if strip_leading_underscore:
  112. result.add(mo.group(1)[1:])
  113. else:
  114. result.add(mo.group(1))
  115. break
  116. mo = re.search(r'fatal error LNK1120: (\d+) unresolved externals', output)
  117. # Make sure we have the same number that the linker thinks we have.
  118. if mo is None and result:
  119. raise SystemExit(output)
  120. if len(result) != int(mo.group(1)):
  121. print output
  122. print 'Expecting %d, got %d' % (int(mo.group(1)), len(result))
  123. assert len(result) == int(mo.group(1))
  124. return sorted(result)
  125. def AsCommandLineArgs(items):
  126. """Intended for output to a response file. Quotes all arguments."""
  127. return '\n'.join('"' + x + '"' for x in items)
  128. def OutputNameForIndex(index):
  129. """Gets the final output DLL name, given a zero-based index."""
  130. if index == 0:
  131. return "chrome.dll"
  132. else:
  133. return 'chrome%d.dll' % index
  134. def ManifestNameForIndex(index):
  135. return OutputNameForIndex(index) + '.intermediate.manifest'
  136. def PdbNameForIndex(index):
  137. return OutputNameForIndex(index) + '.pdb'
  138. def RunLinker(flags, index, inputs, phase, intermediate_manifest):
  139. """Invokes the linker and returns the stdout, returncode and target name."""
  140. rspfile = 'part%d_%s.rsp' % (index, phase)
  141. with open(rspfile, 'w') as f:
  142. print >> f, AsCommandLineArgs(inputs)
  143. print >> f, AsCommandLineArgs(flags)
  144. output_name = OutputNameForIndex(index)
  145. manifest_name = ManifestNameForIndex(index)
  146. print >> f, '/ENTRY:ChromeEmptyEntry@12'
  147. print >> f, '/OUT:' + output_name
  148. print >> f, '/MANIFESTFILE:' + manifest_name
  149. print >> f, '/PDB:' + PdbNameForIndex(index)
  150. # Log('[[[\n' + open(rspfile).read() + '\n]]]')
  151. link_exe = GetOriginalLinkerPath()
  152. popen = subprocess.Popen([link_exe, '@' + rspfile], stdout=subprocess.PIPE)
  153. stdout, _ = popen.communicate()
  154. if index == 0 and popen.returncode == 0 and intermediate_manifest:
  155. # Hack for ninja build. After the linker runs, it does some manifest
  156. # things and expects there to be a file in this location. We just put it
  157. # there so it's happy. This is a no-op.
  158. if os.path.isdir(os.path.dirname(intermediate_manifest)):
  159. shutil.copyfile(manifest_name, intermediate_manifest)
  160. return stdout, popen.returncode, output_name
  161. def GetLibObjList(lib):
  162. """Gets the list of object files contained in a .lib."""
  163. link_exe = GetOriginalLinkerPath()
  164. popen = subprocess.Popen(
  165. [link_exe, '/lib', '/nologo', '/list', lib], stdout=subprocess.PIPE)
  166. stdout, _ = popen.communicate()
  167. return stdout.splitlines()
  168. def ExtractObjFromLib(lib, obj):
  169. """Extracts a .obj file contained in a .lib file. Returns the absolute path
  170. a temp file."""
  171. link_exe = GetOriginalLinkerPath()
  172. temp = tempfile.NamedTemporaryFile(
  173. prefix='split_link_', suffix='.obj', delete=False)
  174. temp.close()
  175. subprocess.check_call([
  176. link_exe, '/lib', '/nologo', '/extract:' + obj, lib, '/out:' + temp.name])
  177. return temp.name
  178. def Unmangle(export):
  179. "Returns the human-presentable name of a mangled symbol."""
  180. # Use dbghelp.dll to demangle the name.
  181. # TODO(scottmg): Perhaps a simple cache? Seems pretty fast though.
  182. UnDecorateSymbolName = ctypes.windll.dbghelp.UnDecorateSymbolName
  183. buffer_size = 2048
  184. output_string = ctypes.create_string_buffer(buffer_size)
  185. if not UnDecorateSymbolName(
  186. export, ctypes.byref(output_string), buffer_size, 0):
  187. raise ctypes.WinError()
  188. return output_string.value
  189. def IsDataDefinition(export):
  190. """Determines if a given name is data rather than a function. Always returns
  191. False for C-style (as opposed to C++-style names)."""
  192. if export[0] != '?':
  193. return False
  194. # If it contains a '(' we assume it's a function.
  195. return '(' not in Unmangle(export)
  196. def GenerateDefFiles(unresolved_by_part):
  197. """Given a list of unresolved externals, generates a .def file that will
  198. cause all those symbols to be exported."""
  199. deffiles = []
  200. Log('generating .def files')
  201. for i, part in enumerate(unresolved_by_part):
  202. deffile = 'part%d.def' % i
  203. with open(deffile, 'w') as f:
  204. print >> f, 'LIBRARY %s' % OutputNameForIndex(i)
  205. print >> f, 'EXPORTS'
  206. for j, part in enumerate(unresolved_by_part):
  207. if i == j:
  208. continue
  209. is_data = \
  210. [' DATA' if IsDataDefinition(export) and not IGNORE_DATA else ''
  211. for export in part]
  212. print >> f, '\n'.join(' ' + export + data
  213. for export, data in zip(part, is_data))
  214. deffiles.append(deffile)
  215. return deffiles
  216. def BuildImportLibs(flags, inputs_by_part, deffiles):
  217. """Runs the linker to generate an import library."""
  218. import_libs = []
  219. Log('building import libs')
  220. for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)):
  221. libfile = 'part%d.lib' % i
  222. flags_with_implib_and_deffile = flags + ['/IMPLIB:%s' % libfile,
  223. '/DEF:%s' % deffile]
  224. RunLinker(flags_with_implib_and_deffile, i, inputs, 'implib', None)
  225. import_libs.append(libfile)
  226. return import_libs
  227. def AttemptLink(flags, inputs_by_part, unresolved_by_part, deffiles,
  228. import_libs, intermediate_manifest):
  229. """Tries to run the linker for all parts using the current round of
  230. generated import libs and .def files. If the link fails, updates the
  231. unresolved externals list per part."""
  232. dlls = []
  233. all_succeeded = True
  234. new_externals = []
  235. Log('unresolveds now: %r' % [len(part) for part in unresolved_by_part])
  236. for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)):
  237. Log('running link, part %d' % i)
  238. others_implibs = import_libs[:]
  239. others_implibs.pop(i)
  240. inputs_with_implib = inputs + filter(lambda x: x, others_implibs)
  241. if deffile:
  242. flags = flags + ['/DEF:%s' % deffile, '/LTCG']
  243. stdout, rc, output = RunLinker(
  244. flags, i, inputs_with_implib, 'final', intermediate_manifest)
  245. if rc != 0:
  246. all_succeeded = False
  247. new_externals.append(ParseOutExternals(stdout))
  248. else:
  249. new_externals.append([])
  250. dlls.append(output)
  251. combined_externals = [sorted(set(prev) | set(new))
  252. for prev, new in zip(unresolved_by_part, new_externals)]
  253. return all_succeeded, dlls, combined_externals
  254. def ExtractSubObjsTargetedAtAll(
  255. inputs,
  256. num_parts,
  257. description_parts,
  258. description_all,
  259. description_all_from_libs):
  260. """For (lib, obj) tuples in the all_from_libs section, extract the obj out of
  261. the lib and added it to inputs. Returns a list of lists for which part the
  262. extracted obj belongs in (which is whichever the .lib isn't in)."""
  263. by_parts = [[] for _ in range(num_parts)]
  264. for lib_spec, obj_spec in description_all_from_libs:
  265. for input_file in inputs:
  266. if re.search(lib_spec, input_file):
  267. objs = GetLibObjList(input_file)
  268. match_count = 0
  269. for obj in objs:
  270. if re.search(obj_spec, obj, re.I):
  271. extracted_obj = ExtractObjFromLib(input_file, obj)
  272. #Log('extracted %s (%s %s)' % (extracted_obj, input_file, obj))
  273. i = PartFor(input_file, description_parts, description_all)
  274. if i == -1:
  275. raise SystemExit(
  276. '%s is already in all parts, but matched '
  277. '%s in all_from_libs' % (input_file, obj))
  278. # See note in main().
  279. assert num_parts == 2, "Can't handle > 2 dlls currently"
  280. by_parts[1 - i].append(obj)
  281. match_count += 1
  282. if match_count == 0:
  283. raise SystemExit(
  284. '%s, %s matched a lib, but no objs' % (lib_spec, obj_spec))
  285. return by_parts
  286. def main():
  287. flags, inputs, intermediate_manifest = GetFlagsAndInputs(sys.argv[1:])
  288. partition_file = os.path.normpath(
  289. os.path.join(BASE_DIR, '../../../build/split_link_partition.py'))
  290. with open(partition_file) as partition:
  291. description = eval(partition.read())
  292. inputs_by_part = []
  293. description_parts = description['parts']
  294. # We currently assume that if a symbol isn't in dll 0, then it's in dll 1
  295. # when generating def files. Otherwise, we'd need to do more complex things
  296. # to figure out where each symbol actually is to assign it to the correct
  297. # .def file.
  298. num_parts = len(description_parts)
  299. assert num_parts == 2, "Can't handle > 2 dlls currently"
  300. description_parts.reverse()
  301. objs_from_libs = ExtractSubObjsTargetedAtAll(
  302. inputs,
  303. num_parts,
  304. description_parts,
  305. description['all'],
  306. description['all_from_libs'])
  307. objs_from_libs.reverse()
  308. inputs_by_part = [[] for _ in range(num_parts)]
  309. for input_file in inputs:
  310. i = PartFor(input_file, description_parts, description['all'])
  311. if i == -1:
  312. for part in inputs_by_part:
  313. part.append(input_file)
  314. else:
  315. inputs_by_part[i].append(input_file)
  316. inputs_by_part.reverse()
  317. # Put the subobjs on to the main list.
  318. for i, part in enumerate(objs_from_libs):
  319. Log('%d sub .objs added to part %d' % (len(part), i))
  320. inputs_by_part[i].extend(part)
  321. unresolved_by_part = [[] for _ in range(num_parts)]
  322. import_libs = [None] * num_parts
  323. deffiles = [None] * num_parts
  324. data_exports = 0
  325. for i in range(5):
  326. Log('--- starting pass %d' % i)
  327. ok, dlls, unresolved_by_part = AttemptLink(
  328. flags, inputs_by_part, unresolved_by_part, deffiles, import_libs,
  329. intermediate_manifest)
  330. if ok:
  331. break
  332. data_exports = 0
  333. for i, part in enumerate(unresolved_by_part):
  334. for export in part:
  335. if IsDataDefinition(export):
  336. print 'part %d contains data export: %s (aka %s)' % (
  337. i, Unmangle(export), export)
  338. data_exports += 1
  339. deffiles = GenerateDefFiles(unresolved_by_part)
  340. import_libs = BuildImportLibs(flags, inputs_by_part, deffiles)
  341. else:
  342. if data_exports and not IGNORE_DATA:
  343. print '%d data exports found, see report above.' % data_exports
  344. print('These cannot be exported, and must be either duplicated to the '
  345. 'target DLL (if constant), or wrapped in a function.')
  346. return 1
  347. mt_exe = GetMtPath()
  348. for i, dll in enumerate(dlls):
  349. Log('embedding manifest in %s' % dll)
  350. args = [mt_exe, '-nologo', '-manifest']
  351. args.append(ManifestNameForIndex(i))
  352. args.append(description['manifest'])
  353. args.append('-outputresource:%s;2' % dll)
  354. subprocess.check_call(args)
  355. Log('built %r' % dlls)
  356. return 0
  357. if __name__ == '__main__':
  358. sys.exit(main())