| #!/usr/bin/env python |
| # Copyright 2011 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Prints a report of symbols stripped by the linker due to being unused. |
| |
| To use, build with these linker flags: |
| -Wl,--gc-sections |
| -Wl,--print-gc-sections |
| the first one is the default in Release; search build/common.gypi for it |
| and to see where to add the other. |
| |
| Then build, saving the output into a file: |
| make chrome 2>&1 | tee buildlog |
| and run this script on it: |
| ./tools/unused-symbols-report.py buildlog > report.html |
| """ |
| |
| from __future__import print_function |
| |
| import cgi |
| import optparse |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| cppfilt_proc=None |
| defDemangle(sym): |
| """Demangle a C++ symbol by passing it through c++filt.""" |
| global cppfilt_proc |
| if cppfilt_procisNone: |
| cppfilt_proc= subprocess.Popen(['c++filt'], stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE) |
| print(sym, file=cppfilt_proc.stdin) |
| return cppfilt_proc.stdout.readline().strip() |
| |
| |
| defUnyuck(sym): |
| """Attempt to prettify a C++ symbol by some basic heuristics.""" |
| sym= sym.replace('std::basic_string<char, std::char_traits<char>, ' |
| 'std::allocator<char> >','std::string') |
| sym= sym.replace('std::basic_string<wchar_t, std::char_traits<wchar_t>, ' |
| 'std::allocator<wchar_t> >','std::wstring') |
| sym= sym.replace( |
| 'std::basic_string<char16_t, std::char_traits<char16_t>, ' |
| 'std::allocator<char16_t> >','std::u16string') |
| sym= re.sub(r', std::allocator<\S+\s+>','', sym) |
| return sym |
| |
| |
| defParse(input, skip_paths=None, only_paths=None): |
| """Parse the --print-gc-sections build output. |
| |
| Args: |
| input: iterable over the lines of the build output |
| |
| Yields: |
| (target name, path to .o file, demangled symbol) |
| """ |
| symbol_re= re.compile(r"'\.text\.(\S+)' in file '(\S+)'$") |
| path_re= re.compile(r"^out/[^/]+/[^/]+/([^/]+)/(.*)$") |
| for linein input: |
| match= symbol_re.search(line) |
| ifnot match: |
| continue |
| symbol, path= match.groups() |
| symbol=Unyuck(Demangle(symbol)) |
| path= os.path.normpath(path) |
| if skip_pathsand skip_pathsin path: |
| continue |
| if only_pathsand only_pathsnotin path: |
| continue |
| match= path_re.match(path) |
| ifnot match: |
| print("Skipping weird path", path, file=sys.stderr) |
| continue |
| target, path= match.groups() |
| yield target, path, symbol |
| |
| |
| # HTML header for our output page. |
| TEMPLATE_HEADER="""<!DOCTYPE html> |
| <head> |
| <style> |
| body { |
| font-family: sans-serif; |
| font-size: 0.8em; |
| } |
| h1, h2 { |
| font-weight: normal; |
| margin: 0.5em 0; |
| } |
| h2 { |
| margin-top: 1em; |
| } |
| tr:hover { |
| background: #eee; |
| } |
| .permalink { |
| padding-left: 1ex; |
| font-size: 80%; |
| text-decoration: none; |
| color: #ccc; |
| } |
| .symbol { |
| font-family: WebKitWorkAround, monospace; |
| margin-left: 4ex; |
| text-indent: -4ex; |
| padding: 0.5ex 1ex; |
| } |
| .file { |
| padding: 0.5ex 1ex; |
| padding-left: 2ex; |
| font-family: WebKitWorkAround, monospace; |
| font-size: 90%; |
| color: #777; |
| } |
| </style> |
| </head> |
| <body> |
| <h1>chrome symbols deleted at link time</h1> |
| """ |
| |
| |
| defOutput(iter): |
| """Print HTML given an iterable of (target, path, symbol) tuples.""" |
| targets={} |
| for target, path, symbolin iter: |
| entries= targets.setdefault(target,[]) |
| entries.append((symbol, path)) |
| |
| print(TEMPLATE_HEADER) |
| print("<p>jump to target:") |
| print("<select onchange='document.location.hash = this.value'>") |
| for targetin sorted(targets.keys()): |
| print("<option>%s</option>"% target) |
| print("</select></p>") |
| |
| for targetin sorted(targets.keys()): |
| print("<h2>%s"% target) |
| print("<a class=permalink href='#%s' name='%s'>#</a>"%(target, target)) |
| print("</h2>") |
| print("<table width=100% cellspacing=0>") |
| for symbol, pathin sorted(targets[target]): |
| htmlsymbol= cgi.escape(symbol).replace('::','::<wbr>') |
| print("<tr><td><div class=symbol>%s</div></td>"% htmlsymbol) |
| print("<td valign=top><div class=file>%s</div></td></tr>"% path) |
| print("</table>") |
| |
| |
| def main(): |
| parser= optparse.OptionParser(usage='%prog [options] buildoutput\n\n'+ |
| __doc__) |
| parser.add_option("--skip-paths", metavar="STR", default="third_party", |
| help="skip paths matching STR [default=%default]") |
| parser.add_option("--only-paths", metavar="STR", |
| help="only include paths matching STR [default=%default]") |
| opts, args= parser.parse_args() |
| |
| if len(args)<1: |
| parser.print_help() |
| sys.exit(1) |
| |
| iter=Parse(open(args[0]), |
| skip_paths=opts.skip_paths, |
| only_paths=opts.only_paths) |
| Output(iter) |
| |
| |
| if __name__=='__main__': |
| main() |