目录
[为什么 JCC 指令必须配合 TEST / CMP 使用?](#为什么 JCC 指令必须配合 TEST / CMP 使用?)
[最常用的"裁判助手"就是 TEST 和 CMP:](#最常用的“裁判助手”就是 TEST 和 CMP:)
[TEST 指令](#TEST 指令)
[CMP 指令](#CMP 指令)
[1. 无符号数比较(主要看 CF 标志):](#1. 无符号数比较(主要看 CF 标志):)
[2. 有符号数比较(主要看 SF 和 OF 标志):](#2. 有符号数比较(主要看 SF 和 OF 标志):)
[JCC 指令(条件跳转指令)](#JCC 指令(条件跳转指令))
[什么是 JCC 指令:](#什么是 JCC 指令:)
[主要 JCC 指令分类及含义:](#主要 JCC 指令分类及含义:)
[1. 通用跳转](#1. 通用跳转)
[2. 零标志 / 相等判断](#2. 零标志 / 相等判断)
[3. 符号标志](#3. 符号标志)
[4. 溢出标志](#4. 溢出标志)
[5. 奇偶标志](#5. 奇偶标志)
[6. 无符号比较跳转](#6. 无符号比较跳转)
[7. 有符号比较跳转](#7. 有符号比较跳转)
[JCC 指令的执行流程](#JCC 指令的执行流程)
[更新 EIP](#更新 EIP)

为什么 JCC 指令必须配合 TEST / CMP 使用?
核心原因:
JCC 本身不进行任何计算,它只"看"标志寄存器(EFLAGS)里的状态。
JCC(Jump if Condition Code)是一组条件跳转指令(je、jg、jb、jl 等)。它们的工作原理是:
- 执行前检查 EFLAGS 寄存器中的标志位(ZF、CF、SF、OF、PF 等);
- 根据标志位的值决定是否跳转。
JCC 自己不会修改标志位,它只是一个"裁判",需要前面有指令先把"比赛结果"写到标志位里。
最常用的"裁判助手"就是 TEST 和 CMP:
- CMP :负责数值大小比较(通过减法设置标志位)
- TEST :负责位测试 / 逻辑与(通过与运算设置标志位)
如果不配合 TEST 或 CMP(或 AND、SUB、OR 等能改标志位的指令),JCC 就无法做出正确判断,跳转条件会使用上一次指令遗留的标志位,导致程序逻辑错误。
总结一句话: JCC 是决策指令,TEST 和 CMP 是提供决策依据的指令。 它们是"搭档"关系,缺一不可。
决策指令
TEST 指令
技术原理:
-
TEST 指令执行按位与(AND)运算,但不保存运算结果
-
仅用于修改 EFLAGS 寄存器中的标志位
-
它本质上是对两个操作数进行"与"操作,用于位测试和标志位设置。
-
指令格式:
TEST 操作数1, 操作数2
影响的标志位:
-
ZF(Zero Flag):结果为0时置1,否则置0
-
SF(Sign Flag):结果最高位为1时置1,否则置0
-
PF(Parity Flag):结果中1的个数为偶数时置1
-
CF 和 OF:总是被清零(不受影响)
示例代码:
mov eax, 1001b ; EAX = ...00001001
test eax, 1001b ; 1001 & 1001 = 1001 → ZF=0(非零)
jnz bit_set ; 如果该位被置位则跳转
mov eax, 0
test eax, 1 ; 0 & 1 = 0 → ZF=1
jz bit_clear ; 如果该位未置位则跳转
用途:
- 常用于检测特定位是否置位(如权限标志、状态标志、设备控制位等),是位操作和条件判断的重要手段。
用途:
- 常用于检测特定位是否置位(如权限标志、状态标志、设备控制位等),是位操作和条件判断的重要手段。

CMP 指令
技术原理:
-
CMP 指令执行减法运算(操作数1 - 操作数2),但不保存结果
-
仅根据减法结果更新 EFLAGS 寄存器的标志位
-
为后续的条件跳转(JCC)提供判断依据。
指令格式:
CMP 操作数1, 操作数2
示例代码:
mov eax, 10
mov ebx, 5
cmp eax, ebx ; 相当于 10 - 5,设置相关标志位
比较规则:
1. 无符号数比较(主要看 CF 标志):
-
A < B:CF=1,ZF=0
-
A > B:CF=0,ZF=0
-
A == B:ZF=1
-
A <= B:CF=1 或 ZF=1
-
A >= B:CF=0
2. 有符号数比较(主要看 SF 和 OF 标志):
-
A < B:SF ≠ OF
-
A > B:SF = OF 且 ZF=0
-
A == B:ZF=1
-
A >= B:SF = OF
-
A <= B:ZF=1 或 SF ≠ OF
补充说明:
-
SF 表示结果的符号(1为负,0为正)
-
OF 表示有符号运算是否发生溢出
JCC 指令(条件跳转指令)
什么是 JCC 指令:
-
JCC 是 x86 汇编中"Jump if Condition Code"(条件码跳转)的统称
-
是一系列根据 EFLAGS 寄存器中特定标志位状态决定是否跳转的指令。
-
JCC 本身不修改任何寄存器或内存,仅改变程序的执行流程(EIP/RIP)。
-
JCC 指令必须紧跟在能修改标志位的指令(如 CMP、TEST、AND、OR、SUB 等)之后使用
-
是实现高级语言中 if、else、while、for、switch 等控制结构的核心机制
-
在逆向工程和底层优化中极其重要。
-
主要 JCC 指令分类及含义:
1. 通用跳转
JMP:无条件跳转(始终跳转)
2. 零标志 / 相等判断
-
JE / JZ:ZF=1 → 相等 或 结果为零 -
JNE / JNZ:ZF=0 → 不相等 或 结果非零
3. 符号标志
-
JS:SF=1 → 结果为负数 -
JNS:SF=0 → 结果为非负数
4. 溢出标志
-
JO:OF=1 → 有符号溢出 -
JNO:OF=0 → 无溢出
5. 奇偶标志
-
JP / JPE:PF=1 → 偶校验 -
JNP / JPO:PF=0 → 奇校验
6. 无符号比较跳转
-
JB / JNAE:CF=1 → 低于(A < B,无符号) -
JNB / JAE:CF=0 → 不低于(A >= B,无符号) -
JBE / JNA:CF=1 或 ZF=1 → 低于等于(A <= B,无符号) -
JA / JNBE:CF=0 且 ZF=0 → 高于(A > B,无符号)
7. 有符号比较跳转
-
JL / JNGE:SF ≠ OF → 小于(A < B,有符号) -
JGE / JNL:SF = OF → 大于等于(A >= B,有符号) -
JLE / JNG:ZF=1 或 SF ≠ OF → 小于等于(A <= B,有符号) -
JG / JNLE:ZF=0 且 SF = OF → 大于(A > B,有符号)
JCC 指令的执行流程
JCC 指令的执行是一个多步骤的过程,涉及 CPU 的指令解码、标志位检查和控制流调整。以下是详细的底层流程:
指令获取与解码
-
步骤:
-
CPU 从内存中读取 JCC 指令的机器码(通过指令指针 EIP 定位)。
-
指令解码单元解析操作码(如 74 表示 JE)和操作数(目标地址的偏移量)。
-
-
细节:
-
JCC 指令通常是 2 字节或更多字节:
-
1 字节操作码(如 74 表示 JE)。
-
1 字节或 4 字节的相对偏移量(短跳转或长跳转)。
-
-
例如:JE label 在内存中可能是 74 05,表示"如果 ZF=1,跳转到当前地址 + 5"。
-
检查标志位
-
步骤:
-
CPU 根据 JCC 的类型,读取 EFLAGS 中对应的标志位。
-
执行条件判断逻辑。
-
-
示例:
-
JE(Jump if Equal):检查 ZF 是否为 1。
-
JB(Jump if Below):检查 CF 是否为 1。
-
JL(Jump if Less):检查 SF 和 OF 是否不同(SF ≠ OF)。
-
计算目标地址
-
步骤:
-
如果条件满足,计算目标地址。
-
目标地址 = 当前 EIP + 偏移量(偏移量是有符号数,可能正或负)。
-
-
细节:
-
偏移量是相对跳转,范围取决于指令类型:
-
短跳转(EB):1 字节偏移量,-128 到 +127。
-
近跳转(E9):4 字节偏移量,范围更大。
-
-
例如:JE 0x05 表示"如果条件满足,EIP = EIP + 5"。
-
更新 EIP
-
步骤:
-
如果条件满足,将计算出的目标地址写入 EIP。
-
如果条件不满足,EIP 指向下一条指令(EIP = EIP + 指令长度)。
-
-
细节:
-
EIP(指令指针)是 CPU 的关键寄存器,始终指向下一条待执行指令的地址。
-
JCC 的跳转本质上是修改 EIP 的值。
-
流水线与分支预测
-
背景:现代 CPU 使用流水线技术并行处理指令,跳转可能导致流水线停顿。
-
优化:
-
分支预测器:预测 JCC 是否跳转,预取可能的目标指令。
-
预测失败:如果预测错误,丢弃预取的指令,重新加载正确路径的指令。
-
-
细节:
-
分支预测基于历史跳转模式(如"总是跳转"或"从不跳转")。
-
提高了 JCC 的执行效率,但增加了硬件复杂度。
-
案例
案例1:简单 if-else 等价结构
mov eax, 15
mov ebx, 10
cmp eax, ebx
jg greater_case ; 如果 eax > ebx(有符号)则跳转
; else 分支
mov ecx, 0
jmp end_if
greater_case:
mov ecx, 1
end_if:
案例2:使用 TEST 的位检测
mov eax, 0x00000004 ; 第2位被置位
test eax, 0x00000004 ; 测试第2位
jnz bit_is_set ; 如果该位为1则跳转
; 该位为0的处理
jmp done
bit_is_set:
; 该位为1的处理
done: