0%

汇编

汇编语言

第二章

通用寄存器AX、BX、CX、DX,为了兼容可分为两个八位XH和XL
SI、DI、SP、BP、

PSW

汇编指令 描述
mov ax/ah, 18 AX = 18 mov指令不能用于修改CS和IP的值
add ax, 8 AX = AX + 8
jmp 段地址:偏移地址 同时修改CS和IP的值
jmp 某一合法寄存器ax IP = ax 仅修改IP的值
mov ax,[offset] AX = 地址为DS+offset内存单元中的数据

段寄存器

一个段的的最大长度为64KB:
段地址1000H左移一位+偏移地址1111H 偏移最大为16位

四个段寄存器SS、 DS、ES、CS
指令指针寄存器IP
即CPU任意时刻将 CS:IP 指向的内容当做指令来执行
8086CPU给出物理地址的方法: 段地址 * 16 + 偏移地址
执行过程:

  1. 从CS:IP指向的内存读取指令,取到指令缓冲器

  2. IP=IP+所读取指令的长度,从而指向下一条指令

  3. 执行指令跳转到(1)

    8086CPU在加电启动或复位后
    CS–>FFFFH
    IP–>0000H

实验

1
2
3
4
5
6
7
8
9
10
11
-r : 查询寄存器状态
-r ax : 查询并且修改ax寄存器
-t : 从CS:IP处开始执行指令
-d 段地址+偏移地址: 查询内存的内容(128bit)
-e 段地址+偏移地址 00 01 02 : 修改内存中的内容
-a 段地址+偏移地址:以汇编的形式写入指令
-g ABCDH: 执行程序到当前代码段的 ABCDH 处


-e 1000:0000 1 'a' 2 'b' 3 'c'
---> 1000:0000 01 61 02 62 ...(修改单位为一个字节 8bit)

各类存储芯片

随机存储器
装有BIOS是ROM(只读)
接口卡上的RAM
00000~9FFFF 主储存器地址空间(RAM)
A000~BFFFF 显存地址空间
C0000~FFFFF 各类ROM地址空间
[^8086PC机内存地址空间分配1]
  1. 从地址0~9FFFF读取数据实际就是在读取主随机存储器上的数据
  2. 向地址A0000~BFFFF的内存单元写入数据就是向显存写入数据
1
2
-e b810:0000 01 01 02 02 03 03 04 04
发现屏幕出现色块
  1. 向C0000~FFFFF的内存单元写入数据是无效的(ROM是只读的)

第三章 寄存器及内存访问

字单元: 存放一个字型数据的内存单元(16位)
N地址字单元: 起始地址为N的字单元
DS寄存器: 存放要访问数据的段地址

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

al 取 (ds)1000H:0000H 内存单元的数据,同理可以将数据存进内存单元
8086CPU不支持将数据直接传入段寄存器

栈、栈段

8086CPU提供出站入站的指令且出站入栈操作都是以字为单位SS:SP选择一段以栈的方式访问的内存空间
段寄存器SS和寄存器SP,任意时刻SS:SP指向栈顶元素 SP递减栈由高地址向低地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
设置栈
mov ax,1000H
mov ss,ax
mov sp,0010H //设置栈顶偏移地址

mov ax,0123H
push ax
mov bx,2266H
push bx
mov cx,1122H
push cx
pop ax //取出栈顶元素给ax
pop bx
pop cx

push : sp = sp - 2 -> push
pop : pop -> sp = sp + 2

Debug的T命令在修改寄存器SS的指令时,下一条指令会紧接着执行

1
2
3
mov ax,1000H
mov ss,ax
mov sp,0010H //这一步会紧接在第二部进行

实验任务

单步调试(t指令)导致新建栈区内存发生改变

你在设置好 SS(栈段)和 SP(栈顶指针)后,就算还没有写任何 push 指令,这块内存也被修改了。

