170 lines
5.6 KiB
Python
170 lines
5.6 KiB
Python
|
#!/usr/bin/python3
|
||
|
|
||
|
################################################################################
|
||
|
# Small python script to retrieve DLL dependencies with objdump
|
||
|
################################################################################
|
||
|
|
||
|
################################################################################
|
||
|
# Usage example
|
||
|
#
|
||
|
# python3 dll_link.py /path/to/run.exe /winenv/ /path/install
|
||
|
#
|
||
|
# In this case, the DLL dependencies for executable run.exe will be extracted
|
||
|
# and copied into /path/install/bin folder. To copy the DLL, the root path to
|
||
|
# Windows environnment should be passed, here /winenv/.
|
||
|
|
||
|
import argparse
|
||
|
import os
|
||
|
import subprocess
|
||
|
import re
|
||
|
import shutil
|
||
|
import sys
|
||
|
|
||
|
################################################################################
|
||
|
# Global variables
|
||
|
|
||
|
# Sets for executable and system DLL
|
||
|
dlls = set()
|
||
|
sys_dlls = set()
|
||
|
|
||
|
# Previously done DLLs in previous runs
|
||
|
done_dlls = set()
|
||
|
|
||
|
# Common paths
|
||
|
bindir = 'bin'
|
||
|
|
||
|
################################################################################
|
||
|
# Functions
|
||
|
|
||
|
# Main function
|
||
|
def main(binary, srcdirs, destdir, debug, dll_file):
|
||
|
global done_dlls
|
||
|
try:
|
||
|
if dll_file is not None:
|
||
|
with open(dll_file, 'r') as f:
|
||
|
done_dlls = { line.strip() for line in f if len(line.strip()) != 0 }
|
||
|
except FileNotFoundError:
|
||
|
pass
|
||
|
|
||
|
sys.stdout.write("{} (INFO): searching for dependencies of {} in {}.\n".format(os.path.basename(__file__),
|
||
|
binary, ', '.join(srcdirs)))
|
||
|
find_dependencies(os.path.abspath(binary), srcdirs)
|
||
|
if debug in ['debug-only', 'debug-run']:
|
||
|
if debug == 'debug-only':
|
||
|
print("Running in debug-only mode (no DLL moved) for '{}'".format(binary))
|
||
|
else:
|
||
|
print("Running with debug output for '{}'".format(binary))
|
||
|
|
||
|
if len(dlls) > 0:
|
||
|
sys.stdout.write("Needed DLLs:\n\t- ")
|
||
|
sys.stdout.write("\n\t- ".join(list(dlls)))
|
||
|
print()
|
||
|
if len(sys_dlls) > 0:
|
||
|
sys.stdout.write('System DLLs:\n\t- ')
|
||
|
sys.stdout.write("\n\t- ".join(sys_dlls))
|
||
|
print()
|
||
|
removed_dlls = sys_dlls & dlls
|
||
|
if len(removed_dlls) > 0:
|
||
|
sys.stdout.write('Non installed DLLs:\n\t- ')
|
||
|
sys.stdout.write("\n\t- ".join(removed_dlls))
|
||
|
print()
|
||
|
installed_dlls = dlls - sys_dlls
|
||
|
if len(installed_dlls) > 0:
|
||
|
sys.stdout.write('Installed DLLs:\n\t- ')
|
||
|
sys.stdout.write("\n\t- ".join(installed_dlls))
|
||
|
print()
|
||
|
|
||
|
if debug == 'debug-run':
|
||
|
copy_dlls(dlls - sys_dlls, srcdirs, destdir)
|
||
|
else:
|
||
|
copy_dlls(dlls - sys_dlls, srcdirs, destdir)
|
||
|
|
||
|
if dll_file is not None:
|
||
|
with open(dll_file, 'w') as f:
|
||
|
f.write("\n".join(set.union(done_dlls, dlls, sys_dlls)))
|
||
|
|
||
|
def find_dependencies(obj, srcdirs):
|
||
|
'''
|
||
|
List DLLs of an object file in a recursive way.
|
||
|
'''
|
||
|
if obj in set.union(done_dlls, sys_dlls):
|
||
|
# Already processed, either in a previous run of the script
|
||
|
# (done_dlls) or in this one.
|
||
|
return True
|
||
|
|
||
|
if not os.path.isabs(obj):
|
||
|
for srcdir in srcdirs:
|
||
|
abs_dll = os.path.join(srcdir, bindir, obj)
|
||
|
abs_dll = os.path.abspath(abs_dll)
|
||
|
if find_dependencies(abs_dll, srcdirs):
|
||
|
return True
|
||
|
else:
|
||
|
# Found in none of the srcdirs: consider it a system DLL
|
||
|
sys_dlls.add(os.path.basename(obj))
|
||
|
return False
|
||
|
elif os.path.exists(obj):
|
||
|
# If DLL exists, extract dependencies.
|
||
|
objdump = None
|
||
|
|
||
|
result = subprocess.run(['file', obj], stdout=subprocess.PIPE)
|
||
|
file_type = result.stdout.decode('utf-8')
|
||
|
if 'PE32+' in file_type:
|
||
|
objdump = 'x86_64-w64-mingw32-objdump'
|
||
|
elif 'PE32' in file_type:
|
||
|
objdump = 'i686-w64-mingw32-objdump'
|
||
|
|
||
|
if objdump is None:
|
||
|
sys.stderr.write('File type of {} unknown: {}\n'.format(obj, file_type))
|
||
|
sys.exit(1)
|
||
|
elif shutil.which(objdump) is None:
|
||
|
# For native objdump case.
|
||
|
objdump = 'objdump.exe'
|
||
|
|
||
|
if shutil.which(objdump) is None:
|
||
|
sys.stderr.write("Executable doesn't exist: {}\n".format(objdump))
|
||
|
sys.exit(1)
|
||
|
|
||
|
result = subprocess.run([objdump, '-p', obj], stdout=subprocess.PIPE)
|
||
|
out = result.stdout.decode('utf-8')
|
||
|
# Parse lines with DLL Name instead of lib*.dll directly
|
||
|
for match in re.finditer(r"DLL Name: *(\S+.dll)", out, re.MULTILINE):
|
||
|
dll = match.group(1)
|
||
|
if dll not in set.union(done_dlls, dlls, sys_dlls):
|
||
|
dlls.add(dll)
|
||
|
find_dependencies(dll, srcdirs)
|
||
|
|
||
|
return True
|
||
|
else:
|
||
|
return False
|
||
|
|
||
|
# Copy a DLL set into the /destdir/bin directory
|
||
|
def copy_dlls(dll_list, srcdirs, destdir):
|
||
|
destbin = os.path.join(destdir, bindir)
|
||
|
os.makedirs(destbin, exist_ok=True)
|
||
|
for dll in dll_list:
|
||
|
if not os.path.exists(os.path.join(destbin, dll)):
|
||
|
# Do not overwrite existing files.
|
||
|
for srcdir in srcdirs:
|
||
|
full_file_name = os.path.join(srcdir, bindir, dll)
|
||
|
if os.path.isfile(full_file_name):
|
||
|
sys.stdout.write("{} (INFO): copying {} to {}\n".format(os.path.basename(__file__), full_file_name, destbin))
|
||
|
shutil.copy(full_file_name, destbin)
|
||
|
break
|
||
|
else:
|
||
|
# This should not happen. We determined that the dll is in one
|
||
|
# of the srcdirs.
|
||
|
sys.stderr.write("Missing DLL: {}\n".format(dll))
|
||
|
sys.exit(1)
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
parser = argparse.ArgumentParser()
|
||
|
parser.add_argument('--debug', dest='debug',
|
||
|
choices=['debug-only', 'debug-run', 'run-only'], default = 'run-only')
|
||
|
parser.add_argument('--output-dll-list', dest='dll_file', action = 'store', default = None)
|
||
|
parser.add_argument('bin')
|
||
|
parser.add_argument('src', nargs='+')
|
||
|
parser.add_argument('dest')
|
||
|
args = parser.parse_args(sys.argv[1:])
|
||
|
|
||
|
main(args.bin, args.src, args.dest, args.debug, args.dll_file)
|