1. 程式人生 > >Windows x64彙編函式呼叫約定

Windows x64彙編函式呼叫約定

最近在寫一些字串函式的優化,用到x64彙編,我也是第一次接觸,故跟大家分享一下。

x86:又名 x32 ,表示 Intel x86 架構,即 Intel 的32位 80386 彙編指令集。

x64:表示 AMD64 和 Intel 的 EM64T ,而不包括 IA64 。至於三者間的區別,可自行搜尋。

x64 跟 x86 相比暫存器的變化,如圖:

從圖上可以看到,X64架構相對於X86架構的主要變化,是將原來所有的暫存器都擴大了一倍,例如EAX現在擴充成RAX,同時,又新增加了從R8~R15這8個64位的寄位器,有點RISC的味道(RISC特點就是暫存器多)。

然後還有下面的一些改變:

  • x64上面預設的函式呼叫約定是 fast call ,也就是 API 是 fast call ;
  • 一個函式在呼叫時,前四個引數是從左至右依次存放於RCX、RDX、R8、R9暫存器裡面,剩下的引數從左至右順序入棧;
  • 呼叫者負責在棧上分配32位元組的“shadow space”,用於存放那四個存放呼叫引數的暫存器的值(亦即前四個呼叫引數);
  • 小於64位(bit)的引數傳遞時高位並不填充零(例如只傳遞ecx),大於64位需要按照地址傳遞;
  • 被呼叫函式的返回值是整數時,則返回值會被存放於RAX;
  • 被呼叫函式不負責清棧,呼叫者負責清理棧;
  • RAX,RCX,RDX,R8,R9,R10,R11是“易揮發”的,不用特別保護,其餘暫存器需要保護。(x86下只有eax, ecx, edx是易揮發的)
  • 棧需要16位元組對齊,“call”指令會入棧一個8位元組的返回值(注:即函式呼叫前原來的RIP指令暫存器的值),這樣一來,棧就對不齊了(因為RCX、RDX、R8、R9四個暫存器剛好是32個位元組,是16位元組對齊的,現在多出來了8個位元組)。所以,所有非葉子結點呼叫的函式,都必須調整棧RSP的地址為16n+8,來使棧對齊。
  • 對於 R8~R15 暫存器,我們可以使用 r8, r8d, r8w, r8b 分別代表 r8 暫存器的64位、低32位、低16位和低8位。

一些其他要注意的小問題:

  • 另外一些小問題要注意,AMD64不支援 push 32bit 暫存器的指令,最好的方法就是 push 和 pop 都用64位暫存器,即 push rbx ,不要使用 push ebx 。
  • 另外要補充的一點是,在一般情況下,X64 平臺的 RBP 棧基指標被廢棄掉,只作為普通暫存器來用,所有的棧操作都通過 RSP 指標來完成。

遺留問題

  以上都是關於 Windows 上的呼叫約定,即 Visual Studio 上使用的呼叫約定,至於 GCC 的函式呼叫約定是否一致,還不清楚,有知道的請指點一下,我從 asmlib 的64位彙編看,GCC 好像第一個引數用的是 rdi ,而不是 rcx 。

示例:

複製程式碼
; 示例程式碼 1.asm
; 語法:GoASM

DATA SECTION
text     db 'Hello x64!', 0
caption  db 'My First x64 Application', 0

CODE SECTION
START:

sub rsp, 28h           ; 堆疊預留 shadow space (40 + 8)位元組

xor r9d, r9d           ; r9
lea r8, caption        ; r8
lea rdx, text          ; rdx
xor rcx, rcx           ; rcx

call MessageBoxA

add rsp, 28h           ; 呼叫者自己恢復堆疊

ret
複製程式碼

參考文章