[持续更新]汇编学习笔记-2

由于第一个笔记写到后面编辑界面和查看界面都出奇的卡,翻页也难受,所以新开了一个

7 标志寄存器

标志寄存器用来存储计算的某些结果,为 cpu 的执行提供依据或控制其行为
与其它寄存器不同,它是每个二进制位代表一个意义(其它都是整个寄存器代表一个意义)
每一位的意义如下,空白说明在 8086cpu 中这一位无意义

[持续更新]汇编学习笔记-2

7.1 ZF

第 6 位,零标志位
如果上一条指令执行的结果为 \(0\),则 \(ZF=1\),否则 \(ZF=0\)

关于“上一步的结果”:8086cpu 中有一些指令会产生结果,比如 add,sub,mul,div,inc,dec,or,and(其中 inc 和 dec 只有可能影响 AF,OF,PF,SF,ZF 标志位,但不影响 CF 标志位);还有一些比如 mov,push,pop 不影响标志位
这一点在后面也会用上

7.2 PF

第 2 位,奇偶标志位
如果上一条指令执行的结果的二进制中有偶数个 \(1\),则 \(PF=1\),否则 \(PF=0\)

7.3 SF

第 7 位,符号标志位
如果上一条指令执行结果为负,\(SF=1\),如果非负,\(SF=0\)

和符号有关,就是要用到补码了,先去学补码再看下面内容会更名白一些
一个数据以二进制保存在计算机中,它既可以代表直接转换成十进制的数(无符号),也可以用补码来转换(有符号)
也就是说,cpu 执行一条指令的时候,已经有了两种含义(当成有符号执行和当成无符号执行),结果也有两种含义(有符号和无符号),虽然它们在计算机中的表达是一样的,把它当成有符号还是无符号是我们的“看待”

所以说,cpu 在执行一条有结果的指令时,必然影响到 SF 的值(当然是当作有符号运算来进行影响),而我们需不需要这个影响就另说了:比如我们对这个运算的“看待”就是无符号运算,那么 SF 受到的影响就是无用的,但 cpu 对 SF 的影响还是会有,只是我们此时不需要罢了

7.4 CF

第 0 位,进位标志位
两个 N 位数字运算时,有可能发生溢出,CF 记录的就是溢出的这一位(第 N 位)

[持续更新]汇编学习笔记-2

当减法出现借位时,CF 也会记录借位值。比如一个八位减法 \(97H-98H\),发生借位,变成 \(197H-98H\),然后 \(CF=1\)

其实可以发现,一般来说这个 CF 也是对于无符号数的,但是如果我们把一个运算看作有符号的运算,cpu 执行指令对 CF 的影响仍然是存在的

7.5 OF

第 11 位,溢出标志位
溢出一般是对于有符号数来说的,就是如果运算过程中结果超过了机器所能表示的范围称为溢出
比如对于两个 8 位数的运算,\(98+99=197\),这个 \(197\) 就超过了 8 位数的表示范围 \([-128,127]\),发生了溢出
这样结果变成十六进制就是 \(0C5H\),又因为是有符号运算,所以它应该被按照补码的规则看作 \(-59\),发生了错误
这时就要用到 OF 了,如果上一个指令的结果发生了溢出,\(OF=1\),否则为零

注意:OF 是对有符号数有意义的标志位,而 CF 是对无符号运算有意义的
但即使一个标志位对当前的运算无意义,它也会被影响(cpu 不知道当前是有符号还是无符号)

7.6 在 debug 中查看标志寄存器

r 命令查看寄存器值时右下角会有一些字符:

[持续更新]汇编学习笔记-2

7.7 adc 与 sbb

adc X,Y 就是 \(X=X+Y+CF\)
比如 adc ax,bx,意义是 \((ax)=(ax)+(bx)+CF\)

