9.1 x86-64指令系统概述
背景
- Intel最早推出的64位架构是基于超长指令字VLIW技术的 IA-64体系结构,Intel 称其为
显式并行指令计算机EPIC( Explicitly Parallel Instruction Computer)。- 安腾和安腾2分别在2000年和2002年问世,它们是IA-64体 系结构的最早的具体实现,
因为是一种全新的、与IA-32不 兼容的架构,所以,没有获得预期的市场份额。- AMD公司利用Intel在IA-64架构上的失败,抢先在2003年 推出兼容IA-32的64位版本指
令集x86-64,AMD获得了以 前属于Intel的一些高端市场。AMD后来将x86-64更名为
AMD64。- Intel在2004年推出IA32-EM64T,它支持x86-64指令集。 Intel为了表示EM64T的64位
模式特点,又使其与IA-64有 所区别,2006年开始把EM64T改名为Intel64。
1. IA-32 与 x86-64 寄存器比较
(1)IA-32支持的数据类型及格式
(2)x86-64中各类数据的长度
2. x86-64的寄存器
(1)x86-64的通用寄存器
- 新增8个64位通用寄存器(整数寄存器)
R
8、R
9、R
10、R
11、R
12、R
13、R
14、R
15
可作为8位(R8B
~ R15B
)、16位(R8W
~ R15W
)或 32位寄存器(R8D
~R15D
)使用 - 所有GPRs都从32位扩充到64位
①8个32位通用寄存器E
AX、E
BX、E
CX、E
DX、E
BP、E
SP、E
SI、E
DI
对应扩展寄存器分别为R
AX、R
BX、R
CX、R
DX、R
BP、R
SP、R
SI、R
DI
②新增
了E
BP、E
SP、E
SI和E
DI 的低8位
寄存器:BPL
、SPL
、SIL
和DIL
③可兼容使用原AH、BH、CH和DH寄存器 (使原来IA-32中的每个通用寄存器都可以是8位、16位、 32位和64位,如:SIL、SI、ESI、RSI)
(2)x86-64中寄存器的使用
- 指令可直接访问16个64位寄存器:RAX、RBX、RCX、RDX、 RBP、RSP、RSI、RDI,以及R8~ R15
- 指令可直接访问16个32位寄存器:EAX、EBX、ECX、EDX、EBP 、ESP、ESI、EDI,以及R8D~ R15D
- 指令可直接访问16个16位寄存器:AX、BX、CX、DX、BP、SP、 SI、DI,以及R8W~ R15W
- 指令可直接访问16个8位寄存器:AL、BL、CL、DL、BPL、SPL 、SIL、DIL,以及R8B~R15B
64位 | R AX | RBX | RCX | RDX | RBP | RSP | RSI | RDI | R8~ R15 |
---|---|---|---|---|---|---|---|---|---|
32位 | E AX | EBX | ECX | EDX | EBP | ESP | ESI | EDI | R8D ~ R15D |
16位 | AX | BX | CX | DX | BP | SP | SI | DI | R8W ~ R15W |
8位 | AL | BL | CL | DL | BPL | SPL | SIL | DIL | R8B ~R15B |
- 为向后兼容,指令也可直接访问AH、BH、CH、DH
- 通过寄存器传送参数,因而很多过程
不用访问栈
,因此,与IA-32 不同,x86-64不需要帧指针寄存器
,即RBP可用作普通寄存器使用
- 程序计数器为64位寄存器
RIP
(3)寄存器组织
IA-32的寄存器组织
x86-64的寄存器组织
63 | 32 | 0 |
---|---|---|
%rax | %eax | 返回值 |
%rbx | %ebx | 被调用保护者 |
%rcx | %ecx | 第四个参数 |
%rdx | %edx | 第三个参数 |
%rsi | %esi | 第二个参数 |
%rdi | %edi | 第一个参数 |
%rbp | %ebp | 被调用保护者 |
%rsp | %esp | 堆栈指针 |
%r8 | %r8d | 第五个参数 |
%r9 | %r9d | 第六个参数 |
%r10 | %r10d | 调用保护者 |
%r11 | %r11d | 调用保护者 |
%r12 | %r12d | 被调用保护者 |
%r13 | %r13d | 被调用保护者 |
%r14 | %r14d | 被调用保护者 |
%r15 | %r15d | 被调用保护者 |
- 通用寄存器个数从8个增加到16个, 宽度从32位增加到64位
- 增加了%sil、 %dil、%bpl、 %spl 四个8位寄存器
- %riw为16位 %rib为8位 (i=8~15)
3. x86-64的地址和寻址空间
- 字长从32位变为64位,64位(8B)数据被称为一个
四字
(qw: quadword) 逻辑地址
最长可达为64位
,即理论上可访问的存储空 间达264字节或16EB(ExaByte)- 编译器为
指针
变量分配64位
(8B) 基址寄存器
和变址寄存器
都应使用64位寄存器
- 但实际上,AMD和Intel的x86-64仅支持48位虚拟地 址,因此,程序的虚拟地址空间大小为248=256TB
4. x86-64的浮点寄存器
long double
型数据虽然还采用80位(10B)
扩展精度格式,但所分配存储空间从12B扩展为16B,即改为16B对齐
方式,但不管是分配12B还是16B,都只用到低10B128位
的XMM寄存器从原来的8个
增加到16个
,XMM0~XMM15- 浮点操作指令集采用
基于SSE的
面向XMM寄存器的指令集,而不采用基于浮点寄存器栈的x87 FPU 指令集 - 浮点操作数存放在
XMM寄存器
中
x86-64继承了IA-32中的8、16、32位通用寄存器和128位XMM寄存器 而取消了IA-32中的80位浮点寄存器栈ST(0)-ST(7)
16个128位寄 存器%xmmi (i=0~15)
5. x86-64中数据的对齐
- 各类型数据遵循一定的对齐规则,而且更严格
- 存储器访问接口被设计成按8字节或16字节为单位进行存取,其对齐规则是,任何K字节宽的基本数据类型和指针类型数据的起始地址一定是K的倍数。
数据类型 | 对齐边界 |
---|---|
short | 2字节 |
int、float | 4字节 |
long、double、指针 | 8字节 |
long double | 16字节 |
9.2 x86-64的基本指令
传送指令 |
---|
movabsq I, R: | 将64位立即数 送64位通用寄存器 |
---|---|
movq: | 传送一个64位的四字 |
movsbq、movswq、movslq: | 将源操作数进行符号扩展并传送到一个64位寄存器或存储单元中 |
movzbq、movzwq: | 将源操作数进行零扩展后传送到一个64位寄存器或存储单元中 |
movl: | 的功能相当于movzlq指令 |
pushq S: | R[rsp]←R[rsp]-8; M[R[rsp]] ←S |
popq D: | D← M[R[rsp]]; R[rsp]←R[rsp]-8 |
算术逻辑指令 |
---|
常规的算术逻辑运算指令只要将原来IA-32中的指令扩展到64位即可 | |
---|---|
addq / subq | 四字相加 / 四字相减 |
incq / decq | 四字加1 / 四字减1 |
imulq | 带符号整数四字相乘 |
orq | 64位相或 |
salq | 64位算术左移 |
leaq | 有效地址 加载到64位寄存器 |
对于x86-64,还有一些特殊的算术逻辑运算指令 | |
imulq S | R[rdx]:R[rax]← S * R[rax] (64位*64位带符号整数) |
mulq S | R[rdx]:R[rax]← S * R[rax] (64位*64位无符号整数) |
cltq | R[rax] ← SignExtend(R[eax]) (将EAX内容符号扩展为四字) |
clto | R[rdx]:R[rax]← SignExtend(R[rax]) (符号扩展为八字) |
idivq S | R[rdx] ← R[rdx]:R[rax]mod S (带符号整数相除、余数) R[rax] ← R[rdx]:R[rax]÷S (带符号整数相除、商) |
divq S | R[rdx] ← R[rdx]:R[rax]mod S (无符号整数相除、余数) R[rax] ← R[rdx]:R[rax]÷S (无符号整数相除、商) |
上述功能描述中,R[rdx]:R[rax]是一个128位的八字(oct word) |
比较和测试指令 |
---|
与IA-32中比较和测试指令类似 | |
---|---|
cmpq S2, S1 | S1-S2 (64位数相减进行比较) |
testq S2, S1 | S1∧S2 (64位数相与进行比较) |
条件转移指令、条件传送指令、条件设置指令 都根据上述比较指令 和测试指令生成的标志进行处理 |
1. 数据传送指令
例
2. 算术逻辑指令
例一
例二 :不同长度操作数混合运算时,编译器必须选择正确的指令的组合。
3. x86-64逆向工程举例
9.3 x86-64的过程调用
IA-32与x86-64过程调用例子
程序代码:
long int sample(long int *xp, long int y) { long int t=*xp+y; *xp=t; reutrn t; }
在x86-64/Linux平台上用以 下命令执行汇编操作,得到与 IA-32兼容的汇编指令代码
$ gcc –O1 –S –m32sample.c
sample: pushl %ebp movl %esp, %ebp movl 8(%ebp), %edx movl 12(%ebp), %eax addl (%edx), %eax movl %eax, (%edx) popl %ebp ret
在x86-64/Linux平台上用以 下命令执行汇编操作,得到 x86-64汇编指令代码
$ gcc –O1 –S –m64sample.c
sample: movq %rsi, %rax addq (%rdi), %rax movq %rax, (%rdi) ret
- 在x86-64/Linux 平台上默认生成 x86-64格式代码 ,故可省略-m64
- Long型数据长度不同 参数传递方式不同
1. x86-64过程调用的参数传递
①通过通用寄存器
传送参数,很多过程不用访问栈,故执行时间比IA-32代码更短
②最多可有6个
整型或指针型参数通过寄存器传递
③超过6个入口参数时,后面的通过栈
来传递
④在栈中传递的参数若是基本类型
,则都被分配8个字节
⑤call(或callq)将64位返址保存在栈中之前,执行R[rsp]←R[rsp]-8
⑥ret从栈中取出64位返回地址后,执行R[rsp]←R[rsp]+8
操作数宽度 (字节) | 1 | 2 | 3 | 4 | 5 | 6 | 返回参数 |
---|---|---|---|---|---|---|---|
8 | RDI | RSI | RDX | RCX | R8 | R9 | RAX |
4 | EDI | ESI | EDX | ECX | R8D | R9D | EAX |
2 | DI | SI | DX | CX | R8W | R9W | AX |
1 | DIL | SIL | DL | CL | R8B | R9B | AL |
2. x86-64过程调用的寄存器使用约定
在过程(函数) 中尽量使用寄存器**RAX、 R10
** 和 R11
。 若使用 RBX、 RBP、R12、 R13、R14
和 R15
,则需要 将它们先保存在栈中再使用 ,最后返回前 再恢复其值.
3. x86-64过程调用举例
参数存放
caller函数中部分指令(return语句前)
test函数中部分指令
caller函数中部分指令(return语句)
4. IA-32和x86-64的比较
例子
IA-32过程调用参数传递
- long double型数据虽然还采用80位(10B)扩展精度 格式,但所分配存储空间从12B扩展为16B,即改为16B对齐方式,但不管是分配12B还是16B,都只用到 低10B
- 128位的XMM寄存器从原来的8个增加到16个
- 浮点操作指令集采用基于SSE的面向XMM寄存器的指令集,而不采用基于浮点寄存器栈的x87 FPU 指令集
- 浮点操作数存放在XMM寄存器中
x86-64过程调用参数传递