maruel@chromium.org | cb155a8 | 2011-11-29 17:25:34 | [diff] [blame] | 1 | #!/usr/bin/env python |
Avi Drissman | dfd88085 | 2022-09-15 20:11:09 | [diff] [blame] | 2 | # Copyright 2011 The Chromium Authors |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
maruel@chromium.org | cb155a8 | 2011-11-29 17:25:34 | [diff] [blame] | 6 | """Prints a report of symbols stripped by the linker due to being unused. |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 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 | |
Raul Tambre | ca9124e4 | 2019-09-27 04:13:35 | [diff] [blame] | 20 | from __future__ import print_function |
| 21 | |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 22 | import cgi |
| 23 | import optparse |
| 24 | import os |
| 25 | import re |
| 26 | import subprocess |
| 27 | import sys |
| 28 | |
| 29 | cppfilt_proc = None |
| 30 | def Demangle(sym): |
| 31 | """Demangle a C++ symbol by passing it through c++filt.""" |
| 32 | global cppfilt_proc |
| 33 | if cppfilt_proc is None: |
| 34 | cppfilt_proc = subprocess.Popen(['c++filt'], stdin=subprocess.PIPE, |
| 35 | stdout=subprocess.PIPE) |
Raul Tambre | ca9124e4 | 2019-09-27 04:13:35 | [diff] [blame] | 36 | print(sym, file=cppfilt_proc.stdin) |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 37 | return cppfilt_proc.stdout.readline().strip() |
| 38 | |
| 39 | |
| 40 | def Unyuck(sym): |
| 41 | """Attempt to prettify a C++ symbol by some basic heuristics.""" |
| 42 | sym = sym.replace('std::basic_string<char, std::char_traits<char>, ' |
| 43 | 'std::allocator<char> >', 'std::string') |
| 44 | sym = sym.replace('std::basic_string<wchar_t, std::char_traits<wchar_t>, ' |
| 45 | 'std::allocator<wchar_t> >', 'std::wstring') |
Jan Wilken Dörrie | 858cc5c4 | 2021-03-14 17:46:17 | [diff] [blame] | 46 | sym = sym.replace( |
| 47 | 'std::basic_string<char16_t, std::char_traits<char16_t>, ' |
| 48 | 'std::allocator<char16_t> >', 'std::u16string') |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 49 | sym = re.sub(r', std::allocator<\S+\s+>', '', sym) |
| 50 | return sym |
| 51 | |
| 52 | |
| 53 | def Parse(input, skip_paths=None, only_paths=None): |
| 54 | """Parse the --print-gc-sections build output. |
| 55 | |
| 56 | Args: |
| 57 | input: iterable over the lines of the build output |
| 58 | |
| 59 | Yields: |
| 60 | (target name, path to .o file, demangled symbol) |
| 61 | """ |
| 62 | symbol_re = re.compile(r"'\.text\.(\S+)' in file '(\S+)'$") |
| 63 | path_re = re.compile(r"^out/[^/]+/[^/]+/([^/]+)/(.*)$") |
| 64 | for line in input: |
| 65 | match = symbol_re.search(line) |
| 66 | if not match: |
| 67 | continue |
| 68 | symbol, path = match.groups() |
| 69 | symbol = Unyuck(Demangle(symbol)) |
| 70 | path = os.path.normpath(path) |
| 71 | if skip_paths and skip_paths in path: |
| 72 | continue |
| 73 | if only_paths and only_paths not in path: |
| 74 | continue |
| 75 | match = path_re.match(path) |
| 76 | if not match: |
Raul Tambre | ca9124e4 | 2019-09-27 04:13:35 | [diff] [blame] | 77 | print("Skipping weird path", path, file=sys.stderr) |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 78 | continue |
| 79 | target, path = match.groups() |
| 80 | yield target, path, symbol |
| 81 | |
| 82 | |
| 83 | # HTML header for our output page. |
| 84 | TEMPLATE_HEADER = """<!DOCTYPE html> |
| 85 | <head> |
| 86 | <style> |
| 87 | body { |
| 88 | font-family: sans-serif; |
| 89 | font-size: 0.8em; |
| 90 | } |
| 91 | h1, h2 { |
| 92 | font-weight: normal; |
| 93 | margin: 0.5em 0; |
| 94 | } |
| 95 | h2 { |
| 96 | margin-top: 1em; |
| 97 | } |
| 98 | tr:hover { |
| 99 | background: #eee; |
| 100 | } |
| 101 | .permalink { |
| 102 | padding-left: 1ex; |
| 103 | font-size: 80%; |
| 104 | text-decoration: none; |
| 105 | color: #ccc; |
| 106 | } |
| 107 | .symbol { |
| 108 | font-family: WebKitWorkAround, monospace; |
| 109 | margin-left: 4ex; |
| 110 | text-indent: -4ex; |
| 111 | padding: 0.5ex 1ex; |
| 112 | } |
| 113 | .file { |
| 114 | padding: 0.5ex 1ex; |
| 115 | padding-left: 2ex; |
| 116 | font-family: WebKitWorkAround, monospace; |
| 117 | font-size: 90%; |
| 118 | color: #777; |
| 119 | } |
| 120 | </style> |
| 121 | </head> |
| 122 | <body> |
| 123 | <h1>chrome symbols deleted at link time</h1> |
| 124 | """ |
| 125 | |
| 126 | |
| 127 | def Output(iter): |
| 128 | """Print HTML given an iterable of (target, path, symbol) tuples.""" |
| 129 | targets = {} |
| 130 | for target, path, symbol in iter: |
| 131 | entries = targets.setdefault(target, []) |
| 132 | entries.append((symbol, path)) |
| 133 | |
Raul Tambre | ca9124e4 | 2019-09-27 04:13:35 | [diff] [blame] | 134 | print(TEMPLATE_HEADER) |
| 135 | print("<p>jump to target:") |
| 136 | print("<select = this.value'>") |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 137 | for target in sorted(targets.keys()): |
Raul Tambre | ca9124e4 | 2019-09-27 04:13:35 | [diff] [blame] | 138 | print("<option>%s</option>" % target) |
| 139 | print("</select></p>") |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 140 | |
| 141 | for target in sorted(targets.keys()): |
Raul Tambre | ca9124e4 | 2019-09-27 04:13:35 | [diff] [blame] | 142 | print("<h2>%s" % target) |
| 143 | print("<a class=permalink href='#%s' name='%s'>#</a>" % (target, target)) |
| 144 | print("</h2>") |
| 145 | print("<table width=100% cellspacing=0>") |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 146 | for symbol, path in sorted(targets[target]): |
| 147 | htmlsymbol = cgi.escape(symbol).replace('::', '::<wbr>') |
Raul Tambre | ca9124e4 | 2019-09-27 04:13:35 | [diff] [blame] | 148 | print("<tr><td><div class=symbol>%s</div></td>" % htmlsymbol) |
| 149 | print("<td valign=top><div class=file>%s</div></td></tr>" % path) |
| 150 | print("</table>") |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 151 | |
| 152 | |
| 153 | def main(): |
| 154 | parser = optparse.OptionParser(usage='%prog [options] buildoutput\n\n' + |
| 155 | __doc__) |
| 156 | parser.add_option("--skip-paths", metavar="STR", default="third_party", |
| 157 | help="skip paths matching STR [default=%default]") |
| 158 | parser.add_option("--only-paths", metavar="STR", |
| 159 | help="only include paths matching STR [default=%default]") |
| 160 | opts, args = parser.parse_args() |
| 161 | |
| 162 | if len(args) < 1: |
| 163 | parser.print_help() |
| 164 | sys.exit(1) |
| 165 | |
| 166 | iter = Parse(open(args[0]), |
| 167 | skip_paths=opts.skip_paths, |
| 168 | only_paths=opts.only_paths) |
| 169 | Output(iter) |
| 170 | |
maruel@chromium.org | cb155a8 | 2011-11-29 17:25:34 | [diff] [blame] | 171 | |
evan@chromium.org | 74b2cbc | 2010-10-12 21:46:41 | [diff] [blame] | 172 | if __name__ == '__main__': |
| 173 | main() |