那么这样一种指令的意义何在?比如当我们执行 add al,bl 后,\((al)=(al)+(bl)\),但这样以后 al 可能发生进位,那么会对应的记录到 CF 中,此时再调用 adc ah,bh,就会在把 bh 的值加到 ah 上的同时,把 CF 也加到 ah 上
那么如果之前 al 进位,也就是 \(CF=1\),多了一个 \(100H\),加到 ah 上就是加一,也就是加 CF 的值(当然没进位 \(CF=0\) 也不会有问题)
所以 adc 的意义其实是使得更大数据的加法可以被支持,通过把 CF 的值加到高位上来解决低位出现进位的问题

比如下面这个程序,我们计算了 \(1EF000H+201000H\),并将结果存进了 ax:bx

;calc 1EF000H+201000H,result in ax:bx
assume cs:code
code segment
start:
	mov ax,001EH
	mov bx,0F00H
	add bx,1000H
	adc ax,0020H

    mov ax,4c00h
    int 21h
code ends
end start

同样,也可以实现下面这样的一个函数,来利用 adc 进行两个 128 位数据的相加

;两个 128 位数字相加,ds:si 指向第一个数,8 个字
;ds:di 指向第二个数,结果存在第一个数的位置
add128:
	push ax
	push cx
	push si
	push di
	
	sub ax,ax;将 CF 设零
	mov cx,8
	S:
		mov ax,[si]
		adc ax,[di]
		inc si
		inc si
		inc di
		inc di;用 inc 而不是 add 来防止改变 CF
	loop S
	
	pop di
	pop si
	pop cx
	pop ax
ret

再来说 abbabb X,Y 就是 \(X=X-Y-CF\)
比如 sbb ax,bx,意义是 \((ax)=(ax)-(bx)-CF\)
现在类比上一个看这个指令,意义也很明确了,实现带借位的减法

这两个指令也体现出了 CF 存在的意义

7.8 cmp

比较指令,对标志寄存器的影响相当于减法指令,但是它不会改变参与减法运算的两个寄存器或内存单元的值(就是说只改变标志寄存器,不保存结果)
比如执行指令 cmp ax,ax,执行后标志寄存器:\(ZF=1,PF=1,SF=0,CF=0,OF=0\),但 ax 以前是多少还是多少
如果执行 cmp ax,bx,则:

[持续更新]汇编学习笔记-2

如果 cmp 是对无符号数进行比较,那么上面的几条也可以倒推

但如果是有符号数,就稍微复杂一些了
首先前两条相等和不相等,当然还是一样
如果 \((ax)<(bx)\),则会引起 \(SF=1\),但是 \(SF=1\) 却不一定可以说明 \((ax)<(bx)\)
比如有符号 8 位减法:\(22H-0A0H=34-(-96)=82H=-126\text{(补码转换为原码)},CF=1\),但是 \(34>-96\)
什么情况下会出现这种问题?\(SF=1\) 并不完全等价于结果为负数(结果为负数我们一定能说明那个小于关系),因为就像上面那个例子,运算中发生了溢出,因此出现了这种情况,所以再经过一些简单分析,就可以得到:

  • \(SF=1,OF=0\),没有溢出,此时 \(SF=1\) 就等价于结果为负,所以 \((ax)<(bx)\)
  • \(SF=1,OF=1\),发生溢出,溢出导致了 \(SF=1\),也就是 cpu“以为”结果为负,那么实际上应该是结果为正,那么 \((ax)>(bx)\)
  • \(SF=0,OF=0\),没溢出,\((ax)\ge (bx)\),注意由于 \(SF=0\) 这里是大于等于
  • \(SF=0,OF=1\),溢出了,\((ax)<(bx)\),这后面两个都是同理

这里感觉比较容易迷惑,主要就是关注有符号数溢出,在原码上的表示超出范围,对应到补码上就是改变了符号

7.9 基于标志寄存器的跳转指令

其实就是通过上面讲述的 cmp 结果,对于无符号数,有这几种:

[持续更新]汇编学习笔记-2

