好的程序员,应该是懂汇编语言的程序员。汇编语言在程序调试中是不可回避的。分析汇编语言在某些时候是必须的,而有的程序就没有源代码和符号表,那么唯一可以利用的就是它的反汇编语言了。在一些底层开发中,还需要在代码中嵌入汇编语言。Linux内核也是通过C与汇编写出来的。因此,首先介绍一下汇编语言的基础。
CPU的一个重要组成部分就是它的寄存器。计算机体系结构中常用到的寄存器包括以下几类寄存器:
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;
PF 奇偶标志,当结果操作数中偶数个"1",置PF=1,否则,PF=0;
SF 符号标志,当结果为负时,SF=1;否则,SF=0。溢出时情形例外;
CF 进位标志,最高有效位产生进位值,例如,执行加法指令时,MSB(最高位)有进位,置CF=1;否则,CF=0;
OF 溢出标志,若操作数结果超出了机器能表示的范围,则产生溢出,置OF=1,否则,OF=0。
标志寄存器的一些值是一些其它相关汇编指令在执行过程中需要参考的值。比如条件跳转指令就需要参考ZF等标志位。
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,调用者负责栈平衡;
5,被调用函数的返回值是整数时,则返回值会被存放于RAX;浮点数返回在xmm0中
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位。
1)CPU构成
CPU主要由运算器、控制器、寄存器组和内部总线等构成。
运算器是计算机中执行各种算术和逻辑运算操作的部件。运算器由算术逻辑单元(ALU)、累加器、状态寄存器、通用寄存器组等组成。算术逻辑运算单元(ALU)的基本功能为加、减、乘、除四则运算,与、或、非、异或等逻辑操作,以及移位、求补等操作。计算机运行时,运算器的操作和操作种类由控制器决定。运算器处理的数据来自存储器;处理后的结果数据通常送回存储器,或暂时寄存在运算器中。
控制器是计算机的指挥中心,负责决定执行程序的顺序,给出执行指令时机器各部件需要的操作控制命令。由程序计数器、指令寄存器、指令译码器、时序产生器和操作控制器组成,它是发布命令的“决策机构”,即完成协调和指挥整个计算机系统的操作。控制器从内存中取出一条指令,并指出下一条指令在内存中位置,对指令进行译码或测试,并产生相应的操作控制信号,以便启动规定的动作,指挥并控制CPU、内存和输入/输出设备之间数据流动的方向。
寄存器组用于在指令执行过后存放操作数和中间数据,由运算器完成指令所规定的运算及操作。
2)系统总线
CPU的系统总线包括控制总线,数据总线,地址总线。
数据总线用于传送数据信息。数据总线是双向总线,即它既可以把CPU的数据传送到存储器或I/O接口等其他部件,也可以将其他部件的数据传送到CPU。
地址总线是专门用来传送地址的,由于地址只能从CPU传向外部存储器或I/O端口,所以地址总线总是单向的,这与数据总线不同。地址总线的位数决定了CPU可直接寻址的内存空间大小,比如8位微机的地址总线为16位,则其最大可寻址空间为216=64KB,16位微型机的地址总线为20位,其可寻址空间为220=1MB。一般来说,若地址总线为n位,则可寻址空间为2n字节。有的系统中,数据总线和地址总线是复用的,即总线在某些时刻出现的信号表示数据而另一些时刻表示地址,而有的系统则是分开的。
控制总线用来传送控制信号。控制信号中,有的是微处理器送往存储器和I/O接口电路的,如读/写,中断响应信号等;也有是其他部件反馈给CPU的,比如:中断申请、复位、总线请求、设备就绪等。因此,控制总线的传送方向由具体控制信号而定,一般是双向的,控制总线的位数要根据系统的实际控制需要而定。实际上控制总线的具体情况主要取决于CPU。