2017-06-14 45 views
2

可有人向我解释为什么我们在主函数@0x6f5移动在rax价值rdi,然后在rdi值复制到get_v的堆栈,然后转移回rax @0x6c8?也许这是x86-64的惯例,但我不明白它的逻辑。为什么要将相同的值复制到他已有的rax中?

main: 
    0x00000000000006da <+0>:  push rbp 
    0x00000000000006db <+1>:  mov rbp,rsp 
    0x00000000000006de <+4>:  sub rsp,0x10 
    0x00000000000006e2 <+8>:  mov rax,QWORD PTR fs:0x28 
    0x00000000000006eb <+17>: mov QWORD PTR [rbp-0x8],rax 
    0x00000000000006ef <+21>: xor eax,eax 
    0x00000000000006f1 <+23>: lea rax,[rbp-0xc] 
=>0x00000000000006f5 <+27>: mov rdi,rax 
    0x00000000000006f8 <+30>: call 0x6c0 <get_v> 
    0x00000000000006fd <+35>: mov eax,0x0 
    0x0000000000000702 <+40>: mov rdx,QWORD PTR [rbp-0x8] 
    0x0000000000000706 <+44>: xor rdx,QWORD PTR fs:0x28 
    0x000000000000070f <+53>: je  0x716 <main+60> 
    0x0000000000000711 <+55>: call 0x580 
    0x0000000000000716 <+60>: leave 
    0x0000000000000717 <+61>: ret  

get_v 
    0x00000000000006c0 <+0>:  push rbp 
    0x00000000000006c1 <+1>:  mov rbp,rsp 
    0x00000000000006c4 <+4>:  mov QWORD PTR [rbp-0x8],rdi 
=>0x00000000000006c8 <+8>:  mov rax,QWORD PTR [rbp-0x8] 
    0x00000000000006cc <+12>: mov DWORD PTR [rax],0x2 
    0x00000000000006d2 <+18>: mov rax,QWORD PTR [rbp-0x8] 
    0x00000000000006d6 <+22>: mov eax,DWORD PTR [rax] 
    0x00000000000006d8 <+24>: pop rbp 
    0x00000000000006d9 <+25>: ret  
+1

因为代码是完全没有优化和一个简单的翻译的任何更高的语言代码为ASM?此外,该函数如何知道rax有什么价值?它超出了它的范围。 –

+1

顺便说一句,'FS:0x28'的东西是堆栈金丝雀,以防止堆栈粉碎漏洞。 gcc包含它,因为'-fstack-protector'被启用。另见http://mudongliang.github.io/2016/05/24/debian-gcc-stack-protector-examples.html –

回答

7

这是未优化代码。这里有很多指令是多余的,并且没什么意义,所以我不确定为什么你已经修复了特定的指示。考虑说明书紧接其前:

其上的64位的低32位操作寄存器隐含地清除上部比特
xor eax,eax 
lea rax,[rbp-0xc] 

首先,RAX被清除(指令,因此xor reg32, reg32相当于稍超过最佳xor reg64, reg64),然后RAX加载一个值。完全没有理由首先清除RAX,所以第一条指令可能已经完全消失了。

在此代码:

lea rax,[rbp-0xc] 
mov rdi,rax 

RAX被加载,然后将其值被复制到RDI。如果您在RAXRDI中都需要相同的值,那么这很有意义,但您不需要。该值只需在RDI中准备函数调用。 (System V的AMD64调用约定通过在RDI寄存器中的第一个整数参数),所以这可以简单过:

lea rdi, [rbp-0xc] 

但同样,这是未经优化的代码。编译器优先考虑快速代码生成以及在生成高效代码(生成时间更长,更难调试)的情况下在单个(高级语言)语句上设置断点的功能。

get_v堆叠中的周期性溢出重载是未优化的代码另一个症状:需要

mov QWORD PTR [rbp-0x8],rdi 
mov rax,QWORD PTR [rbp-0x8] 

的此无。这只是忙碌的工作,一个普通的未经优化的代码。在优化版本或手写汇编中,它将简单地写成寄存器到寄存器的移动,例如

mov rax, rdi 

你会看到,GCC 总是跟着你在未经优化的建立已经观察到的模式。考虑一下这个功能:

void SetParam(int& a) 
{ 
    a = 0x2; 
} 

随着-O0(优化禁用),GCC发出以下内容:

SetParam(int&): 
    push rbp 
    mov  rbp, rsp 
    mov  QWORD PTR [rbp-8], rdi 
    mov  rax, QWORD PTR [rbp-8] 
    mov  DWORD PTR [rax], 2 
    nop 
    pop  rbp 
    ret 

眼熟?

现在能够优化,我们得到了更明智的:

SetParam(int&): 
    mov  DWORD PTR [rdi], 2 
    ret 

这里,店里直接进行到在RDI寄存器传递地址。不需要设置或拆除堆栈帧。实际上,堆栈被完全绕过。代码不仅更简单,更易于理解,而且速度也更快。

这是一个教训:当您尝试分析编译器的目标代码输出时,始终启用优化。学习未优化的版本在很大程度上是浪费时间,除非您真的对编译器如何生成未优化的代码感兴趣(例如,例如,因为您正在编写或反编译器本身)。否则,你关心的是优化代码,因为它更容易理解,更加真实。

你的整个get_v函数可以是简单的:

mov DWORD PTR [rdi], 0x2 
mov eax, DWORD PTR [rdi] 
ret 

没有理由使用堆栈,洗牌值来回。没有理由重新加载地址为RBP-8的数据,因为我们已将该值加载到RDI中。

但实际上,我们可以做得比这更好,因为我们是移动不断到存储在RDI地址:

mov DWORD PTR [rdi], 0x2 
mov eax, 0x2 
ret 

事实上,这正是GCC产生什么,我想象是您get_v功能:

int get_v(int& a) 
{ 
    a = 0x2; 
    return a; 
} 

未优化

get_v(int&): 
    push rbp 
    mov  rbp, rsp 
    mov  QWORD PTR [rbp-8], rdi 
    mov  rax, QWORD PTR [rbp-8] 
    mov  DWORD PTR [rax], 2 
    mov  rax, QWORD PTR [rbp-8] 
    mov  eax, DWORD PTR [rax] 
    pop  rbp 
    ret 

优化

get_v(int&): 
    mov  DWORD PTR [rdi], 2 
    mov  eax, 2 
    ret 
相关问题