汇编语言-王爽-Notes

各种常见寄存器的名称:

  • AX:累加器寄存器(Accumulator Register)的低16位,缩写自“Accumulator”。

  • BX:基址寄存器(Base Register)的低16位,缩写自“Base”。

  • CX:计数寄存器(Count Register),用于循环计数,缩写自“Count”。

  • DX:数据寄存器(Data Register),也可以用于一些算术运算,缩写自“Data”。

  • SI: Source Index,(源索引寄存器),用于存储源操作数地址。

  • DI: Destination Index,(目标索引寄存器),用于存储目标操作数地址。

  • SP: Stack Pointer,(栈指针),指向当前栈顶位置。

  • BP: Base Pointer,(基址指针),通常用于指向函数堆栈帧中的参数和局部变量。

  • IP: Instruction Pointer,(指令指针),存储下一条要执行的指令地址。

  • CS: Code Segment,(代码段寄存器),存储代码段的起始地址。

  • SS: Stack Segment,(栈段寄存器),存储堆栈段的起始地址。

  • DS: Data Segment,(数据段寄存器),存储数据段的起始地址。

  • ES: Extra Segment,(附加段寄存器),通常用于存储附加数据段的起始地址。

  • PSW: Program Status Word,(程序状态字),存储 CPU 执行指令时的状态信息,例如标志位、中断使能等。也被称为标志寄存器。

第1章 基础知识

1.1 机器语言

机器语言是机器指令的集合, 电子计算机的机器指令是一列二进制数字, 计算机将之转变为一列高低电平.

每一种微处理器, 由于硬件设计和内部结构不同, 就需要用不同的电平脉冲来控制, 使它工作, 因此每一种微处理器都有自己的机器指令集, 也就是机器语言.

1.2 汇编语言的产生

汇编指令是机器指令便于记忆的书写格式, 也就是助记符.

1.3 汇编语言的组成

3 类指令:

  • 汇编指令: 机器码的助记符, 有对应的机器码
  • 伪指令: 没有对应的机器码, 由编译器执行, 计算机并不执行
  • 其他符号: 由编译器识别, 没有对应的机器码

1.5 指令和数据

指令和数据是应用上的概念.

在内存或磁盘上, 指令和数据没有任何区别, 都是二进制信息.

CPU 在工作时有时候把信息看作指令, 有时候看作数据, 为同样的信息赋予了不同的意义.

如:

1
2
1000100111011000  ->  89D8H (数据)
1000100111011000 -> mov ax, bx (程序)

1.7 CPU 对存储器的读写

CPU 要想进行数据的读写, 必须和外部器件进行下面 3 类信息的交互:

  • 存储单元的地址 (地址信息)
  • 器件的选择, 读或写的命令 (控制信息)
  • 读或写的数据 (数据信息)

也就有 3 类总线:

  • 地址总线
  • 控制总线
  • 数据总线

1.8 地址总线

一个 CPU 有 N 根地址线, 则可以说这个 CPU 的地址总线的宽度为 N, 这样的 CPU 最多可以寻找 $2^N$ 个内存单元.

1.9 数据总线

数据总线的宽度决定了 CPU 和外界的数据传送速度.

1.10 控制总线

这里的控制总线是个总成, 控制总线是一些不同控制线的集合, 有多少根控制总线, 就意味着 CPU 提供了对外部器件的多少种控制.

1.14 各类存储芯片

从读写属性上看分为两类:

  • RAM (Random Access Memory, 随机存储器), 可读可写. 必须带电存储, 关机后存储的内容丢失
  • ROM (Read Only Memory, 只读存储器), 只能读取不能写入, 关机后其中的内容不会丢失

1.15 内存地址空间

CPU 将系统中各类存储器看作一个逻辑存储器:

这个逻辑存储器就是 内存地址空间

每个物理存储器在这个逻辑存储器中占有一个地址段, 即一段地址空间, CPU 在这段地址空间中读写数据, 实际上就是在相应的物理存储器中读写数据.

第2章 寄存器

在 CPU 中:

  • 运算器进行信息处理
  • 寄存器进行信息存储
  • 控制器控制各种器件进行工作
  • 内部总线连接各种器件, 在他们之间进行数据的传输

程序员可以通过改变各种寄存器中的内容来实现对 CPU 的控制.

不同的 CPU, 寄存器的个数, 结构是不同的.

8086CPU 中的 14 个寄存器为:

  • AX:累加器寄存器(Accumulator Register)的低16位,缩写自“Accumulator”。

  • BX:基址寄存器(Base Register)的低16位,缩写自“Base”。

  • CX:计数寄存器(Count Register),用于循环计数,缩写自“Count”。

  • DX:数据寄存器(Data Register),也可以用于一些算术运算,缩写自“Data”。

  • SI: Source Index,(源索引寄存器),用于存储源操作数地址。

  • DI: Destination Index,(目标索引寄存器),用于存储目标操作数地址。

  • SP: Stack Pointer,(栈指针),指向当前栈顶位置。

  • BP: Base Pointer,(基址指针),通常用于指向函数堆栈帧中的参数和局部变量。

  • IP: Instruction Pointer,(指令指针),存储下一条要执行的指令地址。

  • CS: Code Segment,(代码段寄存器),存储代码段的起始地址。

  • SS: Stack Segment,(栈段寄存器),存储堆栈段的起始地址。

  • DS: Data Segment,(数据段寄存器),存储数据段的起始地址。

  • ES: Extra Segment,(附加段寄存器),通常用于存储附加数据段的起始地址。

  • PSW: Program Status Word,(程序状态字),存储 CPU 执行指令时的状态信息,例如标志位、中断使能等。也被称为标志寄存器。

