]>
Commit | Line | Data |
---|---|---|
75fcc3eb | 1 | #!/usr/bin/env python |
0448002b | 2 | # -*- coding: utf-8 -*- |
75fcc3eb | 3 | # |
a945c346 | 4 | # Copyright (C) 2018-2024 Free Software Foundation, Inc. |
75fcc3eb BRF |
5 | # Contributed by Bernhard Reutner-Fischer <aldot@gcc.gnu.org> |
6 | # Inspired by bloat-o-meter from busybox. | |
7 | ||
8 | # This software may be used and distributed according to the terms and | |
9 | # conditions of the GNU General Public License as published by the Free | |
10 | # Software Foundation. | |
11 | ||
12 | # For a set of object-files, determine symbols that are | |
13 | # - public but should be static | |
14 | ||
15 | # Examples: | |
16 | # unused_functions.py ./gcc/fortran | |
17 | # unused_functions.py gcc/c gcc/c-family/ gcc/*-c.o | grep -v "'gt_" | |
18 | # unused_functions.py gcc/cp gcc/c-family/ gcc/*-c.o | grep -v "'gt_" | |
19 | ||
20 | import sys, os | |
0448002b BRF |
21 | from tempfile import mkdtemp |
22 | from subprocess import Popen, PIPE | |
75fcc3eb BRF |
23 | |
24 | def usage(): | |
0448002b | 25 | sys.stderr.write("usage: %s [-v] [dirs | files] [-- <readelf options>]\n" |
75fcc3eb | 26 | % sys.argv[0]) |
0448002b | 27 | sys.stderr.write("\t-v\tVerbose output\n"); |
75fcc3eb BRF |
28 | sys.exit(1) |
29 | ||
0448002b | 30 | (odir, sym_args, tmpd, verbose) = (set(), "", None, False) |
75fcc3eb BRF |
31 | |
32 | for i in range(1, len(sys.argv)): | |
33 | f = sys.argv[i] | |
0448002b BRF |
34 | if f == '--': # sym_args |
35 | sym_args = ' '.join(sys.argv[i + 1:]) | |
75fcc3eb | 36 | break |
0448002b BRF |
37 | if f == '-v': |
38 | verbose = True | |
39 | continue | |
75fcc3eb BRF |
40 | if not os.path.exists(f): |
41 | sys.stderr.write("Error: No such file or directory '%s'\n" % f) | |
42 | usage() | |
43 | else: | |
0448002b BRF |
44 | if f.endswith('.a') and tmpd is None: |
45 | tmpd = mkdtemp(prefix='unused_fun') | |
75fcc3eb BRF |
46 | odir.add(f) |
47 | ||
0448002b BRF |
48 | def dbg(args): |
49 | if not verbose: return | |
50 | print(args) | |
51 | ||
75fcc3eb BRF |
52 | def get_symbols(file): |
53 | syms = {} | |
0448002b BRF |
54 | rargs = "readelf -W -s %s %s" % (sym_args, file) |
55 | p0 = Popen((a for a in rargs.split(' ') if a.strip() != ''), stdout=PIPE) | |
56 | p1 = Popen(["c++filt"], stdin=p0.stdout, stdout=PIPE, | |
57 | universal_newlines=True) | |
58 | lines = p1.communicate()[0] | |
59 | for l in lines.split('\n'): | |
75fcc3eb | 60 | l = l.strip() |
0448002b BRF |
61 | if not len(l) or not l[0].isdigit(): continue |
62 | larr = l.split() | |
63 | if len(larr) != 8: continue | |
64 | num, value, size, typ, bind, vis, ndx, name = larr | |
75fcc3eb BRF |
65 | if typ == 'SECTION' or typ == 'FILE': continue |
66 | # I don't think we have many aliases in gcc, re-instate the addr | |
67 | # lut otherwise. | |
0448002b | 68 | if vis != 'DEFAULT': continue |
75fcc3eb BRF |
69 | #value = int(value, 16) |
70 | #size = int(size, 16) if size.startswith('0x') else int(size) | |
0448002b BRF |
71 | defined = ndx != 'UND' |
72 | globl = bind == 'GLOBAL' | |
75fcc3eb BRF |
73 | # c++ RID_FUNCTION_NAME dance. FORNOW: Handled as local use |
74 | # Is that correct? | |
0448002b BRF |
75 | if name.endswith('::__FUNCTION__') and typ == 'OBJECT': |
76 | name = name[0:(len(name) - len('::__FUNCTION__'))] | |
75fcc3eb BRF |
77 | if defined: defined = False |
78 | if defined and not globl: continue | |
79 | syms.setdefault(name, {}) | |
0448002b BRF |
80 | syms[name][['use','def'][defined]] = True |
81 | syms[name][['local','global'][globl]] = True | |
75fcc3eb BRF |
82 | # Note: we could filter out e.g. debug_* symbols by looking for |
83 | # value in the debug_macro sections. | |
0448002b BRF |
84 | if p1.returncode != 0: |
85 | print("Warning: Reading file '%s' exited with %r|%r" | |
86 | % (file, p0.returncode, p1.returncode)) | |
87 | p0.kill() | |
75fcc3eb BRF |
88 | return syms |
89 | ||
90 | (oprog, nprog) = ({}, {}) | |
91 | ||
92 | def walker(paths): | |
0448002b BRF |
93 | def ar_x(archive): |
94 | dbg("Archive %s" % path) | |
95 | f = os.path.abspath(archive) | |
96 | f = os.path.splitdrive(f)[1] | |
97 | d = tmpd + os.path.sep + f | |
98 | d = os.path.normpath(d) | |
99 | owd = os.getcwd() | |
100 | try: | |
101 | os.makedirs(d) | |
102 | os.chdir(d) | |
103 | p0 = Popen(["ar", "x", "%s" % os.path.join(owd, archive)], | |
104 | stderr=PIPE, universal_newlines=True) | |
105 | p0.communicate() | |
106 | if p0.returncode > 0: d = None # assume thin archive | |
107 | except: | |
108 | dbg("ar x: Error: %s: %s" % (archive, sys.exc_info()[0])) | |
109 | os.chdir(owd) | |
110 | raise | |
111 | os.chdir(owd) | |
112 | if d: dbg("Extracted to %s" % (d)) | |
113 | return (archive, d) | |
114 | ||
115 | def ar_t(archive): | |
116 | dbg("Thin archive, using existing files:") | |
117 | try: | |
118 | p0 = Popen(["ar", "t", "%s" % archive], stdout=PIPE, | |
119 | universal_newlines=True) | |
120 | ret = p0.communicate()[0] | |
121 | return ret.split('\n') | |
122 | except: | |
123 | dbg("ar t: Error: %s: %s" % (archive, sys.exc_info()[0])) | |
124 | raise | |
125 | ||
75fcc3eb BRF |
126 | prog = {} |
127 | for path in paths: | |
128 | if os.path.isdir(path): | |
129 | for r, dirs, files in os.walk(path): | |
0448002b BRF |
130 | if files: dbg("Files %s" % ", ".join(files)) |
131 | if dirs: dbg("Dirs %s" % ", ".join(dirs)) | |
132 | prog.update(walker([os.path.join(r, f) for f in files])) | |
133 | prog.update(walker([os.path.join(r, d) for d in dirs])) | |
75fcc3eb | 134 | else: |
0448002b BRF |
135 | if path.endswith('.a'): |
136 | if ar_x(path)[1] is not None: continue # extract worked | |
137 | prog.update(walker(ar_t(path))) | |
138 | if not path.endswith('.o'): continue | |
139 | dbg("Reading symbols from %s" % (path)) | |
75fcc3eb BRF |
140 | prog[os.path.normpath(path)] = get_symbols(path) |
141 | return prog | |
142 | ||
143 | def resolve(prog): | |
144 | x = prog.keys() | |
145 | use = set() | |
146 | # for each unique pair of different files | |
147 | for (f, g) in ((f,g) for f in x for g in x if f != g): | |
148 | refs = set() | |
149 | # for each defined symbol | |
0448002b BRF |
150 | for s in (s for s in prog[f] if prog[f][s].get('def') and s in prog[g]): |
151 | if prog[g][s].get('use'): | |
75fcc3eb BRF |
152 | refs.add(s) |
153 | for s in refs: | |
154 | # Prune externally referenced symbols as speed optimization only | |
155 | for i in (i for i in x if s in prog[i]): del prog[i][s] | |
156 | use |= refs | |
157 | return use | |
158 | ||
0448002b BRF |
159 | try: |
160 | oprog = walker(odir) | |
161 | if tmpd is not None: | |
162 | oprog.update(walker([tmpd])) | |
163 | oused = resolve(oprog) | |
164 | finally: | |
165 | try: | |
166 | p0 = Popen(["rm", "-r", "-f", "%s" % (tmpd)], stderr=PIPE, stdout=PIPE) | |
167 | p0.communicate() | |
168 | if p0.returncode != 0: raise "rm '%s' didn't work out" % (tmpd) | |
169 | except: | |
170 | from shutil import rmtree | |
171 | rmtree(tmpd, ignore_errors=True) | |
172 | ||
75fcc3eb | 173 | for (i,s) in ((i,s) for i in oprog.keys() for s in oprog[i] if oprog[i][s]): |
0448002b | 174 | if oprog[i][s].get('def') and not oprog[i][s].get('use'): |
75fcc3eb BRF |
175 | print("%s: Symbol '%s' declared extern but never referenced externally" |
176 | % (i,s)) | |
177 | ||
178 |