前言

在考研过程中好好学了一下计算机组成原理, 让我对底层这些东西产生了浓厚的兴趣, 因此就先学学基础汇编

本文章 是掺杂8086 和 x86一起总结的, 可能有些地方在8086里面不具备

基本信息

  • 机器字长为 16位 存储字长为16位 地址线20根 数据线16根
  • 由于机器字长只有16位, 地址线有20根, 计算物理地址需要特别处理 物理地址 = 段地址*16 + 段内地址 (物理地址表示不唯一)
  • AX,BX,CX,DX 都是通用寄存器, 用来存放数据 段地址寄存器有: CS DS ES SS 偏移地址寄存器有: SP,BP,SI,DI,IP,BX
  • CS:IP 指定下一条执行指令的位置, SS:SP 指定栈顶位置 (进栈操作 SP-字节数 放入数据, 出栈操作 拿出数据 SP+数据字节数) DS , ES是数据段位置
  • 数据在内存中都是小端存放

寄存器

下面是8086 CPU的主要寄存器及其英文全称和功能:

  1. AX (Accumulator Register):累加寄存器。

    • 主要用于算数运算和数据传输。
  2. BX (Base Register):基址寄存器。

    • 除了可以用于算术运算外,它还经常用于地址运算。 默认使用ds作为段地址
  3. CX (Count Register):计数寄存器。

    • 通常用于循环计数。
  4. DX (Data Register):数据寄存器。

    • 用于算数运算和某些特殊指令,如某些I/O操作。
  5. SI (Source Index):源索引寄存器。 默认使用ds作为段地址

    • 主要用于字符串操作,表示源字符串的地址。
  6. DI (Destination Index):目标索引寄存器。默认使用es作为段地址

    • 主要用于字符串操作,表示目标字符串的地址。
  7. BP (Base Pointer):基指针寄存器。

    • 通常用于基于堆栈的参数和局部变量的地址引用。 默认使用 ss作为段地址
  8. SP (Stack Pointer):堆栈指针寄存器。

    • 始终指向堆栈的顶部,随着数据的压入和弹出而变化。

此外,8086还有段寄存器,用于段地址运算:

  1. CS (Code Segment):代码段寄存器。

    • 指向当前执行代码的段。
  2. DS (Data Segment):数据段寄存器。

    • 用于大多数数据操作指令的默认段。
  3. SS (Stack Segment):堆栈段寄存器。

    • 指向堆栈的段。
  4. ES (Extra Segment):附加段寄存器。

    • 为某些字符串操作提供一个附加的段选择。

8086还有一个特殊的寄存器:

  1. IP (Instruction Pointer):指令指针。
    • 与CS寄存器一起,CS:IP总是指向下一条要执行的指令。

以上列举的是8086微处理器的主要寄存器。这些寄存器为低级编程提供了大量的灵活性和功能。

标志位寄存器

下面是8086微处理器中标志位寄存器的总结,以及在DEBUG工具中显示的符号及其意义:

  1. CF (Carry Flag) - 表示进位或借位

    • 值: 0 或 1
      • 0 (NC: No Carry) 表示没有进位
      • 1 (CY: Carry) 表示有进位
  2. PF (Parity Flag) - 反映了结果字节中1的数量。

    • 值: 0 或 1
      • 0 (PO: Parity Odd) 表示1的数量为奇数
      • 1 (PE: Parity Even) 表示1的数量为偶数
  3. AF (Auxiliary Carry Flag) - 在BCD(二进制编码十进制)运算中使用,当低4位产生进位或借位时设置。

    • 值: 0 或 1
      • 0 (NA: No Auxiliary Carry) 表示低4位没有进位
      • 1 (AC: Auxiliary Carry) 表示低4位产生了进位
  4. ZF (Zero Flag) - 如果最后一个操作的结果为零,则设置ZF。

    • 值: 0 或 1
      • 0 (NZ: Not Zero) 表示结果不为零
      • 1 (ZR: Zero) 表示结果为零
  5. SF (Sign Flag) - 反映了最后操作的结果的符号。

    • 值: 0 或 1
      • 0 (PL: Positive) 表示结果为正数或零
      • 1 (NG: Negative) 表示结果为负数
  6. TF (Trap Flag) - 允许单步执行。

    • 值: 0 或 1
      • 0 (NT: No Trap) 表示TF未被设置
      • 1 (TR: Trap) 表示TF被设置
  7. IF (Interrupt Flag) - 控制外部中断的响应。

    • 值: 0 或 1
      • 0 (DI: Disable Interrupts) 禁止中断
      • 1 (EI: Enable Interrupts) 允许中断
  8. DF (Direction Flag) - 控制字符串操作的方向。

    • 值: 0 或 1
      • 0 (UP: Increment) 字符串操作地址自增
      • 1 (DN: Decrement) 字符串操作地址自减
  9. OF (Overflow Flag) - 在有符号运算中,如果结果超出了有符号数值范围,则设置OF。

    • 值: 0 或 1
      • 0 (NV: No Overflow) 表示没有溢出
      • 1 (OV: Overflow) 表示有溢出

