跳转至

Part 4. 80x86 指令系统⚓︎

5307 个字 432 行代码 预计阅读时间 32 分钟

指令结构⚓︎

指令 = 操作码 + 操作数
  • 操作数共有 3 种类型:立即数(常数)(idata)、寄存器 (reg)、变量 / 内存单元 (mem)
  • 一条指令中的多个操作数往往是等宽的(可能要用宽度修饰词来强制转换)

注意

  • 以下内容可能不太像篇笔记,更像一份 reference,忘记指令功能的时候可以来这里查一下 ~
  • 我们约定:
    • 用方括号表示可选的前缀或命令
    • 用竖线表示实际使用时,在这些指令中选择其一
  • 善用 Ctrl+F 查找功能!

数据传送指令⚓︎

通用数据传送指令⚓︎

  • 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可以是寄存器 / 内存,但不能同时为内存

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

端口输入输出指令⚓︎

具体请见 Part 2 端口部分。

地址传送指令⚓︎

  • lea dest, src

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

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

      dest = word ptr [src];
      ds   = word ptr [src + 2];
      
    • 格式:dest是寄存器,src是双字大小的内存变量

  • 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

    • 注:结合pushfpopf指令,可以实现对标志寄存器的访问(修改标签位的值,具体实现可以看“内存”一节上方的代码片段)
  • 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 (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指令
    • 注:可以用该指令实现对更大数据的加法运算(具体可参照下面的例子)
    例子

    计算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指令
    • 注:同inc指令
  • sbb dest, src

    • 功能:带借位减法 (subtract with borrow),等价于dest -= src + cf
    • 格式:同add指令
    • 注:可以用该指令实现对更大数据的减法运算,注意先减低位数据,再减高位数据(具体可参照下面的例子)
    例子

    计算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指令
    • 注:它会影响 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指令

浮点运算指令⚓︎

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

  • float32 位)

  • double64 位)

  • long double80 位)

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


浮点数寄存器:

  • FPU 8 个浮点数寄存器,用于浮点运算,宽度均为 80 位(相当于 C 语言的long double,名称为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
  • 标志位af与十进制调整指令密切相关

压缩 BCD 码调整指令⚓︎

  • daa

    • 功能:在al被做加法后将结果al调整为 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

  • das

    • 功能:在al被做减法后将结果al调整为 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

    • 功能:加法的 ASCII 调整,在al被做加法后连带ah一起调整ax为非压缩 BCD
    • 等价操作:

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

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

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

  • aad

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

移位指令⚓︎

注意

  • 对于所有的移位指令,最后移出去的那一位一定会被保存在进位标志 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

    • 功能:循环左移
    • 等价操作:

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

  • ror dest, count

    • 功能:循环右移
    • 等价操作:

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

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

字符串操作指令⚓︎

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

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

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

可以通过设置方向标志 df的值来改变访问(包括复制、比较、读取、写入等操作)字符串的的方向:

  • 当源地址 < 目标地址时,访问按反方向,即令 df = 1(可使用std指令设置)
  • 当源地址 > 目标地址时,访问按正方向,即令 df = 0(可使用cld指令设置(大多数情况)

字符串复制指令⚓︎

  • [rep] movsb

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

      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 movsb等价于指令
      s: movsb
         loop s
      
      • 该指令一般用于复制长度为(cx 的值)的字符串
  • [rep] movsw

    • 功能:以为单位从 ds:[si] 传送数据到 es:[di],并移动 si、di
    • 等价操作:与movsb类似,区别在于sidi每次增加或减少 2
    • 格式:

      movsw
      rep movsw
      
  • [rep] movsd

    • 功能:以双字为单位从 ds:[si] 传送数据到 es:[di],并移动 si、di
    • 等价操作:与movsb类似,区别在于sidi每次增加或减少 4
    • 格式:

      movsd
      rep movsd
      

字符串比较指令⚓︎

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

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

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

    • 功能:比较双字ds:[si] es:[di]
    • 等价操作:见cmpsb指令
    • 格式:
    cmpsd
    repe cpmsd
    repne cpmsd
    

搜索字符串指令⚓︎

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

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

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

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

写入字符串指令⚓︎

  • [rep] stosb

    • 功能:把 al 内的字节数据存入 es:[di] 中,并移动 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] 中,并移动 di
    • 等价操作:见stosb指令
    • 格式:
    stosw
    rep stosw
    
  • [rep] stosd

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

读取字符串指令⚓︎

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

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

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

控制转移指令⚓︎

转移行为分为两类:

  • 段内转移:只修改 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
    
    • 格式:
    jmp short label|offset
    
    • 注:
      • 该指令的机器码为 2 字节:0E8h, idata8,其中idata8是一个 8 位符号数,表示短跳的跳转距离,取值范围为 [-128, 127],超过该范围编译时会报错
      • 短跳指令转化为机器码后,跳转距离idata8按以下公式计算:\(\mathtt{idata8 = dest - (\$ + 2)}\),其中 \(\mathtt{\$}\) 表示当前这条跳转指令自身的偏移地址
      • 编译器会自动判断跳转距离,因此short修饰可省略不写
  • jmp near ptr dest

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

    • 功能:远跳,跳到 32 位目标地址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 位,表示远跳的跳转目标地址,且低 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 || 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

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

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

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

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

过程指令⚓︎

  • call near ptr label

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

      • 等价指令为:
      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]

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

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

  • call far ptr label

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

      • 等价指令为:
      push cs
      push ip
      jmp far ptr 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;
    
    • 格式:
    retf
    retf idata16
    
    • 注:

      • 等价指令为:
      pop ip
      pop cs
      

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

中断指令⚓︎

具体请见 Part 5 中断部分。

评论区

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