Windows x64彙編函式呼叫約定
阿新 • • 發佈:2019-02-08
最近在寫一些字串函式的優化,用到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
參考文章