Last active
May 20, 2021 13:47
-
-
Save phillipberndt/1e922a12e2b8a177cd01 to your computer and use it in GitHub Desktop.
Check if an elf file has superflous dependencies
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
A quick & dirty replacement for dpkg-shlibdeps | |
Written to check if pqiv uses any unnecessary depencencies. dpkg-shlibdeps | |
claims that this was the case and I wanted to check. | |
""" | |
import functools | |
import multiprocessing.pool | |
import os | |
import pexpect | |
import platform | |
import re | |
import sys | |
def rpath_subst(rpath, object): | |
subst_vars = { | |
"ORIGIN": os.path.dirname(object), | |
"LIB": "lib64" if "64" in platform.architecture()[0] else "lib", | |
"PLATFORM": platform.machine() | |
} | |
for var in subst_vars: | |
rpath = rpath.replace("$%s" % var, subst_vars[var]).replace("${%s}" % var, subst_vars[var]) | |
return rpath | |
def resolve_library(object, rpath=None, runpath=None): | |
""" | |
Resolve a library from name to path | |
See dlopen(3) for the search order | |
""" | |
if object[0] == "/": | |
return object | |
if rpath: | |
rpath = rpath_subst(rpath, object) | |
if os.access(os.path.join(rpath, object), os.X_OK): | |
return os.path.join(rpath, object) | |
if "LD_LIBRARY_PATH" in os.environ: | |
for path in os.environ["LD_LIBRARY_PATH"].split(":"): | |
if os.access(os.path.join(path, object), os.X_OK): | |
return os.path.join(path, object) | |
if runpath: | |
runpath = rpath_subst(runpath, object) | |
if os.access(os.path.join(runpath, object), os.X_OK): | |
return os.path.join(runpath, object) | |
if resolve_library.cache == False: | |
resolve_library.cache = pexpect.spawn("ldconfig -p").read() | |
match = re.search(r"%s .+=> (\S+)" % re.escape(object), resolve_library.cache) | |
if match: | |
return match.group(1) | |
for path in ( "/lib", "/usr/lib" ): | |
if os.access(os.path.join(path, object), os.X_OK): | |
return os.path.join(path, object) | |
raise RuntimeError("Library %s couldn't be resolved" % object) | |
resolve_library.cache = False | |
def get_deps_and_symbols(object): | |
""" | |
Extract required libraries, unresolved symbols and exported symbols | |
from an executable in a safe manner | |
""" | |
ext = pexpect.spawn("objdump", [ "objdump", "-Tx", object ]) | |
libs = [] | |
required_symbols = [] | |
exported_symbols = [] | |
rpath = None | |
runpath = None | |
while True: | |
index = ext.expect([r"NEEDED\s+(\S+)\r?\n", r"[0-9a-f]+.{6}DF\s\*UND\*\s+[0-9a-f]+\s+(?:\S+\s+)?(\S+)\r?\n", | |
r"[0-9a-f]+.{6}DF\s\.text\s+[0-9a-f]+\s+(?:\S+\s+)?(\S+)\r?\n", | |
r"RPATH\s+(.+?)\r?\n", | |
r"RUNPATH\s+(.+?)\r?\n", | |
pexpect.EOF ]) | |
if index == 0: | |
libs.append(ext.match.group(1)) | |
elif index == 1: | |
required_symbols.append(ext.match.group(1)) | |
elif index == 2: | |
exported_symbols.append(ext.match.group(1)) | |
elif index == 3: | |
rpath = ext.match.group(1) | |
elif index == 4: | |
runpath = ext.match.group(1) | |
elif index == 5: | |
break | |
return map(functools.partial(resolve_library, rpath=rpath, runpath=runpath), libs), required_symbols, exported_symbols | |
def find_unused_dependencies(object): | |
""" | |
Check if an executable has superflous dependencies | |
""" | |
dependency_symbols = {} | |
ret = [] | |
app_libs, app_required_symbols, _ = get_deps_and_symbols(object) | |
pool = multiprocessing.pool.ThreadPool() | |
map_results = pool.map(get_deps_and_symbols, app_libs) | |
for lib, result in zip(app_libs, map_results): | |
this_lib_libs, this_lib_required_symbols, this_lib_exported_symbols = result | |
dependency_symbols[lib] = this_lib_exported_symbols | |
for lib in app_libs: | |
used_symbol_found = False | |
for symbol in dependency_symbols[lib]: | |
if symbol in app_required_symbols: | |
print "[\033[34m%-30s\033[0m] \033[32mRequired\033[0m, program uses for example `%s'" % (os.path.basename(lib), symbol) | |
used_symbol_found = True | |
break | |
if not used_symbol_found: | |
print "[\033[34m%-30s\033[0m] \033[31mSuperflous\033[0m" % (os.path.basename(lib),) | |
ret.append(lib) | |
return ret | |
def soname_to_ldflag(name): | |
""" | |
Replace library names by their corresponding ld arguments | |
""" | |
name = os.path.basename(name) | |
assert name[:3] == "lib" | |
name = name[:name.find(".so")][3:] | |
return "-l%s" % name | |
if __name__ == '__main__': | |
executable = sys.argv[1] | |
libs = find_unused_dependencies(executable) | |
if libs: | |
print "\nThe following ldlibs were superflous:\n" + " ".join(map(soname_to_ldflag, libs)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment