通过本篇博客面向8086CPU实模式编程。

先了解一下8086机:

  • 8086是16位机(因此运算器一次能处理16位数据,寄存器最大宽度是16位)

  • 有20位地址总线(在总线上传输时,用两个16位地址,即段地址和偏移地址,通过段地址<<4+偏移地址组合成20位的物理地址在总线上传送)

  • Intel CPU编码方式属于小端存储(逆序存储,高位对应高地址)

    RISC CPU编码方式属于大端存储(正序存储,低位对应高地址)。

    因此属于Intel系列的8086机是小端存储。

一、Intel系列32位微处理器的3种工作模式及联系

CPU在不同阶段的工作状态。

(一)实模式

加电复位之后CPU自动工作在实模式,此时只能访问第一个1M内存,开始主板初始化。

(二)保护模式

引导之后,CPU处于保护模式,此时提供支持多任务环境的工作方式。(CPU绝大多数时间都处于保护模式)。

(三)虚拟8086模式

这种模式方便了用户在保护模式下运行一个或者多个原8086程序,保护模式可以随时切换至该模式的其中一种工作方式。

搭载Intel系列CPU的PC机一开机是处于实模式下,如果安装的系统是DOS,那么会始终处于实模式,但如果是Windows,那么CPU会被切换至保护模式,如果在Windows下运行DOS系统下的程序,那么CPU会切换到虚拟8086模式下运行。

二、存储器

(一)基本概念

  • 存储元:存储器的最小组成单位。

  • 存储单元:CPU访问存储器基本单位,由若干个具有相同操作属性的存储元组成。

  • 存储体:存储单元的集合,是存放二进制信息的地方。

  • 存储器:存储体和地址译码电路、读写控制电路等一起构成存储器。

(二)地址空间

  • 存储空间:包括物理空间(程序的运行空间,即主存空间)、虚拟空间、线性空间
  • I/O空间
三、8086寄存器

(一)通用寄存器、段寄存器、指针寄存器

  • AX/BX/CX/DX/SI/DI/SP/BP:通用寄存器(16位)。

    在现在的64位机上通用寄存器叫RAX/RBX/RCX/RDX/RSI/RDI/RSP/RBP,32位机上通用寄存器叫EAX/EBX/ECX/EDX/ESI/EDI/ESP/EBP,出于兼容性考虑,可以理解为EAX/EBX/ECX/EDX/ESI/EDI/ESP/EBP的低16位就是AX/BX/CX/DX/SI/DI/SP/BP。16位得一般寄存器AX/BX/CX/DX的低8位是AL/BL/CL/DL,高8位是AH/BH/CH/DH。基于8086实模式汇编_汇编语言

  • CS/DS/SS/ES/FS/GS:提供段地址的段寄存器(16位)。

    • CS & IP指令段寄存器CS提供的段地址和指针寄存器IP提供的偏移地址组合即得待执行指令的物理地址,CPU取指执行上一条指令的地址(CPU在执行当前指令的时候,IP就已经指向了下一条指令的偏移地址。)

      [CS:IP]存放下一条要执行的指令的地址。

    • SS & SP栈段寄存器SS提供的段地址和SP栈顶寄存器提供栈顶单元的偏移地址组合成栈顶单元的物理地址。

      [SS:SP]始终指向栈顶。SS和SP的初值都是由操作系统分配的。

    • DS:提供数据段段地址的数据段寄存器

    • ES/FS/GS附加数据段(附加段)寄存器

  • IP:指令指针寄存器,32位称为EIP,低16位为IP。

(二)标志寄存器(EFLAGS)

