Home | History | Annotate | Download | only in tools
      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