有人可以向我解释为什么我们在主函数rax中将rdi中的值移动到@0x6f5,然后将rdi中的值复制到get_v的堆栈中,然后将其移回到?。也许这是x86-64的约定,但我不了解其逻辑。

 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

最佳答案

这是未优化的代码。这里有很多多余的说明,没有什么意义,所以我不确定为什么您要固定在特定的指示上。请考虑紧接其之前的说明:

xor    eax,eax
lea    rax,[rbp-0xc]


首先,清除RAX(对64位寄存器的低32位进行操作的指令会隐式清除高位,因此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

关于assembly - 为什么将相同的值复制到他已经拥有的rax?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44534733/

10-11 12:02