1. 状态标志寄存器

  • CF:进位/借位标志位。

    将操作数(字节、字、双字)视作无符号数进行运算,并判断是否由最高有效位产生向更高位进位或者借位的标志位。发生进位为1,未发生进位为0。

  • OF:溢出标志位。

    将操作数视作有符号数进行运算,并判断是否溢出的溢出标志位。发生溢出为1,未发生溢出为0。

    溢出
    进行有符号运算的时候结果超出了机器所能表示的范围称为溢出。
    OF置位原理
    对于

    \[[X]_补=X_{n-1}X_{n-2}X_{n-3}……; \]

    \[[Y]_补=Y_{n-1}Y_{n-2}Y_{n-3}……; \]

    \[[Z]_补=[X]_补+[Y]_补=Z_{n-1}Z_{n-2}……; \]

    那么

    \[\color{red}{OF=X_s\cdot Y_s\cdot \overline{Z_s}+\overline{X_s}\cdot \overline{Y_s}\cdot Z_s} \]

    我们人工判溢同样可以这样,补码相加之后取加数、被加数、和的最高有效位后,带入OF的公式得到OF的值。

  • AF:辅助进位/辅助借位标志位。

    判断是否由D3位(将操作数从低位到高位依次从0开始标记)产生向更高位进位或者借位的标志位。发生进位为1,未发生进位为0。

  • SF:符号标志位。

    字节/字/双字运算之后,最高位为1,则置1,否则置0。

  • ZF:结果标志位。

    运算结果全0则置1,否则置0。

  • PF:奇偶标志位。

    最低的1个字节中1的个数为偶数个则置1,否则置0.

2. 控制标志寄存器

四、操作数寻址

(一)操作数的分类

  • 立即数:包含在指令中的操作数

    书写规定

    1. 以A-F开头的16进制数必须以0为前缀。

      add al,0C8H
      
    2. 立即数的后缀表示进制,H表示16进制,B表示8进制。十进制数无需任何前缀后缀,系统自动将其转换成补码。

      add dl,10101010B   ;8进制
      add eax,12345678H  ;16进制
      mov al,-4          ;[-4]补=FCH-->al
      
    3. 立即数是字符要包上单引号。

      mov dl,'A'
      
    4. 可以用*+-/组成立即数表达式

      mov si,3*5
      
  • 寄存器操作数:操作数放在CPU某个寄存器中

  • 存储器操作数:操作数存放在寄存器中

  • I/O端口操作数:操作数存放在I/O端口中

(二)寻址方式

  • 立即寻址:操作数就包含在指令中

    mov dx,1234H ;
    
  • 寄存器寻址:操作数在寄存器中。

    mov ax,bx
    
  • 内存操作数寻址:操作数在内存中。

    • 直接寻址:直接在指令中给出偏移地址。
    mov al,ES:[2CH]  ;直接给出偏移地址是2CH
    

    有的时候为了省去手动计算偏移地址的麻烦,我们会用变量名代替上面给出的存储单元的有效地址。变量名关联着一个单元,也有着段基址和偏移量2种属性,所以段基址也可以省略不写。

    mov al,ES:var
    ;相当于 
    mov al,var
    
    • 间接寻址:偏移地址在寄存器(间址寄存器)中。当访问约定的逻辑段时,段寄存器可以省略不写。但并不是所有寄存器都能充当间址寄存器,16位寻址和32位寻址可采用的间址寄存器及对应的约定逻辑段罗列如下。

      间址寄存器 约定访问的逻辑段 字长
      BP 堆栈段SS 16位寻址方式
      BX,SI,DI 数据段DS
      EBP,ESP 堆栈段SS 32位寻址方式
      EAX ~ EDX,ESI,EDI 数据段DS
      mov ax,SS:[bp]
      ;相当于
      mov ax,[bp]
      
      mov ax,DS:[bp]
      ;不等同于
      mov ax,[bp]
      
    • 基址寻址:有效地址由2部分组成,一部分在基址寄存器,另一部分是常量。同样并不是所有寄存器都能充当基址寄存器,16位寻址和32位寻址可采用的基址寄存器及对应的约定逻辑段罗列如下。

      基址寄存器 约定访问的逻辑段 字长
      BP 堆栈段SS 16位寻址方式
      BX 数据段DS
      EBP,ESP 堆栈段SS 32位寻址方式
      EAX ~ EDX,ESI,EDI 数据段DS
      mov ax,SS:[bp+1]
      ;相当于
      mov ax,[bp+1]
      
      mov ax,DS:[bp+1]
      ;不等同于
      mov ax,[bp+1]
      
    • 变址寻址

      \[段寄存器:[比例因子*变址寄存器+偏移量](仅可用于32位寻址,且比例因子只能是1,2,4,8) \]

      \[段寄存器:[变址寄存器+偏移量] \]

      同样并不是所有寄存器都能充当变址寄存器,16位寻址和32位寻址可采用的变址寄存器及对应的约定逻辑段罗列如下。

      变址寄存器 约定访问的逻辑段 适用于
      SI,DI 数据段DS 无比例因子,16位寻址方式
      EBP 堆栈段SS 有比例因子,32位寻址方式
      EAX ~ EDX,ESI,EDI 数据段DS
      mov al,[8*SI+15]  ;非法,比例因子只能用于32位寻址
      mov ax,[7*ESI+5]  ;非法,比例因子只能是1,2,4,8
      
    • 基址加变址寻址

      \[段寄存器:[基址寄存器+比例因子*变址寄存器+偏移量] \]

      此时不存在约定逻辑段的概念,所以段寄存器需要明确写出。结构体通常采用的寻址方式就是基址加变址寻址。