2.1 通用寄存器

AX, BX, CX, DX 这 4 各寄存器通常用来存放一般性的数据, 被称为 通用寄存器.

这 4 各 16 位寄存器都可分为两个可独立使用的 8 位寄存器来用:

  • AX 可分为 AH 和 AL
  • BX 可分为 BH 和 BL
  • CX 可分为 CH 和 CL
  • DX 可分为 DH 和 DL

(“L” 指 Low, “H” 指 High)

2.2 字在寄存器中的存储

  • 字节: 记为 byte, 一个字节由 8 个 bit 组成
  • 字: 记为 word, 一个字由两个字节组成

2.3 几条汇编指令

在写一条汇编指令或一个寄存器的名称时 不区分大小写 , 如:

1
2
mov ax. 18
MOV AX. 18

等价.

2.5 16 位结构的 CPU

其结构特性为:

  • 运算器一次最多可以处理 16 位的数据
  • 寄存器的最大宽度为 16 位
  • 寄存器和运算器之间的通路为 16 位

2.6 8086CPU 给出物理地址的方法

用两个 16 位地址合成的方法来形成一个 20 位的物理地址.

两个地址位:

  • 段地址
  • 偏移地址

物理地址的计算方法位:

1
物理地址 = 段地址 x 16  + 偏移地址

x16 也就是左移 4 位 ( $2^4$ ).

示意图为:

2.7 “段地址 x 16 + 偏移地址 = 物理地址” 的本质含义

可以理解为:

1
基础地址 + 偏移地址 = 物理地址

即, CPU 在访问内存时, 用一个基础地址 (段地址 x 16) 和一个相对于基础地址的偏移地址相加, 给出内存单元的物理地址.

2.8 段的概念

注意两点:

  • 段地址 x 16 必然是 16 的倍数, 所以一个段的其实地址也一定是 16 的倍数
  • 偏移地址为 16 位, 16 位地址的寻址能力为 64KB, 所以一个段的长度最大位 64KB

CPU 可以用不同的段地址和偏移地址形成同一个物理地址.

2.9 段寄存器

8086 CPU 有 4 个段寄存器:

  • CS: Code Segment,(代码段寄存器),存储代码段的起始地址。

  • SS: Stack Segment,(栈段寄存器),存储堆栈段的起始地址。

  • DS: Data Segment,(数据段寄存器),存储数据段的起始地址。

  • ES: Extra Segment,(附加段寄存器),通常用于存储附加数据段的起始地址。

2.10 CS 和 Ip

CS 和 IP 指示了 CPU 当前要读取指令的地址:

  • CS 为代码段寄存器
  • IP 为指令指针寄存器

在 8086 机中, 任意时刻, CPU 将 CS:IP 指向的内容当作指令执行.

读取一条指令后, IP 中的值自动增加. 增加的值为当次所读取的指令的长度.

1
IP = IP + 所读取指令的长度

2.11 修改 CS, IP 的指令

在 CPU 中, 程序员能够用指令读写的部件只有寄存器, 程序员可以通过改变寄存器中的内容实现对 CPU 的控制.

可以用 mov (传送指令), 或 jmp (跳转指令) 来修改 CS, IP 的内容.

如用 jmp 段地址:偏移地址, 可以同时修改 CS, IP 的内容:

1
2
jmp 2AE3:3
jmp 3:0B16

若想用 jmp 仅修改 IP 的内容, 可用 jmp 某一合法寄存器:

1
2
jmp ax
jmp bx

其用寄存器中的值修改 IP.

2.12 代码段

要让 CPU 执行放在代码段中的指令, 必须要将 CS:IP 指向所定义的代码段中的第一条指令的首地址.

实验 1 查看 CPU 和 内存, 用机器指令和汇编指令编程

Alt + Enter 可将 DOSBOX 换为全屏.

常见的 DEBUG 命令:

  • R, (Register): 查看, 改变 CPU 寄存器的内容
  • D, (Display): 显示内存中的内容
  • E, (Enter): 进入内存中修改内容
  • U, (Unassemble): 将内存中的机器指令翻译成汇编指令
  • T, (Trace): 跟踪执行程序, 执行一条机器指令
  • A, (Assemble): 将汇编指令转换为二进制指令并写入内存

第3章 寄存器(内存访问)

3.1 内存中字的存储

字单元 的概念, 存放一个字型数据 (16 位) 的内存单元, 由来两个地址连续的内存单元组成, 高地址内存单元中存放字型数据的高位字节, 低地址内存单元中存放字型数据的低位字节.

将起始地址为 N 的字单元简称为 N 地址字单元, 如一个字节单元由 2, 3 两个内存单元组成, 则这个字单元的起始地址为2, 也就可以称为 2 地址字单元.

