unused-symbols-report.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env python
  2. # Copyright (c) 2011 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. """Prints a report of symbols stripped by the linker due to being unused.
  6. To use, build with these linker flags:
  7. -Wl,--gc-sections
  8. -Wl,--print-gc-sections
  9. the first one is the default in Release; search build/common.gypi for it
  10. and to see where to add the other.
  11. Then build, saving the output into a file:
  12. make chrome 2>&1 | tee buildlog
  13. and run this script on it:
  14. ./tools/unused-symbols-report.py buildlog > report.html
  15. """
  16. from __future__ import print_function
  17. import cgi
  18. import optparse
  19. import os
  20. import re
  21. import subprocess
  22. import sys
  23. cppfilt_proc = None
  24. def Demangle(sym):
  25. """Demangle a C++ symbol by passing it through c++filt."""
  26. global cppfilt_proc
  27. if cppfilt_proc is None:
  28. cppfilt_proc = subprocess.Popen(['c++filt'], stdin=subprocess.PIPE,
  29. stdout=subprocess.PIPE)
  30. print(sym, file=cppfilt_proc.stdin)
  31. return cppfilt_proc.stdout.readline().strip()
  32. def Unyuck(sym):
  33. """Attempt to prettify a C++ symbol by some basic heuristics."""
  34. sym = sym.replace('std::basic_string<char, std::char_traits<char>, '
  35. 'std::allocator<char> >', 'std::string')
  36. sym = sym.replace('std::basic_string<wchar_t, std::char_traits<wchar_t>, '
  37. 'std::allocator<wchar_t> >', 'std::wstring')
  38. sym = sym.replace(
  39. 'std::basic_string<char16_t, std::char_traits<char16_t>, '
  40. 'std::allocator<char16_t> >', 'std::u16string')
  41. sym = re.sub(r', std::allocator<\S+\s+>', '', sym)
  42. return sym
  43. def Parse(input, skip_paths=None, only_paths=None):
  44. """Parse the --print-gc-sections build output.
  45. Args:
  46. input: iterable over the lines of the build output
  47. Yields:
  48. (target name, path to .o file, demangled symbol)
  49. """
  50. symbol_re = re.compile(r"'\.text\.(\S+)' in file '(\S+)'$")
  51. path_re = re.compile(r"^out/[^/]+/[^/]+/([^/]+)/(.*)$")
  52. for line in input:
  53. match = symbol_re.search(line)
  54. if not match:
  55. continue
  56. symbol, path = match.groups()
  57. symbol = Unyuck(Demangle(symbol))
  58. path = os.path.normpath(path)
  59. if skip_paths and skip_paths in path:
  60. continue
  61. if only_paths and only_paths not in path:
  62. continue
  63. match = path_re.match(path)
  64. if not match:
  65. print("Skipping weird path", path, file=sys.stderr)
  66. continue
  67. target, path = match.groups()
  68. yield target, path, symbol
  69. # HTML header for our output page.
  70. TEMPLATE_HEADER = """<!DOCTYPE html>
  71. <head>
  72. <style>
  73. body {
  74. font-family: sans-serif;
  75. font-size: 0.8em;
  76. }
  77. h1, h2 {
  78. font-weight: normal;
  79. margin: 0.5em 0;
  80. }
  81. h2 {
  82. margin-top: 1em;
  83. }
  84. tr:hover {
  85. background: #eee;
  86. }
  87. .permalink {
  88. padding-left: 1ex;
  89. font-size: 80%;
  90. text-decoration: none;
  91. color: #ccc;
  92. }
  93. .symbol {
  94. font-family: WebKitWorkAround, monospace;
  95. margin-left: 4ex;
  96. text-indent: -4ex;
  97. padding: 0.5ex 1ex;
  98. }
  99. .file {
  100. padding: 0.5ex 1ex;
  101. padding-left: 2ex;
  102. font-family: WebKitWorkAround, monospace;
  103. font-size: 90%;
  104. color: #777;
  105. }
  106. </style>
  107. </head>
  108. <body>
  109. <h1>chrome symbols deleted at link time</h1>
  110. """
  111. def Output(iter):
  112. """Print HTML given an iterable of (target, path, symbol) tuples."""
  113. targets = {}
  114. for target, path, symbol in iter:
  115. entries = targets.setdefault(target, [])
  116. entries.append((symbol, path))
  117. print(TEMPLATE_HEADER)
  118. print("<p>jump to target:")
  119. print("<select onchange='document.location.hash = this.value'>")
  120. for target in sorted(targets.keys()):
  121. print("<option>%s</option>" % target)
  122. print("</select></p>")
  123. for target in sorted(targets.keys()):
  124. print("<h2>%s" % target)
  125. print("<a class=permalink href='#%s' name='%s'>#</a>" % (target, target))
  126. print("</h2>")
  127. print("<table width=100% cellspacing=0>")
  128. for symbol, path in sorted(targets[target]):
  129. htmlsymbol = cgi.escape(symbol).replace('::', '::<wbr>')
  130. print("<tr><td><div class=symbol>%s</div></td>" % htmlsymbol)
  131. print("<td valign=top><div class=file>%s</div></td></tr>" % path)
  132. print("</table>")
  133. def main():
  134. parser = optparse.OptionParser(usage='%prog [options] buildoutput\n\n' +
  135. __doc__)
  136. parser.add_option("--skip-paths", metavar="STR", default="third_party",
  137. help="skip paths matching STR [default=%default]")
  138. parser.add_option("--only-paths", metavar="STR",
  139. help="only include paths matching STR [default=%default]")
  140. opts, args = parser.parse_args()
  141. if len(args) < 1:
  142. parser.print_help()
  143. sys.exit(1)
  144. iter = Parse(open(args[0]),
  145. skip_paths=opts.skip_paths,
  146. only_paths=opts.only_paths)
  147. Output(iter)
  148. if __name__ == '__main__':
  149. main()