5.2 汇编语言:标志位测试指令

汇编语言,标志,测试,指令 · 浏览次数 : 35

小编点评

## 命令行指令摘要 本文内容包含对特定寄存器或数据位进行测试、清除、设置或求反等操作的指令,这些指令通常会影响条件码寄存器CF的值。 **指令主要内容:** * **bt:** 测试特定寄存器中的位是否为 1,将测试结果存储在条件码寄存器CF的最低位中。 * **btr:** 将特定寄存器中的位清零,将被清零的位由CF最低位指示,即如果CF为 1,则对应位清零;否则不变。 * **bts:** 将特定寄存器中的位设置为 1,被设置的位由CF最低位指示,即如果CF为 1,则对应位设置为 1;否则不变。 * **bsf:** 从寄存器或内存中获取一个WORD或DWORD数据,从低位到高位扫描,找到第一个值为1的位,将该位的偏移量存储在目标寄存器中,并将条件码寄存器ZF设置为相应的值。 * **bsr:** 从寄存器或内存中获取一个WORD或DWORD数据,从高位到低位扫描,找到最后一个值为1的位,将该位的偏移量存储在目标寄存器中,并将条件码寄存器ZF设置为相应的值。 **示例代码:** ```python # bt 指令测试特定寄存器中的位是否为 1 xor edx,edx mov dx,10000001b bt dx,7 # btr 指令将特定寄存器中的位清零 xor edx,edx mov dx,10000001b btr dx,0 # bts 指令将特定寄存器中的位设置为 1 xor edx,edx mov dx,10000001b bts dx,0 # bsf 指令从寄存器或内存中获取一个WORD数据,从低位到高位扫描,找到第一个值为1的位 xor edx,edx mov dx, 0000111100001100b bsf cx,dx # bsr 指令从寄存器或内存中获取一个WORD数据,从高位到低位扫描,找到最后一个值为1的位 xor edx,edx mov dx,0000111100001100b bsr cx,dx ``` **总结:** 这些指令可以帮助读者测试和设置特定寄存器或数据位的值,这些指令通常会影响条件码寄存器CF的值。

正文

汇编语言是一种面向机器的低级语言,用于编写计算机程序。汇编语言与计算机机器语言非常接近,汇编语言程序可以使用符号、助记符等来代替机器语言的二进制码,但最终会被汇编器编译成计算机可执行的机器码。

标志位测试指令是汇编语言中用于测试处理器标志位状态的指令。标志位是位于处理器状态寄存器中的一组特殊标志,用于指示上一个运算的结果是否为零、是否进位/借位、是否溢出等等。可以使用标志位测试指令来检查标志位的状态,并在需要时根据标志位状态进行操作。

常见的标志位测试指令包括:

  • 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运算,不改变目标操作数的值,只设置相应的标志位,常用于测试某些位是否被设置。

该指令的语法为:

TEST dest, src

其中,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
je END

在这个代码中,CMP指令将eax0相减,不保存结果,而是设置相应的条件码标志位。如果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最低位指示,即如果CF1,则对应位取反;否则不变。
  • BTR 指令:将特定寄存器中的位清零,被清零的位由CF最低位指示,即如果CF1,则对应位清零;否则不变。
  • BTS 指令:将特定寄存器中的位设置为 1,被设置的位由CF最低位指示,即如果CF为 1,则对应位设置为 1;否则不变。

相比之下,BSFBSR指令则是对特定数据中的位进行正反向扫描操作,进而得到位中第一个1和最后一个1的位置,这些操作通常会影响条件码寄存器ZF的值。这两个指令的具体操作如下:

  • BSF 指令:从寄存器或内存中获取一个WORDDWORD数据,从低位到高位扫描,找到第一个值为1的位,将该位的偏移量存储在目标寄存器中,并将条件码寄存器ZF设置为相应的值,如果未找到值为1的位,则目标寄存器的值未定义,且ZF被清零。

  • BSR 指令:从寄存器或内存中获取一个WORDDWORD数据,从高位到低位扫描,找到最后一个值为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

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/3125ea93.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

与5.2 汇编语言:标志位测试指令相似的内容:

5.2 汇编语言:标志位测试指令

