内存参数在堆栈上的位置 x86_64 gcc

Position of Memory parameters on the Stack x86_64 gcc

我从汇编开始,为了测试,我编写了一个简单的 C 程序,对其进行了编译和反汇编,以查看参数是如何传递的。这是 C 代码:

#include <stdio.h>
#include <stdlib.h>

void calc (float*a,float*b,float*c,float*d) {
    a[0]=1000;
    b[0]=100.0;
    c[0]=99.9;
    d[0]=10000;
}


int main() {
    float a[100];
    float b[100];
    float c[100];
    float d[100];
    calc(a,b,c,d);
}   

这是它的反汇编:

default rel

global calc: function
global main: function


SECTION .text   align=1 execute                         ; section number 1, code

calc:   ; Function begin
        push    rbp                                     ; 0000 _ 55
        mov     rbp, rsp                                ; 0001 _ 48: 89. E5
        mov     qword [rbp-8H], rdi                     ; 0004 _ 48: 89. 7D, F8
        mov     qword [rbp-10H], rsi                    ; 0008 _ 48: 89. 75, F0
        mov     qword [rbp-18H], rdx                    ; 000C _ 48: 89. 55, E8
        mov     qword [rbp-20H], rcx                    ; 0010 _ 48: 89. 4D, E0
                   ; 0054 _ 90
        pop     rbp                                     ; 0055 _ 5D
        ret                                             ; 0056 _ C3
; calc End of function

main:   ; Function begin
        push    rbp                                     ; 0057 _ 55
        mov     rbp, rsp                                ; 0058 _ 48: 89. E5
        sub     rsp, 1600                               ; 005B _ 48: 81. EC, 00000640
        lea     rcx, [rbp-640H]                         ; 0062 _ 48: 8D. 8D, FFFFF9C0
        lea     rdx, [rbp-4B0H]                         ; 0069 _ 48: 8D. 95, FFFFFB50
        lea     rsi, [rbp-320H]                         ; 0070 _ 48: 8D. B5, FFFFFCE0
        lea     rax, [rbp-190H]                         ; 0077 _ 48: 8D. 85, FFFFFE70
        mov     rdi, rax                                ; 007E _ 48: 89. C7
        call    calc                                    ; 0081 _ E8, 00000000(rel)
        mov     eax, 0                                  ; 0086 _ B8, 00000000
        leave                                           ; 008B _ C9
        ret                                             ; 008C _ C3
; main End of function

我不明白为什么堆栈上的参数大小不同。第一个在 [ebp-8H] 中,这是可以理解的,因为它是一个 64 位地址,但下一个只多了两个字节,在 [ebp-10H] 而不是 [ebp-16H] 中。
为什么会这样,而且最重要的是,当我编写一个采用这些确切参数的汇编程序时,我应该使用 ebp?

中的哪些地址

这个好像说了很多,估计你还没听过,所以还是要重复一遍:分析未优化代码的反汇编,很大程度上是浪费时间。禁用优化后,编译器会专注于两件事:

  1. 尽可能快地生成代码,以便您获得尽可能快的编译,并且
  2. 使您调试代码变得容易(例如,确保您可以在每个高级语言语句上设置断点,并且不对指令重新排序以允许您单步执行代码)。

未优化的代码混乱、丑陋且令人困惑。它包含大量冗余指令,看起来不像人类会编写的代码,并且与实际应用程序中的代码不匹配(在启用优化的情况下编译)。

当你想分析汇编代码时,打开优化器。

当我们这样做时,我们看到您的代码编译为:

calc(float*, float*, float*, float*):
    mov     DWORD PTR [rdi], 0x447a0000
    mov     DWORD PTR [rsi], 0x42c80000
    mov     DWORD PTR [rdx], 0x42c7cccd
    mov     DWORD PTR [rcx], 0x461c4000
    ret

main:
    xor     eax, eax
    ret

等等,发生了什么事?好吧,优化器发现 main 除了 return 0(隐式地;甚至没有在您的代码中表示)之外, 不会 任何事情,因此它转换了整个函数只是一条清除 EAX 寄存器然后 returns.

的指令

不过,从这里我们可以看出函数的结果是 returned in EAX。这在 Unix 系统上常见的 System V AMD64 调用约定中是正确的,在 Windows 上使用的 64 位调用约定中也是如此,甚至在您将要使用的所有 32 位 x86 调用约定中也是如此在野外寻找。 (32 位结果在 EAX 中被 return 编辑;64 位结果在 EDX:EAX 中被 return 编辑,其中高位在 EDX 中低位在 EAX.)

我们还可以通过查看 calc 函数的反汇编来判断它是如何接收参数的。第一个整数参数在 RDI 中传递,第二个在 RSI 中传递,第三个在 RDX 中传递,第四个在 RCX 中传递。根据 System V AMD64 调用约定,如果有第五个参数,它将在 R8 中传递,第六个参数将在 R9.

中传递

换句话说,寄存器中最多传递前六个整数参数。之后,任何额外的整数参数都会在堆栈上传递。

浮点参数在XMM寄存器中传递(XMM0XMM7),方便SSE指令的使用。同样,任何额外的浮点参数都在堆栈上传递。

你试图在评论中区分"integer parameters"和"memory parameters",但没有后者。当您传递指针(或 C++ 中的引用,编译器根据指针实现)时,您实际上传递的是 地址 。由于地址只是整数,它们就像任何其他整数值一样在寄存器中传递。

如果在栈上传递参数,都是8字节(64位)大小,一个接一个。第一个与堆栈指针的偏移量为 8,RBP。第二个偏移量为 16,等等。当您查看问题中的代码时,似乎有点混乱,因为偏移量以 hexadecimal 表示,其中 10h 相当于十进制的 16,而 18h 相当于十进制的 24。 (为什么第一个参数从偏移量8开始?因为第一个位置RBP+0被return指针占用了。)

这基本上涵盖了调用约定的基础知识。但坦率地说,分析反汇编 不是 学习调用约定的好方法。还有很多你不一定会看到的细节,你也不会得到全局视图。你真的需要 read the fine manual. If you hate manuals, there are more concise (and more simplified) summaries available various places online, e.g., Wikipedia.