总的来说,ALU不是基于有符号和无符号数的概念来执行操作的。而是执行二进制运算,然后设置或清除相关标志以提供有关操作结果的信息。然后,程序员或编译器可以根据这些标志来解释和处理结果。

Debug的使用

通过debug 能够查看寄存器的内容, 以及内存的内容, 并且指令执行跟踪

  1. A [address]: Assemble
    • 用于在指定地址开始手动汇编代码。
    • 当你输入A然后按Enter,你可以开始输入汇编指令。
    • 使用空行结束汇编。
  2. D [range]: Dump
    • 用于显示内存中的内容。
    • D后面可以跟一个地址范围,如D 100 150,表示显示从100到150的内存内容。
  3. E address [list]: Enter
    • 用于修改指定地址的内存内容。
    • 例如,E 100 90 91 92会将地址100、101、102的内存值分别设为90、91、92。
    • 询问式更改 : E 地址 空格继续,回车结束
  4. G [=address] [breakpoints]: Go
    • 执行程序直到遇到指定的断点。
    • G=100表示从地址100开始执行。
  5. I port: Input
    • 从指定的I/O端口读取值。
    • 例如,I 60会读取端口60的值。
  6. L range address: Load
    • 从磁盘上的文件加载数据到指定地址的内存。
  7. N [filename]: Name
    • 指定一个文件名,用于后续的读/写操作。
  8. R [register]: Register
    • 查看或修改CPU寄存器的值。
    • 输入R会显示所有寄存器的值。R AX会显示AX寄存器的值,并允许你修改它。
  9. T [=address] [count]: Trace
    • 单步执行指令。
    • T=100 5表示从地址100开始执行,并执行5条指令。
  10. U [range]: Unassemble
    • 将指定内存地址的机器代码反汇编为汇编语言。
    • U 100 150表示将从100到150的地址反汇编。
  11. Q: Quit
    • 退出DEBUG。

指令

数据传送指令

  1. MOV dest, src:

    • 功能:传送源操作数(src)的值到目标操作数(dest)。
    • 用法
      • MOV AX, BX:将BX寄存器的值复制到AX寄存器。
      • MOV AL, [BX]:将地址BX处的字节值复制到AL寄存器。
      • MOV [BX], AL:将AL的值存到地址BX处。
    • 注意点:不能直接在两个内存操作数之间进行数据传送。例如,MOV [1000h], [2000h] 是非法的。
  2. PUSH src:

    • 功能:将源操作数的值推送到堆栈顶部。
    • 用法
      • PUSH AX:将AX寄存器的内容推送到堆栈。
      • PUSH DS:将段寄存器DS的内容推送到堆栈。
    • 注意点:可以推送所有的16位寄存器和段寄存器,但不能直接推送8位寄存器和内存字节。
  3. POP dest:

    • 功能:从堆栈顶部取一个值并放入目标操作数。
    • 用法
      • POP BX:从堆栈中弹出顶部值并放入BX寄存器。
      • POP ES:从堆栈中弹出顶部值并放入段寄存器ES。
    • 注意点:可以弹出到所有的16位寄存器和段寄存器,但不能直接弹出到8位寄存器和内存字节。
  4. LEA reg, mem:

    • 功能:加载有效地址到指定的寄存器,不是传送内存处的数据,而是传送数据的地址。
    • 用法
      • LEA AX, [BX+SI]:将BX和SI的和(即它们表示的内存地址)加载到AX。
    • 注意点:通常用于地址计算,不改变内存内容。
  5. LDS reg, mem:

    • 功能:加载指针到数据段(DS)和寄存器。
    • 用法
      • LDS AX, [BX]:将地址BX处的两个字作为AX的内容,接下来的两个字作为DS的内容。
    • 注意点:经常在准备进行内存访问之前使用。
  6. LES reg, mem:

    • 功能:加载指针到额外段(ES)和寄存器。
    • 用法
      • LES BX, [SI]:将地址SI处的两个字作为BX的内容,接下来的两个字作为ES的内容。
    • 注意点:与LDS类似,但影响ES而不是DS。
  7. XCHG op1, op2:

    • 功能:交换两个操作数的值。
    • 用法
      • XCHG AX, BX:交换AX和BX寄存器的内容。
      • XCHG AL, [BX]:交换AL和地址BX处的字节值。
    • 注意点:该指令可以用于任意寄存器对,也可以用于一个寄存器和一个内存位置。
  8. MOVSB:

    • 功能:从源字符串位置(由DS:SI指定)传送一个字节到目标字符串位置(由ES:DI指定),并根据DF标志更新SI和DI。
    • 用法
      • MOVSB:从DS:SI处复制一个字节到ES:DI处。
    • 注意点:如果DF=0,SI和DI都会递增;如果DF=1,SI和DI都会递减。
  9. MOVSW:

    • 功能:从源字符串位置(由DS:SI指定)传送一个字(两个字节)到目标字符串位置(由ES:DI指定),并根据DF标志更新SI和DI。
    • 用法
      • MOVSW:从DS:SI处复制一个字到ES:DI处。
    • 注意点:与MOVSB类似,但操作的单位是一个字而不是一个字节。

算术指令

当然可以,以下是8086中关于算术指令的详细总结:

  1. ADD dest, src:

    • 功能:将两个操作数相加并将结果存储在目标操作数中。
    • 用法
      • ADD AX, BX:将AX和BX的值相加,并将结果存放在AX中。
    • 注意点:可能会设置或清除AF、CF、OF、PF、SF和ZF标志。
  2. ADC dest, src:

    • 功能:将两个操作数与进位标志(CF)相加并将结果存储在目标操作数中。
    • 用法
      • ADC AX, BX:将AX、BX和CF的值相加,并将结果存放在AX中。
    • 注意点:这个指令在多字节或多字的加法操作中非常有用,其中一个字节或一个字的加法可能会影响到下一个字节或字的加法。
  3. SUB dest, src:

    • 功能:从目标操作数中减去源操作数,并将结果存放在目标操作数中。
    • 用法
      • SUB AX, BX:从AX的值中减去BX的值,并将结果存放在AX中。
    • 注意点:可能会设置或清除AF、CF、OF、PF、SF和ZF标志。
  4. SBB dest, src:

    • 功能:从目标操作数中减去源操作数和进位标志(CF)。
    • 用法
      • SBB AX, BX:从AX的值中减去BX和CF,并将结果存放在AX中。
    • 注意点:与ADC类似,SBB在多字节或多字的减法操作中很有用。
  5. MUL src:

    • 功能:无符号乘法。将AX与源操作数相乘。
    • 用法
      • MUL BX:如果源操作数是8位,结果将是16位,存放在AX中;如果源操作数是16位,结果将是32位,高16位存放在DX中,低16位存放在AX中。
    • 注意点:如果结果是16位数,则DX将被清零;如果结果是32位数,DX将包含高16位。
  6. IMUL src:

    • 功能:有符号乘法。将AX与源操作数相乘。
    • 用法
      • IMUL BX:与MUL类似,但考虑操作数的符号。
    • 注意点:与MUL不同,IMUL是用于有符号数的乘法。
  7. DIV src:

    • 功能:无符号除法。将DX:AX与源操作数相除。
    • 用法
      • DIV BX:如果源操作数是8位,那么AX将被视为被除数,结果的商将存放在AL中,余数将存放在AH中。如果源操作数是16位,则DX:AX将被视为被除数,结果的商将存放在AX中,余数将存放在DX中。
    • 注意点:除数不应为零。如果是,会触发一个除以零的中断。
  8. IDIV src:

    • 功能:有符号除法。将DX:AX与源操作数相除。
    • 用法
      • IDIV BX:与DIV类似,但考虑操作数的符号。
    • 注意点:与DIV不同,IDIV用于有符号数的除法。
  9. INC dest:

    • 功能:将目标操作数增加1。
    • 用法
      • INC AX:将AX增加1。
    • 注意点:可能会设置或清除AF、OF、PF、SF和ZF标志,但不会影响CF标志。
  10. DEC dest:

    • 功能:将目标操作数减少1。

      • 用法
        • DEC AX:将AX减少1。
    • 注意点:可能会设置或清除AF、OF、PF、SF和ZF标志,但不会影响CF标志。

  11. NEG dest:

    • 功能:求目标操作数的二进制补码。

      • 用法
        • NEG AX:将AX的值替换为其二进制补码。
    • 注意点:如果操作数为0,则CF被清除;如果操作数不为0,则CF被设置。

  12. CMP dest, src:

    • 功能:比较两个操作数,实际上执行一个SUB操作但不保存结果,只设置标志。

      • 用法
        • CMP AX, BX:比较AX和BX的值。
    • 注意点:可能会设置或清除AF、CF、OF、PF、SF和ZF标志。