根本原因在于: Debug 的 t 指令(单步跟踪)是依赖 CPU 的单步中断(Interrupt 1) 来实现的。CPU 在处理任何中断时,都必须强制把当前的“运行现场”保存到当前有效的栈中,以便中断处理完后能找回原来的执行位置。这个“强制保存”的动作,悄无声息地向你的栈里压入了 6 个字节的数据。

  1. 执行目标指令: CPU 首先老老实实地执行完你代码里的一条指令(例如 mov bx, 1111)。

  2. 触发单步中断: 指令执行完毕的瞬间,CPU 硬件检测到标志寄存器(FLAGS)中的 TF(陷阱标志)位为 1,立即触发单步中断。

  3. 强制压栈保存现场(内存被修改的发生地): 为了能去执行 Debug 程序的代码并在屏幕上显示寄存器状态,CPU 自动将 3 个关键寄存器压入你刚刚设置好的栈 (SS:SP) 中。顺序严格如下:

    • SP = SP - 2压入 FLAGS (保存各种状态标志位)

    • SP = SP - 2压入 CS (保存中断发生时的代码段地址)

    • SP = SP - 2压入 IP (保存中断发生时,下一条将要执行的指令的偏移地址)

    (此时,你的栈空间里就多出了这 6 个字节的数据,内存发生了你所观察到的改变。)

  4. 移交控制权: CPU 修改 CS 和 IP 的值,跳转到 Debug 的核心代码去执行,把刚才的寄存器状态打印到你的屏幕上。

  5. 恢复现场并返回: Debug 程序执行到最后一条指令 IRET(中断返回)。CPU 再次接管,按照刚才压栈的逆序,从你的栈中把数据弹出来:

    • 弹出数据到 IPSP = SP + 2

    • 弹出数据到 CSSP = SP + 2

    • 弹出数据到 FLAGSSP = SP + 2 (此时,SP 指针完美复位,CPU 回到你的代码中继续待命,等待你输入下一个命令。)

第四章

汇编 -> 编译 -> 链接

源程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:codesg /* 表示codesg段 与cs寄存器(代码段) 有关系 即为一个代码段 */

/* cs 代码段、 ss 栈段、 ds 数据段 */

codesg segment

mov ax, 0123H
mov bx, 2345H
...

/* 实现程序返回将CPU的控制权交给原来的程序 */
mov ax, 4c00H
int 21H


codesg ends

end

伪指令 用法
end 汇编结束标记符
codesg segment … codesg ends 定义一个段
assume 某寄存器和某段有关

程序执行的过程跟踪

编程 -> 1.asm -> 编译 -> 1.obj -> 连接 -> 1.exe -> 加载(command)

加载过程:

  1. 先找到一段初始SA 地址作为起始地址有足够空间
  2. 在内存区的钱256个字节中创建一个程序前缀(PSP)的数据区
  3. PSP区:SA:0
    程序区:SA:SA + 10H:0
    一般将两个区分成不同段
  4. DA = SA 初始化后 CS:IP = SA + 10H:0
    (程序执行完后可通过DS来查看代码所在地址)

第五章 [ BX ] 和 loop 指令

  • BX -> EA 存放的数据作为一个偏移地址
  • cx(count计数寄存器 用于loop循环的次数)先减一在判 0
  • 在汇编源程序中,数据不能以字母开头 A000H -> 0A000H

Debug 和 汇编编译器 masm 对 [num] 的处理不同:

  • debug会认为是ds:num处的数据,而masm会认为是常数num

解决方式:

  • 在[ num ]前面显式给出段地址所在的段寄存器 例如 ds : [num]
    **此处ds称作段前缀 **

DOS和其他合法程序一般不会使用0:2000:2ff(00200h002ffh)的256个字节空间所以这段空间是安全的(在0:0026处写入数据会造成死机)

实验:向内存0:200 ~ 0:23F 依次传入数据 0 ~ 63(3FH)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
code segment
mov ax, 0020H
mov ds, ax
mov bx, 0
mov cx, 64
s: mov ds:[bx], bx
inc bx
loop s

mov ax, 4c00H
int 21h

code ends
end

第六章包含多个段的程序

  • dw(define word)用于定义字形数据,若在段开头定义则数据地址在该段的起始地址(cs:[0])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

// start指明程序的入口,否则会从dw存储的数据区开始执行

可执行文件由描述信息程序组成,程序来源于源程序中的汇编指令和定义的数据,描述信息主要是编译和连接程序对源程序中相关伪指令进行的处理

1
2
3
4
5
6
7
;6.3
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
;此处定义16个字形数据开辟栈的空间,可以说用于定义数据,也可以说用来开辟内存空间

