• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

SOC/IP验证工程师

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

PyInstaller 调试详细指南

PyInstaller 调试详细指南

PyInstaller 提供了多种调试工具和选项来帮助解决打包过程中的问题。以下是完整的调试方法和技巧:

1. 基本调试选项

启用详细输出

# 显示详细的构建信息
pyinstaller --log-level DEBUG main.py

# 或者使用简写
pyinstaller -d all main.py

日志级别选项

# 不同详细程度的日志
pyinstaller --log-level TRACE main.py    # 最详细
pyinstaller --log-level DEBUG main.py    # 调试信息
pyinstaller --log-level INFO main.py     # 一般信息(默认)
pyinstaller --log-level WARN main.py     # 只显示警告
pyinstaller --log-level ERROR main.py    # 只显示错误

2. 调试模式打包

启用调试模式

# 启用所有调试功能
pyinstaller --debug all main.py

# 启用特定调试功能
pyinstaller --debug imports main.py     # 调试导入
pyinstaller --debug bootloader main.py  # 调试引导程序

调试模式的具体效果

  • 不压缩:生成的 EXE 文件不压缩,便于分析
  • 保留临时文件:不清理构建过程中的临时文件
  • 详细日志:输出更详细的处理信息
  • 包含调试符号:保留调试信息

3. 分析导入依赖

生成导入分析报告

# 生成导入分析 JSON 文件
pyinstaller --json analysis.json main.py

# 分析特定模块的导入
pyinstaller --log-level DEBUG --debug imports main.py

手动分析依赖树

# 使用 pip 检查依赖
pip show package_name

# 使用 Python 检查模块路径
python -c "import module; print(module.__file__)"

4. 运行时调试

保留控制台窗口

# 即使 GUI 程序也显示控制台
pyinstaller --console main.py

# 或者在代码中强制显示控制台
import sys
sys.stdout = open('stdout.log', 'w')
sys.stderr = open('stderr.log', 'w')

添加运行时调试代码

import sys
import os
import traceback

def setup_debugging():
    """设置调试环境"""
    # 创建调试日志文件
    debug_log = open('debug.log', 'w', encoding='utf-8')
    
    # 重定向标准输出和错误
    sys.stdout = debug_log
    sys.stderr = debug_log
    
    # 设置异常钩子
    def exception_handler(exc_type, exc_value, exc_traceback):
        if issubclass(exc_type, KeyboardInterrupt):
            sys.__excepthook__(exc_type, exc_value, exc_traceback)
            return
        
        debug_log.write("未捕获的异常:\n")
        traceback.print_exception(exc_type, exc_value, exc_traceback, file=debug_log)
        debug_log.flush()
    
    sys.excepthook = exception_handler
    
    # 记录启动信息
    print(f"程序启动: {sys.argv}")
    print(f"Python 路径: {sys.path}")
    print(f"工作目录: {os.getcwd()}")
    
    return debug_log

# 在程序开始时调用
if __name__ == "__main__":
    debug_log = setup_debugging()
    try:
        # 你的主程序代码
        main()
    except Exception as e:
        print(f"程序异常: {e}")
        traceback.print_exc()
    finally:
        debug_log.close()

5. 检查打包内容

分析生成的可执行文件

# 检查 EXE 文件信息(Windows)
strings.exe dist/main.exe | findstr "python"
# 或者使用 Linux 的 strings 命令
strings dist/main | grep python

# 使用 UPX 解压(如果使用了压缩)
upx -d dist/main.exe

检查临时文件

# 在调试模式下保留临时文件
pyinstaller --debug all --noclean main.py

# 然后检查 build 目录
ls -la build/main/

6. 特定问题调试

调试隐藏导入问题

# 显示所有导入分析
pyinstaller --log-level DEBUG --debug imports main.py 2> import_debug.log

# 检查生成的导入树
cat build/main/ Analysis-00.toc

调试二进制依赖

# 检查二进制依赖
pyinstaller --log-level DEBUG main.py 2> binary_debug.log

# 使用 ldd 检查 Linux 依赖(Windows 用 depends.exe)
ldd dist/main

调试数据文件

# 详细数据文件处理日志
pyinstaller --log-level DEBUG --debug all main.py 2> data_debug.log

7. 创建调试专用版本

调试版 spec 文件

# debug.spec
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=True,  # 不打包成 archive,便于调试
)

# 添加调试钩子
def debug_runtime_hook():
    import sys
    import os
    debug_file = open(os.path.join(sys._MEIPASS, 'runtime_debug.log'), 'w')
    sys.stdout = debug_file
    sys.stderr = debug_file

runtime_hooks = [debug_runtime_hook]

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    name='main_debug',
    debug=True,  # 包含调试信息
    bootloader_ignore_signals=False,
    strip=False,  # 不剥离符号
    upx=False,    # 不使用压缩
    runtime_tmpdir=None,
    console=True,  # 总是显示控制台
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

8. 高级调试技巧

使用 PyInstaller 的测试模式

# 测试模式,不实际打包
pyinstaller --dry-run main.py

# 只分析依赖
pyinstaller --log-level DEBUG --dry-run main.py > analysis.txt

检查钩子执行

# 创建调试钩子 hooks/hook-debug.py
def pre_safe_import_module(api):
    print(f"处理模块: {api.module_name}")
    
def pre_find_module_path(api):
    print(f"查找模块路径: {api.module_name}")

内存和性能调试

# 使用内存分析工具
pip install memory_profiler

# 在代码中添加内存分析
@profile
def memory_intensive_function():
    # 你的代码
    pass

9. 常见问题诊断

诊断 "Failed to execute script" 错误

# 在代码开头添加详细错误处理
import sys
import traceback

def main():
    try:
        # 你的主程序逻辑
        pass
    except Exception as e:
        # 将错误信息写入文件
        with open('error.log', 'w') as f:
            f.write(f"错误类型: {type(e).__name__}\n")
            f.write(f"错误信息: {str(e)}\n")
            f.write("追踪信息:\n")
            traceback.print_exc(file=f)
        # 重新抛出异常
        raise

if __name__ == '__main__':
    main()

诊断导入错误

# 检查所有导入
import pkg_resources
import importlib

def check_imports():
    required_modules = [
        'numpy', 'pandas', 'PyQt5', 'os', 'sys'
    ]
    
    for module in required_modules:
        try:
            importlib.import_module(module)
            print(f"✓ {module} 导入成功")
        except ImportError as e:
            print(f"✗ {module} 导入失败: {e}")

check_imports()

10. 调试工具和脚本

自动化调试脚本

# debug_build.py
import subprocess
import sys
import os
from datetime import datetime

def debug_build():
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    log_file = f"build_debug_{timestamp}.log"
    
    print(f"开始调试构建,日志文件: {log_file}")
    
    # 构建命令
    cmd = [
        sys.executable, "-m", "PyInstaller",
        "--log-level=DEBUG",
        "--debug=all",
        "--console",
        "--noclean",
        "main.py"
    ]
    
    # 执行构建并记录日志
    with open(log_file, 'w', encoding='utf-8') as log:
        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,
            universal_newlines=True
        )
        
        # 实时输出并记录
        for line in process.stdout:
            print(line, end='')
            log.write(line)
            log.flush()
        
        process.wait()
    
    print(f"构建完成,返回码: {process.returncode}")
    
    # 分析构建结果
    if process.returncode == 0:
        print("✓ 构建成功")
        analyze_build()
    else:
        print("✗ 构建失败")
        analyze_errors(log_file)

def analyze_build():
    """分析构建结果"""
    print("\n=== 构建分析 ===")
    
    # 检查生成的文件
    if os.path.exists("dist/main.exe"):
        print("✓ 可执行文件已生成")
        file_size = os.path.getsize("dist/main.exe") / (1024 * 1024)
        print(f"  文件大小: {file_size:.2f} MB")
    else:
        print("✗ 可执行文件未生成")
    
    # 检查临时文件
    if os.path.exists("build/main"):
        print("✓ 临时文件目录存在")
        # 可以进一步分析临时文件内容

def analyze_errors(log_file):
    """分析错误日志"""
    print(f"\n=== 错误分析 ===")
    
    with open(log_file, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # 查找常见错误模式
    error_patterns = [
        ("ModuleNotFoundError", "模块未找到"),
        ("ImportError", "导入错误"),
        ("FileNotFoundError", "文件未找到"),
        ("PermissionError", "权限错误"),
    ]
    
    for pattern, description in error_patterns:
        if pattern in content:
            print(f"⚠ 发现 {description}")

if __name__ == "__main__":
    debug_build()

运行时诊断工具

# runtime_diagnostics.py
import sys
import os
import platform

def run_diagnostics():
    """运行时环境诊断"""
    print("=== 运行时环境诊断 ===")
    
    # 系统信息
    print(f"系统平台: {platform.system()} {platform.release()}")
    print(f"Python 版本: {platform.python_version()}")
    print(f"可执行文件: {sys.executable}")
    print(f"工作目录: {os.getcwd()}")
    print(f"参数: {sys.argv}")
    
    # 环境变量
    print(f"PATH: {os.environ.get('PATH', '')}")
    
    # 检查 PyInstaller 特定环境
    if hasattr(sys, '_MEIPASS'):
        print(f"PyInstaller 临时目录: {sys._MEIPASS}")
    else:
        print("未在 PyInstaller 环境中运行")
    
    # 检查模块导入
    print("\n=== 模块导入检查 ===")
    test_modules = ['os', 'sys', 'json', 'PyQt5', 'numpy']
    
    for module in test_modules:
        try:
            __import__(module)
            print(f"✓ {module}")
        except ImportError:
            print(f"✗ {module}")
    
    # 检查文件访问
    print("\n=== 文件访问检查 ===")
    test_files = ['config.ini', 'data.json']
    
    for file in test_files:
        if os.path.exists(file):
            print(f"✓ {file}")
        else:
            print(f"✗ {file}")

if __name__ == "__main__":
    run_diagnostics()

11. 调试最佳实践

  1. 逐步调试:先解决一个错误,再处理下一个
  2. 对比测试:在打包环境和开发环境分别测试
  3. 最小复现:创建最小化的测试用例来复现问题
  4. 版本控制:记录每次调试的更改和结果
  5. 文档记录:记录解决方案供以后参考

通过以上调试方法,你可以系统地诊断和解决 PyInstaller 打包过程中的各种问题。

posted on 2025-10-14 21:08  SOC验证工程师  阅读(7)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3