Skip to content

Instantly share code, notes, and snippets.

@valgur
Created July 2, 2024 07:26
Show Gist options
  • Save valgur/0b73e8d4316c3084403493adbc554497 to your computer and use it in GitHub Desktop.
Save valgur/0b73e8d4316c3084403493adbc554497 to your computer and use it in GitHub Desktop.
Find all dynamic libraries needed by ELF or MachO executables recursively
#!/usr/bin/env python3
import fnmatch
import platform
import sys
from pathlib import Path
from pprint import pprint
from elftools.elf.dynamic import DynamicSection
from elftools.elf.elffile import ELFFile
from macholib.MachO import MachO
system_libs = [
"libstdc++*",
"libc++*",
"libgcc*",
"libm",
"libc",
"libpthread",
"libdl",
"librt",
"libutil",
"libresolv",
"libnsl",
"libcrypt",
"libX*",
"libGL*",
"ld-linux-*",
"libcuda*",
"libcublas*",
"libnv*",
]
def get_needed_so(file_path):
with open(file_path, "rb") as f:
elf = ELFFile(f)
for section in elf.iter_sections():
if isinstance(section, DynamicSection):
for tag in section.iter_tags():
if tag.entry.d_tag == "DT_NEEDED":
yield tag.needed
def get_needed_dylib(file_path):
macho = MachO(file_path)
for header in macho.headers:
for command in header.commands:
cmd = command[0]
if cmd.cmd == "LC_LOAD_DYLIB":
dylib_name = command[2].decode("utf-8").strip("\x00")
yield dylib_name
if platform.system() == "Darwin":
get_lib_needed = get_needed_dylib
else:
get_lib_needed = get_needed_so
def get_needed_recursive(executables, search_paths):
def _add_needed(name, path):
if name in needed:
return
needed[name] = path
for dep_file in get_lib_needed(path):
if dep_file.startswith("/"):
raise ValueError(f"Absolute path {dep_file} in {path}")
for search_path in search_paths:
dep_path = Path(search_path, dep_file)
if dep_path.exists():
_add_needed(dep_file, dep_path)
break
else:
missing_name = dep_file.split(".")[0]
if not any(fnmatch.fnmatch(missing_name, pattern) for pattern in system_libs):
print(f"Warning: {dep_file} required by {name} not found")
needed = {}
for executable in executables:
executable = Path(executable)
_add_needed(executable.name, executable)
return needed
def main(bin_dir, search_paths):
bin_dir = Path(bin_dir)
executables = sorted(
p for p in bin_dir.iterdir() if p.is_file() and p.stat().st_mode & 0o111 and not p.suffix
)
needed = get_needed_recursive(executables, search_paths)
pprint(needed)
if __name__ == "__main__":
bin_dir = sys.argv[1]
search_paths = sys.argv[1:]
main(bin_dir, search_paths)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment