contrib: unused_functions.py: Handle archives
one can now use verbatim the arguments used by the driver invocation to link e.g. cc1. 2018-10-04 Bernhard Reutner-Fischer <aldot@gcc.gnu.org> * unused_functions.py: Handle archive files. From-SVN: r264856
This commit is contained in:
parent
f556d6b5d1
commit
0448002b17
|
@ -1,3 +1,7 @@
|
||||||
|
2018-10-04 Bernhard Reutner-Fischer <aldot@gcc.gnu.org>
|
||||||
|
|
||||||
|
* unused_functions.py: Handle archive files.
|
||||||
|
|
||||||
2018-10-04 Bernhard Reutner-Fischer <aldot@gcc.gnu.org>
|
2018-10-04 Bernhard Reutner-Fischer <aldot@gcc.gnu.org>
|
||||||
|
|
||||||
* unused_functions.py: New file.
|
* unused_functions.py: New file.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2018 Free Software Foundation
|
# Copyright (c) 2018 Free Software Foundation
|
||||||
# Contributed by Bernhard Reutner-Fischer <aldot@gcc.gnu.org>
|
# Contributed by Bernhard Reutner-Fischer <aldot@gcc.gnu.org>
|
||||||
|
@ -17,71 +18,125 @@
|
||||||
# unused_functions.py gcc/cp gcc/c-family/ gcc/*-c.o | grep -v "'gt_"
|
# unused_functions.py gcc/cp gcc/c-family/ gcc/*-c.o | grep -v "'gt_"
|
||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
sys.stderr.write("usage: %s [dirs | files] [-- <readelf options>]\n"
|
sys.stderr.write("usage: %s [-v] [dirs | files] [-- <readelf options>]\n"
|
||||||
% sys.argv[0])
|
% sys.argv[0])
|
||||||
|
sys.stderr.write("\t-v\tVerbose output\n");
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
(odir, sym_args) = (set(), "")
|
(odir, sym_args, tmpd, verbose) = (set(), "", None, False)
|
||||||
|
|
||||||
for i in range(1, len(sys.argv)):
|
for i in range(1, len(sys.argv)):
|
||||||
f = sys.argv[i]
|
f = sys.argv[i]
|
||||||
if f == "--": # sym_args
|
if f == '--': # sym_args
|
||||||
sym_args = " ".join(sys.argv[i + 1:])
|
sym_args = ' '.join(sys.argv[i + 1:])
|
||||||
break
|
break
|
||||||
|
if f == '-v':
|
||||||
|
verbose = True
|
||||||
|
continue
|
||||||
if not os.path.exists(f):
|
if not os.path.exists(f):
|
||||||
sys.stderr.write("Error: No such file or directory '%s'\n" % f)
|
sys.stderr.write("Error: No such file or directory '%s'\n" % f)
|
||||||
usage()
|
usage()
|
||||||
else:
|
else:
|
||||||
|
if f.endswith('.a') and tmpd is None:
|
||||||
|
tmpd = mkdtemp(prefix='unused_fun')
|
||||||
odir.add(f)
|
odir.add(f)
|
||||||
|
|
||||||
|
def dbg(args):
|
||||||
|
if not verbose: return
|
||||||
|
print(args)
|
||||||
|
|
||||||
def get_symbols(file):
|
def get_symbols(file):
|
||||||
syms = {}
|
syms = {}
|
||||||
for l in os.popen("readelf -W -s %s %s | c++filt" % (sym_args, file)).readlines():
|
rargs = "readelf -W -s %s %s" % (sym_args, file)
|
||||||
|
p0 = Popen((a for a in rargs.split(' ') if a.strip() != ''), stdout=PIPE)
|
||||||
|
p1 = Popen(["c++filt"], stdin=p0.stdout, stdout=PIPE,
|
||||||
|
universal_newlines=True)
|
||||||
|
lines = p1.communicate()[0]
|
||||||
|
for l in lines.split('\n'):
|
||||||
l = l.strip()
|
l = l.strip()
|
||||||
if not (len(l) and l[0].isdigit() and len(l.split()) == 8):
|
if not len(l) or not l[0].isdigit(): continue
|
||||||
continue
|
larr = l.split()
|
||||||
num, value, size, typ, bind, vis, ndx, name = l.split()
|
if len(larr) != 8: continue
|
||||||
|
num, value, size, typ, bind, vis, ndx, name = larr
|
||||||
if typ == 'SECTION' or typ == 'FILE': continue
|
if typ == 'SECTION' or typ == 'FILE': continue
|
||||||
# I don't think we have many aliases in gcc, re-instate the addr
|
# I don't think we have many aliases in gcc, re-instate the addr
|
||||||
# lut otherwise.
|
# lut otherwise.
|
||||||
if vis != "DEFAULT": continue
|
if vis != 'DEFAULT': continue
|
||||||
#value = int(value, 16)
|
#value = int(value, 16)
|
||||||
#size = int(size, 16) if size.startswith('0x') else int(size)
|
#size = int(size, 16) if size.startswith('0x') else int(size)
|
||||||
defined = ndx != "UND"
|
defined = ndx != 'UND'
|
||||||
globl = bind == "GLOBAL"
|
globl = bind == 'GLOBAL'
|
||||||
# c++ RID_FUNCTION_NAME dance. FORNOW: Handled as local use
|
# c++ RID_FUNCTION_NAME dance. FORNOW: Handled as local use
|
||||||
# Is that correct?
|
# Is that correct?
|
||||||
if name.endswith("::__FUNCTION__") and typ == "OBJECT":
|
if name.endswith('::__FUNCTION__') and typ == 'OBJECT':
|
||||||
name = name[0:(len(name) - len("::__FUNCTION__"))]
|
name = name[0:(len(name) - len('::__FUNCTION__'))]
|
||||||
if defined: defined = False
|
if defined: defined = False
|
||||||
if defined and not globl: continue
|
if defined and not globl: continue
|
||||||
syms.setdefault(name, {})
|
syms.setdefault(name, {})
|
||||||
syms[name][["use","def"][defined]] = True
|
syms[name][['use','def'][defined]] = True
|
||||||
syms[name][["local","global"][globl]] = True
|
syms[name][['local','global'][globl]] = True
|
||||||
# Note: we could filter out e.g. debug_* symbols by looking for
|
# Note: we could filter out e.g. debug_* symbols by looking for
|
||||||
# value in the debug_macro sections.
|
# value in the debug_macro sections.
|
||||||
|
if p1.returncode != 0:
|
||||||
|
print("Warning: Reading file '%s' exited with %r|%r"
|
||||||
|
% (file, p0.returncode, p1.returncode))
|
||||||
|
p0.kill()
|
||||||
return syms
|
return syms
|
||||||
|
|
||||||
(oprog, nprog) = ({}, {})
|
(oprog, nprog) = ({}, {})
|
||||||
|
|
||||||
def walker(paths):
|
def walker(paths):
|
||||||
|
def ar_x(archive):
|
||||||
|
dbg("Archive %s" % path)
|
||||||
|
f = os.path.abspath(archive)
|
||||||
|
f = os.path.splitdrive(f)[1]
|
||||||
|
d = tmpd + os.path.sep + f
|
||||||
|
d = os.path.normpath(d)
|
||||||
|
owd = os.getcwd()
|
||||||
|
try:
|
||||||
|
os.makedirs(d)
|
||||||
|
os.chdir(d)
|
||||||
|
p0 = Popen(["ar", "x", "%s" % os.path.join(owd, archive)],
|
||||||
|
stderr=PIPE, universal_newlines=True)
|
||||||
|
p0.communicate()
|
||||||
|
if p0.returncode > 0: d = None # assume thin archive
|
||||||
|
except:
|
||||||
|
dbg("ar x: Error: %s: %s" % (archive, sys.exc_info()[0]))
|
||||||
|
os.chdir(owd)
|
||||||
|
raise
|
||||||
|
os.chdir(owd)
|
||||||
|
if d: dbg("Extracted to %s" % (d))
|
||||||
|
return (archive, d)
|
||||||
|
|
||||||
|
def ar_t(archive):
|
||||||
|
dbg("Thin archive, using existing files:")
|
||||||
|
try:
|
||||||
|
p0 = Popen(["ar", "t", "%s" % archive], stdout=PIPE,
|
||||||
|
universal_newlines=True)
|
||||||
|
ret = p0.communicate()[0]
|
||||||
|
return ret.split('\n')
|
||||||
|
except:
|
||||||
|
dbg("ar t: Error: %s: %s" % (archive, sys.exc_info()[0]))
|
||||||
|
raise
|
||||||
|
|
||||||
prog = {}
|
prog = {}
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
for r, dirs, files in os.walk(path):
|
for r, dirs, files in os.walk(path):
|
||||||
for f in files:
|
if files: dbg("Files %s" % ", ".join(files))
|
||||||
# TODO: maybe extract .a to a tmpdir and walk that, too
|
if dirs: dbg("Dirs %s" % ", ".join(dirs))
|
||||||
# maybe /there/foolib.a(file.o) as name?
|
prog.update(walker([os.path.join(r, f) for f in files]))
|
||||||
if not f.endswith(".o"): continue
|
prog.update(walker([os.path.join(r, d) for d in dirs]))
|
||||||
p = os.path.join(r, f)
|
|
||||||
prog[os.path.normpath(p)] = get_symbols(p)
|
|
||||||
for d in dirs:
|
|
||||||
tem = prog.copy()
|
|
||||||
tem.update(walker([os.path.join(r, d)]))
|
|
||||||
prog = tem
|
|
||||||
else:
|
else:
|
||||||
|
if path.endswith('.a'):
|
||||||
|
if ar_x(path)[1] is not None: continue # extract worked
|
||||||
|
prog.update(walker(ar_t(path)))
|
||||||
|
if not path.endswith('.o'): continue
|
||||||
|
dbg("Reading symbols from %s" % (path))
|
||||||
prog[os.path.normpath(path)] = get_symbols(path)
|
prog[os.path.normpath(path)] = get_symbols(path)
|
||||||
return prog
|
return prog
|
||||||
|
|
||||||
|
@ -92,8 +147,8 @@ def resolve(prog):
|
||||||
for (f, g) in ((f,g) for f in x for g in x if f != g):
|
for (f, g) in ((f,g) for f in x for g in x if f != g):
|
||||||
refs = set()
|
refs = set()
|
||||||
# for each defined symbol
|
# for each defined symbol
|
||||||
for s in (s for s in prog[f] if prog[f][s].get("def") and s in prog[g]):
|
for s in (s for s in prog[f] if prog[f][s].get('def') and s in prog[g]):
|
||||||
if prog[g][s].get("use"):
|
if prog[g][s].get('use'):
|
||||||
refs.add(s)
|
refs.add(s)
|
||||||
for s in refs:
|
for s in refs:
|
||||||
# Prune externally referenced symbols as speed optimization only
|
# Prune externally referenced symbols as speed optimization only
|
||||||
|
@ -101,10 +156,22 @@ def resolve(prog):
|
||||||
use |= refs
|
use |= refs
|
||||||
return use
|
return use
|
||||||
|
|
||||||
oprog = walker(odir)
|
try:
|
||||||
oused = resolve(oprog)
|
oprog = walker(odir)
|
||||||
|
if tmpd is not None:
|
||||||
|
oprog.update(walker([tmpd]))
|
||||||
|
oused = resolve(oprog)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
p0 = Popen(["rm", "-r", "-f", "%s" % (tmpd)], stderr=PIPE, stdout=PIPE)
|
||||||
|
p0.communicate()
|
||||||
|
if p0.returncode != 0: raise "rm '%s' didn't work out" % (tmpd)
|
||||||
|
except:
|
||||||
|
from shutil import rmtree
|
||||||
|
rmtree(tmpd, ignore_errors=True)
|
||||||
|
|
||||||
for (i,s) in ((i,s) for i in oprog.keys() for s in oprog[i] if oprog[i][s]):
|
for (i,s) in ((i,s) for i in oprog.keys() for s in oprog[i] if oprog[i][s]):
|
||||||
if oprog[i][s].get("def") and not oprog[i][s].get("use"):
|
if oprog[i][s].get('def') and not oprog[i][s].get('use'):
|
||||||
print("%s: Symbol '%s' declared extern but never referenced externally"
|
print("%s: Symbol '%s' declared extern but never referenced externally"
|
||||||
% (i,s))
|
% (i,s))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue