gcc_solink_wrapper.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #!/usr/bin/env python
  2. # Copyright 2015 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. """Runs 'ld -shared' and generates a .TOC file that's untouched when unchanged.
  6. This script exists to avoid using complex shell commands in
  7. gcc_toolchain.gni's tool("solink"), in case the host running the compiler
  8. does not have a POSIX-like shell (e.g. Windows).
  9. """
  10. import argparse
  11. import os
  12. import shlex
  13. import subprocess
  14. import sys
  15. import wrapper_utils
  16. def CollectSONAME(args):
  17. """Replaces: readelf -d $sofile | grep SONAME"""
  18. # TODO(crbug.com/1259067): Come up with a way to get this info without having
  19. # to bundle readelf in the toolchain package.
  20. toc = ''
  21. readelf = subprocess.Popen(wrapper_utils.CommandToRun(
  22. [args.readelf, '-d', args.sofile]),
  23. stdout=subprocess.PIPE,
  24. bufsize=-1,
  25. universal_newlines=True)
  26. for line in readelf.stdout:
  27. if 'SONAME' in line:
  28. toc += line
  29. return readelf.wait(), toc
  30. def CollectDynSym(args):
  31. """Replaces: nm --format=posix -g -D -p $sofile | cut -f1-2 -d' '"""
  32. toc = ''
  33. nm = subprocess.Popen(wrapper_utils.CommandToRun(
  34. [args.nm, '--format=posix', '-g', '-D', '-p', args.sofile]),
  35. stdout=subprocess.PIPE,
  36. bufsize=-1,
  37. universal_newlines=True)
  38. for line in nm.stdout:
  39. toc += ' '.join(line.split(' ', 2)[:2]) + '\n'
  40. return nm.wait(), toc
  41. def CollectTOC(args):
  42. result, toc = CollectSONAME(args)
  43. if result == 0:
  44. result, dynsym = CollectDynSym(args)
  45. toc += dynsym
  46. return result, toc
  47. def UpdateTOC(tocfile, toc):
  48. if os.path.exists(tocfile):
  49. old_toc = open(tocfile, 'r').read()
  50. else:
  51. old_toc = None
  52. if toc != old_toc:
  53. open(tocfile, 'w').write(toc)
  54. def CollectInputs(out, args):
  55. for x in args:
  56. if x.startswith('@'):
  57. with open(x[1:]) as rsp:
  58. CollectInputs(out, shlex.split(rsp.read()))
  59. elif not x.startswith('-') and (x.endswith('.o') or x.endswith('.a')):
  60. out.write(x)
  61. out.write('\n')
  62. def InterceptFlag(flag, command):
  63. ret = flag in command
  64. if ret:
  65. command.remove(flag)
  66. return ret
  67. def SafeDelete(path):
  68. try:
  69. os.unlink(path)
  70. except OSError:
  71. pass
  72. def main():
  73. parser = argparse.ArgumentParser(description=__doc__)
  74. parser.add_argument('--readelf',
  75. required=True,
  76. help='The readelf binary to run',
  77. metavar='PATH')
  78. parser.add_argument('--nm',
  79. required=True,
  80. help='The nm binary to run',
  81. metavar='PATH')
  82. parser.add_argument('--strip',
  83. help='The strip binary to run',
  84. metavar='PATH')
  85. parser.add_argument('--dwp', help='The dwp binary to run', metavar='PATH')
  86. parser.add_argument('--sofile',
  87. required=True,
  88. help='Shared object file produced by linking command',
  89. metavar='FILE')
  90. parser.add_argument('--tocfile',
  91. required=True,
  92. help='Output table-of-contents file',
  93. metavar='FILE')
  94. parser.add_argument('--map-file',
  95. help=('Use --Wl,-Map to generate a map file. Will be '
  96. 'gzipped if extension ends with .gz'),
  97. metavar='FILE')
  98. parser.add_argument('--output',
  99. required=True,
  100. help='Final output shared object file',
  101. metavar='FILE')
  102. parser.add_argument('command', nargs='+',
  103. help='Linking command')
  104. args = parser.parse_args()
  105. # Work-around for gold being slow-by-default. http://crbug.com/632230
  106. fast_env = dict(os.environ)
  107. fast_env['LC_ALL'] = 'C'
  108. # Extract flags passed through ldflags but meant for this script.
  109. # https://crbug.com/954311 tracks finding a better way to plumb these.
  110. partitioned_library = InterceptFlag('--partitioned-library', args.command)
  111. collect_inputs_only = InterceptFlag('--collect-inputs-only', args.command)
  112. # Partitioned .so libraries are used only for splitting apart in a subsequent
  113. # step.
  114. #
  115. # - The TOC file optimization isn't useful, because the partition libraries
  116. # must always be re-extracted if the combined library changes (and nothing
  117. # should be depending on the combined library's dynamic symbol table).
  118. # - Stripping isn't necessary, because the combined library is not used in
  119. # production or published.
  120. #
  121. # Both of these operations could still be done, they're needless work, and
  122. # tools would need to be updated to handle and/or not complain about
  123. # partitioned libraries. Instead, to keep Ninja happy, simply create dummy
  124. # files for the TOC and stripped lib.
  125. if collect_inputs_only or partitioned_library:
  126. open(args.output, 'w').close()
  127. open(args.tocfile, 'w').close()
  128. # Instead of linking, records all inputs to a file. This is used by
  129. # enable_resource_allowlist_generation in order to avoid needing to
  130. # link (which is slow) to build the resources allowlist.
  131. if collect_inputs_only:
  132. if args.map_file:
  133. open(args.map_file, 'w').close()
  134. if args.dwp:
  135. open(args.sofile + '.dwp', 'w').close()
  136. with open(args.sofile, 'w') as f:
  137. CollectInputs(f, args.command)
  138. return 0
  139. # First, run the actual link.
  140. command = wrapper_utils.CommandToRun(args.command)
  141. result = wrapper_utils.RunLinkWithOptionalMapFile(command,
  142. env=fast_env,
  143. map_file=args.map_file)
  144. if result != 0:
  145. return result
  146. # If dwp is set, then package debug info for this SO.
  147. dwp_proc = None
  148. if args.dwp:
  149. # Explicit delete to account for symlinks (when toggling between
  150. # debug/release).
  151. SafeDelete(args.sofile + '.dwp')
  152. # Suppress warnings about duplicate CU entries (https://crbug.com/1264130)
  153. dwp_proc = subprocess.Popen(wrapper_utils.CommandToRun(
  154. [args.dwp, '-e', args.sofile, '-o', args.sofile + '.dwp']),
  155. stderr=subprocess.DEVNULL)
  156. if not partitioned_library:
  157. # Next, generate the contents of the TOC file.
  158. result, toc = CollectTOC(args)
  159. if result != 0:
  160. return result
  161. # If there is an existing TOC file with identical contents, leave it alone.
  162. # Otherwise, write out the TOC file.
  163. UpdateTOC(args.tocfile, toc)
  164. # Finally, strip the linked shared object file (if desired).
  165. if args.strip:
  166. result = subprocess.call(
  167. wrapper_utils.CommandToRun(
  168. [args.strip, '-o', args.output, args.sofile]))
  169. if dwp_proc:
  170. dwp_result = dwp_proc.wait()
  171. if dwp_result != 0:
  172. sys.stderr.write('dwp failed with error code {}\n'.format(dwp_result))
  173. return dwp_result
  174. return result
  175. if __name__ == "__main__":
  176. sys.exit(main())