DS 和 [address]

8086 CPU 中的 DS (Data Segment) 寄存器, 通常用来存放要访问的数据的段地址.

如, 读取 10000H 单元的内容:

1
2
3
mov bx, 1000H
mov ds, bx
mov al, [0]

mov 可以:

  • 将数据直接送入寄存器
  • 将一个寄存器中的内容送入另一个寄存器
  • 将一个内存单元的内容送入一个寄存器
  • 将一个寄存器的内容送入一个内存单元

[...] 表示一个内存单元.

[...] 中的 0 表示内存单元的偏移地址.

此时的段地址自动取自 ds 中的数据.

注意 , 8086 CPU 不允许直接将数据送入段寄存器. 因此需要一个寄存器来进行中转.

3.3 字的传送

如:

1
2
3
4
mov bx, 1000H
mov ds, bx
mov ax, [0]
mov [0], cx

3.4 mov, add, sub 指令

mov 指令的用法:

也有:

1
mov 寄存器, 段寄存器

addsub 指令的用法:

3.6 栈

这里讨论的栈, 单指: 一种具有特殊访问方式的存储空间 (LIFO, Last In First Out)

两个操作:

  • 入栈, 将一个新的元素放到栈顶
  • 出栈, 从栈顶取出一个元素

CPU 提供的栈机制

两个基本指令:

  • push, 入栈

    1
    push ax

    ax 中的数据送入栈中.

  • pop, 出栈

    1
    pop ax

    从栈顶取出数据送入 ax.

字型数据用两个单元存放, 高地址单元存放高 8 位, 低地址单元存放低 8 位.

两个寄存器来控制栈:

  • SS: Stack Segment,(栈段寄存器),存储堆栈段的起始地址。
  • SP: Stack Pointer,(栈指针),指向当前栈顶位置。

任意时刻, SS:SP 指向栈顶元素. 因此 pushpop 指令执行时, CPU 从 SS 和 SP 中得到栈顶的地址.

push ax 的执行由两步完成:

  1. SP = SP - 2
  2. ax 中的内容送入 SS:SP 指向的内存单元处, SS:SP 此时指向新栈顶

也可以看出, 入栈时, 栈顶从高地址向低地址方向增长

注意, 如果将 10000H ~ 1000FH 这段空间当作栈, 初始状态为空时, SS = 1000H, SP = 1010H

pop ax 的执行过程同样分为两步:

  1. SS:SP 指向内存单元处的数据送入 ax
  2. SP = SP + 2, SS:SP 指向当前栈顶下面的单元

3.8 栈顶超界问题

SSSP 只是记录了栈顶的地址, 依靠 SSSP 可以保证在入栈和出栈时找到栈顶, 但并不能保证其不会超出栈空间.

当栈满时使用 push 指令入栈, 或在栈空时使用 pop 入栈都有可能发生栈顶超界问题.

3.9 push, pop 指令

可用形式:

如:

1
2
3
4
mov ax, 1000H
mov ds, ax
push [0]
pop [2]

注意, push, pop 等指令, 修改的只是 SP, 也就是说, 栈顶的变化范围最大为 0 ~ FFFFH (也就是 16 位寄存器能操作的范围)

3.10 栈段

将一段内存当作栈段, 仅仅是在编程时的一种安排, CPU 并不会由于这种安排, 就在执行 push, pop 等栈操作指令时自动地将我们定义的栈段当作栈空间来访问.

实验2 用机器指令和汇编指令编程

注意一点 , Debug 程序的 T 命令在执行修改寄存器 SS 的指令时, 下一条指令也紧接着被执行.

第4章 第一个程序

2.1 4.1 一个源程序从写出到执行的过程

4.2 源程序

如:

1
2
3
4
5
6
7
8
9
10
11
assume cs:coding
codeseg segment
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax

mov ax, 4c00H
int 21H
codesg code
end

这里有 3 种伪指令:

  1. XXX segmentXXX ends
    segmentends (end segment) 是一对成对使用的伪指令, 这是在写可被编译器编译的汇编程序时, 必须要用到的一对伪指令, 其定义一个段, 且需要一个名称来标识

一个有意义的汇编程序中至少要有一个段, 这个段用来存放代码.

  1. end
    end 是一个汇编程序的结束标记, 编译器在编译汇编程序时, 如果碰到了伪指令 end, 就结束对源程序的编译.

  2. assume
    其假设某一段寄存器和程序中的某一个用 segment...ends 定义的段相关联.


注意 codeseg 是一个标号, 一个标号指代了一个地址.

实现程序返回的代码

1
2
mov ax, 4c00H
int 21H

在 DOS 中:

  1. 正在运行的 command 程序将可执行文件加载入内存
  2. command 程序设置 CPU 的 CS:IP 指向程序的第一条指令, 从而使程序的以运行
  3. 程序运行结束后, 返回到 command 中, CPU 继续运行 command

第5章 [BX]loop 指令

[bx] 同样表示一个内存单元, 它的偏移地址存放在 bx 中:

1
mov ax, [bx]

5.1 [BX]

有:

1
2
mov ax, [bx]
mov [bx], ax

5.2 Loop 指令

使用格式为: loop + 标号