五、汇编源程序

汇编源程序的构成

  • 指令性语句:即符号指令,由CPU执行
  • 指示性语句
    • 伪指令:为汇编链接工具提供信息,在汇编链接期间由汇编链接工具执行。
    • 宏指令

(一)伪指令

1. 数据定义伪指令

  • DB:字节定义伪指令。

    \[变量名\quad DB\quad 一个或者多个用逗号间隔的单字节数 \]

  • DW:字定义伪指令。

    \[变量名\quad DW\quad 一个或者多个用逗号间隔的双字节数 \]

    例如:

    N1 DB 12H,64,-1,3*3
       DB 01010101B,'A',0A6H,'HELLO'
    N2 DB ?,?       ;?代表随机数,相当于N2 DB 2DUP(?)
    N3 DW 1234H,12,'AB','C'
       DW ?,? 
    

    汇编程序把DB后面的单字节数依次存入从第一个定义的变量开始的单元,这些内存单元的属性都是”字节型”,负数用补码表示,单引号的字符翻译成ASCII码。

    汇编程序把DW后面的双字节数依次存入从第一个定义的变量开始的单元,每一个数占2个字节,低位放到低地址单元,高字节放到高地址单元,这些内存单元的属性都是”字型”,负数用补码表示,单引号的字符翻译成ASCII码。

    根据上面的案例,各个变量在内存中的地址分配如下。

    基于8086实模式汇编_数据段_02

    值得注意的是,我们访问内存单元的时候偏移量仍然是以字节为单位的,字属性仅仅是与内存单元的读取有关,例如

    mov ax,N3+1
    

    此时的ax寄存器中的值并不是N3变量的第二个字12(000CH),而是0C12H,也就是说这里的偏移量1并不是1个字,而是一个字节,尽管N3拥有字属性。

  • DD/DF/DQ/DT:分别是双字定义伪指令,6字节定义,8字节定义,10字节定义

    \[变量名\quad DD/DF/DQ/DT\quad 一个或者多个用逗号间隔的4字节数 \]

    类比DW。。

2. 符号定义伪指令

