x64寄存器
x64体系提供了16个通用寄存器,以及16个通用寄存器,以及16个浮点寄存器XMM/YMM寄存器。这些寄存器分为两类:
- 易失寄存器:由调用方假想的临时寄存器,并要在调用过程中销毁。
- 非易失寄存器:需要在整个函数调用过程中保留其值,一旦使用,必须由调用方保存。
也就是说,易失寄存器被定义为随时会改变,不用恢复它的初始值。但是如果要嵌入一些汇编语句,还是要对它们进行保护和恢复。而易失寄存器一旦使用,必须由调用方来对它们进行保存。也就是说在任何情况下使用它们,都必须进行保存。
寄存器 | 使用 | 是否在调用前保存 |
---|---|---|
RAX | 临时寄存器传递参数寄存器数量,第一返回值寄存器 | 否 |
RBX | 被调用者保存寄存器,选择性的基址指针 | 是 |
RCX | 传递第四个参数 | 否 |
RDX | 传递第三个参数,第二返回值寄存器 | 否 |
RSP | 栈指针 | 是 |
RBP | 被调用者保存寄存器,选择性的栈帧寄存器 | 是 |
RSI | 传递第二个参数 | 否 |
RDI | 传递第一个参数 | 否 |
R8 | 传递第五个参数 | 否 |
R9 | 传递第六个参数 | 否 |
R10 | 临时寄存器,用于传递函数的静态链指针 | 否 |
R11 | 临时寄存器 | 否 |
R12-R15 | 被调用者保护寄存器 | 是 |
xmm0-xmm1 | 传递和返回浮点参数 | 否 |
xmm2-xmm7 | 传递浮点参数 | 否 |
xmm8-xmm15 | 临时寄存器 | 否 |
mmx0-mmx7 | 临时寄存器 | 否 |
st0-st1 | 临时寄存器,用来保存long double返回值 | 否 |
st2-st7 | 临时寄存器 | 否 |
fs | 系统预留(线程特殊寄存器) | 否 |
mxcsr | SSE2控制和状态子寄存器 | 部分 |
x87 SW | x87状态字 | 否 |
x87 CW | x87控制字 | 是 |
参数传递
可以看出,在Linux中,前6个参数都是利用寄存器来进行传递的。那么参数多于6个的情况下,是如何传递的呢?首先参数按照从左到右的顺序,依次使用寄存器,在寄存器被使用完后,参数从右到左依次入栈,使用堆栈进行参数的传递。此处有一个例子:
1 |
typedef struct { |
那么,在这个函数的调用中,寄存器的使用情况如下:
通用寄存器 | 浮点寄存器 | 栈帧偏移 |
---|---|---|
%rdi:e | %xmm0:s.d | 0:ld |
%rsi:f | %xmm1:m | 16:j |
%rdx:s.a,s.b | %xmm2:y | 24:k |
%rcx:g | %xmm3:n | |
%r8:h | ||
%r9:i |
此处存在两个疑问:第一、s.a,s.b为什么使用同一个寄存器;第二、ld为什么直接使用了栈帧传递?第一个是在结构体中,s.a,s.b是对齐可合并的,因此可以使用一个寄存器来传递这两个参数(此处存在疑问,是我自己的理解);第二个是因为long double被归为X87类,这类参数是必须通过内存来传递的。
Red zone
在linux中,red zone是函数栈帧中,返回地址之下的一片区域,被调用函数可以使用red zone来储存局部变量,来避免对栈指针进行过多的修改。这大概就是在某些函数中,rsp直接被sub某个很大值的原因。
The link of this page is https://blog.nooa.tech/articles/bc46dabc/ . Welcome to reproduce it!