loop 指令的执行分为两步:

  1. cx = cx - 1
  2. 判断 cx 的值, 不为零则转至标号处执行程序, 如果为零则向下执行

也就是说, cx 中存放循环次数.

如:

1
2
3
4
5
6
7
8
   mov ax, 2
mov cx, 11

s: add ax, ax
loop s

mov ax, 4c00h
int 21h

5.5 loop 和 [bx] 的联合应用

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code
code segment
mov ax, 0fffh
mov ds, ax
mov bx, 0

mov dx, 0

mov cx, 12
S: mov al, [bx]
mov ah, 0
add dx, ax
inc bx
loop s

mov ax, 4c00h
int 21h

code ends
end

5.6 段前缀

可以在访问内存单元时显示地给出内存单元的段地址所在的段寄存器:

1
2
3
4
mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]

这里的 ds:, cs: 等就被称为 段前缀

5.7 一段安全的空间

在 8086 模式中, 随意向一段内存空间写入内容是横危险的, 因为这段空间中可能存放着重要的系统数据或代码.

关于 8086 CPU 的 8086 模式和实模式

8086 模式是指 Intel 8086 微处理器在运行时的一种操作模式,该模式可以访问 1MB 的内存空间。在 8086 模式下,CPU 可以同时使用 16 位和 8 位的寄存器,其指令集包括了 16 位和 8 位的指令。在这种模式下,CPU 可以通过段地址和偏移地址来访问内存。

实模式是指 Intel 8086 微处理器在启动时进入的初始模式,该模式下 CPU 可以访问 1MB 的内存空间,但是只能使用 16 位的寄存器和指令。在实模式下,内存被分为多个 64KB 的段,每个段可以通过一个 16 位的段寄存器来访问。在实模式下,CPU 可以通过物理地址来访问内存。

需要注意的是,实模式是 8086/8088 微处理器的初始模式,而 8086 模式则是后来的一种操作模式。在实模式下,CPU 可以通过一些特殊的指令将自己切换到 8086 模式下运行,然后就可以使用 8086 模式下的指令集和功能了。


在一般的 PC 机中, DOS 方式下. DOS 和其他合法程序一般都不会使用 0:200~0:2ff 的 256 个字节的空间. 所以可以认为这段空间是安全的.

第6章 包含多个段的程序

程序取得所需空间的两种方法:

  • 加载程序的时候程序分配
  • 程序在执行的过程中向系统申请

6.1 在代码段中使用数据

将一些数据存储在一组地址连续的内存单元中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
mov bx, 0
mov ax, 0

mov cx, 8
S: add ax, cs:[bx]
add bx, 2
loop s

mov ax, 4c00h
int 21h
code ends
end

dw (define word) 是定义字型数据.

由于这里 dw 定义的数据处于代码段的最开始, 所以偏移地址为 0.

可以指明程序的入口所在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assume cs:code

code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h

start: mov bx, 0
mov ax, 0

mov cx, 8
S: add ax, cs:[bx]
add bx, 2
loop s

mov ax, 4c00h
int 21h
code ends

end start

注意这里的 end start, end 除了通知编译器程序结束外, 还可以通知编译器程序的入口在什么地方.

在编译, 链接后, 由 end start 指明程序入口, 被转化为一个入口地址, 存储在可执行文件的描述信息中.

当程序被加载如内存之后, 加载者从程序的可执行文件的描述信息中读到程序的入口地址, 设置 CS:IP

6.2 在代码段中使用栈

可以在程序中通过定义数据来取得一段空间, 然后将这段空间当作栈空间来用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
assume cs:code

code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
dw 0, 0, 0, 0, 0, 0 ,0 ,0 , 0, 0, 0, 0, 0, 0 ,0 ,0

start: mov ax, cs
mov ss, ax
mov sp, 30h

mov bx, 0
mov cs, 8
s: push cs:[bx]
add bx, 2
loop s

mov bx, 0
mov cx, 8
s0: pop cs:[bx]
add bx, 2
loop s0

mov ax, 4c00h
int 21h
code ends
end start

6.3 将数据, 代码, 栈放入不同的段

用定义代码段一样的方法来定义多个段, 然后在这些段里面定义需要的数据, 或通过定义数据来取得栈空间.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
assume cs:code, ds:data, ss:stack
data segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
data ends

stack segment
dw 0, 0, 0, 0, 0, 0 ,0 ,0 , 0, 0, 0, 0, 0, 0 ,0 ,0
stack ends

code segment
start: mov ax, stack
mov ss, ax
mov sp, 20h

mov ax, data
mov ds, ax

mov bx, 0

mov cx, 8
s: push [bx]
add bx, 2
loop s

mov bx, 0

mov cx, 8
s0: pop [bx]
add bx, 2
loop s0

mov ax, 4c00h
int 21h
code ends
end start

第7章 更灵活的定位内存地址的方法

7.1 and 和 or 指令

and, 逻辑与, 如:

1
2
mov al, 01100011B
and al, 01100011B

or, 逻辑或, 如:

1
2
mov al, 01100011B
or al, 01100011B

7.3 以字符形式给出的数据