类似于C语言中的宏定义。

  • EQU:等值伪指令,但EQU后面的值一旦定义之后是不能修改的

    \[符号常数\quad EQU\quad 表达式 \]

    例如:

    NUM EQU 33
    mov al,NUM   ;该指令相当于mov al,33,此时是立即寻址
    NUM EQU 22   ;非法语句
    
  • =:等号伪指令,等号后面的值定义之后是可以修改的

    \[符号常数\quad =\quad 表达式 \]

    例如:

    NUM = 33
    mov al,NUM   ;该指令相当于mov al,33,此时是立即寻址
    NUM = 22
    mov al,NUM   ;该指令相当于mov al,22,此时是立即寻址
    

3. 运算符

运算符通常在汇编指令之前就完成了计算,此时表达式也被计算结果代替。

  • []:访问内存操作数,可以标注数组元素下标。

  • $:可以返回汇编计数器的当前值,通常用于计算字符串、数据段的长度。

    BUF DB 'Hello World'
    LENGTH1 EQU $-BUF     ;此时LENGTH1就是字符出的长度
    
    BUF DB 'Hello World'
    BUF1 EQU 'A'
    LENGTH2 EQU $-BUF     ;此时LENGTH2仍然是字符出'Hello World'的长度,因为BUF1 EQU 'A'其实是不占空间的
    
  • SEG:计算某一逻辑段的段基址。

    \[SEG\quad段名/变量名/标号名 \]

    mov ax,SEG DATA  ;DATA是数据段的段名
    mov DS,ax        ;数据段段基址放到数据段段寄存器
    
  • OFFSET:计算某个变量或者标号名所在单元相对于段首的偏移基址。

    \[OFFSET\quad 变量名/标号名 \]

  • 算术运算符、逻辑运算符、关系运算符

    可以用AND、OR、XOR(异或)、NOT(非)、SHL(左移位)、SHR(右移位)组合成逻辑表达式

    或者用EQ(等于)、NE(不等于)、GT(大于)、LT(小于)、GE(大于或等于)、LE(小于等于)组合成关系表达式,如果为真,结果是FFFFH,否则为0。

  • PTR:在本条指令中临时修改地址表达式的属性,即让该地址表达式指向的单元在该指令中具备类型说明符指明的属性。

    \[类型说明符\quad PTR\quad 地址表达式 \]

    类型说明符和地址表达式指向之间存在一定的对应关系。

    类型说明符 地址表达式
    BYTE、WORD、DWORD、FAR、NEAR 内存单元的5种寻址方式
    FAR、NEAR 子程序的名称

    在双操作数指令中当出现下面几种的情况时,

    (1)源操作数为立即数,目标操作数为直接寻址内存操作数。两者不一致时,目标操作数必须要用PTR临时修改属性。

    (2)源操作数是单字节/双字节立即数,目标操作数为非直接寻址的内存操作数,必须要用PTR临时修改属性

    修改的属性不一定要跟立即数一致,但不能低于立即数的字长。

    mov BYTE PTR [BX], 12H
    mov WORD PTR [BX], 12H   ;这两个指令其实都是对的
    
    mov BYTE PTR [BX], 0012H   ;非法指令,BYTE位数显然低于0012H的位数
    mov WORD PTR [BX], 0012H   
    

    (3)源操作数和目标操作数有一方是直接寻址内存操作数(另一方必然是寄存器操作数,因为双操作数不允许源操作数和目标操作数都是内存操作数),两者不一致,则必须以该条指令的操作意图为依据用PTR临时修改直接寻址的操作数属性。

    在单操作数指令中当出现下面几种的情况时,

    (1)非直接寻址的内存操作数必须用PTR说明

    (2)如果是直接寻址的内存操作数,要根据指令对操作数的类型属性要求(如push指令)该条指令的操作意图

(二)8086 ISA

在进行数据传送或者运算时,要注意两个操作对象的属性是一致的,比如add bx,al 就是错的。

双操作数指令不允许两个操作数都是内存操作数。

MOV

