#!/usr/bin/env python # Copyright (c) 2011 The Chromium Authors. All rights reserved. # 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 """ import cgi import optparse import os import re import subprocess import sys cppfilt_proc = None def Demangle(sym): """Demangle a C++ symbol by passing it through c++filt.""" global cppfilt_proc if cppfilt_proc is None: cppfilt_proc = subprocess.Popen(['c++filt'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) print >>cppfilt_proc.stdin, sym return cppfilt_proc.stdout.readline().strip() def Unyuck(sym): """Attempt to prettify a C++ symbol by some basic heuristics.""" sym = sym.replace('std::basic_string, ' 'std::allocator >', 'std::string') sym = sym.replace('std::basic_string, ' 'std::allocator >', 'std::wstring') sym = sym.replace('std::basic_string >', 'string16') sym = re.sub(r', std::allocator<\S+\s+>', '', sym) return sym def Parse(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 line in input: match = symbol_re.search(line) if not match: continue symbol, path = match.groups() symbol = Unyuck(Demangle(symbol)) path = os.path.normpath(path) if skip_paths and skip_paths in path: continue if only_paths and only_paths not in path: continue match = path_re.match(path) if not match: print >>sys.stderr, "Skipping weird path", path continue target, path = match.groups() yield target, path, symbol # HTML header for our output page. TEMPLATE_HEADER = """

chrome symbols deleted at link time

""" def Output(iter): """Print HTML given an iterable of (target, path, symbol) tuples.""" targets = {} for target, path, symbol in iter: entries = targets.setdefault(target, []) entries.append((symbol, path)) print TEMPLATE_HEADER print "

jump to target:" print "

" for target in sorted(targets.keys()): print "

%s" % target print "" % (target, target) print "

" print "" for symbol, path in sorted(targets[target]): htmlsymbol = cgi.escape(symbol).replace('::', '::') print "" % htmlsymbol print "" % path print "
%s
%s
" 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()