跳转到主要内容

解析字节缓存.pyc文件

项目描述

序列化解析器

marshal 是Python内部对象序列化,用于将代码对象序列化到*.pyc文件中。

在可预见的未来,这种无用的脑力训练应该能帮助我解决无法重现的.pyc文件问题。

安装

Marshal解析器可在PyPI上使用

pip install marshalparser

以及作为Fedora RPM包

sudo dnf install python3-marshalparser

解析器操作

以人类可读的方式打印解析内容

当前版本的解析器创建了一个可读的解析对象列表,其中包含对象开始的字节信息以及它们的内容

$ python3 -m marshalparser -p test/pure_marshal/list_of_simple_objects.dat
n=0/0x0 byte=(b'5b', b'[', 0b1011011) TYPE_LIST REF[0]
  tuple/list/set size: 4
  n=5/0x5 byte=(b'54', b'T', 0b1010100) TYPE_TRUE
  result=True, type=<class 'bool'>
  n=6/0x6 byte=(b'46', b'F', 0b1000110) TYPE_FALSE
  result=False, type=<class 'bool'>
  n=7/0x7 byte=(b'4e', b'N', 0b1001110) TYPE_NONE
  result=None, type=<class 'NoneType'>
  n=8/0x8 byte=(b'2e', b'.', 0b101110) TYPE_ELLIPSIS
  result=Ellipsis, type=<class 'ellipsis'>
result=[True, False, None, Ellipsis], type=<class 'list'>

对于 .pyc 文件也是如此,但它们更复杂,因为它们包含代码对象,这些对象被报告为字典