这些是8086的主要算术指令。每当执行算术指令时,都应该注意相关的标志,因为它们提供了关于操作结果的信息,如是否产生进位、溢出等。

逻辑指令

  1. AND dest, src:

    • 功能:执行位与操作。
    • 用法
      • AND AX, BX:将AX与BX执行按位与,并将结果存放在AX中。
    • 注意点:会设置或清除OF、CF、PF、SF、ZF标志。OF和CF始终被清零。
  2. OR dest, src:

    • 功能:执行位或操作。
    • 用法
      • OR AX, BX:将AX与BX执行按位或,并将结果存放在AX中。
    • 注意点:OF和CF标志始终被清零。
  3. XOR dest, src:

    • 功能:执行位异或操作。
    • 用法
      • XOR AX, BX:将AX与BX执行按位异或,并将结果存放在AX中。
    • 注意点:当两个操作数相同时,结果为0。OF和CF标志始终被清零。
  4. NOT dest:

    • 功能:求操作数的一元反码(按位取反)。
    • 用法
      • NOT AX:求AX的一元反码。
    • 注意点:此指令不影响标志。

移位指令

  1. SHL dest, countSAL dest, count:

    • 功能:将目标操作数向左移动指定的位数。
    • 用法
      • SHL AX, 1:将AX左移1位。
    • 注意点:移出的最后一位存储在CF中,最高位的空位被清零。
  2. SHR dest, count:

    • 功能:将目标操作数向右移动指定的位数。
    • 用法
      • SHR AX, 1:将AX右移1位。
    • 注意点:移出的最后一位存储在CF中,最低位的空位被清零。
  3. SAR dest, count:

    • 功能:算术右移,保持最高位不变。
    • 用法
      • SAR AX, 1:将AX算术右移1位。
    • 注意点:移出的最后一位存储在CF中。
  4. ROL dest, count:

    • 功能:循环左移操作数。
    • 用法
      • ROL AX, 1:将AX循环左移1位。
    • 注意点:移出的最后一位存储在CF中,并从右边重新输入。
  5. ROR dest, count:

    • 功能:循环右移操作数。
    • 用法
      • ROR AX, 1:将AX循环右移1位。
    • 注意点:移出的最后一位存储在CF中,并从左边重新输入。
  6. RCL dest, count:

    • 功能:带进位的循环左移。

      • 用法
        • RCL AX, 1:将AX和CF一起循环左移1位。
    • 注意点:移出的最后一位存储在CF中。

  7. RCR dest, count:

    • 功能:带进位的循环右移。

      • 用法
        • RCR AX, 1:将AX和CF一起循环右移1位。
    • 注意点:移出的最后一位存储在CF中。

比较指令

  1. TEST dest, src:

    • 功能:执行逻辑与操作,但结果不存储,仅用于设置标志。

      • 用法
        • TEST AX, BX:将AX和BX进行逻辑与操作。
    • 注意点:此指令与AND类似,但不会存储结果。

控制转移指令

