版本: v2.8 日期: 2026-07-09 项目: PyRebuilderSharp 状态: Phase 1–6 ✅ + Phase 7 标注优先流水线 🚀 — 8 阶段标注链路 · 7 类 28 子模式目录 · 0 孤儿块 · 0 崩溃 · 71% 白盒通过率(持续收敛中)
PyRebuilderSharp 是一个基于 C#(.NET 10) 的 Python 字节码反编译器,对标 pycdc,以 Avalonia UI 提供跨平台 GUI。核心目标:
- 多版本兼容: 支持 Python 2.7 + 3.5
3.14(marshal读取),完整反编译 2.73.14 - 块级容错: 每个基本块独立反编译,失败块输出注释兜底,不影响其他块
- 高还原度: AST 级语义比较,确保反编译结果等价于原源码
- 跨平台: .NET 10 + Avalonia UI → Windows/macOS/Linux
| 特性 | pycdc (C++) | PyRebuilderSharp (C#) |
|---|---|---|
| 语言 | C++17 | C# 10 (.NET 10) |
| AST 模型 | 手写多态 + enum | record 类型 + 模式匹配 |
| 内存管理 | shared_ptr/unique_ptr | GC 自动回收 |
| 容错机制 | 整体失败 | 逐块注释兜底 |
| 测试体系 | 手动测试为主 | xUnit + AST 语义比较 |
| GUI | 无 | Avalonia UI |
| 跨平台 | CMake 编译 | dotnet build 单命令 |
本节用于明确陈述项目的架构事实,避免外部分析者(如 AI 生成的代码审查报告) 因未仔细阅读源码而做出「线性模板拼接」、「缺少 CFG」、「版本混在解析路径」等不准确判断。
.pyc 文件
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 1: PycReader (版本隔离) │
│ ├── VersionStrategy27 (2.7) │
│ ├── VersionStrategyPre311 (3.5 ~ 3.10) │
│ ├── VersionStrategy311 (3.11) │
│ ├── VersionStrategy312 (3.12) │
│ ├── VersionStrategy313 (3.13) │
│ └── VersionStrategy314 (3.14) │
│ 每个策略独立负责 MapOpcode / CACHE / ExceptionTable 差异 │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 2: BlockScanner — 基本块划分 │
│ 线性扫描 → 跳转目标切割 → BasicBlock 列表 │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 3: ControlFlowScanner — CFG + 支配树 + 自然循环 │
│ ├── BuildCFG() → ControlFlowGraph │
│ │ (含前驱/后继/异常边) │
│ ├── ComputeImmediateDominators() → Dictionary<BB,BB> │
│ │ (立即支配树 — 用于循环头识别) │
│ ├── ComputeDominators() → HashSet<BB> │
│ │ (完整支配集 — 用于自然循环检测) │
│ ├── DetectNaturalLoops() → List<LoopStructure> │
│ │ (回边 + 支配节点 → For/While/Infinite 分类) │
│ └── BuildStructuredCFG() → StructuredCFG │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 4-5: SequentialBlockBuilder (Phase 7 增强) │
│ 7 轮标注 → 28 子模式目录 → 控制结构链接 │
│ ├── MergeLinearChain Phase 1 │
│ ├── AnnotateExceptionTable Phase 2 │
│ ├── AnnotateMatchBlocks Phase 2a │
│ ├── AnnotateForWhileSubtypes Phase 2b │
│ ├── AnnotateHandlerDepths Phase 2c │
│ ├── AnnotateSequentialBlock Phase 3 │
│ ├── AnnotateMergePointsAndExits Phase 3b │
│ └── AnnotateBackEdges Phase 4 │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 6: AstBuilder — AST 构建(不是模板字符串拼接) │
│ │
│ 每个基本块 → StackMachine.Execute() → AST 节点 │
│ AstBuilder 在 CFG 后继图上递归遍历: │
│ ├── BuildStatements() — 递归遍历 CFG │
│ ├── BuildIfElse() / BuildForLoop() / ... │
│ ├── 孤儿块容错 + 注释兜底 │
│ └── 后处理管道: │
│ ├── PostProcessFunctionDefs ← 嵌套 code object 函数体 │
│ ├── ConvertComprehensionCalls ← 推导式转换 │
│ ├── ConvertAugAssign ← i+=1 折叠 │
│ ├── ConvertDocstring ← __doc__ 修复 │
│ ├── CollapseRedundantPasses ← 冗余 pass 消除 │
│ ├── FixEmptyFunctionBodies ← 空函数体修复 │
│ └── TrimPostTerminalDeadCode ← 死代码消除 │
│ │
│ AST 模型 (60+ 节点类型) 位于 Models/AST/: │
│ AstNode → Stmt (If/For/While/Try/Match/FunctionDef...) │
│ → Expr (Call/BinOp/Compare/ListComp/NamedExpr...) │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 7: PythonCodeGenerator — 访问者模式代码生成 │
│ │
│ PythonCodeGenerator.Visit(AstNode) — switch 模式匹配 │
│ → 对每种节点类型独立格式化输出 │
│ → 支持缩进控制 / 空行策略 / docstring 处理 │
│ → **非模板字符串拼接** — 纯访问者模式遍历 AST 树 │
└──────────────────────────┬──────────────────────────────────┘
▼
Python 源码 (.py)
| ADR | 决策 | 替代方案 | 理由 |
|---|---|---|---|
| ADR-001 | 使用 C# record 类型构建 AST | 手写多态类 / 接口 | record 提供值语义 + 模式匹配,简化 AST 遍历 |
| ADR-002 | per-version 策略类隔离 | 统一路径 + if-else 分支 | 每个版本 opcode/cache/ET 差异独立,改一处不影响其他 |
| ADR-003 | CFG + 支配树 + 模式目录 | 纯线性扫描 / 纯支配树还原 | 支配树保证循环正确性,模式目录覆盖边界模糊情况 |
| ADR-004 | 逐块容错 (per-block) | 整体编译 | 块失败不影响其他,最大恢复 |
| ADR-005 | AST 后处理管道链 | 单次生成 | 多 pass 链式处理,每个 pass 只做一件事,可独立测试 |
| ADR-006 | Token 级语义比较测试 | 字符串 diff / compile 验证 | 语义等价即通过,不要求逐字符一致,比 compile 更细 |
| 外部文档常见错误判断 | 实际架构事实 | 关键证据文件 |
|---|---|---|
| 「线性模板拼接」 | 完整的 AST 树 (60+ 节点) + 访问者模式代码生成 | Models/AST/Stmt.cs, Models/AST/Expr.cs, Generators/PythonCodeGenerator.cs |
| 「缺少 CFG + 支配树」 | 完整 CFG + 立即支配树 + 完整支配集 + 自然循环检测 | Scanners/ControlFlowScanner.cs |
| 「版本混在解析路径」 | 7 个独立 VersionStrategy 类,MapOpcode 隔离 | Versioning/VersionStrategy*.cs (7个文件) |
| 「简单槽位映射」 | StackMachine 全栈模拟 + 7 个后处理 pass | Builders/StackMachine.cs, Builders/AstBuilder.cs |
| 「缺少 round-trip 验证」 | Token 级语义比较(比 compile 更细) | Testing/PycdcSuiteRunner.cs, Testing/TokenDumper.cs |
基本块列表 [B1, B2, B3, B4, B5]
│
▼
┌────────────────────────────────────────┐
│ BlockDecompiler.DecompileBlocks() │
│ │
│ B1 ─► 栈机模拟 ─► AST ─► "x = a + b" │ ✅ 成功
│ B2 ─► 栈机模拟 ─► AST ─► "return x" │ ✅ 成功
│ B3 ─► 栈机模拟 ─► ❌ 异常 │ ❌ 失败→注释
│ └── ► 输出注释块 │
│ B4 ─► 栈机模拟 ─► AST ─► "y = 42" │ ✅ 成功
│ B5 ─► 栈机模拟 ─► AST ─► "print(y)" │ ✅ 成功
└────────────────────────────────────────┘
核心原则:
- 块隔离 — 每个基本块独立反编译,一个块失败不影响其他块
- 注释兜底 — 失败块输出
# [Block #{id} Decompilation Failed]注释,含偏移/错误/字节码 - 最大恢复 — 即使部分块失败,整体仍生成最大可读的 Python 源码
- 控制结构保持 — 失败块的外层 if/for/try 结构仍正确生成
- CPython 源代码是最高权威 — 遇到难以解释的字节码偏移、操作码映射、marshal 格式等问题时,必须首先查看 CPython 源代码(
Python/marshal.c,Python/compile.c,Python/ceval.c,Include/opcode.h),而非依赖第三方文档、pycdc 实现或推理猜测。CPython 源码是 gcc/msvc/any C 编译器编译的真实行为,任何第三方实现都可能与真实行为有偏差。
注释块格式:
// # ════════════════════════════════════════
// # [Block #{id} Decompilation Failed]
// # Offsets: 0x0000 - 0x0010
// # Engine: StackMachine
// # Error: Unhandled opcode PRECALL (156)
// # Raw bytes: 64 01 00 6E 02 00 ...
// # ════════════════════════════════════════反编译器本质上是编译器的逆过程。要写好反编译器,必须深刻理解编译器的每一步做了什么。CPython 源代码是反编译工作的权威参考——任何第三方文档或 pycdc 的实现都可能与真实行为有偏差。
Python 源码 (.py)
│
▼
┌──────────────────────────────────────────────────┐
│ Phase A: Lexer (词法分析) │
│ ├── Python/tokenize.c → tokens │
│ └── 将源码字符流 → token 序列 │
└──────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ Phase B: Parser (语法分析) │
│ ├── Parser/pgen.c / Python/ast.c │
│ ├── LL(1) 解析器 → CST → AST (mod_ty) │
│ └── 输出: mod_ty (Module_ty / Interactive_ty) │
└──────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ Phase C: Compiler (字节码编译器) │
│ ├── Python/compile.c → compiler.c的核心逻辑 │
│ ├── Python/symtable.c → 符号表分析 │
│ ├── 三阶段: │
│ │ ① 符号表构建 (symtable) │
│ │ ② CFG 构建 (basicblock, 非 Python 3.11 的块) │
│ │ ③ 汇编 → bytecode 数组 │
│ └── 输出: PyCodeObject │
└──────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ Phase D: Marshal (序列化) │
│ ├── Python/marshal.c │
│ ├── 将 PyCodeObject → 二进制流 │
│ ├── 类型编码: TYPE_CODE, TYPE_STRING, TYPE_TUPLE │
│ ├── 版本3.4+: FLAG_REF(0x80) + TYPE_REF(0x52) │
│ ├── 版本2.7: TYPE_INTERNED(0x74) + TYPE_STRINGREF(0x52) │
│ └── 输出: .pyc 二进制文件 │
└──────────────────────┬───────────────────────────┘
▼
.pyc 文件
| CPython 源文件 | 功能 | 反编译对应模块 |
|---|---|---|
Python/compile.c |
字节码编译器核心 — AST→CFG→bytecode | AstBuilder(逆过程) |
Python/symtable.c |
符号表 — 变量作用域分析 | CodeObject.Names/Varnames/Freevars/Cellvars |
Python/ast.c |
AST 定义与操作 | AstNode/Expr/Stmt 模型 |
Python/marshal.c |
marshal 序列化/反序列化 | PycReader (ReadMarshalValue/ReadMarshalValue27) |
Python/ceval.c |
字节码解释器 — ceval 循环 | StackMachine(栈机模拟) |
Include/code.h |
PyCodeObject 结构体定义 | CodeObject 模型 |
Include/opcode.h |
操作码常量定义 | Opcode 枚举 |
Include/compile.h |
编译器内部结构(basicblock/CFG) | BasicBlock/BlockFlags |
Lib/importlib/_bootstrap_external.py |
.pyc 文件格式解析 | PycReader header 读取 |
Include/pycache.h |
PEP 552 哈希/时间戳缓存 | .pyc header flags |
这是反编译最重要的源文件。理解 compile.c 的编译步骤 = 知道反编译器需要逆推的步骤:
// compile.c 的核心入口
static PyCodeObject *
compiler_mod(struct compiler *c, mod_ty mod)
{
// 1. 符号表分析 — 确定变量作用域
// PySymtable_Build(mod, filename, c->c_future)
// 2. 为当前作用域创建编译器单元
// compiler_enter_scope(c, filename, ...)
// 3. 根据 AST 节点类型分发
switch (mod->kind) {
case Module_kind:
compiler_body(c, mod->v.Module.body);
break;
case Interactive_kind:
...
}
// 4. 生成最终的 PyCodeObject
// compiler_make_instruction(c, ...) 逐个指令
// assemble(c, ...) 汇编 → 字节码数组
return compiler_make_return(c);
}编译器的关键内部结构:
// basicblock — 基本块(Compiler 内部的 CFG)
// 这不是运行时的块,而是编译过程中的结构
typedef struct basicblock {
PyObject *b_instr; // 指令数组
struct basicblock *b_next; // 下一块(线性顺序)
struct basicblock *b_prevy; // 前驱(用于 JUMP_IF 等)
int b_iused; // 已使用的指令数
int b_ialloc; // 分配的容量
int b_offset; // 字节码偏移
unsigned b_next_addr; // 下一个指令的地址(跳转目标计算)
unsigned b_start_addr; // 块的起始地址
int b_label; // 标签(跳转标记)
int b_code; // 代码类型
} basicblock;
// compiler 结构 — 编译上下文
struct compiler {
PyObject *c_filename; // 当前文件名
PyObject *c_name; // 当前代码对象名("<module>"/函数名)
PyObject *c_u?; // 符号表
struct compiler_unit *u; // 当前编译单元
PyObject *c_stack; // 编译栈
Py_ssize_t c_stack_size; // 栈深度
int c_flags; // 编译器标志
int c_optimize; // 优化级别(-O)
// 指令发出
int c_nestlevel; // 嵌套深度(用于 max_depth)
};| 编译步骤 | 影响 | 反解策略 |
|---|---|---|
| 符号表分析 建立作用域链 | names=全局名, varnames=局部名, freevars/cellvars=闭包 | CodeObject.Names 全反写回 Name(Id, Load/Store), VarName 需判断 Load/Store |
常数折叠 2+3→5 |
运行时看不到 2+3,只看到 LOAD_CONST 5 |
Constant(5) 直接输出,无法恢复 2+3 |
条件表达式折叠 if x: → POP_JUMP_IF_* |
分支条件消失,只剩跳转 | AstBuilder 通过 jump target 反推 if 条件 |
| 循环展开 for/while → 跳转图 | 循环体变成有回边的 CFG | ControlFlowScanner 检测回边→识别循环头 |
| try 编译 SETUP_FINALLY(3.10-) / ExceptionTable(3.11+) | 异常处理偏移量编码 | BuildTryFromBlock 解析偏移 |
优化级 -O 移除 assert/docstring |
反编译输出中 assert/docstring 可能缺失 | 无法恢复(信息丢失) |
反编译器最难的地方——编译器怎么发出指令,反编译器就要怎么逆推:
// compile.c 发出一条指令
static int
compiler_addop(struct compiler *c, int opcode)
{
// 向当前 basicblock 添加一条指令
return compiler_addop_i(c, opcode, 0);
}
static int
compiler_addop_i(struct compiler *c, int opcode, Py_ssize_t arg)
{
struct basicblock *b;
// 获取当前基本块
b = compiler_current_block(c);
// 向块内添加指令
// 如果块满则扩展
if (b->b_iused >= b->b_ialloc) {
// 扩展指令数组
}
b->b_instr[b->b_iused].i_opcode = opcode;
b->b_instr[b->b_iused].i_oparg = arg;
b->b_iused++;
// 某些指令会导致块分裂(如 JUMP_ABSOLUTE)
compiler_use_next_block(c); // 可能分裂块
}关键: 编译器用 basicblock 作为指令容器,然后在 assemble() 中将这些块拼成连续的字节码数组。反编译器需要反转这个过程——从连续字节码恢复基本块。
通过研读 CPython 不同版本的 Python/marshal.c,得到的版本差异:
| 特性 | Python 2.7 | Python 3.4-3.7 | Python 3.8-3.10 | Python 3.11+ |
|---|---|---|---|---|
| header 大小 | 8B | 12B | 16B | 16B |
| code 字段数 | 4 (arg,nl,ss,flags) | 5 (arg,kw,nl,ss,flags) | 6 (+pos) | 5 (-nl,+pos) |
| ref 机制 | TYPE_INTERNED + TYPE_STRINGREF(0x52) | FLAG_REF(0x80) + TYPE_REF(0x52) | 同 3.4-3.7 | 同 3.4-3.7 |
| refstack | 无 | 全局 ref list | 同 | 同 |
| interned strings | p->strings 列表 |
同 | 同 | 同 |
| TYPE_CODE_SIMPLE | 无 | 无 | 无 | 有(0x73) |
| locplus | varnames+cellvars+freevars 分开 | 同 | 同 | varnames+cellvars+freevars→localsplusnames+kinds |
编译 .py → 用 dis 模块查看字节码:
import dis, marshal
# 编译源码
with open('test.py') as f:
code = compile(f.read(), 'test.py', 'exec')
# 查看字节码
dis.dis(code)
# 查看代码对象的内部结构
print(f"co_names: {code.co_names}")
print(f"co_varnames: {code.co_varnames}")
print(f"co_consts: {code.co_consts}")
print(f"co_filename: {code.co_filename}")
print(f"co_name: {code.co_name}")查看 CPython 内部结构的变化:
# Python 2.7 vs 3.x 的差异
# v2.7: co_freevars, co_cellvars 是单独字段
# v3.11+: co_localsplusnames = varnames + cellvars + freevars
# 查看指令分布
from collections import Counter
Counter(instr.opname for instr in dis.get_instructions(code))┌─────────────────────────────────────────────────────────────────────────┐
│ Decompiler 主入口 │
└───────────────────────────┬─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Phase 1: 字节码读取 │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ PycReader │ → │ CodeObject │ → │ Instruction[] + 常数表 │ │
│ │ (Marshal) │ │ 反序列化 │ │ 符号表(code_names/var) │ │
│ └─────────────┘ └──────────────┘ └───────────────────────────┘ │
│ 支持: 2.7(8B header), 3.0-3.6(12B), 3.7+(16B), pre-3.8 ref_index │
└─────────────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Phase 2: 分块与控制流分析 │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ BlockScanner│ → │ BasicBlock[] │ → │ ControlFlowScanner │ │
│ │ (Leader标记)│ │ CFG 构建 │ │ (LoopHeader/Body 标记) │ │
│ └─────────────┘ └──────────────┘ └───────────────────────────┘ │
│ BlockFlags: Entry, Exit, LoopHeader, LoopBody, ConditionHeader │
└─────────────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Phase 3: AST 构建 (核心容错阶段) │
│ ┌─────────────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ BlockDecompiler │ → │ BlockResult │ → │ AstBuilder │ │
│ │ (逐块栈机模拟) │ │ Success/Fail │ │ (While/For/If/Try) │ │
│ │ StackMachine │ │ 注释兜底 │ │ 控制块构建 │ │
│ └─────────────────┘ └──────────────┘ └───────────────────────┘ │
│ ● 每个 block 独立 try/catch,失败→BlockResult.FallbackAsComment() │
│ ● 嵌套循环使用 visited.Remove(bb) 防止 StackOverflow │
│ ● break/continue 通过 _isForLoop + POP_TOP 和 JUMP_ABSOLUTE 检测 │
└─────────────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Phase 4: 代码生成 │
│ ┌──────────────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ PythonCodeGen │ → │ 缩进管理 │ → │ Python 源码 │ │
│ │ (Visitor 模式) │ │ IndentStack │ │ (含注释块) │ │
│ └──────────────────┘ └──────────────┘ └───────────────────────┘ │
│ ● AST → Python 字符串:If→"if ...:", While→"while ...:", For→"for" │
│ ● 注释块作为 CommentStmt 节点,输出为 # 注释 │
└─────────────────────────────────┬───────────────────────────────────────┘
│
▼
Python 源代码(含注释兜底块)
核心思想:Phase 7 引入全新的反编译架构,以标注优先为原则——先扫描获取所有信息(标注阶段),再统一链接和组装(组装阶段)。整个流水线遵循"标注→链接→组装"的递进式哲学。
原有架构(Phase 3-6)的问题:
- visited 集污染 — 嵌套控制块使用共享 visited HashSet,一个块被标记后其后续路径的块可能被静默跳过
- 孤儿块修复复杂 — 被 visited 遗漏的块需要多轮
ClassifyOrphanBlock+ 条件恢复,每类处理逻辑不同 - 递归深度不可控 — BuildLoop/BuildTryFromBlock 嵌套递归导致 StackOverflow(已通过 visited.Remove 缓解但未根治)
- 块级容错不彻底 — 一个块失败(如未处理的操作码)导致整个控制结构断裂,后继块全部成为孤儿
改进后的架构解决:
- 标注先于链接 — 在链接之前收集所有必需信息,避免孤儿块(2026-07-09 实测 0 孤儿块)
- 多轮标注链路 — ExceptionTable → IsExceptBlock → 控制块起始 → 回边 → 最终链接,每轮添加新标注
- 链接失效时可恢复 — 标注信息独立于链接结果,链接失败可以重新调整策略而不丢失标注
Phase 1: 顺序块构建 + DecompileStatements 缓存
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 2: ExceptionTable 标注扫描 │
│ ├── 遍历所有 ExceptionTable 条目(如果是 3.11+) │
│ ├── 为每个条目对应的 SequentialBlock 设置: │
│ │ ├── IsExceptBlock = true —— 该 block 是异常处理器 │
│ │ ├── ExceptionTryStartOffset —— try body 起始偏移 │
│ │ └── ExceptionTryEndOffset —— try body 结束偏移 │
│ └── 输出: 所有 IsExceptBlock 已标注的 SeqBlock 列表 │
└──────────────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 3: 控制块起始标注扫描 │
│ ├── 遍历所有 SequentialBlock,检测控制块起始指令: │
│ │ ├── FOR_ITER → IsLoopHeader = true │
│ │ ├── SETUP_FINALLY / SETUP_EXCEPT → IsTryHeader = true │
│ │ ├── SETUP_WITH / BEFORE_WITH / LOAD_SPECIAL → IsWithHeader = true │
│ │ ├── POP_JUMP_IF_* / JUMP_IF_* → IsConditionHeader = true │
│ │ └── 如果 Block 的 StartOffset 匹配 ET 的 StartOffset → IsTryHeader = true│
│ └── 输出: 所有控制块起始已标注的 SeqBlock 列表 │
└──────────────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 4: 回边标注扫描 │
│ ├── 遍历所有 SequentialBlock 的指令,检查 JUMP_ABSOLUTE 目标: │
│ │ ├── 目标指向已标记为 IsLoopHeader 的块 → 标记回边块 │
│ │ └── 目标指向 POP_JUMP_IF_* 的 Fallthrough(跳过循环体)→ else 块 │
│ ├── FOR_ITER 的 JumpTarget 指向的块 → ForIterExitTarget │
│ ├── 循环体入口块标记为 IsLoopBody │
│ ├── break 目标(跳出到循环后)→ IsBreakTarget │
│ ├── continue 目标(回到循环头)→ IsContinueTarget │
│ └── 输出: 所有回边和循环体已标注的 SeqBlock 列表 │
└──────────────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 2a: Match/Case 标注扫描 │
│ ├── MATCH_KEYS / MATCH_CLASS / MATCH_MAPPING → IsMatchHeader = true │
│ ├── JUMP_IF_NOT_EXC_MATCH 目标块 → IsCaseEntry = true │
│ ├── match body 内联指令标注 → IsMatchBody = true │
│ └── 输出: match 和 case 已标注的 SeqBlock 列表 │
└──────────────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 2b: For/While 细分标注 │
│ ├── FOR_ITER 存在 → IsForLoopHeader = true │
│ ├── POP_JUMP_IF_* + 回边(IsBackEdgeTarget)→ IsWhileLoopHeader = true │
│ ├── FOR_ITER 的 Argument(循环出口)→ ForIterExitTarget │
│ ├── FOR_ITER 的后继(第一个 body 块)→ IsForIterBody = true │
│ └── 输出: For/While 区分标注的 SeqBlock 列表 │
└──────────────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 2c: Handler 深度标注 │
│ ├── ExceptionTable Depth → HandlerDepth = et.Depth(标注到 seqBlock) │
│ ├── IsFinally 条目 → IsFinallyBlock = true │
│ ├── handler 后、finally 前的候选块 → IsTryElseBlock = true │
│ └── 输出: handler/else/finally 分类的 SeqBlock 列表 │
└──────────────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 3b: 汇聚点/出口标注扫描 │
│ ├── 条件分支的后继块汇聚点 → IsMergePoint = true │
│ ├── break 目标(循环后第一条指令)→ IsBreakTarget │
│ ├── continue 目标(循环头)→ IsContinueTarget │
│ ├── 控制结构的后继出口偏移 → StructureExitOffset │
│ └── 不可达块(RETURN/RAISE 后)→ IsDeadCodeBlock = true │
└──────────────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 5: 统一链接 — 基于控制块模式目录的确定性链接 │
│ │
│ 链接使用 4 阶段顺序,每阶段只处理一种控制块类型,避免歧义: │
│ │
│ ① Try 结构 —— 优先级最高,ExceptionTable 定义了严格的 offset 边界 │
│ 模式目录 T1-T7: SETUP_FINALLY/ExceptionTable → IsTryHeader │
│ 链接条件: seqBlock.IsTryHeader == true │
│ │
│ ② Loop 结构 —— 逆序(内层优先),可嵌套在 Try body 中 │
│ 模式目录 F1-F4 / W1-W4: FOR_ITER → IsForLoopHeader │
│ POP_JUMP_IF_* + 回边 → IsWhileLoopHeader │
│ 链接条件: seqBlock.IsForLoopHeader || seqBlock.IsWhileLoopHeader │
│ F/W 歧义通过 Phase 2b 的 For/While 细分标注消除 │
│ │
│ ③ With 结构 —— SETUP_WITH / LOAD_SPECIAL 7 模式 │
│ 模式目录 S1-S4 │
│ 链接条件: seqBlock.IsWithHeader || HasBeforeWith || HasLoadSpecial │
│ │
│ ④ IfElse 结构 —— 最灵活,最后链接 │
│ 模式目录 I1-I4: POP_JUMP_IF_* + 无回边 → if │
│ 链接条件: IsConditionHeader (pop_jump_if_*, 跳过已在 visited 的块) │
│ │
│ ⚠️ 歧义处理规则(详见 docs/control-block-patterns.md): │
│ - POP_JUMP_IF_FALSE + 回边 → while(Loop 阶段处理) │
│ - POP_JUMP_IF_FALSE + 无回边 → if(IfElse 阶段处理) │
│ - SETUP_FINALLY → v3.10- try header(Try 阶段处理) │
│ - FOR_ITER → for loop(Loop 阶段处理) │
│ - 链接失败的块(visited 未命中)在下一阶段自动重试 │
│ │
│ ├── 输出: ISequentialControlStructure[] 列表 │
└──────────────────────────────────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Phase 3c: AST 组装(混合遍历) │
│ ├── GenerateAstStatementsHybrid: 从入口顺序块出发,DFS 遍历 │
│ ├── 未归属结构 / 无 ParentStructure → 直接输出缓存的 Statements │
│ ├── 有 ParentStructure → 调用 BuildStructureStatements │
│ │ ├── BuildForLoopStructureStatements → For(iter, target, body) │
│ │ ├── BuildWhileLoopStructureStatements → While(test, body) │
│ │ ├── BuildIfElseStructureStatements → If(test, body, orelse) │
│ │ ├── BuildTryStructureStatements → Try(body, handlers, orelse, fin) │
│ │ └── BuildWithStructureStatements → With(items, body) │
│ ├── 每处理完一个结构 → MarkStructureBlocksProcessed 标记已处理 │
│ └── 第二轮扫描: 处理剩余未处理的顺序块 │
│ │
│ ├── 输出: List<Stmt> → Module AST │
│ └── ✅ 保证: 所有 SequentialBlock 都被消费,无孤儿块 │
└─────────────────────────────────┬───────────────────────────────────────────┘
│
▼
Python 源代码(含注释兜底块)
传统方法: 扫描 → 解析 → 递归构建
↓
标注优先: 多轮扫描(获取全部信息) → 统一链接(基于标注) → 混合组装
标注优先的优势:
┌─────────────────────────────────────────────────────┐
│ 1. 信息完整性: 链接前就已知道所有 try 边界和循环回边 │
│ 2. 无孤儿块: 链接失败时不丢失块,保留在 orphan 恢复 │
│ 3. 可扩展: 新增结构类型只需增加扫描 phase │
│ 4. 可调试: 每个 phase 的输出独立可检查 │
└─────────────────────────────────────────────────────┘
- 0 孤儿块 — 三阶段标注 + 链接保证全覆盖
- 0 运行时崩溃 — 1325 个文件全部反编译成功(1325/1325 = 100%)
- try/except 结构从全局合并修复为逐 ET 条目标注后,EMPTY_TRY 从 163 降至 54
public class SequentialBlock
{
public int Id; // 唯一标识
public int StartOffset, EndOffset; // 字节码偏移范围
public List<Instruction> Instructions; // 完整指令序列
public List<BasicBlock> SourceBlocks; // 原始基本块引用
public List<Stmt>? Statements; // Phase 1 缓存的反编译结果
public List<SequentialBlock> Successors; // 后继顺序块
// Phase 2 标注: ExceptionTable 相关
public bool IsExceptBlock; // 是异常处理器
public int ExceptionTryStartOffset; // try body 起始 (from ET)
public int ExceptionTryEndOffset; // try body 结束 (from ET)
// Phase 3 标注: 控制块起始识别
public bool IsLoopHeader; // FOR_ITER / 回边 POP_JUMP
public bool IsTryHeader; // SETUP_FINALLY / ET StartOffset
public bool IsWithHeader; // SETUP_WITH / BEFORE_WITH
public bool IsConditionHeader; // POP_JUMP_IF_* / JUMP_IF_*
public bool HasSetupFinally, HasSetupExcept;
public bool HasSetupWith, HasBeforeWith, HasLoadSpecial;
// Phase 4 标注: 回边与循环体
public bool IsLoopBody; // 在循环体内
public bool IsBackEdgeTarget; // 跳转目标指向循环头
public int? JumpTarget; // 跳转目标偏移
// Phase 5 链接
public ISequentialControlStructure? ParentStructure; // 所属控制结构
}public interface ISequentialControlStructure
{
ControlStructureType Type { get; }
SequentialBlock Header { get; }
List<SequentialBlock> BodyBlocks { get; }
}
public enum ControlStructureType { Unknown, ForLoop, WhileLoop, Try, With, IfElse }
public class ForLoopControlStructure : ISequentialControlStructure { ... }
public class WhileLoopControlStructure : ISequentialControlStructure { ... }
public class TryControlStructure : ISequentialControlStructure { ... }
public class WithControlStructure : ISequentialControlStructure { ... }
public class IfElseControlStructure : ISequentialControlStructure { ... }如果 VerifyNoOrphanBlocks 检测到有基本块未被任何 SequentialBlock 覆盖,整个 seq-blocks 架构会自动降级到原有的 BuildFallback 方法(即 Phase 3-6 的 visited-based 递归),保证在任何情况下都有输出。
| 维度 | Phase 3-6(原有) | Phase 7(--seq-blocks) |
|---|---|---|
| 控制流解构方式 | visited HashSet 递归 | 多轮标注 + 统一链接 |
| 孤儿块处理 | 多种分类 + 条件恢复 | 三阶段标注保证全覆盖 |
| 嵌套深度 | 受 C# 调用栈限制 | 用 Queue 显式栈,无递归深度问题 |
| 确定性 | visited 顺序依赖遍历路径 | 标注信息无关顺序 |
| 容错粒度 | 基本块级别 | SequentialBlock 级别(更大) |
| 失败降级 | 无自动降级 | 孤儿块检测 → 自动 Fallback |
| 可扩展性 | 新结构类型需改遍历逻辑 | 新增标注 Phase 即可 |
| 调试可观测性 | [BUILD] / [ORPHAN] 日志 |
[SEQ_BUILD] / [PARSE_CTL] 结构化日志 |
PyRebuilderSharp.slnx
├── src/
│ ├── PyRebuilderSharp.Core/ # 核心库 (net10.0)
│ │ ├── Models/
│ │ │ ├── AST/ # AST 节点(record 类型)
│ │ │ │ ├── AstNode.cs # 基类
│ │ │ │ ├── Expr.cs # 表达式节点
│ │ │ │ ├── Stmt.cs # 语句节点(含 CommentStmt)
│ │ │ │ └── CommentStmt.cs # 注释兜底语句节点
│ │ │ ├── Bytecode/ # 字节码模型
│ │ │ │ ├── Instruction.cs # 指令 (record struct)
│ │ │ │ ├── Opcode.cs # 操作码枚举
│ │ │ │ └── CodeObject.cs # 代码对象
│ │ │ └── CFG/ # 控制流图
│ │ │ ├── BasicBlock.cs # 基本块
│ │ │ ├── BlockFlags.cs # 块属性标志
│ │ │ └── StructuredCFG.cs # 结构化 CFG
│ │ ├── Readers/
│ │ │ └── PycReader.cs # .pyc 文件读取器
│ │ ├── Scanners/
│ │ │ ├── BlockScanner.cs # 基本块划分
│ │ │ └── ControlFlowScanner.cs # 控制流分析
│ │ ├── Builders/
│ │ │ ├── AstBuilder.cs # AST 构建(控制结构识别)
│ │ │ ├── StackMachine.cs # 栈机模拟
│ │ │ ├── BlockDecompiler.cs # 逐块反编译
│ │ │ └── BlockResult.cs # 反编译结果 + 注释兜底
│ │ ├── Generators/
│ │ │ └── PythonCodeGenerator.cs # Python 源码输出
│ │ └── Decompiler.cs # 主入口编排
│ │
│ ├── PyRebuilderSharp.Cli/ # 命令行工具
│ │ └── Program.cs # CLI 入口
│ │
│ └── PyRebuilderSharp.Gui/ # Avalonia GUI
│ ├── App.axaml / App.axaml.cs # 应用入口
│ ├── Program.cs # 启动
│ ├── ViewModels/
│ │ ├── ViewModelBase.cs # MVVM 基类
│ │ └── MainViewModel.cs # 主 ViewModel
│ └── Views/
│ ├── MainWindow.axaml # 主窗口布局
│ └── MainWindow.axaml.cs # 窗口代码
│
└── tests/
└── PyRebuilderSharp.Tests/ # 测试项目
├── PycReaderTests.cs # 读取器测试
├── StackMachineTests.cs # 栈机测试
├── PycdcSuiteTests.cs # pycdc 套件
│ └── PycdcSuiteRunner.cs # 测试运行器
├── QuickTests/
│ ├── VersionMatrixTests.cs # 版本矩阵测试(Lv0/Lv1/Lv2)
│ ├── DiagnoseWhileLoops.cs # while 诊断
│ └── TestPopJump.cs # POP_JUMP 测试
└── TestData/
├── input/ # .py 源文件
├── compiled/ # .pyc 文件(编译矩阵)
└── tokenized/ # 预期 token 输出
// 每个基本块独立反编译,捕获异常→注释兜底
var result = BlockDecompiler.DecompileBlock(
instructions, codeObject, blockId, loopHeaders, isForLoop);
if (result.IsSuccess)
// result.Statements → AST 节点列表
else
// result.CommentFallback → "# [Block #{id} Decompilation Failed]..."public class BlockResult
{
public bool IsSuccess { get; init; }
public List<Stmt> Statements { get; init; } // 成功时
public string CommentFallback { get; init; } // 失败时
public string? ErrorMessage { get; init; }
public static BlockResult Success(List<Stmt> stmts);
public static BlockResult FallbackAsComment(
List<Instruction> instructions, Exception exception, int blockId);
}| 指令类型 | 处理方式 |
|---|---|
| LOAD_CONST/NAME/FAST | 压栈 Expr |
| BINARY_ADD/SUB/MUL | 弹栈右→左→BinOp |
| STORE_NAME/FAST/ATTR | 弹栈值→Assign |
| POP_TOP | for 循环体→Break,否则丢弃 |
| RETURN_VALUE | 弹栈值→Return |
| JUMP_ABSOLUTE→循环头 | Continue |
| COMPARE_OP | 弹栈右→左→Compare |
| CALL_FUNCTION | 弹栈 args→Call |
| 控制结构 | 检测方法 | 构建输出 |
|---|---|---|
| while | LoopHeader + 回边 | While(Test, Body, Orelse) |
| for | FOR_ITER 指令 | For(Target, Iter, Body) |
| if/else | POP_JUMP_IF_* 分支 | If(Test, Body, Orelse) |
| try | SETUP_FINALLY | Try(Body, Handlers, Orelse, Finalbody) |
| break | for 中 POP_TOP | Break() |
| continue | JUMP_ABSOLUTE→循环头 | Continue() |
嵌套循环保护:使用 visited.Remove(bb) 从 visited 集合中移除 body 块,使 GetStructuredBlockStmts 能用同一 visited 集重新管理,防止嵌套时 StackOverflow。
| 版本 | Magic Number | Header | 字段数 | 状态 | Seq-Blocks |
|---|---|---|---|---|---|
| 2.7 | 03 F3 0D 0A | 8B | arg,nl,ss,flags(4) | ✅ marshal + Lv0-Lv3 | ✅ |
| 3.5 | 17 0D 0D 0A | 12B | arg,kw,nl,ss,flags(5) | ✅ Lv0-Lv3 | ✅ |
| 3.6 | 33 0D 0D 0A | 12B | arg,kw,nl,ss,flags(5) | ✅ Lv0-Lv3 | ✅ |
| 3.7 | 42 0D 0D 0A | 16B | arg,kw,nl,ss,flags(5) | ✅ Lv0-Lv3 | ✅ |
| 3.8 | 55 0D 0D 0A | 16B | arg,pos,kw,nl,ss,flags(6) | ✅ Lv0-Lv3 | ✅ |
| 3.9 | 61 0D 0D 0A | 16B | arg,pos,kw,nl,ss,flags(6) | ✅ Lv0-Lv3 | ✅ |
| 3.10 | 6F 0D 0D 0A | 16B | arg,pos,kw,nl,ss,flags(6) | ✅ Lv0-Lv3 | ✅ |
| 3.11 | A7 0D 0D 0A | 16B+CACHE | arg,pos,kw,ss,flags(5) | ✅ marshal + def + class + yield | ✅ |
| 3.12 | C0 0D 0D 0A | 16B+CACHE | 同 3.11 | ✅ | ✅ |
| 3.13 | D0 0D 0D 0A | 16B+CACHE | 同 3.11 | ✅ | ✅ |
| 3.14 | D2 0D 0D 0A | 16B+CACHE | 同 3.11 | ✅ | ✅ |
77 tests = 7 层级 × 11 版本 (全覆盖 2.7 → 3.14)
├─ Lv0_Expressions: 2.7, 3.5-3.14 ✅ (11)
├─ Lv1_Sequential: 2.7, 3.5-3.14 ✅ (11)
├─ Lv2_ControlFlow: 2.7, 3.5-3.14 ✅ (11)
├─ Lv3_NestedDepth(5层): 2.7-3.14 ✅ (11)
├─ Lv3_NestedMixed: 2.7-3.14 ✅ (11)
├─ Lv3_NestedMatrix: 2.7-3.14 ✅ (11)
└─ Lv3-1_NestedDepth(九层塔): 2.7-3.14 ✅ (11) ← 新增
所有版本均标记为 known_issue(AST 语义比较跳过),仅验证反编译不崩溃。
3.11+ 版本通过 marshal 3.11+ 格式修复(localsplusnames + localspluskinds)可正确读取。
3.13/3.14 兼容 marshal 格式(与 3.11/3.12 一致)。
比较方式(按优先级):
- AST 语义比较(首选)—
python3 -c "import ast; print(ast.dump(ast.parse(...)))" - Token 比较(回退)— 当 .py 源文件不存在时
compile_pyc_matrix.py 使用 pyenv 多版本 Python 编译测试文件:
VERSIONS=("2.7.18" "3.5.10" "3.6.15" "3.7.17" "3.8.18" "3.9.18" "3.10.14")
for v in "${VERSIONS[@]}"; do
pyenv local $v && python3 -m py_compile "$file"
done| 层级 | 内容 | 状态 |
|---|---|---|
| Lv0 | 表达式(常量/变量/二目/调用/属性/比较/切片) | ✅ |
| Lv1 | 顺序代码块(赋值/return/表达式语句) | ✅ |
| Lv2 | 控制流(if/while/for/try/break/continue/else) | ✅ |
| Lv3 | 嵌套控制块(深度5 + 矩阵对偶 + 混合嵌套 + 九层塔) | ✅ |
| v2.7 marshal | TYPE_STRINGREF 修复、FLAG_REF 正确禁用 | ✅ |
| v3.11+ marshal | 8 修复:localsplusnames+kinds、FLAG_REF ref slot、container ref、exceptiontable TYPE_REF 等 | ✅ 0/938 警告 |
def 语句 |
函数定义、参数、返回值、闭包 | ✅ |
class 定义 |
类定义、方法、init、类级属性 | ✅ |
yield / yield from |
生成器函数 | ✅ |
@decorator |
装饰器链 | ✅ |
async def / await |
异步函数 | ✅ |
| 展开赋值 | a, b = ..., a, *rest = ... |
✅ |
| CrashCollector | JSON 崩溃记录到 ~/.pyrebuilder/crashes/ | ✅ |
| GUI | Avalonia 暗色主题 + 拖放 + 语法高亮 + 版本检测 | ✅ |
| 编译脚本 | tools/compile_test_data.py 2.7→3.14 全覆盖 |
✅ |
| 语法 | 版本 | 状态 |
|---|---|---|
| 表达式 / 顺序代码块 | 2.7–3.14 | ✅ |
| 控制流 (if/while/for/try) | 2.7–3.14 | ✅ |
| 嵌套 (5 层 + 九层塔) | 2.7–3.14 | ✅ |
| lambda | 2.7–3.14 | ✅ |
def 函数定义 |
2.7–3.14 | ✅ |
class 类定义 |
2.7–3.14 | ✅ |
yield / yield from |
2.7–3.14 | ✅ |
@decorator |
2.7–3.14 | ✅ |
async def / await |
3.5–3.14 | ✅ |
展开赋值 a, b = ... |
2.7–3.14 | ✅ |
walrus := |
3.8–3.14 | ✅ NamedExpr + COPY+STORE |
except* |
3.11–3.14 | ✅ ExceptionTable + IsGroup |
match/case |
3.10–3.14 | ✅ 11 pattern types + codegen |
| linetable 解析 | 3.11–3.14 | ✅ PEP 626 |
| 跨平台 GUI | Windows/macOS/Linux | ✅ |
| 项目 | 文件 | 状态 |
|---|---|---|
| 批量反编译模式 | src/PyRebuilderSharp.Cli/Program.cs |
✅ -d <dir>, --stats |
| CrashCollector Dashboard | PyRebuilderSharp.Gui — Avalonia 面板 |
✅ 按钮 + 列表 + 清除 |
| AST 自动对比验证 | tools/ast_compare.py |
✅ AST 语义比较 |
详见 docs/summary_phase6_close.md。