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

时间:2017-06-13 13:48:20

标签: assembly x86-64 calling-convention

我从Assembly开始,为了测试,我编写了一个简单的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 个答案:

答案 0 :(得分:2)

好像我说了很多,但可能你还没有听过,所以需要重复一遍:分析未经优化的代码的反汇编在很大程度上是浪费时间。当禁用优化时,编译器会关注两件事:

  1. 尽快生成代码,以便获得最快的编译,
  2. 让您轻松调试代码(例如,确保您可以在每个高级语言语句上设置断点,并且不重新排序指令以允许您单步执行代码)。
  3. 未优化的代码混乱,丑陋,令人困惑。它包含许多冗余指令,看起来不像人类会编写的内容,并且与实际应用程序中找到的代码不匹配(在启用优化的情况下编译)。

    如果要分析汇编代码,请启用优化程序。

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

    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除了返回0之外没有任何东西(隐式地;甚至没有在代码中表示),所以它将整个函数转换为一个清除指令的指令EAX注册然后返回。

    但是,我们可以告诉我们EAX中返回了函数的结果。在Unix系统上常见的System V AMD64调用约定中也是如此,在Windows上使用的64位调用约定中也是如此,它甚至可以在所有32位x86调用约定中实现。 。 (在EAX中返回32位结果;在EDX:EAX中返回64位结果,其中高位位于EDX中,低位位于EAX中。 1}}。)

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

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

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

    您尝试在“整数参数”和“内存参数”之间的注释中进行区分,但不存在后者。当您传递指针(或C ++中的引用,哪些编译器根据指针实现)时,您实际上正在传递地址。由于地址只是整数,所以它们就像任何其他整数值寄存器一样传递。

    如果在堆栈上传递参数,它们的大小都是8字节(64位),并且一个接一个地传递。第一个与堆栈指针RBP的偏移量为8。第二个是偏移16,等等。当您查看问题中的代码时,似乎有点混乱,这些代码来自十六进制中表示的偏移量,其中{ {1}}相当于十进制的16,10h相当于十进制的24。 (为什么第一个参数从偏移量8开始?因为第一个位置18h被返回指针占用。)

    这基本上涵盖了调用约定的基础知识。但坦率地说,分析反汇编是而不是学习调用约定的一种非常好的方法。您不一定会看到更多细节,也无法获得全局视图。你真的需要read the fine manual。如果您讨厌手册,可以在线提供更简洁(和更简化)的各种摘要,例如Wikipedia