x64汇编 寄存器

1、寄存器

通用寄存器:

8位 al/ah cl/ch dl/dh bl/bh spl bpl sil dil r8b r9b r10b r11b r12b r13b r14b r15b
16位 ax cx dx bx sp bp si di r8w r9w r10w r11w r12w r13w r14w r15w
32位 eax ecx edx ebx esp ebp esi edi r8d r9d r10d r11d r12d r13d r14d r15d
64位 rax rcx rdx rbx rsp rbp rsi rdi r8 r9 r10 r11 r12 r13 r14 r15


64位媒介、浮点寄存器

mmx0/fpr0
mmx1/fpr1
mmx2/fpr2
mmx3/fpr3
mmx4/fpr4
mmx5/fpr5
mmx6/fpr6
mmx7/fpr7


标识寄存器

16位 32位 64位
flags eflags rflags


指令指针寄存器

16位 32位 64位
ip eip rip


128位媒介寄存器

xmm0
xmm1
xmm2
xmm3
xmm4
xmm5
xmm6
xmm7
xmm8
xmm9
xmm10
xmm11
xmm12
xmm13
xmm14
xmm15



注:al为ax的低8位,ah为ax的高8位,相同的还有cx(cl低,ch高)、dx(dl低,dh高)、bx(bl低,bh高)。spl、bpl、sil、dil分别为sp、bp、si、di的低8位。r8b-r15b分别为r8w-r15w的低8位。

2、调用约定
这里所说的调用约定,是Windows API的调用约定,以及VS编译的x64平台C语言程序中函数的调用约定。
规则如下:

  • 前四个参数用rcx、rdx、r8、r9存储,但是同时也要留出栈空间。剩下的参数均通过栈传参。
  • 调用者维护栈,被调用者返回的时候只需要ret就行了。



来解释一下其中的规则。首先就是参数传递的规则。来用MessageBox举例。

首先如果你链接了x64平台的user32.lib,那么你的整个程序之中就会有__imp_MessageBoxA这个符号,它是个函数指针,指向user32.dll中的MessageBoxA函数的入口。
范例代码(NASM编译通过):

[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
%define MB_OK 0
 
segment .text
start:
sub rsp,0x28
mov r9,MB_OK
mov r8,g_szTitle
mov rdx,g_szPrompt
xor rcx,rcx
call [__imp_MessageBoxA]
add rsp,0x28
ret
 
segment .data
g_szTitle db "x64汇编" ,0
g_szPrompt db "Hello World!" ,0

其中我们要调用的函数是MessageBoxA,它有四个参数:窗口句柄,提示文本,标题栏文本,样式。
四个参数,每个参数8字节对齐,所有参数总字节数又是16字节对齐的,因此我们需要预留出0x20个字节的栈空间。然后我们还要再留出8个字节用于和call指令压入的返回地址值拼成16字节对齐。
因此预留的栈大小为:8 + 16字节对齐(要调用的函数的参数个数×8)
为什么要预留栈空间?因为,当你使用寄存器传参数的时候,你会发现有时候这个寄存器有时要被用于其它用途(比如调用别的函数、用作数学运算等),但是你又不想丢失寄存器之前存储的数值。因此,你可以把寄存器存储的数值暂存到栈上的预留空间里,以便于后续的读取。
调用者不必帮被调用者存储参数的值——调用者只需要分配好栈空间然后把前四个参数用rcx、rdx、r8、r9来传递(剩下的参数则通过栈传递)。被调用者自己决定是否将参数存储到栈上。
此外,编写x64汇编时,要注意一点:尽可能不要重复使用push和pop,因为这样会降低性能。其中,x64的push如果是要压入立即数的话,可能会产生一个字节数很多的指令,CPU从内存中读取指令是需要时间的。另外就是,push、pop需要做两个操作,先改变rsp的值,然后将数据存入栈顶,这个过程需要消耗额外的CPU时钟周期。而一次性将栈内存分配好,然后用mov来穿参的话,会比使用连续的push和pop快很多。

猜你喜欢

转载自www.cnblogs.com/hjbf/p/10254525.html