mov指令有几个注意点:

  • mov指令不允许把数据直接送入段寄存器,如mov ds,1000H\(\color{red}{(\times)}\)

    但可以用一个寄存器作中转,如mov bx,1000H;mov ds,bx\(\color{red}{(\surd)}\)

  • mov指令不允许用来直接设置CS和IP的值,如mov IP,0H\(\color{red}{(\times)}\)

LEA

有效地址传送指令。将内存表达式的有效地址放入目标寄存器中。

\[LEA\quad R16/R32,\quad内存地址表达式 \]

lea ax, BUF
mov ax, offset BUF   ;这两个指令等价

XCHG

完成2个操作数的互换。

  • 这两个操作数不能都是内存操作数
  • 操作数不能是段寄存器和立即数

PUSH/POP & PUSHF/POPF & PUSHA/POPA

8086CPU的入栈和出栈操作是以为单位进行的,Intel系列机中栈顶对应低地址,栈底对应高地址

栈为空时SS:SP指向栈空间下面一单元。

8086CPU不能自动检测栈溢出。

当向一个栈段内一直压栈,从SP=0xFFFF到SP=0后继续压栈,此时就会覆盖栈底的元素。

入栈,操作数可以是除CS之外的段寄存器16位及以上的一般寄存器带字属性的内存单元立即数

出栈,操作数可以是除CS之外的段寄存器16位及以上的一般寄存器带字属性的内存单元

  • push reg指令分为两步:

    1. SP=SP-2
    2. reg中的内容入栈
  • pop reg指令分为两步:

    1. SS:SP送入reg
    2. SP=SP+2
  • PUSHA指令依次把AX、CX、DX、BX、SP、BP、SI、DI的值压栈。

  • POPA指令则是以字为单位倒序出栈。

  • PUSHF指令把标志寄存器压栈

  • POPF指令出栈到各个标志寄存器

ADC/SBB & ADD/SUB & INC/DEC

  • ADC/SBB是带进位(上条指令执行后的CF标志寄存器中的值)的加减运算指令。

  • ADD/SUB是不带进位的加减运算指令。

    上面两对加减运算指令,会影响A、C、O、P、S、Z标志寄存器的值。

  • INC/DEC是自增1自减1的单操作数指令。

    不影响C标志

    影响的是A、O、P、S、Z标志位

NEG

求补单操作数指令,影响所有状态标志寄存器。

neg ax
相当于
mov bx,0
sub bx,ax
mov ax,bx

常用于负数取绝对值

CMP

比较指令,通过 “目标操作数-源操作数”来影响所有状态标志寄存器,但不改变源操作数和目标操作数

该指令通常跟条件转移指令配合使用。

MUL/IMUL

1. 单操作数形式

\[MUL/IMUL\quad 乘数 \]

实现乘法的隐含操作数指令

  • MUL是无符号二进制乘法

  • IMUL是有符号二进制乘法

MUL/IMUL 被乘数默认在 乘数为 高位积在 低位积在
字节相乘 AL R8/M8 AH AL
字相乘 AX R16/M16 DH AX
双字相乘 EAX R32/M32 EDX EAX

如果乘法运算之后产生了高位积,则CF=1、OF=1,此时要到对应的存放高位积的寄存器中去获取高位积的值。

如果没有产生高位积则C和O标志位置0。

2.双操作数形式和三操作数形式

只有有符号数才具备这两种形式。

\[IMUL\quad 目标操作数,源操作数 \]

\[IMUL\quad 目标操作数,\quad 源操作数,\quad 立即数 \]

对于三操作数乘法,\(源操作数\times 立即数\rightarrow 目标操作数\)

DIV/IDIV

\[DIV/IDIV\quad 除数 \]

实现除法的隐含操作数指令

  • DIV是无符号二进制乘法

  • IDIV是有符号二进制乘法

DIV/IDIV 除数由指定格式指定 被除数默认在 商在 余数在
字节除法 R8/M8 AX AL AH
字除法 R16/M16 DX=高16位
AX=低16位
AX DX
双字除法 R32/M32 EDX=高32位
EAX=低32位
EAX EDX

