汇编语言中的标志寄存器及其相关指令
深夜动调,有感而发。
本来是打算看完《汇编语言》再出一篇博客的,但是我急了,一边学汇编一边学动调,调到一半发现循环出不来,标志寄存器这么多又看不懂,度娘又讲不明白,所以直接从第四章跳到第十一章标志寄存器,粗浅学习一下罢。
看不懂汇编代码的动调挺没安全感的。
FLAGS
标志寄存器(PSW/FLAGS)和其他寄存器不一样,其他寄存器用来存放数据,都是整个寄存器具有一个含义。而标志寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
书中以8086CPU(16位)作为例子,它的结构如下图所示:
相关指令(大多是运算指令)执行后,执行结果会对标志寄存器的某些位造成影响。
ZF(零标志)
ZF(Zero flag),零标志。它记录相关指令执行后,其结果是否为零。如果结果为零,那么zf=1
;如果结果不为零,那么zf=0
。
e.g.
1 | mov ax,1 |
PF(奇偶校验标志)
PF(Parity flag),奇偶校验标志。它记录相关指令执行后,其结果的所有比特位中1的个数是否为偶数。如果1的个数为偶数,pf=1
;如果为奇数,pf=0
。
e.g.
1 | mov al,1 |
SF(符号标志)
SF(Sign flag),符号标志。它记录相关指令执行后,其结果是否为负。如果结果为负,sf=1
;如果非负,sf=0
。
e.g.
1 | mov al,10000001B |
上述例子中,如果10000001B是有符号数(通常用补码表示),则为-127,上述指令相当于计算-127+1=-126(10000010B)
。
然而,我们也可以把10000001B看做无符号数,即130。这种情况下,SF的值没有意义,虽然相关计算影响了它的值。
CF(进位标志)
CF(Carry flag),进位标志。一般情况下,在进行无符号数运算时,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。这里的进位为假想位,具体见下图:
可以理解为,两个数据相加时可能产生的从最高有效位向更高位的进位值并不舍弃,而是用CF来记录这个进位值。借位值亦然。
e.g.
1 | mov al,98H |
OF(溢出标志)
OF(Overflow flag),溢出标志。一般情况下,在进行有符号数运算时,它记录了运算结果是否发生溢出。如果发生溢出,of=1
;如果没有,of=0
。
溢出:运算结果超出预定范围导致结果不正确。
e.g.
1 | mov al,98 |
上述例子可以看出,由于一个数不可能同时是有符号数和无符号数,所以CF和OF虽然相似但他们之间没有任何关系。
DF(方向标志)
DF(Direction flag),方向标志。用于指示串操作指令地址的变化方向。如果df=1
,存储器由自高向低方向变化(si、di递减);如果df=0
,存储器由自低向高方向变化(si、di递增)。
AF(辅助进位标志)
AF(Auxiliary carry flag),辅助进位标志。书中并未提及这个FLAG。它的作用是记录在算术运算中第3位向第4位(从0计数)发生进位或借位的情况。一般用于BCD码。
TF和IF
书中并未提及这两个FLAG。这里把参考资料大致摘要如下:
TF(Trap flag),陷阱标志。当tf=1
时将开启单步调试模式,每个指令被执行后都将产生一个调试异常,以便于观察指令执行后的情况。
IF(Interrupt flag),中断标志。当if=1
时表示CPU可响应可屏蔽中断(maskable interrupt)。
32位汇编中的EFLAGS
与16位相比增加的基本上是系统控制的FLAG。感觉也不会经常用到。
-
IOPL:I/O特权级别标志(I/O privilege level flag)是标志寄存器的第12位以及第13位,表示当其程序或任务的I/O权限级别。I/O权限级别为0~3范围之间的值,通常一般用户程序I/O特权级别为0。当前运行程序的CPL(current privilege level)必须小于等于IOPL,否则将发生异常。
-
NT:嵌套任务(Nested task flag)是标志寄存器的第14位,用于控制中断返回指令IRET的执行方式。若被设置则将通过中断的方式执行返回,否则通过常规的堆栈的方式执行。在执行CALL指令、中断或异常处理时,处理器将会设置该标志。
-
RF:恢复标志(Resume flag)是标志寄存器的第16位,用于控制处理器对调试异常的响应。若其被设置则会暂时禁止断点指令产生的调试异常,其复位后断点指令将会产生异常。
-
VM:虚拟8086模式标志(Virtual 8086 mode flag)是标志寄存器的第17位,当其被设置表示启用虚拟8086模式(在保护模式下模拟实模式),否则退回到保护模式工作。
-
AC:对齐检查标志(Alignment check (or access control) flag)是标志寄存器的第18位。当该标志位被设置且CR0寄存器中的AM位被设置时,将对用户态下对内存引用进行对齐检查,在存在未对齐的操作数时产生异常。
-
VIF:虚拟中断标志(Virtual interrupt flag)是标志寄存器的第19位,为IF标志的虚拟映象。该标志与VIP标志一起,且在CR4寄存器中VME或PVI位被设置且IOPL小于3时,处理器才将识别该标志。
-
VIP:虚拟中断挂起标志(Virtual interrupt pending flag)是标志寄存器的第20位,其被设置表示有一个中断被挂起(等待处理),否则表示没有等待处理的中断。该标志通常与VIF标志搭配一起使用。
-
ID:ID标志(Identification flag)是标志寄存器的第21位,通过修改该位的值可以测试是否支持CPUID指令。
64位汇编使用64位的RFLAGS寄存器,其低32位即EFLAGS,高32位保留暂未使用。
和IDA里的差不多。
FLAGS相关指令
adc
adc(带进位加法指令)利用CF位上记录的进位值实现任意长度数据的加法运算。
指令格式: adc 操作对象1,操作对象2
功能:操作对象1 = 操作对象1 + 操作对象2 + CF
e.g. adc ax,bx 实现的功能是 ax=ax+bx+cf
众所周知,加法分两步进行:
- 低位相加
- 高位相加再加上低位相加产生的进位值
于是add ax,bx
便可以写成:
1 | add al,bl |
结论可以扩展至理论上的任意长度数据的加法运算。
sbb
sbb(带借位减法指令)利用CF位上记录的进位值实现任意长度数据的减法运算。
指令格式: sbb 操作对象1,操作对象2
功能:操作对象1 = 操作对象1 - 操作对象2 - CF
e.g. sbb ax,bx 实现的功能是 ax=ax-bx-cf
减法是加法的逆运算,sbb和adc基于相同原理设计,应用思路相似,这里不细🔒。
cmp
cmp(比较指令)相当于不保存结果的减法指令,只对FLAGS进行影响。
指令格式: cmp 操作对象1,操作对象2
功能:计算操作对象1-操作对象2但不保存结果,仅影响FLAGS。
e.g. cmp ax,ax ,ax-ax=0
但不在ax中进行保存,仅影响FLAGS各位:zf=1
pf=1
sf=0
cf=0
of=0
。
所以,我们可以根据cmp指令执行后的相关标志位的值来看出比较的结果。
以cmp ax,bx
为例,下面列举无符号数比较后的FLAGS状态:
如果ax==bx,则zf=1,cf=0
如果ax!=bx,则zf=0,cf=0或1
如果ax<bx,则zf=0,cf=1(减法产生借位)
如果ax>bx,则zf=0,cf=0
如果ax<=bx,则zf=0或1,cf=0或1,通过ax==bx和ax<bx的结论可看出,他们不可能同时为1或同时为0,所以zf^cf=1。
如果ax>=bx,则zf=0或1,cf=0。
那么:
zf=0,cf=0时,ax>bx
zf=0,cf=1时,ax<bx
zf=1,cf=0时,ax==bx
zf=1,cf=1时,好像不存在这种情况
以cmp al,bl
为例,有符号数比较后的FLAGS状态:
sf=0,of=0 表示无负数无溢出,则代表al>=bl
sf=1,of=0 表示有负数无溢出,则代表al<bl
sf=0,of=1 表示无负数有溢出,则代表al<bl
sf=1,of=1 表示有负数有溢出,则代表al>bl
等于的情况可以通过ZF看出:
zf=0代表al!=bl
zf=1代表al==bl
检测状态位的条件转移指令
指令 | 英文含义 | 中文含义 | 检测的相关标志位 |
---|---|---|---|
je/jz | jump if equal/zero | 等于/为零则转移 | zf=1 |
jne/jnz | jump if not equal/not zero | 不等于/不为零则转移 | zf=0 |
jb | jump if below | 低于则转移 | cf=1 |
jnb | jump if not below | 不低于则转移 | cf=0 |
ja | jump if above | 高于则转移 | cf=0且zf=0 |
jna | jump if not above | 不高于则转移 | cf=1或zf=1 |
由于CF和ZF不能同时为1,所以ja的条件可不可以理解为cf^zf=0
(只剩cf=zf=0的情况),jna的条件可不可以理解为cf^zf=1
?没有研究过具体判断的过程只作帮助理解之用。
一般情况下前面都会加个cmp的指令帮助调整相关标准位,联合使用时体现出高级语言的IF语句功能。
指令格式同jmp指令,具体如下:
指令实例 | 代表含义 |
---|---|
JMP 1000H | 段内直接转移,偏移地址由指令直接给出 |
JMP CX | 段内间接转移,偏移地址由CX指出 |
JMP 1000H:2000H | 段间直接转移,段地址和偏移地址由指令给出 |
JMP WORD PTR [SI] | 段内间接转移,偏移地址在SI所指地址开始的2个单元中 |
JMP DWORD PTR [SI] | 段间间接转移,段地址和偏移地址在SI所指地址开始的4个单元中 |
JMP SHORT S | 段内间接转移,执行指令后CS:IP指向标号S处的地址,跳转范围为[-128,127](用补码表示) |
JMP NEAR PTR S | 段内间接转移,执行指令后CS:IP指向标号S处的地址,跳转范围为[-32768,32767] |
JMP FAR PTR S | 段间间接转移,段地址和偏移地址由标号S处的地址指出 |
串传送指令
等我看完这本书再写。
pushf和popf
指令格式:pushf
功能:将标志寄存器的值压栈
指令格式:popf
功能:从栈中弹出数据,送入标志寄存器中
写在最后
今天是“三天看完《汇编语言》挑战”的第七天。
楼上的某个同志莫名其妙🐑了,封寝了,妈的。