x86_64汇编:调用约定

1.什么是调用约定

函数的调用过程中有两个参与者,调用者caller以及被调用者callee。

调用约定规定了caller和callee之间如何相互配合来实现函数调用,如下:

  • 函数的参数存放在哪里。存放在寄存器还是栈,以及哪个寄存器、栈中的哪个位置?
  • 函数的参数传递顺序。从左到右将参数入栈,还是从右到左将参数入栈?
  • 返回值如何传递给caller。是放在寄存器,还是其他地方?
  • 等等

2.caller保存的寄存器以及callee保存的寄存器

在调用约定的规定中:

(1)有些寄存器由调用者保存(caller-saved register),此类寄存器也叫易失性寄存器(volatile register)。

     调用者调用其他函数时,某些寄存器值会被被调用者改变,但是callee并不负责这些寄存器的保存和恢复,由于需要在函数返回后继续使用,

     故需要caller保存这些寄存器值,通常是压入栈中,函数调用返回后再恢复这些寄存器的值。

(2)有些寄存器由被调用者保存(callee-save register),此类寄存器也叫非易失性寄存器(non-volatile register)。

     同上,有些寄存器由callee保存,确保callee调用前后这些寄存器的值不变,通常是压入栈中。

3.有哪些调用约定

不同架构和操作系统,调用约定可能不同,常见的调用约定如下:

  • cdecl (C declaration): 32位平台常见的一种约定,GCC、Clang、VS编译器均默认采用该约定。
  • stdcall: 32位windows上的一种约定。
  • Microsoft x64: 微软提出的基于x86_64架构的windows系统上一种调用约定。
  • System V AMD64: 基于x86_64架构Linux系统上广泛使用的一种调用约定。

本文及后续文章,如无特别说明,均采用System V AMD64该调用约定,且平台为x64 Linux。

4.System V AMD64

部分调用约定细节:

  • 调用call指令之前,必须保证栈是16字节对齐的。
  • 一个函数在调用时,如果参数个数小于等于6个时,前6个参数是从左至右依次存放于RDS、RSI、RDX、RCX、R8、R9寄存器中,如果参数大于6个,剩余的参数通过栈传递,从右至左顺序入栈。
  • XMM0~XMM7用于传递浮点参数,前8个参数从左至右依次存放在XMM0~XMM7中,剩余的参数通过栈传递,从右至左顺序入栈。
  • 被调用函数的返回值64位以内(包括64位)的整形或指针时,则返回值存放在RAX,如果返回值128位的,则高64位放入RDX。
  • 如果返回值是浮点值,则返回值存放在XMM0。
  • 可选地,被调函数推入 RBP,以使 caller-return-rip 在其上方8个字节,并将 RBP 设置为已保存的 RBP 的地址。这允许遍历现有堆栈帧,通过指定GCC的 -fomit-frame-pointer 选项可以消除此问题。
  • 对于 R8~R15 寄存器,我们可以使用 r8, r8d, r8w, r8b 分别代表 r8 寄存器的64位、低32位、低16位和低8位。
  • 等等

5.x86跟x64相比的寄存器的变化,如图:

6.简单例子

源代码如下:

 1 #include <iostream>
 2                                                                                                                                                                                                                                                  
 3 //八个整形参数
 4 int add(int x, int y, int a, int b ,int c,int d, int e, int f)
 5 {
 6   return x + y + a + b + c + d + e + f;
 7 }
 8 
 9 int main()
10 {
11   int sum_8_int = add(1,2,3,4,5,6,7,8);
12   std::cout << sum_8_int << std::endl;
13   return 0;
14 }

汇编代码如下:

 1 _Z3addiiiiiiii:
 2 .LFB1493:
 3   .cfi_startproc
 4   pushq %rbp
 5   .cfi_def_cfa_offset 16
 6   .cfi_offset 6, -16
 7   movq  %rsp, %rbp
 8   .cfi_def_cfa_register 6
 9   movl  %edi, -4(%rbp)
10   movl  %esi, -8(%rbp)
11   movl  %edx, -12(%rbp)
12   movl  %ecx, -16(%rbp)
13   movl  %r8d, -20(%rbp)
14   movl  %r9d, -24(%rbp)
15   movl  -4(%rbp), %edx
16   movl  -8(%rbp), %eax
17   addl  %eax, %edx
18   movl  -12(%rbp), %eax
19   addl  %eax, %edx
20   movl  -16(%rbp), %eax
21   addl  %eax, %edx
22   movl  -20(%rbp), %eax
23   addl  %eax, %edx                                                                                                                                                                                                                               
24   movl  -24(%rbp), %eax
25   addl  %eax, %edx
26   movl  16(%rbp), %eax
27   addl  %eax, %edx
28   movl  24(%rbp), %eax
29   addl  %edx, %eax
30   popq  %rbp
31   .cfi_def_cfa 7, 8
32   ret 
33   .cfi_endproc
34 
35 main:
36 .LFB1494:
37   .cfi_startproc
38   pushq %rbp
39   .cfi_def_cfa_offset 16
40   .cfi_offset 6, -16
41   movq  %rsp, %rbp
42   .cfi_def_cfa_register 6
43   subq  $16, %rsp
44   pushq $8
45   pushq $7
46   movl  $6, %r9d
47   movl  $5, %r8d
48   movl  $4, %ecx
49   movl  $3, %edx
50   movl  $2, %esi
51   movl  $1, %edi
52   call  _Z3addiiiiiiii
53   addq  $16, %rsp
54   movl  %eax, -4(%rbp)
55   movl  -4(%rbp), %eax
56   movl  %eax, %esi
57   leaq  _ZSt4cout(%rip), %rdi
58   call  _ZNSolsEi@PLT
59   movq  %rax, %rdx
60   movq  _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rax
61   movq  %rax, %rsi
62   movq  %rdx, %rdi
63   call  _ZNSolsEPFRSoS_E@PLT
64   movl  $0, %eax
65   leave
66   .cfi_def_cfa 7, 8
67   ret                                                                                                                                                                                                                                            
68   .cfi_endproc

 

调用堆栈图如下:

1.图片1对应指令执行完51行的栈帧:

movl $1, %edi

寄存器状态如下:

1 rbx            0x0      0
2 rcx            0x4      4
3 rdx            0x3      3
4 rsi            0x2      2
5 rdi            0x1      1
6 rbp            0x7fffffffe3b0   0x7fffffffe3b0
7 rsp            0x7fffffffe390   0x7fffffffe390
8 r8             0x5      5
9 r9             0x6      6

参数1、2、3、4、5、6分别放在寄存器 rdi、rsi、rdx、rcx、r8、r9中(低32位),

参数7、8存放在堆栈中,如图1所示,参数8先入栈,参数7后入栈,符合从右至左的入栈顺序约定。

2.图片2对应开始调用call指令的准备工作,将call指令的下一条指令压入栈中,即53行

3.图片3对应进入add函数后,并执行至17行的栈帧。

   26行指令,以及28行指令,分别获取了栈上数据,即add函数的第7、第8个参数。

 

posted @ 2021-06-04 19:05  一瞬光阴  阅读(1302)  评论(0编辑  收藏  举报