跳转至

6 80x86 指令系统⚓︎

4715 个字 451 行代码 预计阅读时间 29 分钟

指令结构⚓︎

指令 = 操作码 + 操作数

操作数共有 3 种类型:立即数(常数)(idata)、寄存器 (reg)、变量 (mem)

注意

我们约定:

  • 用方括号表示可选的前缀或命令
  • 用竖线表示实际使用时,在这些指令中选择其一(或)

数据传送指令⚓︎

通用数据传送指令⚓︎

mov dest, src
  • 功能:赋值,等价于dest = src;
  • 格式:dest可以是寄存器 / 内存,src可以是立即数 / 寄存器 / 内存,但两者不能同时为内存
  • 注:
    • 该指令不影响任何标志位
    • destsrc必须等宽
    • 不能将立即数或段寄存器赋值给段寄存器
    • 不能直接对cs进行赋值
    • (任何指令都)不能引用ipfl

push op
  • 功能:将op压入栈内
  • 等价操作:

    if (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
  • 等价操作:

    if (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
  • 功能:交换op1op2
  • 等价操作:

    temp = op1;
    op1  = op2;
    op2  = temp;
    
  • 格式:op1op2可以是寄存器 / 内存,但不能同时为内存

  • 注:
    • 该指令不影响任何标志位
    • 操作数中不能有段寄存器

输入输出指令⚓︎

in al, port
  • 功能:从port号端口读取一个字节并保存到al中,等价操作为al = [port];,其中[port]表示port号端口的值
  • 格式:port可以是立即数或寄存器dx
  • 注:立即数的取值范围为 [00h, 0FFh],寄存器的取值范围为 [0000h, 0FFFFh],使用时要注意

out port, al
  • 功能:把al的值写入port号端口,等价操作为[port] = al;
  • 格式:同in指令
  • 注:同in指令

地址传送指令⚓︎

lea dest, src
  • 功能:取变量src的偏移地址,并赋值给dest,等价操作为dest = offset src;
  • 格式:src可以是寄存器 / 内存

lds dest, src
  • 功能:取出保存在变量src中的远指针,先将远指针的段地址部分赋值给ds,再将远指针的偏移地址部分赋值给dest
  • 等价操作:

    dest = word ptr [src];
    ds   = word ptr [src + 2];
    
  • 格式:dest可以是寄存器或 32 位内存

les dest, src
  • 功能:取出保存在变量src中的远指针,先将远指针的段地址部分赋值给es,再将远指针的偏移地址部分赋值给dest
  • 等价操作:

    dest = word ptr [src];
    es   = word ptr [src + 2];
    
  • 格式:同lds指令

标志寄存器传送指令⚓︎

lahf
  • 功能:将标志寄存器fl的低 8 位赋值给ah,等价操作为ah = fl & 0FFh
  • 格式:lahf

sahf
  • 功能:将ah赋值给fl的低 8
  • 等价操作:

    fl = (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压入栈中
  • 等价操作:

    sp -= 2;
    word ptr ss:[sp] = fl;
    
  • 格式:pushf

popf
  • 功能:从栈中弹出一个字给fl
  • 等价操作:

    fl = word ptr ss:[sp];
    sp += 2;
    
  • 格式:popf

pushfd
  • 功能:把efl压入栈中
  • 等价操作:

    sp -= 4;
    dword ptr ss:[sp] = efl;
    
  • 格式:pushfd

popfd
  • 功能:从栈中弹出一个字给efl
  • 等价操作:

    efl = dword ptr ss:[sp];
    sp += 4;
    
  • 格式:popfd

转换指令⚓︎

扩充指令⚓︎

cbw
  • 功能:将al中的值符号扩展至ax中,即把字节扩充至字
  • 等价操作:

    ah = 0 - ((al & 80h) != 0);
    // al & 80h 取 al 最高位(符号位)
    // 如果该位是0,逻辑运算结果为0,ah = 00000000
    // 如果该位是1,逻辑运算结果为1,ah = -1,补码为11111111
    
  • 格式:cbw

cwd
  • 功能:将ax中的值符号扩展至dx:axdxax分别存储一个值的高 16 位和低 16 ,即把字扩充至双字
  • 等价操作:

    dx = 0 - ((ax & 8000h) != 0);
    
  • 格式:cwd

cdq
  • 功能:将eax中的值符号扩展至edx:eaxedxeax分别存储一个值的高 32 位和低 32 ,即把双字扩充至四字
  • 等价操作:

    edx = 0 - ((eax & 80000000h) != 0);
    
  • 格式:cdq

movsx dest, src
  • 功能:将src符号扩展至dest
  • 等价操作:

    dest = 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
  • 等价操作:

    dest = src;
    dest &= (1 << sizeof(src) * 8) - 1;
    
    // 就是movsx等价操作的前两行
    
  • 格式:同movsx指令

换码指令⚓︎

xlat
  • 功能:把byte ptr ds:[bx + al]的值赋值给al,等价操作为al = byte ptr ds:[bx + al]
  • 格式:xlat

算术运算指令⚓︎

加法指令⚓︎

add dest, src
  • 功能:等价于dest += src;
  • 格式:dest可以是寄存器 / 内存,src可以是立即数 / 寄存器 / 内存,但两者不能同时为内存

inc op
  • 功能:等价于op++;
  • 格式:op可以是寄存器 / 内存
  • 注:inc指令不影响CF

adc dest, src
  • 功能:带进位加法 (add with carry),等价于dest += src + CF;
  • 格式:同add指令
  • 注:可以用该指令模拟 32 位加法
例子

计算2F365h + 5E024h,其中dx存放结果的高 16 位,ax存放结果的低 16 位。

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

减法指令⚓︎

sub dest, src
  • 功能:等价于dest -= src;
  • 格式:同add指令

dec op
  • 功能:等价于op--;
  • 格式:同inc指令

sbb dest, src
  • 功能:带借位减法 (subtract with borrow),等价于dest -= src + CF
  • 格式:同add指令
  • 注:可以用该指令模拟 32 位减法
例子

计算127546h - 109428h,其中dx存放结果的高 16 位,ax存放结果的低 16 位。

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

neg op
  • 功能:计算op的相反数,等价于op = -op;
  • 格式:同inc指令

cmp op1, op2
  • 功能:比较op1op2,等价于temp = op1 - op2
  • 格式:同add指令
  • 注:
    • cmp指令并不会保存op1 - op2的差,但会影响状态标志
    • cmp指令后通常会跟随jcc条件跳转指令,跟无符号数、符号数比较相关的jcc类指令请见 [ 条件跳转指令 ] 一节

乘法指令⚓︎

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 op
  • 功能:无符号数除法
    • 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指令

idiv op
  • 功能:符号数除法,同div指令分为 3 种情况
  • 格式:同mul指令
  • 注:除法溢出的触发和解决方法同div指令

浮点运算指令⚓︎

浮点数的存储格式和求值公式:

  • float32 位)

  • double64 位)

  • long double80 位)

    • 存储格式:1 位符号,15 位指数,64 位尾数
    • 求值公式:


