Part 4. 80x86 指令系统
指令结构
!!! card ""
<div style="text-align: center; font-size: 1.3em">
指令 = 操作码 + 操作数
</div>
- 操作数共有3种类型:立即数(常数)(idata)、寄存器(reg)、变量/内存单元(mem)
- 一条指令中的多个操作数往往是等宽的(可能要用宽度修饰词来强制转换)
!!! info "注意"
- 以下内容可能不太像篇笔记,更像一份reference,忘记指令功能的时候可以来这里查一下~
- 我们约定:
- 用方括号表示可选的前缀或命令
- 用竖线表示实际使用时,在这些指令中选择其一
- 善用Ctrl+F查找功能!
数据传送指令
通用数据传送指令
mov dest, src- 功能:赋值,等价于
dest = src; - 格式:
dest可以是寄存器/内存,src可以是立即数/寄存器/内存,但两者不能同时为内存 - 注:
- 该指令不影响任何标志位
dest和src必须等宽- 不能将立即数或段寄存器赋值给段寄存器
- 不能直接对
cs进行赋值 - (任何指令都)不能引用
ip和fl
- 功能:赋值,等价于
push op- 功能:将
op压入栈内 - 等价操作:
cif (sizeof(op) == 2) { // 若op的宽度为2字节 sp -= 2; word ptr ss:[sp] = op; } else if(sizeof(op) == 4) { // 若op的宽度为4字节 sp -= 4; dword ptr ss:[sp] = op; }- 格式:
op可以是16/32位(字/双字)的寄存器/内存 - 注:
- 该指令不影响任何标志位
- 不支持8位宽度的操作数
- 功能:将
pop op- 功能:将从栈中弹出的数据放入
op中 - 等价操作:
cif (sizeof(op) == 2) { // 若op的宽度为2字节 op = word ptr ss:[sp]; sp += 2; } else if(sizeof(op) == 4) { // 若op的宽度为4字节 op = dword ptr ss:[sp]; sp += 4; }- 格式:同
push指令 - 注:同
push指令
- 功能:将从栈中弹出的数据放入
xchg op1, op2- 功能:交换
op1与op2 - 等价操作:
ctemp = op1; op1 = op2; op2 = temp;- 格式:
op1、op2可以是寄存器/内存,但不能同时为内存 - 注:
- 该指令不影响任何标志位
- 操作数中不能有段寄存器
- 功能:交换
端口输入输出指令
具体请见Part 2的端口部分。
地址传送指令
lea dest, src- 功能:取变量
src的偏移地址,并赋值给dest,等价操作为dest = offset src; - 格式:
dest是寄存器,src是字大小的内存变量
- 功能:取变量
lds dest, src- 功能:取出保存在变量
src中的远指针,先将远指针的段地址部分赋值给ds,再将远指针的偏移地址部分赋值给dest - 等价操作:
cdest = word ptr [src]; ds = word ptr [src + 2];- 格式:
dest是寄存器,src是双字大小的内存变量
- 功能:取出保存在变量
les dest, src- 功能:取出保存在变量
src中的远指针,先将远指针的段地址部分赋值给es,再将远指针的偏移地址部分赋值给dest - 等价操作:
cdest = word ptr [src]; es = word ptr [src + 2];- 格式:同
lds指令
- 功能:取出保存在变量
标志寄存器传送指令
lahf- 功能:将标志寄存器
fl的低8位赋值给ah,等价操作为ah = fl & 0FFh - 格式:
lahf
- 功能:将标志寄存器
sahf- 功能:将
ah赋值给fl的低8位 - 等价操作:
cfl = (fl & 0FF00h) | 2 | (ah & 0D5h) // fl & 0FF00h 保留了fl的高8位,去掉了低8位 // 2h = 10,因此第1位恒为1(保留位) // 0D5h = 11010101,实际上只取了ah的第0、2、4、6、7位,其余位是保留位,除了第1位外其余位均为0- 格式:
sahf
- 功能:将
pushf- 功能:将
fl压入栈中 - 等价操作:
csp -= 2; word ptr ss:[sp] = fl;- 格式:
pushf
- 功能:将
popf- 功能:从栈中弹出一个字给
fl - 等价操作:
cfl = word ptr ss:[sp]; sp += 2;- 格式:
popf - 注:结合
pushf和popf指令,可以实现对标志寄存器的访问(修改标签位的值,具体实现可以看“内存”一节上方的代码片段)
- 功能:从栈中弹出一个字给
pushfd- 功能:把
efl压入栈中 - 等价操作:
csp -= 4; dword ptr ss:[sp] = efl;- 格式:
pushfd
- 功能:把
popfd- 功能:从栈中弹出一个字给
efl - 等价操作:
cefl = dword ptr ss:[sp]; sp += 4;- 格式:
popfd
- 功能:从栈中弹出一个字给
转换指令
扩充指令
cbw- 功能:将
al中的值符号扩展至ax中,即把字节扩充至字 - 等价操作:
cah = 0 - ((al & 80h) != 0); // al & 80h 取 al 最高位(符号位) // 如果该位是0,逻辑运算结果为0,ah = 00000000 // 如果该位是1,逻辑运算结果为1,ah = -1,补码为11111111- 格式:
cbw
- 功能:将
cwd- 功能:将
ax中的值符号扩展至dx:ax(dx、ax分别存储一个值的高16位和低16位),即把字扩充至双字 - 等价操作:
cdx = 0 - ((ax & 8000h) != 0);- 格式:
cwd
- 功能:将
cdq- 功能:将
eax中的值符号扩展至edx:eax(edx、eax分别存储一个值的高32位和低32位),即把双字扩充至四字 - 等价操作:
cedx = 0 - ((eax & 80000000h) != 0);- 格式:
cdq
- 功能:将
movsx dest, src- 功能:将
src符号扩展至dest中 - 等价操作:
cdest = src; dest &= (1 << sizeof(src) * 8) - 1; if (src & (1 << sizeof(src) * 8 - 1)) { dest |= ((1 << (sizeof(dest) - sizeof(src)) * 8) - 1) << sizeof(src) * 8 } // 在C语言中,乘法优先级高于移位 // sizeof(var) * 8 表示var的位宽 // 第2行:将dest的高位清零 // 第3行:判断src符号位是否为1 // 第4行:若是,则将dest的高位 置1- 格式:
- 若
dest是16位寄存器,则src可以是8位的寄存器/内存 - 若
dest是32位寄存器,则src可以是8/16位的寄存器/内存
- 若
- 功能:将
movzx dest, src- 功能:把
src零扩展至dest中 - 等价操作:
cdest = src; dest &= (1 << sizeof(src) * 8) - 1; // 就是movsx等价操作的前两行- 格式:同
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指令 - 注:可以用该指令实现对更大数据的加法运算(具体可参照下面的例子)
??? example "例子"
计算 `2F365h` + `5E024h`,其中`dx`存放结果的高16位,`ax`存放结果的低16位。 ```asm mov ax, 0F365h ; ax = 2F365h的低16位 mov dx, 2 ; dx = 2F365h的高16位 add ax, 0E024h ; 两数的低16位相加,产生进位,使得ax = 0D389, CF = 1 adc dx, 5 ; 两数的高16位相加,再加上低16位加法的进位,因此dx = 2 + 5 + 1 = 8 ; dx: ax = 8D389h ```- 功能:带进位加法(add with carry),等价于
减法指令
sub dest, src- 功能:等价于
dest -= src; - 格式:同
add指令
- 功能:等价于
dec op- 功能:等价于
op--; - 格式:同
inc指令 - 注:同
inc指令
- 功能:等价于
sbb dest, src- 功能:带借位减法(subtract with borrow),等价于
dest -= src + CF - 格式:同
add指令 - 注:可以用该指令实现对更大数据的减法运算,注意先减低位数据,再减高位数据(具体可参照下面的例子)
??? example "例子"
计算 `127546h` - `109428h`,其中`dx`存放结果的高16位,`ax`存放结果的低16位。 ```asm mov ax, 7546h ; ax = 127546h的低16位 mov dx, 12h ; dx = 127546h的高16位 sub ax, 9428h ; 两数的低16位相减,需要借位,此时ax = 0E11Eh, CF = 1 sbb dx, 10h ; 两数的高16位相减,再减去低16位减法的借位,因此dx = 12h - 10h - 1 = 1 ; dx: ax = 1E11Eh ```- 功能:带借位减法(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指令
- 功能:符号数除法,同
浮点运算指令
浮点数的存储格式和求值公式:
float(32位)- 存储格式
- 求值公式:
  double(64位)- 存储格式
- 求值公式: s
  long double(80位)- 存储格式:1位符号,15位指数,64位尾数
- 求值公式:
 
浮点数寄存器:
- FPU有8个浮点数寄存器,用于浮点运算,宽度均为80位(相当于C语言的
long double),名称为st(i)(),其中 st(0)可简写为st - 这8个浮点数寄存器构成了一个FPU堆栈
- 栈顶的浮点数寄存器的物理编号为TOP(3位二进制数,位于FPU状态寄存器的第11至第13位),逻辑编号恒为0
st(i)的逻辑编号为i,物理编号p = (TOP + i) % 8
浮点运算相关指令:
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码 - 等价操作:
cold_cf = cf; // 先转化低4位 if (af == 1 || (al & 0Fh) >= 0Ah) { al = al + 6; af = 1; } else { af = 0; } // 再转化为高4位 if (old_cf == 1 || (al & 0F0h) >= 0A0h) { al = al + 60h; cf = 1; } else { cf = 0; }- 格式:
daa
- 功能:在
das- 功能:在
al被做减法后将结果al调整为BCD码 - 等价操作:
cold_cf = cf; old_al = al; // 先转化低4位 if (af == 1 || (al & 0Fh) >= 0Ah) { al = al - 6; af = 1; } else { af = 0; } // 再转化为高4位 if (old_cf == 1 || old_al >= 99h) { al = al - 60h; cf = 1; } else { cf = 0; }- 格式:
das
- 功能:在
非压缩BCD码调整指令
aaa- 功能:加法的ASCII调整,在
al被做加法后连带ah一起调整ax为非压缩BCD码 - 等价操作:
cif (af == 1 || (al & 0Fh) >= 0Ah) { al += 6; ah++; af = 1; cf = 1; } else { af = 0; cf = 0; } al &= 0Fh;- 格式:
aaa
- 功能:加法的ASCII调整,在
aas- 功能:减法的ASCII调整,在
al被做减法后连带ah一起调整ax为非压缩BCD码 - 等价操作:
cif (af == 1 || (al & 0Fh) >= 0Ah) { al -= 6; ah--; af = 1; cf = 1; } else { af = 0; cf = 0; } al &= 0Fh;- 格式:
aas
- 功能:减法的ASCII调整,在
aam- 功能:乘法的ASCII调整,在
al被做乘法后连带ah一起调整ax为非压缩BCD码 - 等价操作:
cah = al / 10; al = al % 10;- 格式:
aam
- 功能:乘法的ASCII调整,在
aad- 功能:除法的ASCII调整,在
al被做除法后连带ah一起调整ax为非压缩BCD码 - 等价操作:
cal = (ah * 10 + al) & 0FFh; ah = 0;- 格式:
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指令
- 功能:位检测指令,它计算
移位指令
!!! warning "注意"
- 对于所有的移位指令,最后移出去的那一位一定会被保存在进位标志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- 功能:循环左移
- 等价操作:
ccount &= 1Fh; dest = (dest << count) | ((dest >> sizeof(dest) * 8 - count) & ((1 << count) - 1));- 格式:同
shl指令
  ror dest, count- 功能:循环右移
- 等价操作:
ccount &= 1Fh; L = sizeof(dest) * 8 - count; dest = ((dest >> count) & ((1 << L) - 1)) | (dest << L);- 格式:同
shl指令
  rcl dest, count- 功能:带进位循环左移
- 等价操作:
ccount &= 1Fh; for (i = 0; i < count; i++) { old_cf = cf; msb = (dest >> sizeof(dest) * 8 - 1) & 1; dest = dest << 1 | old_cf; cf = msb; }- 格式:同
shl指令
  rcr dest, count- 功能:带进位循环右移
- 等价操作:
ccount &= 1Fh; for (i = 0; i < count; i++) { old_cf = cf; lsb = dest & 1; L = sizeof(dest) * 8 - 1; dest = (dest >> 1) & ((1 << L) - 1); cf = lsb; }- 格式:同
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
等价操作:
=== "movsb"
```c byte ptr es:[di] = byte ptr ds:[si]; // 标志位 DF 表示字符串访问的方向 if (df == 0) { // 0 表示正方向 si++; // 令 ds:si 指向下一字节 di++; // 令 es:di 指向下一字节 } else { // 1 表示反方向 si--; // 令 ds:si 指向上一字节 di--; // 令 es:di 指向上一字节 } ```=== "rep movsb"
```c again: // 循环 cx 遍 if (cx == 0) goto done; byte ptr es:[di] = byte ptr ds:[si]; if (df == 0) { si++; di++; } else { si--; di--; } cx--; goto again; done: ```格式:
asmmovsb rep movsb注:
rep movsb等价于指令
asms: movsb loop s- 该指令一般用于复制长度为(cx的值)的字符串
[rep] movsw功能:以字为单位从ds:[si]传送数据到es:[di],并移动si、di
等价操作:与
movsb类似,区别在于si和di每次增加或减少2格式:
asmmovsw rep movsw
[rep] movsd功能:以双字为单位从ds:[si]传送数据到es:[di],并移动si、di
等价操作:与
movsb类似,区别在于si和di每次增加或减少4格式:
asmmovsd rep movsd
字符串比较指令
[repe|repne] cmpsb功能:比较字节ds:[si]与es:[di]
等价操作:
=== "cmpsb"
```c 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]; // ... } ```=== "repe|repne cmpsb"
```c 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: ```格式:
asmcmpsb repe cpmsb repne cpmsb[repe|repne] cmpsw- 功能:比较字ds:[si]与es:[di]
- 等价操作:见
cmpsb指令 - 格式:
asmcmpsw repe cpmsw repne cpmsw[repe|repne] cmpsd- 功能:比较双字ds:[si]与es:[di]
- 等价操作:见
cmpsb指令 - 格式:
asmcmpsd repe cpmsd repne cpmsd
搜索字符串指令
[repe|repne] scasb功能:比较al与es:[di](1字节),即计算al - es:[di],丢弃结果保留符号位,并移动di
等价操作:
=== "scasb"
```c 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]; // ... } ```=== "repe|repne scasb"
```c 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: ```格式:
asmscasb repe scasb repne scasb[repe|repne] scasw- 功能:比较ax与es:[di](1字),即计算ax - es:[di],丢弃结果保留符号位,并移动di
- 等价操作:见
scasb指令 - 格式:
asmscasw repe scasw repne scasw[repe|repne] scasd- 功能:比较eax与es:[di](1双字),即计算eax - es:[di],丢弃结果保留符号位,并移动di
- 等价操作:见
scasb指令 - 格式:
asmscasd repe scasd repne scasd
写入字符串指令
[rep] stosb功能:把al内的字节数据存入es:[di]中,并移动di
等价操作:
=== "stosb"
```c 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; // ... } ```=== "rep stosb"
```c 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: ```格式:
asmstosb rep stosb[rep] stosw- 功能:把ax内的字数据存入es:[di]中,并移动di
- 等价操作:见
stosb指令 - 格式:
asmstosw rep stosw[rep] stosd- 功能:把eax内的双字数据存入es:[di]中,并移动di
- 等价操作:见
stosb指令 - 格式:
asmstosd rep stosd
读取字符串指令
lodsb- 功能:从ds:[si]读取一个字节存入al,并移动si
- 等价操作:
cif (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]; // ... }- 格式:
lodsblodsw- 功能:从ds:[si]读取一个字存入ax,并移动si
- 等价操作:见
lodsb指令 - 格式:
lodswlodsd- 功能:从ds:[si]读取一个双字存入eax,并移动si
- 等价操作:见
lodsb指令 - 格式:
lodsd
控制转移指令
转移行为分为两类:
- 段内转移:只修改ip
- 短转移:ip的修改范围为-128~127
- 近转移:ip的修改范围为-32768~32767
- 段间转移:同时修改cs和ip
无条件跳转指令
!!! tip "省流"
大多数情况下,无条件跳转指令可一律写成`jmp label`的形式。
jmp short lebel|offset- 功能:短跳,跳到目标标号
label或偏移地址offset - 等价操作:
cdelta = idata8 delta |= (0 - ((delta & 80h) >> 7 & 1)) << 8; // 符号扩展至16位 ip += 2 + delta; // dest = cs + ip- 格式:
asmjmp short label|offset- 注:
- 该指令的机器码为2字节:
0E8h, idata8,其中idata8是一个8位符号数,表示短跳的跳转距离,取值范围为[-128, 127],超过该范围编译时会报错 - 短跳指令转化为机器码后,跳转距离
idata8按以下公式计算:,其中 表示当前这条跳转指令自身的偏移地址 - 编译器会自动判断跳转距离,因此
short修饰可省略不写
- 该指令的机器码为2字节:
- 功能:短跳,跳到目标标号
jmp near ptr dest- 功能:近跳,跳到目标标号
dest或16位的寄存器/变量 - 等价操作:
cdelta = idata16 ip += 3 + delta; // dest = cs + ip- 格式:
asmjmp near ptr label jmp reg16 jmp mem16- 注:
- 该指令的机器码为3字节:
0E9h, idata16_L8, idata16_H8,后两者分别是16位符号数idata16的低8位和高8位,表示近跳的跳转距离,故取值范围为[-32768, 32767] - 近跳指令转化为机器码后,跳转距离
idata16按以下公式计算:,其中 表示当前这条跳转指令自身的偏移地址 - 编译器会自动判断跳转距离,因此
near ptr修饰可省略不写 - 该指令还可以将16位寄存器或内存作为跳转目标地址
- 该指令的机器码为3字节:
- 功能:近跳,跳到目标标号
jmp far ptr dest- 功能:远跳,跳到32位目标地址
dest或变量 - 等价操作:
cip = idata32_L16 cs = idata32_H16 // dest = idata32- 格式:
asmjmp far ptr dest jmp mem32- 注:
- 该指令的机器码为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 | |
| je | 相等则跳 | zf == 1 | |
| jne | 不等则跳 | zf == 0 | |
| jg | 符号大于则跳 | sf == of && zf == 0 | |
| jge | 符号大于等于则跳 | sf == of | |
| jl | 符号小于则跳 | sf != of | |
| jle | 符号小于等于则跳 | `sf != of | |
| 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- 功能:循环
- 等价操作:
ccx--; if (cx != 0) ip = dest;- 格式:
asmloop dest注:
- loop的跳转距离为1字节,即跳转范围为[-128, 127]
- 需要注意loop先会对cx减1然后再与0判断,所以在loop前令cx=0反而会循环最大次数10000h次
- 常用模板:
asmmov cx, loop_time s: ;statements loop s
loopz dest- 功能:若
dest等于0且cx不等于0则循环 - 等价操作:
cold_fl = fl; cx--; fl = old_fl if (zf == 1 && cx != 0) ip = dest;- 格式:
asmloopz dest- 注:
- 用法与
loop类似 loopz=loope
- 用法与
- 功能:若
loopnz dest- 功能:若
dest不等于0且cx不等于0则循环 - 等价操作:
cold_fl = fl; cx--; fl = old_fl if (zf == 0 && cx != 0) ip = dest;- 格式:
asmloopnz dest- 注:
- 用法与
loop类似 loopnz=loopne
- 用法与
- 功能:若
过程指令
call near ptr label- 功能:近调用(用于调用相同段的函数),目标地址为标号
label对应的地址 - 等价操作:
cback_addr = ip + 3; sp -= 2; word ptr ss:[sp] = back_addr; delta = idata16; ip = back_addr + delta;- 格式:
asmcall near ptr label call reg16 call mem16注:
- 等价指令为:
asmpush ip ; 这个ip实际上指的是call指令后面那条指令 jmp near ptr label- 该指令的机器码为3字节:
0E8h, idata16_L8, idata16_H8,后两者分别是16位符号数idata16的低8位和高8位,表示近调用的跳转距离,取值范围为[-32768, 32767] - 编译器会自动判断跳转距离,因此
near ptr修饰可省略 - 短跳指令转化为机器码后,跳转距离
idata16按以下公式计算:,其中 表示当前这条跳转指令自身的偏移地址 - 该指令还可以将16位寄存器或内存作为跳转目标地址
- 功能:近调用(用于调用相同段的函数),目标地址为标号
retn [idata16]- 功能:近返回
- 等价操作:
cback_addr = word ptr ss:[sp]; sp += 2; if (idata16) sp += idata16; ip = back_addr;- 格式:
asmretn retn idata16注:
- 等价指令为
pop ip(但在实际编程中不能这么用),即从栈中获取ip以跳转到被保存的指令位置上 - 在用标号定义的函数里,或者用下列方法定义的函数中,
retn一般简记为ret
asmfun_name proc ; instructions func_name endp ; or fun_name proc near ; instructions func_name endp- 等价指令为
call far ptr label- 功能:远调用(用于调用不同段的函数),目标地址为标号
label对应的地址 - 等价操作:
csp -= 4; word ptr ss:[sp] = ip + 5; word ptr ss:[sp+2] = cs; ip = idata32_L16; cs = idata32_H16;- 格式:
asmcall far ptr label call mem32注:
- 等价指令为:
asmpush cs push ip jmp far ptr label- 该指令的机器码为5字节:
9Ah, idata32_L16, idata32_H16,后两者分别是32位符号数idata32的低16位和高16位,表示远调用的目标地址 - 该指令的
label必须是已定义的标号,不能是某个常数 - 该指令还可以将32位内存作为跳转目标地址
- 功能:远调用(用于调用不同段的函数),目标地址为标号
retf [idata16]- 功能:远返回
- 等价操作:
cback_ip = word ptr ss:[sp]; back_cs = word ptr ss:[sp+2]; sp += 4; if (idata16) sp += idata16; ip = back_ip; cs = back_cs;- 格式:
asmretf retf idata16注:
- 等价指令为:
asmpop ip pop cs即从栈中获取ip和cs,以跳转到被保存的更远的指令位置上
中断指令
具体请见Part 5的中断部分。