Linux系统参数传递

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个的情况下,是如何传递的呢?首先参数按照从左到右的顺序,依次使用寄存器,在寄存器被使用完后,参数从右到左依次入栈,使用堆栈进行参数的传递。此处有一个例子:

typedef struct {
int a, b;
double d;
} structparm;
structparm s;
int e, f, g, h, i, j, k;
long double ld;
double m, n;
__m256 y;
extern void func (int e, int f,
structparm s, int g, int h,
long double ld, double m,
__m256 y,
double n, int i, int j, int k);
func (e, f, s, g, h, ld, m, y, n, i, j, k);

那么,在这个函数的调用中,寄存器的使用情况如下:

通用寄存器 浮点寄存器 栈帧偏移
%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某个很大值的原因。



本文链接: http://home.meng.uno/articles/bc46dabc/ 欢迎转载!

© 2018.02.08 - 2020.06.02 Mengmeng Kuang  保留所有权利!

UV : | PV :

:D 获取中...

Creative Commons License