在汇编程序中, 用 ‘…..’ 的方式指明数据是以字符的形式给出的, 编译器将它们转化为相对应的 ASCII 码, 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
assume cs:code, ds:data
data segment
db 'unIX'
db 'foRK'
data ends
code segment
start: mov al, 'a'
mov ax, 'b'

mov ax, 4c00h
int 21h
code ends
end start

7.4 大小写转换的问题

由于小写字母的 ASCII 码值比大写字母的 ASCII 码值大 20H, 而改写一个字母的大小写, 实际上就是要改变他所对应的 ASCII 码.

另一个规律为, 就 ASCII 码的二进制形式来看, 除了第 5 位(位数从 0 开始计算) 外, 大写字母和小写字母的其他各位都一样, 大写字母 ASCII 码的第 5 位为 0, 小写字母的第 5 位为 1. 因此, 将一个字母的第 5 位改为 0 即可变为小写字母.

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
assume cs:code, ds:data

data segment
db 'BaSic'
db 'iNfOrMaTiOn'
data ends

code segment
start: mov ax, data
mov ds, ax

mov bx, 0

mov cx, 5
s: mov al, [bx]
and al, 11011111B
mov [bx], al
inc bx
loop s

mov cx, 11
s0: mov al, [bx]
or al, 00100000B
mov [bx], al
inc bx
loop s0

mov ax 4c00h
int 21h
code ends
end start

7.5 [bx + idata]

[bx + idata] 来表示一个内存单元.

常用的格式为:

1
2
3
mov ax, [200 + bx]
mov ax, 200[bx]
mov ax, [bx].200

7.6 用 [bx + idata] 的方式进行数组的处理

1
2
C 语言:   a[i], b[i]
汇编语言: 0[bx], 5[bx]

7.7 SI 和 DI

sidi 是 8086 CPU 中和 bx 功能相近的寄存器, 但是 sidi 不能分成两个 8 位寄存器来使用.

下面 3 组指令功能相同:

1
2
3
4
5
6
7
8
mov bx, 0
mov ax, [bx]

mov si, 0
mov ax, [si]

mov di, 0
mov ax, [di]

7.8 [bx+si] 和 [bx+di]

[bx+si][bx+di] 来指明一个内存单元, 也可以写为:

1
mov ax, [bx][si]

7.9 [bx+si+idata] 和 [bx+di+idata]

常用格式为:

1
2
3
4
5
mov ax, [bx+200+si]
mov ax, [200+bx+si]
mov ax, 200[bx][si]
mov ax, [bx].200[si]
mov ax, [bx][si].200

7.10 不同的寻址方式的灵活应用

第8章 数据处理的两个基本问题

计算机是进行数据处理, 运算的机器, 其包含两个基本问题:

  1. 处理的数据在什么地方
  2. 要处理的数据有多长

8.1 bx, si, di 和 bp

只有以下组合是正确的:

使用 bp 寄存器时, 若指令中没有显性给出段地址, 段地址就默认在 ss 中.

8.2 机器指令处理的数据在什么地方

3 个地方:

  • CPU 内部
  • 内存
  • 端口

8.5 指令要处理的数据有多长

8086 CPU 的指令, 可以处理两种尺寸的数据, byte 和 word.

在没有寄存器名存在的情况下, 用操作符 X ptr 指明内存单元的长度, X 在汇编指令中可以为 wordbyte:

1
2
mov word ptr ds:[0], 1
mov byte ptr ds:[0], 1

“ptr” 是 “pointer” 的缩写,表示指针。”word ptr” 则表示指针所指向的数据类型为 “word”

有些指令默认了访问的是字单元还是字节单元, 如 push [1000H], push 指令只进行字操作

8.7 div 指令

div 是除法指令, 需注意:

格式为:

1
2
div reg
div 内存单元

8.8 伪指令 dd

dd (define double word, 双字).

8.9 dup

dup (duplicate), 用来进行数据的重复, 如:

1
db 3 dup (0)

相当于:

1
db 0, 0, 0

同样:

1
db 3 dup (0,1,2)

相当于:

1
db 0, 1, 2, 0, 1, 2, 0, 1, 2

第9章 转移指令的原理

可以修改 IP, 或同时修改 CSIP 的指令统称为 转移指令.

其分类为:

9.1 操作符 offset

操作符 offset 在汇编语言中是由编译器处理的符号, 它的功能是取得标号的偏移地址:

1
2
3
4
5
6
assume cs:code
code segment
start: mov ax, offset start
s: mov ax, offset s
code ends
end start

这里, offset 操作符取得了标号 starts 的偏移地址 0 和 3.

9.2 jmp 指令

jmp 为无条件转移指令, 可以只修改 IP, 也可以同时修改 CSIP.

需要给出两种信息:

  1. 转移的目的地址
  2. 转移的距离

9.3 依据位移进行转移的 jmp 指令

格式: jmp short + 标号

这里的 short 指进行的是段内短转移, 对 IP 的修改范围为 -128 ~ 127

如:

1
2
3
4
5
6
7
8
9
10
assume cs:code
code segment

start: mov ax, 0
jmp short s
add ax, 1
s: inc ax

code ends
end start

需要注意一个现象, CPU 在执行 jmp short 指令的时候并不需要转移的目的地址, 其对应的机器码中不包含目的地址.

