Files
hello_js_reverse_skill/tools/tdc_query.py
T
yml2213 2e7aaff9ec feat(vm): 自上而下重构工具链 —— 新增提升器(lift)与查询CLI(query)
做了什么:
- 新增 tools/tdc_lift.py: 字节码 -> 可读伪代码 (AGENTS.md §3.3 步骤5)
  * 表达式栈还原 (算术/比较/调用/属性)
  * 寄存器引用惯用法还原 (PUSH_IMM_ARRAY n + MAKE_REG_PROP_REF -> rN[key])
  * 控制流 label/goto/if; CREATE_CLOSURE 递归展开成具名函数
- 新增 tools/tdc_query.py: who rN / at @pc / funcs / grep RE (对齐 §4.2 验收)
- 新增 tools/tdc_keytrace.js: 用真 VM 当神谕, 定位某值在哪段字节码诞生
- VM_SPEC §7/§8 重写为"自上而下五件套 + 路线", 并把 tdc_vm_run.py 标注为归档(打地鼠陷阱)
- 修正 lift 的 [n]->rN 归一 与 标签刷屏

为什么:
- 之前用 Python 复刻 JS 运行时去"跑"VM = §2.3 铁律2 的打地鼠陷阱, 收敛极慢
- §3.3 步骤5 要的是"读指令流得到算法; 闭包递归展开" -> 提升(lift)才是正路
- §2.1 最后一步: 读出算法后用目标语言原生重写, 跳过 VM/环境层

怎么验证:
- lift 首次把 XTEA 轮读成可读式: r2=(r2+2654435769); r?+=(((r6<<4)^(r6>>>5))+r6)^(r2+(r7[((r2>>>11)&3)]+855311))
- query 三个验收问题均可秒答: who r2 / at @9285 / funcs
2026-10-11 22:15:43 +08:00

110 lines
3.6 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
TDC VM 工具链 ⑤ 查询 CLI —— 对齐 AGENTS.md §4.2 验收标准
验收要求能秒答三个问题:
* 「这个字段/寄存器谁写的」 -> who <rN>
* 「这段代码在哪(属于哪个函数)」 -> at @pc
* 「有多少个子程序/闭包」 -> funcs
另附 grep 正则查询。
用法(先跑 tools/tdc_lift.py 生成 <name>.lifted.txt):
.venv/bin/python tools/tdc_query.py <lifted.txt> who r7
.venv/bin/python tools/tdc_query.py <lifted.txt> at @9285
.venv/bin/python tools/tdc_query.py <lifted.txt> funcs
.venv/bin/python tools/tdc_query.py <lifted.txt> grep ">>> 11"
"""
from __future__ import annotations
import re
import sys
from typing import List, Tuple
def parse(path: str):
"""切分 lifted 文件 -> [(func_name, [ (pc或None, 行文本) ])]"""
funcs: List[Tuple[str, List[Tuple[int, str]]]] = []
cur_name, cur_lines = None, []
cur_pc = [-1]
for raw in open(path, encoding="utf-8"):
line = raw.rstrip("\n")
m = re.match(r"\s*function\s+([A-Za-z0-9_]+)\(\)\s*\{", line)
if m:
if cur_name is not None:
funcs.append((cur_name, cur_lines))
cur_name, cur_lines = m.group(1), []
continue
if line.strip() == "}" and cur_name is not None:
funcs.append((cur_name, cur_lines))
cur_name, cur_lines = None, []
continue
if cur_name is not None:
lm = re.match(r"\s*@(\d+):", line)
if lm:
cur_pc[0] = int(lm.group(1))
cur_lines.append((cur_pc[0], line))
if cur_name is not None:
funcs.append((cur_name, cur_lines))
return funcs
def main() -> int:
if len(sys.argv) < 3:
print(__doc__)
return 2
path, cmd = sys.argv[1], sys.argv[2]
arg = sys.argv[3] if len(sys.argv) > 3 else ""
funcs = parse(path)
print(f"[{path}] 解析出 {len(funcs)} 个函数")
if cmd == "funcs":
for name, lines in funcs:
pcs = [p for p, _ in lines if p >= 0]
print(f" {name:16s} 行数={len(lines):5d} pc范围=@{min(pcs) if pcs else '-'}..@{max(pcs) if pcs else '-'}")
return 0
if cmd == "who":
# 谁写了 rN (赋值/属性写)
pat = re.compile(rf"(^|[^\w]){re.escape(arg)}\s*(=[^=]|\[[^\]]*\]\s*=)")
hits = 0
for name, lines in funcs:
for pc, line in lines:
if pat.search(line):
print(f" {name} @{pc if pc>=0 else '?'}: {line.strip()}")
hits += 1
if not hits:
print(f" 未发现写 {arg} 的语句")
return 0
if cmd == "at":
want = int(arg.lstrip("@"))
for name, lines in funcs:
pcs = [p for p, _ in lines if p >= 0]
if not pcs or pcs[0] > want:
continue
nxt = [p for p in pcs if p >= want]
if nxt and (want <= max(pcs)):
print(f" @{want} 属于函数 {name} (该函数 pc 范围 @{min(pcs)}..@{max(pcs)})")
# 打印附近 8 行
for pc, line in lines:
if 0 <= pc and want <= pc <= want + 20:
print(" " + line.strip())
break
return 0
if cmd == "grep":
pat = re.compile(arg)
for name, lines in funcs:
for pc, line in lines:
if pat.search(line):
print(f" {name} @{pc if pc>=0 else '?'}: {line.strip()}")
return 0
print(f"未知命令: {cmd}")
return 2
if __name__ == "__main__":
raise SystemExit(main())