【问题标题】:Position of Memory parameters on the Stack x86_64 gcc堆栈 x86_64 gcc 上内存参数的位置
【发布时间】:2017-06-13 13:48:20
【问题描述】:

我从汇编开始,为了测试,我编写了一个简单的 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 中的哪些地址?

【问题讨论】:

  • 64 bit uses registers to pass arguments。您在内存中看到的不是参数,它们是临时局部变量,看起来很傻,因为您没有启用优化。本地人的布局完全取决于编译器(或程序员)。 PS:10H 是十进制的 16,所以它不是来自 8H 的 2 个字节。
  • 你知道十六进制吗?
  • 顺便说一句,x86 calling convention on wikipedia。底部也有相关的源材料。最值得注意的是,ABI-IA64.pdf
  • “除了有趣的一点,它首先将所有参数存储到寄存器中” 您刚刚嘲笑了十分之一年的惯例。我觉得这没什么好笑的。这样做有非常充分的理由。
  • @Jester ,AFAIK 内存参数在堆栈上传递,整数参数在寄存器中。我很确定ebp-8H 直到ebp-20H 的内容是参数,因为它们被用作存储修改值的地址。 PS:当然,你是完全正确的。

标签: assembly x86-64 calling-convention


【解决方案1】:

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

  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 除了返回 0(隐式地;甚至没有在您的代码中表示)之外没有任何事情,因此它将整个函数转换为简单的清除 @987654326 的指令@注册然后返回。

不过,我们可以看出,函数的结果是在EAX 中返回的。这在 Unix 系统上常见的 System V AMD64 调用约定中是正确的,在 Windows 上使用的 64 位调用约定中也是如此,甚至在你会发现的所有 32 位 x86 调用约定中也是如此. (EAX返回32位结果;EDX:EAX返回64位结果,其中高位在EDX,低位在EAX。)

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

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

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

您尝试在 cmets 中区分“整数参数”和“内存参数”,但没有后者。当您传递指针(或 C++ 中的引用,编译器根据指针实现)时,您实际上是在传递 地址。由于地址只是整数,因此它们就像任何其他整数值一样在寄存器中传递。

如果你在堆栈上传递参数,它们的大小都是 8 字节(64 位),并且一个接一个。第一个距离堆栈指针RBP 偏移8。第二个的偏移量为 16,依此类推。当您查看问题中的代码时,似乎有点困惑,这些代码是由 hexadecimal 表示的偏移量引起的,其中@ 987654342@ 相当于十进制的 16,18h 相当于十进制的 24。 (为什么第一个参数从偏移量8开始?因为第一个位置RBP+0被返回指针占用了。)

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

【讨论】:

  • -O0 比不重新排序更糟糕。它会在 C 语句之间(或源代码行之间?)将所有内容溢出到内存中,因此使用调试器修改变量将产生预期的结果。这要求它非常低效。
  • 相关:ABI 定义了如何按值传递数组,因为像通过值传递struct { float a[100]; } 这样的情况。见What kind of C11 data type is an array according to the AMD64 ABI。我认为 OP 将数组视为不适合寄存器的“大对象”,并错过了数组衰减为 C 中的指针。
猜你喜欢
  • 2016-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-11
  • 1970-01-01
  • 2018-10-29
  • 2015-04-11
相关资源
最近更新 更多