被除数应该是除数的双倍长度

如果除数太小,使商超过范围,会引发内中断,此时对中断的处理由操作系统决定

DAA

有的时候我们需要通过计算机将两个10进制数相加,并能够很容易看出来相加结果的大小,所以选择用BCD码表示这个结果会比二进制数据更加直观。在用BCD码表示加数和被加数之和,我们通过DAA指令对结果进行适当修正,就能得到结果的BCD码形式。

关于BCD码的表示规范

  • 组合形式:一个字节中含有2位BCD码,如十进制69可以用BCD码69H代表
  • 未组合形式:一个字节中含有1位BCD码,高四位为0,如十进制69可以用BCD码09H,06H代表

BCD码修正指令,根据标志位选择适当的修正数,所以DAA指令必须紧跟在加减指令之后,默认操作对象是AL,根据具体情况对AL中的高低4位进行修正。

;计算1234+5678
N1 DW 1234H
N2 DW 5678H
SUM DW ?

mov al, byte ptr N1
add al, byte ptr N2
DAA
mov byte ptr SUM, al
mov al, byte ptr N1+1
adc al, byte ptr N2+1
DAA
mov byte ptr SUM+1, al

转移指令

  • 实现段内短转移

\[jmp/jnz/……\quad short\quad 标号 \]

  • 实现段间转移

\[jmp\quad 标号 \]

short是短转移,相对于指令地址\(+129\)\(-126\)个单元(正代表向下,负代表向上)。

不加short是长转移可以转移的范围是64k个单元。

1. 无条件跳转指令JMP

\[jmp\quad 标号 \]

相当于C语言的goto。

2. 条件跳转指令

除了A标志位,其余的标志位的状态都可以单独作为条件跳转指令的条件,如下:

  • jc 跳转指令,条件:CF=1

  • jnc 跳转指令,条件:CF=0

  • jz 跳转指令,条件:ZF=1

  • jnz 跳转指令,条件:ZF=0

  • js 跳转指令,条件:SF=1

  • jns 跳转指令,条件:SF=0

  • jp 跳转指令,条件:PF=1

  • jnp 跳转指令,条件:PF=0

  • jo 跳转指令,条件:OF=1

  • jno 跳转指令,条件:OF=0

其他的一些条件跳转指令如下:

  • ja 跳转指令,条件:CF=0 和 ZF=0
  • jab 跳转指令,条件:CF=0
  • jb 跳转指令,条件:CF=1
  • jbe 跳转指令,条件:CF=1 或者 ZF=1
  • jcxz 跳转指令,条件:CX=0
  • je 跳转指令,条件:ZF=1
  • jecxz 跳转指令,条件:ECX=0
  • jg 跳转指令,条件:ZF=0 和 SF=OF
  • jge 跳转指令,条件:SF=OF
  • jl 跳转指令,条件:SF!=OF
  • jle 跳转指令,条件:ZF=1 和 SF!=OF
  • jna 跳转指令,条件:CF=1 或者 ZF=1
  • jnae 跳转指令,条件:CF=1
  • jnb 跳转指令,条件:CF=0
  • jnbe 跳转指令,条件:CF=0 和 ZF=0
  • jne 跳转指令,条件:ZF=0
  • jng 跳转指令,条件:ZF=1 或者 SF!=OF
  • jnge 跳转指令,条件:SF!=OF
  • jnl 跳转指令,条件:SF=OF
  • jnle 跳转指令,条件:ZF=0 和 SF=OF
  • jpe 跳转指令,条件:PF=1
  • jpo 跳转指令,条件:PF=0

3.循环控制转移指令LOOP

      mov cx, 值
next:   ......
	    ......
	    ......
	  loop next

