将O2优化的for-loop从汇编转换为C

时间:2015-09-18 08:14:32

标签: c loops assembly x86 reverse-engineering

这是一个家庭作业问题。 我试图从以下汇编代码(x86 linux机器,使用gcc -O2优化编译)获取信息。我评论了每个部分以显示我所知道的内容。我的一大部分假设可能是错误的,但我已经做了足够的搜索,我知道我应该在这里问这些问题。

.section        .rodata.str1.1,"aMS",@progbits,1

.LC0:
    .string "result %lx\n"  //Printed string at end of program
    .text

main:
.LFB13: 
    xorl    %esi, %esi         // value of esi = 0; x
    movl    $1, %ecx           // value of ecx = 1; result
    xorl    %edx, %edx         // value of edx = 0; Loop increment variable (possibly mask?)
.L2:
    movq    %rcx, %rax         // value of rax = 1; ?
    addl    $1, %edx           // value of edx = 1; Increment loop by one;
    salq    $3, %rcx           // value of rcx = 8; Shift left rcx;
    andl    $3735928559, %eax  // value of eax = 1; Value AND 1 = 1;
    orq     %rax, %rsi         // value of rsi = 1; 1 OR 0 = 1;
    cmpl    $22, %edx          // edx != 22 
    jne     .L2                // if true, go back to .L2 (loop again)
    movl    $.LC0, %edi        // Point to string
    xorl    %eax, %eax         // value of eax = 0;
    jmp     printf             // print
.LFE13: ret                    // return

我应该把它变成下面的C代码并填入空白

#include <stdio.h>
int main()
{
 long x = 0x________;
 long result = ______;
 long mask;
 for (mask = _________; mask _______; mask = ________) {
   result |= ________;
}
 printf("result %lx\n",result);
}

我有几个问题和健全性检查,我想确保我正确,因为我找到的类似示例都没有用于优化代码。在自己编译一些试验后,我得到了一些接近但L2的中间部分总是关闭。

我的理解

开始时,esi与其自身xor'd,结果为0,由 x 表示。然后将1添加到ecx,它将由变量结果表示。

x = 0;     result = 1;

然后,我相信循环增量变量存储在edx中并设置为0.这将在for循环的第三部分(更新表达式)中使用。我还认为这个变量必须是掩码,因为稍后将1添加到edx,表示循环增量(mask = mask ++),同时在for循环的中间部分比较edx(测试表达式也称为掩码!= 22 )。

mask = 0; (in a way)

然后输入循环, rax 设置为1.我不明白它的使用位置,因为我没有声明第四个变量,尽管它稍后会显示被淘汰并归零。

movq %rcx, %rax;

然后循环变量加一个

addl $1, %edx;

下一部分让我感受到的最少数量

我认为接下来的三个操作构成了循环的正文表达式,但是我不知道如何处理它们。它会导致类似于结果| = x ...但我不知道还有什么

salq    $3, %rcx      
andl    $3735928559, %eax  
orq     %rax, %rsi

其余的我觉得我很了解。进行比较(如果掩码!= 22,再次循环),并打印结果。

我遇到的问题 我不明白一些事情。

1)我不明白如何找出我的变量。似乎有3个硬编码的,以及在程序集中找到的一个增量或临时存储变量(rax,rcx,rdx,rsi)。我认为rsi将是 x ,而rcx将是结果,但我不确定掩码是否是rdx或rax,无论哪种方式,最后一个变量是什么是

2)我不确定的3个表达是做什么的?我觉得我让它们以某种方式与增量混淆,但不知道变量我不知道如何解决这个问题。

任何和所有帮助都会很棒,谢谢!

2 个答案:

答案 0 :(得分:1)

答案是:

#include <stdio.h>
int main()
{
    long x = 0xDEADBEEF;
    long result = 0;
    long mask;
    for (mask = 1; mask != 0; mask = mask << 3) {
        result |= mask & x;
    }
    printf("result %lx\n",result);
}

在集会中:

rsiresult。我们推导出这是因为它是获得OR的唯一值,它是printf的第二个参数(在x64 linux中,参数存储在rdi中,{{1} },rsi和其他一些,按顺序)。

rdx是一个设置为x的常量。这肯定是不可扣除的,但它有意义,因为它似乎在C代码中被设置为常量,并且在此之后似乎没有设置。

现在其余部分,它被GCC的反优化混淆了。你看,GCC检测到循环将被执行21次,并且认为是巧妙地破坏条件并用无用的计数器替换它。知道这一点,我们发现0xDEADBEEF是无用的计数器,edxrcx。然后我们可以推导出真实情况和真正的“增量”操作。我们可以在程序集中看到mask,并注意到如果向左移动64位int 22次,它变为0(移位3,22次意味着移位66位,所以它全部移出)。

对于海湾合作委员会来说,这种反优化很可悲。装配可以替换为:

<<= 3

它完全相同,但我们删除了无用的计数器并保存了3个装配说明。它也更好地匹配C代码。

答案 1 :(得分:1)

让我们倒退一点。我们知道result必须是printf()的第二个参数。在x86_64调用约定中,那是%rsi。循环是.L2标签和jne .L2指令之间的所有内容。我们在模板中看到循环结束时有result |=行,实际上,orl指令以%rsi为目标,因此检出。我们现在可以在.main的顶部看到它被初始化的内容。

ElderBug是正确的,通过添加计数器进行虚假优化编译器。但是我们仍然可以弄清楚:当循环重复时|=之后会立即运行哪条指令?这必须是循环的第三部分。什么在循环体之前运行?那必须是循环初始化。不幸的是,你必须弄清楚在原始循环的第22次迭代中会发生什么,以对循环条件进行反向工程。 (但sal是一个左移,该行是原始循环条件的遗迹,在插入%rdx测试之前,条件分支后面会跟着它。)

请注意,代码会在mask中保留%rcx左右的副本,然后在%rax中对其进行修改,并将x折叠为常量(仔细查看andl行。

另请注意,您可以将.S文件提供给gas以获取.o并查看其功能。