Skip to content

Latest commit

 

History

History
927 lines (802 loc) · 57.6 KB

File metadata and controls

927 lines (802 loc) · 57.6 KB

文档一:Python反编译总体设计.md

Python字节码反编译器总体设计文档

版本: v2.8 日期: 2026-07-09 项目: PyRebuilderSharp 状态: Phase 1–6 ✅ + Phase 7 标注优先流水线 🚀 — 8 阶段标注链路 · 7 类 28 子模式目录 · 0 孤儿块 · 0 崩溃 · 71% 白盒通过率(持续收敛中)


1. 项目概述

1.1 背景与目标

PyRebuilderSharp 是一个基于 C#(.NET 10) 的 Python 字节码反编译器,对标 pycdc,以 Avalonia UI 提供跨平台 GUI。核心目标:

  • 多版本兼容: 支持 Python 2.7 + 3.53.14(marshal读取),完整反编译 2.73.14
  • 块级容错: 每个基本块独立反编译,失败块输出注释兜底,不影响其他块
  • 高还原度: AST 级语义比较,确保反编译结果等价于原源码
  • 跨平台: .NET 10 + Avalonia UI → Windows/macOS/Linux

1.2 核心优势

特性 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 单命令

1.3 架构澄清

本节用于明确陈述项目的架构事实,避免外部分析者(如 AI 生成的代码审查报告) 因未仔细阅读源码而做出「线性模板拼接」、「缺少 CFG」、「版本混在解析路径」等不准确判断。

1.3.1 架构全景图

.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)

1.3.2 关键设计决策 (ADRs)

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 更细

1.3.3 常见误判对照表

外部文档常见错误判断 实际架构事实 关键证据文件
「线性模板拼接」 完整的 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

1.4 逐块兜底策略(核心设计原则)

基本块列表 [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)"   │ ✅ 成功
    └────────────────────────────────────────┘

核心原则

  1. 块隔离 — 每个基本块独立反编译,一个块失败不影响其他块
  2. 注释兜底 — 失败块输出 # [Block #{id} Decompilation Failed] 注释,含偏移/错误/字节码
  3. 最大恢复 — 即使部分块失败,整体仍生成最大可读的 Python 源码
  4. 控制结构保持 — 失败块的外层 if/for/try 结构仍正确生成
  5. 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 ...
// # ════════════════════════════════════════

2. CPython 源码研读 — 理解 .py → .pyc 编译管道

2.1 为什么需要研读 CPython 源码

反编译器本质上是编译器的逆过程。要写好反编译器,必须深刻理解编译器的每一步做了什么。CPython 源代码是反编译工作的权威参考——任何第三方文档或 pycdc 的实现都可能与真实行为有偏差。

2.2 .py → .pyc 编译管道全貌

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 文件

2.3 关键源文件清单与对应关系

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

2.4 Python/compile.c 核心流程(反编译重点)

这是反编译最重要的源文件。理解 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)
};

2.5 编译步骤对反编译的启示

编译步骤 影响 反解策略
符号表分析 建立作用域链 names=全局名, varnames=局部名, freevars/cellvars=闭包 CodeObject.Names 全反写回 Name(Id, Load/Store), VarName 需判断 Load/Store
常数折叠 2+35 运行时看不到 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 可能缺失 无法恢复(信息丢失)

2.6 compile.c 指令发出机制

反编译器最难的地方——编译器怎么发出指令,反编译器就要怎么逆推:

// 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() 中将这些块拼成连续的字节码数组。反编译器需要反转这个过程——从连续字节码恢复基本块。

2.7 marshal.c 版本差异总结(反编译重点)

通过研读 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

2.8 compile.c 调试方法

编译 .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))

3. 四阶段流水线架构

┌─────────────────────────────────────────────────────────────────────────┐
│                         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 — --seq-blocks 模式)

核心思想:Phase 7 引入全新的反编译架构,以标注优先为原则——先扫描获取所有信息(标注阶段),再统一链接和组装(组装阶段)。整个流水线遵循"标注→链接→组装"的递进式哲学。

为什么需要标注优先架构?

原有架构(Phase 3-6)的问题:

  1. visited 集污染 — 嵌套控制块使用共享 visited HashSet,一个块被标记后其后续路径的块可能被静默跳过
  2. 孤儿块修复复杂 — 被 visited 遗漏的块需要多轮 ClassifyOrphanBlock + 条件恢复,每类处理逻辑不同
  3. 递归深度不可控 — BuildLoop/BuildTryFromBlock 嵌套递归导致 StackOverflow(已通过 visited.Remove 缓解但未根治)
  4. 块级容错不彻底 — 一个块失败(如未处理的操作码)导致整个控制结构断裂,后继块全部成为孤儿

改进后的架构解决

  1. 标注先于链接 — 在链接之前收集所有必需信息,避免孤儿块(2026-07-09 实测 0 孤儿块)
  2. 多轮标注链路 — ExceptionTable → IsExceptBlock → 控制块起始 → 回边 → 最终链接,每轮添加新标注
  3. 链接失效时可恢复 — 标注信息独立于链接结果,链接失败可以重新调整策略而不丢失标注

五阶段标注架构概览

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 的输出独立可检查               │
  └─────────────────────────────────────────────────────┘

效果验证(2026-07-09)

  • 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 { ... }

Fallback 机制

如果 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] 结构化日志

4. 解决方案结构

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 输出

5. 核心组件详解

5.1 BlockDecompiler — 逐块反编引擎

// 每个基本块独立反编译,捕获异常→注释兜底
var result = BlockDecompiler.DecompileBlock(
    instructions, codeObject, blockId, loopHeaders, isForLoop);

if (result.IsSuccess)
    // result.Statements → AST 节点列表
else
    // result.CommentFallback → "# [Block #{id} Decompilation Failed]..."

5.2 BlockResult — 成功/失败统一返回

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);
}

5.3 StackMachine — 栈机模拟

指令类型 处理方式
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

5.4 AstBuilder — 控制结构识别

控制结构 检测方法 构建输出
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。


6. 版本支持矩阵

版本 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

7.1 版本矩阵测试(核心)

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 一致)。

比较方式(按优先级):

  1. AST 语义比较(首选)— python3 -c "import ast; print(ast.dump(ast.parse(...)))"
  2. Token 比较(回退)— 当 .py 源文件不存在时

7.2 编译矩阵

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

8. 当前状态与剩余工作

8.1 已完成的里程碑

层级 内容 状态
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 全覆盖

8.2 语法覆盖(全部 ✅ 完成)

语法 版本 状态
表达式 / 顺序代码块 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