Python中如何解决PyMarshal_ReadObjectFromFile导致的segmentation fault
如题 我的 c++代码:
#include <Python.h>
#include <iostream>
#include <marshal.h>
using namespace std;
int main(){
PyObject *pName, *pModule, *pFunc;
PyObject *pArgs, *pValue;
//cout<<233<<endl;
Py_Initialize();
FILE *fff = fopen("./a.pyc","br");
pModule = PyMarshal_ReadObjectFromFile(fff);
//Py_DECREF(pModule);
//cout<<PyCode_GetNumFree(pModule);
//PyImport_ExecCodeModule("asd",pModule);
pValue = PyLong_FromLong(123);
cout<<PyCode_Check(pModule);
return 0;
}
只要运行了 PyCode_Check(pModule) 就会导致段错误 进一步测试发现 只要调用 pModule->ob_type 就会导致段错误 同时 a.pyc 文件在 python 中使用 marshal.load 读取 运行 是完全正常的
环境: macos 10.13 测试过 homebrew 的 python 以及 miniconda 的 python 均有这个问题
该如何解决?
Python中如何解决PyMarshal_ReadObjectFromFile导致的segmentation fault
2 回复
这问题我遇到过,PyMarshal_ReadObjectFromFile 导致的段错误通常是因为读取的 .pyc 文件损坏或不兼容。
核心原因有这几个:
- Python版本不匹配:用 Python 3.8 生成的 .pyc 文件,用 Python 3.11 去读取
- 文件损坏:.pyc 文件在传输或存储过程中损坏
- 手动修改了字节码:直接编辑了 .pyc 文件内容
最直接的解决方案:
import marshal
import struct
import sys
def safe_read_pyc_file(filepath):
"""安全读取 .pyc 文件,避免 segmentation fault"""
try:
with open(filepath, 'rb') as f:
# 读取 magic number(Python版本标识)
magic = f.read(4)
if len(magic) < 4:
raise ValueError("文件太小,不是有效的 .pyc 文件")
# 读取时间戳(Python 3.7+)
if sys.version_info >= (3, 7):
timestamp = f.read(8)
if len(timestamp) < 8:
raise ValueError("文件格式错误")
# 尝试读取字节码
data = f.read()
if not data:
raise ValueError("文件为空或已损坏")
# 使用 marshal 加载
code_obj = marshal.loads(data)
return code_obj
except (EOFError, ValueError, TypeError) as e:
print(f"文件读取失败: {e}")
return None
except Exception as e:
print(f"未知错误: {e}")
return None
# 使用示例
code_obj = safe_read_pyc_file("your_file.pyc")
if code_obj:
print("成功加载字节码对象")
如果问题持续存在,按这个顺序排查:
- 重新生成 .pyc 文件
# 删除所有 .pyc 文件
find . -name "*.pyc" -delete
find . -name "__pycache__" -type d -exec rm -rf {} +
# 强制重新编译
python -m py_compile your_script.py
# 或
python -c "import py_compile; py_compile.compile('your_script.py')"
- 检查 Python 版本兼容性
import importlib.util
import sys
def check_pyc_compatibility(filepath):
"""检查 .pyc 文件是否与当前 Python 版本兼容"""
try:
spec = importlib.util.spec_from_file_location("module", filepath)
if spec and spec.loader:
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
print("文件兼容,可以正常导入")
return True
except ImportError as e:
print(f"导入失败,可能版本不兼容: {e}")
return False
- 验证文件完整性
import hashlib
def verify_file_integrity(original_py, pyc_file):
"""通过对比源文件和 .pyc 的哈希来验证完整性"""
with open(original_py, 'rb') as f:
py_hash = hashlib.md5(f.read()).hexdigest()
with open(pyc_file, 'rb') as f:
# 跳过 magic number 和时间戳
if sys.version_info >= (3, 7):
f.read(12) # 4字节magic + 8字节时间戳
else:
f.read(4) # 4字节magic
pyc_data = f.read()
pyc_hash = hashlib.md5(pyc_data).hexdigest()
print(f"源文件哈希: {py_hash}")
print(f".pyc 数据哈希: {pyc_hash}")
根本解决方案:
别直接操作 .pyc 文件,用标准导入机制。如果必须处理字节码,用 compile() 和 marshal.dumps() 而不是直接读写文件。
总结:优先重新编译 .pyc 文件,检查版本兼容性。
docker 内运行就能解决这个问题
同时还有个坑: 网上都说 pyc 文件前 8 字节是文件头要去掉 但是 python3.7 中是 16 字节

