Skip to content

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可以是立即数/寄存器/内存,但两者不能同时为内存
    • 注:
      • 该指令不影响任何标志位
      • destsrc必须等宽
      • 不能将立即数或段寄存器赋值给段寄存器
      • 不能直接对cs进行赋值
      • (任何指令都)不能引用ipfl
  • push op

    • 功能:将op压入栈内
    • 等价操作:
    c
    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
    • 等价操作:
    c
    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
    • 等价操作:
    c
    temp = op1;
    op1  = op2;
    op2  = temp;
    • 格式:op1op2可以是寄存器/内存,但不能同时为内存
    • 注:
      • 该指令不影响任何标志位
      • 操作数中不能有段寄存器

端口输入输出指令

具体请见Part 2的端口部分。

地址传送指令

  • lea dest, src

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

    • 功能:取出保存在变量src中的远指针,先将远指针的段地址部分赋值给ds,再将远指针的偏移地址部分赋值给dest
    • 等价操作:
    c
    dest = word ptr [src];
    ds   = word ptr [src + 2];
    • 格式:dest是寄存器,src是双字大小的内存变量
  • les dest, src

    • 功能:取出保存在变量src中的远指针,先将远指针的段地址部分赋值给es,再将远指针的偏移地址部分赋值给dest
    • 等价操作:
    c
    dest = word ptr [src];
    es   = word ptr [src + 2];
    • 格式:同lds指令

标志寄存器传送指令

  • lahf

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

    • 功能:将ah赋值给fl的低8位
    • 等价操作:
    c
    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压入栈中
    • 等价操作:
    c
    sp -= 2;
    word ptr ss:[sp] = fl;
    • 格式:pushf
  • popf

    • 功能:从栈中弹出一个字给fl
    • 等价操作:
    c
    fl = word ptr ss:[sp];
    sp += 2;
    • 格式:popf
    • 注:结合pushfpopf指令,可以实现对标志寄存器的访问(修改标签位的值,具体实现可以看“内存”一节上方的代码片段)
  • pushfd

    • 功能:把efl压入栈中
    • 等价操作:
    c
    sp -= 4;
    dword ptr ss:[sp] = efl;
    • 格式:pushfd
  • popfd

    • 功能:从栈中弹出一个字给efl
    • 等价操作:
    c
    efl = dword ptr ss:[sp];
    sp += 4;
    • 格式:popfd

转换指令

扩充指令

  • cbw

    • 功能:将al中的值符号扩展ax中,即把字节扩充至字
    • 等价操作:
    c
    ah = 0 - ((al & 80h) != 0);
    // al & 80h 取 al 最高位(符号位)
    // 如果该位是0,逻辑运算结果为0,ah = 00000000
    // 如果该位是1,逻辑运算结果为1,ah = -1,补码为11111111
    • 格式:cbw
  • cwd

    • 功能:将ax中的值符号扩展dx:axdxax分别存储一个值的高16位和低16位),即把字扩充至双字
    • 等价操作:
    c
    dx = 0 - ((ax & 8000h) != 0);
    • 格式:cwd
  • cdq

    • 功能:将eax中的值符号扩展edx:eaxedxeax分别存储一个值的高32位和低32位),即把双字扩充至四字
    • 等价操作:
    c
    edx = 0 - ((eax & 80000000h) != 0);
    • 格式:cdq
  • movsx dest, src

    • 功能:将src符号扩展dest
    • 等价操作:
    c
    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
    • 等价操作:
    c
    dest = 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
      ```
    

减法指令

  • 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
      ```
    
  • neg op

    • 功能:计算op的相反数,等价于op = -op;
    • 格式:同inc指令
    • 注:它会影响cf、zf、sf等标志位
  • cmp op1, op2

    • 功能:比较op1op2,等价于temp = op1 - op2
    • 格式:同add指令
    • 注:
      • cmp指令并不会保存op1 - op2的差,但会影响标志位
      • cmp指令后通常会跟随条件跳转指令,跟无符号数、符号数比较相关的jcc类指令请见条件跳转指令一节
      • 对于符号数而言,cmp不是只根据操作数之差来得到比较结果的,因为符号数的减法存在溢出现象,所以cmp会综合考虑sf(符号标志位)和of(溢出标志位)的值
        • 当of = 0时,sf反映了opt1 - opt2结果的符号位
        • 当of = 1时,sf的值与opt1 - opt2结果的符号位相反

