编程与调试 -- PyInstaller & Extractor

PyInstaller 是开源的,可以对其深度订制(改进 Bootloader)。 PyInstaller Manual

遇到的最大问题就是启动慢的问题。 每次都会生成临时文件夹并释放文件,有点烦人。如果崩溃了,临时文件就残留了。本文尝试修改启动器,改掉这个问题。 This can be done using the --runtime-tmpdir option.

我的调整:hawkhai / pyinstaller (还未完成)

Why is my Python Tkinter program lagging?

2020.2 User interface very laggy/slow response (macOS …

https://stackoverflow.com/questions/68954466/very-slow-window-refresh-update-via-tkinter-in-macos-python-pysimplegui-tki

I recommend replacing calls to .update() with .updateidletasks(). update not only causes the window to refresh, but it has to process all pending events of all types, and won't return until all events are processed. If, while processing an event this code is called again, you end up with event loops nested inside of event loops.

updateidletasks only process the events in the idle queue, which is mostly screen updates and jobs queue with after_idle, greatly reducing the chance you will end up with nested event loops.

https://www.quora.com/Why-is-my-Python-Tkinter-program-lagging

升级 pyinstaller

pip install --upgrade pyinstaller

运行时检测

#!/usr/bin/env python3
import sys, os
frozen = 'not'
if getattr(sys, 'frozen', False):
    # we are running in a bundle
    frozen = 'ever so'
    bundle_dir = sys._MEIPASS
else:
    # we are running in a normal Python environment
    bundle_dir = os.path.dirname(os.path.abspath(__file__))
print( 'we are', frozen, 'frozen' )
print( 'bundle dir is', bundle_dir )
print( 'sys.argv[0] is', sys.argv[0] )
print( 'sys.executable is', sys.executable )
print( 'os.getcwd is', os.getcwd() )

Bootloader

这里 描述详细的启动过程。


Structure of the ZlibArchive

Structure of the CArchive

Structure of the Self Extracting Executable

双进程实现(除了 Windows one-folder 模式)。

  1. 主进程:bootloader 启动,在准备工作。
    • 如果是 one-file 模式,释放文件到:temppath/_MEIxxxxxx
    • 修改大量环境变量。
    • 设置 handle signals,便于两个进程通信。
    • 运行子进程。
    • 等待子进程结束。
    • 如果是 one-file 模式,删除清理 temppath/_MEIxxxxxx
  2. 子进程。
    • 如果是 Windows,设置 激活上下文
    • 加载 Python 动态库。
    • 初始化 Python 解释器:set sys.path, sys.prefix, sys.executable.
    • Run python code.

编译

python ./waf all
python ./waf all --target-arch=32bit

改造计划

不能对现有流程造成影响,保持将来的升级能力。 把附加配置通过后期资源打到最终 pe 文件里面。 然后 Bootloader 读取配置。

  1. 指定释放临时目录。 pyi_launch_need_to_extract_binaries
    • swprintf(prefix, 16, L"_MEI%d", getpid()); – 这里一个固定的版本号接上去。
    • pyi_get_temp_path pyi_create_temp_path
  2. 如果发现临时目录文件存在了,就跳过。 pyi_launch_extract_binaries
    • 怎么判断文件完整? pyi_arch_extract2fs(ARCHIVE_STATUS *status, TOC *ptoc)
  3. 进程结束,不要清理文件夹(就需要每个版本指定的临时文件夹唯一)。 pyi_remove_temp_path

加密与解密

from

python pyinstxtractor.py xx.exe
uncompyle6

set CL=-FI"Full-Path\stdint.h" (use real value for Full-Path for the environment)
pip install pycrypto
pyinstaller.exe -F --key 123456 xxx.py

PyInstaller Extractor

PyInstaller Extractor

conda or pyenv

$ conda create -n my-py3.6-environment python=3.6
$ conda activate my-py3.6-environment
pipenv install

[[source]]
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
verify_ssl = true
name = "pypi"

# 把 pyinstaller 安装到开发环境中
pipenv install pyinstaller --dev

# 进入虚拟环境
pipenv shell

import os
import sys
os.chdir(os.path.dirname(__file__))
sys.path.append("..")
import settings

# 直接运行 js 代码
import js2py

context = js2py.EvalJs()
with open("./ids-encrypt.js") as f:
    js_content = f.read()

def encryptAES(data, salt):
    # 执行整段 JS 代码
    context.execute(js_content)
    result = context.encryptAES(data, salt)
    return result

A cross-version Python bytecode decompiler

# 通过清华镜像源,下载快
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple uncompyle
uncompyle test.pyc > test.py

代码加密

https://pyob.oxyry.com/ https://blog.csdn.net/ir0nf1st/article/details/61650984

Pyinstaller 加密打包应用的示例代码

抓取真实 api 后

def obfuscation(py_file, save_path):
    print("读取文件:", py_file)
    with open(py_file, "r", encoding="utf-8") as f:
        py_content = f.read()

    print("进行混淆中 ...")
    url = "https://pyob.oxyry.com/obfuscate"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
        "Referer": "http://pyob.oxyry.com/",
        "content-type": "application/json",
        "cookie": "_ga=GA1.2.1306886713.1588752647; _gid=GA1.2.46944674.1588899118"
    }
    data = json.dumps({
        "append_source": "false",
        "preserve": "",
        "remove_docstrings": "true",
        "rename_default_parameters": "false",
        "rename_nondefault_parameters": "true",
        "source": py_content
    })
    result = json.loads(requests.post(url, data=data, headers=headers).text)["dest"]
    result = "# cython: language_level=3\n" + result
    print("混淆成功 ...")

    with open(save_path, "w", encoding="utf-8") as f:
        f.write(result)
    print("混淆文件已写入 {}\n".format(save_path))

