汇编语言是一种面向机器的低级语言,用于编写计算机程序。汇编语言与计算机机器语言非常接近,汇编语言程序可以使用符号、助记符等来代替机器语言的二进制码,但最终会被汇编器编译成计算机可执行的机器码。
标志位测试指令是汇编语言中用于测试处理器标志位状态的指令。标志位是位于处理器状态寄存器中的一组特殊标志,用于指示上一个运算的结果是否为零、是否进位/借位、是否溢出等等。可以使用标志位测试指令来检查标志位的状态,并在需要时根据标志位状态进行操作。
常见的标志位测试指令包括:
test 指令:测试指定寄存器中的值与另一个值(常数或寄存器)的按位与操作结果,而不改变寄存器的值。如果结果为零,将设置零标志位ZF。
cmp 指令:比较两个操作数并确定它们是否相等;如果两个操作数相等,则设置ZF标志位。使用此指令时,通常将第一个操作数减去第二个操作数,并且不需要保存差值。
and 指令:对两个操作数进行逐位与操作,并将结果写入目标操作数。如果结果为零,将设置ZF标志位。
or 指令:对两个操作数进行逐位或操作,并将结果写入目标操作数。如果结果为零,将清除ZF标志位。
xor 指令:对两个操作数进行逐位异或操作,并将结果写入目标操作数。如果结果为零,将设置ZF标志位。
2.1 PSR 标志寄存器又称程序状态寄存器(Program Status Register,PSR),是CPU中存放处理器标志位的寄存器。它记录了上一个操作的结果,这些结果可以用于下一条指令的条件转移或其他操作。标志寄存器通常包含一些二进制位(标志位),每个标志位用于表示不同的条件或状态。不同的架构和体系结构会有不同的标志位设置。
常见的标志位包括:
零标志位(Zero Flag,ZF):当上一个操作的结果为零时,将设置该标志位。
进位标志位(Carry Flag,CF):当上一个操作的结果产生了进位或借位时,将设置该标志位。
溢出标志位(Overflow Flag,OF):当上一个操作的结果产生了溢出时,将设置该标志位。
符号标志位(Sign Flag,SF):当上一个操作的结果为负数时(最高位为1),将设置该标志位。
奇偶校验标志位(Parity Flag,PF):当上一个操作的结果具有偶数个二进制位为1时,将设置该标志位。
这些标志位通常用于指令的条件分支操作,例如 jz(零标志位为真时跳转)、jnz(零标志位为假时跳转)等。直接操作这些标志位可能会对系统的运行产生影响,因此在编程时应该使用相应的指令来读写标志寄存器状态。
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .code main PROC ; CF 进位标志位: 当执行一个加法(或减法)运算,使最高位产生进位(或借位)时,CF为1;否则为0 mov ax,0ffffh add ax,1 ; cf = 1 af = 1 ; PF 奇偶标志位: 当运算结果中,所有bit位(例:1001010)中1的个数为偶数时,则PF=1;为基数PF=0 mov eax,00000000b add eax,00000111b ; pf = 0 mov eax,00000000b add eax,00000011b ; pf = 1 ; ZF 零标志位: 若当前的运算结果为零,则ZF=1; 否则ZF=0 mov eax,2 sub eax,2 ; zf = 1 cf = 0 af = 0 ; SF 符号标志位: 若运算结果为负数,则SF=1;若为非负数则SF=0 mov eax,3e8h sub eax,3e9h ; sf = 1 cf = 1 af = 1 zf = 0 ; DF 方向标志位: 当DF=0时为正向传送数据(cld),否则为逆向传送数据(std) cld mov eax,1 ; df = 0 std mov eax,1 ; df = 1 ; OF 溢出标志位: 记录是否产生了溢出,当补码运算有溢出时OF=1;否则OF=0 mov al,64h add al,64h ; of = 1 cf = 0 pf = 0 af = 0 invoke ExitProcess,0 main ENDP END main
2.2 TEST TEST 指令是一种逻辑操作指令,用于执行两个操作数的逐位AND
运算,不改变目标操作数的值,只设置相应的标志位,常用于测试某些位是否被设置。
该指令的语法为:
其中,dest
是目标操作数,src
是源操作数,两个操作数可以是寄存器或内存地址。
执行TEST
指令时,CPU将目标操作数和源操作数直接逐位AND
运算,结果并不保存到任何位置。但同时,CPU会设置目标操作数的条件码标志位,以反映运算的结果。具体地,CPU会根据运算结果将零标志位(ZF)和进位标志位(CF)设置或清空,符号标志位(SF)和溢出标志位(OF)未定义。
TEST 指令通常用于测试某些位是否被设置,可以通过与一个掩码进行TEST
和来测试某一位(或一组位)是否被置位。例如,要测试寄存器eax
是否为偶数,可以使用以下代码:
test eax, 1 jz even_number
在这个代码中,使用TEST
指令将eax
和常数1逐位AND
运算,并将结果保存到条件码标志位中。如果eax
的最低位为0,则ZF
处于设置状态,执行jz
指令跳转到even_number
标号处,否则继续执行后续指令。
TEST指令可以同时检测设置多个标志位的值,该指令执行时总是清除溢出标志和进位标志,它修改符号标志,基偶标志,零标志的方式与AND指令相同。
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .code main PROC mov al,00001111b test al,2 ; zf=0 pf=0 mov al,00100101b test al,00001001b ; zf=0 pf=0 mov al,00100100b test al,00001001b ; zf=1 pf=1 mov eax,0100h test eax,eax ; zf=0 mov eax,0 test eax,eax ; zf=0 or al,80h ; 设置符号标志 zf=0 pf=0 sf=1 and al,7fh ; 清除符号标志 zf=1 pf=1 sf=0 mov al,0 or al,1 ; 清除符号标志 zf=0 pf=0 stc ; 设置进位标志 cf = 1 clc ; 清除进位标志 cf = 0 mov al,07fh ; AL = +127 inc al ; 设置溢出标志 AL = 80h (-128) of=1 af=1 sf=1 or eax,0 ; 清除溢出标志 invoke ExitProcess,0 main ENDP END main
2.3 CMP CMP 指令是一种比较指令,通常用于比较两个操作数的大小关系,并根据比较结果设置相应的条件码标志位。
该指令的语法与SUB
指令相同,但是CMP
指令不会改变目标操作数的值,只对源操作数和目标操作数进行逐位减法运算,并根据运算结果设置标志位。具体地,CMP
指令执行DEST - SRC
的减法运算,但不保存结果,只把运算结果的条件码标志位设置为反映运算结果的值。
根据CMP
指令所设置的标志位,可以通过条件跳转指令来实现跳转。例如,要判断eax
是否为0并跳转到标号END
,可以使用以下代码:
在这个代码中,CMP
指令将eax
和0
相减,不保存结果,而是设置相应的条件码标志位。如果eax
等于0,则ZF
处于设置状态,条件跳转指令je
跳转到END
标号处。如果eax
不等于0,则ZF
处于未设置状态,不会执行跳转指令,而是继续执行后续指令。
如下代码片段则是CMD指令的更多使用方法,读者可自行编写代码进行测试,根据注释信息相信很容易理解。
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .code main PROC ; 比较5和10 mov ax,5 cmp ax,10 ; 5-10 > zf=0 cf=1 pf=0 af=1 sf=1 ; 比较两个相同数 mov ax,1000 mov cx,1000 cmp cx,ax ; 1000-1000 > zf=1 cf=0 pf=1 sf=0 ; 比较100和0 mov ax,100 cmp ax,0 ; 100-0 > zf=0 cf=0 pf=0 ; 比较100和50 mov eax,100 mov ebx,50 cmp eax,ebx ; 100-50 > zf=0 cf=0 pf=0 ; 比较-100和50 mov eax,-100 mov ebx,50 cmp eax,ebx ; -100-50 > sf=1 pf=1 ; 比较-100和-50 mov eax,-100 mov ebx,-50 cmp eax,ebx ; -100--50 > cf=1 af=1 pf=0 invoke ExitProcess,0 main ENDP END main
2.4 JX/JNX/JSX/JPX 汇编语言中的跳转指令可以根据条件码标志位来判断条件是否成立,并根据判断结果来跳转到指定的地址。以下是基于特定 CPU 标志寄存器来实现跳转的常见指令及其含义:
JZ / JE:当零标志位 (ZF) 为 1 时跳转,即前一个操作执行结果为零。
JNZ / JNE:当零标志位 (ZF) 为 0 时跳转,即前一个操作执行结果不为零。
JC / JB / JNAE:当进位标志位 (CF) 为 1 时跳转,即前一个操作执行结果产生了进位或借位。
JNC / JNB / JAE:当进位标志位 (CF) 为 0 时跳转,即前一个操作执行结果没有产生进位或借位。
JO:当溢出标志位 (OF) 为 1 时跳转,即前一个操作执行结果产生了溢出。
JNO:当溢出标志位 (OF) 为 0 时跳转,即前一个操作执行结果没有产生溢出。
JS:当符号标志位 (SF) 为 1 时跳转,即前一个操作执行结果为负数。
JNS:当符号标志位 (SF) 为 0 时跳转,即前一个操作执行结果为非负数。
JP / JPE:当奇偶校验标志位 (PF) 为 1 时跳转,即前一个操作执行结果具有偶数个二进制位为 1。
JNP / JPO:当奇偶校验标志位 (PF) 为 0 时跳转,即前一个操作执行结果不具有偶数个二进制位为 1。
以上这些跳转指令中,条件判断所依赖的条件码标志位是由前一条指令执行结果所决定的,因此在使用跳转指令时需要注意前一条指令的结果是否符合预期。同时,在跨过一些较大距离的内存位置时,还需要确保指令地址是否能够被正确地计算和访问。
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .code main PROC ; JZ/JE 当ZF置1时 也就是结果为零则跳转 (leftOp - rightOp = 0) mov eax,1 sub eax,1 ; zf=1 pf=1 je jump mov eax,1 mov ebx,1 cmp eax,ebx ; zf=1 jz jump ; JNZ/JNE 当ZF置0时 也就是结果不为零则跳转 (leftOp - rightOp != 0) mov eax,2 sub eax,1 jnz jump ; zf=0 pf=0 mov eax,2 cmp eax,1 jne jump ; zf=0 ; JC/JNC 当 CF=1/0 设置进位标志则跳/未设置进位标志则跳 mov al,0 cmp al,1 jc jump jnc jump ; JO/JNO 当 OF=1/0 设置溢出标志则跳/未设置溢出标志则跳 mov al,0ffh add al,1 jo jump ; JS/JNS 当 SF=1/0 设置符号标志则跳/未设置符号标志则跳 mov eax,1 cmp eax,1 js jump ; cf=0 af=0 ; JP/JNP PF=1/0 设置奇偶标志则跳(偶)/未设置奇偶标志则跳(基) mov al,00100100b cmp al,0 jp jump ; zp=0 jump: xor eax,eax xor ebx,ebx invoke ExitProcess,0 main ENDP END main
跳转指令与比较指令可以很好的结合起来,通过使用cmp eax,ebx
比较等式两边的值,影响相应的标志寄存器中的值,从而决定是否要跳转,常用的如下:
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .code main PROC ; JA(无符号)/JG(有符号) 跳转标志: (left > right) 大于则跳转 mov eax,100 mov ebx,200 cmp ebx,eax ; 无符号 ebx > eax ja jump ; zf=0 pf=0 mov eax,20 mov ebx,-100 cmp eax,ebx ; 有符号 eax > ebx jg jump ; zf=0 cf=1 pf=1 af=1 ; JAE(无符号)/JGE(有符号) 跳转标志: (left >= right) 大于或等于则跳转 mov eax,50 mov ebx,20 cmp eax,ebx ; 无符号 eax >= ebx jae jump ; jae 被替换成了jnb 小于则跳 (eax<ebx) mov eax,50 mov ebx,-20 cmp eax,ebx ; 有符号 eax >= ebx jge jump ; zf=0 af=1 pf=0 sf ; JB(无符号)/JL(有符号) 跳转标志:(left < right) 小于则跳转 mov eax,10 mov ebx,20 cmp eax,ebx ; 无符号 eax < ebx jb jump ; cf=0 af=0 pf=1 mov eax,-10 mov ebx,20 cmp eax,ebx ; 有符号 eax < ebx jl jump ; JBE(无符号)/JLE(有符号) 跳转标志:(left <= right) 小于或等于则跳转 mov eax,20 mov ebx,20 cmp eax,ebx ; 无符号 eax <= ebx jbe jump ; zf=1 mov eax,-20 mov ebx,10 cmp eax,ebx ; 无符号 eax,ebx jle jump ; sf=1 ; JNB(不小于则跳 同JAE)/JNA(不大于则跳 同JBE) 跳转标志:(lef !>< right) 无符号 mov eax,10 mov ebx,5 cmp eax,ebx ; eax !< ebx jnb jump ; zf=0 cf=0 mov eax,5 mov ebx,10 cmp eax,ebx ; eax !> ebx jbe jump ; cf=1 af=1 zf=0 ; JNL(不小于则跳 同JGE)/JNG(不大于则跳 同JLE) 跳转标志:(lef !>< right) 有符号 mov eax,10 mov ebx,-5 cmp eax,ebx ; eax !< ebx jnl jump ; sf=0 cf=1 pf=1 af=1 zf=0 mov eax,-10 mov ebx,5 cmp eax,ebx ; eax !> ebx jng jump ; sf=1 jump: xor eax,eax xor ebx,ebx invoke ExitProcess,0 main ENDP END main
2.5 BT/BTR/BSF/BSR BT 系列指令主要用于对特定寄存器中的位进行测试、清除、设置或求反等操作,这些操作通常会影响条件码寄存器CF
的值。这些指令的具体操作如下:
BT 指令:测试特定寄存器中的位是否为 1,将测试结果存储在条件码寄存器CF
的最低位中,即CF
的值等于被测试位的值。
BTC 指令:将特定寄存器中的位取反,被取反的位由CF
最低位指示,即如果CF
为1
,则对应位取反;否则不变。
BTR 指令:将特定寄存器中的位清零,被清零的位由CF
最低位指示,即如果CF
为1
,则对应位清零;否则不变。
BTS 指令:将特定寄存器中的位设置为 1,被设置的位由CF
最低位指示,即如果CF
为 1,则对应位设置为 1;否则不变。
相比之下,BSF
和BSR
指令则是对特定数据中的位进行正反向扫描操作,进而得到位中第一个1
和最后一个1
的位置,这些操作通常会影响条件码寄存器ZF
的值。这两个指令的具体操作如下:
BSF 指令:从寄存器或内存中获取一个WORD
或DWORD
数据,从低位到高位扫描,找到第一个值为1
的位,将该位的偏移量存储在目标寄存器中,并将条件码寄存器ZF
设置为相应的值,如果未找到值为1
的位,则目标寄存器的值未定义,且ZF
被清零。
BSR 指令:从寄存器或内存中获取一个WORD
或DWORD
数据,从高位到低位扫描,找到最后一个值为1
的位,将该位的偏移量存储在目标寄存器中,并将条件码寄存器ZF
设置为相应的值,如果未找到值为1
的位,则目标寄存器的值未定义,且ZF
被清零。
如下代码片段则是指令的更多使用方法,读者可自行编写代码进行测试,根据注释信息相信很容易理解。
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .code main PROC ; bt 普通的位测试命令 xor edx,edx mov dx,10000001b bt dx,7 ; 把DX第7位送入CF = 1 bt dx,6 ; 把DX第六位送入CF = 0 ; bts 位测试并置位 mov dx,10000001b bts dx,6 ; cf = 0 bts dx,7 ; cf = 1 ; btr 位测试并复位.在执行BT同时,把操作数的指定位置为0 mov dx,10000001b btr dx,7 btr dx,6 ; cf = 0 ;btc 位测试并取反.在执行BT同时,把操作数的指定位取反 mov dx,10000001b btc dx,0 ; cf = 1 btc dx,0 ; cf = 0 ; BSF 执行位扫描 由低->高位 | BSR 由高 -> 到低 xor edx,edx mov dx, 0000111100001100b bsf cx,dx ; 正向扫描,将扫描到1的位置放入CX bsr cx,dx ; 反向扫描 zf=0 pf=0 xor ecx,ecx mov cx,0 mov dx,0 bsf cx,dx lahf invoke ExitProcess,0 main ENDP END main