jmp short + 标号 指令所对应的机器码中, 并不包含转移的目的地址, 而包含的是转移的位移 , 这个位移则是根据 “标号” 计算出来的.

1
位移 = 标号处的地址 - jmp 指令后的第一个字节的地址

而这里的功能则为:

1
(IP) = (IP) + 8 位位移

另一种:

1
jmp near ptr + 标号

实现段内近转移, 功能为:

1
(IP) = (IP) + 16 位位移

9.4 转移的目的地址在指令中的 jmp 指令

jmp far ptr + 标号, 功能为:

1
2
(CS) = 标号所在段的段地址
(IP) = 标号在段中的偏移地址

其机器码中包含转移的目的地址. 高地址处的字是转移的目的段地址, 低地址处的字是转移的目的偏移地址.

9.5 转移地址在寄存器中的 jmp 指令

格式: jmp + 16位reg

功能: (IP) = (16位 reg)

9.6 转移地址在内存中的 jmp 指令

两种格式:

  1. jmp word ptr + 内存单元地址, 用于段内转移
    如:

    1
    2
    3
    mov ax, 0123H
    mov ds:[0], ax
    jmp word ptr ds:[0]
  2. jmp dword ptr + 内存单元地址, 用于段间转移
    高地址处的字是转移的目的段地址, 低地址处的字是转移的目的偏移地址.

    1
    2
    (CS) = (内存单元地址+2)
    (IP) = (内存单元地址)

    如:

    1
    2
    3
    4
    mov ax, 0123H
    mov ds:[0], ax
    mov word ptr ds:[2], 0
    jmp dword ptr ds:[0]

9.7 jcxz 指令

jcxz 指令为有条件转移指令.

所有的有条件转移指令都是 短转移. 在对应的机器码中包含转移的位移, 而不是目的地址, 且对 IP 的修改范围为: -128 ~ 127

功能:

9.8 loop 指令

loop 指令为循环指令, 所有的循环指令都是 短转移, 在对应的机器码中包含转移的位移, 而不是目的地址, 且对 IP 的修改范围为: -128 ~ 127.

功能:

第10章 CALL 和 RET 指令

callret 都是转移指令, 都修改 IP 或同时修改 CSIP.

10.1 retretf

ret 指令用栈中的数据, 修改 IP 的内容, 从而实现近转移, 其分两步执行:

  1. (IP) = ((ss)*16 + (sp))
  2. (sp) = (sp) + 2

相当于:

1
pop IP

retf 指令用栈中的数据, 修改 CSIP 的内容, 从而实现远转移. 其分 4 步操作:

  1. (IP) = ((ss)*16 + (sp))
  2. (sp) = (sp) + 2
  3. (CS) = ((ss)*16 + (sp))
  4. (sp) = (sp) + 2

相当于:

1
2
pop IP 
pop CS

示例, 下面程序中, ret 指令执行后, (IP) = 0, CS:IP 指向代码段的第一条指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code

stack segment
db 16 dup (0)
stack ends

code segment
mov ax, 4c00h
int 21h

start: mov ax, stack
mov ss, ax
mov sp, 16
mov ax, 0
push ax
mov bx, 0
ret
code ends

end start

10.2 call 指令

CPU 执行 call 指令时, 进行两步操作:

  1. 将当前的 IPCSIP 压入栈中
  2. 转移

call 指令不能实现短转移, 除此之外, call 指令实现转移的方法和 jmp 指令的原理相同.

10.3 依据位移进行转移的 call 指令

格式为:

1
call + 标号

结果: 将当前 IP 压栈后, 转到标号处执行指令.

此时 CPU 的操作为:

  1. (sp) = (sp) - 2
    ((ss)*16+(sp)) = (IP)
  2. (IP) = (IP) + 16 位位移

相当于:

1
2
push IP
jmp near ptr + 标号

10.4 转移的目的地址在指令中的 call 指令

格式为:

1
call far ptr + 标号

实现段间转移, 目的地址会出现在机器码中.

CPU 此时的操作为:

  1. (sp) = (sp) - 2
    ((ss)*16 + (sp)) = (CS)
    (sp) = (sp) - 2
  2. (CS) = 标号所在段的段地址
    (IP) = 标号在段中的偏移地址

相当于:

1
2
3
push CS
push IP
jmp far ptr + 标号

10.5 转移地址在寄存器中的 call 指令

格式为:

1
call + 16reg

CPU 的操作为:

  1. (sp) = (sp) - 2
    ((ss)*16 + (sp)) = (IP)
    (IP) = (16位reg)

相当于:

1
2
push IP
jmp 16位reg

10.6 转移地址在内存中的 call 指令

格式为:

1
call word ptr + 内存单元地址

相当于:

1
2
push IP
jmp word ptr + 内存单元地址

或:

1
call dword ptr + 内存单元地址

相当于:

1
2
3
push CS
push IP
jmp dword ptr + 内存单元地址

10.7 call 和 ret 的配合使用

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
start: mov ax, 1
mov cx, 3
call s
mov bx, ax

mov ax, 4c00h
int 21h

s: add ax, ax
loop s
ret
code ends
end start

10.8 mul 指令