8086的控制转移指令可以分为三类:无条件跳转、条件跳转和循环控制。以下是它们的详细总结:

1. 无条件跳转指令

  1. JMP destination:
    • 功能:跳转到指定的地址。
    • 用法
      • JMP LABEL:跳转到LABEL标签指定的地址。
    • 注意点:该跳转是无条件的,执行此指令后,总会跳转到指定的地址。

2. 条件跳转指令

这些指令基于标志寄存器中的某个或某些标志的状态来决定是否进行跳转。

  1. JE/JZ destination (Jump if Equal/Zero):

    • 功能:如果ZF标志为1,则跳转。
    • 用法
      • JE LABEL:如果上一指令的结果为零(ZF=1),则跳转到LABEL。
  2. JNE/JNZ destination (Jump if Not Equal/Not Zero):

    • 功能:如果ZF标志为0,则跳转。
    • 用法
      • JNE LABEL:如果上一指令的结果非零(ZF=0),则跳转到LABEL。
  3. JB/JNAE/JC destination (Jump if Below/Not Above or Equal/Carry):

    • 功能:如果CF标志为1,则跳转。
    • 用法
      • JB LABEL:如果上一指令产生了进位(CF=1),则跳转。
  4. JAE/JNB/JNC destination (Jump if Above or Equal/Not Below/No Carry):

    • 功能:如果CF标志为0,则跳转。
    • 用法
      • JAE LABEL:如果上一指令没有产生进位(CF=0),则跳转。

… 以此类推。8086有一系列的条件跳转指令,如:JL, JGE, JO, JNO, JP, JNP, JS, JNS 等,它们基于OF、SF、ZF、PF和CF的组合来决定是否跳转。

3. 循环控制指令

  1. LOOP destination:

    • 功能:递减CX,并且如果CX不为0,则跳转。
    • 用法
      • LOOP LABEL:减少CX的值。如果CX不为0,则跳转到LABEL。
    • 注意点:无论是否跳转,CX都会递减。
  2. LOOPE/LOOPZ destination (Loop while Equal/Zero):

    • 功能:递减CX,如果CX不为0且ZF为1,则跳转。
    • 用法
      • LOOPE LABEL:如果上一指令的结果为零且CX不为0,则跳转。
  3. LOOPNE/LOOPNZ destination (Loop while Not Equal/Not Zero):

    • 功能:递减CX,如果CX不为0且ZF为0,则跳转。
    • 用法
      • LOOPNE LABEL:如果上一指令的结果非零且CX不为0,则跳转。

这些指令都依赖于特定的条件。在使用这些指令时,程序员需要确保在跳转之前设置了相应的条件(通常是上一个算术或逻辑指令的结果)。而循环控制指令则常常与算术指令结合使用,以创建计数器控制的循环。

标志操作指令

8086的标志操作指令主要用于操作或测试标志寄存器(Flags Register)中的特定标志。以下是相关的标志操作指令的详细总结:

1. CLC

  • 功能:清除进位标志(Carry Flag, CF)。
  • 用法
    • CLC
  • 注意点:此指令将CF置为0,不影响其他标志。

2. CMC

  • 功能:反转进位标志(Complement Carry Flag)。
  • 用法
    • CMC
  • 注意点:如果CF原来是1,则将其变为0;如果原来是0,则将其变为1。

3. STC

  • 功能:设置进位标志。
  • 用法
    • STC
  • 注意点:此指令将CF置为1,不影响其他标志。

4. CLD

  • 功能:清除方向标志(Direction Flag, DF)。
  • 用法
    • CLD
  • 注意点:此指令将DF置为0,这意味着在使用字符串指令时,SI和DI寄存器会递增。

5. STD

  • 功能:设置方向标志。
  • 用法
    • STD
  • 注意点:此指令将DF置为1,这意味着在使用字符串指令时,SI和DI寄存器会递减。

6. CLI

  • 功能:清除中断标志(Interrupt Flag, IF)。
  • 用法
    • CLI
  • 注意点:此指令将IF置为0,禁止外部中断。必须谨慎使用以避免禁止长时间的中断。

7. STI

  • 功能:设置中断标志。
  • 用法
    • STI
  • 注意点:此指令将IF置为1,允许外部中断。

