目录
前言
参考教材:王爽编著的《汇编语言》(第3版)
第1章 基础知识
1.6 存储单元
一个存储单元存储 1字节(Byte/B) = 8比特/位(bit)。
转换前 | 转换后 |
---|---|
1KB | 210B |
1MB | 220B |
1GB | 230B |
1TB | 240B |
1KB的存储器有_1024_个存储单元,存储单元的编号从__0__到__1023__。
8080、8088、80286、80386的地址总线宽度分别为16根、20根、24根、32根,则它们的寻址能力分别为:__64__KB、__1__MB、__16__MB、__4__GB。
在存储器中,数据和程序以__二进制__形式存放。
1.8 地址总线
一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。
1个CPU的寻址能力为8KB,那么它的地址总线的宽度为_13_位。
解答:8KB = 23 x 210B = 213B。
1.9 数据总线
8根数据总线一次可传送一个8位二进制数据(即一个字节)。
8086和80386的数据总线宽度分别是16根和32根,也就是说8086和80386一次可传送16位和32位二进制数据即2Byte和4Byte。
8080、8088、8086、80286、80386的数据总线宽度分别为8根、8根、16根、16根、32根,则它们一次可以传送的数据分别为:__1__B、__1__B、__2__B、__2__B、__4__B。
从内存中读取1024字节的数据,8086至少要读__512__次,80386至少要读__256__次。
第2章 寄存器
- 8086CPU有14个寄存器,每个寄存器有一个名称。这些寄存器是:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。
add ax,bx
的含义:
将寄存器 ax 中的数据与寄存器 bx 中的数据相加,结果保存在寄存器 ax 中。
2.1 通用寄存器
8086CPU的所有寄存器都是16位的,可以存放两个字节。AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,被称为通用寄存器。
2.3 几条汇编指令
add al,93H 指令产生的进位不会存储在 ah 中,add al,93H 进行的是 8 位运算。
2.7 “段地址×16+偏移地址=物理地址”的本质含义
给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为__00010H__和__1000FH__。
解答:0001H×10+0000H=00010H,0001H×10+FFFFH=1000FH。
有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为__1001H__,最大为__2000H__。
解答:偏移地址最大时,段地址最小,偏移地址最大为FFFFH,20000H-FFFFH=10001H,发现10001H除以10H除不尽,所以不能这样计算。可以换一种角度理解,当段地址为1000H时,偏移地址取FFFFH,则其内存单元为1FFFFH,而1FFFFH+1H=20000H,偏移地址仍是FFFF,则段地址应为1001H。求最大则可以直接计算。
2.10 CS和IP
CS为代码段寄存器,IP为指令指针寄存器。
在8086PC机中,任意时刻设CS中的内容为M,IP中的内容为N,8086CPU将从内存M×16+N单元开始,读取一条指令并执行。
2.11 修改CS、IP的指令
mov指令不能用于设置CS、IP的值。
jmp指令可以修改CS、IP。
- “jmp 段地址:偏移地址”——同时修改CS、IP的内容
例:jmp 2AE3:3,执行后:CS=2AE3H,IP=0003H,CPU将从2AE33H处读取指令。
- “jmp 某一合法寄存器”——仅修改IP的内容,jmp ax 可以理解为 mov IP,ax。
例:jmp ax,指令执行前:ax=1000H,CS=2000H,IP=0003H
指令执行后:ax=1000H,CS=2000H,IP=1000H
下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?
mov ax,bx
sub ax,ax
jmp ax
解答:CPU读取mov ax,bx的时候,第一次修改IP
读取sub ax,ax的时候,第二次修改IP
读取jmp ax的时候,第三次修改IP
当执行jmp ax的时候,IP变为0,这是第4次修改
最后IP变为0
第3章 寄存器(内存访问)
3.1 内存中字的存储
我们用0、1两个内存单元存放数据20000(4E20H)。0、1两个内存单元用来存储一个字,这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,由0、1两个字节单元组成)。对于这个字节单元来说,0号单元是低地址单元,1号单元是高地址单元,则字型数据4E20H的低位字节单元存放在0号单元中,高位字节存放在1号单元中。同理,将2、3号单元看作一个字单元,它的起始地址为2。在这个字单元中存放数据18(0012H),则在2号单元中存放低位字节12H,在3号单元中存放高位字节00H。
3.2 DS和[address]
在8086PC中,内存地址由段地址和偏移地址组成。8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。
在DEBUG中,用 “d 0:0 lf” 查看内存,结果如下:
0000:0000 70 80 F0 30 EF 60 30 E2-00 80 80 12 66 20 22 60
0000:0010 62 26 E6 D6 CC 2E 3C 3B-AB BA 00 00 26 06 66 88
下面的程序执行前,AX=0,BX=0,写出每条汇编指令执行完后相关寄存器中的值
mov ax,1
mov ds,ax
mov si,2
mov ax,[0000] ax=__2662__H
mov bx,[0001] bx=__E626__H
mov ax,bx ax=__E626__H
mov ax,[000C] ax=__0626__H
mov bx,[0002] bx=__D6E6__H
add ax,bx ax=__DD0C__H
提示:注意ds的设置。
解答:为什么从0000:0010开始找?因为0001:0000和0000:0010指向的都是00010H处,是等价的。
相关题目:https://blog.csdn.net/freezhanacmore/article/details/17740999
3.7 CPU提供的栈机制
8086CPU的入栈和出栈操作都是以字为单位进行的。
8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。
- push ax 的执行,由以下两步完成:
- SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
- 将 ax 中的内容送入 SS:SP 指向的内存单元处, SS:SP 此时指向新栈顶。
- pop ax 的执行过程和 push ax 刚好相反,由以下两步完成:
- 将 SS:SP 指向的内存单元处的数据送入ax中;
- SP=SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
如果将 10000H~1000FH 这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=0010H。
3.9 push、pop指令
push和pop指令是可以在寄存器和内存之间传送数据的。
push 寄存器 ;将一个寄存器中的数据入栈
pop 寄存器 ;出栈,用一个寄存器接收出栈的数据
push 内存单元 ;将一个内存字单元处的字入栈(注意:栈操作都是以字为单位)
pop 内存单元 ;出栈,用一个内存字单元接收出栈的数据
1.补全下面的程序,使其可以将10000H ~ 1000FH中的8个字,逆序复制到20000F ~ 2000FH中。逆序复制的含义如图所示(图中内存里的数据均为假设)。
mov ax,1000H
mov ds,ax
----补充部分----
mov ax,2000H
mov ss,ax
mov sp,0010
----补充部分----
push [0]
push [2]
push [4]
push [6]
push [8]
push [A]
push [C]
push [E]
2.补全下面的程序,使其可以将10000H ~ 1000FH中的8个字,逆序复制到20000H ~ 2000FH中。
mov ax,2000H
mov ds,ax
----补充部分----
mov ax,1000H
mov ss,ax
mov sp,0000H
----补充部分----
pop [E]
pop [C]
pop [A]
pop [8]
pop [6]
pop [4]
pop [2]
pop [0]
如果要在10000H处写入字数据2266H,可以用以下的代码完成:
mov ax,1000H
mov ds,ax
mov ax, 2266H
mov [0], ax
补全下面的3条指令,实现采用push ax 语句完成同样的功能:在10000H处写入字型数据2266H。
____mov ax,1000H__
____mov ss,ax_____
____mov sp,2______
mov ax,2266H
push ax
第4章 第一个程序
4.2 源程序
下面就是一段简单的汇编语言源程序。
assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21H
codesg ends
end
下面对程序进行说明。
1. 伪指令
- segment和ends是一对成对使用的伪指令,它们的功能是定义一个段,segment说明一个段开始,ends说明一个段结束。
比如,程序中的:codesg segment ;定义一个段,段的名称为“codesg”,这个段从此开始 ...... codesg ends ;名称为“codesg”的段到此结束
- end是一个汇编程序的结束标记。
- assume的含义为“假设”。它假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联。
5. 程序返回
mov ax,4c00H
int 21H
这两条指令所实现的功能就是程序返回。
4.8 谁将可执行文件中的程序装载进入内存并使它运行?
汇编程序从写出到执行的过程:
编程 (Edit) → 1.asm → 编译 (masm) → 1.obj → 连接 (link) → 1.exe → 加载 (command) → 内存中的程序 → 运行 (CPU)
第5章 [BX]和loop指令
- 为了描述上的简洁,我们将使用一个描述性的符号“()”来表示一个寄存器或一个内存单元中的内容。比如:
- (ax)表示ax中的内容、(al)表示al中的内容。
- 约定符号idata表示常量。
5.1 [BX]
mov ax,[bx]
功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据传入ax中。即:(ax)=((ds)*16+(bx))。
mov [bx],ax
功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA处。即:((ds)*16+(bx))=(ax)。
程序和内存中的情况如图所示,写出程序执行后,21000H~21007H单元中的内容。(inc bx的含义是bx中的内容加1)
mov ax,2000H
mov ds,ax
mov bx,1000H
mov ax,[bx]
inc bx
inc bx
mov [bx],ax
inc bx
inc bx
mov [bx],ax
inc bx
mov [bx],al
inc bx
mov [bx],al
执行后:
5.2 Loop指令
用cx和loop指令相配合实现循环功能的程序框架如下:
mov cx,循环次数
s:
循环执行的程序段
loop s
第6章 包含多个段的程序
6.1 在代码段中使用数据
考虑这样一个问题,编程计算以下8个数据的和,结果存在ax寄存器中:
0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
start: mov bx,0
mov ax,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end start
- 程序第一行的“dw”的含义是定义字型数据。dw即“define word”。在这里,定义了8个字型数据(数据之间以逗号分隔),它们所占的内存空间的大小为16个字节。“db”的含义则是定义字节型数据。
- end除了通知编译器程序结束外,还可以指明程序的入口在标号start处。
我们可以这样安排程序的框架(便于debug):assume cs:code code segment .......... ...数据... .......... start: .......... ...代码... .......... code ends end start
1.下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,完成程序:
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
start:mov ax,0
mov ds,ax
mov bx,0
mov cx,8
s: mov ax,[bx]
____mov cs:[bx],ax____
add bx,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
2.下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,数据的传送用栈来进行。栈空间设置在程序内。完成程序:
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0 ;10个字单元用栈空间
start:
mov ax,__cs__
mov ss,ax
mov sp,__24h__
mov ax,0
mov ds,ax
mov bx,0
mov cx,8
s:
push [bx]
___pop cs:[bx]___
add bx,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
注意:程序中的数据是0123h~0987h,是使用别处的数据(位于内存0:0~0:15)对它们进行更新。cs指向的是0123h~0987h所在的段。
解答:第二空为什么是24h?因为0123h~0987h为0h~0Fh,栈空间为11h~23h,sp指向下一个即24h。
第7章 更灵活的定位内存地址的方法
7.1 and和or指令
- and指令:逻辑与指令,按位进行与运算。
例如指令:
执行后:al=00100011Bmov al,01100011B and al,00111011B
- or指令:逻辑或指令,按位进行或运算。
例如指令:
执行后:al=01111011Bmov al,01100011B or al,00111011B
7.3 以字符形式给出的数据
我们可以在汇编程序中,用 ‘…’ 的方式指明数据是以字符的形式给出的,编译器将把它们转化为相对应的ASCII码。
例如:
db 'unIX'
mov al,'a'
7.5 [bx+idata]
[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加上idata)。
下面三种形式是等价的:
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
7.7 SI和DI
si 和 di 是8086CPU中和 bx 功能相近的寄存器,si和di不能够分成两个8位寄存器来使用。
7.10 不同的寻址方式的灵活应用
- [idata]用一个常量来表示地址,可用于直接定位一个内存单元;
- [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
- [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
- [bx+si]用两个变量表示地址;
- [bx+si+idata]用两个变量和一个常量表示地址。
第8章 数据处理的两个基本问题
我们定义的描述性符号:reg(寄存器)和 sreg(段寄存器)。
- reg 的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;
- sreg 的集合包括:ds、ss、cs、es。
8.1 bx、si、di 和 bp
- 在8086CPU中,只有这4个寄存器可以用在 “[…]” 中来进行内存单元的寻址。
- 在[…]中,这4个寄存器可以单个出现,或只能以4种组合出现:bx 和 si、bx 和 di、bp 和 si、bp 和 di。
- 只要在[…]中使用寄存器 bp ,而指令中没有显性地给出段地址,段地址就默认在 ss 中。
8.3 汇编语言中数据位置的表达
立即数(idata)
立即数在汇编指令中直接给出。
例:mov ax,1
段地址(SA)和偏移地址(EA)
在汇编指令中可用 [X] 的格式给出 EA,SA 在某个段寄存器中。
存放段地址的寄存器可以显性给出。
例:mov ax,ds:[bp]
8.4 寻址方式
寻址方式 | 名称 |
---|---|
[idata] | 直接寻址 |
[bx] | 寄存器间接寻址 |
[bx+idata] | 寄存器相对寻址 |
[bx+si] | 基址变址寻址 |
[bx+si+idata] | 相对基址变址寻址 |
8.5 指令要处理的数据有多长
在没有寄存器名存在的情况下,用操作符 X ptr 指明内存单元的长度,X 在汇编指令中可以为 word 或 byte。
- word ptr——指令访问一个字单元
- byte ptr——指令访问一个字节单元
假设我们用 Debug 查看内存的结果如下:
2000:1000 FF FF FF FF FF FF ...
那么指令:
mov ax,2000H
mov ds,ax
mov byte ptr [1000H],1
将使内存中的内容变为:
2000:1000 01 FF FF FF FF FF ...
而指令:
mov ax,2000H
mov ds,ax
mov word ptr [1000H],1
将使内存中的内容变为:
2000:1000 01 00 FF FF FF FF ...
push 指令不用指明访问的是字单元还是字节单元,因为 push 指令只进行字操作。
8.7 div 指令
div 是除法指令,使用 div 做除法的时候应该注意以下问题。
- 除数:有 8 位和 16 位两种,在一个 reg 或内存单元中。
- 被除数:默认放在 AX 或 DX 和 AX 中,如果除数为 8 位,被除数则为 16 位,默认在 AX 中存放;如果除数为 16 位,被除数则为 32 位,在 DX 和 AX 中存放,DX 存放高 16 位,AX 存放低 16 位。
- 结果:如果除数为 8 位,则 AL 存储除法操作的商,AH 存储除法操作的余数;如果除数为 16 位,则 AX 存储除法操作的商,DX 存储除法操作的余数。
几个例子:
div byte ptr ds:[0]
含义: (al) = (ax) / ((ds) * 16 + 0) 的商
(ah) = (ax) / ((ds) * 16 + 0) 的余数
div word ptr [bx+si+8]
含义: (ax) = [(dx) * 10000H + (ax)] / ((ds) * 16 + (bx) + (si) + 8) 的商
(dx) = [(dx) * 10000H + (ax)] / ((ds) * 16 + (bx) + (si) + 8) 的余数
8.8 伪指令dd
dd是用来定义 dword(doubleword,双字)型数据的。比如:
data segment
db 1
dw 1
dd 1
data ends
在 data 段定义了 3 个数据:
- 第一个数据为 01H,在 data:0 处,占 1 个字节;
- 第二个数据为 0001H,在 data:1 处,占1个字;
- 第三个数据为 00000001H,在 data:3 处,占 2 个字。
用 div 计算 data 段中第一个数据除以第二个数据后的结果,商存在第三个数据的存储单元中。
data segment
dd 100001
dw 100
dw 0
data ends
分析:
data 段中的第一个数据是被除数,为 dword(双字)型,32 位,所以在做除法之前,用 dx 和 ax 存储。应将 data:0 字单元中的低 16 位存储在 ax 中,data:2 字单元中的高 16 位存储在 dx 中。程序如下:
mov ax,data
mov ds,ax
mov ax,ds:[0] ;ds:0字单元中的低16位存储在ax中
mov dx,ds:[2] ;ds:2字单元中的高16位存储在dx中
div word ptr ds:[4] ;用dx:ax中的32位数据除以ds:4字单元中的数据
mov ds:[6],ax ;将商存储在ds:6字单元中
8.9 dup
dub 是和 db、dw、dd 等数据定义伪指令配合使用的,用来进行数据的重复。
比如:
db 3 dup (0)
db 3 dup (0,1,2)
相当于:
db 0,0,0
db 0,1,2,0,1,2,0,1,2
第9章 转移指令的原理
可以修改 IP,或同时修改 CS 和 IP 的指令统称为转移指令。概括地讲,转移指令就是可以控制 CPU 执行内存中某处代码的指令。
8086CPU 的转移行为有以下几类:
- 段内转移(只修改 IP)
例如:
段内转移根据 IP 的修改范围又分为jmp ax
- 短转移(short)(修改范围为-128~127,一个字节)
- 近转移(near)(修改范围为-32768~32767,一个字)
- 段间转移(far,又称为远转移)(同时修改 CS 和 IP)
例如:jmp 1000:0
8086CPU 的转移指令分为以下几类:
- 无条件转移指令(如:jmp)
- 条件转移指令(如:jcxz)
- 循环指令(如:loop)
- 过程
- 中断
9.1 操作符 offset
offest 的功能是取得标号的偏移地址。比如下面的程序:
assume cs:codesg
codesg segment
start:mov ax,offset start ;相当于mov ax,0
s:mov ax,offset s ;相当于mov ax,3
codesg ends
end start
在上面的程序中,offset 操作符取得了标号 start 和 s 的偏移地址 0 和 3。第一条指令长度为 3 个字节,则 s 的偏移地址为 3 。
有如下程序段,添写两条指令,使该程序在运行中将 s 处的一条指令复制到 s0 处。
assume cs:codesg
codesg segment
s: mov ax,bx
mov si,offest s
mov di,offset s0
__mov ax,cs:[si]__
__mov cs:[di],ax__
s0: nop
nop
codesg ends
end s
9.3 依据位移进行转移的 jmp 指令
转移位移的计算方法:
9.6 转移地址在内存中的 jmp 指令
转移地址在内存中的 jmp 指令有两种格式:
- jmp word ptr 内存单元地址(段内转移)
例如:
执行后,(IP)=0123H。mov ax,0123H mov ds:[0],ax jmp word ptr ds:[0]
- jmp dword ptr 内存单元地址(段间转移)
(CS)=(内存单元地址+2)
(IP)=(内存单元地址)
例如:
执行后,(CS)=0,(IP)=0123H。mov ax,0123H mov ds:[0],ax mov word ptr ds:[2],0 jmp dword ptr ds:[0]
程序如下:
assume cs:code
data segment
?
data ends
code segment
start: mov ax,data
mov ds,ax
mov bx,0
jmp word ptr [bx+1]
code ends
end start
若要使程序中的jmp指令执行后,CS:IP指向程序的第一条指令,在data段中应该定义哪些数据?
答:db 8 dup (0)
程序如下:
assume cs:code,ds:data
data segment
dd 12345678h
data ends
code segment
start: mov ax,data
mov ds,ax
mov bx,0
mov [bx],__bx__
mov [bx+2],__cs__
jmp dword ptr ds:[0]
code ends
end start
补全程序,使jmp指令执行后,CS:IP指向程序的第一条指令。
用Debug查看内存,结果如下:
2000:1000 BE 00 06 00 00 00 ......
则此时,CPU执行指令:
mov ax,2000H
mov es,ax
jmp dword ptr es:[1000H]
后,(CS)=__0006__ H, (IP)=__00BE__H
9.7 jcxz 指令
jcxz 指令为有条件转移指令,所有的有条件转移指令都是短转移。
“jcxz 标号”的功能用 C 语言描述:
if((cx)==0) jmp short 标号;
补全编程,利用jcxz指令,实现在内存2000H段中找查第一个值为为0的字节,找到后,将它的偏移地址存储在dx中。
assume cs:code
code segment
start: mov ax,2000H
mov ds,ax
mov bx,0
s: __mov ch,0__
__mov cl,[bx]__
__jcxz ok__
__inc bx__
jmp short s
ok: mov dx,bx
mov ax,4c00h
int 21h
code ends
end start
9.8 loop 指令
补全程序,利用loop指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。
assume cs:code
code segment
start: mov ax,2000h
mov ds,ax
mov bx,0
s: mov cl,[bx]
mov ch,0
__inc cx__
inc bx
loop s
ok: dec bx ;dec指令的功能和inc相反,dec bx进行的操作为:(bx)=(bx)-1
mov dx,bx
mov ax,4c00h
int 21h
code ends
end start
第10章 CALL 和 RET 指令
10.1 ret 和 retf
- ret 指令用栈中的数据,修改 IP 的内容,从而实现近转移,相当于:
pop IP
- reft 指令用栈中的数据,修改 CS 和 IP 的内容,从而实现远转移,相当于:
pop IP pop CS
补全程序,实现从内存1000:0000处开始执行指令。
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,__1000H__
push ax
mov ax,__0__
push ax
retf
code ends
end start
10.2 call 指令
CPU 执行 call 指令时,进行两步操作:
- 将当前的 IP 或 CS 和 IP 压入栈中
- 转移
10.3 依据位移进行转移的 call 指令
CPU 执行“call 标号”时,相当于进行:
push IP
jmp near ptr 标号
下面的程序执行后,ax 中的数值为__6__。
内存地址 | 机器码 | 汇编指令 |
---|---|---|
1000:0 | b8 00 00 | mov ax,0 |
1000:3 | e8 01 00 | call s |
1000:6 | 40 | inc ax |
1000:7 | 58 | s:pop ax |
10.4 转移的目的地址在指令中的 call 指令
CPU 执行“call far ptr 标号”时,相当于进行:
push CS
push IP
jmp far ptr 标号
下面的程序执行后,ax 中的数值为__1010H__。
内存地址 | 机器码 | 汇编指令 |
---|---|---|
1000:0 | b8 00 00 | mov ax,0 |
1000:3 | 9A 09 00 00 10 | call far ptr s |
1000:8 | 40 | inc ax |
1000:9 | 58 | s:pop ax |
add ax,ax | ||
pop bx | ||
add ax,bx |
10.5 转移地址在寄存器中的 call 指令
CPU 执行“call 16位reg”时,相当于进行:
push IP
jmp 16位reg
下面的程序执行后,ax 中的数值为__000BH__。
内存地址 | 机器码 | 汇编指令 |
---|---|---|
1000:0 | b8 06 00 | mov ax,6 |
1000:3 | ff d0 | call ax |
1000:5 | 40 | inc ax |
1000:6 | mov bp,sp | |
add ax,[bp] |
第11章 标志寄存器
8086CPU 的 flag 寄存器的结构如图所示:
11.1 ZF 标志
零标志位。
mov ax,1
sub ax,1
执行后,结果为 0,则 zf=1。
mov ax,2
sub ax,1
执行后,结果不为0,则 zf=0。
11.2 PF 标志
奇偶标志位。
mov al,1
add al,10
执行后,结果为 00001011B,其中有 3(奇数)个 1,则 pf=0。
mov al,1
or al,2
执行后,结果为 00000011B,其中有 2(偶数)个 1,则 pf=1。
11.3 SF 标志
符号标志位。
mov al,10000001B
add al,1
执行后,结果为 10000010B,sf=1,表示:如果指令进行的是有符号数运算,那么结果为负。
mov al,10000001B
add al,01111111B
执行后,结果为 0,sf=0,表示:如果指令进行的是有符号数运算,那么结果为非负。
11.4 CF 标志
进位/借位标志位。
mov al,98H
add al,al ;执行后:(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值
add al,al ;执行后:(al)=60H,CF=0,CF记录了从最高有效位向更高位的进位值
mov al,97H
sub al,98H ;执行后:(al)=FFH,CF=1,CF记录了向更高位的借位值
sub al,al ;执行后:(al)=0,CF=0,CF记录了向更高位的借位值
11.5 OF 标志
溢出标志位。
11.6 adc 指令
adc 是带进位加法指令,它利用了 CF 位上记录的进位值。
比如指令 adc ax,bx
实现的功能是:(ax)=(ax)+(bx)+CF
11.7 sbb 指令
sbb 是带借位减法指令,它利用了 CF 位上记录的借位值。
比如指令 sbb ax,bx
实现的功能是:(ax)=(ax)-(bx)-CF
11.8 cmp 指令
cmp 是比较指令,cmp 的功能相当于减法指令,只是不保存结果。
指令 cmp ax,bx
的逻辑含义是比较 ax 和 bx 中的值,如果执行后:
- zf=1,说明(ax)=(bx)
- zf=0,说明(ax)≠(bx)
- cf=1,说明(ax)<(bx)
- cf=0,说明(ax)≥(bx)
- cf=0 并且 zf=0,说明(ax)>(bx)
- cf=1 或 zf=1,说明(ax)≤(bx)
11.9 检测比较结果的条件转移指令
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移 | zf=1 |
jne | 不等于则转移 | zf=0 |
jb | 低于则转移 | cf=1 |
jnb | 不低于则转移 | cf=0 |
ja | 高于则转移 | cf=0 且 zf=0 |
jna | 不高于则转移 | cf=1 或 zf=1 |
第一个字母 j 表示 jump,后面的字母表示意义如下:
e:表示 equal
ne:表示 not equal
b:表示 below
nb:表示 not below
a:表示 above
na:表示 not above
11.11 pushf 和 popf
pushf 的功能是将标志寄存器的值压栈,而 popf 是从栈中弹出数据,送入标志寄存器中。