最近在寫一些字符串函數的優化,用到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 ,也就是 ABI 是 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
參考文章
Windows平台X64函數調用約定與匯編代碼分析 | http://kelvinh.github.io/blog/2013/08/05/windows-x64-calling-conventions/
x64 參數傳遞 | http://hyperiris.blog.163.com/blog/static/1808400592011715111957863/
Windows X64匯編入門(1) | http://wenku.baidu.com/view/3093d52d453610661ed9f4b0.html