8. LAHF

  • 功能:加载标志到AH寄存器。
  • 用法
    • LAHF
  • 注意点:此指令将当前的标志(SF, ZF, AF, PF, CF)的值加载到AH寄存器中。

9. SAHF

  • 功能:将AH寄存器的内容存储到标志寄存器。
  • 用法
    • SAHF
  • 注意点:此指令将AH寄存器中的值加载到标志寄存器中的标志(SF, ZF, AF, PF, CF)。

10. PUSHF

  • 功能:将标志寄存器的内容压入堆栈。
  • 用法
    • PUSHF
  • 注意点:标志寄存器的当前值被压入堆栈中。

11. POPF

  • 功能:从堆栈中弹出值到标志寄存器。
  • 用法
    • POPF
  • 注意点:堆栈的顶部值被加载到标志寄存器中。

这些标志操作指令允许程序员直接控制或测试标志寄存器中的标志。当使用这些指令时,需要特别注意确保不会不小心改变无意中改变的标志,因为这可能会影响程序的其他部分。

中断

8086 微处理器中的中断系统是该处理器响应外部和内部事件的一种机制。在8086中,中断可以分为两类:硬件中断和软件中断。

  1. 硬件中断

    • 外部中断:由外部事件触发,例如外部设备发出的请求。这些中断通常用于设备驱动程序中,使得微处理器能够响应外部设备的需求。
    • NMI(非屏蔽中断):是一个特殊的外部中断,它不能被程序禁用。在8086中,NMI由特定的硬件条件触发,如电源故障。
    • INTR(可屏蔽中断):不同于NMI,INTR是可以被程序通过CLI指令禁用或通过STI指令启用的。
  2. 软件中断

    • 通过指令(如INT指令)明确生成。在8086中,软件中断常常用于系统调用或其他高级功能,例如BIOS中的服务调用。

中断过程
当中断发生时,8086会完成当前的指令,然后执行以下动作:

  • 当前的标志寄存器、CS和IP被推送到堆栈中。
  • 根据中断类型和中断号,从中断向量表(IVT)中查找中断处理程序的地址。IVT位于物理地址0000:0000到0000:03FF的内存区域,其中每个中断使用一个4字节的条目(2字节的偏移量和2字节的段地址)
  • 微处理器从IVT中取得中断处理程序的段地址和偏移量,然后跳转到该地址以执行中断处理程序。
  • 中断处理程序执行结束后,通常使用IRET指令返回到被中断的程序。

总之,8086的中断系统为微处理器提供了与外部设备交互和执行系统级服务的机制。

补充

CMOS RAM

访问的时候直接使用这个数就行, 比如 0,2,4,6,8, 拿出来的数据都是一个字节

  • 00h: 存储秒信息
  • 02h: 存储分钟信息
  • 04h: 存储小时信息
  • 06h: 存储一周中的某一天的信息
  • 07h: 存储月中的某一天的信息
  • 08h: 存储月份信息
  • 09h: 存储年份信息

偏移地址寄存器与段地址寄存器的默认对应

在x86架构中,段寄存器和偏移寄存器一起为我们提供了物理地址。在实模式下(比如早期的DOS系统),通过段地址和偏移地址的组合,我们得到物理地址:[ \text{物理地址} = (\text{段地址} \times 16) + \text{偏移地址} ]

在实模式下,不同的指令或操作经常有一个默认的段寄存器。以下是x86架构中各偏移寄存器默认对应的段寄存器:

  1. BX、SI、DI:默认与DS(数据段寄存器)相关联。
  2. SP、BP:默认与SS(堆栈段寄存器)相关联。
  3. IP:默认与CS(代码段寄存器)相关联。IP是指令指针寄存器,用于指向当前执行的指令。

但是,这些默认的段:偏移关系可以通过使用段重写前缀来更改。例如,你可以使用ES:前缀来明确地指定使用ES段寄存器与DI或其他寄存器组合。

在受保护模式和长模式中(例如,现代32位和64位操作系统中),段寄存器的行为和意义与实模式中有所不同。在这些模式下,段寄存器主要是用于访问描述符表,而不是简单地提供物理地址的基地址。但了解实模式下的默认段:偏移组合对于理解x86架构的历史和基本概念很有帮助。