Be cautious of tutorials(tutors) that teach you bunch of instructions

when you first learn assembly.

Because the read idea behind assmebly language is how the processor works,

not what those little words mean.

0x00 Preface

学校终于也是开了汇编语言这门课(笑

我们学校使用的汇编语言是基于8086 CPU的汇编语言,虽然我之前有过在80x86(specifically, 80386)和IA-64的汇编语言经验,但是对更加古老的8086系列CPU确实没有什么经验。

写这个博客的目的之一,也是希望能够加深自己对这方面的理解吧(毕竟要考试www

主要的参考资料是 Intel 8086 Manual, 网上有很多,想看的话可以自己去找()

0x01 The 8086 Family

Microprocessor Characteristics

标准的运行速度是 5MHz,可以买到8MHz的版本;

  • 有40个引脚;
  • 处理器可以处理8位和16位的数据类型;内部的数据通路至少有16位宽;
  • 最多可以寻址1MB的内存,和一块额外的64KB大小的I/O空间。
  • 地址/数据传输接口是相同的(即:数据和地址总线由处理器进行基于时间的复用)

8086可以执行在两种模式下:在最小模式,CPU处理所有所需的总线控制信号;在最大模式,CPU假设有一个8288总线控制器在处理系统总线,因此CPU原先用于处理这些信号的引脚可以被用于提供支持多处理器的信号。

Interrupt Controller

8086使用8259A中断控制器。这种中断控制器支持8个中断源,每个中断源会被赋予一个代表优先级的数字criticality(危急程度),来反映它对整个系统的影响程度。8259A有一个内建的优先级处理机制。一般来说,当危急程度更高的中断发生时,它会向CPU发出一个中断请求。如果CPU接受了,它会接着发送一个代表中断源的代码。

Bus Organization

  Private System Bus                              Public System Bus
 Priv.   |                                               |
Memory<->|           Bus                   Bus           |<-> Processing Module
         |  <->   Interface              Interface   <-> |
  Priv.  |           Group    Processor    Group         |<-> Public Memory
  I/O <->|             ↕         ↕           ↕           |
                    ----------------------------------   |<-> Public I/O
                          ↕    Local Bus   ↕             |
                       Processor         Processor       |<-> Processing Module

公共系统总线左边的部分是一个处理模块的组成内容。

处理模块由被一个局部总线连接的处理器和总线接口组组成。一个简单的处理模块包含一个处理器和一个总线接口组。当一个公共系统总线有多个处理模块连接时,所有的公共IO和公共内存都可以被所有的处理模块访问。每个处理模块内部的8289总线控制器负责控制这些访问。

每个处理模块的局部总线可以连接另外一个总线,形成仅该处理模块访问的私有内存和I/O。

0x02 Processor Overview

     +------8086-----+
  GND|1            40|VCC
 AD14|2            39|AD15
 AD13|3            38|A16/S3
 AD12|4            37|A17/S4
 AD11|5            36|A18/S5
 AD10|6            35|A19/S6
  AD9|7            34|BHE/S7
  AD8|8            33|MN/MX
  AD7|9            32|RD
  AD6|10           31|HOLD(RO/GT0)
  AD5|11           30|HLDA(RO/GT1)
  AD4|12           29|WR(LOCK)
  AD3|13           28|M/IO(S2)
  AD2|14           27|DT/R(S1)
  AD1|15           26|DEN(S0)
  AD0|16           25|ALE(OS0)
  NMI|17           24|INTA(OS1)
 INTR|18           23|TEST
  CLK|19           22|READY
  GND|20           21|RESET
     +---------------+

0x03 Processor Architecture

一般来说,微处理器通过反复执行以下过程来运行一个程序。

  1. 从内存读取下一条指令。
  2. 读取操作数(if required)
  3. 执行指令
  4. 写出结果(if required)

8086将这四步分配给两个不同的处理单元:

  • 执行单元:执行指令
  • 总线接口单元:读取指令、操作数、写出结果

这两个单元可以独立工作,从而能够广泛的进行指令覆盖。在这种情况下,读取指令的时间看起来“消失了”,从而达到比较高的性能水平。

Execution Unit

一个执行单元内含:

  • 数学/逻辑单元(ALU):处理操作数、通用寄存器,管理标志位和CPU状态。所有的寄存器和数据通路都是16位的。
  • 通用寄存器
  • 标志位

执行单元必须通过请求总线接口单元来执行获取/写出数据的操作。总线接口单元使得执行单元能够访问全部1MB的内存空间。

Bus Interface Unit

一个总线接口单元内含:

  • 所有段寄存器和指令指针IP:指示当前执行的指令和当前执行的段
  • 地址生成和总线控制:用于控制对内存等的总线IO
  • 指令队列:存储预加载的指令序列,8086的指令队列可以存储6字节的指令。

8086的BIU一次指令预加载加载2字节的指令。当出现跳转指令时,BIU将会清空指令队列,并且立即开始准备重新填充它。预加载仅加载地址上紧邻当前执行指令的指令。当EU请求I/O时,指令预取会被暂停。

General Registers

DATA:
15              0
+----------------+
|    AX(AH+AL)   | ACCUMULATOR
+----------------+
|    BX(BH+BL)   | BASE
+----------------+
|    CX(CH+CL)   | COUNT
+----------------+
|    DX(DH+DL)   | DATA
+----------------+
POINTER & INDEX:
15              0
+----------------+
|       SP       | Stack Pointer
+----------------+
|       BP       | Base Pointer
+----------------+
|       SI       | Source Index
+----------------+
|       DI       | Destination Index
+----------------+

一些指令隐式地使用某些通用寄存器。

注:与现代的CPU不同(现代处理器支持两个操作数

REGISTER | Operations
   AX    | Word Multiply, Word Divide, Word I/O
   AL    | Byte Multiply, Byte Divide, Byte I/O, Translate, Decimal Arithmetic
   AH    | Byte Multiply, Byte Divide
   BX    | Translate
   CX    | String Operations, Loops
   CL    | Variable Shift & Rotate
   DX    | Word Multiply, Word Divide, Indirect I/O
   SP    | Stack Operations
   SI    | String Operations
   DI    | String Operations

所有的指针/数据寄存器都可以参加绝大多数的数学/逻辑指令。

Flags

  • AF:8位进位标志。当设置时,代表出现了8位操作数高半字节的进位或借位。
  • CF:进位标志。当设置时,代表出现了高字节的进位或借位。旋转操作也可能会使用这个位来临时存放。
  • OF:溢出标志。当设置时,代表有一个有效位由于大小限制而丢失了。
  • SF:符号标志。当设置时,代表结果的最高位是1.
  • PF:奇偶标志。当设置时,代表结果中有偶数个1.
  • ZF:零标志。当设置时,代表结果为0.

可编程的标志位如下 :

  • DF:当设置时,使字符串操作变为递减(即,从高地址向低地址处理)
  • IF:当设置时,使CPU能够处理可屏蔽的中断
  • TF:当设置时,使CPU进入单步调试模式(即,每执行一个指令后产生一个内部中断信号)

0x03 Memory Layout, SRs and IP

Segment Registers

15              0
+----------------+
|       CS       | Code Segment
+----------------+
|       DS       | Data Segment
+----------------+
|       SS       | Stack Segment
+----------------+
|       ES       | Extra Segment
+----------------+

8086的1MB内存分为64K大小的逻辑段,这些段的基地址保存在段寄存器中,处理器能够同时直接访问四个段,分别是代码段、数据段、栈段和额外段。

Instruction Pointer

IP指向BIU下一条将被预取的指令。但是如果你试图将它保存到栈上,那么它会自动变为下一条将被执行的指令。

Storage Organization of Memory

Alignment

  • 双字节数据的起始地址需要是偶数,否则会产生性能损失
  • 双字节指令则不需要

Byte Order

Most significant byte in the higher memory location!

Far Pointers

远指针用于定位当前段外的指令,它占4字节大小,从低到高位分别是IP:CS。

Segmentation

8086的程序通过一个个64KB大小的段来使用整个1MB内存空间。

每个段有一个基地址,代表它在内存中的起始地址。所有的起始地址必须与16字节对齐。

Address Translation

8086的地址有两种:物理地址和逻辑地址。

物理地址是从0x0~0xFFFFF的值,代表了物理内存中的唯一位置。

逻辑地址用于编写程序时,允许程序被加载到内存的任意位置,并且允许动态内存资源管理。逻辑地址由段基地址和偏移量组成。不同的逻辑地址可以指向同一个物理地址;段基地址和偏移都是无符号双字节整数。

我们使用的基地址是实际段物理基地址右移4位的值。

从逻辑地址转换到物理地址需要首先将基地址左移4位(16字节;段对齐),然后加上偏移量。如果超过64K大小,将会被对64K取余。

BIU对内存访问时使用的段寄存器如下:

      TYPE        | Def. SegBase | Alt. SegBase | Offset
Instruction Fetch |      CS      |     NONE     |   IP
Stack Operation   |      SS      |     NONE     |   SP
     Variable     |      DS      |   CS,ES,SS   |   Effective Addr.
(Except following)
  String Source   |      DS      |   CS,ES,SS   |   SI
  String Dest.    |      ES      |     NONE     |   DI
BP as Base Reg.   |      SS      |   CS,DS,ES   |   Effective Addr.

字符串操作与其他操作略有不同:源操作数被认为位于当前数据段中,但是可以指定其他段;偏移量来自SI,目标操作数被认为位于当前额外段中,它的偏移量来自DI。字符串操作会自动修改SI和DI的值。

Dedicated & Reserved Memory Locations

0x0~0x7F; 0xFFFF0~0xFFFFF 用于中断和系统重置。