首先将原始的控制流图节点结构进行转换,方便后续进行分析:
原始节点格式如下:
jsconst node = {
astNode: astNode, // AST 语法节点
postNodes: [], // 后继节点在 nodeGraph 中的索引列表
index: nodeGraph.length, // 当前节点在 nodeGraph 中的位置
};
转换后的目标块结构如下:
js{
index: 0, // 当前块编号(唯一)
type: "direct" | "condBranch", // 块类型:顺序执行 or 条件分支
astNodes: [], // 所包含的 AST 节点(通常是一组语句)
entryCond: null, // 如果是条件块,记录条件表达式(Babel AST 节点)
directNext: null, // 顺序块的下一跳 index
trueBranch: null, // 条件为 true 跳转的块 index(条件块专用)
falseBranch: null, // 条件为 false 跳转的块 index(条件块专用)
predecessors: [], // 所有前驱块 index 列表
successors: [], // 所有后继块 index 列表
}
使用如下步骤完成转化:
jsupdatePreNodes(nodeGraph, enterNode.index); // 更新每个节点的前驱信息
processEmptyStatement(nodeGraph); // 去除无效或空语句节点
const { blocks, enterIndex } = buildControlFlowBlocks(nodeGraph, enterNode.index);
buildControlFlowBlocks
的核心作用是将一组顺序执行的语句合并到一个逻辑块(基本块),便于后续分析。主函数如下:
js/**
* 构建并拆解 SSA 的主函数
* @param {Array} blocks - 控制流图块列表(基本块)
* @param {number} entry - 入口块索引(默认 0)
*/
function buildAndDestructSSA(blocks, entry = 0) {
// Step 1: 计算支配关系
const Dom = computeDominators(blocks, entry); // 所有块的支配集合
const idoms = computeImmediateDoms(Dom, blocks); // 每个块的最近支配者(iDom)
const domChildren = buildDomChildren(idoms); // 支配树子节点映射
// Step 2: 计算支配前沿(dominance frontier)
const DF = computeDomFrontier(blocks, Dom);
// Step 3: 收集变量的定义位置
const defSites = collectDefSites(blocks); // defSites: 变量 -> 块索引数组
// Step 4: 活跃性分析
computeFullLiveness(blocks); // 分析每个块中变量的 live-in/live-out
// Step 5: 插入 φ 函数
insertPhi(blocks, DF, defSites); // 在 DF 上插入 φ 节点
// Step 6: 进行 SSA 重命名(核心:唯一化变量版本)
renameSSA(blocks, entry, domChildren);
// Step 7: 拆解 φ 函数为真实赋值
const idomsForDestruct = idoms;
destructPhiAndInsertBlocks({ blocks }, idomsForDestruct);
}
js/**
* 从 CFG 结构化生成 AST,支持 while + break/continue
*/
function decompileCFGWithLoops(blocks, enter, loops) {
const postDom = computePostDominators(blocks);
function findNearestCommonPostDominator(n, m) {
const sN = postDom[n], sM = postDom[m];
const common = [...sN].filter(x => sM.has(x));
if (!common.length) return null;
common.sort((a, b) => postDom[b].size - postDom[a].size);
return common[0];
}
const loopsByHead = new Map(loops.map(l => [l.head, l]));
function region(cur, stop, currentLoop) {
const stmts = [];
while (cur != null && cur !== stop) {
const blk = blocks[cur];
// 循环头
if (loopsByHead.has(cur) && (!currentLoop || currentLoop.head !== cur)) {
const loop = loopsByHead.get(cur);
const bodyStmts = region(loop.head, loop.exit, loop);
stmts.push(t.whileStatement(t.booleanLiteral(true), wrapNodesInBlockStatement(bodyStmts)));
cur = loop.exit;
continue;
}
// 条件分支
if (blk.type === 'condBranch') {
const [t0, f0] = blk.successors;
const merge = findNearestCommonPostDominator(t0, f0);
const thenStmts = [];
const elseStmts = [];
// 处理 then 分支
if (currentLoop) {
if (t0 === currentLoop.exit) {
thenStmts.push(t.breakStatement());
} else if (t0 === currentLoop.head) {
thenStmts.push(t.continueStatement());
} else {
thenStmts.push(...region(t0, merge, currentLoop));
}
} else {
thenStmts.push(...region(t0, merge, currentLoop));
}
// 处理 else 分支
if (currentLoop) {
if (f0 === currentLoop.exit) {
elseStmts.push(t.breakStatement());
} else if (f0 === currentLoop.head) {
elseStmts.push(t.continueStatement());
} else {
elseStmts.push(...region(f0, merge, currentLoop));
}
} else {
elseStmts.push(...region(f0, merge, currentLoop));
}
// 生成 if
stmts.push(...blk.astNodes);
stmts.push(
t.ifStatement(
blk.entryCond,
wrapNodesInBlockStatement(thenStmts),
elseStmts.length ? wrapNodesInBlockStatement(elseStmts) : null
)
);
cur = merge;
continue;
}
// 直接跳转
stmts.push(...blk.astNodes);
const nxt = blk.directNext;
if (currentLoop) {
if (nxt === currentLoop.exit) { stmts.push(t.breakStatement()); return stmts; }
if (nxt === currentLoop.head) { stmts.push(t.continueStatement()); return stmts; }
}
cur = nxt;
continue;
}
return stmts;
}
return region(enter, null, null);
}
可以看到反编译的代码都变成了静态单赋值形式,接下来要进行中间变量的合并,以消除代码行数,以及可以对代码进行的各种优化
后续更新啦
本文作者:韦峰
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!