将 CIL 代码反编译为一些高级代码 - 在数据流分析过程中是否需要引入新变量?

逆向工程 反编译 反编译 反编译器 编译器 编译器优化
2021-06-21 19:03:24

我正在编写一个从 .NET CIL 代码到某种高级语言的编译器。过程类似于反编译。我做了一些控制流分析——检测循环、ifs 等等。在数据流分析方面,我通过模拟一些涉及评估堆栈的指令完成了简单的表达式传播 - 我将评估堆栈视为隐藏变量,将更复杂的表达式推送到其上,并且每当有对某个变量的任何赋值指令(例如stargstloc) - 我弹出并将传播的表达式从堆栈分配给这个变量,并将这个表达式转换为高级语言代码中的语句。当然,现在它是如此简单,以至于它会产生故障。考虑一个用 C# 编写的函数:

int GCD(int n1, int n2)
{
    while(n2 != 0)
    {
        int c = n1 % n2;
        n1 = n2;
        n2 = c;
    }

    return n1;
}

此函数编译为 IL:

.method private hidebysig 
    instance int32 GCD (
        int32 n1,
        int32 n2
    ) cil managed 
{
    .maxstack 8

    IL_0000: br.s IL_000a
        IL_0002: ldarg.1    // load n1 on eval stack
        IL_0003: ldarg.2    // load n2 on eval stack
        IL_0004: rem        // pop n1 and n2 from stack, compute n1 % n2 and push it on stack
        IL_0005: ldarg.2    // load n2 on stack
        IL_0006: starg.s n1 // pop n2 from stack and store it in n1
        IL_0008: starg.s n2 // pop n1 % n2 from stack and store it in n2

        IL_000a: ldarg.2
        IL_000b: brtrue.s IL_0002

    IL_000d: ldarg.1
    IL_000e: ret
}

通过这个简单的传播,我们n1 % n2压栈,然后加载n2栈,然后我们有starg指令,所以我们从栈中弹出表达式并将赋值转换为语句。然后我们再次弹出,并执行相同的操作。结果代码如下所示:

GCD(n1, n2) {
    while (n2) { 
        n1 = n2;
        n2 = (n1 % n2); 
    }
    return n1;
}

这表明我必须做一些与消除死代码相反的事情,可能被称为“必要的代码引入”。我搜索了一些有关在反编译中引入新变量的方法的资料,但没有找到。你有什么想法?

1个回答

正如我认为您意识到的那样,问题在于您在逻辑上将表达式的计算移动到超过其输入值之一被更改的点。

鉴于您当前的实施,我建议处理此问题的最简单方法是 -

  • 当指令写入变量时,查看表达式堆栈中是否有任何涉及此变量的表达式。
  • 如果有,
    • 将每个这样的现有表达式溢出到一个新的临时变量,以及
    • 用相应的新临时变量替换堆栈上的现有表达式。
  • 然后才生成对原始变量的写入

将此过程应用于您的代码给出 -

IL_0002: // stack = { n1 }

IL_0003: // stack = { n2, n1 }

IL_0004: // stack = { n1 % n2 }

IL_0005: // stack = { n2, n1 % n2 }

IL_0006: // this instruction will write to n1
         // inspection shows that we have an expression involving n1 on the stack so
         // (a) spill this expression to a new temporary variable and
         // (b) replace the expression on the stack with the new temporary variable

         temp = n1 % n2;     // stack = { n2, temp }

         // now the write to n1 can safely take place

         n1 = n2;            // stack = { temp };

IL_0008: // this instruction will write to n2, 
         // inspection shows no expressions on the stack involving n2
         // the write to n2 can safely take place

         n2 = temp;         // stack = {}

删除注释,内部循环现在看起来像原始代码。

temp = n1 % n2;
n1 = n2;
n2 = temp;