我正在为一种简单的脚本语言编写反编译器。这就是我所做的:
基本块
创建了一个基本块的集合,如下所述:
http://www.backerstreet.com/decompiler/basic_blocks.php
控制流图,支配树和循环集
由此我可以创建控制流图。
http://www.backerstreet.com/decompiler/control_flow_graph.php
从CFG我创建了支配树,这样我就可以在CFG中找到循环集,如下所述:
http://www.backerstreet.com/decompiler/loop_analysis.php
这是一张包含我目前所有数据的图片:
构建循环
我的下一步应该是:
http://www.backerstreet.com/decompiler/creating_statements.php
这就是我的问题所在,因为我完全陷入困境。在我给定的数据中,结构化循环算法将如何应用?我不明白为什么它首先尝试将所有内容组织为do while循环 - 在我的示例中,这意味着块3中的“temp5_14 = temp5_14 + 16”将始终至少执行一次,这不是原始代码所执行的操作一点都不
如何才能工作?如何将它从do-while转换为while循环的下一步实际上有效?对于在块6中结束的循环3,这看起来应该是一段时间(true) - 但是当它的head块是if语句时,它如何与算法一起工作?
TL; DR - 有人请举例说明“结构化循环”算法是如何实际运作的。
答案 0 :(得分:6)
结构化是反编译器开发中最难的部分(至少对于高级语言而言)。这是一个相当简单的算法,因此它是一个很好的起点,但如果您正在使用真正的反编译器,那么您可能想要使用更好的算法或制作自己的算法。
有了这个问题,你在链接的页面上已经回答了关于如何使用do-while循环而不是while循环的实际问题的答案。
每个循环都可以用" do-while"来描述。言。
" while"循环(预测试循环)是" do-while"的特殊情况。 循环,底部条件始终为真,第一个 循环语句是" if"跳出循环。
说你有像
这样的东西beforeloop
while(foo) {
stmt1
stmt2
}
afterloop
它将被编译为
的内容beforeloop
LOOPBEGIN:
if !foo goto LOOPEND
stmt1
stmt2
goto LOOPBEGIN
LOOPEND:
afterloop
反编译器算法将其转换为
beforeloop
do {
if (!foo) {break}
stmt1
stmt2
} while (true)
afterloop
我希望清除它。如果没有,请随时询问任何其他问题。
编辑:示例2,显示如何折叠具有相同入口点的多个循环。
for(;;) { while(foo) {} while(bar){} }
首先,for(;;)
相当于while(true)
,因此我将使用以下(伪)代码
while(true) { while(foo) {stmt1} while(bar){stmt2} }
让外部循环为循环A,内部循环为循环B和C.这将编译为类似下面的伪程序集。
LOOP_A_BEGIN:
LOOP_B_BEGIN:
if !foo goto LOOP_B_END
stmt1
goto LOOP_B_BEGIN
LOOP_B_END:
LOOP_C_BEGIN:
if !bar goto LOOP_C_END
stmt2
goto LOOP_C_BEGIN
LOOP_C_END:
goto LOOP_A_BEGIN
但当然标签不占用任何空间。因此,相同的标签折叠后,它变为
POINT1:
if !foo goto POINT2
stmt1
goto POINT1
POINT2:
if !bar goto POINT3
stmt2
goto POINT2
POINT3
goto POINT1
现在,有两个支持点 - 第1点和第2点。我们可以为每个节点创建一个循环,为清晰起见使用带标记的断点。变换并不是那么简单,因为你必须稍微搞乱if语句,但它仍然很容易。
LOOP1: while(true) {
IF1: if (!foo) {
break IF1;
}
else {
stmt1;
continue LOOP1;
}
LOOP2: while(true) {
if (!bar) {
break LOOP2;
}
else {
stmt2;
continue LOOP2;
}
}
continue LOOP1;
}
现在,带有不必要标签的相同代码简化了
while(true) {
if (!foo) {
}
else {
stmt1;
continue;
}
while(true) {
if (!bar) {
break;
}
else {
stmt2;
}
}
}
现在使用if语句简化
while(true) {
if (foo) {
stmt1;
continue;
}
while(true) {
if (!bar) {
break;
}
stmt2;
}
}
最后,您可以将while(true) if(!x)
变换应用于内循环。外环不能像这样进行转换,因为它不是简单的while(cond)循环,因为它是合并循环的结果。
while(true) {
if (foo) {
stmt1;
continue;
}
while(bar) {
stmt2;
}
}
所以希望这能说明如何通过将它们合并到一个循环中来总是处理具有相同入口点的多个循环的情况,同时也可能需要重新排列一些if语句。
答案 1 :(得分:5)
有一篇名为No More Gotos的论文提出了一种用于模式无关控制流结构的算法。这是理解和实施的一大部分工作,但我将它用于我的高级项目,它看起来很有效。
我的实施可以在zneak/fcd的GitHub上找到。该项目使用LLVM IR作为中间表示。
编辑在很高的层次上,这就是算法的工作原理:
只是为了确保理解这些概念:如果从图形条目到Z 的任何路径通过A,则节点A支配节点Z.类似地,节点Z post - 如果从A到图表末尾的任何路径都必须通过Z,则支持节点A.
算法构建区域。区域是图形的一部分,具有单个入口边缘和单个出口边缘。我亲自将这个定义扩展到基本块(区域有一个入口块和一个出口块),并使得出口块被排除在该区域之外。
我使用的区域的定义是:
该定义意味着该条目通过出口节点支配每个节点,并且退出节点后来支配该条目中的每个节点。
循环是一个带有“后边缘”的区域(如果您以深度优先遍历图形,则边缘将返回到已访问过的节点)。
确保循环表示为具有单个后沿的单入口单出口区域。也就是说,它们应该只有一个入口节点(后边缘也指向该节点)和一个后继节点。如果不是这种情况,您可以引入一个新的输入块并使所有边指向它,然后使用Φ节点从那里转发执行(换句话说,引入您在每个传入结束时设置的变量阻止,并从新块中执行if (var == 0) { first entry } else if (var == 1) { second entry }
。
在我的实现中,这发生在写入时在主分支的StructurizeCFG传递中。然而,它产生的结果很差,因为它比它需要的工作更难。我只需要它来构造循环,但它也构造了if-else结构,虽然它没有破坏算法,但它引入了许多Φ节点的waaaay以产生漂亮的输出。在撰写时,还有一个名为seseloop
的分支,其custom pass确保循环是单项单退出。如果不需要,这个传递不会触及if-else构造。
按顺序遍历基本程序段图。确定从此区块开始的区域。你可以使用post-dominator树来加速这一点,因为一个区域必须以一个块的后支配者结束(因此对于每个块,只检查块的后支配者)。
如果输入块具有指向它的后边缘,则将其结构化为循环。如果没有,请将其结构化为区域。区域结构化后,将其作为折叠的单个节点放回到图形中,该节点可以包含在另一个更大的结构化区域中。
这发生在ast/ast_backend.cpp。
在区域上使用深度优先遍历(跳过循环)来识别导致执行任何块的条件。例如:
节点A没有条件。如果节点A结尾的条件为真,则到达节点B.如果它是假的,则到达节点C.如果节点D为真或假,则到达节点D.
(A);
if (a_cond) (B);
if (!a_cond) (C);
if (a_cond || !a_cond) (D);
然后你必须简化这些条件,遗憾的是这是NP完全问题。一般来说,通过折叠A; if (a_cond) B; else C; D;
并按顺序比较条件术语来回到a_cond || !a_cond
之类的东西应该不会太难。
你基本上做同样的事情就好像你正在构建一个区域而不关心它是一个循环,但之后你在可以退出循环的块的末尾添加break语句(相关条件)并且你包装while true
块中的区域。然后,作者已经确定了可以用更易读的模式替换的6种模式(例如,以while true
开头的if (cond) break
可以转换为while !cond
。
就是这样。