其中各个字母缩写的含义:not,equal,below,above,可能会帮助记忆
其实这个图稍微有一些歧义,要知道中间那一竖栏只是一个辅助的描述,比如如果你只执行一个 je 并不会直接起到中间竖栏的作用,而只是通过 ZF 的值来进行转移,只有当在 je 之前执行一个 cmp,它才会起到“等于则跳转”的效果
也就是,这些指令都可以单独使用,根据标志寄存器跳转,但一般都是通过和 cmp 搭配使用来起到根据两数大小来跳转的作用
就好像 callret 一般搭配使用,但也可以单独拿出一个来用

然后,对于有符号数,原理上是一样的,只是检测的标志位不同,整理出了下面这一个和无符号数跳转指令的对应关系(以下同一个指令两种助记符用斜杠隔开,其实可以发现它们是有规律的)

无符号 有符号 何时跳转
je / jz je / jz 等于
jne / jnz jne / jnz 不等于
jb / jnae jl / jnge 低于
jnb / jae jnl / jge 不低于(大于等于)
ja / jnbe jg / jnle 高于
jna / jbe jng / jle 不高于(小于等于)

例子

可以用如下程序检测 ds:si 开始的一些数据中有几个 8

assume cs:code,ds:datasg;看 datasg 里有多少数等于 8,结果存 ax
datasg segment
	db 8,11,8,1,8,5,63,38
datasg ends
code segment
start:
	mov ax,datasg
	mov ds,ax
	xor bx,bx
	xor ax,ax
	
	mov cx,8
S:
	cmp byte ptr [bx],8
	jne next
	inc ax
	next:
	inc bx
loop S

    mov ax,4c00h
    int 21h
code ends
end start

同理,也可以统计有多少个大于,小于,不等于 8

7.10 DF 和串传送指令

第 10 位,方向表示位,串处理指令中,控制每次 di 和 si 的加减,\(DF=0\),就加,否则就减

串传送指令:movsbmovsw
相当于每次把 es:si 处的数据送入 ds:di 中,每次的长度分别是 byte 和 word
然后每次传送完以后变更 si 和 di,就依靠 DF 的值

它可以和 rep 配合使用,rep movsb 就相当于:

S: movsb
loop S

所以要提前设置 cx
cldstd 分别将 DF 置为 \(0\) 和 \(1\)

下面就是一个例子

assume cs:code,ds:datasg;把 datasg 第一个 16 字节传送到第二个 16 字节
datasg segment
	db 'Welcome to masm!'
	db 16 DUP(0)
datasg ends
code segment
start:
	mov ax,datasg
	mov ds,ax
	mov es,ax
	xor si,si
	mov di,16
	mov cx,8
	
	cld
	rep movsw
	
    mov ax,4c00h
    int 21h
code ends
end start

7.11 pushf 和 popf

分别是把标志寄存器的值入栈、出栈。这也是一种可以直接访问标志寄存器的方法

7.12 实例

写一个大小写转换的子程序,小写转大写,但是转换的字符串不一定都是字母,要提前判断

assume cs:code,ds:datasg
datasg segment
	db "Beginner's All-purpose Symbolic Instruction Code.",0
datasg ends
code segment
start:
	mov ax,datasg
	mov ds,ax
	xor si,si
	call letterc
	
    mov ax,4c00h
    int 21h
	
letterc:;ds:si 指向的以 0 结尾的字符串中小写字母转成大写
	push ax
	push cx
	push si
	do:
		mov al,[si]
		cmp al,'a'
		jb no
		cmp al,'z'
		ja no
		and al,11011111B
		mov [si],al
		no:
		inc si
		mov cx,[si]
		inc cx
	loop do
	pop si
	pop cx
	pop ax
ret

code ends
end start
上一篇:解决java.lang.NoClassDefFoundError错误


下一篇:解决java.lang.SecurityException: Invalid signature file digest for Manifest main attributes