mul 是乘法指令, 需注意:

格式为:

1
2
mul reg
mul 内存单元

10.10 参数传递

用寄存器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
assume cs:code
data segment
dw 1, 2, 3, 4, 5, 6, 7, 8
dw 0, 0, 0, 0, 0, 0, 0, 0
data ends

code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov di, 16

mov cx, 8
s: mov bx, [si]
call cube
mov [di], ax
mov [di].2, dx
add si, 2
add di, 4
loop s

mov ax, 4c00h
int 21h

cube mov ax, bx
mul bx
mul bx
ret

code ends
end start

用内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
assume cs:code

data segment
db 'conversation'
data ends

code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov cx, 12
call capital
mov ax, 4c00h
int 21h

capital: and byte ptr [si], 11011111b
inc si
loop capital
ret

code ends
end start

第11章 标志寄存器

CPU 内部的寄存器中, 有一种特殊的寄存器, 具有以下 3 种作用:

  1. 用来存储相关指令
  2. 用来为 CPU 执行相关指令提供行为依据
  3. 用来控制 CPU 的相关工作方式

被称为 标志寄存器.

8086CPU 的标志寄存器有 16 位, 其中存储的信息通常被称为程序状态字 (PSW, Program Status Word)

flag 寄存器是 按位起作用的 , 也就是每一位有专门的含义, 记录特定的信息.

如:

11.1 ZF 标志

ZF (Zero Flag), 零标志位, 记录相关指令执行后, 结果是否为 0, 如果结果为 0, 那么 zf = 1, 反之.

11.2 PF 标志

PF (Parity Flag), 奇偶标志位, 其记录相关指令执行后, 其结果的所有 bit 位中 1 的个数是否为 偶数 , 如果是, 则 pf=1, 反之.

11.3 SF 标志

SF (Sign Flag), 符号标志位, 其记录相关指令执行后, 其结果是否为 , 如果是, 则 sf=1, 反之.

在我们将数据当作有符号数来运算时, 可以通过 SF 标志位来得知结果的正负, 如果我们将数据当作无符号数来运算, SF 的值则没有意义.

11.4 CF 标志

CF (Carry Flag), 进位标志位, 一般情况下, 在进行无符号数运算的时候, 其记录了运算结果的最高有效位向更高位的进位值, 或借位值.

OF 标志

由于在进行有符号数运算时, 可能发生溢出而造成结果的错误, 则需要 CPU 对指令执行后是否产生溢出进行记录.

OF (Overflow Flag), 溢出标志, 一般情况下, OF 记录了有符号数运算的结果是否发生了溢出, 如果发生溢出, OF=1, 反之.

注意, CF 是对无符号数运算有意义的标志位. OF 是对有符号数运算有意义的标志位.

11.6 abc 指令

abc (add carry) 是带进位加法指令, 他利用了 CF 上记录的进位值.

格式: abc 操作对象1, 操作对象2

功能: 操作对象1 = 操作对象1 + 操作对象2 + CF

11.7 sbb 指令

sbb (sub borrow) 是带借位减法指令, 其利用了 CF 上记录的借位值.

格式: sbb 操作对象1, 操作对象2

功能: 操作对象1 = 操作对象1 - 操作对象2 - CF

11.8 cmp 指令

cmp (compare) 是比较指令, 其相当于减法指令, 只是不保存结果, 且其会对标志寄存器产生影响.

格式: cmp 操作对象1, 操作对象2

功能: 操作对象1 - 操作对象2, 但不保存结果, 仅仅根据计算结果对标志寄存器进行设置.

如:

1
cmp ax, ax

相当于做 (ax) - (ax) 的操作, 但不保存结果, 根据这个结果来设置 flag 的相关位.

11.9 检测比较结果的条件转移指令

如:

11.10 DF 标志和串传送指令

DF (Direction Flag), 方向标志位, 在串处理指令中, 控制每次操作后 si, di 的增减.

  • df=0, 每次操作后 si, di 递增
  • df=1, 每次操作后 si, di 递减

两种串传送指令:

  • movsb (move string byte)
  • movsw (move string word)

其都是将 ds:si 指向的内存单元的字节/字送入 es:di 中, 然后根据标志寄存器 df 位的值, 将 sidi 递增或递减.

movsb 相当于:

  1. ((es)*16 + (di)) = ((ds)*16 + (si))
  2. 如果 df=0, (si) = (si) + 1, (di) = (di) + 1
    如果 df=1, (si) = (si) - 1, (di) = (di) - 1

这两个指令常配合 rep (repete) 指令执行, 其根据 cx 的值, 重复执行后面的指令, 如:

1
rep movsb

相当于:

1
2
s:movsb
loop s

有两个指令来对 df 位进行设置.

  • cld (clear direction flag), 将标志寄存器的 df 位置置 0
  • std (set direction flag), 将标志寄存器的 df 位置置 1

11.11 pushf 和 popf

pushf 的功能是将标志寄存器的值压栈.

popf 是从栈中弹出数据, 送入标志寄存器中.

注意这里的 pushf, popfpush, pop 公用一个栈.

第12章 内中断