汇编语言是一种面向机器的低级语言,用于编写计算机程序。汇编语言与计算机机器语言非常接近,汇编语言程序可以使用符号、助记符等来代替机器语言的二进制码,但最终会被汇编器编译成计算机可执行的机器码。标志位测试指令是汇编语言中用于测试处理器标志位状态的指令。标志位是位于处理器状态寄存器中的一组特殊标志,用于指示上一个运算的结果是否为零、是否进位/借位、是否溢出等等。可以使用标志位测试指令来检查标志位的状态

[转帖]linux--Segfault详解

linux--Segfault详解 1 简介1.1 段错误的定义1.2 痛点 2 知识点2.1 报错内容2.2 error number 3 排除步骤(借助汇编)3.1 日志确定错误类型3.2 计算相对地址3.3 反汇编该库文件3.4 查找地址对应的汇编语句3.5 在中间件中查找信息 3 排除步骤(

5.2 基于ROP漏洞挖掘与利用

通常情况下栈溢出可能造成的后果有两种,一类是本地提权另一类则是远程执行任意命令,通常C/C++并没有提供智能化检查用户输入是否合法的功能,同时程序编写人员在编写代码时也很难始终检查栈是否会发生溢出,这就给恶意代码的溢出提供了的条件,利用溢出攻击者可以控制程序的执行流,从而控制程序的执行过程并实施恶意行为,本章内容笔者通过自行编写了一个基于网络的FTP服务器,并特意布置了特定的漏洞,通过本章的学习,

5.2 磁盘CRC32完整性检测

CRC校验技术是用于检测数据传输或存储过程中是否出现了错误的一种方法,校验算法可以通过计算应用与数据的循环冗余校验(CRC)检验值来检测任何数据损坏。通过运用本校验技术我们可以实现对特定内存区域以及磁盘文件进行完整性检测,并以此来判定特定程序内存是否发生了变化,如果发生变化则拒绝执行,通过此种方法来保护内存或磁盘文件不会被非法篡改。总之,内存和磁盘中的校验技术都是用于确保数据和程序的完整性和安全性

TypeScript又出新关键字了?

TypeScript 5.2将引入一个新的关键字:`using`。当它离开作用域时,你可以用`Symbol.dispose`函数来处置任何东西。 ```jsx { const getResource = () => { return { [Symbol.dispose]: () => { conso

.NET周刊【5月第2期 2024-05-12】

国内文章 C#在工业数字孪生中的开发路线实践 https://mp.weixin.qq.com/s/b_Pjt2oii0Xa_sZp_9wYWg 这篇文章探讨了C#在工业数字孪生技术中的应用,介绍了三种基于C#的数字孪生系统实现方案: WPF + Unity:结合WPF技术和Unity引擎,实现客户

[转帖]5.2. 使用HINT

¶ 本章节包含以下内容: 概述 HINT的功能 HINT的使用 配置参数 示例 注意 5.2.1. 概述 ¶ KingbaseES使用的是基于成本的优化器。优化器会估计SQL语句的每个可能的执行计划的成本,然后选择成本最低的执行计划来执行。因为优化器不计算数据的某些属性,比如列之间的相关性,优化器有

「C++」简单模拟

这是一个公式: \[F_n=\dfrac{\left(\frac{1+\sqrt{5}}{2}\right)^n-\left(\frac{1-\sqrt{5}}{2}\right)^n}{\sqrt{5}} \]根据大家的数学经验可以知道这是一个计算斐波那契数列的公式,那么假设我们不知道这是一个斐波

[转帖]50年来Intel CPU变化有多大?频率从0.75MHz提升到5.2GHz

https://m.baidu.com/bh/m/detail/ar_9297450181050583423?data_from=lemon 今天(11月15日)是Intel推出4004处理器50周年的日子,当初为日本计算器客户开发的芯片成为全球首个微处理器,并且改变了Intel的发展,从当初的内存

C++如何在main函数开始之前(或结束之后)执行一段逻辑?

1. 问题 2. 考察的要点 3. 解决策略 3.1. 方案一:使用GCC的拓展功能 3.2. 方案二:使用全局变量 3.3. 方案三:atexit 4. Demo测试 4.1. 测试代码 4.2. 执行结果 5. 程序异常退出场景 5.1. 存在的问题 5.2. 解决方案 5.2.1. 原理 5.