汇编语言笔记11标志寄存器
汇编语言笔记11标志寄存器
0x00 标志寄存器简介
标志寄存器是cpu内部的一种特殊的寄存器,有以下三种作用:
(1)用来储存相关指令的某些执行结果
(2)用来为cpu执行相关指令提供行为依据
(3)用来控制cpu的相关工作方式
8086中的标志寄存器有16位,其中储存的信息被称为程序状态字(Program Status Word)PSW,又称为PSW寄存器 flag寄存器
flag和其他寄存器不同,其他寄存器用来储存数据,而flag是按位起作用,每一位都有专门的含义,记录特定的信息
0x01 各标志含义
ZF标志
flag的第6位是ZF ,零标志位,它记录相关指令(一般是刚被执行的指令)执行后,其结果是否位0.如果结果为0,那ZF=1 如果不为0,ZF =0,例如
mov ax,1 sub ax,1 执行后结果为0,则 ZF=1
mov ax,2 sub ax,1 执行后结果不为0,则ZF=0
注意 在8086cpu的指令集中,有的指令的执行是影响标志寄存器的,比如add,sub,mul,div,inc,or,and等,大多是运算指令(逻辑或者算数运算);
而有的指令对标志寄存器没有影响,比如,mov,push,pop等 大多是传送指令
PF标志
flag的第2位是PF,奇偶标志位,记录相关指令执行后,结果的所有bit位中1的个数是否位偶数(偶数包含0),如果为偶数,PF=1否则为0,例如
mov al,1 add al,10 执行后结果为11也就是0000 1011b,有3个1,PF=0
mov al,1 or al,2 执行后为0000 0011b 有2个1,偶数PF=1
SF标志
flag的第7位是SF,符号标志位,记录相关指令执行后,其结果是否为负。如果结果为负,SF=1否则为0。值得注意的是 这里是把数据当作有符号数来看待,看其运算结果作为有符号数是否为负数,因为无符号数不存在负数
0000 0001b 可以看作无符号数1,或有符号数+1 1000 0001b 可以看作无符号数129 也可以看作有符号数 -127 (补码表示 127是 0111 1111b 取反 1000 0000 再加1 -》1000 0001b)
不管我们如何看待 cpu执行add等指令时,就已经包含了两种含义,关键在于我们的程序需要哪一种结果,当我们将数据当作无符号数看待时,那么SF的值将没有意义,虽然相关指令影响了他的值
mov al,10000001b add al,1 执行后al 1000 0010b SF=1 表示 如果是有符号数的运算,那么结果为负
sub al,al -> al=0 那么ZF=1 PF=1 SF=0 (注意0个1也是偶数个 PF=1)
mov al,1 -> al=1 ZF=1 PF=1 SF=0 (mov指令不影响标志寄存器的任何标志位)
push ax -> ax=1 ZF=1 PF=1 SF=0(push mov pop指令不影响标志寄存器的任何标志位)
pop bx -> bx=1 ZF=1 PF=1 SF=0(push mov pop指令不影响标志寄存器的任何标志位)
add al,bl -> al=2 ZF=0 PF=0 SF=0
add al,10 -> al=12 ZF=0 PF=1 (0000 1100b) SF=0
mul al -> al=无符号144 有符号 -112 ZF=0 PF=1 (1001 0000b) SF=1
CF标志
flag的第0位是CF,进位标志位,一般情况下,在进行无符号数运算时,它记录了运算结果的最高有效位向更高位的进位值,或者从更高位的借位值
为什么说是无符号数的时候? 不论我们将数据看作有符号数还是无符号数,cpu不管,cpu在设置CF标志时,将其看作无符号位一旦发生进位或者借位就置1
mov al,98h add al,al 执行后 (al)=30h,CF=1,CF记录了从最高有效位向更高位的进位值1
add al,al 执行后(al)=60h CF=0 没发生进位和借位
mov al,79h sub al,98h 执行后,al=0e1h 发生借位 CF =1
OF标志位
溢出问题(针对有符号数),8位寄存器或内存单元所能表示的有符号数范围时-128-127,16位是-32768-32767,如果运算结果超出了机器所能表达的范围,将产生溢出
mov al,98 add al,99 执行后al=197(无符号数)-59(有符号数) 超过了127变负数了 OF=1
mov al,0f0h add al,088h 执行后al=78h (120 无符号数 有符号数都是) -16+-120=-136 超过了-128因而变成正数了,所以OF=1
sub al,al al=0 CF=0(无进位) OF=0(无溢出) SF=0(无负数) ZF=1 PF=1(0个1)
mov al,10h al=10h CF=0 OF=0 SF=0 ZF=0 PF=1 mov指令不影响标志寄存器
add al,90h al=0a0h CF=0 OF=1(溢出了 有符号数下 变负数-96) SF=1 (负数了) ZF=0 PF=0(三个1)
mov al,80h al=80h CF=0 OF=1 SF=1 OF=O PF=0
add al,80h al=0h CF=1 OF=1(-128+-128溢出了) SF=0(不是负数) ZF=1 PF=1
mov al,0fch al=fch CF=1 OF=1 SF=0 ZF=1 PF=1
add al,05h al=01h CF=1 OF=0 SF=0 ZF=0 PF=0
mov al,7dh al=7dh CF=1 OF=0 SF=0 ZF=0 PF=0
add al,0bh al=88h CF=0 OF=1 SF=1 ZF=0 PF=1
0x02 adc与sbb指令
adc指令
adc是带进位加法指令,它利用CF位上记录的进位值
指令格式:adc 操作对象1,操作对象2 例如adc ax,bx –>ax=ax+bx+cf
例如 mov ax,0ffh sub ax,1 mov bx,1 adc bx,1 ->bx=3
原因是sub ax,1时 cf=1 mov bx,1不改变cf 然后 adc bx,1实际上时 bx=bx+1+cf=1+1+1=3
adc指令是用来自己实现加法运算的进位的,例如:0198h和0183h运算
mov al,98h
mov bl,83h;初始认为cf=0也可以在前面手动置0
adc al,bl;因为cf=0 所以等同于 add al,bl cf=1
mov ah,1
mov bh,1
adc ah,bh;因为cf=1 所以 ah=ah+bh+1=3 这个时候 ax的值就是0198h+0183h的结果了
上面的示例能说明adc的实现进位加法的原理 但是不具有代表性,因为16位的加法 8086原生支持,那我们用32位+32位来说明吧 计算 1ef000h + 201000h 结果放在ax(高16位)和bx(低16)中
;默认初始cf=0,也可以随便加些无关运算 让cf=0
mov ax,001eh
mov bx,0f000h
adc bx,1000h;发生进位 cf=1
adc ax,0020h;ax=ax+0020h+1=003fh 所以最终结果正确
为了进一步说明其用处,我们来编写一个子程序,对两个128位数据进行相加:
assume cs:code,ds:data
data segment
db 16 dup (0)
db 16 dup (0)
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov di,16
call add_128
mov ax,4c00h
int 21h
add_128: push ax
push cx
mov cx,8
sub ax,ax;置零cf
s: mov ax,ds:[si]
adc ax,ds:[di]
mov ds:[si],ax
inc si
inc si
inc di
inc di
loop s
pop cx
pop ax
ret
code ends
end start
输入1e2586+74dc58结果运行正确9301de 值得注意的是 inc不会改变CF即使发生了al=ffh,inc al 也不会置cf=1 而add会 所以 为了让si 和di 增加2 用了两个inc 而不是 add si,2防止运算中CF错误改变
sbb指令
sbb指令是带借位减法,它利用了cf位上记录的借位值,用法功能和adc类似,就不详细展开了(书中也是简略带过),举个例子,计算003e 1000h - 0020 2000h,结果放在ax,bx中
mov bx,1000h
mov ax,003eh
sub bx,2000h;1000h-2000h发生借位置 CF1
sbb ax,0020h;003EH-0020h-1=001dh
0x03 检测比较结果的条件转移指令
cmp指令
cmp指令进行减法运算,并改变标志寄存器,但是不保存结果,格式和sub 一样,例如:
cmp ax,ax ax-ax=0 这个0不会被赋值给ax,但是0这个结果还是改变了标志寄存器,所以ZF=1 PF=1 SF=0 CF =0 OF=0
mov ax,8 mov bx,3 cmp ax,bx 执行后结果位5 所以PF=1 ZF=0 SF=0 CF=0 SF=0
下面针对cmp ax,bx 中 ax,bx的相对大小与标志寄存器的改变关系做一个总结:
如果ax=bx,则 ax-bx=0 zf=1 PF=1 SF=0 CF=0 OF =0
如果ax!=bx,则 ZF=0,其余不能判断
如果ax<bx,则必将借位 那么CF=1 SF及其余不能判断 原因在于如果ax=1 bx=ffff时,结果从有符号数看来仍然是正数
如果ax>=bx,则ax-bx比不产生借位,所以cf=0
如果ax>bx 则既不产生借位 又不可能为0 所以 cf=0 zf=0
如果ax<=bx 则既可能产生借位 有可能为0 所以 cf=1 或zf=1
上面的讨论只针对无符号数及其影响的标识符 对于看作有符号数及其标识符 OF SF 而言 情况就复杂的多
条件转移指令
前面学过jcxz指令 检测cx中的值 如果cx=0就修改ip进行跳转,否则什么也不做
这里另外学习6条根据无符号数的比较结果的跳转指令 也是条件转移指令如下:
je 等于则跳转(jump when equal) zf=1时跳转
jne 不等于则跳转(jump when not equal) zf=0时跳转
jb 低于则跳转(jump when below) cf=1(借位)时跳转
jnb 不低于则跳转(jump when not below) cf=0(不发生借位)时跳转
ja 高于则跳转(jump when above) CF=0且ZF=0
jna 不高于则跳转(jump when not above) cf=1(发生借位)或zf=1(等于)
cmp ah,bh
je s
add ah,bh
jmp ok
s: add,ah,ah
ok: ...
我们看到 上述cmp指令和je指令配合 实现了高级语言中的if else功能
assume cs:code,ds:data
data segment
db 8,11,8,1,8,5,63,38
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
call func1
mov ax,4c00h
int 21h
func1: push bx
push cx
mov ax,0
mov cx,8
f1s: mov bl,ds:[si]
cmp bl,8
jne f1s1
inc ax
f1s1: inc si
loop f1s
pop cx
pop bx
ret
func2: push bx
push cx
mov ax,0
mov cx,8
f2s: mov bl,ds:[si]
cmp bl,8
jna f2s1
inc ax
f2s1: inc si
loop f2s
pop cx
pop bx
ret
code ends
end start
上述两个函数都运行正确 第三个如法炮制即可
根据题意只有[32,128] 才可以让计数的dx+1 而位移的标号s0是bx+1 前面有一个dx+1 所以如果想要执行到dx+1 就不能跳转,那么对于 cmp al,32 对于小于32的部分要让他跳转 所以这里是jb s0,让后执行到cmp al,128那么大于128的部分要让它跳转,所以这里是ja s0
答案是 jb s0 ja s0
思路和刚刚一样 不过这里去掉了32 和 128 所以 要执行跳转的部分分别是 jna s0 和jnb s0 也就是不大于32的部分和不小于128的部分
答案 jna s0 jnb s0
0x04 DF标志与串传送指令
串传送指令
movsb movsw两种 作用分别是从ds:[si]复制1个字节或者1个字到es:[di],并且复制完后,会同时递增或者递减 si和 di的值,
DF标志位
具体是递增还是递减,取决于df标志位 如果df=0 则si,di递增 反之df=1 则递减
df位受两条指令的控制 cld指令(clear df 将df标志位置0) std指令(set df df位置1)
rep movsb和rep movsw
该指令是重复指令,根据cx的值决定重复次数
相当于
s:movsb
loop s
代码如下:
assume cs:code,ds:data
data segment
db 'welcome to masm!'
db 16 dup (0)
data ends
code segment
start: mov ax,data
mov ds,ax
inc ax
mov es,ax
mov si,0
mov di,0
cld
mov cx,16
rep movsb
mov ax,4c00h
int 21h
code ends
end start
上述代码运行正确
一个段的最后16个字节在哪儿呢? 段起始地址F000:0 结束地址F000:FFFF 因为是倒序 所以必定要使用 std指令 让df置1 让串传送 递减 所以 ds:si 中 ds 指向 F000 si指向 FFFF es:di 中 es指向data,di指向0fh
assume cs:code,ds:data
data segment
db 16 dup (0)
data ends
code segment
start: mov ax,data
mov es,ax
mov di,0fh
mov ax,0f000h
mov ds,ax
mov si,0ffffh
std
mov cx,16
rep movsb
mov ax,4c00h
int 21h
code ends
end start
上述代码运行正确 成功复制了 bios中的制造日期
0x05 pushf和popf
pushf的功能是将标志寄存器的值压入栈,而popf则是从栈中弹出数据,送入标志寄存器
pushf和popf为直接访问标志寄存器提供了一种方法
思路解析push ax 压入ax入栈 为0 popf让标志寄存器为0 add ax,0010h 后 ZF=1 PF=1 SF=0 CF=1 OF=0 (-16+16) DF=0 pushf 将标志寄存器压入栈 (0000 0000 0100 0101b) pop ax ax即为标志寄存器的值 然后 and al al=0100 0101b ah =0000 0000b 所以 ax=45h 十进制105
0x06 标志寄存器在debug中的表示