定义多个不同段可以直接用段的名称找到该段的地址

例如定义了 stack段,可以使用 mov ax, stack 可以将栈段的地址传入ax


实验五

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
assume cs:code, ds:data, ss:stack

data segment(076A)
dw 0123h, 0456h, 0789h, 0abch, 0fedh, 0cbah, 0987h
data ends

stack segment(076B)
dw 0,0,0,0,0,0,0,0
stack ends

code segment(076C)

start : mov ax, stack 【2
mov ss, ax 【2
mov sp, 16 【3

mov ax, data 【2
mov ds, ax 【2

push ds:[0] 【4
push ds:[2] 【4
pop ds:[2] 【4
pop ds:[0] 【4

mov ax, 4c00h 【3
int 21h 【2

code ends
end start
  • CPU执行程序,返回前,data段中的数据没有变化
  • cs = 076C 、ss = 076B、 ds = 076A
  • X - 2, X - 1
  • 实际占有空间 [ N / 16] * 16 个字节
  • 写在后面需要提前计算代码段的长度计算偏移(32)
指令 作用 结果
end start 告诉编译器:程序结束了,并且程序的启动入口在 start 标号处 编译出的 EXE 文件头里会记录入口地址。
end 仅仅告诉编译器:源文件到此结束,不再编译后面的文字。 不指定入口地址,系统默认从程序的第一行代码开始执行。
内存寻址方法和指令长度

我们将寻址方式按“从简到繁”排列,看看字节数是如何增加的:

  1. 寄存器寻址(最快、最短)
  • 示例: mov ax, bx
  • 长度: 2 字节
  • 原理: 1 字节操作码 + 1 字节 ModR/M(告诉 CPU 是哪两个寄存器)。不需要任何额外的内存地址信息。
  1. 寄存器间接寻址(无位移)
  • 示例: mov ax, [bx]
  • 长度: 2 字节
  • 原理: 地址已经存在寄存器里了,CPU 只需要 ModR/M 字节就能确定“去 bx 指向的地方提货”。
  1. 直接寻址(带 16 位偏移量)
  • 示例: mov ax, [1234h]push ds:[0]
  • 长度: 3 ~ 4 字节
  • 原理: 除了操作码,指令里必须硬编码进 1234h 这两个字节的地址。
    • mov ax, [addr] 是特化指令,3 字节
    • push ds:[0] 是通用格式,4 字节(操作码 + ModR/M + 2字节偏移量 0000h)。
  1. 基址变址寻址(最复杂)
  • 示例: mov ax, [bx + si + 1234h]
  • 长度: 4 字节
  • 原理: 这是最复杂的组合。
    • ModR/M 字节记录了 bxsi 的组合关系。
    • 指令结尾必须带上 1234h2 字节 的位移量。

例5:编写程序将a段和b段中的数据相加存储到c段中

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
36
37
38
39
40
assume cs:code

a segment
db 1, 2, 3, 4, 5, 6, 7, 8
a ends

b segment
db 1,2,3,4,5,6,7,8
b ends

c segment
db 0,0,0,0,0,0,0,0
c ends

code segment

start:
mov ax, a
mov ds, ax
mov ax, b
mov es, ax
mov ax, c
mov ss, ax

mov bx, 0
mov cx, 8

s:
mov al, ds:[bx]
mov ah, es:[bx]
add al, ah
mov ss:[bx], al
inc bx
loop s

mov ax, 4c00h
int 21h

code ends
end start

例6:用push指令将a段中的前八个字形数据,逆序存储到b段中

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
assmue cs:code
a segment
dw 1,2,3,4,5,6,7,8,9,...
a ends

b segment
dw 0,0,0,0,0,0,0,0
b ends

code segment
start:
mov ax, a
mov dx, ax
mov ax, b
mov ss, ax
mov sp, 8
mov bx, 0
mov cx, 16
s:
mov ax, [bx]
push ax
add bx, 2
loop s

mov ax, 4c00h
int 21h

code ends
end start

内存地址方法

与或指令 andor

大小写转换问题使用and方式就可以完美解决:
大写字母的ASCII码的二进制第五位(从零位开始)置零就一定是大写

基址变址寻址 [bx + idata]

-------------到底咯QAQ嘎嘎-------------