CPU 检测到从外部或内部产生的一种特殊信息后, 立即对所接收到的信息进行处理, 这种信息称为中断信息. 中断的含义是, CPU 不再接着 (刚执行完的指令) 向下执行, 而是转去处理这个特殊信息.

12.1 内中断的产生

当 CPU 内部有下面情况发生的时候, 将产生相应的中断信息如:

  1. 除法错误
  2. 单步执行
  3. 执行 into 指令
  4. 执行 int 指令

8086CPU 用 中断类型码 来标识中断信息的来源. 其为一个字节型数据 (8位), 可以表示 256 种中断信息的来源 (中断源).

上述中断源的类型码如下:

  1. 除法错误, 0
  2. 单步执行, 1
  3. 执行 into 指令, 4
  4. 执行 int 指令, int n, 其中 n 为字节型立即数, 是提供给 CPU 的中断类型码

12.2 中断处理程序

用来处理中断信息的程序被称为 中断处理程序.

CPU 需要在中断信息和其处理程序的入口地址之间建立某种联系, 而中断类型码的作用就是用来定位中断处理程序.

12.3 中断向量表

CPU 用 8 位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址.

中断向量 指中断处理程序的入口地址, 而 中断向量表 指中断处理程序入口地址的列表. 其保存在内存中. 如:

对于 8086PC 机, 中断向量表指定放在内存地址 0 处, 从内存 0000:00000000:03FF 的 1024 个单元中存放着中断向量表. 一个表项占两个字, 高地址字存放段地址, 低地址字存放偏移地址.

12.4 中断过程

找到中断程序的入口地址的最终目的是用它设置 CSIP.

中断过程 , 指 CPU 的硬件用中断类型码找到中断向量, 并用它设置 CSIP 的过程.

由于 CPU 在执行完中断处理程序后, 应该返回原来的执行点继续执行下面的指令, 所以在中断过程中, 在设置 CSIP 之前, 还要将原来的 CSIP 的值保存起来.

过程如下:

  1. 取得中断类型码 N
  2. pushf
  3. TF=0, IF=0
  4. push CS
  5. push IP
  6. (IP)=(N*4), (CS)=(N*4+2)

12.5 中断处理程序和 iret 指令

由于 CPU 随时都可能执行中断处理程序, 所以中断处理程序必须一直存储在内存某段空间中, 而中断处理程序的入口地址, 即中断向量, 必须存储在对应的中断向量表表项中.

编写中断处理程序的常规方法:

  1. 保存用到的寄存器
  2. 处理中断
  3. 恢复用到的寄存器
  4. iret 指令返回

iret 指令相当于:

1
2
3
pop IP
pop CS
popf

12.11 单步中断

CPU 在执行完一条指令后, 如果检测到 TF 位为 1, 则产生单步中断, 引发中断过程.

单步中断的中断类型码为 1, 其引发的中断过程为:

  1. 取得中断类型码 1
  2. 将标志寄存器入栈, TF, IF 设置为 0
  3. CS, IP 入栈
  4. (IP) = (1*4), (CS) = (1*4+2)

单步中断即为, 执行完当前指令后, 再执行单步中断程序.

12.12 响应中断的特殊情况

在有些情况下, CPU 在执行完当前指令后, 即便是发生中断, 也不会响应, 如: 在执行完向 ss 寄存器传送数据的指令后.

第13章 int 指令

int (interrupt) 指令引发的中断属于内中断.

13.1 int 指令

格式: int n

相当于引发一个 n 号中断的中断过程:

  1. 取中断类型码 n
  2. 标志寄存器入栈, IF=0, TF=0
  3. CS, IP 入栈
  4. (IP)=(n*4), (CS)=(n*4+2)

可以在程序中使用 int 指令调用任何一个中断的处理程序.

如:

1
2
3
4
5
6
7
8
assume cs:code
code segment
start:mov ax, 0b800h
mov byte ptr es:[12*160+40*2], '!'
int 0
code ends

end start

int 指令的最终功能和 call 指令相似, 都是调用一段程序.

13.4 BIOS 和 DOS 所提供的中断例程

在系统板的 ROM 中存放着一套程序, 称为 BIOS (Basic Input/Output System, 基本输入输出系统), 其主要包含:

  1. 硬件系统的检测和初始化程序
  2. 外部中断和内部中断的中断例程
  3. 用于对硬件设备进行 I/O 操作的中断例程
  4. 其他和硬件系统相关的中断例程

可以用 int 指令直接调用 BIOS 和 DOS 提供的中断例程.

13.5 BIOS 和 DOS 中断例程的安装过程

第14章 端口

CPU 可以直接读写以下 3 个地方的数据:

  1. CPU 内部的寄存器
  2. 内存单元
  3. 端口

与 CPU 相连的其他芯片的寄存器被当作 端口 , 对它们进行统一编址, 从而建立一个统一的端口地址空间, 每一个端口在地址空间中都有一个地址.

14.1 端口的读写

端口地址也是通过地址总线来传送.

对端口的读写不能用 mov, push, pop 等内存读写指令, 其读写指令只有两条:

  1. in, 读取数据
  2. out, 写入数据

汇编语言-王爽-Notes
http://example.com/2023/07/03/汇编语言-王爽-Notes/
作者
Jie
发布于
2023年7月3日
许可协议