函数之间的参数传递在汇编程序中如何工作?

How does transfer of parameters between functions work in Assembler?

Soo 我正在尝试了解 Assembler 如何处理堆栈帧等。 我做了一些练习,并使用英特尔符号用 GDB 反汇编了一些 C 代码。现在的任务是找出变量 'r' 是如何通过在 'main' 和 'f' 函数之间传递参数来计算的。我刚开始学习,有点迷失在这个例子的实际作用上。关于从哪里开始的任何想法或提示?

这是一个与教员合作的递归程序:

#include <stdio.h>

unsigned int f(unsigned int i) {
    if (i>1) {
        return i * f(i-1);
    } else {
        return 1;
    }
}

int main() {
    unsigned int i=5, r=0;

    r = f(i);

    printf("i = %d, f(i) = %d\n", i, r);
}

汇编代码如下所示:

#include <stdio.h>

unsigned int f(unsigned int i) {
    1149:   f3 0f 1e fa             endbr64 
    114d:   55                      push   rbp
    114e:   48 89 e5                mov    rbp,rsp
    1151:   48 83 ec 10             sub    rsp,0x10
    1155:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
  if (i>1) {
    1158:   83 7d fc 01             cmp    DWORD PTR [rbp-0x4],0x1
    115c:   76 13                   jbe    1171 <f+0x28>
    return i * f(i-1);
    115e:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
    1161:   83 e8 01                sub    eax,0x1
    1164:   89 c7                   mov    edi,eax
    1166:   e8 de ff ff ff          call   1149 <f>
    116b:   0f af 45 fc             imul   eax,DWORD PTR [rbp-0x4]
    116f:   eb 05                   jmp    1176 <f+0x2d>
  } else {
    return 1;
    1171:   b8 01 00 00 00          mov    eax,0x1
  }
}
    1176:   c9                      leave
    1177:   c3                      ret


int main() {
    1178:   f3 0f 1e fa             endbr64
    117c:   55                      push   rbp
    117d:   48 89 e5                mov    rbp,rsp
    1180:   48 83 ec 10             sub    rsp,0x10
  unsigned int i=5, r=0;
    1184:   c7 45 f8 05 00 00 00    mov    DWORD PTR [rbp-0x8],0x5
    118b:   c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0

  r = f(i);
    1192:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
    1195:   89 c7                   mov    edi,eax
    1197:   e8 ad ff ff ff          call   1149 <f>
    119c:   89 45 fc                mov    DWORD PTR [rbp-0x4],eax

研究您的环境的调用约定。许多架构的许多调用约定的概述:https://www.dyncall.org/docs/manual/manualse11.html

调用约定指定:

  1. 其中参数和 return 值必须出现在指令流控制从调用方到被调用方的单个转移点。对于传递的参数,该单点是在调用之后和被调用者的第一条指令之前(对于 return 值,在被调用者完成的点和调用者恢复执行之前)。

    许多约定将传入 CPU 寄存器的参数与堆栈内存相结合,以处理不适合 CPU 寄存器的参数。甚至一些不使用 CPU 寄存器作为参数的一些仍然使用 CPU 寄存器作为 return 值。

  2. 函数允许破坏的寄存器与必须保留的寄存器。 Call-clobbered 寄存器可以被赋予新值而无需担心 Call-preserved 寄存器可以使用,但必须恢复到它们在 return 调用方之前输入时的值。 call-preserved 寄存器的优点在于,由于它们在一次调用中得以保留,因此您可以将它们用于需要在另一次调用中存活的变量。

  3. 堆栈指针的含义和处理方式,关于当前指针下方和上方的内存,以及堆栈分配的对齐要求。

如果函数以某种方式分配堆栈 space,那么内存参数看起来会远离堆栈顶部(当然,它们实际上并没有移动,但会变得更大当前堆栈或帧指针)。编译器知道这一点并相应地调整他们对堆栈内存的访问。

一些编译器设置帧指针来引用堆栈内存。帧指针是在序言中某个时刻创建的堆栈指针的副本。帧指针并不总是必需的,但有助于异常处理和堆栈展开,以及动态堆栈分配。