寄存器
一、通用寄存器
在 32 位 x86 中,通用寄存器(如 EAX、EBX)宽度是 32 位(4 字节),虚拟内存地址宽度也是 32 位,指针大小为 4 字节。
在 64 位 x86-64 中,通用寄存器(如 RAX、RBX)宽度是 64 位(8 字节),虚拟内存地址(指针)是 64 位(8 字节)。
1. x86寄存器
- 数据寄存器
EAX(累加寄存器,Accumulator)
- 常用于算术运算、I/O 操作。
- 在 Windows 程序中通常用于存放函数返回值。
EBX(基址寄存器,Base)
- 用于指向数组或数据结构的基址。
ECX(计数寄存器,Count)
- 常用于循环计数器或移位操作。
- 调用约定相关:
- 在 Windows x86 的
fastcall调用约定中,ECX 用于传递函数的第一个参数。 - 在
stdcall调用约定中,参数通常通过栈传递,ECX 可作为临时数据存储或循环计数器使用。
- 在 Windows x86 的
- 与 C++
this指针的关系:- 在 C++ 成员函数中,
this指针作为隐含参数传递,存放位置由编译器决定。 - 在 Windows x86(MSVC)下,若使用
fastcall约定,this指针通常存放在 ECX 中。
- 在 C++ 成员函数中,
EDX(数据寄存器,Data)
- 用于存储乘法、除法的结果,或与 I/O 操作相关。
- 索引寄存器
ESI(源索引寄存器,Source Index)
- 用于指向源操作数,尤其在字符串操作中。
EDI(目的索引寄存器,Destination Index)
- 用于指向目标操作数,尤其在字符串操作中。
- 指针寄存器(栈管理)
EBP(基指针寄存器,Base Pointer)
- 用于保存当前栈帧的基地址(栈底),函数调用时栈帧管理依赖它。
- 指向栈底(高地址)。
ESP(堆栈指针寄存器,Stack Pointer)
- 指向当前栈顶位置(低地址)。
push/pop等栈操作依赖它,对维护栈平衡至关重要。
2. 64 位(x86-64)架构通用寄存器
- 原有寄存器的 64 位扩展
以下 32 位寄存器被扩展为 64 位版本:
| 32 位 | 64 位 |
|---|---|
| EAX | RAX |
| EBX | RBX |
| ECX | RCX |
| EDX | RDX |
| ESI | RSI |
| EDI | RDI |
| EBP | RBP |
| ESP | RSP |
| EIP | RIP |
扩展后的高位部分可用于更大范围的数据处理。
- 新增通用寄存器
- R8 ~ R15 是 x86-64 架构新增的 8 个通用寄存器,进一步提升了处理能力。
3. 32 位与 64 位寄存器的主要区别
- 位宽扩展:原 32 位寄存器(如 EAX)扩展为 64 位(如 RAX),支持更大数据范围。
- 指令集支持:x86-64 指令集新增对 64 位寄存器的操作符,可直接处理 64 位数据。
- 寄存器数量增加:从 8 个通用寄存器(x86)增加到 16 个(x86-64,含 R8~R15),有助于减少内存访问,提升性能。
二、指令指针寄存器
1. x86
- EIP(指令指针寄存器,Instruction Pointer)
- 存储当前正在执行的指令在内存中的地址(偏移量)。
- 程序顺序执行时,EIP 会自动递增到下一条指令;遇到跳转(
jmp)、调用(call)、返回(ret)等指令时,EIP 会被修改,从而实现流程控制。 - 不能直接赋值(不能像
mov eax, 123那样操作 EIP),只能通过跳转、分支、函数调用等指令间接改变。 - 与 EBP/ESP 协作完成函数调用:
call指令:将返回地址压入栈,然后跳转到目标函数,EIP 更新为目标函数地址。ret指令:从栈顶弹出返回地址到 EIP,回到调用者继续执行。
2. x64
- RIP(64 位 Instruction Pointer)
- 功能与 EIP 相同,但地址宽度扩展到 64 位。
- 支持 RIP-相对寻址(RIP-relative addressing),即用当前指令地址 + 偏移量来访问数据,这在位置无关代码(PIC)中非常常用。
- 在 Windows 异常处理、调试器或反汇编工具(如 x64dbg)中,RIP 是核心参考寄存器,指示 CPU“下一步该执行什么”。
三、段寄存器
1. 基本概念
段寄存器是 x86 架构中的一个历史遗留功能,最初用于内存管理,帮助 CPU 确定内存地址范围。段寄存器包含了程序在内存中的段基址,可以用来寻址特定的内存区域(如代码、数据、堆栈段等)。
在现代操作系统中,段寄存器并不直接表示内存的基地址,而是一个 段选择子(Segment Selector)。段选择子是一个索引,用来指向全局描述符表(GDT)或局部描述符表(LDT)中的段描述符。段描述符中定义了段的基地址、段界限和访问权限等信息。
2. x86 架构中的 6 个段寄存器
| 寄存器 | 名称 | 用途 |
|---|---|---|
| CS | 代码段寄存器(Code Segment) | 存储指令的基址(代码段) |
| DS | 数据段寄存器(Data Segment) | 存储数据段的基址 |
| SS | 堆栈段寄存器(Stack Segment) | 存储堆栈的基址 |
| ES | 附加段寄存器(Extra Segment) | 用于字符串操作 |
| FS | 扩展段寄存器(General Purpose Segment) | 用于操作系统和多任务管理 |
| GS | 扩展段寄存器(General Purpose Segment) | 用于操作系统和多任务管理 |
注:CS、DS、SS、ES 在 Windows 中指向同一个段基址。
3. 现代操作系统中的段寄存器
Windows 和其他现代操作系统通常依赖于 分页机制(Paging) 来管理内存,而不再使用传统的基于段的内存管理方式。
在现代的 32 位和 64 位 x86 架构中,段寄存器的使用较少,通常由操作系统管理。现代操作系统大多采用 平坦地址空间模型(Flat Address Space Model),即不再使用段寄存器进行内存管理,而直接使用线性地址。
4. FS 和 GS 的特殊作用(Windows 环境)
在 Windows 操作系统中,FS(32 位) 和 GS(64 位) 寄存器与以下核心结构有密切关系:
TEB(线程环境块,Thread Environment Block)
- 存储与当前线程相关的信息。
- FS 寄存器指向当前线程的 TEB。
PEB(进程环境块,Process Environment Block)
- 存储与进程相关的信息。
- 在 64 位 Windows 中,通过 GS 寄存器可以访问 PEB。
SEH(结构化异常处理,Structured Exception Handling)
- 存储线程的异常处理信息,保存在 TEB 中,通过 FS/GS 寄存器可以访问。
VEH(向量化异常处理,Vectored Exception Handling)
- 全局的异常处理机制,虽然不直接依赖 FS/GS,但在处理线程相关异常时,仍然涉及 TEB 和 FS/GS 寄存器。
理解这些结构和 FS/GS 寄存器之间的关系,对于 Windows 逆向工程、调试和系统编程 非常重要。
五、标志寄存器(Flags Register)
标志寄存器用于存储 CPU 执行操作后的状态信息,它包含了一个或多个 标志位,这些标志位表示算术运算和逻辑运算后的结果状态。
常见标志位
| 标志位 | 名称 | 说明 |
|---|---|---|
| CF | 进位标志(Carry Flag) | 表示加法或移位操作是否产生进位 |
| ZF | 零标志(Zero Flag) | 表示结果是否为零 |
| SF | 符号标志(Sign Flag) | 表示运算结果的符号位 |
| OF | 溢出标志(Overflow Flag) | 表示加法或减法是否发生了溢出 |
| PF | 偶校验标志(Parity Flag) | 用于检查结果是否具有偶数个 1 |
| DF | 方向标志(Direction Flag) | 用于字符串操作时的方向控制(增量或减量) |
| AF | 辅助进位标志(Auxiliary Carry Flag) | 用于 BCD 运算 |
标志寄存器的常见用途是在 条件跳转指令(如
JZ、JNC)中作为判断依据。
六、控制寄存器(Control Registers)
控制寄存器用于控制 CPU 的执行模式和特性。这些寄存器通常在更低层的系统编程中使用。
- CR0 ~ CR4:这些寄存器控制 CPU 的不同特性,如保护模式、分页机制、缓存策略等。
| 寄存器 | 主要功能 |
|---|---|
| CR0 | 控制保护模式、分页、协处理器等 |
| CR1 | 保留(未使用) |
| CR2 | 存储页错误(Page Fault)发生时的线性地址 |
| CR3 | 存储页目录基址(PDBR),用于分页管理 |
| CR4 | 控制扩展特性,如 PAE、SMEP、SMAP 等 |
七、浮点寄存器(Floating Point Registers)
x86 架构还包括一些专门用于浮点运算的寄存器,尤其是在使用浮点运算单元(FPU)时。
ST(0) ~ ST(7):浮点堆栈寄存器,共 8 个,采用堆栈结构(寄存器堆栈)进行浮点运算。
FPU(浮点运算单元,Floating Point Unit)
- 是计算机硬件中专门用于执行浮点数运算的单元。
- 负责高效地执行加法、减法、乘法、除法、平方根等浮点数相关操作。
在现代 x86-64 架构中,浮点运算更多地使用 XMM 寄存器(SSE/AVX 指令集),而非传统的 FPU 堆栈寄存器。
八、调试寄存器(Debug Registers)
x86 架构提供了 8 个调试寄存器,编号为 DR0 ~ DR7,用于硬件断点和调试支持。
| 寄存器 | 用途 |
|---|---|
| DR0 ~ DR3 | 存储硬件断点的地址。调试器可以在这些地址上设置断点,用于监视内存的访问或指令的执行。 |
| DR4, DR5 | 已被保留,不再使用(现代处理器中用作保留字段)。 |
| DR6 | 调试状态寄存器,存储断点触发的状态信息,用于判断哪个断点被触发。 |
| DR7 | 调试控制寄存器,设置硬件断点的条件(如读/写/执行)及权限(全局断点/本地断点)。 |
调试寄存器是硬件调试的核心组件,提供了一种高效监视内存访问或指令执行的方法。在现代调试器(如 x32dbg、x64dbg)中,硬件断点的设置与管理已经被工具简化,但理解调试寄存器的底层原理对于深入调试和逆向工程非常重要。
汇编语言的基本结构:指令、操作数、标签、操作符等
汇编语言作为低级语言,与计算机硬件关系紧密,它通过使用一组简化的助记符来表示机器指令,程序员可以通过汇编语言来直接控制计算机硬件。了解汇编语言的基本结构是学习汇编和逆向工程的基础。
一、指令(Instruction)
指令是汇编语言中最重要的组成部分,代表 CPU 执行的一个操作。每条指令通常由操作码(Opcode)和操作数(Operand)组成。
1. 操作码(Opcode)
操作码告诉 CPU 执行什么操作,常见的操作码包括:
| 操作码 | 说明 |
|---|---|
| MOV | 数据传送 |
| ADD | 加法 |
| SUB | 减法 |
| MUL | 乘法 |
| DIV | 除法 |
| AND | 按位与 |
| OR | 按位或 |
| XOR | 按位异或 |
| SHL | 逻辑左移 |
| SHR | 逻辑右移 |
2. 操作数(Operand)
操作数指定操作对象,可以是寄存器、内存地址或立即数(常数)。
注意:指令的赋值方向为 从右向左,例如
MOV EAX, 5表示将 5 存入 EAX。
指令示例
1 | MOV EAX, 5 |
- 操作码:
MOV,表示将数据传送到寄存器。 - 操作数:
EAX(寄存器),5(立即数),表示将常数 5 存入 EAX 寄存器。
二、操作数(Operand)详解
操作数是指令的参数,表示指令操作的对象。操作数通常有以下几种类型:
1. 寄存器
表示 CPU 内部的寄存器,如 EAX、EBX 等。
1 | MOV EAX, EBX ; 将 EBX 的值复制到 EAX |
2. 内存地址
表示内存中的某个位置,如 0x401000。
- 不带方括号:表示取地址本身(作为立即数)
- 带方括号
[]:表示取出该地址中存储的值
1 | MOV EAX, 0x00401000 ; 将地址 0x00401000 作为数值存入 EAX |
特别注意:方括号
[]只能在内存地址上使用,表示取出地址里面的值。
3. 间接寻址(寄存器作为地址)
如果寄存器中存储的是一个内存地址,则可以通过 [寄存器] 来访问该地址中的值。
1 | ; 假设 EBX = 0x00401000 |
4. 立即数
直接使用的常数,不需要从内存或寄存器中读取。
1 | MOV EAX, 5 ; 将常数 5 存入 EAX |
三、操作符(Operator)
操作符是汇编语言中执行运算的符号,包括算术操作符、逻辑操作符和位操作符等。
1. 算术操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
| ADD | 加法 | ADD EAX, 5 → EAX = EAX + 5 |
| SUB | 减法 | SUB EAX, 3 → EAX = EAX - 3 |
| MUL | 乘法 | MUL EBX → EAX = EAX * EBX |
| DIV | 除法 | DIV EBX → EAX = EAX / EBX |
2. 位操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
| AND | 按位与 | AND EAX, 0xFF → 取 EAX 的低 8 位 |
| OR | 按位或 | OR EAX, 0x10 → 将 EAX 的第 4 位置 1 |
| XOR | 按位异或 | XOR EAX, EAX → 将 EAX 清零(常用技巧) |
| SHL | 逻辑左移 | SHL EAX, 2 → EAX 左移 2 位(乘以 4) |
| SHR | 逻辑右移 | SHR EAX, 1 → EAX 右移 1 位(除以 2) |
示例
1 | ADD EAX, 5 ; 将寄存器 EAX 的值与常数 5 相加,结果存回 EAX |
四、标签(Label)
(补充内容) 标签是汇编语言中用于标记代码位置的符号,通常用于跳转指令(如 JMP、JZ 等)的目标地址。
1 | _start: |
标签在逆向工程中通常对应函数的入口地址或分支跳转的目标地址。
五、注释
汇编语言中,注释通常以分号 ; 开头,用于对代码进行说明,汇编器会忽略注释内容。
1 | MOV EAX, 5 ; 将常数 5 存入 EAX |
数据传输指令
| 操作符 | 功能 | 示例 |
|---|---|---|
| mov | 将数据从源传输到目的位置 | mov eax, ebx |
| push | 将数据压入栈 | push eax |
| pop | 从栈中弹出数据到目的位置 | pop eax |
| xchg | 交换两个操作数的值 | xchg eax, ebx |
| lea | 取地址 | lea eax, [ebx+4] |
FS 段寄存器与 TEB(线程环境块)
一、FS 段寄存器概述
在 Windows 操作系统中,FS 是一个段寄存器(Segment Register),它用于指向当前线程的 TEB(线程环境块,Thread Environment Block)。
核心作用
- FS 寄存器主要用于访问当前线程的局部存储区域,而不是全局数据。
- 这使得线程能够高效地存储和访问其专有的信息,而不需要锁定整个进程的数据。
- 在 32 位 Windows 中,
fs:[0]存储的是当前线程的 TEB 的基地址,通常被用来获取该线程的相关信息。
进程与线程的关系:进程是外壳,内部是由线程来构成的。
二、TEB(线程环境块)概述
TEB(Thread Environment Block)是为每个线程分配的一个数据结构,用来存储线程的相关信息。
TEB 的特点
- 每个线程都有一个独立的 TEB。
- 每个线程通过
fs:[0]获取指向其 TEB 的指针。 - TEB 存储的信息包括:
- 线程本地存储
- 异常处理信息
- TLS(线程局部存储)
- 栈指针
- 进程环境块(PEB)指针
三、TEB 结构详细定义(32 位 Windows)
在 32 位 Windows 中,TEB 结构大致如下:
1 | typedef struct _TEB { |
四、TEB 主要字段详解
- NtTib(线程信息块,TIB)
TIB 是 TEB 的最开始部分,主要用于维护线程的堆栈和异常处理的基本结构。
| 字段 | 说明 |
|---|---|
| NtTib.StackTrace | 线程栈的基地址 |
| NtTib.StackTraceLimit | 线程栈的限制地址 |
EnvironmentPointer
- 指向线程的环境块。
- 这个指针通常为空,在某些特殊情况下可能会用到。
ClientId(客户端标识符)
包含线程的唯一标识符以及其所属的进程 ID。
1 | typedef struct _CLIENT_ID { |
ThreadLocalStoragePointer(TLS 指针)
指向线程局部存储(TLS,Thread Local Storage)的指针。
TLS 是每个线程可以独立使用的内存区域,每个线程都有自己的独立副本。
ProcessEnvironmentBlock(PEB 指针)
指向进程环境块(PEB,Process Environment Block)的指针。
PEB 存储了与当前进程相关的信息,例如:
进程的启动参数
模块信息
进程内存布局
LastErrorValue
线程最近的错误值。
通常是 Windows 系统调用或 API 函数的错误代码。
可通过 GetLastError() 获取。
CountOfOwnedCriticalSections
线程拥有的临界区(Critical Section)数量。
临界区用于线程间的同步,防止多个线程同时访问共享资源。
CsrClientThread
通常用于与 CSRSS(Client/Server Runtime Subsystem) 通信。
它是用户模式与内核模式之间的一个接口。
Win32ThreadInfo
向 Win32 线程信息的指针。
与用户模式的线程交互相关。
FlsData(Fiber Local Storage)
线程本地存储数据(FLS)。
允许线程存储和检索特定于线程的数据。
五、FS 与 TEB 的关系速查
| 项目 | 说明 |
| — | — |
|FS 寄存器 | 指向当前线程的 TEB |
|fs:[0] |TEB 的基地址 |
|TEB 偏移+0x30 | PEB 指针 |
|TEB 偏移+0x34|LastErrorValue |
|TEB 偏移+0x2C|TLS 指针 |
|TEB 偏移+0x20|ClientId(线程 ID + 进程 ID)|
标志寄存器(EFLAGS)详解
一、EFLAGS 寄存器概述
EFLAGS 是一个 32 位寄存器(64 位下为 RFLAGS),其中的每一位都是一个独立的标志位,用于记录 CPU 执行算术或逻辑运算后的状态信息。这些标志位不仅反映运算结果,还控制着 CPU 的某些行为(如方向标志 DF 控制字符串操作方向)。标志位是条件跳转、循环和许多高级指令(如 ADC、SBB)得以工作的基础。
32 位名为 EFLAGS,64 位名为 RFLAGS(高 32 位保留)。下文以 EFLAGS 代指。
二、常用标志位详解
| 标志位 | 名称 | 功能说明 |
|---|---|---|
| CF | Carry Flag(进位标志) | 无符号运算结果超出寄存器位宽时置 1(如加法进位、减法借位)。也用于移位指令移出的位。 |
| PF | Parity Flag(奇偶标志) | 结果的低 8 位中“1”的个数为偶数时置 1(用于校验)。 |
| AF | Auxiliary Carry Flag | BCD 运算时,低 4 位向高 4 位产生进位/借位时置 1(很少直接使用)。 |
| ZF | Zero Flag(零标志) | 运算结果为零时置 1。 |
| SF | Sign Flag(符号标志) | 运算结果的最高位(符号位)为 1 时置 1(表示负数,针对有符号数)。 |
| OF | Overflow Flag(溢出标志) | 有符号运算结果超出范围时置 1(例如两个正数相加得负数)。 |
| DF | Direction Flag(方向标志) | 控制字符串操作(MOVS、STOS、CMPS 等)中地址寄存器的增减方向: • DF=0:地址递增(从低到高) • DF=1:地址递减(从高到低) |
| IF | Interrupt Flag(中断允许标志) | 控制 CPU 是否响应可屏蔽中断(1=允许,0=屏蔽)。 |
| TF | Trap Flag(陷阱标志) | 置 1 时 CPU 进入单步调试模式(每条指令执行后触发调试异常)。 |
以上标志位中,CF、PF、AF、ZF、SF、OF 会受算术/逻辑运算指令(如 ADD、SUB、AND、SAL 等)的结果影响;DF 由
CLD(清零)和STD(置 1)指令显式控制;IF 和 TF 属于系统控制标志,应用程序不常直接操作。
三、影响标志位的常见指令
1. 算术运算指令
| 指令 | 影响的标志位 | 说明 |
|---|---|---|
ADD |
CF, ZF, SF, OF, AF, PF | 加法,无符号溢出置 CF,有符号溢出置 OF |
SUB |
同上 | 减法,借位置 CF |
ADC |
同上 | 带进位加法:dest = dest + src + CF,常用于多精度加法 |
SBB |
同上 | 带借位减法:dest = dest - src - CF,常用于多精度减法 |
INC |
ZF, SF, OF, AF, PF(不影响 CF) | 加 1 |
DEC |
同上(不影响 CF) | 减 1 |
NEG |
CF, ZF, SF, OF, AF, PF | 取补(0 - 操作数),结果不为零时 CF 置 1 |
MUL / IMUL |
CF, OF(其他未定义) | 无符号/有符号乘法,结果高位有意义时 CF/OF 置 1 |
DIV / IDIV |
不影响标志位(但溢出会产生异常) | 除法 |
2. 逻辑运算和移位指令
| 指令 | 影响的标志位 | 说明 |
|---|---|---|
AND / OR / XOR |
CF=0, OF=0, ZF, SF, PF(AF 未定义) | 按位逻辑运算,CF 和 OF 强制清零 |
NOT |
不影响任何标志 | 按位取反 |
SHL / SHR / SAL / SAR |
CF(移出的最后一位),ZF, SF, PF, OF(仅当移位数为1时有效) | 移位操作 |
3. 特殊指令:XCHG
XCHG(交换)指令不影响任何标志位,这点需要特别注意。
四、串操作指令与方向标志 DF
1. MOVS(Move String)
MOVS 用于将内存中的一个数据(字节/字/双字/四字)复制到另一个内存地址。它隐式使用 ESI(源地址)和 EDI(目标地址)寄存器。
1 | MOVS BYTE PTR [EDI], BYTE PTR [ESI] ; 复制 1 字节(MOVSB) |
操作原理:从 [ESI] 读取数据,写入 [EDI],然后根据 DF 标志 自动调整 ESI 和 EDI 的值:
若 DF = 0(向上,CLD 指令设置),则 ESI 和 EDI 均 增加 传输数据的宽度(1/2/4/8 字节)。
若 DF = 1(向下,STD 指令设置),则 ESI 和 EDI 均 减少 传输数据的宽度。
MOVS 常用于配合 REP 前缀实现批量内存拷贝,其方向完全由 DF 控制。
2. STOS(Store String)
STOS 将 AL/AX/EAX/RAX 中的值存入 [EDI],并同样根据 DF 标志更新 EDI(增或减)。
1 | STOS DWORD PTR [EDI] ; 将 EAX 存入 [EDI],EDI 根据 DF 增减 4 |
STOS常用于初始化内存(如REP STOS配合清零)。
3. REP 前缀(Repeat)
REP 是一个前缀,用于重复执行后面的串操作指令(如 MOVS、STOS、CMPS、SCAS),重复次数由 ECX/RCX 寄存器指定。
1 | MOV ECX, 100 |
- 执行过程:每执行一次操作,ECX 减 1,直到 ECX = 0 或满足特定条件(如
REPE/REPNE用于CMPS/SCAS时还检查 ZF)。
注意:
REP本身不影响标志位,但被重复的串操作指令会影响标志位(如CMPS会影响 ZF 等)。
JCC(条件转移指令)与流程控制
JCC 是 “Jump if Condition Code” 的缩写,指一类根据标志位状态决定是否跳转的指令。理解这些指令需要熟悉标志位的含义,并清楚 CMP、TEST 等指令如何设置标志位。
一、无条件跳转与函数调用
| 指令 | 功能 | 说明 |
|---|---|---|
| JMP | 无条件跳转到目标地址 | 直接修改 EIP/RIP,不保存返回地址 |
| CALL | 调用子程序(函数) | 将下一条指令的地址(返回地址)压入栈,然后跳转到目标地址 |
| RET | 从子程序返回 | 从栈顶弹出返回地址到 EIP/RIP,继续执行 |
原理简述
CALL 执行时:push 返回地址 ; jmp 目标
RET 执行时:pop 返回地址 ; jmp 返回地址
栈的平衡是函数调用正确返回的关键(详见堆栈相关章节)。
二、比较指令:CMP 和 TEST
这两条指令只修改标志位,不改变操作数本身,专为条件跳转服务。
1. CMP(Compare)
1 | CMP dest, src |
功能:执行 dest - src(减法),但不存储结果,只根据结果设置标志位(CF、ZF、SF、OF、AF、PF)。
常用场景:比较两个数的大小,随后根据标志位跳转。
标志位变化(同 SUB 指令):
| 比较结果(无符号) | CF | ZF |
|---|---|---|
| dest == src | 0 | 1 |
| dest < src | 1 | 0 |
| dest > src | 0 | 0 |
| 比较结果(有符号) | SF | OF | ZF |
|---|---|---|---|
| dest == src | 0 | 0 | 1 |
| dest < src | 与 OF 异或后为 1 | * | 0 |
| dest > src | 与 OF 异或后为 0 | * | 0 |
实际上,有符号比较使用 SF 和 OF 的组合来判断大小,而条件转移指令(如 JG/JL)会自动处理这些组合。
2. TEST(Test)
1 | TEST dest, src |
功能:执行 dest AND src(按位与),不存储结果,只根据结果设置标志位(ZF、SF、PF,且 CF=OF=0)。
常用场景:检查某位是否为 1(如 TEST EAX, EAX 用于判断 EAX 是否为零)。
示例:
1 | TEST EAX, 1 ; 检查 EAX 的最低位 |
三、条件转移指令(JCC)分类
所有条件转移指令的助记符均以 J 开头,后跟条件缩写。它们检查特定的标志位组合,满足条件则跳转到目标标签/地址,否则顺序执行下一条指令。
1. 基于单个标志位的跳转
| 指令 | 条件 | 标志位状态 | 说明 |
|---|---|---|---|
| JZ / JE | 结果为零(相等) | ZF = 1 | Jump if Zero / Equal |
| JNZ / JNE | 结果不为零(不相等) | ZF = 0 | Jump if Not Zero / Not Equal |
| JC / JB / JNAE | 进位(无符号小于) | CF = 1 | Jump if Carry / Below / Not Above or Equal |
| JNC / JAE / JNB | 无进位(无符号大于等于) | CF = 0 | Jump if Not Carry / Above or Equal / Not Below |
| O | 溢出 | OF = 1 | Jump if Overflow |
| JNO | 无溢出 | OF = 0 | Jump if Not Overflow |
| JS | 结果符号为负 | SF = 1 | Jump if Sign (negative) |
| JNS | 结果符号为正 | SF = 0 | Jump if Not Sign (non-negative) |
| JP / JPE | 偶校验(低 8 位含偶数个 1) | PF = 1 | Jump if Parity / Parity Even |
| JNP / JPO | 奇校验 | PF = 0 | Jump if Not Parity / Parity Odd |
2. 无符号数比较跳转(常用于地址、长度等)
| 指令 | 条件(无符号) | 对应的 CMP 结果 |
|---|---|---|
| JA / JNBE | 高于(大于) | CF=0 且 ZF=0 (dest > src) |
| JAE / JNB | 高于或等于 | CF=0 (dest >= src) |
| JB / JNAE | 低于(小于) | CF=1 (dest < src) |
| JBE / JNA | 低于或等于 | CF=1 或 ZF=1 (dest <= src) |
记忆技巧:A = Above(高于),B = Below(低于),E = Equal。
3. 有符号数比较跳转(常用于整数运算)
| 指令 | 条件(有符号) | 标志位组合 |
|---|---|---|
| JG / JNLE | 大于(Greater) | ZF=0 且 SF == OF |
| JGE / JNL | 大于或等于 | SF == OF |
| JL / JNGE | 小于(Less) | SF != OF |
| JLE / JNG | 小于或等于 | ZF=1 或 SF != OF |
记忆技巧:G = Greater(大于),L = Less(小于),E = Equal。有符号比较需同时考虑 SF 和 OF,因为溢出会改变符号位的含义。
四、常见 JCC 使用示例
示例 1:条件判断分支
1 | CMP EAX, 10 |
示例 2:循环计数
1 | MOV ECX, 100 |
示例 3:检查空指针
1 | TEST EAX, EAX |
示例 4:字符串比较(CMPS + REPE)
1 | MOV ECX, 10 |
五、CMP / TEST 与 JCC 综合应用技巧
CMP EAX, 0 等同于 TEST EAX, EAX,两者都可用于判断 EAX 是否为 0,但 TEST 不改变 CF 和 OF(清零),而 CMP 会设置 CF 和 OF(对于减法)。若只需判断零,TEST 更常用且更高效。
条件跳转指令不会修改标志位,它们仅读取标志位。因此,CMP 或 TEST 之后的多个条件跳转可以连续使用,标志位保持不变。
编写汇编代码时,务必区分无符号和有符号比较,选择对应的跳转指令(JA/JB 或 JG/JL),否则会得到错误逻辑。