if __name__ == '__main__':
    obfuscation("my.py", "../ 混淆 /my.py")
    obfuscation("approach.py", "../ 混淆 /approach.py")

编译 pyd

build_pyd.py

from distutils.core import setup
from Cython.Build import cythonize

setup(
    name='any words.....',
    ext_modules=cythonize(["my.py","approach.py" ])
)

执行打包

import json
import os
# 清理旧 pyd 文件
import uuid
import requests

def clearPyd():
    for file in os.listdir():
        if ".pyd" in file:
            print("删除 .pyd:", file)
            os.remove(file)
    print("***********************************************************************")

# 构建 pyd 文件
def buildPyd():
    os.system("python build_pyd.py build_ext --inplace")

# 重命名 pyd 文件
def renamePyd():
    print("***********************************************************************")
    for file in os.listdir():
        if ".pyd" in file:
            print("重新命名 pyd:", file)
            os.rename(file, file[:file.find(".")] + ".pyd")
    for file in os.listdir():
        if ".c" in file:
            print("删除 .c 文件:", file)
            os.remove(file)
    print("***********************************************************************")

# 执行打包
def pyinstaller(key, ico):
    os.system("pyinstaller -F --key {} -i {} main.py".format(key, ico))

# 删除 bulid 和 spec 文件
def clearBuildAndSpec():
    import shutil
    shutil.rmtree('build')
    print("删除 bulid 文件夹")
    os.remove("main.spec")
    print("删除 spec 文件")

if __name__ == '__main__':
    clearPyd() # 清理旧 pyd 文件
    buildPyd() # 构建 pyd 文件
    renamePyd() # 重命名 pyd 文件
    pyinstaller(uuid.uuid4()[0:16], "1.ico") # 执行打包
    clearPyd() # 清理 pyd 文件
    clearBuildAndSpec() # 删除 bulid 和 spec 文件

Windows cmd: piping python 3.5 py file results works but pyinstaller exe's leads to UnicodeEncodeError

sys.stdout.buffer.write('\u5000'.encode('utf8'))

https://stackoverflow.com/questions/44780476/windows-cmd-piping-python-3-5-py-file-results-works-but-pyinstaller-exes-leads https://github.com/chriskiehl/Gooey/issues/520 https://blog.csdn.net/hello_crayon/article/details/80940390

# determine if application is a script file or frozen exe
if getattr(sys, 'frozen', False):
    PATH_CUR = os.path.dirname(sys.executable)
elif __file__:
    PATH_CUR = os.path.dirname(__file__)
sys.stdout.reconfigure(encoding='utf-8')
sys.stdin.reconfigure(encoding='utf-8')

import codecs
if sys.stdout.encoding != 'UTF-8':
    sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')
if sys.stderr.encoding != 'UTF-8':
    sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict')

windows pipe

https://www.cnblogs.com/MMLoveMeMM/articles/3811166.html

其它问题

貌似不同 python 版本打包出来的文件不能混用一个文件夹。 https://juejin.cn/s/python%20failed%20to%20execute%20script%20pyi_rth_win32comgenpy loader2.exe 多余的文件要重命名。

simple_launcher

https://bitbucket.org/vinay.sajip/simple_launcher


参考资料快照
参考资料快照

本文短链接:
If you have any questions or feedback, please reach out .