4. 应用

  • 实现无符号数条件跳转指令

    cmp N1, N2    ;若程序员认定N1,N2是无符号数
    ja  标号       ;跳转条件N1>N2
    jna 标号       ;跳转条件N1<=N2
    jc 标号        ;跳转条件N1<N2
    jnc 标号       ;跳转条件N1>=N2
    
  • 实现有符号数条件转移指令

    cmp N1, N2    ;若程序员认定N1,N2是无符号数
    jg 标号        ;跳转条件:N1的真值>N2的真值
    jge 标号       ;跳转条件:N1的真值>=N2的真值
    jl 标号        ;跳转条件:N1的真值<N2的真值
    jle 标号       ;跳转条件:N1的真值<=N2的真值
    

CALL/RET & INT/IRET

  • (CALL)调用子程序并(RET)返回。

    ;子程序编写的模板
    子程序名称  PROC
    		  .....
    		  ;实现
    		  .....
    		   RET
    子程序名称  ENDP		
    
    • 段内调用:主程序和子程序在同一个代码段,用堆栈只需保存IP。

    • 段间调用:主程序和子程序在不同的代码段,先把CS压栈,再把IP压栈。

  • INT调用中断程序,IRET返回

    \[INT\quad N \]

    N是中断号(\(0 \backsim 255\)),每一个号都对应一个中断处理程序,相比于call,int调用的是中断处理程序,且进入中断处理程序前先将标志寄存器入栈,再将CS和IP入栈。

逻辑运算指令

所有逻辑运算指令都影响P、S、Z标志,且C、O运算之后置0。

  • NOT:所有位按位取反。单操作数指令
  • AND
  • OR
  • XOR
  • TEST:相当于不改变2个操作数的值进行与运算。

    应用

    • 可以用于检测特定位是1还是0。
    • 可以用于检测寄存器是否为空。
      test ax, ax
      jnz 标号
      

移位指令

1. 开环移位指令

\[SAL/SAR/SHL/SHR\quad 操作数\quad 移动位数 \]

移动位数可以是CL或者立即数

  • SAL/SAR算术移位:左移时0补全,右移时符号位(最高位)补全。(通常用于有符号数的运算)

  • SHL/SHR逻辑移位:左移时0补全,右移时0补全。(通常用于无符号数的运算)

    基于8086实模式汇编_汇编语言_03

2.闭环移位指令

\[RCL/RCR/ROL/ROR\quad 操作数\quad 移动位数 \]

移动位数可以是CL或者立即数

基于8086实模式汇编_操作数_04

处理机控制指令

STD/CLD

通常与串传送指令配合使用。

改变控制标志寄存器中D标志的值。

  • 无操作数指令STD可以使D标志置1。
  • 无操作数指令CLD可以使D标志置0。

STC/CLC/CMC

  • STC可以使C状态标志位置1
  • CLC可以使C状态标志位置0
  • CMC可以使C状态标志位取反

STI/CLI

  • STI可以使I标(中断标志)置1
  • CLI可以使I标置0

HLT/NOP

  • HLT是暂停操作
  • NOP是空操作

串操作指令

  • 串操作书指令都是隐含指令,源串要放在DS数据段,目标串要放在ES附加段。

  • 对于16位寻址操作:

    • 访问源串一定要用SI寄存器间址访问,访问附加段一定要用DI寄存器间址访问,一定要用CX作为串计数器。
  • 对于32位寻址操作:

    • 访问源串一定要用ESI寄存器间址访问,访问附加段一定要用EDI寄存器间址访问,一定要用ECX作为串计数器。

1. 串传送指令

内存到内存。

运行完之后自动根据控制标志位D的值对应的元素属性自动修改间址寄存器的值。(控制标志位D的值决定是增量传送还是减量传送,D=0,则为增址型;D=1,则为减址型)

  • \(\color{red}{MOVSB}\):传送一个字节元素。

  • \(\color{red}{MOVSW}\):传送一个字元素。

  • \(\color{red}{MOVSD}\):传送一个双字元素。

  • \(\color{red}{REP\quad MOVSB/MOVSW/MOVSD}\):实现重复传送。

