1. CPU中的运算器做信息处理;寄存器进行信息存储;控制器控制各种器件进行工作;内部总线连接各器件,在它们之间进行数据的传送。对于汇编程序员来说,CPU中的主要部件是寄存器。寄存器是CPU中程序员可以用指令读写的部件,并通过改变各种寄存器的内容来实现对CPU的控制。
8086CPU(16位CPU)有14个寄存器,分别是:AX,BX,CD,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES和PSW。
每个寄存器又可分成两个寄存器,一个是高位,一个是低位。如AX,可分成AH,AL两个寄存器,如下图:
AX的低8位(0位-7位)构成了AL寄存器,高8位(8位-15位)构成了AH寄存器。AH和AL是可以独立使用的8位寄存器。存储形式如下图:
一个16位寄存器所能存储数据的最大值是:2 ^ 16 - 1 = 65535,8位寄存器所能存储数据的最大值是:2 ^ 8 – 1 = 255
2. mov, add 指令
格式:
mov 目标寄存器, 源数据(可以是常量值或寄存名) ; 将源数据存入目标寄存器中
add 目标寄存器,源数据(可以是常量值或寄存名) ; 将源数据与目标寄存器中的数据相加,结果存入目标寄存器中
如:
mov ax, 18 ; 将18送入寄存器AX
mov ah, 78 ; 将78送入寄存器AH
add ax, 8 ; 将寄存器AX中的数值加上8
mov ax, bx ; 将寄存器BX中的数据送入寄存器AX
add ax, bx ; 将AX和BX中的数值相加,结果存在AX中
注:汇编语言中不区分大小写,mov ax, 18 与 MOV AX, 18 是等价的
3. 物理地址
8086是16位结构的CPU,有如下结构特性:
1.) 运算器一次最多可处理16位的数据
2.) 寄存器的最大宽度为16位
3.) 寄存器的运算器之间的通路为16位
而8086 CPU的地址总线是20位的,可以传送20位地址,寻址能力是1MB。而CPU内部结构是16位的,寻址能力只有64KB,具体如下图:
上图的8086 CPU读写内存示意图分如下步骤:
1.) CPU提供两个16位的地址,一个为段地址,一个为偏移地址;
2.) 段地址和偏移地址通过内部总线送入地址加法器;
3.) 地址加法器将两个16位地址合成为一个20位的物理地址;
4.) 地址加法器再通过内部总线把20位的物理地址送入输入输出控制电路;
5.) 输入输出控制电路将20位物理地址送上地址总线;
6.) 最后, 20位物理地址被地址总线传送到存储器
4. 物理地址的求法
地址加法器将两个16位地址合成为一个20位物理地址的方法是:
物理地址 = 段地址 * 16 + 偏移地址
如:8086 CPU要访问地址为:123C8H的内存单元,地址加法器所做的工作如下(以下地址均以16进制表示):
1. CPU先将123C8H 转换成段地址和偏移地址,段地址对应为1230,偏移地址对应为00C8,并传入给地址加法器;
2. 加法器将段地址*16,结果为:1230 * 16 = 12300 ;
3. 将段地址乘16后的结果与偏移地址相加:12300 + 00C8 = 123C8,得到物理地址 ;
4. 输出物理地址:123C8
段地址 * 16 + 偏移地址 = 物理地址的本质含义是:CPU在访问内存时,用一个基础地址(段地址 * 16)和一个相对基础地址的偏移地址相加,给出内存单元的物理地址。这也是8086 CPU的寻址方式,它以段地址 * 16看作是基础地址。
5. 段
事实上,内存并没有分段,段的划分来自于CPU,CPU通过分段的方式来管理内存。而采用分段管理内存的依据也是来自于CPU的寻址方式,即:物理地址 = 段地址 * 16 + 偏移地址。根据段地址 * 16为基础地址,一个段的起始地址也一定是16的倍数;偏移地址是16位,16位的寻址能力为64KB,所以一个段的长度最大为64KB。
而下图说明:CPU可以用不同的段地址和偏移地址形成同一个物理地址。
再根据段的最大寻址能力为64KB,假如有段地址为:1000H,用偏移地址寻址,CPU的寻址范围为:1000H~1FFFFH。
在8086PC机中,存储单元的地址用两个元素来描述,即段地址和偏移地址。所以,如果“数据在21F60H内存单元中”,的说话有两种:1.) 数据存在内存2000: 1F60单元中;2.) 数据存在内在的2000段中的1F60单元中。
6. 段寄存器
8086CPU有4个段寄存器:CS,DS,SS,ES。
1.) CS和IP
CS和IP是8086CPU中两个最关键的寄存器,它们指针了CPU当前要读取指令的地址。CS为代码段寄存器,IP为指令指针寄存器,从名称上可以看出它们和指令的关系。
在8086PC机中,任意时刻,设CS中的内容为M,IP中的内容为N,8086CPU将从内存M * 16 + N单元开始,读取一条指令并执行。也可表述为:CPU将CS:IP指向的内容当作指令执行。
8086CPU工作过程:
1.)从CS:IP指向内在单元读取指令,读取的指令进入指令缓冲器;
2.)IP = IP + 所读取指令的长度,从而指向下一条指令;
3.)执行指令。转到团聚1.),重复这具过程
8086CPU 加电启动或复位后(CPU刚开始工作时)CS和IP被设置为CS = F000H,IP = FFFFH,即CPU是从内存FFFF0H单元中读取指令执行,FFFF0H单元中的指令是8086CPU开机后执行的第一条指令。
在这里,CS和IP为CPU提供了所要执行指令的地址。
在任何时候,CPU将CS、IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,执行。如果说,内存中的一段信息被CPU执行过的话,那么,它所在的内存单元必然被CS:IP指向过。
2.) 修改CS、IP的指令
程序员能用指令读写的部件只有寄存器,并可以通过改变寄存器中的指令实现对CPU控制。而CPU中的指令是由CS,IP中的内容决定的,所以,通过修改CS, IP中的内容来控制CPU执行的目标指令。
8086CPU大部分寄存器的值,都可用mov指令来修改,mov指令也被称为传送指令。但mov指令却不能用于修改CS, IP的值,原因是8086CPU没有提供这样的功能。用于修改CS, IP的指令被称为转移指令,即:jmp指令。
若想同时修改CS, IP的内容,可用指令“jmp 段地址: 偏移地址”来完成,如:
jmp 2AE3:3, 执行后:CS = 2AE2H, IP = 0003H, CPU将从2AE33H处读取指令。
jmp 3:0B1B, 执行后:CS = 0003H, IP = 0B16H,CPU将从00B46H处读取指令。
“jmp 段地址: 偏移地址”的指令功能是:用指令中给出的段地址修改CS, 偏移地址修改IP。
若想仅修改IP的内容,可用指令“jmp 某一合法寄存器” 完成,如:
jmp ax, 指令执行前:ax = 1000H, CS = 2000H, IP = 0003H
指令执行后:ax = 1000H, CS = 2000H, IP = 1000H
jmp bx, 指令执行前:bx = 0B16H, CS = 2000H, IP = 0003H
指令执行后:ax = 0B16H, CS = 2000H, IP = 0B16H
指令“jmp 某一合法寄存器”的功能是:用寄存器中的值修改IP
jmp ax, 相当于mov IP, ax 这样的指令。当然,前面已经说到mov指令是不能用于修改CS, IP的,这里只是类比说明。
如果有段代码需要CPU执行,就要让CS、IP指向这块代码所存储的内存地址的首地址。
7. 基于Windows下Debug 的使用
Debug是DOS,Windows的实模式(8086方式)程序的调试工具,使用它可查看CPU各种寄存器中的内容、内存的情况和机器码和跟踪程序的运行。
Debug常用命令:
1.)R命令查看/修改CPU寄存器内容;
2.)D命令查看内存中的内容;
3.)E命令修改内存中的内容;
4.)U命令将内存中的机器指令翻译成汇编指令(反汇编);
5.)T命令机器指令跟踪;
6.)A命令以汇编指令的格式在内存中写入一条机器指令;
1.)R命令格式:R {寄存器名} (注:{} 表参数为可选,下同)
以上执行了r,r ax,1111,r命令,
r 先查看CPU寄存器中的内容;
r ax 查看/修改(此时也可修改寄存器)寄存器ax中的内容;
1111 输入需修改的值
r 查看CPU寄存器中内容
2.)D命令格式:D {段地址: 偏移地址}
要查看内在10000H处的内容,先将这个地址表示为段地址:偏移地址的格式,可以是:1000:0,然后用“d 1000:0”列出在1000:0处的内容。
上面输出分3部分,中间部分是从指定的地址开始的128个内存单元的内容,用16进制的格式输出,每行输出从16的整数倍的地址开始,最多输出16个单元的内容。注意在每行的中间有一个‘-’,它将每行的输出分为两部分,这样便于查看。‘-’的左边是高段,右边是低段。
左边是每行的起始地址。
右边是每个内存单元中的数据对应可显示的ASCII码。
在使用“d 段地址: 偏移地址”后,再接着使用d命令,可列出以偏移量(前一条d 段地址: 偏移地址中的偏移量)为单元显示后面内存地址中的内容。如:
也可以查看指定内存地址范围内的内容“d 段地址: 起始偏移地址 结尾偏移地址”,如要查看1000:0 ~ 1000:9,命令为:d 1000:0 9:
如果想查看内在单元10000H中的内容,可以让“段地址: 偏移地址 ”都表示10000H这个物理地址,命令为:d 1000:0 0:
3.)E命令格式:E 起始地址 数据 数据 数据 …
如果要将内存1000:0 ~ 1000:9 单元中的内容改写为:0, 1, 2, 3, 4, 5, 6, 7, 8, 9,命令为:e 1000:0 0 1 2 3 4 5 6 7 8 9
当然也可一个一个地改写内存中的内容,如:
以上在输入e 1000:10后,会提示在"B0.”后输入你想修改的数据,此时空格,提示下一个内存单元想要输入的数据。如果还想继续输入,可继续空格。如果要跳过某个内存单元,可直接空格,跳到下一单元。
如果想对1000:0, 1000:2, 1000:4单元内写入数据1,2,3,对1000:1, 1000:3, 1000:5单元内写入字符'a', 'b', 'c',命令为:e 1000:0 1 'a' 2 'b' 3 'c':
如果想从1000: 0开始写入:1, 字符串"a+b", 2, 字符串"c++", 3, 字符串"IBM",命令为:e 1000:0 1 "a+b" 2 "c++" 3 "IBM":
4.)U命令格式:U 段地址
如果要查看e 1000:0 b8 01 00 b9 02 00 01 c8 这条指令翻译成汇编(反汇编)指令,可以用命令:U 1000:0
U命令的输出分为3部分:最左边的是机器指令的地址,中间是机器指令,右边是机器指令所对应的汇编指令。
5.)T命令格式:T {段地址: 偏移地址}
如果简单执行T命令,可执行CS: IP指向命令:
上图中,先是E命令从1000:0开始的内存单元里写入8个字节的机器码,再用R命令查看CPU中寄存器的状态,此时是CS = 1383, IP = 0100。接着再修改CS,IP的指向,使CS:IP 指向1000:0。再使用T命令跟踪前面的命令,执行后的AX中的内容被改写为1,IP改为IP + 3(mov ax, 0001的指令长度为3个字节),CS:IP指向下一指令。
6.)A命令格式:A {段地址:偏移地址}
A命令写入汇编指令时,在给出的起始地址后按ENTER键结束操作。
当然,你也可以直接使用A命令来给一直预定的地址(这个预定地址可以用R命令来查看)写入汇编指令,如: