学习参考:汇编语言(第2版)王爽
- ZF 标志
- pf 标志
- SF 标志
- CF 标志
- OF 标志
- adc 指令
- sbb 指令
- cmp 指令
- 检测比较结果的条件转移指令
- DF 标志和串传送指令
- pushf 和 popf
CPU 内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下 3 种作用
- 用来存储相关指令的某些执行结果
- 用来为 CPU 执行相关指令提供行为依据
- 用来控制 CPU 的相关工作方式
这种特殊的寄存器在 8086CPU 中,被称为标志寄存器。8086CPU 的标志寄存器有 16 位,其中存储的信息通常称为程序状态字(PSW)。
标志寄存器(以下简称 flag)和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而 flag 寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。8086CPU 的 flag 寄存器的结构如下图
flag 的 1、3、5、12、13、14、15 位在 8086CPU 中没有使用,不具有任何含义。而0、2、4、6、7、8、9、10、11 位都具有特殊的含义
注意 在8086CPU 的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and 等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对寄存器没有影响,比如,mov,push、pop等,它们大都是传送指令。
在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的哪些标志位造成影响
ZF 标志
flag 的第 6 位是 ZF( Zero Flag ),零标志位。它记录相关指令后,其结果是否为 0.如果结果为 0,zf=1;如果结果不为 0,那么 zf=0。
1 | mov ax,1 |
pf 标志
flag 的第 2 位是 PF( Parity Flag ),奇偶标志位。它记录相关指令执行后,其结果的所有 bit 位中 1 的个数是否为偶数。如果 1 的个数是偶数,pf=1,如果是奇数,pf=0
1 | mov al,1 |
SF 标志
flag 的第 7 位是 SF( Symobl Flag ),符号标志位。它记录相关指令执行后,其结果是否为负。如果为负,sf=1;如果非负,sf=0
1 | mov al,10000001B |
CF 标志
flag 的第 0 位是 CF ( Carry Flag ),进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值
当两数相加时,有可能产生进位,CPU 在运算的时候,将这个进位值记录在一个特殊的寄存器的某一位上。8086CPU 就用 flag 的 CF 位来记录这个进位值。当两数相减时,有可能向更高位借位,而 flag 的 CF 位也可以用来记录这个借位值。
1 | mov al,98H |
OF 标志
在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。
对 8 位有符号数据,机器所能表示的范围就是 -128127。同理,对于 16 位有符号数据,机器所能表示的范围是 -3276832767
如果超出了及其所能表达的范围,将产生溢出
flag 的第 11 位是 OF(Overflow Flag)。一般情况下,OF 记录了有符号数运算的结果是否发生了溢出。如果发生了溢出,OF=1;如果没有,OF=0
注意 注意 CF 和 OF 的区别:
- CF 是对无符号数运算有意义的标志位
- OF 是对有符号数运算有意义的标志位
1 | mov al,98 |
CPU 在执行 add 等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。对于无符号数运算,CPU 用 CF 位来记录是否产生了进位;对于有符号位运算,CPU 用 OF 位来记录是否产生了溢出,当然,还要用 SF 位来记录结果的符号。对于无符号数运算,98+99 没有进位,CF=0;对于有符号数运算,则发生了溢出,OF=1。
CF 和 OF 所表示的进位和溢出,是分别对无符号数和有符号数运算而言的,它们之间没有任何关系。
adc 指令
adc 是带进位加法指令,它利用了 CF 位上记录的进位值
指令功能:adc 操作对象 1,操作对象 2
功能:操作对象 1 = 操作对象 1 + 操作对象 2 + CF
比如指令 adc ax,bx 实现的功能是:(ax)=(ax)+(bx)+CF
1 | mov ax,2 |
Q:那么为什么 CPU 要提供这样一条指令?
A:先来看一下CF的值的含义。在执行adc指令的时候加上的 CF 的值的含义,是由adc 指令前面的指令决定的,也就是说,关键在于所加上的 CF 值是被什么指令设置的。显然,如果 CF 的值是被 sub 指令设置的,那么它的含义就是借位值; 如果是被 add 指令设置的,那么它的含义就是进位值。两个十六进制数相加时,可以分为两步来进行:1.低位相加;2.高位相加再加上低位相加产生的进位值。adc指令的目的,就是来进行加法的第二步运算的。adc 指令和 add 指令相配合就可以对更大的数据进行加法运算。
sbb 指令
sbb 是带借位减法指令,它利用了 CF 位上记录的借位值。
指令格式: sbb操作对象1,操作对象2
功能: 操作对象1=操作对象1 - 操作对象2 - CF
比如指令 sbb ax,bx
实现的功能是: (ax)=(ax)-(bx)-CF
sbb 指令执行后,将对 CF 进行设置。利用 sbb 指令可以对任意大的数据进行减法运
算。比如,计算 003E1000-00202000H,结果放在 ax,bx 中,程序如下:
1 | mov bx,1000H |
sbb 和 adc 是基于同样的思想设计的两条指令,在应用思路上和 adc 类似
cmp 指令
cmp 是比较指令,cmp 的功能相当于减法指令,只是不保存结果。cmp 指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp 指令格式:cmp 操作对象1, 操作对象2
功能:计算操作对象1 - 操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置
比如,指令 cmp ax,ax 做(ax)-(ax) 的运算,结果为 0,但并不保存在 ax 中,仅影响 flag 的相关各位。指令执行后:zf=1,pf=1,sf=0,cf=0,of=0
下面的指令:
1 | mov ax,8 |
检测比较结果的条件转移指令
“转移” 指的是它能够修改 IP,而 “条件” 指的是它可以根据某种条件,决定是否修改 IP
因为cmp 指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据 cmp 指令的比较结果进行转移的指令也分为两种,即根据无符号数的比较结果进行转移的条件转移指令(它们检测 zf,cf的值)和根据有符号数的比较结果进行转移的条件转移指令(它们检测sf.of和zf的值)。
下面是常用的根据无符号数的比较结果进行转移的条件转移指令
指令 | 含义 | 检测的相关标志位 |
---|---|---|
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
通过结合 cmp 指令和条件转移指令就可以实现类似高级语言中 IF 的效果
编程 如果 (ah)=(bh) 则 (ah)=(ah)+(ah),否则 (ah)=(ah)+(bh)
1 | cmp ah,bh |
DF 标志和串传送指令
flag 的第 10 位是 DF(Direction Flag),方向标志位。在串处理指令中,控制每次操作后 si、di 的增减。
- df=0 每次操作后 si、di 递增
- df=1 每次操作后 si、di 递减
介绍一个串传送指令
格式:movsb (MOV String Byte)
功能:执行 movsb 指令相当于进行下面几步操作
- ((es)*16+(di))=((di)*16+(si)) 把 ds:di 指向的内存单元中的字节送入 es:di 中
- 如果 df=0,则 (si)=(si)+1 ; (di)=(di)+1
- 如果 df=1,则 (si)=(si)-1 ; (di)=(di)-1
当然也可以传送一个字,使用指令 movsw (MOV String Word),不过这里 di 和 si 的递增或递减的数量是 2
movsb 和 movsw 都和 rep 配合使用,格式如下:
1 | rep movsb |
用汇编语法来描述该功能就是:
1 | s: movsb |
可见,rep 的作用是根据 cx 的值,重复执行后面的串传送zh’li指令。由于每执行一次 movsb 指令 si 和 di 都会递增或递减指向后一个单元或前一个单元,则 rep movsb 就可以循环实现 (cx) 个字符的传送
movsw 同理
下面两条指令对 df 位进行色设置
cld 指令 (CLear Direction flag):将标志位寄存器的 df 位置 0
std 指令 (SeT Direction flag):将标志位寄存器的 df 位置 1
编程 用串传送指令,将 data 段中的第一个字符串复制到它后面的空间中
1 | data segment |
分析:使用串传送指令进行数据的传送,需要给它提供一些必要的信息
- 传送的原始位置:ds:si
- 传送的目的位置:es:di
- 传送的长度:cx
- 传送的方向:df
在这个问题下,这些信息
- 传送的原始位置:data:0
- 传送的目的位置:data:0010
- 传送的长度:16
- 传送的方向:df=0
核心代码如下
1 | mov ax,data |
pushf 和 popf
pushf 的功能是将标志位寄存器的值压栈,而 popf 是从栈中弹出数据,送入标志寄存器中
pushf 和 popf,为直接访问标志寄存器提供了一种方法