Skip to content

Instantly share code, notes, and snippets.

@phillipberndt
Last active May 20, 2021 13:47
Show Gist options
  • Save phillipberndt/1e922a12e2b8a177cd01 to your computer and use it in GitHub Desktop.
Save phillipberndt/1e922a12e2b8a177cd01 to your computer and use it in GitHub Desktop.
Check if an elf file has superflous dependencies
#!/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