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() 172