零. 课程要点:
- IA-32的体系结构
- IA-32的常用指令类型
这章的内容如果想深入学习,更适合专门找一本《汇编语言》或《微机原理》来研读,这里只需能够了解IA-32的体系结构,以及能查表看懂汇编语句即可。主要目的是为了弄懂C语言的一条语句在底层是如何实现的,在这种体系结构和操作下,可能会存在哪些需要注意的地方。而汇编语言是能够看懂,又能够比较清晰展示底层操作的语言,和机器指令一一对应。
一. 指令集体系结构(ISA)
什么是指令集体系结构?先来看这张图:
指令集体系结构(Instruction Set Architecture, ISA),位于计算机系统层次的软件和硬件分界处,起到一个沟通桥梁的作用。ISA是一种规约,规定了如何使用“硬件”,如:
- 可执行的指令的集合,包括指令格式、操作种类以及每种操作对应的操作数的相应规定;
- 指令可以接受的操作数的类型;
- 操作数所能存放的寄存器组的结构,包括每个寄存器的名称、编号、长度和用途;
- 操作数所能存放的存储空间的大小和编址方式;
- 操作数在存储空间存放时按照大端还是小端方式存放;
- 指令获取操作数的方式,即寻址方式;
- 指令执行过程的控制方式,包括程序计数器、条件码定义等;
没有ISA,软件就无法使用计算机硬件。换言之,可以有不同的指令集体系结构(确实如此,如IA-32,MIPS,ARM等),甚至同一种ISA也可以有不同的计算机组成,只要相应的计算机组成能够实现ISA规定的功能。这里主要介绍IA-32体系结构。
二. IA-32体系结构
现代的冯·诺依曼计算机结构大致如下:
其中:CPU-中央处理器,PC-程序计数器,MAR-存储器地址寄存器,ALU-算术逻辑部件,IR-指令寄存器,MDR-存储器数据寄存器,GPRs-通用寄存器组。
首先我们得知道计算机是如何工作的呢?
- 程序在执行前
数据和指令事先存放在存储器中,每条指令和每个数据都有地址,指令按序存放,指令由OP、ADDR字段组成,程序起始地址置PC - 开始执行程序
第一步:根据PC取指令
第二步:指令译码
第三步:取操作数
第四步:指令执行
第五步:回写结果
第六步:修改PC的值
继续执行下一条指令
那么可以想想作为一个指令集体系结构,它的体系结构是怎样的?有哪些需要确定?如:寄存器个数及各自功能?寄存器宽度?存储空间大小?编址单位?指令格式?指令条数?指令操作功能?寻址方式?数据类型?小端/大端?标志寄存器各位含义?PC位数?I/O端口编址方式?……
IA-32是典型的CISC(复杂指令集计算机,与之对应的是精简指令集-RISC)风格ISA,包括:
- 8个通用寄存器(8位、16位、32位)
2个专用寄存器:EIP(PC)、标志寄存器EFLAGS
6个段寄存器(间接给出段基址)
OF,SF,ZF,CF之前介绍过了,AF表示辅助进位标志(BCD码运算时有用),PF表示奇偶标志。另外还有三个控制标志:DF表示方向标志(自动变址方式是增还是减),IF表示中断允许标志(仅对外部可屏蔽中断有用),TF表示陷阱标志(是否允许单步跟踪状态)。
- 支持的数据类型及格式
IA-32架构由16位架构发展而来,因此,虽然字长为32位或更大,但一个字为16位,长度后缀为 w;32位为双字,长度后缀为 long double实际长度为80位,但分配96位=12B(按4B对齐)。
- 存储器地址空间为4GB,按字节编址,小端方式
1个字节(1Byte = 8bits)一个地址,32位机器可表示232个地址,即4GB,这就是为什么32位机器最多支持4GB内存。
那什么是小端方式和大端方式?
如果以字节排列,有一个int变量,共32位数据位,4个字节,如0xFF FF 00 01,那么FF即为最高有效字节MSB,01即为最低有效字节LSB。
这个变量共需占4个地址,假设为100,101,102,103,那么这个变量的地址是100还是103?答案是按最小地址表示,即我们说这个变量的地址是100,或者说变量存放在100-103单元。
那么大端方式就是MSB所在的地址是数的地址,小端方式就LSB所在的地址是数的地址,即“大大小小”:
IBM,Motorola 68k,MIPS等是大端方式,Intel 80x86,DEC VAX等是小端方式,因此不同机器之间程序移值或者数据通信的时候要考虑字节顺序和字节交换问题。
- 寻址方式
什么是寻址方式?即如何根据指令得到操作数或操作地址。
那么指令长什么样?
机器指令是由一个0/1序列组成,如100010000100100111111011表示:
机器指令太难读懂了,所以一般用汇编指令来表示,汇编指令也可以有不同的格式,如Intel格式和AT&T格式,这门课主要用后者:
根据操作数所在的位置不同,IA-32的寻址方式可分为:立即寻址,寄存器寻址,存储单元寻址,和相对寻址。而寻址方式与微处理器的工作模式有关,工作模式可分为:实地址模式和保护模式,这里主要讨论保护模式下的寻址方式(这方面内容跟后面分页和内存管理关系较大,深入细节较多,不详细讨论,这里主要能看懂分清各种寻址方式的指令即可)。
举个例子:movw 8(%ebp, %edx, 4), %ax 表示R[ax] ← M[R[ebp]+R[edx]x4+8],传送字。
- 变长指令字、变长操作码
三. IA-32的常用指令类型
再次说明一下,学习汇编指令是为了我们能通过反编译出来的汇编语句,看懂高级程序语句的执行过程细节,更深刻的理解计算机工作原理。不需要特别记忆,只要用到某条指令的时候,会查手册并理解手册中所描述的内容即可。
& 1. 传送指令
- 通用数据传送指令
MOV:一般传送,包括movb、movw和movl等
MOVS:符号扩展传送,如movsbw、movswl等
MOVZ:零扩展传送,如movzwl、movzbl等
XCHG:数据交换
PUSH/POP:入栈/出栈,如pushl,pushw,popl,popw等 - 地址传送指令
LEA:加载有效地址,如leal (%edx,%eax), %eax”的功能为R[eax]←R[edx]+R[eax],执行前,若R[edx]=i,R[eax]=j,则指令执行后,R[eax]=i+j - 输入输出指令
IN和OUT:I/O端口与寄存器之间的交换 - 标志传送指令
PUSHF、POPF:将EFLAG压栈,或将栈顶内容送EFLAG
“栈”是什么?栈(Stack)是一种采用“先进后出”方式进行访问的一块存储区,从高地址向低地址增长。执行pushw %ax表示把16位寄存器里的“字”入栈:先将当前栈顶位置减2,然后把AX内容存入栈中,按小端方式存储。反过来出栈popw %ax就是相反的操作。
& 2. 定点算术运算指令
- 加 / 减运算(影响标志、不区分无/带符号)
ADD:加,包括addb、addw、addl等
SUB:减,包括subb、subw、subl等 - 增1 / 减1运算(影响除CF以外的标志、不区分无/带符号)
INC:加,包括incb、incw、incl等
DEC:减,包括decb、decw、decl等 - 取负运算(影响标志、若对0取负,则结果为0且CF清0,否则CF置1)
NEG:取负,包括negb、negw、negl等 - 比较运算(做减法得到标志、不区分无/带符号)
CMP:比较,包括cmpb、cmpw、cmpl等 - 乘 / 除运算(不影响标志、区分无/带符号)
MUL / IMUL:无符号乘 / 带符号乘
DIV/ IDIV:带无符号除 / 带符号除
其中,乘法指令:可给出一个、两个或三个操作数
- 若给出一个操作数SRC,则另一个源操作数隐含在AL/AX/EAX中,将SRC和累加器内容相乘,结果存放在AX(16位)或DX-AX(32位)或EDX-EAX(64位)中。DX-AX表示32位乘积的高、低16位分别在DX和AX中。 n位× n位=2n位
- 若指令中给出两个操作数DST和SRC,则将DST和SRC相乘,结果在DST中。n位× n位=n位
- 若指令中给出三个操作数REG、SRC和IMM,则将SRC和立即数IMM相乘,结果在REG中。n位× n位=n位
例:mulb %bl 表示R[ax] ← R[al] x R[bl];imull -16, (%eax,%ebx,4), %eax 表示R[eax] ← (-16) × M[R[eax]+R[ebx]×4]
除法指令:只明显指出除数,用EDX-EAX中内容除以指定的除数
- 若为8位,则16位被除数在AX寄存器中,商送回AL,余数在AH
- 若为16位,则32位被除数在DX-AX寄存器中,商送回AX,余数在DX
- 若为32位,则被除数在EDX-EAX寄存器中,商送EAX,余数在EDX
& 3. 按位运算指令
- 逻辑运算
NOT:非,包括 notb、notw、notl等
AND:与,包括 andb、andw、andl等
OR:或,包括 orb、orw、orl等
XOR:异或,包括 xorb、xorw、xorl等
TEST:做“与”操作测试,仅影响标志
仅NOT不影响标志,其他指令OF=CF=0,而ZF和SF则根据结果设置:若全0,则ZF=1;若最高位为1,则SF=1。
- 移位运算(左/右移时,最高/最低位送CF)
SHL/SHR: 逻辑左/右移,包括 shlb、shrw、shrl等
SAL/SAR: 算术左/右移,左移判溢出,右移高位补符(移位前、后符号位发生变化,则OF=1 )包括 salb、sarw、sarl等
ROL/ROR: 循环左/右移,包括 rolb、rorw、roll等
RCL/RCR: 带进位循环左/右移,即:将CF作为操作数的一部分循环移位,包括 rclb、rcrw、rcll等
& 4. 控制转移指令
指令执行可按顺序 或 跳转到转移目标指令处执行
- 无条件转移指令
JMP DST:无条件转移到目标指令DST处执行 - 条件转移
Jcc DST:cc为条件码,根据标志(条件码)判断是否满足条件,若满足,则转移到目标指令DST处执行,否则按顺序执行 - 条件设置
SETcc DST:按条件码cc判断的结果保存到DST(是一个8位寄存器 ) - 调用和返回指令 (用于过程调用)
CALL DST:返回地址RA入栈,转DST处执行
RET:从栈中取出返回地址RA,转到RA处执行 - 中断指令
& 5. x87浮点处理指令
IA-32的浮点处理架构有两种:
- x87FPU指令集(gcc默认)
- SSE指令集(x86-64架构所用)
早期的浮点处理器是作为CPU的外置协处理器出现的,x87 FPU 特指与x86处理器配套的浮点协处理器架构。
- 浮点寄存器采用栈结构
• 深度为8,宽度为80位,即8个80位寄存器
• 名称为 ST(0) ~ ST(7),栈顶为ST(0),编号分别为 0~7 - 所有浮点运算都按80位扩展精度进行
- 浮点数在浮点寄存器和内存之间传送
• float、double、long double型变量在内存分别用IEEE 754单精度、双精度和扩展精度表示,分别占32位(4B)、64位(8B)和96位(12B,其中高16位无意义)
• float、double、long double类型变量在浮点寄存器中都用80位扩展精度表示
• 从浮点寄存器到内存:80位扩展精度格式转换为32位或64位(那样就存在精度问题)
• 从内存到浮点寄存器: 32位或64位格式转换为80位扩展精度格式
数据传送类
(1) 装入 (转换为80位扩展精度)
FLD:将数据从存储单元装入浮点寄存器栈顶 ST(0)
FILD:将数据从int型转换为浮点格式后,装入浮点寄存器栈顶
(2) 存储(转换为IEEE 754单精度或双精度)
FSTx:x为s/l时,将栈顶ST(0)转换为单/双精度格式,然后存入存储单元
FSTPx:弹出栈顶元素,并完成与FSTx相同的功能
FISTx:将栈顶数据从浮点格式转换为int型后,存入存储单元
FISTP:弹出栈顶元素,并完成与FISTx相同的功能
带P结尾指令表示操作数会出栈,也即ST(1)将变成ST(0)
(3) 交换
FXCH:交换栈顶和次栈顶两元素
(4) 常数装载到栈顶
FLD1 :装入常数1.0
FLDZ :装入常数0.0
FLDPI :装入常数pi (=3.1415926...)
FLDL2E :装入常数log(2)e
FLDL2T :装入常数log(2)10
FLDLG2 :装入常数log(10)2
FLDLN2 :装入常数Log(e)2算术运算类
(1) 加法
FADD/FADDP: 相加/相加后弹出栈
FIADD:按int型转换后相加
(2) 减法
FSUB/FSUBP : 相减/相减后弹出栈
FSUBR/FSUBRP:调换次序相减/相减后弹出栈
FISUB:按int型转换后相减
FISUBR:按int型转换并调换次序相减
若指令未带操作数,则默认操作数为ST(0)、ST(1)
带R后缀指令是指操作数顺序变反,例如:fsub执行的是x-y,fsubr执行的就是y-x
(3) 乘法
FMUL/FMULP: 相乘/相乘后弹出栈
FIMUL:按int型转换后相乘
(4) 除法
FDIV/FDIVP : 相除/相除后弹出栈
FIDIV:按int型转换后相除
FDIVR/FDIVRP:调换次序相除/相减后弹出栈
FIDIVR:按int型转换并调换次序相除
& 6. MMX及SSE指令
(暂略)
注:文中图片来源于mooc官网