2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2018-2023 Free Software Foundation, Inc.
5 # Contributed by Bernhard Reutner-Fischer <aldot@gcc.gnu.org>
6 # Inspired by bloat-o-meter from busybox.
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.
12 # For a set of object-files, determine symbols that are
13 # - public but should be static
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_"
21 from tempfile
import mkdtemp
22 from subprocess
import Popen
, PIPE
25 sys
.stderr
.write("usage: %s [-v] [dirs | files] [-- <readelf options>]\n"
27 sys
.stderr
.write("\t-v\tVerbose output\n");
30 (odir
, sym_args
, tmpd
, verbose
) = (set(), "", None, False)
32 for i
in range(1, len(sys
.argv
)):
34 if f
== '--': # sym_args
35 sym_args
= ' '.join(sys
.argv
[i
+ 1:])
40 if not os
.path
.exists(f
):
41 sys
.stderr
.write("Error: No such file or directory '%s'\n" % f
)
44 if f
.endswith('.a') and tmpd
is None:
45 tmpd
= mkdtemp(prefix
='unused_fun')
49 if not verbose
: return
52 def get_symbols(file):
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'):
61 if not len(l
) or not l
[0].isdigit(): continue
63 if len(larr
) != 8: continue
64 num
, value
, size
, typ
, bind
, vis
, ndx
, name
= larr
65 if typ
== 'SECTION' or typ
== 'FILE': continue
66 # I don't think we have many aliases in gcc, re-instate the addr
68 if vis
!= 'DEFAULT': continue
69 #value = int(value, 16)
70 #size = int(size, 16) if size.startswith('0x') else int(size)
71 defined
= ndx
!= 'UND'
72 globl
= bind
== 'GLOBAL'
73 # c++ RID_FUNCTION_NAME dance. FORNOW: Handled as local use
75 if name
.endswith('::__FUNCTION__') and typ
== 'OBJECT':
76 name
= name
[0:(len(name
) - len('::__FUNCTION__'))]
77 if defined
: defined
= False
78 if defined
and not globl
: continue
79 syms
.setdefault(name
, {})
80 syms
[name
][['use','def'][defined
]] = True
81 syms
[name
][['local','global'][globl
]] = True
82 # Note: we could filter out e.g. debug_* symbols by looking for
83 # value in the debug_macro sections.
84 if p1
.returncode
!= 0:
85 print("Warning: Reading file '%s' exited with %r|%r"
86 % (file, p0
.returncode
, p1
.returncode
))
90 (oprog
, nprog
) = ({}, {})
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
)
103 p0
= Popen(["ar", "x", "%s" % os
.path
.join(owd
, archive
)],
104 stderr
=PIPE
, universal_newlines
=True)
106 if p0
.returncode
> 0: d
= None # assume thin archive
108 dbg("ar x: Error: %s: %s" % (archive
, sys
.exc_info()[0]))
112 if d
: dbg("Extracted to %s" % (d
))
116 dbg("Thin archive, using existing files:")
118 p0
= Popen(["ar", "t", "%s" % archive
], stdout
=PIPE
,
119 universal_newlines
=True)
120 ret
= p0
.communicate()[0]
121 return ret
.split('\n')
123 dbg("ar t: Error: %s: %s" % (archive
, sys
.exc_info()[0]))
128 if os
.path
.isdir(path
):
129 for r
, dirs
, files
in os
.walk(path
):
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
]))
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
))
140 prog
[os
.path
.normpath(path
)] = get_symbols(path
)
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
):
149 # for each defined symbol
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'):
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
]
162 oprog
.update(walker([tmpd
]))
163 oused
= resolve(oprog
)
166 p0
= Popen(["rm", "-r", "-f", "%s" % (tmpd
)], stderr
=PIPE
, stdout
=PIPE
)
168 if p0
.returncode
!= 0: raise "rm '%s' didn't work out" % (tmpd
)
170 from shutil
import rmtree
171 rmtree(tmpd
, ignore_errors
=True)
173 for (i
,s
) in ((i
,s
) for i
in oprog
.keys() for s
in oprog
[i
] if oprog
[i
][s
]):
174 if oprog
[i
][s
].get('def') and not oprog
[i
][s
].get('use'):
175 print("%s: Symbol '%s' declared extern but never referenced externally"