乘法指令

  • 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位)

    ![](images/12_dark.png#only-dark) ![](images/12_light.png#only-light)
  • double(64位)

    ![](images/13_dark.png#only-dark) ![](images/13_light.png#only-light)
  • long double(80位)

    • 存储格式:1位符号,15位指数,64位尾数
    • 求值公式:
    ![](images/14_dark.png#only-dark) ![](images/14_light.png#only-light)

浮点数寄存器:

  • FPU有8个浮点数寄存器,用于浮点运算,宽度均为80位(相当于C语言的long double),名称为st(i)i[0,7]),其中st(0)可简写为st
  • 这8个浮点数寄存器构成了一个FPU堆栈
    • 栈顶的浮点数寄存器的物理编号为TOP(3位二进制数,位于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码
  • 标志位af与十进制调整指令密切相关

压缩BCD码调整指令

  • daa

    • 功能:在al被做加法后将结果al调整为BCD码
    • 等价操作:
    c
    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
  • das

    • 功能:在al被做减法后将结果al调整为BCD码
    • 等价操作:
    c
    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

    • 功能:加法的ASCII调整,在al被做加法后连带ah一起调整ax为非压缩BCD码
    • 等价操作:
    c
    if (af == 1 || (al & 0Fh) >= 0Ah) {
        al += 6;
        ah++;
        af = 1;
        cf = 1;
    } else {
        af = 0;
        cf = 0;
    }
    al &= 0Fh;
    • 格式:aaa
  • aas

    • 功能:减法的ASCII调整,在al被做减法后连带ah一起调整ax为非压缩BCD码
    • 等价操作:
    c
    if (af == 1 || (al & 0Fh) >= 0Ah) {
        al -= 6;
        ah--;
        af = 1;
        cf = 1;
    } else {
        af = 0;
        cf = 0;
    }
    al &= 0Fh;
    • 格式:aas
  • aam

    • 功能:乘法的ASCII调整,在al被做乘法后连带ah一起调整ax为非压缩BCD码
    • 等价操作:
    c
    ah = al / 10;
    al = al % 10;
    • 格式:aam
  • aad

    • 功能:除法的ASCII调整,在al被做除法后连带ah一起调整ax为非压缩BCD码
    • 等价操作:
    c
    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指令

移位指令

!!! warning "注意"

- 对于所有的移位指令,最后移出去的那一位一定会被保存在进位标志cf中
- 若源代码开头有`.386`汇编指示语句,则立即数可以是一个8位大小的任意数,否则**立即数只能等于1**
  • shl dest, count

    • 功能:逻辑左移,等价操作为dest <<= count & 1Fh
    • 格式:dest可以是寄存器/内存,count可以是立即数/寄存器cl
    ![](images/18_dark.png#only-dark) ![](images/18_light.png#only-light)
  • shr dest, count

    • 功能:逻辑右移,等价操作为dest >>= count & 1Fh
    • 格式:同shl指令
    • 注:该指令会在最左边补0,是一种针对无符号数的右移操作
    ![](images/20_dark.png#only-dark) ![](images/20_light.png#only-light)
  • sal dest, count

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

    • 功能:算术右移,等价操作类似shr,但略有区别,见下面图示
    • 格式:同shl指令
    • 注:该指令会在最左边补符号位(移动负数时补1,移动正数是补0),是一种针对符号数的右移操作
    ![](images/21_dark.png#only-dark) ![](images/21_light.png#only-light)
  • rol dest, count

    • 功能:循环左移
    • 等价操作:
    c
    count &= 1Fh;
    dest = (dest << count) | ((dest >> sizeof(dest) * 8 - count) & ((1 << count) - 1));
    • 格式:同shl指令
    ![](images/19_dark.png#only-dark) ![](images/19_light.png#only-light)
  • ror dest, count

    • 功能:循环右移
    • 等价操作:
    c
    count &= 1Fh;
    L = sizeof(dest) * 8 - count;
    dest = ((dest >> count) & ((1 << L) - 1)) | (dest << L);
    • 格式:同shl指令
    ![](images/22_dark.png#only-dark) ![](images/22_light.png#only-light)
  • rcl dest, count

    • 功能:带进位循环左移
    • 等价操作:
    c
    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指令
    ![](images/24_dark.png#only-dark) ![](images/24_light.png#only-light)
  • rcr dest, count

    • 功能:带进位循环右移
    • 等价操作:
    c
    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指令
    ![](images/23_dark.png#only-dark) ![](images/23_light.png#only-light)

字符串操作指令

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

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

      asm
      movsb
      rep movsb
    • 注:

      • rep movsb等价于指令
      asm
      s: movsb
         loop s
      • 该指令一般用于复制长度为(cx的值)的字符串
  • [rep] movsw

    • 功能:以为单位从ds:[si]传送数据到es:[di],并移动si、di

    • 等价操作:与movsb类似,区别在于sidi每次增加或减少2

    • 格式:

      asm
      movsw
      rep movsw
  • [rep] movsd

    • 功能:以双字为单位从ds:[si]传送数据到es:[di],并移动si、di

    • 等价操作:与movsb类似,区别在于sidi每次增加或减少4

    • 格式:

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

    asm
    cmpsb
    repe cpmsb
    repne cpmsb
  • [repe|repne] cmpsw

    • 功能:比较ds:[si]与es:[di]
    • 等价操作:见cmpsb指令
    • 格式:
    asm
    cmpsw
    repe cpmsw
    repne cpmsw
  • [repe|repne] cmpsd

    • 功能:比较双字ds:[si]与es:[di]
    • 等价操作:见cmpsb指令
    • 格式:
    asm
    cmpsd
    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:
        ```
      
    • 格式:

    asm
    scasb
    repe scasb
    repne scasb
  • [repe|repne] scasw

    • 功能:比较ax与es:[di](1),即计算ax - es:[di],丢弃结果保留符号位,并移动di
    • 等价操作:见scasb指令
    • 格式:
    asm
    scasw
    repe scasw
    repne scasw
  • [repe|repne] scasd

    • 功能:比较eax与es:[di](1双字),即计算eax - es:[di],丢弃结果保留符号位,并移动di
    • 等价操作:见scasb指令
    • 格式:
    asm
    scasd
    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:
        ```
      
    • 格式:

    asm
    stosb
    rep stosb
  • [rep] stosw

    • 功能:把ax内的数据存入es:[di]中,并移动di
    • 等价操作:见stosb指令
    • 格式:
    asm
    stosw
    rep stosw
  • [rep] stosd

    • 功能:把eax内的双字数据存入es:[di]中,并移动di
    • 等价操作:见stosb指令
    • 格式:
    asm
    stosd
    rep stosd

读取字符串指令

  • lodsb

    • 功能:从ds:[si]读取一个字节存入al,并移动si
    • 等价操作:
    c
    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,并移动si
    • 等价操作:见lodsb指令
    • 格式:
    lodsw
  • lodsd

    • 功能:从ds:[si]读取一个双字存入eax,并移动si
    • 等价操作:见lodsb指令
    • 格式:
    lodsd

控制转移指令

转移行为分为两类:

  • 段内转移:只修改ip
    • 短转移:ip的修改范围为-128~127
    • 近转移:ip的修改范围为-32768~32767
  • 段间转移:同时修改cs和ip

无条件跳转指令

!!! tip "省流"

大多数情况下,无条件跳转指令可一律写成`jmp label`的形式。
  • jmp short lebel|offset

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

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

    • 功能:远跳,跳到32位目标地址dest或变量
    • 等价操作:
    c
    ip = idata32_L16
    cs = idata32_H16
    // dest = idata32
    • 格式:
    asm
    jmp far ptr dest
    jmp mem32
    • 注:
      • 该指令的机器码为5字节:0E9h, idata32_L16, idata32_H16,后两者分别是32位符号数idata32的低16位和高16位,表示远跳的跳转目标地址,且低16位表示偏移地址,高16位表示段地址
      • 编译器会自动判断跳转距离,因此far ptr修饰可省略不写
      • 该指令的dest必须是已定义的标号,不能是某个常数
      • 该指令还可以将32位内存作为跳转目标地址
      • 该指令同时修改了cs和ip的值,而前两条指令只修改了ip的值

条件跳转指令

jcc:

  • 功能:条件跳转
  • 注:
    • jcc类指令通常配合cmp指令一起食用
    • jcc实际上指的是一类指令(具体见下面的表格),并没有一个名为jcc的指令
    • jcc类指令的跳转距离宽度均为1字节,因此都属于短跳指令
  • 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
jcxzcx=0则跳cx == 0配合loop指令(因为loop会改变cx的值)
jecxzecx=0则跳ecx == 0

循环指令

  • loop dest

    • 功能:循环
    • 等价操作:
    c
    cx--;
    if (cx != 0)
        ip = dest;
    • 格式:
    asm
    loop dest
    • 注:

      • loop的跳转距离为1字节,即跳转范围为[-128, 127]
      • 需要注意loop先会对cx减1然后再与0判断,所以在loop前令cx=0反而会循环最大次数10000h次
      • 常用模板:
      asm
          mov cx, loop_time
      s:
          ;statements
          loop s
  • loopz dest

    • 功能:若dest等于0且cx不等于0则循环
    • 等价操作:
    c
    old_fl = fl;
    cx--;
    fl = old_fl
    if (zf == 1 && cx != 0)
        ip = dest;
    • 格式:
    asm
    loopz dest
    • 注:
      • 用法与loop类似
      • loopz = loope
  • loopnz dest

    • 功能:若dest不等于0且cx不等于0则循环
    • 等价操作:
    c
    old_fl = fl;
    cx--;
    fl = old_fl
    if (zf == 0 && cx != 0)
        ip = dest;
    • 格式:
    asm
    loopnz dest
    • 注:
      • 用法与loop类似
      • loopnz = loopne

过程指令

  • call near ptr label

    • 功能:近调用(用于调用相同段的函数),目标地址为标号label对应的地址
    • 等价操作:
    c
    back_addr = ip + 3;
    sp -= 2;
    word ptr ss:[sp] = back_addr;
    delta = idata16;
    ip = back_addr + delta;
    • 格式:
    asm
    call near ptr label
    call reg16
    call mem16
    • 注:

      • 等价指令为:
      asm
      push ip            ; 这个ip实际上指的是call指令后面那条指令
      jmp near ptr label
      • 该指令的机器码为3字节:0E8h, idata16_L8, idata16_H8,后两者分别是16位符号数idata16的低8位和高8位,表示近调用的跳转距离,取值范围为[-32768, 32767]
      • 编译器会自动判断跳转距离,因此near ptr修饰可省略
      • 短跳指令转化为机器码后,跳转距离idata16按以下公式计算:idata16=dest($+3),其中$表示当前这条跳转指令自身的偏移地址
      • 该指令还可以将16位寄存器或内存作为跳转目标地址
  • retn [idata16]

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

      • 等价指令为pop ip(但在实际编程中不能这么用),即从栈中获取ip以跳转到被保存的指令位置上
      • 在用标号定义的函数里,或者用下列方法定义的函数中,retn一般简记为ret
      asm
      fun_name proc
      ; instructions
      func_name endp
      
      ; or
      fun_name proc near
      ; instructions
      func_name endp

  • call far ptr label

    • 功能:远调用(用于调用不同段的函数),目标地址为标号label对应的地址
    • 等价操作:
    c
    sp -= 4;
    word ptr ss:[sp] = ip + 5;
    word ptr ss:[sp+2] = cs;
    ip = idata32_L16;
    cs = idata32_H16;
    • 格式:
    asm
    call far ptr label
    call mem32
    • 注:

      • 等价指令为:
      asm
      push cs
      push ip
      jmp far ptr label
      • 该指令的机器码为5字节:9Ah, idata32_L16, idata32_H16,后两者分别是32位符号数idata32的低16位和高16位,表示远调用的目标地址
      • 该指令的label必须是已定义的标号,不能是某个常数
      • 该指令还可以将32位内存作为跳转目标地址
  • retf [idata16]

    • 功能:远返回
    • 等价操作:
    c
    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;
    • 格式:
    asm
    retf
    retf idata16
    • 注:

      • 等价指令为:
      asm
      pop ip
      pop cs

      即从栈中获取ip和cs,以跳转到被保存的更远的指令位置上

中断指令

具体请见Part 5的中断部分。