$ python3 -m marshalparser -p test/python_stdlib/3.9/doctest.cpython-39.opt-1.pyc | head -n 30
n=16/0x10 byte=(b'63', b'c', 0b1100011) TYPE_CODE REF[0]
  n=41/0x29 byte=(b'73', b's', 0b1110011) TYPE_STRING
  result=b'd\x00Z\x00d\x01Z\x01g\x00d\x02\xa2\x01Z\x02d\x03d\x04l\x03Z\x03d\x03d\x04l\x04Z\x04d\x03d\x04l\x05Z\x05d\x03d\x04l\x06Z\x06d\x03d\x04l\x07Z\x07d\x03d\x04l\x08Z\x08d\x03d\x04l\tZ\td\x03d\x04l\nZ\nd\x03d\x04l\x0bZ\x0bd\x03d\x04l\x0cZ\x0cd\x03d\x05l\rm\x0eZ\x0e\x01\x00d\x03d\x06l\x0fm\x10Z\x10\x01\x00e\x10d\x07d\x08\x83\x02Z\x11i\x00Z\x12d\td\n\x84\x00Z\x13e\x13d\x0b\x83\x01Z\x14e\x13d\x0c\x83\x01Z\x15e\x13d\r\x83\x01Z\x16e\x13d\x0e\x83\x01Z\x17e\x13d\x0f\x83\x01Z\x18e\x13d\x10\x83\x01Z\x19e\x14e\x15B\x00e\x16B\x00e\x17B\x00e\x18B\x00e\x19B\x00Z\x1ae\x13d\x11\x83\x01Z\x1be\x13d\x12\x83\x01Z\x1ce\x13d\x13\x83\x01Z\x1de\x13d\x14\x83\x01Z\x1ee\x13d\x15\x83\x01Z\x1fe\x1be\x1cB\x00e\x1dB\x00e\x1eB\x00e\x1fB\x00Z d\x16Z!d\x17Z"d\x18d\x19\x84\x00Z#drd\x1bd\x1c\x84\x01Z$d\x1dd\x1e\x84\x00Z%d\x1fd \x84\x00Z&dsd"d#\x84\x01Z\'d$d%\x84\x00Z(G\x00d&d\'\x84\x00d\'e\x0e\x83\x03Z)d(d)\x84\x00Z*d*d+\x84\x00Z+d,d-\x84\x00Z,G\x00d.d/\x84\x00d/e\x08j-\x83\x03Z.d0d1\x84\x00Z/G\x00d2d3\x84\x00d3\x83\x02Z0G\x00d4d5\x84\x00d5\x83\x02Z1G\x00d6d7\x84\x00d7\x83\x02Z2G\x00d8d9\x84\x00d9\x83\x02Z3G\x00d:d;\x84\x00d;\x83\x02Z4G\x00d<d=\x84\x00d=\x83\x02Z5G\x00d>d?\x84\x00d?e6\x83\x03Z7G\x00d@dA\x84\x00dAe6\x83\x03Z8G\x00dBdC\x84\x00dCe4\x83\x03Z9d\x04a:dtdFdG\x84\x01Z;dDd\x04d\x04d\x04d\x04dDd\x03d\x04dEe2\x83\x00d\x04f\x0bdHdI\x84\x01Z<dudKdL\x84\x01Z=d\x03a>dMdN\x84\x00Z?G\x00dOdP\x84\x00dPe\x0cj@\x83\x03ZAG\x00dQdR\x84\x00dReA\x83\x03ZBG\x00dSdT\x84\x00dTe\x0cjC\x83\x03ZDdvdUdV\x84\x01ZEG\x00dWdX\x84\x00dXeA\x83\x03ZFdDd\x04d\x04e2\x83\x00d\x04f\x05dYdZ\x84\x01ZGd[d\\\x84\x00ZHd]d^\x84\x00ZId_d`\x84\x00ZJdwdadb\x84\x01ZKdxdcdd\x84\x01ZLdydedf\x84\x01ZMG\x00dgdh\x84\x00dh\x83\x02ZNeNdidjdkdldmdn\x9c\x06ZOdodp\x84\x00ZPeQdqk\x02\x90\x03r2e\n\xa0ReP\x83\x00\xa1\x01\x01\x00d\x04S\x00', type=<class 'bytes'>
  n=868/0x364 byte=(b'29', b')', 0b101001) TYPE_SMALL_TUPLE
    Small tuple size: 122
    n=870/0x366 byte=(b'61', b'a', 0b1100001) TYPE_ASCII
    result=b'Module doctest -- a framework for running examples in docstrings.\n\nIn simplest use, end each module M to be tested with:\n\ndef _test():\n    import doctest\n    doctest.testmod()\n\nif __name__ == "__main__":\n    _test()\n\nThen running the module as a script will cause the examples in the\ndocstrings to get executed and verified:\n\npython M.py\n\nThis won\'t display anything unless an example fails, in which case the\nfailing example(s) and the cause(s) of the failure(s) are printed to stdout\n(why not stderr? because stderr is a lame hack <0.2 wink>), and the final\nline of output is "Test failed.".\n\nRun it with the -v switch instead:\n\npython M.py -v\n\nand a detailed report of all examples tried is printed to stdout, along\nwith assorted summaries at the end.\n\nYou can force verbose mode by passing "verbose=True" to testmod, or prohibit\nit by passing "verbose=False".  In either of those cases, sys.argv is not\nexamined by testmod.\n\nThere are a variety of other ways to run doctests, including integration\nwith the unittest framework, and support for running non-Python text\nfiles containing doctests.  There are also many ways to override parts\nof doctest\'s default behaviors.  See the Library Reference Manual for\ndetails.\n', type=<class 'bytes'>
    n=2096/0x830 byte=(b'7a', b'z', 0b1111010) TYPE_SHORT_ASCII
    result=b'reStructuredText en', type=<class 'bytes'>
    n=2117/0x845 byte=(b'29', b')', 0b101001) TYPE_SMALL_TUPLE
      … etc …

未使用的 FLAG_REF

新版本的解析器还可以生成未使用 FLAG_REF 的列表——具有启用引用可能性的对象,但未使用该功能。

这里我们使用了之前相同的例子,所以你可以尝试手动在本页顶部找到未使用的 FLAG_REF

python3 -m marshalparser -u test/pure_marshal/list_of_simple_objects.dat
Unused FLAG_REFs:
0 - Flag_ref(byte=0, type='TYPE_LIST', content=[True, False, None, Ellipsis], usages=0)