浮点数寄存器:

  • FPU 8 个浮点数寄存器,用于浮点运算,宽度均为 80 位,名称为st(i)\(i \in [0, 7]\),其中st(0)可简写为st
  • 8 个浮点数寄存器构成了一个 FPU 堆栈
    • 栈顶的浮点数寄存器的物理编号为 TOP3 位二进制数,位于 FPU 状态寄存器的第 11 至第 13 ,逻辑编号恒为 0
    • st(i)的逻辑编号为i,物理编号p = (TOP + i) % 8

浮点运算相关指令:

  • faddfsubfmulfdiv:加减乘除的浮点数版本,具体细节不再赘述
  • 其他指令:

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

压缩 BCD 码调整指令⚓︎

  • 功能:压缩 BCD 码的加法调整
  • 等价操作:

    old_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

  • 功能:压缩 BCD 码的减法调整
  • 等价操作:

    old_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
  • 功能:非压缩 BCD 码的加法调整
  • 等价操作:

    if (af == 1 || (al & 0Fh) >= 0Ah) {
        al += 6;
        ah++;
        af = 1;
        cf = 1;
    } else {
        af = 0;
        cf = 0;
    }
    al &= 0Fh;
    
  • 格式:aaa

aas
  • 功能:非压缩 BCD 码的减法调整
  • 等价操作:

    if (af == 1 || (al & 0Fh) >= 0Ah) {
        al -= 6;
        ah--;
        af = 1;
        cf = 1;
    } else {
        af = 0;
        cf = 0;
    }
    al &= 0Fh;
    
  • 格式:aas

aam
  • 功能:非压缩 BCD 码的乘法调整
  • 等价操作:

    ah = al / 10;
    al = al % 10;
    
  • 格式:aam

