这是一个家庭作业问题。 我试图从以下汇编代码(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个表达是做什么的?我觉得我让它们以某种方式与增量混淆,但不知道变量我不知道如何解决这个问题。
任何和所有帮助都会很棒,谢谢!
答案 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);
}
在集会中:
rsi
是result
。我们推导出这是因为它是获得OR
的唯一值,它是printf
的第二个参数(在x64 linux中,参数存储在rdi
中,{{1} },rsi
和其他一些,按顺序)。
rdx
是一个设置为x
的常量。这肯定是不可扣除的,但它有意义,因为它似乎在C代码中被设置为常量,并且在此之后似乎没有设置。
现在其余部分,它被GCC的反优化混淆了。你看,GCC检测到循环将被执行21次,并且认为是巧妙地破坏条件并用无用的计数器替换它。知道这一点,我们发现0xDEADBEEF
是无用的计数器,edx
是rcx
。然后我们可以推导出真实情况和真正的“增量”操作。我们可以在程序集中看到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并查看其功能。