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