aad
  • 功能:非压缩 BCD 码的除法调整
  • 等价操作:

    al = (ah * 10 + al) & 0FFh;
    ah = 0;
    
  • 格式:aad

逻辑运算指令⚓︎

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指令

移位指令⚓︎

shl dest, count
  • 功能:逻辑左移,等价操作为dest <<= count & 1Fh
  • 格式:dest可以是寄存器 / 内存,count可以是立即数 / 寄存器cl
  • 注:若源代码开头有.386汇编指示语句,则立即数可以是一个 8 位大小的任意数,否则立即数只能等于 1

shr dest, count
  • 功能:逻辑右移,等价操作为dest >>= count & 1Fh
  • 格式:同shl指令
  • 注:同shl指令

sal dest, count
  • 功能:算术左移,等价与shl
  • 格式:同shl指令
  • 注:同shl指令

sal dest, count
  • 功能:算术右移,等价操作同shr,实际上略有区别,见下面图示
  • 格式:同shl指令
  • 注:同shl指令

rol dest, count
  • 功能:循环左移
  • 等价操作:

    count &= 1Fh;
    dest = (dest << count) | ((dest >> sizeof(dest) * 8 - count) & ((1 << count) - 1));
    
  • 格式:同shl指令

  • 注:同shl指令

ror dest, count
  • 功能:循环右移
  • 等价操作:

    count &= 1Fh;
    L = sizeof(dest) * 8 - count;
    dest = ((dest >> count) & ((1 << L) - 1)) | (dest << L);
    
  • 格式:同shl指令

  • 注:同shl指令

rcl dest, count
  • 功能:带进位循环左移
  • 等价操作:

    count &= 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指令

  • 注:同shl指令

rcr dest, count
  • 功能:带进位循环右移
  • 等价操作:

    count &= 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指令

  • 注:同shl指令

字符串操作指令⚓︎

与字符串操作指令相关的指令前缀(可用可不用)包括:

  • rep:重复
  • repe:若相等则重复
  • repz:若结果为 0 则重复
  • repne:若不相等则重复
  • repnz:若结果不为 0 则重复

其中第 23 个前缀等价,第 45 个前缀等价。这些指令前缀有一个共同的限制:重复执行最多cx次字符串操作

字符串复制指令⚓︎

[rep] movsb
  • 功能:以字节为单位复制字符串
  • 等价操作:

    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 指向上一字节
    }
    
    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:
    
  • 格式:

    movsb
    rep movsb
    

[rep] movsw
  • 功能:以为单位复制字符串
  • 等价操作:与movsb类似,区别在于sidi每次增加或减少 2
  • 格式:

    movsw
    rep movsw
    

[rep] movsd
  • 功能:以双字为单位复制字符串
  • 等价操作:与movsb类似,区别在于sidi每次增加或减少 4
  • 格式:

    movsd
    rep movsd
    

字符串比较指令⚓︎

[repe|repne] cmpsb
  • 功能:以字节为单位比较字符串
  • 等价操作:

    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:
    
  • 格式:

cmpsb
repe cpmsb
repne cpmsb

[repe|repne] cmpsw
  • 功能:以为单位比较字符串
  • 等价操作:见cmpsb指令
  • 格式:
cmpsw
repe cpmsw
repne cpmsw

[repe|repne] cmpsd
  • 功能:以双字为单位比较字符串
  • 等价操作:见cmpsb指令
  • 格式:
cmpsd
repe cpmsd
repne cpmsd

搜索字符串指令⚓︎

个人感觉“搜索”这个词可能描述得不太准确,因为这一类指令实质上还是比较运算,只是比较的内容不是两个完整的字符串,而是将字符串的某几个字节(字符)与寄存器的值进行比较,但目前我也想不到比较合适的词汇,所以还是保留了这个标题。

[repe|repne] scasb
  • 功能:在 es:di 指向的目标字符串中搜索寄存器 al 的值(即比较 1 字节内容)
  • 等价操作:

    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:
    
  • 格式:

scasb
repe scasb
repne scasb

[repe|repne] scasw
  • 功能:在 es:di 指向的目标字符串中搜索寄存器 ax 的值(即比较 2 字节内容)
  • 等价操作:见scasb指令
  • 格式:
scasw
repe scasw
repne scasw

[repe|repne] scasd
  • 功能:在 es:di 指向的目标字符串中搜索寄存器 eax 的值(即比较 4 字节内容)
  • 等价操作:见scasb指令
  • 格式:
