Weizhenwei’s Technical Blog

A technical blog of weizhenwei’s technical docs and ideas.

x86-64指令和ABI

  本文是针对x86-64指令和ABI的综述,其内容翻译自参考文献[1]。

1 引言

x86-64指令一般有两个操作数:源操作数和目的操作数,目的操作数也即指令执行结果。
linux下的x86-64指令格式采用AT&T格式,也即源操作数在左边,目的操作数在右边。
绝大多数x86-64指令采用后缀字母标示操作数的大小,例如q表示64为,b表示8位,w表示16位,l表示32位。

2 寄存器

x86-64有16个64位通用寄存器,在AT&T格式中使用%做前缀,其定义和在ABI中的角色如下表所示:

Register Callee Save Description
%rax no result register, also used in idiv and imul
%rbx yes miscellaneous register
%rcx no fourth argument register
%rdx no third argument register, also idiv and imul
%rsp no stack pointer
%rbp yes frame pointer
%rsi no second argument register
%rdi no first argument register
%r8 no fifth argument register
%r9 no sixth argument register
%r10 no miscellaneous register
%r11 no miscellaneous register
%r12~%15 yes miscellaneous registers

寄存器%rbp,%rbx和%r12~%r15是callee-save的,也即由被调用函数保存。

3 函数调用约定

Mac OS X和Linux操作系统的函数调用都遵从System V ABI,
有三个x86-64指令用来实现函数调用和返回:
   call:返回地址(当前下一条指令地址)压栈,控制权转移到操作数所在的地址;
  leave:设置栈指针%rsp为为帧%rbp,恢复帧指针%rbp为栈上保存的帧指针,
        该指针从函数栈上弹出;
    ret:返回值从函数栈弹出,并跳转到该地址;

3.1 函数参数

函数调用的前6个参数通过寄存器传递,传递顺序为%rdi,%rsi,%rdx,%rcx,%r8,%r9。超出部分的参数通过函数 栈帧传递。%rax用作第一个函数返回值,%rdx用作第二个函数返回值。

3.2 函数栈帧

函数栈帧从高地址往低地址方向增长,System V ABI使用两个寄存器访问函数栈帧:帧指针%rbp和栈指针%rsp。 帧指针%rbp指向当前函数栈帧基址(栈底),栈指针%rsp指向当前函数栈帧栈顶。
一般说来,帧指针%rbp用来存取函数栈帧上的数据,例如传递进来的函数参数,或者函数的本地局部变量。 System V ABI要求要求函数栈帧16字节对齐,这要求函数栈帧的大小应该是16的倍数。
函数栈帧结构图如下所示:

stack-frame

3.3 函数调用协议

函数调用协议分为caller端和callee端,每端各有两个重要步骤:
1)caller端首先保存caller-save寄存器到到函数栈上,并根据ABI加载函数参数到规定的寄存器和函数栈位置,然后执行call指令;
2)call指令使得程序控制转向callee函数,该函数首先需要执行链接和初始化任务,这些任务由以下指令序列完成:
    pushq %rbp;
    movq %rsp, %rbp;
    subq $N, %rbp;
pushq指令用来保存帧指针,movq指令初始化帧指针为当前栈指针,subq指令为callee函数分配栈空间;对于callee-save的那些寄存器,如果在函数体中用到这些寄存器,那么接下来需要通过一系列pushq指令保存这些callee-save寄存器到函数栈帧上。程序接下来执行函数体部分代码。
3)函数体部分执行完以后,将执行callee返回部分:首先将函数返回值置入%rax中;然后释放函数栈帧空间,并恢复%rsp,%rbp寄存器;最后调用leave/ret指令返回到caller部分。
4)在caller端,如果它为callee的函数参数分配了存储空间,则在此时需要释放这些空间。
至此,一个完整的函数调用过程完成。

4 指令

关于指令部分,内容实在太多,具体请参考x86-64的指令手册。此处仅仅略述操作数。 x86-64指令的操作数有三种:寄存器(以%为前缀),立即数(以$为前缀)和内存地址。内存地址的寻址方式概述如下:

Syntax Address Description
(reg) reg Base addressing
d(reg) reg+d Base plus displacement addressing
d(reg, s) s*reg+d Scaled index plus displacement, s in {2, 4, 8}
d(reg1, reg2, s) reg1+s*reg2+d Base plus scaled index plus displacement
参考文献

http://www.classes.cs.uchicago.edu/archive/2009/spring/22620-1/docs/handout-03.pdf