Created
July 2, 2024 07:26
-
-
Save valgur/0b73e8d4316c3084403493adbc554497 to your computer and use it in GitHub Desktop.
Find all dynamic libraries needed by ELF or MachO executables recursively
This file contains 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 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