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
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 用**真 VM** 当神谕: 定位「XTEA 密钥数组」在哪段字节码被造出来。
|
||||
*
|
||||
* 思路(绕开"用 Python 复刻 JS 运行时"的泥潭):
|
||||
* 给真 VM 的分发器打钩子, 每执行一条指令就扫一遍当前 E(寄存器区+栈),
|
||||
* 一旦出现「4 个元素、且每个元素 4 字节都是可打印 ASCII 的 32bit 数」
|
||||
* —— 那就是 XTEA 密钥数组(已确证形态) —— 立刻 dump 最近 N 条 PC。
|
||||
* 那段 PC 窗口就是密钥例程, 之后手工提升成 Python 即可。
|
||||
*
|
||||
* 用法: node tools/tdc_keytrace.js <tdc.js> [--window=400]
|
||||
*/
|
||||
const fs = require("fs");
|
||||
|
||||
const file = process.argv[2] || "tcaptcha/core_js/tdc_sample.js";
|
||||
const winArg = process.argv.find((a) => a.startsWith("--window="));
|
||||
const WINDOW = winArg ? parseInt(winArg.split("=")[1], 10) : 400;
|
||||
|
||||
let src = fs.readFileSync(file, "utf8");
|
||||
const needle = "M=G[z[H++]]()";
|
||||
if (!src.includes(needle)) { console.error("[!] 未找到分发器"); process.exit(2); }
|
||||
|
||||
globalThis.__PC = [];
|
||||
globalThis.__HITS = [];
|
||||
|
||||
// 目标: 密钥 4 个 ASCII 打包字对应的字符串 (由分析确定, 可用环境变量覆盖)
|
||||
const KEY_ASCII = process.env.KEY_ASCII || "ldFPRVkjJCKeigfH";
|
||||
const CHUNKS = [KEY_ASCII.slice(0, 4), KEY_ASCII.slice(4, 8), KEY_ASCII.slice(8, 12), KEY_ASCII.slice(12, 16)];
|
||||
function printablePacked(v) {
|
||||
if (!Number.isInteger(v)) return false;
|
||||
const b = [(v >>> 24) & 255, (v >>> 16) & 255, (v >>> 8) & 255, v & 255];
|
||||
return b.every((x) => x >= 32 && x < 127);
|
||||
}
|
||||
function isKeyArray(a) {
|
||||
if (!a || typeof a !== "object" || typeof a.length !== "number" || a.length !== 4) return false;
|
||||
for (let i = 0; i < 4; i++) if (!printablePacked(a[i])) return false;
|
||||
return true;
|
||||
}
|
||||
function asciiOf(a) {
|
||||
return a.map((x) => String.fromCharCode((x >>> 24) & 255, (x >>> 16) & 255, (x >>> 8) & 255, x & 255)).join("");
|
||||
}
|
||||
// 命中判定: 4-int 数组 / 含任意 key chunk 的字符串 / 恰好16字符的字符串
|
||||
function detect(v, depth, seen) {
|
||||
if (typeof v === "string") {
|
||||
if (v.indexOf(KEY_ASCII) >= 0) return "str-full:" + v;
|
||||
for (const c of CHUNKS) if (v.indexOf(c) >= 0) return "str-chunk:" + v;
|
||||
return null;
|
||||
}
|
||||
if (v && typeof v === "object") {
|
||||
if (depth > 3 || seen.has(v)) return null;
|
||||
seen.add(v);
|
||||
if (typeof v.length === "number" && isKeyArray(v)) return "arr4:" + asciiOf(v);
|
||||
// 数组元素
|
||||
if (typeof v.length === "number") {
|
||||
for (let i = 0; i < v.length; i++) { const d = detect(v[i], depth + 1, seen); if (d) return "[" + i + "]." + d; }
|
||||
}
|
||||
// 对象属性 (含字符串键)
|
||||
let ks = [];
|
||||
try { ks = Object.keys(v); } catch (e) { ks = []; }
|
||||
for (const k of ks) {
|
||||
let x; try { x = v[k]; } catch (e) { continue; }
|
||||
const d = detect(x, depth + 1, seen);
|
||||
if (d) return "." + k + ":" + d;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
globalThis.__SCAN = function (E) {
|
||||
try {
|
||||
for (let i = 0; i < E.length; i++) {
|
||||
const cell = E[i];
|
||||
if (!cell) continue;
|
||||
// 寄存器单元可能是 [值], 也可能本身就是那个值; 两种都扫
|
||||
const cands = [cell];
|
||||
if (Array.isArray(cell)) { cands.push(cell[0]); if (Array.isArray(cell[0])) cands.push(cell[0]); }
|
||||
let d = null, v = null;
|
||||
for (const c of cands) { d = detect(c, 0, new Set()); if (d) { v = c; break; } }
|
||||
if (d) {
|
||||
globalThis.__HITS.push({
|
||||
reg: i, how: d, value: typeof v === "string" ? v : (Array.isArray(v) ? Array.from(v) : String(v)),
|
||||
pc: globalThis.__PC[globalThis.__PC.length - 1],
|
||||
window: globalThis.__PC.slice(-WINDOW),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
};
|
||||
|
||||
src = src.replace(needle, "M=(globalThis.__PC.push(H),globalThis.__SCAN(E),G[z[H++]]())");
|
||||
|
||||
globalThis.window = globalThis;
|
||||
globalThis.document = { cookie: "", referrer: "", createElement: () => ({ getContext: () => null, toDataURL: () => "", style: {} }), getElementById: () => null };
|
||||
globalThis.navigator = { userAgent: "Mozilla/5.0", platform: "MacIntel", languages: ["zh-CN"], language: "zh-CN", hardwareConcurrency: 10, cookieEnabled: true };
|
||||
globalThis.screen = { width: 2560, height: 1440, availWidth: 2560, availHeight: 1345, colorDepth: 24, pixelDepth: 24 };
|
||||
globalThis.location = { href: "https://turing.captcha.gtimg.com/1/template/drag_ele.html", hostname: "turing.captcha.gtimg.com", protocol: "https:" };
|
||||
globalThis.localStorage = { getItem: () => null, setItem: () => {} };
|
||||
globalThis.sessionStorage = { getItem: () => null, setItem: () => {} };
|
||||
globalThis.requestAnimationFrame = (f) => setTimeout(f, 0);
|
||||
globalThis.addEventListener = () => {}; globalThis.removeEventListener = () => {};
|
||||
globalThis.__TENCENT_CHAOS_STACK = {};
|
||||
|
||||
let err = null;
|
||||
try { eval(src); } catch (e) { err = String((e && e.stack) || e); }
|
||||
|
||||
// 顶层只"注册模块"; 真正算密钥要等宿主调用 -> 手动驱动
|
||||
const names = Object.keys(globalThis).filter((k) => /^(TDC|TCaptcha|__|tcaptcha)/i.test(k));
|
||||
process.stderr.write("[keytrace] globalThis 候选: " + JSON.stringify(names.slice(0, 20)) + "\n");
|
||||
try {
|
||||
if (globalThis.TDC && typeof globalThis.TDC.getData === "function") {
|
||||
process.stderr.write("[keytrace] 调用 TDC.getData(true) 驱动...\n");
|
||||
globalThis.__PC.push(-1);
|
||||
const c = globalThis.TDC.getData(true);
|
||||
process.stderr.write(`[keytrace] getData 返回长度=${c ? c.length : 0}\n`);
|
||||
} else {
|
||||
process.stderr.write("[keytrace] 未找到 TDC.getData\n");
|
||||
}
|
||||
} catch (e) { process.stderr.write("[keytrace] 驱动异常: " + String(e).slice(0, 200) + "\n"); }
|
||||
|
||||
process.stderr.write(`[keytrace] 执行 PC=${globalThis.__PC.length} 命中=${globalThis.__HITS.length}${err ? " 顶层抛错: " + err.slice(0, 120) : ""}\n`);
|
||||
let out = globalThis.__HITS[0] || null;
|
||||
if (out) {
|
||||
process.stderr.write(`[keytrace] 命中寄存器 r${out.reg}, 密钥=${JSON.stringify(out.key)}, ASCII=${out.ascii}, 命中时 PC=@${out.pc}\n`);
|
||||
}
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,287 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
腾讯天御 Chaos VM —— **字节码提升器 (lifter / devirtualizer)**
|
||||
|
||||
对应 AGENTS.md §3.3 步骤 5:「读指令流得到算法;子程序调用(闭包创建)递归展开」。
|
||||
|
||||
做什么
|
||||
------
|
||||
把栈式字节码**提升为可读伪代码**(不是模拟执行!):
|
||||
* 表达式栈还原: 每条指令的栈效应翻译成表达式文本
|
||||
* 寄存器/引用惯用法还原: `PUSH_IMM_ARRAY n`+`MAKE_REG_PROP_REF` -> `rN[key]`
|
||||
* 控制流: 基本块 + `label`/`goto`/`if (...) goto`
|
||||
* 闭包: `CREATE_CLOSURE` 递归展开成具名函数 (子程序)
|
||||
|
||||
为什么(对比"用 Python 复刻 JS 运行时")
|
||||
--------------------------------------
|
||||
* §2.3 铁律2: 复刻 JS 语义 = 打地鼠; 提升只是**读代码**, 不需要任何运行时
|
||||
* §2.1 最后一步: 读出算法后用目标语言原生重写, 跳过 VM/环境层
|
||||
* §4.2: 产出可查询资产 ("这个字段谁写的" = 看提升后的赋值语句即可)
|
||||
|
||||
用法
|
||||
----
|
||||
.venv/bin/python tools/tdc_lift.py tcaptcha/core_js/tdc_sample.js [--func @1518]
|
||||
输出 `<name>.lifted.txt`
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from tdc_disasm import OP_NAMES, DYNAMIC_ARITY, OP_CREATE_CLOSURE, OP_JUMP, OP_JUMP_IF_TRUE, OP_PUSH_TRY, extract_bytecode, disassemble_cfg # noqa: E402
|
||||
|
||||
# 二元运算 => 源码里写出的运算符
|
||||
BINOP = {
|
||||
3: "^", 4: "===", 5: "%", 13: "==", 15: ">", 18: "in", 20: ">=", 23: "*",
|
||||
30: "&", 33: "-", 35: ">>", 36: "<<", 39: "+", 52: "/", 62: "|", 65: ">>>",
|
||||
}
|
||||
CMP_PUSH = {19: "true", 29: "null", 42: "undefined", 45: "false", 55: '""', 66: "true"}
|
||||
|
||||
|
||||
def _q(s: str) -> str:
|
||||
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
|
||||
|
||||
class Expr:
|
||||
"""栈上的一个抽象值: 表达式文本 或 引用 (base, key)。"""
|
||||
|
||||
__slots__ = ("kind", "text", "base", "key")
|
||||
|
||||
def __init__(self, kind, text="", base=None, key=None):
|
||||
self.kind = kind # "v" = 值表达式, "ref" = 引用
|
||||
self.text = text
|
||||
self.base = base
|
||||
self.key = key
|
||||
|
||||
def __str__(self):
|
||||
if self.kind == "ref":
|
||||
return f"{self.base.text if isinstance(self.base, Expr) else self.base}" \
|
||||
f"[{self.key.text if isinstance(self.key, Expr) else self.key}]"
|
||||
return self.text
|
||||
|
||||
|
||||
def deref(e: Expr) -> str:
|
||||
return str(e)
|
||||
|
||||
|
||||
class Lifter:
|
||||
def __init__(self, z: List[int]):
|
||||
self.z = z
|
||||
self.funcs: Dict[int, dict] = {} # target pc -> {name}
|
||||
self.out: List[str] = []
|
||||
|
||||
# ------------------------------------------------------------------ 主流程
|
||||
def run(self, entry: int = 0, name: str = "vm_entry"):
|
||||
self.out.append(f"// ===== lifted from tdc.js bytecode ({len(self.z)} bytes) =====")
|
||||
self.funcs[entry] = {"name": name}
|
||||
self._lift_func(entry, name)
|
||||
return "\n".join(self.out)
|
||||
|
||||
def _func_name(self, target: int) -> str:
|
||||
if target not in self.funcs:
|
||||
self.funcs[target] = {"name": f"sub_{target}"}
|
||||
return self.funcs[target]["name"]
|
||||
|
||||
def _lift_func(self, entry: int, name: str):
|
||||
all_ins = disassemble_cfg(self.z, entry)
|
||||
insts = {i["pc"]: i for i in all_ins if i["pc"] >= entry}
|
||||
# 只在"跳转目标/入口/洞"处打标签, 避免每行一个标签
|
||||
targets = {entry}
|
||||
for i in all_ins:
|
||||
for o in i.get("operands") or []:
|
||||
if isinstance(o, int) and o in insts:
|
||||
targets.add(o)
|
||||
# 只保留可达块; 逐块输出
|
||||
self.out.append("")
|
||||
self.out.append(f"function {name}() {{")
|
||||
stack: List[Expr] = []
|
||||
pending: List[str] = []
|
||||
last_pc = None
|
||||
|
||||
def flush():
|
||||
for s in pending:
|
||||
self.out.append(" " + s)
|
||||
pending.clear()
|
||||
|
||||
pcs = sorted(insts)
|
||||
for pc in pcs:
|
||||
ins = insts[pc]
|
||||
op = ins["op"]
|
||||
if op is None:
|
||||
continue
|
||||
if pc in targets:
|
||||
flush()
|
||||
self.out.append(f" @{pc:05d}:") # 目标标签
|
||||
self._emit(op, ins, pc, stack, pending)
|
||||
flush()
|
||||
self.out.append("}")
|
||||
|
||||
def _fallthrough(self, ins) -> int:
|
||||
op = ins["op"]
|
||||
if op == OP_JUMP:
|
||||
return ins["operands"][0]
|
||||
if op == OP_JUMP_IF_TRUE:
|
||||
return ins["operands"][0]
|
||||
# 其它: 用下一块起点近似 (由 label_needed 决定是否打标签)
|
||||
return -1
|
||||
|
||||
# ------------------------------------------------------- 单条指令的栈效应
|
||||
def _emit(self, op: int, ins: dict, pc: int, st: List[Expr], out: List[str]):
|
||||
ops = ins["operands"]
|
||||
|
||||
def pop():
|
||||
return st.pop() if st else Expr("v", "<?>")
|
||||
|
||||
def push(t):
|
||||
st.append(Expr("v", t))
|
||||
|
||||
# --- 常量/字符串
|
||||
if op == 0: # PUSH_STR_CHAR
|
||||
c = chr(ops[0] & 0xFF)
|
||||
if st and st[-1].kind == "v" and st[-1].text.startswith('"'):
|
||||
st[-1].text = st[-1].text[:-1] + c.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
else:
|
||||
push(_q(c))
|
||||
return
|
||||
if op in CMP_PUSH:
|
||||
push(CMP_PUSH[op]); return
|
||||
if op == 22:
|
||||
push(str(ops[0])); return
|
||||
if op == 31: # PUSH_IMM_ARRAY -> [n] (寄存器引用惯用法)
|
||||
push(f"[{ops[0]}]"); return
|
||||
if op == 64:
|
||||
if st: st[-1] = Expr("v", str(ops[0]))
|
||||
else: push(str(ops[0]))
|
||||
return
|
||||
|
||||
# --- 寄存器
|
||||
if op == 68: # LOAD_REG_IMM r
|
||||
push(f"r{ops[0]}"); return
|
||||
if op == 25: # LOAD_REG_INDIRECT: E[key][0]
|
||||
idx = pop().text
|
||||
if idx.startswith("[") and idx.endswith("]"):
|
||||
idx = idx[1:-1]
|
||||
push(f"r{idx}"); return
|
||||
if op == 47: # ALLOC_REG r
|
||||
out.append(f"var r{ops[0]};"); return
|
||||
if op == 46: # STORE_REG: E[idx][0] = val
|
||||
val = pop(); ref = pop()
|
||||
out.append(f"r{ref.text.strip('[]')} = {val};"); return
|
||||
|
||||
# --- 引用/属性
|
||||
if op == 32: # MAKE_REG_PROP_REF: [rN[0], key]
|
||||
key = pop(); idx = pop()
|
||||
st.append(Expr("ref", base=Expr("v", f"r{idx.text.strip('[]')}"), key=key)); return
|
||||
if op == 9: # MAKE_PROP_REF: [base[key0], key]
|
||||
key = pop(); ref = pop()
|
||||
st.append(Expr("ref", base=Expr("v", str(ref)), key=key)); return
|
||||
if op == 59: # GET_PROP
|
||||
push(str(pop())); return
|
||||
if op == 8: # GET_GLOBAL_PROP -> c[key]
|
||||
push(f"c[{pop().text}]"); return
|
||||
if op == 14: # SET_PROP
|
||||
val = pop(); ref = pop()
|
||||
out.append(f"{ref} = {val};"); return
|
||||
if op == 10:
|
||||
push(f"typeof {pop().text}"); return
|
||||
if op == 11:
|
||||
push(f"delete {pop().text}"); return
|
||||
if op == 12:
|
||||
push(f"!{pop().text}"); return
|
||||
if op == 38:
|
||||
push(f"Object.keys({pop().text})"); return
|
||||
|
||||
# --- 算术/比较
|
||||
if op in BINOP:
|
||||
b = pop(); a = pop()
|
||||
push(f"({a.text} {BINOP[op]} {b.text})"); return
|
||||
|
||||
# --- 栈操作
|
||||
if op == 2:
|
||||
st.append(Expr("v", st[-1].text) if st else Expr("v", "<?>")); return
|
||||
if op == 51:
|
||||
pop(); return
|
||||
if op == 54: # REVERSE_TOP2 -> [a, b].reverse()
|
||||
b = pop(); a = pop(); push(f"[{b.text}, {a.text}]"); return
|
||||
if op == 17: # SWAP n
|
||||
n = ops[0]
|
||||
if len(st) >= 2 + n:
|
||||
i = len(st) - 2 - n
|
||||
st[i], st[-1] = st[-1], st[i]
|
||||
return
|
||||
if op in (53, 42, 69): # SET_STACK_LEN / PUSH_UNDEFINED / CLEAR_EXCEPTION
|
||||
return
|
||||
if op == 1:
|
||||
out.append("// (异常检查: 结束)"); return
|
||||
|
||||
# --- 调用
|
||||
if op in (6, 34, 61, 41):
|
||||
n = ops[0]
|
||||
args = [pop().text for _ in range(n)][::-1]
|
||||
if op == 34:
|
||||
ref = pop()
|
||||
push(f"{ref}({', '.join(args)})")
|
||||
else:
|
||||
fn = pop()
|
||||
push(f"{fn.text}({', '.join(args)})")
|
||||
return
|
||||
|
||||
# --- 闭包
|
||||
if op == OP_CREATE_CLOSURE:
|
||||
target = self.z[pc + 1]
|
||||
capn = self.z[pc + 2]
|
||||
parn = self.z[pc + 3]
|
||||
pos = pc + 4
|
||||
caps = []
|
||||
for _ in range(capn):
|
||||
caps.append(f"r{self.z[pos]}<-r{self.z[pos+1]}"); pos += 2
|
||||
params = [f"p{i}->r{self.z[pos+i]}" for i in range(parn)]
|
||||
nm = self._func_name(target)
|
||||
out.append(f"// closure {nm}({', '.join(params)}) caps=[{', '.join(caps)}] target=@{target}")
|
||||
push(nm)
|
||||
return
|
||||
|
||||
# --- 控制流
|
||||
if op == OP_JUMP:
|
||||
out.append(f"goto @{ops[0]};"); return
|
||||
if op == OP_JUMP_IF_TRUE:
|
||||
c = pop()
|
||||
out.append(f"if ({c.text}) goto @{ops[0]};"); return
|
||||
if op == OP_PUSH_TRY:
|
||||
out.append(f"try {{}} catch -> @{ops[0]} (var r{ops[1]})"); return
|
||||
if op == 48:
|
||||
out.append("// pop try"); return
|
||||
if op == 49:
|
||||
out.append(f"throw {pop().text};"); return
|
||||
if op == 26:
|
||||
push(f"next({pop().text})"); return
|
||||
if op == 28:
|
||||
v = pop(); push("[c, " + v.text + "]"); return
|
||||
# 其它未覆盖: 记录原名, 不破坏栈
|
||||
out.append(f"// [unhandled {OP_NAMES.get(op, op)} {ops}]")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
path = sys.argv[1] if len(sys.argv) > 1 else "tcaptcha/core_js/tdc_sample.js"
|
||||
func = None
|
||||
if "--func" in sys.argv:
|
||||
func = int(sys.argv[sys.argv.index("--func") + 1].lstrip("@"))
|
||||
js = open(path, encoding="utf-8", errors="replace").read()
|
||||
z = extract_bytecode(js)
|
||||
lf = Lifter(z)
|
||||
text = lf.run(func if func is not None else 0, "vm_entry" if func is None else f"func_{func}")
|
||||
out = path.rsplit(".", 1)[0] + ".lifted.txt"
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
f.write(text + "\n")
|
||||
print(f"bytecode={len(z)} lifted functions={len(lf.funcs)} -> {out}")
|
||||
print("--- 头 40 行 ---")
|
||||
print("\n".join(text.splitlines()[:40]))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user