reccon 用一系列图变换操作,把控制流图(CFG)转为等价的抽象语法树(AST)。在转化过程中,reccon 通过引入新变量和复制节点,消除 goto 语句。
算法大体分为“循环结构化”和“AST转化”两步。
图中可能出现三种非结构化的形式:循环有多个入口、循环有多个出口、异常选择路径。前两种情况在“循环结构化”中处理,最后一种情况在“AST转化”时处理。
利用 DFS 识别每个循环的入口和循环包含的节点。
DFS 时,记录每个点的 DFS 序数(先序和后序),把边分三类(前向边、后向边、横插边)。所有前向边构成图的生成树。
循环头:后向边的目标节点。
循环包含的节点:与循环头 h
双向可达的节点(不经过 h
在生成树上的任何父节点)。
循环之间的包含关系应是树形结构。按 DFS 后序遍历,可以由内到外访问所有循环。
注意! 循环识别结果与 DFS 遍历顺序相关,reccon 总是从图的入口开始,先访问 false 分支再访问 true 分支。
根据 Conversion of Unstructured Flow Diagrams to Structured Form 论文中提出的方法,把图中非结构化的循环转为结构化的循环,即每个循环只有一个入口和至多一个出口。
入口:从一个循环外的点连向一个循环内的点。连向循环头节点的是主入口,其它入口为异常入口。
出口:从一个循环内的点连向一个循环外的点。
对于出口边 (x_i, X_i)
,原图
|
-->--|
| |
| x_0 --> X_0
| |
| x_1 --> X_1
| |
| x_n --> X_n
| |
---<--
添加新变量 b
,记录从循环退出的位置,转为
|
b=-1
-->--|
| b!=-1 -->----------|
| | |
| x_0 -> b=0 -->| b==0 --> X_0
| | | |
| x_1 -> b=1 -->| b==1 --> X_1
| | | |
| x_n -> b=n -->| (b==n) -> X_n
| | |
---<------------<--
只保留主入口。
对于异常入口 (E_i, e_i)
,原图
|
|-<--
| |
pre |
| |
E_i --> e_i |
| |
succ |
| |
->--|
递归复制 e_i
即其后继节点(到主入口为止),转为
|
-->--|-<--
| | |
E_i | pre |
| | | |
e_i | e_i |
| | | |
succ-->| succ |
| |
->--|
按从内到外的顺序遍历每个循环,分别进行 AST 转化。这样每个循环内部包含的小循环已经被处理完了,只需要单个循环。
对于单个循环,把所有连向循环头的边改为 continue,把出口边改为 break。这样单循环图就转化成了有向无环图(DAG)。
对于 DAG,采用 Decompilation of Conirol Structures by Means of Graph Transformations 论文中的方法,逐步把特定结构的子图替换成新节点,直到整个图变成一个点。
上述论文没有处理异常选择路径,因此 reccon 在其基础上,加入短路条件优化,来缓解这一问题。
即如下形式的子图
cond1 ------------>|
| |
(mid1) (mid2)
| |
|<- (mid3) <-- cond2
| |
br1 br2
添加临时变量 p
,转为
if (cond1) {
mid1;
p = true;
}
else {
mid2;
p = cond2;
if (p)
mid3;
}
if (p)
br1;
else
br2;
(当 mid1, mid2, mid3
都不存在时,应转为 cond1 or cond2
)
对于短路条件优化仍没法处理的异常选择路径,使用 A Comb for Decompiled C Code 论文中的方法,复制某些节点,来消除异常选择路径:若存在点 N
,位于某条件节点 C
到 “C
的最近反向支配点”的路径上,且存在不被 C
支配的点 X
和边 (X,N)
,则复制点 N
为 N'
,把 (X,N)
替换为 (X,N')
。
给定一个 CFG 和控制流恢复的结果,reccon 可以自动化验证结果是否正确。这样就可以随机生成一些图来做测试了。
验证方法是,同步在 CFG 和 AST 上模拟执行,遍历所有路径,检查所有状态下 AST 节点是否恰好与 CFG 的节点对应。
模拟执行过程中,维护当前在 CFG 和 AST 上所处的位置,和各个自定义变量的值。
遇到条件节点(对应条件语句),就拆分成两个状态,分别模拟条件为 false 和 true 的情形。
用哈希表记录所有模拟中出现的状态。如果有多个状态位于同一个 CFG 位置,且它们的所有变量的值都相同,则被认为是相同的状态,不再重复模拟。