寄存器
好的程序员,应该是懂汇编语言的程序员。汇编语言在程序调试中是不可回避的。分析汇编语言在某些时候是必须的,而有的程序就没有源代码和符号表,那么唯一可以利用的就是它的反汇编语言了。在一些底层开发中,还需要在代码中嵌入汇编语言。Linux内核也是通过C与汇编写出来的。因此,首先介绍一下汇编语言的基础。
1.1寄存器
CPU的一个重要组成部分就是它的寄存器。计算机体系结构中常用到的寄存器包括以下几类寄存器:
1.1.1 32位寄存器
32位寄存器是以e开头的,主要包含下面一些寄存器:
a) 通用寄存器:EAX,EBX,ECX,EDX
b) 源变址目标变址寄存器:ESI,EDI
c) 栈相关积存器:SS,ESP(栈顶指针寄存器),EBP(栈基址寄存器)
d) 代码段寄存器,程序指令寄存器:CS,EIP(指令寄存器)
e) 数据段寄存器:DS(常与ESI寄存器结合使用)
f) 附加段寄存器:ES(常与EDI寄存器集合使用)
g) 控制寄存器:CR0-CR3。
CR0包括指示处理器工作方式的控制位,包含启用和禁止分页管理机制的控制位,包含控制浮点协处理器操作的控制位。CR1被保留,供今后开发的处理器使用。CR2及CR3由分页管理机制使用。CR2用于发生页异常时报告出错信息。当发生页异常时,处理器把引起页异常的线性地址保存在CR2中。操作系统中的页异常处理程序可以检查CR2的内容,从而查出线性地址空间中的哪一页引起本次异常。CR3 用于保存页目录表页面的物理地址,因此被称为PDBR。
h) 系统地址寄存器:GDTR(全局描述符表寄存器),LDTR(局部描述符表寄存器),IDTR(中断描述符表寄存器),TR(任务状态段寄存器)
i) Flag标志寄存器:
ZF 零标志,零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0;
AF 辅助进位标志,运算过程中第三位有进位值,置AF=1,否则,AF=0;
减法时,第3位向第4位(从第0位开始计)有借位。有则该标志位就置1。通常用于对BCD算术运算结果的调整。
例:1101 1000+1010 1110=1 1000 0110其中AF=1,CF=1。
BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码。用4位二进制数来表示1位十进制数中的0~9这10个
数码。是一种二进制的数字编码形式,用二进制编码的十进制代码。BCD码这种编码形式利用了四个位元来储存一个
十进制的数码,使二进制和十进制之间的转换得以快捷的进行。
PF 奇偶标志,当结果操作数中偶数个"1",置PF=1,否则,PF=0;
SF 符号标志,当结果为负时,SF=1;否则,SF=0。溢出时情形例外;
CF 进位标志,最高有效位产生进位值,例如,执行加法指令时,MSB(最高位)有进位,置CF=1;否则,CF=0;
OF 溢出标志,若操作数结果超出了机器能表示的范围,则产生溢出,置OF=1,否则,OF=0。
标志寄存器的一些值是一些其它相关汇编指令在执行过程中需要参考的值。比如条件跳转指令就需要参考ZF等标志位。



1.1.2 64位寄存器
64位汇编与32位汇编来说,更加复杂。分析64位汇编比32位汇编提高了不少难度。这是因为在64位系统中,增加了比32位系统还要多的寄存器。因此,在调用函数的时候,参数的传递发生了很多的改变。大多数参数都放在了寄存器中,即使用的是fastcall调用约定。
通用寄存器:rax,
rbx, rcx, rdx
栈寄存器:rsp(栈顶指针寄存器),
rbp
源变址和目标变址的寄存器: rsi, rdi
指令寄存器:rip
传参寄存器:rcx,rdx, r8, r9
scratch寄存器:rbx,r12, r13, r14, r15(scratch),需要保护
RAX,RCX,RDX,R8,R9,R10,R11是“易挥发”的,不用特别保护(push备份),其余寄存器需要保护。X86下只有eax, ecx, edx是易挥发。
x64的调用约定:fastcall

1,一个函数在调用时,前四个参数是从左至右依次存放于RCX、RDX、R8、R9寄存器里面,剩下的参数从右至左顺序入栈;
2,浮点前4个参数传入XMM0、XMM1、XMM2 和 XMM3 中。其他参数传递到堆栈中。
3,调用者负责在栈上分配32字节的“shadow
space”,用于存放那四个存放调用参数的寄存器的值(亦即前四个调用参数);小于64位(bit)的参数传递时高位并不填充零(例如只传递ecx),大于64位需要按照地址传递;
4,调用者负责栈平衡;
6,RAX,RCX,RDX,R8,R9,R10,R11是“易挥发”的,不用特别保护(所谓保护就是使用前要push备份),其余寄存器需要保护。(x86下只有eax,
ecx, edx是易挥发的)
7,栈需要16字节对齐,“call”指令会入栈一个8字节的返回值(注:即函数调用前原来的RIP指令寄存器的值),这样一来,栈就对不齐了(因为RCX、RDX、R8、R9四个寄存器刚好是32个字节,是16字节对齐的,现在多出来了8个字节)。所以,所有非叶子结点调用的函数,都必须调整栈RSP的地址为16n+8,来使栈对齐。比如sub
rsp,28h
8,对于 R8~R15 寄存器,我们可以使用 r8, r8d, r8w, r8b 分别代表 r8 寄存器的64位、低32位、低16位和低8位。