scasd
repe scasd
repne scasd

写入字符串指令⚓︎

[rep] stosb
  • 功能:把 al 的值写入 es:di 指向的目标字符串中
  • 等价操作:

    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;
        // ...
    }
    
    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:
    
  • 格式:

stosb
rep stosb

[rep] stosw
  • 功能:把 ax 的值写入 es:di 指向的目标字符串中
  • 等价操作:见stosb指令
  • 格式:
stosw
rep stosw

[rep] stosd
  • 功能:把 eax 的值写入 es:di 指向的目标字符串中
  • 等价操作:见stosb指令
  • 格式:
stosd
rep stosd

读取字符串指令⚓︎

lodsb
  • 功能:从 ds:si 指向的源字符串中读取一个字节的数据,并保存到 al
  • 等价操作:
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];
    // ...
}
  • 格式:
lodsb

lodsw
  • 功能:从 ds:si 指向的源字符串中读取一个字的数据,并保存到 ax
  • 等价操作:见lodsb指令
  • 格式:
lodsw

lodsd
  • 功能:从 ds:si 指向的源字符串中读取一个双字的数据,并保存到 eax
  • 等价操作:见lodsb指令
  • 格式:
lodsd

控制转移指令⚓︎

无条件跳转指令⚓︎

jmp short dest
  • 功能:短跳,跳到目标地址 dest
  • 等价操作:
delta = idata8
delta |= (0 - ((delta & 80h) >> 7 & 1)) << 8;  // 符号扩展至16位
ip += 2 + delta;
// dest = cs + ip
  • 格式:
jmp short dest
  • 注:
    • 该指令的机器码为 2 字节:0E8h, idata8,其中idata8是一个 8 位符号数,表示短跳的跳转距离,故取值范围为 [-128, 127],超过该范围编译时会报错
    • 编译器会自动判断跳转距离,因此short修饰可省略不写
    • 短跳指令转化为机器码后,跳转距离idata8按以下公式计算:\(idata8 = dest - (\$ + 2)\),其中 \(\$\) 表示当前这条跳转指令自身的偏移地址

jmp near ptr dest
  • 功能:近跳,跳到目标地址 dest
  • 等价操作:
delta = idata16
ip += 3 + delta;
// dest = cs + ip
  • 格式:
jmp near ptr dest
jmp reg16
jmp mem16
  • 注:
    • 该指令的机器码为 3 字节:0E9h, idata16_L8, idata16_H8,后两者分别是 16 位符号数idata16的低 8 位和高 8 位,表示近跳的跳转距离,故取值范围为 [-32768, 32767]
    • 编译器会自动判断跳转距离,因此near ptr修饰可省略不写
    • 短跳指令转化为机器码后,跳转距离idata16按以下公式计算:\(idata16 = dest - (\$ + 3)\),其中 \(\$\) 表示当前这条跳转指令自身的偏移地址
    • 该指令还可以将 16 位寄存器或内存作为跳转目标地址

jmp far ptr dest
  • 功能:远跳,跳到目标地址 dest
  • 等价操作:
ip = idata32_L16
cs = idata32_H16
// dest = idata32
  • 格式:
jmp far ptr dest
jmp mem32
  • 注:
    • 该指令的机器码为 5 字节:0E9h, idata32_L16, idata32_H16,后两者分别是 32 位符号数idata32的低 16 位和高 16 位,表示远跳的跳转目标地址
    • 该指令的dest必须是已定义的标号,不能是某个常数
    • 该指令还可以将 32 位内存作为跳转目标地址

条件跳转指令⚓︎

