实现反编译的一种常用技术是使用称为“区间分析”的方法来识别循环的范围。
当与成语识别和称为“图的派生序列”的模式结合使用时,可以从包含汇编语言(或 MSIL)的控制流图开始,然后迭代地简化它,直到您拥有一个 AST(抽象语法树)节点表示程序(或方法)的“源级别”视图。给定 AST,生成源代码就很简单了:然后您就可以漂亮地打印生成的 AST。
以下是更多信息的链接:
http://en.wikipedia.org/wiki/Interval_(graph_theory)
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.50.8004&rep=rep1&type=pdf
通常,原始源代码和反编译的源代码之间永远不会完全保真。
foreach 循环的控制流图类似于 while 循环的控制流图。这主要是因为下面的代码:
foreach (var x in xs) {
//body
}
实际上是语法糖:
var enumerator = xs.GetEnumerator()
try {
while (enumerator.MoveNext()) {
var x = xs.Current;
//body
}
}
finally {
enumerator.dispose();
}
即foreach循环基本翻译成while循环,然后while循环编译成MSIL。
为了让反编译器产生一个 for-each 循环,它必须添加特殊支持来尝试猜测 while 循环何时实际上是一个 foreach 循环。任何这样的逻辑都不会是完美的(上面的 while 循环和 foreach 循环都应该产生相同(或非常相似)的 MSIL 代码)。
在某些情况下它会匹配您编写的源代码,而在其他情况下则不会。
您可能已经编写了一个 for-each 循环,因此从可用性的角度来看,在 for-each 循环和 while 循环方面犯错是一个不错的选择。
但是,这是额外的工作。反编译器编写者必须开始说“我想添加一堆启发式方法来尝试检测 for-each 循环”。
最后,有很多事情会阻碍反编译。例如,“break”和“continue”语句的存在确实会使事情复杂化。某些类型的嵌套也是如此(switch 语句中的循环,反之亦然)。它们都可能导致 CFG 循环具有多个入口点和多个出口点。这增加了生成可读源代码的难度。
通常处理这些情况的唯一方法是使用启发式方法。这些启发式方法有时会“做错事”。
此外,即使是很小的事情,例如用于循环边界的特定表达式,也会破坏习语识别。有时编译器会引入临时变量(源代码中不存在)。这可能需要额外的习语检查,或者更高级的技术,例如数据流分析(实时变量分析、定义-使用链等)。
总结:反编译很难,而且永远不会完美。此外,我确信实施者必须考虑权衡。例如,投资检测 for-each 循环是否有意义,还是应该花时间反编译 lambda 和推断 LINQ 查询?