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個參數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM