CPython pyc 检查¶
inspect_python_pyc / inspect-python-pyc 报告所选本地 .pyc 文件的 magic 所声明的受支持 CPython 格式和版本、其 16 字节头如何记录源标识,以及解码后的 marshal 图包含哪些嵌套代码对象。它读取文件时不运行字节码,也不需要安装 Python、活动二进制目标、Hopper、Ghidra 或 IDA。
CLI 会相对于调用方的工作目录解析相对路径。MCP 需要绝对文件系统路径并保留该精确选择:
选定的代码对象¶
完整输出仍是默认行为。要从大型报告中读取选定的代码对象,请在 MCP 请求中设置 detail: "summary"。摘要包含工件/头标识、源覆盖范围和限制计数;其 normalized_result.parent_evidence_id 标识完整保留的报告。请将该父 ID 与 inspect_analysis_view 配合使用,并传入:
{
"kind": "python-pyc",
"collection": "code_objects",
"names": ["parse_args", "parse_known_args"],
"fields": [
"name",
"qualname",
"filename",
"firstlineno",
"bytecode",
"nested_code"
],
"offset": 0,
"limit": 64
}
这是请求的 view 字段。要在首次调用中接收这些选定的事实,请在 inspect_python_pyc 中直接包含相同的 view,并同时使用 detail: "summary"。后续页使用返回的父 ID 配合 inspect_analysis_view。view 要求摘要交付;完整交付会拒绝 view 而不是忽略它。names 匹配保留的显示字符串,并返回每个同名的对象。indices 选择原始 code_objects 索引;将 names 和 indices 组合会对这些选择器取交集。省略的选择器包含所有行,而 [] 则不选择任何行。每行始终包含限定在其父 Evidence 范围内的 source_index;fields: [] 仅返回该索引。过滤保留源顺序,分页对匹配行计数。view 的 pyc.coverage 独立于页覆盖范围保留源覆盖范围。视图保留 nested_code 索引及其代码常量语义。
CLI 摘要交付需要为完整 Evidence 提供持久化目标:
添加 --view '{"kind":"python-pyc","collection":"code_objects","names":["parse_args"],"fields":["name","bytecode"]}' 以在该初始 CLI 调用中选择事实。
输出的 summary 是派生的 Evidence。将其父 ID 和保存的 bundle 传给 inspect-analysis-view --evidence-bundle module.evidence.json 以进行离线查询。MCP 视图读取保留的 Evidence,离线视图读取 bundle;两者都不要求原始 pyc 仍然存在。解析预算和完整源保留仍然适用于摘要交付。
观察¶
内联 Evidence 结果包含工件路径、SHA-256 和字节长度。header 包含:
magic,即 16 位小端字段;python_version和code_layout,映射自固定的 CPython 3.7–3.13 表(py37、py38或py311);3494 之前的 3.11 alpha magic 使用其他代码布局且不受支持。3.7.0a1/a2 的 magic 3390/3391 使用 12 字节头,不在该读取器支持的格式范围内;- 来自 PEP 552 的
flags、hash_based和check_source; - 基于哈希的 pyc 使用
source_hash_hex,基于时间戳的 pyc 使用source_mtime和source_size。
code_objects 在解码后的 marshal 图中列出每个可达的代码对象一次,顶层模块代码在前。这遵循 CPython 容器行为;它不是原始序列化代码对象记录的清单。nested_code 保存从该对象的常量可达的不同代码对象的索引,包括嵌套在元组和其他容器中的代码,以及当常量容器到达所有者自身时的所有者本身。通过 marshal 引用共享的函数按索引在每个所有者处命名一次,即使该所有者通过同一容器再次到达它。关系在 marshal 图完成后收集,因此当嵌套代码对象也引用的容器的条目仍然缺失时,不会对该容器进行摘要。每个对象报告名称、可选的 qualname、文件名、有符号 32 位 co_firstlineno 值、参数计数、flags、names、locals(3.11+ 上为 localsplusnames)、const 摘要以及字节码长度和 SHA-256。
集合和字典使用的代码对象相等性遵循所选的 CPython 格式:3.7–3.10 从 free/cell 表推导 CO_NOFREE 以进行相等性比较,而 3.11+ 比较原始 flags;原始 co_flags 仍保留在报告的事实中。较旧的布局忽略行表,而 3.11+ 包含行表和异常表,并根据精确 magic 的缓存表跳过内联缓存代码单元。报告的字节码长度和 SHA-256 仍然描述原始代码字节。这不模拟 CPython 对手工制作的 pyc 文件中特化或插桩操作码字节的去优化。
工件 SHA-256 独立于该解码对象图标识所选的确切 .pyc 字节。引用槽遵循每个标签的 CPython 行为:单例值和 TYPE_REF 即使其标签设置了 FLAG_REF 也不会保留新槽。
报告的名称是 JavaScript 显示字符串;因此不同的 CPython Unicode 名称可能具有相同的显示值,并且不能总是仅从该字段重建。代码相等性使用保留的 marshal 字符串字节,而工件 SHA-256 继续标识原始文件字节。
字节码不会被反编译。摘要中的 const 字符串可能被截断,而 marshal 遍历仍消费了原始字节;字符串摘要的 bytes 就是该 marshal 长度。整数常量报告 bits;超过 256 位时其十进制 value 为 null。非有限浮点数和复数部分使用 CPython 的 inf、-inf 和 nan 拼写;负零使用 "-0.0" 以便 JSON 保留其符号。字符串预览保留源中的孤立代理项,但绝不会在预览边界处拆分有效的代理对。字典和集合长度遵循 CPython:不可哈希的键和成员导致遍历失败,相等的集合成员只计数一次,相等的字典键保留第一个键对象并仅替换其值,代码对象仅当其所有常量都可哈希时才是有效的键和成员,null 字典值会在没有该条目的情况下结束字典。
限制与失败¶
所选文件默认限制为 16 MiB(最大 64 MiB)。Marshal 对象数量、嵌套深度和每个字符串的大小均设有上限;超出预算会直接失败,不会出现部分成功。解码限制失败是确定性的:恢复操作会指明所选字段和 CLI 选项、其有效值及其允许的最大值。如果最大值允许,请在下次调用时提高该字段;等待另一次分析或增加主机内存并不能提高这些上限。参数数量、仅限位置参数的数量、仅限关键字参数的数量和局部变量数量都必须为非负数。当所选 CPython 构造函数接受 co_stacksize 和 co_flags 时,它们会作为有符号 32 位源字段保留。CPython 3.7 以及直到 3.8.0a4 为止的 3.8 alpha 构造函数不会拒绝负的栈大小或标志;该检查由 CPython commit a9f05d69 添加。Magic 3410 存在歧义:3.8.0a4 和 3.8.0b1 都使用它,但二者位于该构造函数变更的两侧。在 magic 3410 下,负值会以明确限制的方式保留,因为头部无法识别它们是由哪个构造函数修订版生成的。Magic 3411 以及之后的其他 3.8 magic 是在那次变更之后引入的,因此它们的构造函数会拒绝负值。固定的 magic 条目可以区分这些情况;参见 CPython 3.7.0 构造函数、3.8.0a4 构造函数、3.8.0b1 构造函数 和 3.8.0b2 magic 表。
完整的 marshal 覆盖意味着所选文件中的所有字段都已解码;但这并不保证 CPython 会接受或执行该代码对象。在 3.11+ 布局中,co_code 必须覆盖整数个代码单元。每种布局都会拒绝不符合 CPython 局部表检查的参数数量(co_varnames,包括 *args / **kwargs,或 3.11+ 上的 fast locals)。这些文件是格式错误的,因此工作流不会报告完整覆盖。3.7–3.10 仍然接受奇数长度的 co_code,与那些构造函数行为一致。嵌套代码收集会以迭代方式遍历引用链,并且对于仅包装另一个容器的容器,会保留一个共享的后代列表,而不是将该列表复制到每个包装器上。集合和字典键也以相同方式遍历 tuple 和 frozenset 引用链,因此很长的 TYPE_REF 链会保持在调用栈内。存储两次的共享子项仍会将其展开后的键计入字符串预算。代码对象相等性键会记录每个名称表的长度以及每个条目的边界,因此 () 和 ("",) 保持不同,且 co_varnames、co_freevars 和 co_cellvars 保持为独立的表。通过 marshal 引用重复出现的名称(包括报告复制的每个代码常量名称)按每次复制计入文件预算。空字符串不会增加任何字节,因此每个复制的名称表条目(co_names、varnames、freevars、cellvars 和 localsplusnames)都会在构建其数组之前计入对象预算。复制的常量表以及每个已报告的嵌套代码关系都会计入同一对象预算。整数的数字位以及展开后的集合或字典键(包括每一层重复出现的共享 tuple)都会计入字符串预算。不在固定的 3.7–3.13 表中的 magic、截断的头部、末尾多余的 marshal 字节,以及非代码的顶层对象,都属于类型化失败。
解码出的头部并不能证明对应的 .py 文件存在。python_version 指的是 magic 表,而不是正在运行的解释器。本工作流不会恢复源代码。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 rea2/rea