(17)python开发经验 --- Python查找dll exe依赖缺失
文章目录
- @[toc]
- 概述
- 适用于windows
- 适用于linux
文章目录
- @[toc]
- 概述
- 适用于windows
- 适用于linux
| 更多精彩内容 |
|---|
| 👉内容导航 👈 |
| 👉Qt开发 👈 |
| 👉python开发 👈 |
概述
使用Python调用可执行程序或者dll动态库时如果依赖出现缺失则会导致调用失败;
在调用前可通过代码检查依赖是否完整,以确保成功调用,或者调用失败自动给出异常详细。
- 在
load_dll_with_dependency_check函数中尝试加载指定路径的 DLL 文件,并检查其依赖项是否缺失。- 在
check_exe_missing_dependencies函数中检查指定 exe 文件的缺失依赖项。
适用于windows

pip install pefile
import ctypes
import pefile
import os
from functools import lru_cachedef is_dll_in_path(dll_name):"""检查系统 PATH 环境变量中是否存在指定名称的 DLL 文件。参数:dll_name (str): 要查找的 DLL 名称(不带路径)。返回:bool: 如果找到该 DLL 返回 True,否则返回 False。"""path_env = os.getenv("PATH")if not path_env:return False# 分割 PATH 为多个路径paths = path_env.split(os.pathsep)# 自动补全 .dll 后缀,避免用户遗漏if not dll_name.lower().endswith(".dll"):dll_name += ".dll"for path in paths:full_path = os.path.join(path, dll_name)if os.path.exists(full_path):return Truereturn False@lru_cache(maxsize=128) # 缓存最多 128 个 DLL 查找结果
def cached_is_dll_in_path(dll_name):"""缓存的结果,避免重复查找相同 DLL,提升性能。参数:dll_name (str): 要查找的 DLL 名称。返回:bool: 是否在系统 PATH 中找到该 DLL。"""return is_dll_in_path(dll_name)def scan_paths_for_dll(dll_name, paths):"""在指定路径列表中查找是否存在某个 DLL 文件。参数:dll_name (str): 要查找的 DLL 名称。paths (list): 包含路径的列表。返回:bool: 如果在任意路径中找到该 DLL 返回 True,否则返回 False。"""if not dll_name.lower().endswith(".dll"):dll_name += ".dll"for path in paths:full_path = os.path.join(path, dll_name)if os.path.exists(full_path):return Truereturn Falsedef get_missing_dependencies(dll_path, search_paths=None):"""解析 PE 文件(DLL 或 EXE),找出其依赖的其他 DLL,并检查这些依赖是否缺失。参数:dll_path (str): 目标 DLL 文件路径。search_paths (str or list): 附加的依赖查找路径。返回:list: 缺失的 DLL 名称列表。"""try:pe = pefile.PE(dll_path)except pefile.PEFormatError as e:raise ValueError(f"Invalid PE file format: {e}") from eexcept Exception as e:raise RuntimeError(f"Failed to parse PE file: {e}") from emissing = set()default_paths = ["."] # 默认从当前目录查找依赖# 确保 search_paths 是列表形式if isinstance(search_paths, str):search_paths = [search_paths]elif search_paths is None:search_paths = []all_search_paths = default_paths + list(search_paths)# 检查是否有导入表(即依赖项)if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):for entry in pe.DIRECTORY_ENTRY_IMPORT:try:dll_name = entry.dll.decode('utf-8')except UnicodeDecodeError:continue # 忽略无法解码的 DLL 名称# 统一大小写进行比较,增强兼容性normalized_name = os.path.normcase(dll_name)# 查找当前路径和指定路径中的 DLLif scan_paths_for_dll(normalized_name, all_search_paths) or cached_is_dll_in_path(normalized_name):continuemissing.add(dll_name)return list(missing)def load_dll_with_dependency_check(dll_path, search_paths=None):"""尝试加载指定路径的 DLL 文件,并检查其依赖项是否缺失。参数:dll_path (str): 要加载的 DLL 文件路径。search_paths (str or list): 可选,用于查找依赖 DLL 的额外搜索路径(可以是字符串或列表)。返回:bool: 加载成功返回 True,失败返回 False。"""if not os.path.exists(dll_path):print(f"❌ {dll_path} does not exist.")return Falsetry:# 使用 ctypes.CDLL 加载 DLL 文件ctypes.CDLL(dll_path)print(f"✅ Successfully loaded {dll_path}")return Trueexcept OSError as e:# 如果加载失败,捕获异常并输出错误信息print(f"❌ Failed to load {dll_path}: {e}")# 获取缺失的依赖项missing_deps = get_missing_dependencies(dll_path, search_paths)if missing_deps:print("Missing dependencies:")for dep in missing_deps:print(f" ❌ {dep}")else:print("No missing dependencies found. Possible reason: wrong architecture or runtime error.")return Falsedef check_exe_missing_dependencies(exe_path, search_paths=None):"""检查指定 exe 文件的缺失依赖项。参数:exe_path (str): 要检查的 exe 文件路径。search_paths (str or list): 可选,用于查找依赖 DLL 的额外搜索路径。"""if not os.path.exists(exe_path):print(f"❌ {exe_path} does not exist.")return# 获取缺失的依赖项missing_deps = get_missing_dependencies(exe_path, search_paths)print(missing_deps)if missing_deps:print(f"❌ Failed to load {exe_path}")print("Missing dependencies:")for dep in missing_deps:print(f" ❌ {dep}")# 示例调用
dll_path = 'E:/lib/libusb/VS2017/MS64/dll/libusb-1.0.dll'
load_dll_with_dependency_check(dll_path, "E:/lib/libusb/VS2017/MS64/dll")
check_exe_missing_dependencies("D:/Typora/Typora.exe", "D:/Typora/")
适用于linux
pip install pyelftools lief
import os
import subprocess
from functools import lru_cache
from elftools.elf.elffile import ELFFile
from elftools.elf.dynamic import DynamicSection
from elftools.common.exceptions import ELFError
import ctypes# 常量定义
LDCONFIG_PATH = "/sbin/ldconfig"
DEFAULT_CACHE_SIZE = 128def is_so_in_ldpath(so_name):"""检查系统 LD_LIBRARY_PATH 或 /etc/ld.so.cache 是否存在指定名称的 .so 文件。参数:so_name (str): 要查找的 .so 名称(不带路径)。返回:bool: 如果找到该 .so 返回 True,否则返回 False。"""if '.so' not in so_name:so_name += ".so"# 检查 LD_LIBRARY_PATHld_path = os.getenv("LD_LIBRARY_PATH", "")paths = ld_path.split(os.pathsep) if ld_path else []for path in paths:full_path = os.path.join(path, so_name)if os.path.exists(full_path):return True# 尝试用 `ldconfig -p` 查询全局缓存(更准确)try:output = subprocess.check_output([LDCONFIG_PATH, "-p"]).decode()return so_name in outputexcept Exception:passreturn False@lru_cache(maxsize=DEFAULT_CACHE_SIZE)
def cached_is_so_in_path(so_name):return is_so_in_ldpath(so_name)def scan_paths_for_so(so_name, paths):"""在指定路径列表中查找是否存在某个 .so 文件。参数:so_name (str): 要查找的 .so 名称。paths (list): 包含路径的列表。返回:bool: 如果在任意路径中找到该 .so 返回 True,否则返回 False。"""if not so_name.endswith(".so"):so_name += ".so"for path in paths:if not os.path.isdir(path):print(f"⚠️ Skipping invalid path: {path}")continuefull_path = os.path.join(path, so_name)if os.path.exists(full_path):return Truereturn Falsedef get_missing_dependencies_elf(elf_path, search_paths=None):"""解析 ELF 文件(.so 或可执行文件),找出其依赖的其他 .so,并检查是否缺失。参数:elf_path (str): 目标 ELF 文件路径。search_paths (str or list): 附加的依赖查找路径。返回:list: 缺失的 .so 名称列表。"""missing = set()# 统一 search_paths 为列表if isinstance(search_paths, str):search_paths = [search_paths]elif search_paths is None:search_paths = []all_search_paths = list(set(["."] + search_paths))try:with open(elf_path, "rb") as f:elf = ELFFile(f)# 遍历动态段查找 NEEDED 项for section in elf.iter_sections():if isinstance(section, DynamicSection):for tag in section.iter_tags():if tag.entry.d_tag == 'DT_NEEDED':so_name = tag.needed# 查找当前路径和指定路径中的 .soif scan_paths_for_so(so_name, all_search_paths) or cached_is_so_in_path(so_name):continuemissing.add(so_name)except ELFError as e:print(f"❌ Invalid ELF file: {e}")return []except Exception as e:print(f"❌ Unexpected error while parsing ELF: {e}")return []return list(missing)def load_so_with_dependency_check(so_path, search_paths=None):"""尝试加载指定路径的 .so 文件,并检查其依赖项是否缺失。参数:so_path (str): 要加载的 .so 文件路径。search_paths (str or list): 可选,用于查找依赖 .so 的额外搜索路径。返回:bool: 加载成功返回 True,失败返回 False。"""try:ctypes.CDLL(so_path)print(f"✅ Successfully loaded {so_path}")return Trueexcept OSError as e:print(f"❌ Failed to load {so_path}: {e}")missing_deps = get_missing_dependencies_elf(so_path, search_paths)if missing_deps:print("Missing dependencies:")for dep in missing_deps:print(f" ❌ {dep}")else:print("No missing dependencies found. Possible reason: wrong architecture or runtime error.")return Falseif __name__ == "__main__":so_path = 'Release/libburn.so'load_so_with_dependency_check(so_path, "Release")
