最近在写一些字符串函数的优化,用到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!',
caption db 'My First x64 Application', 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