② 提取器实现(extractor/)

提取器把 C 驱动变成 FormalRIS,约 15,600 行 Python。主编排在 extractor.py,模块 docstring 一句话概括全流程:

parse TU → collect macros → find target functions
→ dataflow extraction (with wrapper inlining)
→ intent annotation → build FormalRIS

2.1 入口与配置(extractor.py · cli.py)

ExtractorConfig 是贯穿全管的配置对象,关键字段:

字段默认作用
max_inline_depth3包装函数内联深度上限
alias_modeoffSVF 别名分析:off / auto / required(required 失败即报错)
compile_context_modeautoKbuild 编译上下文导入:off / auto / required
include_framework / extra_blacklist框架代码包含控制与额外黑名单函数

多源入口 _extract_multi 逐 TU 解析 → 合并宏表 → 可选 SVF 链接分析 → 交给 extract_multi_with_inlining。模块名消歧(_assign_multi_module_names):同名 static 函数重命名为 {源文件stub}__{函数名}[__N],外部符号冲突直接 ValueError —— 绝不静默合并 C 符号。

2.2 编译上下文(compile_context.py)

要得到与真实内核一致的 AST,必须知道驱动实际是怎么编译的。优先级:

  1. compile_commands.json(如果提供)
  2. Kbuild 的 .o.cmd 文件(kbuild_cmd_path 自动发现)—— 记录真实 include 路径、-D 宏、优化选项

_sanitize_arguments 清洗 token(去 -o/-c 输出项等),CompileContext.display() 输出审计信息,来源与 SHA 写进 analysis metadata —— 这是「可复现」的地基。

2.3 AST 模型(ast_model.py)

2.4 宏表(macros.py)

内核驱动大量用宏定义寄存器偏移(#define GPIODIR 0x400)。MacroTable.build(tu, source, source_text) 收集驱动自身与头文件里的宏,_eval_int_expr 做常量求值,支持 BIT(n)、算术折叠。地址解析时:pl061->base + GPIODIR → 宏值 0x400 → Symbolic{device, GPIODIR} 并登记 register_map(dspec 里就是 register GPIODIR: B1 at base + 0x400)。多源时 combined_macros.merge 检测同名宏冲突。

2.5 调用图与有界内联(call_graph.py)

驱动里的寄存器访问常藏在包装函数里(如 pl061_write 内部才调 writeb)。方案是过程间有限深度内联

2.6 流敏感数据流(dataflow.py,1,487 行)

模块 docstring:

"Walks each function's calls in source order, maintaining an abstract store
(var -> AbsVal). At each MMIO call it resolves the address argument to a
RegAddr via the store + macro table, records branch conditions, and detects
read-modify-write patterns (readl→modify→writel on the same address)."

核心数据结构 Op(后续所有阶段的传输对象):

@dataclass
class Op:
    kind: str                 # Read/Write/ReadModifyWrite/Transaction*/State*/...
    addr: dict                # RegAddr: Fixed|Symbolic|Computed
    width: int
    value: Optional[str]      # 写值/变换的原始 C 文本
    condition: Optional[str]
    intent: str = "Unknown"
    source_loc / line         # 源码位置
    reg_name: Optional[str]   # 解析出的寄存器宏名
    var: Optional[str]        # Read 的 LHS 变量
    cond_stack: list          # 分支谓词栈(formalize 用)
    control_stack: list       # 结构化 Cond/Loop 帧
    evidence: dict            # 可审计来源证据
    state_field / transaction # State 与事务扩展

2.7 污点抽象域(taint.py)

抽象存储的值域设计(对应 Linux 驱动的典型寻址模式):

AbsVal = BasePtr(变量) | Offset(常量) | ReadTaint(来源读) | Const | SymExpr | Top
addr_fixed(base, off)      # 常量偏移
addr_offset(base, delta)   # 变量偏移
addr_indirect(ptr)         # 指针间接
addr_equal(a, b)           # 地址相等判定(RMW 用)
addr_base_of(a)            # 取基址

它能回答的关键问题:两个地址参数是否指向同一寄存器(RMW 判定)、地址基址是什么(Symbolic 命名)、偏移是否静态可知。

2.8 MMIO 分类与事务(mmio.py · transactions.py)

2.9 控制流(control.py)

_function_cfg 为每个函数构建显式 CFG(block 节点 + pred/succ 边,标注 break/goto/backedge/loop header);immediate() 计算支配/后支配关系。产物 build_control_accounting 记录哪些控制结构被证明、哪些阻塞了 strict —— 是「未证明循环显式阻塞」的实现者。

2.10 SVF 别名分析(alias.py,可选)

真实驱动的 MMIO 指针常经过多层赋值/别名。SVF 路线:

  1. _generate_stubbed_bc:clang 编译成 LLVM IR(对缺失的内核符号打 stub),_assemble_ir 汇编成 bitcode。
  2. 多源时 先链接所有 TU 的 bitcode 再跑一次 WPAfind_mmio_aliases_multi),记录 linked-bitcode SHA、工具版本、source provenance。
  3. _parse_wpa_aliases:把 WPA 的变量 ID 映射回 C 名(_map_var_to_c_name + 行号定位 LHS),筛选 MMIO lvalue(_accept_mmio_lvalue)。

默认 off(快);auto 失败降级警告;required 失败即终止。C67X00 required 运行成功链接 4 TU。

2.11 形式化(formalize.py)

把扁平 Op 流变成嵌套 FormalRIS 的三件事:

_to_risop 是 Op → RIS op 的分派器:Read 带 var、Write 带 value、RMW 带 transform+read_var、Transaction* 带 endpoint/payload/协议字段。

fail-closed 原则(全章贯穿):address/value 含 Top → precision 降级 → reliability 变 Unknown → strict readiness 为 false。有 helper 内联(无论是否触发 rescue)就没有正式 Call verifier → strict 保持 false。系统用「拒绝」而不是「猜」来保证诚实性。
下一章:FormalRIS 之后,spec_infer 如何把它升级成后端无关的 DeviceSpec(寄存器表、回调绑定、effect/ensure 契约)。