最近学习了X86汇编,其实无论是古老的8086还是现在i3/5/7/9,Xeon3/5,在最基本原理上,都是相通的,只是CPU位数,寻址空间,寄存器个数,指令集的扩充等方面有所不同,对于学习,8086永不过时。
1.CPU组成
一个典型CPU由:运算器、控制器、寄存器(CPU工作原理)等部件构成,这些器件依靠内部总线连接。
运算器进行信息处理
寄存器进行信息存储,每个CPU有不同数量的寄存器
控制器控制各种器件进行工作
内部总线连接各种器件,在他们之间进行数据的传送
对于一个汇编程序员来说,寄存器是CPU中可以用指令读写的部件,程序员通过改变寄存器中的内容来实现对CPU的控制。
常用的寄存器(reg)有:ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,sp,bp,si,di
段寄存器(sreg)有:ds,ss,cs,es
2.通用寄存器
以8086为例,8086CPU的寄存器都是16位的,有4个通用寄存器:AX、BX、CX、DX,但是为了兼容更老的CPU,每个16位通用寄存器可以分为2个8位的独立通用寄存器,如:AX可分成:AH、AL独立使用,其他也一样。
汇编指令(不区分大小写):
mov ax, 18 ;将18存入ax寄存器
mov ah, 78 ;将78存入ah寄存器,也就是ax的高8位
add ax, 8 ;将ax寄存器的值加8,并将结果写入ax寄存器
mov ax, bx ;将bx寄存器的值,存入ax寄存器
add ax, bx ;将ax寄存器值加上bx寄存器的值,并将结果写入ax寄存器
3.8086CPU寻址方式
8086CPU是16位机,字长为16,运算一次最多可以处理16位数据,寄存器最大宽度为16位,寄存器和运算器之间通路为16位,但是,8086却有20位地址总线,其寻址空间为1MB。
那么8086CPU怎么实现20位地址总线寻址呢?
8086采用在内部用2个16位地址合成的方式来形成一个20位的物理地址。
(1)CPU中相关部件提供2个16位地址,一个称为段地址,另一个称为偏移地址
(2)段地址和偏移地址通过内部总线送入一个称为地址加法器的部件
(3)地址加法器将2个16位地址合成一个20位的物理地址,算法:物理地址 = 段地址16 + 偏移地址,说明:段地址16也称为基础地址
(4)地址加法器通过内部总线将20位物理地址送入输入输出控制电路
(5)输入输出控制电路将20位物理地址送上地址总线
(6)20位物理地址被地址总线传入到外部存储器芯片上
如:8086处理器要访问123C8H的内存单元,此时地址加法器是这样工作的:
(1)相关部件将地址拆分成:1230 (地址段)和 00C8(偏移段 )
(2)1230 和 00C8 送入地址加法器
(3)地址段16成为:12300
(4)物理地址 = 12300 + 00C8
(5)地址加法器输出:123C8
段地址16 + 偏移地址的本质含义:CPU在访问物理内存时,用一个基础地址(段地址*16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。
基于基础地址(段地址*16) + 偏移地址 = 物理地址的思想,我们可以用分段的方式管理内存,记住:内存本来不分段,是CPU给他分的段。我们可以认为:地址10000H ~ 100FFH的内存单元组成一个段,该段的基地址(起始地址)为10000H,段地址为1000H,大小为100H。段怎么划分都可以,可以根据需要将地址连续,起始地址为16倍数的一组内存单元定义为一个段。
8086一个段最大为64KB(2的16次方,因为只有16位)。
寻址方式总结:
直接寻址:[idata], EA=idata,SA=(ds)
寄存器间接寻址:[bx], EA=(bx),SA=(ds)
寄存器相对寻址:[bx+idata], EA=(bx)+idata,SA=(ds)
基址变址寻址:[bx+si], EA=(bx)+(si),SA=(ds)
相对基址变址寻址:[bx+si+idata], EA=(bx)+(si)+idata,SA=(ds)
4.段寄存器、IP
段地址在段寄存器中存放,8086有4个段寄存器:CS、DS、SS、ES,当8086CPU要访问内存时,由这四个段寄存器提供内存单元的段地址。
CS为代码段寄存器
IP为指令指针寄存器
如果CS中内容为M,IP中内容为N,8086CPU将从内存M*16+N单元开始读取一条指令并执行。也可以表述为:CPU将CS:IP执向的内容当做指令执行。
8086CPU工作过程概要如下:
(1)从CS:IP执向的内存单元读取指令,读取的指令进入指令缓冲器
(2)IP=IP+所读取指令长度,从而执向下一条指令
(3)执行指令。然后转到步骤(1)
在启动和复位后,即CPU刚开始工作的时候,CS和IP被设置为:CS=FFFFH,IP为:0000H,即:在CPU刚启动时候,CPU从内存FFFF0H单元中读取指令,FFFF0H单元中的指令是8080PC机开机后执行的第一条指令。
程序员可以通过修改CS、IP来控制CPU执行指令,这个现在应该很容易理解了。
(1)同时改变CS和IP,能够改变CS、IP的指令成为跳转指令,即:JMP指令,如:jmp 2AE3:3 ,执行后:CS=2AE3H,IP=0003H,CPU将从2AE3:3读取指令并执行。
(2)只改变IP,如果只想修改IP,可以:jmp 某一合法寄存器,前提是该寄存器必须存入合法的IP值。
5.代码段
我们可以认为内存是存放代码的,而内存又可以被CPU分段,那么我们可以定一个代码段。如何使代码段中的代码被执行呢?由于CPU只会执行CS:IP的代码,不会自动指向我们的代码段去执行,所以我们要通过修改CS、IP的值,让CPU指向我们的代码段去执行。
6.段前缀
在访问内存地址时候,显示给出段地址所在的段寄存器:
mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]
mov ax, cs:[0]
mov ax, ss:[0]
这些出现在访问内存单元指令中,用于显示指明内存单元的段地址的“ds”,“cs”,“es”,“ss”,在汇编语言中称为段前缀。
7.其他寄存器
bp寄存器
只要在[...]中使用寄存器bp,而指令中没有显性给出段地址,段地址默认在ss寄存器中。
eg. mov ax, bx
8.特殊寄存器
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有如下3种作用。
(1)用来存储相关指令的某些执行结果
(2)用来为CPU执行相关指令提供行为数据
(3)用来控制CPU的相关工作方式
这种寄存器在8086中被称为标志寄存器,flag寄存器和其他寄存器不一样,其他寄存器是存放数据的,flag寄存器是按位来使用的。
下图为8086的flag寄存器示意图:
bit位 | 意义 |
---|---|
0(CF) | 进位标志 |
1 | 未使用 |
2(PF) | 奇偶标志位,它记录相关指令执行后,结果是否为偶数,如果结果为偶数,zf=1,如果结果奇数,zf=0 |
3 | 未使用 |
4(AF) | |
5 未使用 | |
6(ZF) | 零标志位,它记录相关指令执行后,结果是否为0;如果结果为0,zf=1,如果结果不为0,zf=0 |
7(SF) | 正负标志位,它记录相关指令执行后,结果是否为负,如果结果为负,zf=1,如果结果非负,zf=0 |
8(TF) | |
9(IF) | |
10(DF) | 方向标志位,在串指令中,控制si和di的增减。df=0:每次操作后,si、di递增,df=1:每次操作后,si、di递减 |
11(OF) | 溢出标志位 |
12 | 未使用 |
13 | 未使用 |
14 | 未使用 |
15 | 未使用 |
原文地址:https://blog.51cto.com/14207158/2473558