Part 4. 80x86 指令系统⚓︎
约 5307 个字 432 行代码 预计阅读时间 32 分钟
指令结构⚓︎
- 操作数共有 3 种类型:立即数(常数)(idata)、寄存器 (reg)、变量 / 内存单元 (mem)
- 一条指令中的多个操作数往往是等宽的(可能要用宽度修饰词来强制转换)
注意
- 以下内容可能不太像篇笔记,更像一份 reference,忘记指令功能的时候可以来这里查一下 ~
- 我们约定:
- 用方括号表示可选的前缀或命令
- 用竖线表示实际使用时,在这些指令中选择其一
- 善用 Ctrl+F 查找功能!
数据传送指令⚓︎
通用数据传送指令⚓︎
-
mov dest, src
- 功能:赋值,等价于
dest = src;
- 格式:
dest
可以是寄存器 / 内存,src
可以是立即数 / 寄存器 / 内存,但两者不能同时为内存 - 注:
- 该指令不影响任何标志位
dest
和src
必须等宽- 不能将立即数或段寄存器赋值给段寄存器
- 不能直接对
cs
进行赋值 - (任何指令都)不能引用
ip
和fl
- 功能:赋值,等价于
-
push op
- 功能:将
op
压入栈内 -
等价操作:
-
格式:
op
可以是 16/32 位(字 / 双字)的寄存器 / 内存 - 注:
- 该指令不影响任何标志位
- 不支持 8 位宽度的操作数
- 功能:将
-
pop op
- 功能:将从栈中弹出的数据放入
op
中 -
等价操作:
-
格式:同
push
指令 - 注:同
push
指令
- 功能:将从栈中弹出的数据放入
-
xchg op1, op2
- 功能:交换
op1
与op2
-
等价操作:
-
格式:
op1
、op2
可以是寄存器 / 内存,但不能同时为内存 - 注:
- 该指令不影响任何标志位
- 操作数中不能有段寄存器
- 功能:交换
端口输入输出指令⚓︎
具体请见 Part 2 的端口部分。
地址传送指令⚓︎
-
lea dest, src
- 功能:取变量
src
的偏移地址,并赋值给dest
,等价操作为dest = offset src;
- 格式:
dest
是寄存器,src
是字大小的内存变量
- 功能:取变量
-
lds dest, src
- 功能:取出保存在变量
src
中的远指针,先将远指针的段地址部分赋值给ds
,再将远指针的偏移地址部分赋值给dest
-
等价操作:
-
格式:
dest
是寄存器,src
是双字大小的内存变量
- 功能:取出保存在变量
-
les dest, src
- 功能:取出保存在变量
src
中的远指针,先将远指针的段地址部分赋值给es
,再将远指针的偏移地址部分赋值给dest
-
等价操作:
-
格式:同
lds
指令
- 功能:取出保存在变量
标志寄存器传送指令⚓︎
-
lahf
- 功能:将标志寄存器
fl
的低 8 位赋值给ah
,等价操作为ah = fl & 0FFh
- 格式:
lahf
- 功能:将标志寄存器
-
sahf
- 功能:将
ah
赋值给fl
的低 8 位 -
等价操作:
-
格式:
sahf
- 功能:将
-
pushf
- 功能:将
fl
压入栈中 -
等价操作:
-
格式:
pushf
- 功能:将
-
popf
- 功能:从栈中弹出一个字给
fl
-
等价操作:
-
格式:
popf
- 注:结合
pushf
和popf
指令,可以实现对标志寄存器的访问(修改标签位的值,具体实现可以看“内存”一节上方的代码片段)
- 功能:从栈中弹出一个字给
-
pushfd
- 功能:把
efl
压入栈中 -
等价操作:
-
格式:
pushfd
- 功能:把
-
popfd
- 功能:从栈中弹出一个字给
efl
-
等价操作:
-
格式:
popfd
- 功能:从栈中弹出一个字给
转换指令⚓︎
扩充指令⚓︎
-
cbw
- 功能:将
al
中的值符号扩展至ax
中,即把字节扩充至字 -
等价操作:
-
格式:
cbw
- 功能:将
-
cwd
- 功能:将
ax
中的值符号扩展至dx:ax
(dx
、ax
分别存储一个值的高 16 位和低 16 位) ,即把字扩充至双字 -
等价操作:
-
格式:
cwd
- 功能:将
-
cdq
- 功能:将
eax
中的值符号扩展至edx:eax
(edx
、eax
分别存储一个值的高 32 位和低 32 位) ,即把双字扩充至四字 -
等价操作:
-
格式:
cdq
- 功能:将
-
movsx dest, src
- 功能:将
src
符号扩展至dest
中 -
等价操作:
-
格式:
- 若
dest
是 16 位寄存器,则src
可以是 8 位的寄存器 / 内存 - 若
dest
是 32 位寄存器,则src
可以是 8/16 位的寄存器 / 内存
- 若
- 功能:将
-
movzx dest, src
- 功能:把
src
零扩展至dest
中 -
等价操作:
-
格式:同
movsx
指令
- 功能:把
查表指令⚓︎
xlat
(translate)- 功能:把
byte ptr ds:[bx + al]
的值赋值给al
,等价操作为al = byte ptr ds:[bx + al]
。该指令也称查表指令。 - 格式:
xlat
- 注:在该指令执行前,
ds:bx
应指向表的基地址,al
作为列表的索引
- 功能:把
算术运算指令⚓︎
加法指令⚓︎
-
add dest, src
- 功能:等价于
dest += src;
- 格式:
dest
可以是寄存器 / 内存,src
可以是立即数 / 寄存器 / 内存,但两者不能同时为内存
- 功能:等价于
-
inc op
- 功能:等价于
op++;
- 格式:
op
可以是寄存器 / 内存 - 注:` 该指令不影响进位标志 cf
- 功能:等价于
-
adc dest, src
- 功能:带进位加法 (add with carry),等价于
dest += src + cf;
- 格式:同
add
指令 - 注:可以用该指令实现对更大数据的加法运算(具体可参照下面的例子)
- 功能:带进位加法 (add with carry),等价于
减法指令⚓︎
-
sub dest, src
- 功能:等价于
dest -= src;
- 格式:同
add
指令
- 功能:等价于
-
dec op
- 功能:等价于
op--;
- 格式:同
inc
指令 - 注:同
inc
指令
- 功能:等价于
-
sbb dest, src
- 功能:带借位减法 (subtract with borrow),等价于
dest -= src + cf
- 格式:同
add
指令 - 注:可以用该指令实现对更大数据的减法运算,注意先减低位数据,再减高位数据(具体可参照下面的例子)
- 功能:带借位减法 (subtract with borrow),等价于
-
neg op
- 功能:计算
op
的相反数,等价于op = -op;
- 格式:同
inc
指令 - 注:它会影响 cf、zf、sf 等标志位
- 功能:计算
-
cmp op1, op2
- 功能:比较
op1
和op2
,等价于temp = op1 - op2
- 格式:同
add
指令 - 注:
cmp
指令并不会保存op1 - op2
的差,但会影响标志位cmp
指令后通常会跟随条件跳转指令,跟无符号数、符号数比较相关的jcc
类指令请见条件跳转指令一节- 对于符号数而言,
cmp
不是只根据操作数之差来得到比较结果的,因为符号数的减法存在溢出现象,所以cmp
会综合考虑 sf(符号标志位)和 of(溢出标志位)的值- 当 of = 0 时,sf 反映了
opt1 - opt2
结果的符号位 - 当 of = 1 时,sf 的值与
opt1 - opt2
结果的符号位相反
- 当 of = 0 时,sf 反映了
- 功能:比较
乘法指令⚓︎
-
mul src
- 功能:无符号数乘法
src
为 8 位宽度时:ax = al * src;
src
为 16 位宽度时:dx:ax = ax * src;
src
为 32 位宽度时:edx:eax = eax * src;
- 格式:
src
可以是寄存器 / 内存
- 功能:无符号数乘法
-
imul src
- 功能:符号数乘法,同
mul
指令分为 3 种情况 - 格式:同
mul
指令
- 功能:符号数乘法,同
除法指令⚓︎
-
div src
- 功能:无符号数除法
src
为 8 位宽度时:al = ax / src;
ah = ax % src;
src
为 16 位宽度时:ax = dx:ax / src;
dx = dx:ax % src;
src
为 32 位宽度时:eax = edx:eax / src;
edx = edx:eax % src;
- 格式:同
mul
指令 - 注:若除数为 0,或保存商的寄存器无法容纳商时都会发生除法溢出,此时 CPU 会在除法指令上方插入并执行一条
int 00h
指令,DOS 系统会显示溢出信息并终止程序运行
- 功能:无符号数除法
-
idiv src
- 功能:符号数除法,同
div
指令分为 3 种情况 - 格式:同
mul
指令 - 注:除法溢出的触发和解决方法同
div
指令
- 功能:符号数除法,同
浮点运算指令⚓︎
浮点数的存储格式和求值公式:
浮点数寄存器:
- FPU 有 8 个浮点数寄存器,用于浮点运算,宽度均为 80 位(相当于 C 语言的
long double
) ,名称为st(i)
(\(i \in [0, 7]\)) ,其中st(0)
可简写为st
- 这 8 个浮点数寄存器构成了一个 FPU 堆栈
- 栈顶的浮点数寄存器的物理编号为 TOP(3 位二进制数,位于 FPU 状态寄存器的第 11 至第 13 位
) ,逻辑编号恒为 0 st(i)
的逻辑编号为i
,物理编号p = (TOP + i) % 8
- 栈顶的浮点数寄存器的物理编号为 TOP(3 位二进制数,位于 FPU 状态寄存器的第 11 至第 13 位
浮点运算相关指令:
fadd
、fsub
、fmul
、fdiv
:加减乘除的浮点数版本,具体细节不再赘述-
其他指令:
-
fld op
- 功能:将
op
(浮点数)压入 FPU 堆栈 - 格式:
op
可以是 32 位 /64 位 /80 位的内存,或者浮点数寄存器st(i)
- 功能:将
-
fild op
- 功能:先将
op
(整数)转化为浮点数类型,然后将其压入 FPU 堆栈 - 格式:
op
可以是 32 位 /64 位 /80 位的内存
- 功能:先将
-
fst op
- 功能:把
st(0)
保存到op
中 - 格式:
op
可以是 32 位 /64 位的内存,或者浮点数寄存器st(i)
- 功能:把
-
fstp op
- 功能:先把
st(0)
保存到op
中,再把st(0)
从 FPU 堆栈中弹出 - 格式:同
fld
指令
- 功能:先把
-
十进制调整指令⚓︎
BCD 码(Binary Coded Decimal):用二进制编码的十进制数,分为压缩 BCD 码和非压缩 BCD 码
-
压缩 BCD 码:用 4 个二进制位表示 1 个十进制位
- 因此 8 个二进制位最多表示从 00 到 99 共 100 个压缩 BCD 码
-
非压缩 BCD 码:用 8 个二进制位表示 1 个十进制位
- 8 位上的高 4 位没有意义,可以为任意值
- 因此 16 个二进制位最多表示从 0000h 到 0909h 共 100 个非压缩 BCD 码
-
标志位af与十进制调整指令密切相关
压缩 BCD 码调整指令⚓︎
-
daa
- 功能:在
al
被做加法后将结果al
调整为 BCD 码 -
等价操作:
-
格式:
daa
- 功能:在
-
das
- 功能:在
al
被做减法后将结果al
调整为 BCD 码 -
等价操作:
-
格式:
das
- 功能:在
非压缩 BCD 码调整指令⚓︎
-
aaa
- 功能:加法的 ASCII 调整,在
al
被做加法后连带ah
一起调整ax
为非压缩 BCD 码 -
等价操作:
-
格式:
aaa
- 功能:加法的 ASCII 调整,在
-
aas
- 功能:减法的 ASCII 调整,在
al
被做减法后连带ah
一起调整ax
为非压缩 BCD 码 -
等价操作:
-
格式:
aas
- 功能:减法的 ASCII 调整,在
-
aam
- 功能:乘法的 ASCII 调整,在
al
被做乘法后连带ah
一起调整ax
为非压缩 BCD 码 -
等价操作:
-
格式:
aam
- 功能:乘法的 ASCII 调整,在
-
aad
- 功能:除法的 ASCII 调整,在
al
被做除法后连带ah
一起调整ax
为非压缩 BCD 码 -
等价操作:
-
格式:
aad
- 功能:除法的 ASCII 调整,在
逻辑运算指令⚓︎
-
and dest, src
- 功能:与运算,等价操作为
dest &= src;
- 格式:同
add
指令
- 功能:与运算,等价操作为
-
or dest, src
- 功能:或运算,等价操作为
dest |= src;
- 格式:同
add
指令
- 功能:或运算,等价操作为
-
xor dest, src
- 功能:异或运算,等价操作为
dest ^= src;
- 格式:同
add
指令
- 功能:异或运算,等价操作为
-
not op
- 功能:取反运算,等价操作为
op = ~op;
- 格式:
op
可以是寄存器 / 内存
- 功能:取反运算,等价操作为
-
test dest, src
- 功能:位检测指令,它计算
dest & src
而不保存结果,但会影响状态标志。等价操作为temp = dest & src;
- 格式:同
add
指令
- 功能:位检测指令,它计算
移位指令⚓︎
注意
- 对于所有的移位指令,最后移出去的那一位一定会被保存在进位标志 cf 中
- 若源代码开头有
.386
汇编指示语句,则立即数可以是一个 8 位大小的任意数,否则立即数只能等于 1
-
shl dest, count
- 功能:逻辑左移,等价操作为
dest <<= count & 1Fh
- 格式:
dest
可以是寄存器 / 内存,count
可以是立即数 / 寄存器cl
- 功能:逻辑左移,等价操作为
-
shr dest, count
- 功能:逻辑右移,等价操作为
dest >>= count & 1Fh
- 格式:同
shl
指令 - 注:该指令会在最左边补 0,是一种针对无符号数的右移操作
- 功能:逻辑右移,等价操作为
-
sal dest, count
- 功能:算术左移,等价与
shl
- 格式:同
shl
指令
- 功能:算术左移,等价与
-
sar dest, count
- 功能:算术右移,等价操作类似
shr
,但略有区别,见下面图示 - 格式:同
shl
指令 - 注:该指令会在最左边补符号位(移动负数时补 1,移动正数是补 0
) ,是一种针对符号数的右移操作
- 功能:算术右移,等价操作类似
-
rol dest, count
- 功能:循环左移
-
等价操作:
-
格式:同
shl
指令
-
ror dest, count
- 功能:循环右移
-
等价操作:
-
格式:同
shl
指令
-
rcl dest, count
- 功能:带进位循环左移
-
等价操作:
-
格式:同
shl
指令
-
rcr dest, count
- 功能:带进位循环右移
-
等价操作:
-
格式:同
shl
指令
字符串操作指令⚓︎
与字符串操作指令相关的指令前缀(可用可不用)包括:
rep
:重复repe
:若相等则重复repz
:若结果为 0 则重复repne
:若不相等则重复repnz
:若结果不为 0 则重复
其中第 2、3 个前缀等价,第 4、5 个前缀等价。这些指令前缀有一个共同的限制:重复执行最多cx
次字符串操作。
可以通过设置方向标志 df的值来改变访问(包括复制、比较、读取、写入等操作)字符串的的方向:
- 当源地址 < 目标地址时,访问按反方向,即令 df = 1(可使用
std
指令设置) - 当源地址 > 目标地址时,访问按正方向,即令 df = 0(可使用
cld
指令设置) (大多数情况)
字符串复制指令⚓︎
-
[rep] movsb
- 功能:以字节为单位从 ds:[si] 传送数据到 es:[di],并移动 si、di
-
等价操作:
-
格式:
-
注:
rep movsb
等价于指令
- 该指令一般用于复制长度为(cx 的值)的字符串
-
[rep] movsw
- 功能:以字为单位从 ds:[si] 传送数据到 es:[di],并移动 si、di
- 等价操作:与
movsb
类似,区别在于si
和di
每次增加或减少 2 -
格式:
-
[rep] movsd
- 功能:以双字为单位从 ds:[si] 传送数据到 es:[di],并移动 si、di
- 等价操作:与
movsb
类似,区别在于si
和di
每次增加或减少 4 -
格式:
字符串比较指令⚓︎
-
[repe|repne] cmpsb
- 功能:比较字节ds:[si] 与 es:[di]
-
等价操作:
if (sizeof(operand) == 1) { // cmpsb 指令(1字节) temp = byte ptr ds:[si] - byte ptr es:[di]; // 比较两个字符串的字节数据 old_fl = fl; if (df == 0) { si++; di++; } else { si--; di--; } fl = old_fl; // 寄存器 fl 记录了比较结果,位于其 zf 位上 } else if (operand == 2) { // cmpsw 指令(2字节) temp = word ptr ds:[si] - word ptr es:[di]; // ... } else if (operand == 4) { // cmpsw 指令(4字节) temp = dword ptr ds:[si] - dword ptr es:[di]; // ... }
again: // 循环cx遍 if (cx == 0) { goto done; } if (sizeof(operand) == 1) { temp = byte ptr ds:[si] - byte ptr es:[di]; old_fl = fl; if (df == 0) { si++; di++; } else { si--; di--; } fl = old_fl; } else if (operand == 2) { temp = word ptr ds:[si] - word ptr es:[di]; // ... } else if (operand == 4) { temp = dword ptr ds:[si] - dword ptr es:[di]; // ... } if (zf == 1) { // 若前缀为 repe 且比较结果为相等,则重复,否则结束 if (prefix == repe) goto again; else goto done; } else if (zf == 0) { // 若前缀为 repne 且比较结果为不等,则重复,否则结束 if (prefix == repne) goto again; else goto done; } done:
-
格式:
-
[repe|repne] cmpsw
- 功能:比较字ds:[si] 与 es:[di]
- 等价操作:见
cmpsb
指令 - 格式:
-
[repe|repne] cmpsd
- 功能:比较双字ds:[si] 与 es:[di]
- 等价操作:见
cmpsb
指令 - 格式:
搜索字符串指令⚓︎
-
[repe|repne] scasb
- 功能:比较 al 与 es:[di](1字节
) ,即计算 al - es:[di],丢弃结果保留状态位(类似cmp
指令) ,并移动 di -
等价操作:
if (sizeof(operand) == 1) { // scasb 指令(1字节) temp = al - byte ptr es:[di]; // 比较 old_fl = fl; if (df == 0) di++; else di--; fl = old_fl; } else if (sizeof(operand) == 2) { // scasw 指令(2字节) temp = ax - word ptr es:[di]; // ... } else if (sizeof(operand) == 4) { // scasd 指令(4字节) temp = eax - dword ptr es:[di]; // ... }
again: if (cx == 0) goto done; if (sizeof(operand) == 1) { temp = al - byte ptr es:[di]; old_fl = fl; if (df == 0) di++; else di--; cx--; fl = old_fl; } else if (sizeof(operand) == 2) { temp = ax - word ptr es:[di]; // ... } else if (sizeof(operand) == 4) { temp = eax - dword ptr es:[di]; // ... } if (zf == 1) { if (prefix == repe) // 相等重复 goto again; else goto done; } else if (zf == 0) { if (prefix == repne) // 不等重复 goto again; else goto done; } done:
-
格式:
- 功能:比较 al 与 es:[di](1字节
-
[repe|repne] scasw
- 功能:比较 ax 与 es:[di](1字
) ,即计算 ax - es:[di],丢弃结果保留状态位,并移动 di - 等价操作:见
scasb
指令 - 格式:
- 功能:比较 ax 与 es:[di](1字
-
[repe|repne] scasd
- 功能:比较 eax 与 es:[di](1双字
) ,即计算 eax - es:[di],丢弃结果保留符号位,并移动 di - 等价操作:见
scasb
指令 - 格式:
- 功能:比较 eax 与 es:[di](1双字
写入字符串指令⚓︎
-
[rep] stosb
- 功能:把 al 内的字节数据存入 es:[di] 中,并移动 di
-
等价操作:
again: if (cx == 0) goto done; if (sizeof(operand) == 1) { // stosb 指令(1字节) byte ptr es:[di] = al; // 写入 old_fl = fl; if (df == 0) di++; else di--; fl = old_fl; } else if (sizeof(operand) == 2) { // stosw 指令(2字节) word ptr es:[di] = ax; // ... } else if (sizeof(operand) == 4) { // stosd 指令(4字节) dword ptr es:[di] = eax; // ... } cx--; goto again; done:
-
格式:
-
[rep] stosw
- 功能:把 ax 内的字数据存入 es:[di] 中,并移动 di
- 等价操作:见
stosb
指令 - 格式:
-
[rep] stosd
- 功能:把 eax 内的双字数据存入 es:[di] 中,并移动 di
- 等价操作:见
stosb
指令 - 格式:
读取字符串指令⚓︎
-
lodsb
- 功能:从 ds:[si] 读取一个字节存入 al,并移动 si
- 等价操作:
if (sizeof(operand) == 1) { // lodsb 指令(1字节) al = byte ptr ds:[si]; // 读取 old_fl = fl; if (df == 0) si++; else si--; fl = old_fl; } else if (sizeof(operand) == 2) { // lodsw 指令(2字节) ax = word ptr ds:[si]; // ... } else if (sizeof(operand) == 4) { // lodsd 指令(4字节) eax = dword ptr ds:[si]; // ... }
- 格式:
-
lodsw
- 功能:从 ds:[si] 读取一个字存入 ax,并移动 si
- 等价操作:见
lodsb
指令 - 格式:
-
lodsd
- 功能:从 ds:[si] 读取一个双字存入 eax,并移动 si
- 等价操作:见
lodsb
指令 - 格式:
控制转移指令⚓︎
转移行为分为两类:
- 段内转移:只修改 ip
- 短转移:ip 的修改范围为 -128~127
- 近转移:ip 的修改范围为 -32768~32767
- 段间转移:同时修改 cs 和 ip
无条件跳转指令⚓︎
省流
大多数情况下,无条件跳转指令可一律写成jmp label
的形式。
-
jmp short lebel|offset
- 功能:短跳,跳到目标标号
label
或偏移地址offset
- 等价操作:
delta = idata8 delta |= (0 - ((delta & 80h) >> 7 & 1)) << 8; // 符号扩展至16位 ip += 2 + delta; // dest = cs + ip
- 格式:
- 注:
- 该指令的机器码为 2 字节:
0E8h, idata8
,其中idata8
是一个 8 位符号数,表示短跳的跳转距离,取值范围为 [-128, 127],超过该范围编译时会报错 - 短跳指令转化为机器码后,跳转距离
idata8
按以下公式计算:\(\mathtt{idata8 = dest - (\$ + 2)}\),其中 \(\mathtt{\$}\) 表示当前这条跳转指令自身的偏移地址 - 编译器会自动判断跳转距离,因此
short
修饰可省略不写
- 该指令的机器码为 2 字节:
- 功能:短跳,跳到目标标号
-
jmp near ptr dest
- 功能:近跳,跳到目标标号
dest
或 16 位的寄存器 / 变量 - 等价操作:
- 格式:
- 注:
- 该指令的机器码为 3 字节:
0E9h, idata16_L8, idata16_H8
,后两者分别是 16 位符号数idata16
的低 8 位和高 8 位,表示近跳的跳转距离,故取值范围为 [-32768, 32767] - 近跳指令转化为机器码后,跳转距离
idata16
按以下公式计算:\(\mathtt{idata16 = dest - (\$ + 3)}\),其中 \(\mathtt{\$}\) 表示当前这条跳转指令自身的偏移地址 - 编译器会自动判断跳转距离,因此
near ptr
修饰可省略不写 - 该指令还可以将 16 位寄存器或内存作为跳转目标地址
- 该指令的机器码为 3 字节:
- 功能:近跳,跳到目标标号
-
jmp far ptr dest
- 功能:远跳,跳到 32 位目标地址
dest
或变量 - 等价操作:
- 格式:
- 注:
- 该指令的机器码为 5 字节:
0E9h, idata32_L16, idata32_H16
,后两者分别是 32 位符号数idata32
的低 16 位和高 16 位,表示远跳的跳转目标地址,且低 16 位表示偏移地址,高 16 位表示段地址 - 编译器会自动判断跳转距离,因此
far ptr
修饰可省略不写 - 该指令的
dest
必须是已定义的标号,不能是某个常数 - 该指令还可以将 32 位内存作为跳转目标地址
- 该指令同时修改了 cs 和 ip 的值,而前两条指令只修改了 ip 的值
- 该指令的机器码为 5 字节:
- 功能:远跳,跳到 32 位目标地址
条件跳转指令⚓︎
jcc:
- 功能:条件跳转
- 注:
- jcc 类指令通常配合
cmp
指令一起食用 - jcc 实际上指的是一类指令(具体见下面的表格
) ,并没有一个名为jcc
的指令 - jcc 类指令的跳转距离宽度均为 1 字节,因此都属于短跳指令
- jcc 类指令通常配合
- jcc 类指令汇总:
jcc 指令 | 含义 | 跳转条件 | 说明 |
---|---|---|---|
ja | 无符号大于则跳 | cf == 0 && zf == 0 |
|
jae | 无符号大于等于则跳 | cf == 0 |
|
jb | 无符号小于则跳 | cf == 1 |
|
jbe | 无符号小于等于则跳 | cf == 1 || zf == 1 |
|
je | 相等则跳 | zf == 1 |
|
jne | 不等则跳 | zf == 0 |
|
jg | 符号大于则跳 | sf == of && zf == 0 |
|
jge | 符号大于等于则跳 | sf == of |
|
jl | 符号小于则跳 | sf != of |
|
jle | 符号小于等于则跳 | sf != of || zf == 1 |
|
jc | 有进位则跳 | cf == 1 |
|
jnc | 无进位则跳 | cf == 0 |
|
jz | 有零标志则跳 | zf == 1 |
|
jnz | 无零标志则跳 | zf == 0 |
|
js | 有符号位则跳 | sf == 1 |
|
jns | 无符号位则跳 | sf == 0 |
|
jo | 有溢出则跳 | of == 1 |
|
jno | 无溢出则跳 | of == 0 |
|
jp | 有奇偶校验标志则跳 | pf == 1 |
|
jnp | 无奇偶校验标志则跳 | pf == 0 |
|
jcxz | cx=0 则跳 | cx == 0 |
配合loop 指令(因为loop 会改变cx 的值) |
jecxz | ecx=0 则跳 | ecx == 0 |
循环指令⚓︎
-
loop dest
- 功能:循环
- 等价操作:
- 格式:
-
注:
- loop 的跳转距离为 1 字节,即跳转范围为 [-128, 127]
- 需要注意 loop 先会对 cx 减 1 然后再与 0 判断,所以在 loop 前令 cx=0 反而会循环最大次数 10000h 次
- 常用模板:
-
loopz dest
- 功能:若零标志位
zf == 0
且cx != 0
则循环 - 等价操作:
- 格式:
- 注:
- 用法与
loop
类似 loopz
=loope
- 用法与
- 功能:若零标志位
-
loopnz dest
- 功能:若零标志位
zf != 0
且cx != 0
则循环 - 等价操作:
- 格式:
- 注:
- 用法与
loop
类似 loopnz
=loopne
- 用法与
- 功能:若零标志位
过程指令⚓︎
-
call near ptr label
- 功能:近调用(用于调用相同段的函数
) ,目标地址为标号label
对应的地址 - 等价操作:
- 格式:
-
注:
- 等价指令为:
- 该指令的机器码为 3 字节:
0E8h, idata16_L8, idata16_H8
,后两者分别是 16 位符号数idata16
的低 8 位和高 8 位,表示近调用的跳转距离,取值范围为 [-32768, 32767] - 编译器会自动判断跳转距离,因此
near ptr
修饰可省略 - 短跳指令转化为机器码后,跳转距离
idata16
按以下公式计算:\(idata16 = dest - (\$ + 3)\),其中 \(\$\) 表示当前这条跳转指令自身的偏移地址 - 该指令还可以将 16 位寄存器或内存作为跳转目标地址
- 功能:近调用(用于调用相同段的函数
-
retn [idata16]
- 功能:近返回
- 等价操作:
- 格式:
-
注:
- 等价指令为
pop ip
(但在实际编程中不能这么用) ,即从栈中获取 ip 以跳转到被保存的指令位置上 - 在用标号定义的函数里,或者用下列方法定义的函数中,
retn
一般简记为ret
- 等价指令为
-
call far ptr label
- 功能:远调用(用于调用不同段的函数
) ,目标地址为标号label
对应的地址 - 等价操作:
- 格式:
-
注:
- 等价指令为:
- 该指令的机器码为 5 字节:
9Ah, idata32_L16, idata32_H16
,后两者分别是 32 位符号数idata32
的低 16 位和高 16 位,表示远调用的目标地址 - 该指令的
label
必须是已定义的标号,不能是某个常数 - 该指令还可以将 32 位内存作为跳转目标地址
- 功能:远调用(用于调用不同段的函数
-
retf [idata16]
- 功能:远返回
- 等价操作:
back_ip = word ptr ss:[sp]; back_cs = word ptr ss:[sp+2]; sp += 4; if (idata16) sp += idata16; ip = back_ip; cs = back_cs;
- 格式:
-
注:
- 等价指令为:
即从栈中获取 ip 和 cs,以跳转到被保存的更远的指令位置上
中断指令⚓︎
具体请见 Part 5 的中断部分。
评论区