2. 串装入指令

内存到寄存器。

  • \(\color{red}{LODSB}\):将DS:[SI]的一个字节存入AL,根据控制标志位D和元素属性自动修改SI
  • \(\color{red}{LODSW}\):将DS:[SI]的一个字存入AX,根据控制标志位D和元素属性自动修改SI
  • \(\color{red}{LODSD}\):将DS:[SI]的一个双字存入EAX,根据控制标志位D和元素属性自动修改SI

3.串存储指令

寄存器到内存。

  • \(\color{red}{STOSB}\):将AL内的值存入ES:[DI]的1个单元,根据控制标志位D和元素属性自动修改DI
  • \(\color{red}{STOSW}\):将AX内的值存入ES:[DI]的2个单元,根据控制标志位D和元素属性自动修改DI
  • \(\color{red}{STOSD}\):将EAX内的值存入ES:[DI]的4个单元,根据控制标志位D和元素属性自动修改DI
  • \(\color{red}{REP\quad STOSB/STOSW/STOSD}\):实现重复存储,存储元素个数取决于计数器CX的值。

4. 串比较指令

比较源目操作数对应的元素,相等则置ZF=1,不相等则置ZF=0。

自动根据控制标志位D的值对应的元素属性自动修改间址寄存器的值。(控制标志位D的值决定是增量传送还是减量传送,D=0,则为增址型;D=1,则为减址型)

  • \(\color{red}{CMPSB}\):比较字节元素

  • \(\color{red}{CMPSW}\):比较字元素

  • \(\color{red}{CMPSD}\):比较双字元素

  • \(\color{red}{REPE\quad CMPSB/CMPSW/CMPSD}\):逐个比较多个元素是否相等,都相等则置1,出现有一个不等则置0,不再继续比较,结束当前指令。比较个数取决于计数器CX内的值。

  • \(\color{red}{RENPE\quad CMPSB/CMPSW/CMPSD}\):逐个比较多个元素是否不相等,有一个不等则置0,出现有一个相等则置1,不再继续比较,结束当前指令。比较个数取决于计数器CX内的值。

    适用于搜索字符串。

5. I/O串操作

(四)汇编源码模板

assume cs:codesg  //编译器将CS指令段寄存器与codesg段号关联
codesg segment    //标志了名为codesg的代码段开始
	... ...       //由CPU执行的指令
	
	mov ax,4c00H  //由CPU执行,实现程序返回功能
	int 21H
codesg ends       //标志了名为codesg的代码段结束
end               //标志整个程序的结束

程序返回

(五)从汇编源程序编写到可执行文件运行

\(\color{red}{编写源码文件1.asm\xrightarrow{汇编编译器编译}1.obj\xrightarrow{链接器连接}1.exe\xrightarrow{由command命令解释器加载}内存中的程序\rightarrow运行}\)

可执行文件的运行机制

在DOS系统中,系统启动时,首先进行一些初始化操作,然后运行command.com命令解释器,运行后执行完相关任务后,屏幕上才显示命令提示符,等待用户输入,用户可以输入一些如cd,dir之类的命令由command执行。
基于8086实模式汇编_寻址_05
而要想在DOS系统中执行可执行文件,那么就要输入可执行文件名,command根据文件名将其加载入内存,设置CS:IP指向程序入口处,command停止运行,CPU控制权交给该程序,程序运行完成后把CPU控制权交还给command,如此往复……

但debug程序是如何实现对程序的调试的?其实在运行debug的时候,command一如既往把CPU控制权交给debug程序,debug程序从开始运行到调试结束都没有放弃对CPU的控制,而在这个过程中debug程序把待调试程序载入内存,CS:IP指向程序入口。

基于8086实模式汇编_寻址_06