# Basic Knowledge

## 1 基础知识

### 1.1 数制、代码与计算机输入输出的基本过程

* 数制：二进制(十六进制)、十进制
  * 二进制数(值)是计算机进行处理、运算的数制，是和处理器、存储器密切相关的
  * 十六进制是**人看待机器中二进制数的数制**，是系统软件常见的数制
  * 十进制是人进行计算的数制，计算机一般不能直接进行十进制的运算
  * 计算机中的“值”一般都是值二进制，或者说是十六进制的
  * 因此，数值：255D,0FFH,11111111B,在计算机中都是二进制的，也不需要进行转换，只有在进行输入/输出，与人打交道时，才需要进行转换
* 数(值)与代码：
  * 计算机中处理或运算时，需由“值”与“值”进行运算(二进制运算)
  * 计算机从“人”那里输入(如从键盘)时，一般采用十进制，而且输入的是代码
  * 计算机为“人”输出(如在显示器上显示运算结果)时，一般采用十进制，输出的是代码
* 典型的输入输出过程如下表：

|   输入   | 运算(处理) |   输出   |
| :----: | :----: | :----: |
| ASCII码 |  二进制值  | ASCII码 |
|  (十进制) |  (16位) |  (十进制) |

* 举个例子：在16位计算机上进行12×12=121的过程

  1\) 输入：'1' '2' 31h,32h

  2\) ASCII码转换为值 0001h, 0002h

  3\) 十进制至二进制 000Ch

  4\) 输入：'1' '2' 31h,32h

  5\) ASCII码转换为值 0001h, 0002h

  6\) 十进制至二进制 000Ch

  7\) 做乘法: 000Ch× 000Ch 0079h(121D)

  8\) 输出(二 十的ASCII) '1' '2' '1'(31h,32h,31h)

### 1.2 ASCII码和汉字的GB编码

* ASCII码:
  * 8位(高位为0)的字符、数字编码，**占一个字节**
  * 美国信息交换标准码
  * 'A'—'Z' 41h — 5Ah '0'—'9' 31h — 39h
  * 'a'—'z' 61h — 7Ah 空格—20h '\0' — 00h
    * 空格不为空，有字节码20h，\0也不为空，字节码00h，NULL才是空
  * 换行—0Ah 回车 — 0Dh 换页 — 0Ch 文件尾 — 1Ah
  * DOS(Windows)文本换行——0dh,0ah; Unix文本换行——0ah
    * 所以有时候Windows下不能正常显示Unix的文本是因为缺少 0dh--\r -- 回车
* 汉字GB码:
  * 双字节双高位为1的汉字标点、符号和汉字的国标(GB2312-80)编码。
  * 分区、位分别编码，共9个汉字符号区(01-09)，72个汉字区(16-97)。
  * 每个区内有94个字，共6763(6768，40区空了5个)，也称“区位码”。
  * '、' —— A1A2h '啊' —— B0A1h&#x20;
  * 字符区从A1开始，汉字区从B0开始，区内位编码从A1开始。
* 其他编码(可选项)
  * BCD码，压缩的BCD码
  * Unicode: 对**全部语种**的双字节编码
  * BIG5 码:我国台湾省的汉字编码(13000多字)
  * 国标汉字大字符集的编码
  * ASCII码与Unicode的转换
  * GB与BIG5的转换

## 2 IBM-PC计算机组织

### 2.1 PC机的基本结构

* PC三大部分:&#x20;
  * 中央处理器(CPU)
  * 存储器
  * 输入/输出(I/O)子系统
* 一般计算机的五大部分:
  * 运算器
  * 存储器
  * 控制器
  * 输入设备
  * 输出设备