jcc
  • 功能:条件跳转
  • 注:
    • jcc 实际上指的是一类指令(具体见下面的表格,并没有一个名为jcc的指令
    • jcc 指令的跳转距离均为 1 字节
  • 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
jne 无零标志则跳 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
jecxz ecx=0 则跳 ecx == 0

循环指令⚓︎

loop dest
  • 功能:循环
  • 等价操作:
cx--;
if (cx != 0)
    ip = dest;
  • 格式:
loop dest
  • 注:
    • loop 的跳转距离为 1 字节,即跳转范围为 [-128, 127]
    • 需要注意 loop 先会对 cx 1 然后再与 0 判断,所以在 loop 前令 cx=0 反而会循环最大次数 10000h

loopz dest
  • 功能:若等于 0 则循环
  • 等价操作:
old_fl = fl;
cx--;
fl = old_fl
if (zf == 1 && cx != 0)
    ip = dest;
  • 格式:
loopz dest
  • 注:loopz = loope

loopnz dest
  • 功能:若不等于 0 则循环
  • 等价操作:
old_fl = fl;
cx--;
fl = old_fl
if (zf == 0 && cx != 0)
    ip = dest;
  • 格式:
loopnz dest
  • 注:loopnz = loopne

子程序调用与返回指令⚓︎

call near ptr dest
  • 功能:近调用,目标地址为 dest
  • 等价操作:
back_addr = ip + 3;
sp -= 2;
word ptr ss:[sp] = back_addr;
delta = idata16;
ip = back_addr + delta;
  • 格式:
call near ptr dest
call reg16
call mem16
  • 注:
    • 该指令的机器码为 3 字节:0E8h, idata16_L8, idata16_H8,后两者分别是 16 位符号数idata16的低 8 位和高 8 位,表示近调用的跳转距离,故取值范围为 [-32768, 32767]
    • 编译器会自动判断跳转距离,因此near ptr修饰可省略不写
    • 短跳指令转化为机器码后,跳转距离idata16按以下公式计算:\(idata16 = dest - (\$ + 3)\),其中 \(\$\) 表示当前这条跳转指令自身的偏移地址
    • 该指令还可以将 16 位寄存器或内存作为跳转目标地址

怎么好像和jmp near ptr dest差不多呀 ...

retn [idata16]
  • 功能:近返回
  • 等价操作:
back_addr = word ptr ss:[sp];
sp += 2;
if (idata16) 
    sp += idata16;
ip = back_addr;
  • 格式:
retn
retn idata16

call far ptr dest
  • 功能:远调用,目标地址为 dest
  • 等价操作:
sp -= 4;
word ptr ss:[sp] = ip + 5;
word ptr ss:[sp+2] = cs;
ip = idata32_L16;
cs = idata32_H16;
  • 格式:
call far ptr dest
call mem32
  • 注:
    • 该指令的机器码为 5 字节:9Ah, idata32_L16, idata32_H16,后两者分别是 32 位符号数idata32的低 16 位和高 16 位,表示远调用的目标地址
    • 该指令的dest必须是已定义的标号,不能是某个常数
    • 该指令还可以将 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;
  • 格式:
retf
retf idata16

中断和中断返回指令⚓︎

int n
  • 功能:中断
  • 等价操作:
old_fl = fl;
if = 0;
tf = 0;
sp -= 6;
word ptr ss:[sp] = ip + 2;
word ptr ss:[sp+2] = cs;
word ptr ss:[sp+4] = old_fl;
ip = word ptr 0000:[idata8 * 4]
cs = word ptr 0000:[idata8 * 4 + 2]
  • 格式:
int idata8
  • 注:
    • 该指令的机器码为 2 字节:0CDh, idata8,其中idata8是中断号
    • 该指令的目标地址是一个 32 位的远指针,称为中断向量(interrupt vector),被保存在 0000:idata8*4
    • [0000:0000, 0000:03FFh] 这个内存区间称为中断向量表,一共存放了从int 00hint 0FFh 256 个中断向量
    • 中断大全

int 3
  • 功能:软件断点中断
  • 等价操作:
old_fl = fl;
if = 0;
tf = 0;
sp -= 6;
word ptr ss:[sp] = ip + 1;
word ptr ss:[sp+2] = cs;
word ptr ss:[sp+4] = old_fl;
ip = word ptr 0000:[000Ch]
cs = word ptr 0000:[000Eh]
  • 格式:
int 3

into
  • 功能:溢出中断
  • 等价操作:
if (of == 1) {
    old_fl = fl;
    if = 0;
    tf = 0;
    sp -= 6;
    word ptr ss:[sp] = ip + 1;
    word ptr ss:[sp+2] = cs;
    word ptr ss:[sp+4] = old_fl;
    ip = word ptr 0000:[0010h]
    cs = word ptr 0000:[0012h]
}
  • 格式:
into

iret
  • 功能:中断返回
  • 等价操作:
back_ip = word ptr ss:[sp];
back_cs = word ptr ss:[sp+2];
back_fl = word ptr ss:[sp+4];
sp += 6;
fl = back_fl;
ip = back_ip;
cs = back_cs;
  • 格式:
iret

评论区

如果大家有什么问题或想法,欢迎在下方留言~