汇编语言笔记11标志寄存器

汇编语言笔记11标志寄存器

0x00 标志寄存器简介

​ 标志寄存器是cpu内部的一种特殊的寄存器,有以下三种作用:

​ (1)用来储存相关指令的某些执行结果

​ (2)用来为cpu执行相关指令提供行为依据

​ (3)用来控制cpu的相关工作方式

​ 8086中的标志寄存器有16位,其中储存的信息被称为程序状态字(Program Status Word)PSW,又称为PSW寄存器 flag寄存器

​ flag和其他寄存器不同,其他寄存器用来储存数据,而flag是按位起作用,每一位都有专门的含义,记录特定的信息 QQ截图20200604161957

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 表示 如果是有符号数的运算,那么结果为负

QQ截图20200604164133

​ 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

QQ截图20200604170732

QQ截图20200604170759

​ 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位数据进行相加:

QQ截图20200605083919

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
			

QQ截图20200605085232

​ 输入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(等于)

QQ截图20200605093534

		cmp ah,bh
		je s
		add ah,bh
		jmp ok
s:		add,ah,ah
ok:		...

​ 我们看到 上述cmp指令和je指令配合 实现了高级语言中的if else功能

QQ截图20200605094415

QQ截图20200605094428

QQ截图20200605094443

QQ截图20200605094456

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

QQ截图20200605095406

QQ截图20200605100002

​ 上述两个函数都运行正确 第三个如法炮制即可

QQ截图20200605100056

QQ截图20200605100108

​ 根据题意只有[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

QQ截图20200605100117

​ 思路和刚刚一样 不过这里去掉了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

QQ截图20200605102448

​ 代码如下:

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

QQ截图20200605103155

​ 上述代码运行正确

QQ截图20200605103303

​ 一个段的最后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

QQ截图20200605104134

​ 上述代码运行正确 成功复制了 bios中的制造日期

0x05 pushf和popf

​ pushf的功能是将标志寄存器的值压入栈,而popf则是从栈中弹出数据,送入标志寄存器

​ pushf和popf为直接访问标志寄存器提供了一种方法

QQ截图20200605104620

QQ截图20200605104635

​ 思路解析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中的表示

QQ截图20200605105712