* 示意图： ![](https://i.niupic.com/images/2020/05/12/7Orb.png)
  * CPU: 16位的8086,32位的80386等，由运算器和控制器组成
  * 存储器:包括RAM, ROM等，寻址能力由地址总线确定
  * 控制器及总线控制逻辑：控制、取指、译码、取数、执行、存数等
  * I/O子系统：由大容量外存、一般I/O设备组成(一般通过接口卡连接)

### 2.2 中央处理机(以8086为主)

* 8086 CPU的三部分：ALU, 控制逻辑，寄存器组 ![](https://i.niupic.com/images/2020/05/12/7OrJ.png)
  * 字长(ALU, 寄存器) : 16位
  * 地址：20位(寻址能力为1 MB)
  * 数据总线:16位(8086), 8位(8088) 最早的PC以8088为主
* 8086/8088更进一步的结构 ![](https://i.niupic.com/images/2020/05/12/7OrO.png)

#### 2.2.1 Intel 8086的寄存器

1. 通用寄存器
   * 累加器：AX(Accumulate) —— 16位,可分为两个8位：AH, AL&#x20;
   * 基址寄存器：BX(Basic) —— 16位,可分为两个8位：BH, BL
   * 计数寄存器：CX(Count) —— 16位,可分为两个8位：CH, CL
   * 数据寄存器：DX(Data) —— 16位,可分为两个8位：DH, DL
2. 指针寄存器
   * SP —— 16位堆栈指针
     * 总是指示堆栈段中的栈顶位置，专门用于数据进栈和出栈的位置指示，只能与SS配对使用，SS:SP 组成当前堆栈
   * BP —— 16位参数指针(基址指针)
     * 指示堆栈段中一个数据区的基址位置，通常与SS配对使用
3. 变址寄存器(字符串指针)
   * SI —— 源变址，指向源串
   * DI —— 目的变址，指向目的串
   * SI/DI与DS联用，用来确定数据段中某一存储单元的偏移地址。在串处理指令中SI和DI作为隐含的源变址和目的变址寄存器，此时SI和DS联用，DI和ES联用，分别达到在数据段和附加段中寻址的目的
4. 段寄存器
   * CS(Code Segment)  —— 代码段寄存器
   * DS(Data Segment)  —— 数据段寄存器
   * SS(Stack Segment) —— 堆栈段寄存器
   * ES(Extra Segment) —— 扩展段寄存器
5. IP —— 指令指针(指令计数器)
   * CS:IP 组成当前可执行点
6. PSW —— 标志寄存器(又称"程序状态字" —— Program Status Word) ![](https://i.niupic.com/images/2020/05/12/7OvG.png)

   ![](https://i.niupic.com/images/2020/05/13/7R2f.jpg)

   * OF 溢出标志&#x20;
     * Overflow of = OV\[OVerflow] NV \[No Overflow]
     * 当进行**算术运算**的时候结果超过了最大的范围时OF=1，如：AX/BL时候结果大于256则出现除法溢出
   * DF 地址递增/减方向标志&#x20;
     * Direction df = DN \[decrement] UP \[increment]
     * DF=1:标志执行字符串的操作按照高地址向低地址方向执行
     * DF=0:标志执行字符串的操作按照低地址向高地址方向执行(在数据栈中大多用这个)
   * SF 符号标志   &#x20;
     * Sign sf = NG \[NeGative] PL \[PLus]
     * SF=1:运算后的结果为负
   * IF 中断标志
     * Interrupt if = EI\[Enable Interrupt] DI\[Disable Interrupt]
     * IF=1:CPU可以响应外部的中断请求
     * IF=0:CPU不允许响应中断请求
   * ZF 零标志&#x20;
     * ZF=1，运算结果为0；ZF=0,运算结果不为0
     * Zero zf = ZR \[zero] NZ \[Not zero]
   * TF 陷阱标志
     * 又称跟踪标志，在调试程序的时候可以直接控制CPU的工作状态
     * TF=1:单步执行，CPU每一条执行命令都进入内部的单步中断处理，以便对指令的执行情况进行检查
     * TF=0:CPU继续执行程序
   * CF 进位标志&#x20;
     * Carry cf = CY \[Carry] NC \[No Carry]
     * 表示算术运算时产生进位(加法)或借位(减法)
   * PF 奇偶标志
     * Parity pf = PE \[Parity Even] PO \[Parity Odd]
   * AF 辅助进位标志
     * 又称半进位标志
     * Auxiliary Carry af = AC\[Auxiliary Carry] NA \[Not Auxiliary carry]
     * AF=1:字节运算产生低半字节向高半字节的进位或借位
   * 在DEBUG中PSW的显示：

     ![](https://i.niupic.com/images/2020/06/15/8gKJ.png)

     ![](https://i.niupic.com/images/2020/06/15/8gKK.png)

### 2.3 存储器(8086可寻址的1MB 内存为主)

* **20根地址线** 决定了寻址能力为 2^20=1M + 字节寻址B = 1MB
* 内存中基本单元为1个字节(8位)，线性顺序存放
* 两个字节组合为1个字(16位)
  * 在内存中存放1个字时，低字节在前，高字节在后
* 两个字组合为一个双字(32位)
  * 在内存中存放双字时，低字在前，高字在后
* 在内存中可任意组合存放 字节、字、双字

#### 2.3.1 内存中字节、字、双字的存取

![内存图](https://i.niupic.com/images/2020/05/12/7PPD.png)

#### 2.3.2 逻辑地址和物理地址

* 用16位地址寄存器(指针)来表示地址时，最多可寻址 64KB。
* 要表示20位地址，需要对内存进行分段，然后用一个寄存器 **(段寄存器)** 表示段地址，用另一个寄存器 **(指针寄存器)** 表示段内地址 **(偏移值)**。
* 可将1MB内存分段，每段最大 64KB。
* 物理地址：用20位二进制表示，且与内存单元一一对应的地址
  * ![物理地址](https://i.niupic.com/images/2020/05/12/7PRh.png)
* 逻辑地址：用段地址和偏移值组合来表示的内存地址。
  * 常写成 **"段地址：偏移值"** 的形式。
  * ![逻辑地址](https://i.niupic.com/images/2020/05/12/7PRi.png)
  * 偏移地址(Offset Address) 又称 有效地址(Effective Address)，简写 EA
* 段地址×16D(10h) + 偏移地址 = 物理地址
  * 一个物理地址可能有多个逻辑地址的组合。
  * ![一个物理地址是多个逻辑地址组合](https://i.niupic.com/images/2020/05/12/7PRj.png)
  * 中间三位16进制重合然后相加得到12345h即可
* 典型的程序在内存中执行时，一般都有 **代码段、数据段、堆栈段，其段地址分别用 CS, DS, SS 来存放(指向)**。
* 需要注意：段地址+1 -> 内存空间增加16D(10H)。我们通常把16D Bytes称为1节(paragraph)，英文简写为para。

### 2.4 堆栈的组成和操作

* 堆栈是内存中由 **SS** 和 **SP** 确定的一块特殊的内存区域。它严格按照 "先进后出"(First In, Last Out) 的方式工作。
* SS 表示堆栈的基址， SP 表示当前的指针
* 举例：SS=2000h，SP=0100h，则堆栈区域为：2000h:0000h 至 2000h:00FFh，栈顶指针值为0100h
* 堆栈的增长方向为高地址向低地址增长

  ![高地址向低地址增长](https://i.niupic.com/images/2020/05/12/7PSr.png) &#x20;

|       压栈操作      |       出栈操作      |
| :-------------: | :-------------: |
|   PUSH OP（操作数）  |   POP OP（操作数）   |
|   (1) SP=SP-2   | (1) OP SS:\[SP] |
| (2) OP SS:\[SP] |   (2) SP=SP+2   |

堆栈示意图：\
![](https://i.niupic.com/images/2020/06/16/8gNA.png)

### 2.5 实际内存分配方法

* 对于实际运行的程序,会根据代码段、数据段、堆栈段的实际大小，以**节**为单位分配内存区,然后将每个段的首地址设为 seg:0000H 方式确定逻辑地址。
* 例如,假定一个程序的代码段大小为4090D,数据段大小为8180D,堆栈段大小为252D。按节方式上舍入为4096D,8192D, 256D,即1000H, 2000H, 100H。
* 设给此程序分配的内存区从21000H开始(也是节边界对齐),于是这三个段在内存中的位置如下图所示：

  ![](https://i.niupic.com/images/2020/06/16/8gND.png)

  * 内存起始地址21000H,起始段地址为2100H,代码段、数据段、堆栈段大小依次为1000H,2000H,100H,SS=DS+2000H=24000H。
  * 代码段、数据段及堆栈段的偏移地址范围分别为:0\~0FFFH, 0\~1FFFH, 0\~FFH。

## 3 寻址方式和指令系统

### 3.1 指令格式与寻址方式

* Intel 8086/8088的指令格式
  * OP Dst, Src 由 Src 至 Dst，动宾词组结果在目的操作数中可以有2个，1个，0个操作数
  * 指令编码的要素：
    * 操作码字节
    * 寻址方式字节
    * 段超越字节
  * 操作数指令长度由1-7个字节组成，变长指令编码
* 寻址方式：指令中寻找操作数的方式
  * 与数据有关的6种
    * 立即寻址，寄存器寻址
    * 直接寻址，寄存器间接寻址，寄存器相对寻址，基址变址寻址
  * 与转移有关的4种
    * 段内直接寻址，段内间接寻址，段间直接寻址，段间间接寻址
  * 与I/O有关的2种
    * 直接I/O端口寻址方式
    * 寄存器DX间接寻址方式
  * MOV Dst, Src

#### 3.1.1 与数据有关的6种寻址方式

* 操作数来自：
  * 寄存器:&#x20;
    * 8个通用寄存器：AH, AL, BH, BL, CH, CL, DH, DL
    * 4个段寄存器：CS, DS, SS, ES
    * 1个标志寄存器：PSW
  * 立即数: 指令本身给出的立即数(常量)
  * 内存单元：直接寻址和间接寻址
  * 常量、字节、字、双字的定义&#x20;
    * EQU：等于，用来赋值立即数
    * DB：Define Byte，定义一个字节
    * DW：Define Word，定义一个字
    * DD：Define Double，定义一个双字
    * 若只想声明变量而不想赋初始值，则使用 `?`，如： `x DW ?`，就声明了一个字的变量x，没有初始值
* 立即寻址：
  * 指令操作数包含在指令中，为一个常量，常量可以是8位的也可以是16位的，但是**必须要和另一个操作数类型匹配**。
  * 立即寻址方式通常用来为寄存器赋初始值，只能出现在源操作数的位置，不能出现在目的操作数位置。
  * 立即数为常数时，可以直接写入指令中；立即数为常量时，则在指令前面必须有EQU伪指令定义，或经过计算得到。
  * 样例：
    * VALUE EQU 512
    * MOV AL, 05H&#x20;
    * MOV AX, VALUE (VALUE是一个常量)
  * 反例：
    * MOV AL, 100H (AL是字节，100H超过了字节的范围)
    * MOV BL, VALUE (BL也是字节，512D=200H 超过了字节的范围)
    * MOV AX, 10000H (AX是字，10000H超过了字的范围)
* 寄存器寻址：
  * 指令操作数为CPU的寄存器，存取操作完全在CPU内部进行，不需要动用总线，访问内存，所以执行速度比较快。
  * 可以是8为操作数或16位操作数，但依然是要保证操作数类型相等。
  * 样例：
    * MOV AX, BX
    * MOV AX, 1234H (目的寄存器是寄存器寻址方式)
    * PUSH DS
  * 隐含操作数的样例：
    * PUSHF (PSW为源操作数，属于寄存器寻址)
    * STD (设置DF=1，属于寄存器寻址)
* 直接寻址：
  * 操作数的偏移地址EA直接在指令中给出
  * 样例：
    * MOV AX, \[2000H]
  * 表示一个内存单元的逻辑地址是 "段地址:偏移地址" 的形式，其中段地址位于段寄存器中。如果**没有段超越**的情况，对于一般的操作数存取而言都是相对于**数据段DS**的，所以DS可以省略不写。偏移地址是相对于段基地址而言的，表示相对于段基地址之间的距离。
  * 在执行指令时，CPU的执行单元EU要使用偏移量EA来通过**地址总线**获取操作数。对**直接寻址**来说，**偏移量EA会直接跟在指令机器码中操作码字节和寻址方式字节后面**，后面的三种寻址则不具备这种能力，都需要通过另外的寄存器计算得出。
  * 实际编程时，通常使用变量(称为符号)来定义内存中的单元。 &#x20;
  * 样例：

    * x DW ?&#x20;
    * c DB 'A'
    * MOV AX, x (这里的x在编译时期会被偏移值替换)
    * MOV AL, c (c同理)
    * MOV AX, x+1 (是将偏移量为x的地址+1单元的字送入AX寄存器，而不是将x的值+1赋值给AX，属于直接寻址)

      \`\`\`asm

      data segment para

      numbera     dw 1

      numberb     dw 0ffffh

      divisor     dw 10000, 1000, 100, 10, 1

      data ends

    code segment para assume cs:code, ds:data, ss:stack main proc far

    ```
    mov ax, stack
    mov ss, ax
    mov sp, stack_btm
    mov ax, data
    mov ds, ax

    mov ax, numbera+1 
    ```

    main endp code ends end main

    \`\`\`\
    ![](https://i.niupic.com/images/2020/06/16/8gNO.png) 可以看到numbera+1使用的是\[0001]说明是numbera的地址+1单位偏移，而不是numbera的值+1=2，得到的结果也是AX=FF00H
* 寄存器间接寻址：
  * 操作数地址EA位于一个寄存器中&#x20;
  * 寄存器只能是基址 **BX, BP**，或者变址 **SI, DI**&#x20;
  * 寄存器必须是16位，因为偏移地址EA是16位，所以不能是BX中的BH或者BL
  * 从效果上来讲，这个寄存器就是一个**地址指针**
  * 样例：
    * MOV AX, \[BX]
    * MOV BH, \[BP]
    * MOV CX, \[SI]
    * MOV DL, \[DI]
  * 注意：没有显示地指定段寄存器的情况下，BX, SI, DI 是相对于DS段而言的偏移地址；BP则是相对于SS段的偏移地址，上面的样例和下面的等价：
    * MOV AX, DS:\[BX]
    * MOV BH, SS:\[BP]
    * MOV CX, DS:\[SI]
    * MOV DL, DS:\[DI]
  * 反例：
    * MOV AX, \[DX] (DX既不是基址也不是变址，不能用来作为间接寻址寄存器)
    * MOV DL, \[BL] (只能用BX，不能用BL或者BH，因为偏移是16位)
* 寄存器相对寻址：
  * 操作数地址EA由间址寄存器+8位和16位的常量组成
  * EA = 间址寄存器的值 + 8位或16位常量
  * 这种寻址方式**不过是在寄存器间接寻址的基础上增加了一个常量**，因此这种寻址方式首先是寄存器间接寻址，然后是相对寻址。
  * 除了段超越或段前缀外，和寄存器间接寻址相同，BX, SI, DI寄存器寻址的缺省段还是DS；BP寄存器寻址的缺省段还是SS
  * 样例：
    * MOV AX, \[SI+10H]
    * MOV AX, 10H\[SI]
    * MOV AX, DS:\[SI+10H]
    * 以上三种寻写法的效果是一样的
  * 同直接寻址方式一样，这种寻址方式的**16位偏移量**可以是**符号名或变量名**。因为符号名和变量名在段内的地址偏移值实际上是固定的，所以作用等同于常量。
  * 样例：
    * MOV AX, ARRAY\[SI]
    * MOV TABLE\[DI], AL
    * MOV TABLE\[DI+1], AL
    * 若TABLE的偏移值是00A0H，则最后一条指令在汇编器汇编之后，在DEBUG下看到的是如下形式：
      * MOV \[DI+00A1H], AL
* 基址变址寻址：
  * 操作数地址EA为一个基址寄存器和一个变址寄存器之和，因此也被成为"基址加变址寻址方式"
  * 这种寻址方式的特点是指令中会出现两个寄存器，一个基址寄存器BX或BP，和一个变址寄存器SI或DI。
  * 和上面两种寻址方式相同，如果基址寄存器是BX，则缺省时段寄存器为DS；若基址寄存器是BP，则缺省时段寄存器为SS
  * 样例：
    * MOV AX, \[BX]\[SI]
    * MOV AX, DS:\[BX]\[SI]
    * MOV ES:\[BX+SI], AL
    * MOV \[BP+DI], AX
  * 在基址变址寻址方式中，也可以增加相对寻址的方式，只需要保证基址寄存器和变址寄存器都存在即可。
  * 样例：
    * MOV AX, \[BX+SI+200]
    * MOV ARRAY\[BP+SI], AX
  * 反例：
    * MOV \[BX+CX], AX (变址寄存器只能是DI或SI)
    * MOV \[BX+BP], AX (BP只能做基址寄存器不能做变址寄存器)
    * MOV \[BX+DI], ARRAY (如果ARRAY是变量，则源操作数和目的操作数都在内存中，这在语法上是不合法的)
  * Conclusion:

    |                      寻址方式                      |                              说明                             |                                                  举例                                                 |
    | :--------------------------------------------: | :---------------------------------------------------------: | :-------------------------------------------------------------------------------------------------: |
    |      <p>立即数寻址<br>immediate addressing</p>      |             <p>指令中包含立即数<br>可以直接是数字或是EQU定义的常量</p>            |                         <p>MOV AL, 05H<br>VALUE EQU 512<br>MOV AX, VALUE</p>                        |
    |       <p>寄存器寻址<br>register addressing</p>      |            <p>存取完全在CPU的寄存器中进行<br>不需要动用总线及访问内存</p>           |             <p>MOV AX, BX<br>MOV AX, 1234H<br>PUSHF ;隐含PSW寄存器<br>STD ;设置DF动用了PSW寄存器</p>             |
    |        <p>直接寻址<br>direct addressing</p>        |            <p>需要访问内存<br>但是操作数在内存中的地址直接在指令中给出</p>            |                   <p>MOV AX, \[200H]<br>x dw 100H<br>MOV BX, x ;链接时会将x的地址带入指令中</p>                  |
    | <p>寄存器间接寻址<br>register indirect addressing</p> | <p>上面的EA位于指令中<br>寄存器间接寻址的EA位于基址或变址寄存器中<br>基址和变址寄存器都只有2个</p> | <p>MOV AX, \[BX] ;相对于DS<br>MOV BX, \[BP] ;相对于SS<br>MOV CX, \[SI] ;相对于DS<br>MOV DX, \[DI] ;相对于DS</p> |
    | <p>寄存器相对寻址<br>register relative addressing</p> |                      在寄存器间接寻址的基础上加了一个常量                     |                <p>MOV AX, \[SI+10H]<br>MOV AX, ARRAY\[SI]<br>MOV TABLE\[DI+1], AL</p>               |
    |    <p>基址变址寻址<br>based indexed addressing</p>   |                     EA直接被一个基址和一个变址寄存器指定                     |      <p>MOV AX, \[BX+SI]<br>MOV ES:\[BX+SI], AL<br>MOV \[BP+DI], AX<br>MOV AX, \[BX+SI+200]</p>     |
* 掌握和理解寻址要点：

  1\) 寄存器的使用规则

  2\) 类型匹配

  3\) 数据通路

  4\) 是操作 "内容" 还是 "地址"（指针）

#### 3.1.2 与转移指令有关的寻址方式

* 标号与过程名：
  * 定义了代码段内的偏移值 —— 通过**标号或过程名**来定义 (直接)
  * 标号的定义类似于高级程序设计语言中goto的定义
  * 样例：
    * l1: MOV AX, ARRAY\[SI]
    * 上面就定义了一个 `l1` 标号，下次可以直接跳转到 `l1` 处
  * 过程名的定义发生在过程 `PROC` 中
  * 样例：
    * p1 PROC near
    *      ...
    *      RET
    * p1 ENDP
    * 或
    * p2 PROC far (通过 far 指出了所处的CS段值)
    *      ...
    *      RET
    * p2 ENDP   &#x20;
* 段内直接寻址：
  * 要转向(JMP,CALL,条件转移等)的地址由当前IP与指令中的8位或16位位移量之和
  * 例如在定义了前面的标号或子程序名后，可以使用下面的方式进行段内直接寻址。
  * 样例：
    * JMP l1 (跳转至l1处)
    * CALL p1 (先保存CALL的下一跳指令的偏移地址至堆栈中，然后跳转至p1处)
  * **与操作数的直接寻址方式不同**，上面的指令在汇编后不会立刻出现 `l1` 或 `p1` 的偏移地址，而是相对于当前的 IP 的**位移量**，因此是一种相对寻址。
  * 特别要注意的是**偏移量**的计算时的IP不是当前指令的IP，因为当前**指令被装载到指令缓冲器之后，IP指向了下一条指令**，所以在实际计算位移量的时候，要使用**跳转的目标地址与下一跳指令之间的位移量**
  * 根据偏移量的不同选择的三种不同的跳转指令：
    * **JMP SHORT l1**&#x20;
      * l1与当前IP的位移量是一个8位值(含符号)，即-127\~128之间，直接JMP默认为SHORT
      * OP码为 E8H
    * **JMP NEAR PTR l1**&#x20;
      * l1与当前IP的位移量是一个16位值(含符号)，即-32768\~32767之间
      * OP码为 E9H
* 段内间接寻址：
  * 要转向的偏移地址由一个寄存器或一个内存单元给出
    * 寄存器可以是任意一个16位寄存器
    * 内存单元在数据段中可以使用上面提到的任意一种寻址方式
      * 在使用**寄存器间接寻址**时，寄存器中保存的是**相对于本代码段(由CS确定段地址)**，而不是数据段的偏移值
      * 样例：
        * MOV AX, OFFSET p1
        * CALL AX
        * 可以看出，这种转移指令段内间接寻址和数据寄存器的间接寻址方式的区别，数据寄存器的间接寻址需要由方括号`[]`，而转移指令段内间接寻址不需要方括号，因为规定是相对本代码段CS了
      * 加强分辨样例：
        * MOV AX, OFFSET p1
        * MOV ADD1, AX
        * CALL ADD1
        * 和
        * MOV BX, OFFSET ADD1
        * CALL \[BX]
        * 跳转的目标地址是一样的
        * 对于第三条指令而言，ADD1中携带的就是跳转目标地址相对于本CS的值，所以直接作为操作数；但是对于第五条指令而言，BX存储的是ADD1所在的地址，所以在CALL时需要加一个方括号`[]`表示取地址，取出BX所在的地址存放的内容ADD1之后，再进行跳转。
      * 注意区别：
        * CALL p1 (p1是过程名，这里是段内**直接**转移)
        * CALL ADD1 (ADD1是变量名，这里是段内**间接**转义)
        * 在DEBUG模式下可能生成的指令为：
        * CALL 000A (直接跳转到000AH处)
        * CALL \[000A] (跳转到位于数据段000AH地址内的数据处)
* 段间直接寻址：
  * 在指令中直接给出了要转移至(由CALL,JMP)目标处的段地址和偏移地址
  * 要转至的标号或过程名必须具备FAR属性
  * 举例：
    * **JMP FAR PTR l1**
      * l1与当前IP的位移量大于-32768\~32767，属于段间转移，会同时修改CS和IP
      * 值得注意的是，当位移量大于-32768\~32767的时候，不使用FAR**编译可以成功，但是链接过程会失败**
      * OP码为 EAH
* 段间间接寻址：
  * 要转移的目标地址(长转移：CALL或JMP)由内存单元给出
  * 要转至的地址放在内存单元中，并且必须是一个双字 (DWORD)
  * 内存的寻址方式可以是**除了** 立即寻址 和 寄存器寻址方式 之外的任意一种数据寻址方式
  * 举例：
    * JMP DWORD PTR 基地址指针:\[偏移指针]
  * 转移后内存单元中的双字的**高字节变成了CS，低字节变成了IP**

### 3.2 指令系统

#### 3.2.1 数据传送指令

* MOV, XCHG, PUSH, POP, PUSHF, POPF, LEA, LDS, LES
* 操作符:OFFSET, SEG, BYTE PTR, WORD PTR, DWORD PTR

  ![数据通路与类型匹配](https://i.niupic.com/images/2020/05/12/7QQI.png)
* 数据之间的传输需要遵循一定的规则，上图的数据通路就是表示数据可以传输的方向

**3.2.1.1 MOV, XCHG**

* 记：ac为立即数, reg为通用寄存器, segreg为段寄存器, mem为内存, 以下数据转移是合法的：
  * MOV reg,ac
  * MOV mem, ac
  * MOV reg, reg
  * MOV mem, reg
  * MOV reg, segreg
  * MOV mem, segreg
  * MOV reg, mem
  * MOV segreg, reg
  * MOV segreg, mem
* 当使用段寄存器作目的操作数时,不允许使用cS作目的操作数。
* MOV或其他大多数指令必须遵守以下规则:
  * 源和目的必须类型匹配(8位对8位,16位对16位)。
  * 目的操作数不能为立即数。
  * 源和目的操作数不能同时为内存操作数(串指令除外)。
  * 源和目的操作数不能同时为段寄存器。
* 根据数据通路及类型匹配的规则, 如下指令虽然意图是好的, 但不正确:

  1\) MOV AX,BL;将BL送AX,且扩展成16位

  &#x20;  修改后：

  * MOV AL, BL
  * MOV AH, 0

    2\) MOV ES,DS;将ES设成与DS相同

    修改后：
  * MOV AX, DS
  * MOV ES, DS

    3\) MOV y, x;如同高级语言一样赋值:y=x

    修改后：
  * MOV AX, x
  * MOV y, AX

    4\) MOV \[DI],\[SI];内存变量传送,间接寻址

    修改后：
  * MOV AL, \[SI]
  * MOV \[DI], AL
* 有的时候,仅凭符号名或变量名很难准确地知道内存操作数的类型。由于**内存单元的基本单位是字节(byte)**,所以不管变量是如何定义的,都可以进行类型转换。
* 通过BYTE PTR , WORD PTR , DWORD PTR三个操作符,可以**明确地指定内存操作数的类型**,或进行**强制类型转换**:
* 样例：
  * MOV BYTE PTR x, AL
  * MOV WORD PTR x, AX
* 与MOV非常类似的有一条指令 **XCHG**, 表示两操作数的内容互换，其指令格式为：
  * XCHG OPR1, OPR2
* 显然，XCHG指令不允许立即数做操作数

**MOV和XCHG的执行不会对标志寄存器造成影响。**

**3.2.1.2 PUSH, POP, PUSHF, POPF**

* 这四条都是堆栈传送指令，作用如下：
  * PUSH SRC : SP = SP - 2; \[SP] <- SRC
  * PUSHF : SP = SP - 2; \[SP] <- PSW
  * POP DST : DST <- SS:\[SP]; SP = SP + 2
  * POPF : PSW <- SS:\[SP]; SP = SP + 2
* 堆栈的存取只能以字为单位，不允许以字节为操作数
* **PUSH指令可以使用CS寄存器；但是POP指令不可以使用CS寄存器**
* 可以使用除立即数外的任何数据寻址方式
* 堆栈操作也不影响标志寄存器
* 堆栈严格遵守先进后出(First In, Last Out)
* 在调用程序时，常常将需要保存的数据存放入堆栈中(PUSH)，调用程序结束后再取出(POP)
* 堆栈的重要原则就是**保证SP的稳定**，**开始时候的SP和结束时候的SP应该相等**
* 利用堆栈可以传递内容：
  * PUSH AX
  * POP BX
  * 等价于： MOV BX, AX

**3.2.1.3 LEA, LDS, LES**

* LEA, LDS 和 LES三条指令的作用是把地址送到指定寄存器。这三条指令的格式如下:
  * LEA reg, src
    * Load Effective Address
    * 将源操作数src的**偏移地址**送入reg中，**并不是获取里面的内容！**
  * LDS reg, src&#x20;
    * Load Data Segment
    * 将src中的**双字内容**依次送入reg及DS中，reg存储低字节，DS存储高字节
  * LES reg, src&#x20;
    * Load Extend Segment
    * 将src中的**双字内容**依次送入reg及ES中，reg存储低字节，ES存储高字节
  * 注：上面三条指令的reg不能为**段寄存器**

**3.2.1.4 取变量or取地址**

* 当变量名直接位于指令中时，都是直接寻址获得此变量的内容。
* 若要获得其偏移地址EA，需要使用LEA指令。
* 有两个特殊的操作符也可以获得地址：
  * MOV BX, **OFFSET** x 等价于 LEA BX, x —— 获取x的偏移地址EA
  * MOV BX, **SEG** x 等价于 MOV BX, DS —— 获取x的段地址

#### 3.2.2 算术运算指令：

* ADD, ADC, SUB, SBB, INC, DEC, CMP, NEG, MUL, IMUL, CBW, DIV, IDIV, CWD

**3.2.2.1 加减法指令**

* ADD dst, src 普通加 dst <- dst + src + CF
* ADC dst, src 进位加 dst <- dst + src + CF
* INC opr  自增 opr <- opr + 1
* SUB dst, src 普通减 dst <- dst - src
* SBB dst, src 借位减 dst <- dst - src - CF
* DEC opr  自减 opr <- opr - 1
* 除了INC及DEC指令不影响CF(进位标志)外,所有指令都会影响标志寄存器,其中最重要的是 CF, ZF, SF, OF, 分别表示进位、结果为零、符号和溢出。
  * ZF：如果运算结果为0,则ZF=1。
  * SF：等于运算结果dst的最高位,即符号位。
  * CF：如果加法时有进位或减法时有借位，则CF=1。因为减法实质上是补码加,所以借位也是进位。
  * OF：根据操作数的符号及其变化情况确定:若两个操作数的符号相同,而相加结果的符号与之相反时,OF=1,否则OF=0。
* 显然,**OF表示的是带符号数运算的溢出**,**CF则表示的是无符号数的溢出**。
* 由于OF要根据两个操作数的符号及其变化来确定,所以OF=1时,说明运算产生了溢出。而如果是两个无符号数进行运算,由于无符号数的最高位也有数值意义而没有符号意义，所以CF表示的正是两数相加的进位或两数相减时的借位。CF=1,有进位或借位;CF=0则无进位或借位。
* 样例:
  * ADD AX,0FF00H  ;AX与立即数相加，当最高位为A\~F时要再补一个0,以免与变量名相混淆
  * ADD AX, WORD PTR\[BX+SI+100]   ;AX与一个基址变址寻址的内存操作数相加,结果在AX中
  * ADD WORD PTR\[SI],13H    ;内存操作数与立即数相加
  * SUB x,AX    ;x与AX相减,结果在x中
* 反例：
  * ADD x, y &#x20;
    * 不允许源操作数和目的操作数都为内存操作数,直接寻址不可以,间接寻址也不可以。
    * 修改后：
      * MOV AX, y
      * ADD x, AX
  * ADD \[SI], 13H&#x20;
    * 因为CPU不知道\[SI]内存单元究竟是一个字还是一个字节,因为减13H对它们来说都是可能的。
    * 修改后：
      * ADD WORD PTR\[SI], 13H
  * SUB AX, BL
    * 类型不匹配
    * 修改后：
      * MOV BH, 0
      * SUB AX, BX

**3.2.2.2 32位加减法**

* 思路，先安排好32位，然后先做低地址的16位普通加减(ADD, SUB), 再做高地址的进位、借位加减(ADC, SBB)
* 32位减法：Z = X - Y&#x20;
  * MOV DX, WORD PTR X+2
  * MOV AX, WORD PTR X
  * SUB AX, WORD PTR Y
  * SBB DX, WORD PTR Y+2
  * MOV WORD PTR Z, AX
  * MOV WORD PTR Z+2, DX
* 32位加法：Z = X + Y
  * MOV DX, WORD PTR X+2
  * MOV AX, WORD PTR X
  * ADD AX, WORD PTR Y
  * ADC DX, WORD PTR Y+2
  * MOV WORD PTR Z, AX
  * MOV WORD PTR Z+2, DX
* 16位加至32位：Z = X + Y&#x20;
  * MOV DX, 0
  * MOV AX, X
  * ADD AX, Y
  * ADC DX, 0
  * MOV WORD PTR Z, AX
  * MOV WORD PTR Z+2, DX

**3.2.2.3 CMP与NEG指令**

* CMP(比较)和NEG(求补)指令是两条特殊的指令。
* NEG指令用于求操作数的补码。
* CMP指令则比较两个数的大小,它的本质是**用目的操作数减源操作数**,但**结果不送回**到目的操作数。
  * 因为做了减法,所以会产生标志位,而这些标志位正是进行条件转移时的“条件”。
* NEG OPR   ;OPR <- -OPR
* CMP OPR1, OPR2  ; OPR1-OPR2, 结果不送回, 影响标志位

**3.2.2.4 乘法指令MUL和IMUL 以及 CBW指令**

* 乘法指令分为带符号数乘法(IMUL)和无符号数乘法两种。
* 格式:&#x20;
  * MUL src
  * IMUL src
* MUL指令执行的操作为:
  * **字节**操作数 —— 8位X8位, AX <- AL \* src
  * **字**操作数 —— 16位X16位, DX:AX <- AX \* src&#x20;
    * 其中,DX:AX为32位乘积,DX为高16位,AX为低16位。
* IMUL的操作与MUL相同,只是**MUL乘的是无符号数**,**IMUL乘的是有符号数**。
* 在乘法指令中,隐含的目的操作数是AL(8位X8位)或AX(16位X16位)。
* src可以是**通用寄存器**或**任意一种寻址方式所确定的内存操作数**。
  * 如果src为8位操作数,则隐含的被乘数为AL,乘积为AX;如果src为16位操作数,则隐含的被乘数为AX,乘积为DX:AX。
  * 注意:src不能为立即数。
* 究竟是使用MUL还是IMUL,取决于你怎样看待操作数(包括乘数和被乘数)。
* 样例：
  * 如果 AL= 1111111B , BL=11111111B&#x20;
  * 使用 MUL BL 指令, 则 1111111B *1111111B = 255D* 255D = 65205D。
  * 如果使用 IMUL BL 指令, 则是 (-1) \* (-1) = 1 。
* 对同样的操作取 AL 及 BL , 使用 MUL 指令及 IMUL 指令,在 AX 中的乘积值是不一样的。
* 乘法指令会影响标志位。但除了CF和OF以外的标志位无定义。
* 从语法上说, 乘法指令最容易让初学者犯错误的地方是, 乘数不能为立即数。
* 样例：
  * AX = X\*10
  * 正确形式：
    * MOV AL, 10
    * MUL X
  * 错误形式：
    * MOV AL, X
    * MUL 10 (操作数不能是立即数)
* 对乘法指令的另一个要注意的地方是不能以16位操作数乘8位操作数, 必须将**8位操作数扩展成16位操作数**。
* 对**无符号8位操作数**, 扩展成16位时, 只需将高8位清零。
* 对**有符号的8位操作数**, 扩展成16位时, 要使用**CBW指令**。
* **CBW指令无参数**, 它的作用是将AL的符号扩展至AH。
  * CBW: Convert signed Byte to Word
  * 如果AL的最高位为0,则AH = 00H
  * 如果AL的最高位为1,则AH = 0FFH

**3.2.2.5 除法指令DIV和IDIV以及CWD指令**

* 除法是乘法的逆运算。DIV为无符号数除法, IDIV为有符号数除法。
* 格式为
  * DIV src   ;无符号数除法
  * IDIV src  ;有符号数除法
* DIV 指令执行的操作数为：
  * **字节操作数** —— 16位被除数放在AX中，8位src为除数，结果为8位的商在AL中，8位余数在AH中
    * AX/src -> AL(商) ... AH(余)
  * **字操作数** —— 32位被除数在DX:AX中，16位src为除数，结果为16位的商在AX中，16位余数在DX中
    * DX:AX/src -> AX(商) ... DX(余)
* 除法指令对所有的条件码都**没有定义**，但**除法溢出的问题是很严重的**。
* 除法指令的操作数寻址方式与乘法相同，不能是立即数。
* 必须用**32位除以16位** 或 **16位除8位**，其他的都是不合法的。
* 样例：
  * 无符号除法，被除数16位情况：x/BX = AX ... DX
    * MOV AX, x
    * MOV DX, 0
    * DIV BX
  * 有符号除法，被除数16位情况：x/BX = AX ... DX
    * MOV AX, x
    * CWD
    * IDIV BX
* **CWD指令与CBW类似，无参数，作用是将AX的符号位扩展至DX中**
  * CWD: Convert signed Word to Double word
  * 如果AX的最高位为0，则DX = 00H
  * 如果AX的最高位为1，则DX = 0FFFFH
* 除法指令必须 **主动考虑溢出问题**。
  * 如果产生除法溢出，会触发0号中断，导致程序出错。
  * 反例：
    * MOV AX 65535
    * MOV BL 10
    * DIV BL
    * 这里 AX/BL = 6553 > 0FFH 所以结果不能存入 AL 中，导致除法溢出
  * 修改后：
    * MOV AX 65535
    * MOV BX 10
    * MOV DX 0 (**千万不要忘记清零**)
    * DIV BX
    * 将16位除以8位会溢出转换为32位除以16位不会溢出的情况

#### 3.2.3 逻辑运算指令：

1. 逻辑运算指令.

   共有四条逻辑运算指令,它们分别是AND(逻辑“与”)、OR(逻辑“或”)、XOR(逻辑“异或”)和NOT(逻辑“非”)。它们的格式是:

   * AND dst, src&#x20;
   * OR dst, src &#x20;
   * XOR dst, src&#x20;
   * NOT dst
   * 上述四条指令都是**按位进行逻辑运算**的。
   * 源操作数和目的操作数的寻址方式与ADD指令一样,即可以是通用寄存器或内存操作数,但**不能两者都是内存操作数**。
   * 源操作数可以使用立即数,但目的操作数却不能。
   * NOT指令对标志寄存器各位均无影响，而其他三条指令对标志寄存器都有影响:
     * SF ,ZF,PF会根据运算结果确定
     * CF,DF总是0
     * AF不确定。
   * 逻辑运算指令的主要用途是对操作数的某一位或若干位进行操作。可用逻辑指令来组合、屏蔽、分离或设置某些位。例如:
     * AND AL, 0FH ;将高4位清零
     * AND AL, 0F0H ;将低4位清零
     * AND AL, 0FEH ;将最低位清零&#x20;
     * OR AL, 80H ;将最高位置1
     * XOR AL, AL ;AL清零,比 MOV AL,0 效率高
     * OR AL, 30H ;OR之前AL=0\~9,则将AL中的值变ASCII码
     * AND AL, 0FH ;AND之前AL=30H\~39H,則將AL中的値変カ0\~9
2. TEST

   测试指令的作用类似于AND,但它不将运算结果送目的操作数,**而是为了产生标志位**,主要是ZF。它的格式是:

   * TEST dst, src ;src与dst执行逻辑“与”,但结果不送dst
   * TEST命令的主要作用是测试一个对象中某1位或某几位的状态。因为它不改变目的操作数,所以通常将被测试对象放在目的操作数位置上,然后把测试用的位模式(bit pattern)放入源操作数位置上。
   * 例如,要测试一个字节的最高位是否为1,可如下进行:
     * MOV AL, status-byte
     * TEST AL, 80H
     * JZ IS\_NOT\_SET ; ZF=1，说明AND为0，说明高位为0
     * JNZ IS\_SET&#x20;
3. 移位指令 移位指令共有八条,它们的指令格式分别是:

   |    名称   |       命令       |            全称            |
   | :-----: | :------------: | :----------------------: |
   |   逻辑左移  | SHL dst, count |    Shift Logical Left    |
   |   算术左移  | SAL dst, count |  Shift Arithmetical Left |
   |   逻辑右移  | SHR dst, count |    Shift Logical Right   |
   |   算术右移  | SAR dst, count | Shift Arithmetical Right |
   |   循环左移  | ROL dst, count |        Rotate Left       |
   |   循环右移  |  ROR dst，count |       Rotate Right       |
   | 带进位循环左移 | RCL dst, count |  Rotate Left with Carry  |
   | 带进位循环右移 | RCR dst, count |  Rotate Right with Carry |

   * 一图流：

     ![](https://i.niupic.com/images/2020/06/16/8gM5.png)
   * 记忆方式：
     * 首先所有都要影响CF位（也会影响CF、PF、ZF、SF、OF但是不关心）
     * 逻辑和算术的区别在于是否会影响原来的符号，逻辑是无符号的，算术是有符号的，所以SAR会保持最高位
     * ROR和RCR的区别就是下一位是从自己来的还是CF来的
   * Mention：
     * 这里的count只能是1或者CL，不能是其他的寄存器
     * 实现32位乘4可以通过将两个寄存器的值左移两位，但是千万不能一次移动两位，要低字节SHL一位之后，高字节RCL一位

#### 3.2.4 控制转移指令：

* JMP (short, near, word, far,dword)
  * 段内直接短转移 JMP SHORT PTR 标号: 转移的IP地址由当前的IP地址+8位位移量组成，所以寻址范围为-128～+127，所以循环不要写太大
  * 段内直接转移 JMP NEAR PTR 标号: 移的IP地址由当前的IP地址+16位位移量组成，标号可以在同一代码段的任何一个位置(段寄存器确定之后，偏移寄存器就是16位，所以可以到达段内的任何位置)
  * 段内间接转移 JMP WORD PTR寄存器或内存单元: 间接寻址提供的偏移值是相对于当前代码段(CS)的,此偏移值可以位于任何一个通用寄存器或以任何内存寻址方式确定的单元中
  * 段间直接转移 JMP FAR PTR 标号: 标号必须定义为FAR,转移时除了修改IP外,还会修改CS
  * 段间间接转移 JMP DWORD PTR 内存单元: 转移地址只能位于内存单元中,且是一个双字,**高位字是转移内存目的地的CS值,低位字是转移目的地的IP值**
  * 当标号或者操作数很明确的时候可以不带上范围的关键词，如：
    * JMP l1 ; 要么是short，要么是near
    * JMP BX ; 等价于 JMP WORD PTR BX
    * JMP \[BX] ; 取出BX地址内的字进行IP的位移量跳转
    * JMP DWORD PTR \[BX] ; 需要在内存中取出CS:IP，DWORD不能省略
    * JMP DWORD PTR add1 ; 从数据段内取出CS:IP，进行跳转，DWORD不能省略
* 标志寄存器的跳转(使用的是JMP short，范围有限)：
  * JZ(或JE) 标号 ;如果ZF=1,则转移至标号处
  * JNZ(或JNE) 标号 ;如果ZF=0,则转移至标号处
  * JC 标号 ;如果CF=1,则转移至标号处
  * JNC 标号 ;如果CF=0,则转移至标号处
  * JO 标号 ;如果OF=1,则转移至标号处
  * JNO 标号 ;如果OF=0,则转移至标号处
  * JS 标号 ;如果SF=1,则转移至标号处
  * JNS 标号 ;如果SF=0,则转移至标号处
  * JP(或JPE) 标号 ;如果PF=1,则转移至标号处
  * JNP(JPO) 标号 ;如果PF=0,则转移至标号处
* 无符号比较(使用的是JMP short，范围有限)：
  * JA(或JNBE) 高于或不低于不等于转移
  * JAE(或JNB,JNC) 高于等于,不低于或进位位为0时转移
  * JE(或JZ) 等于或结果为0时转移
  * JBE(或JNA) 低于等于或不高于转移
  * JB(或NAE,JC) 低于,或不高于等于,或进位位为1时转移
  * JNE(或JNZ) 不等于,或结果不为0时转移
* 对于有符号数比较后的转移,则有如下一些指令(使用的是JMP short，范围有限):
  * JG(或JNLE) 大于或不小于等于转移
  * JGE(或JNL) 大于等于转移
  * JE(或JZ) 等于或结果为0时转移
  * JLE(或JNG) 小于等于或不大于转移
  * JL(或JNGE) 小于或不大于等于转移
  * JNE(或JNZ) 不等于,或结果不为0时转移&#x20;
* JCXZ label: 当CX=0时转移
* LOOP,LOOPZ,LOOPNZ
  * 循环指令则用于构造循环程序。利用循环指令来编写循环的典型结构如下:

    ```
      MOV CX, N ;先预置循环次数,以及其他循环条件
    lp-body:
      ... ;循环体,每循环一次,都重复执行一遍
      LOOP lp-body ;循环指令,且每执行一次,CX-1,CX不为0时,转移至lp_ body,cx为0时,顺序往下执行
    ```
  * LOOP指令的格式是: LOOP label
  * 它的执行步骤是:
    1. CX=CX-1
    2. 如果CX≠0,则转移至标号处;如果CX=0,则顺序往下执行。
  * 还有另外两条循环指令:
    * LOOPZ/LOOPE 当结果为0时或相等时循环
      * 当CX≠0且ZF=1时转移
    * LOOPNZ/LOOPNE 当结果不为0或不相等时循环
      * 当CX≠0,ZF=0时转移
    * 这两条指令与LOOP指令的执行步骤相似:先进行CX=CX-1,然后判断CX是否为0,同时还判断ZF的值。
* CALL(near,word,far,dword)
  * 与前面介绍的JMP指令相比,CALL指令的不同之处在于转移至dst处前,**先将CALL下一条指令的偏移地址入栈(段内转移),或将CALL下一条指令的偏移地址和段地址入栈**。
  * 在CALL指令之后的下一条指令地址称为返回地址。
  * CALL指令的执行过程如下:
    1. 段内直接调用:

       ```
       CALL dst
       SP=SP-2 ;SS:[SP]←返回地址偏移值
       IP=IP+D16
       ```
    2. 段内间接调用:

       ```
       CALL dst
       SP=SP-2 ;SS:[SP]←返回地址偏移值
       IP=dst中的有效地址
       ```
    3. 段间直接调用:

       ```
       CALL dst 
       SP=SP-2 ; SS:[SP]←返回地址段值
       SP=SP-2 ; SS:[SP]←返回地址偏移值
       IP=dst的偏移地址
       CS=dst的段地址
       ```
    4. 段间间接调用:

       ```
       CALL dst
       SP=SP-2 ;SS:[SP]←返回地址段值
       SP=SP-2 ;SS:[SP]←返回地址偏移值
       IP=EA的低16位
       CS=EA的高16位
       ```

       其中,EA是CALL指令中dst寻址确定后的有效地址(Effective Address)。
* INT,IRET
  * 从过程中返回至调用处的指令为RET.RET指令一般放置在过程的最后。RET指令的执行过程如下:
    1. 段内返回: RET&#x20;

       ```
       IP←[SP] ;
       SP=SP+2 ;
       ```
    2. 段内带立即数返回: RET exp

       ```
       IP←[SP] ;
       SP=SP+2 ;
       SP=SP+exp ;
       ```
    3. 段间返回: RET

       ```
       IP←[SP] ;
       SP=SP+2 ;
       CS←[SP] ;
       SP=SP+2 ;
       ```
    4. 段间带立即数返回: RET exp

       ```
       IP←[SP] ;
       SP=SP+2 ;
       CS←[SP] ;
       SP=SP+2 ;
       SP=SP十exp ;
       ```
  * 究竟是**段内返回**还是**段间返回**,是由过程定义时的属性确定的。
  * 如果是NEAR属性的过程,则RET是段内返回,经汇编器汇编后,RET指令仍然是RET。
  * 如果是FAR属性的过程,则RET是段间返回,经汇编器汇编后,RET指令变成了**RETF**。
  * 在RET指令后带上立即数,会使RET返回后再将SP加上此立即数。这相当于从堆栈中弹出了几个参数。立即数必须为偶数。有些过程在被调用前,先压进了一些参数,因而需要在返回时修改SP,**保持堆栈的平衡**。

#### 3.2.5 处理器控制命令

* 处理器控制命令没有参数,它们用于CPU的内务操作。这些指令是:
  * CLC ;CF<-0
  * STC ;CF<-1&#x20;
  * CMC ;CF求反
  * CLD ;DF<-0
  * STD ;DF<-1
  * CLI ;IP<-0
  * STI ;IP<-1
  * NOP ;无操作,但消耗一个时钟周期
  * HLT ;停机
  * WAIT ;等待
  * ESC mem ;换码
  * LOCK ;封锁指令

#### 3.2.5 串处理指令：

* 串操作隐含的是相对于DS段和ES段的SI和DI间接寻址：
  * DS:\[SI] -> ES:\[DI]
  * MOVS：串传输指令
    * MOVSB ; Byte
    * MOVSW ; Word
    * 将 DS:\[SI] 中的字节或字取出，防区 ES:\[DI] 中，同时根据DF和字节或字对SI和DI进行加或减操作
    * **在MOV指令中，我们说源操作数和目的操作数不能同时是内存操作数，但是使用MOVSB/MOVSW却可以，他的源操作数和目的操作数都是寄存器间接寻址的内存单元**
  * LODS：取串指令
    * LODSB ; DS:\[SI] -> AL
    * LODSW ; DS:\[SI] -> AX
    * 根据DF和字节或字对SI进行加或减操作
  * STOS：存串指令
    * STOSB ; AL -> ES:\[DI]
    * STOSW ; AX -> ES:\[DI]
    * 根据DF和字节或字对DI进行加或减操作
  * CMPS：串比较指令
    * CMPSB ; CMP byte ptr DS:\[SI], byte ptr ES:\[DI]
    * CMPSW ; CMP word ptr DS:\[SI], word ptr ES:\[DI]
    * 实际上也是做减法：DS:\[SI]-ES:\[DI]，所以会影响标志寄存器 CF,SF,ZF，从而判断两者的大小，后面通常跟上跳转指令
    * 根据DF和字节或字对SI和DI进行加或减操作
  * SCAS：串扫描指令
    * SCASB ; scan if ES:\[DI]'s string contains AL
    * SCASW ; scan if ES:\[DI]'s string contains AX
      * 注意SCASW每次结束后将DI+2所以可能会导致出现遗漏，如判别 0dh,0dh,0ah,0ah 中是否含有 0dh,0ah 就不能使用SCASW判定
    * 实际上是使用AL/AX减去ES:\[DI]从而影响比爱毮只寄存器
    * 根据DF和字节或字对DI进行加或减操作
  * REP：重复前缀指令
    * 前面的指令都是一次性的，包括串比较和串扫描，所以需要一个重复指令
    * e.g. REP MOVSB 会重复传送 DS:\[SI] -> ES:\[DI] 直到CX=0(同循环)
  * REPE/REPZ 和 REPNE/REPNZ：条件重复前缀指令
    * REPE/REPZ: 当 CX!=0 && ZF=1 时执行串操作指令，同时CX--
    * REPNE/REPNZ: 当 CX!=0 && ZF=0 时执行串操作指令，同时CX--
  * CLD/STD
    * CLD: clear DF flag 字符串在内存中从低地址到高地址
    * STD: set DF flag 字符串在内存中从高地址到低地址