如果我们能够检测到它,我们也可以修复它。使用选项 -f,Marshal解析器生成一个新文件,其中删除了所有未使用的 FLAG_REF,并重新计算了所有有用的引用。

# Fix it
$ python3 -m marshalparser -f test/pure_marshal/list_of_simple_objects.dat
# Check the fixed file
$ python3 -m marshalparser -u test/pure_marshal/list_of_simple_objects.fixed.dat
# Print it
$ python3 -m marshalparser -p test/pure_marshal/list_of_simple_objects.fixed.dat
n=0/0x0 byte=(b'5b', b'[', 0b1011011) TYPE_LIST
  tuple/list/set size: 4
  n=5/0x5 byte=(b'54', b'T', 0b1010100) TYPE_TRUE
  result=True, type=<class 'bool'>
  n=6/0x6 byte=(b'46', b'F', 0b1000110) TYPE_FALSE
  result=False, type=<class 'bool'>
  n=7/0x7 byte=(b'4e', b'N', 0b1001110) TYPE_NONE
  result=None, type=<class 'NoneType'>
  n=8/0x8 byte=(b'2e', b'.', 0b101110) TYPE_ELLIPSIS
  result=Ellipsis, type=<class 'ellipsis'>
result=[True, False, None, Ellipsis], type=<class 'list'>

也可以使用 -fo 覆盖现有文件。

测试

测试使用 pytest,/test/python_stdlib/3.X 包含大约一百个来自 Python 标准库(python3-libs 或 python36 等)的随机 pyc 文件,这些文件是 Fedora 中每个支持的 Python 版本的 RPM 包。

测试检查解析器是否能够解析/修复 pyc 文件,然后检查解包后的代码对象在两个文件(原始和修复)中相同。

测试确保运行(例如)Python 3.9 的 MarshalParser 能够解析和修复其他支持 Python 版本的 pyc 文件。但为了检查原始和修复的 pyc 文件是否相同,我们需要使用编译文件的 Python 版本运行 marshal_content_check.py

Python 支持

代码已在 Python 3.6+ 上进行了测试,并且它还能够修复由 Python 3.6+ 产生的 pyc 文件。Python 3.6 本身需要 dataclasses

支持的对象类型

  • ✓ TYPE_NULL(作为 TYPE_DICT 的空操作符)
  • ✓ TYPE_NONE
  • ✓ TYPE_FALSE
  • ✓ TYPE_TRUE
  • ✓ TYPE_STOPITER
  • ✓ TYPE_ELLIPSIS
  • ✓ TYPE_INT
  • ✘ TYPE_INT64(不再生成)
  • ✘ TYPE_FLOAT(仅在 marshal 版本 1 中)
  • ✓ TYPE_BINARY_FLOAT
  • ✘ TYPE_COMPLEX(仅在 marshal 版本 1 中)
  • ✓ TYPE_BINARY_COMPLEX
  • ✓ TYPE_LONG(解析为数字,但不会重建为 PyLong)
  • ✓ TYPE_STRING
  • ✓ TYPE_INTERNED
  • ✓ TYPE_REF
  • ✓ TYPE_TUPLE
  • ✓ TYPE_LIST
  • ✓ TYPE_DICT
  • ✓ TYPE_CODE
  • ✓ TYPE_UNICODE
  • ? TYPE_UNKNOWN(不知道如何测试未知字节对象作为字节对象)
  • ✓ TYPE_SET
  • ✓ TYPE_FROZENSET
  • ✓ FLAG_REF(识别为所有复杂类型的标志)
  • ✓ TYPE_ASCII
  • ✓ TYPE_ASCII_INTERNED
  • ✓ TYPE_SMALL_TUPLE
  • ✓ TYPE_SHORT_ASCII
  • ✓ TYPE_SHORT_ASCII_INTERNED

参考

项目详情


下载文件

下载适合您的平台的文件。如果您不确定要选择哪个,请了解更多关于 安装包 的信息。

源分发

marshalparser-0.3.4.tar.gz (14.8 kB 查看哈希值)

构建分发

marshalparser-0.3.4-py3-none-any.whl (12.5 kB 查看哈希值)

上传时间 Python 3

支持者