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][bx + si + idata]

SI和DI类似bx但不能拆成高低八位

双重循环用栈保存cx

实验六

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, ss:stack, ds:data
stack segment
dw 0,0,0,0,0,0,0,0
stack ends

data segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
data ends

code segment
start:
mov ax, stack
mov ss, ax
mov sp, 16
mov ax, data
mov ds, ax
mov cx, 4
s0:
push cx
mov cx, 4
mov bx, 0
mov si, 0
s1:
mov al, [bx + si + 3]
and al, 11011111b
mov [bx + si + 3], al
inc si
loop s1
pop cx
add bx, 16
loop s0

mov ax, 4c00h
int 21h

code ends
end

第八章

[bp] 默认的段前缀是ss

用 X ptr 指明内存单元的长度,(dw)word(字)16、byte(字节)8、(dd)double word(两个字)32

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

div指令/mul指令

div word ptr 除数

  1. (DX + AX)/ (reg(N)) = AX … DX
  2. (AX) / reg(N) = AL… AH

mul reg

  1. 8*8: AL * reg = AX
  2. 16*16:AX * reg = DX(高位) AX(低位)

dup 、dw 、dd

1
2
db 3 dup (0) -> 0, 0, 0
db 3 dup (0,1,2) -> 0,1,2,0,1,2,0,1,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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
assume cs:codesg, ds:data, es:table

data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
; 以上是 21 年的 21 个字符串 (偏移地址 0 ~ 83)

dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
; 以上是 21 年公司总收入的 21 个 dword 型数据 (偏移地址 84 ~ 167)

dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
; 以上是 21 年公司雇员人数的 21 个 word 型数据 (偏移地址 168 ~ 209)
data ends

table segment
db 21 dup ('year summ ne ?? ')
table ends

code segment
mov ax, data
mov ds, data
mov ax, table
mov es, ax

mov bx, 0 ;行
mov si, 0 ;年、收入
mov di, 0 ;雇员

mov cx, 21

s:
mov ax, ds:[si]
mov es:[bx+0], ax
mov ax, ds:[si+2]
mov es:[bx+2], ax

mov ax, ds:[si+84]
mov es:[bx+5], ax
mov dx, ds:[si+86] ;高16位存入dx
mov es:[bx+7], dx

div word ptr ds:[di+168]
mov es:[bx+0Dh], ax

mov ax, ds:[di+168]
mov es:[bx+0Ah], ax

add bx, 16
add si, 4
add di, 2
loop s

mov ax, 4c00H
int 21h

code ends
end


第九章 转移指令

操作符 offset 取得相应标号的偏移地址(在段中的偏移地址)

1
start : mov ax, offset start ;相当于mov ax, 0

问题9.1

将s处的一条指令复制到s处

1
2
3
4
5
6
7
8
9
10
11
12
13
assume cs:code
code segment
s:
mov ax, bx
mov si, offset s
mov di, offset s0
mov ax, cs:[si]
mov cs:[di], ax
s0:
nop
nop
......

jmp指令

jmp short 标号 ;段内短转移

jmp word ptr 内存单元地址 ;段内转移

jmp far ptr 标号 ;段间转移,远转移

jmp dword ptr 内存单元地址 ; 段间转移

指令数据中存储的不是标号的具体地址而是当前指令到标号地址的偏移量

jcxz指令

jcxz是有条件转移指令(所有有条件转移都是短转移)

jcxz 标号(如果 cx = 0, 转移)相当于 if ((cx) == 0) jmp short 标号

实验八

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
assume cs:code
code segment

mov ax, 4c00h
int 21h

start:
mov ax, 0
s:
nop
nop

mov di, offset s
mov si, offset s2
mov ax, cs:[si]
mov cs:[di], ax

s0:
jmp short s

s1:
mov ax, 0
int 21h
mov ax, 0

s2:
jmp short s1
nop

code ends
end start

由于jmp short s1指令保存的是s2到s1的位移 所以复制指令之后位移不变,会从s跳转到mov ax, 4c00h程序正常结束

实验九

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
41
assume cs:code ds:data
data segment
db 'w', 02h, 'e', 02h, 'l', 02h, 'c', 02h, 'o', 02h, 'm', 02h, 'e', 02h, ' ', 02h, 't', 02h, 'o', 02h, ' ', 02h, 'a', 02h, 's', 02h, 'm', 02h, '!', 02h
db 'w', 24h, 'e', 24h, 'l', 24h, 'c', 24h, 'o', 24h, 'm', 24h, 'e', 24h, ' ', 24h, 't', 24h, 'o', 24h, ' ', 24h, 'a', 24h, 's', 24h, 'm', 24h, '!', 24h
db 'w', 71h, 'e', 71h, 'l', 71h, 'c', 71h, 'o', 71h, 'm', 71h, 'e', 71h, ' ', 71h, 't', 71h, 'o', 71h, ' ', 71h, 'a', 71h, 's', 71h, 'm', 71h, '!', 71h
data ends
code segment
start:
mov ax, 0B800H
mov es, ax

mov si, 0
mov di, 160 * 12

mov ax, data
mov ds, ax

mov cx, 15

s:
mov al, ds:[si]
mov ah, ds:[si+1]
mov es:[di], ax

mov al, ds:[si+30]
mov ah, ds:[si+31]
mov es:[di+160], ax

mov al, ds:[si+60]
mov ah, ds:[si+61]
mov es:[di+320], ax

add di, 2
add si, 2
loop s

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

第十章CALL 和 RET

ret 和 retf 和 call

两者均使用栈中的数据
ret : pop IP
retf : pop CS & pop IP
call 标号: push IP & jmp near ptr 标号
call far ptr : push CS & push IP & jmp far ptr 标号

实验十

编写三个子程序

  1. 子程序1:显示字符串
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
;显示字符串
assume cs:code
data segment
db 'Welcome to masm!', 0
data ends

code segment
start:
mov dh, 8
mov dl, 3
mov cl, 2
mov ax, data
mov ds, ax
mov si, 0
call show_str

mov ax, 4c00h
int 21h

show_str:
push cx
push si
push di
push ax
push dx
push es

mov al, 160
mul dh
mov di, ax
mov al, 2
mul dl
add di, ax

mov ax, 0b800h
mov es, ax
mov al, cl

s:
mov ch, 0
mov cl, ds:[si] ;
jcxz show_str_end ;

mov es:[di], cl
mov es:[di+1], al

inc si
add di, 2
jmp short s
show_str_end:
pop es
pop dx
pop ax
pop di
pop si
pop cx
ret

code ends
end start
  1. 子程序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
;解决除法溢出问题
assume cs:code
code segment
mov ax, 4240H
mov dx, 000FH
mov cx, 0AH
call divdw

divdw :
push bx ;要利用bx寄存器,先保存
push ax ;先保存低十六位
mov ax, dx
mov dx, 0

div cx
mov bx, ax
pop ax

div cx

mov cx, dx
mov dx, bx

pop bx
ret
  1. 子程序3:word型数据转十进制显示
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
;word型数据转十进制显示
assume cs:code, ds:data, ss:stack

data segment
db 10 dup(0)
data ends
stack segment
db 20 dup(0)
stack ends

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

mov ax, 11451
mov bx, data
mov ds, bx
mov si, 0
call dtoc

mov dh, 8
mov dl, 3
mov cl, 2
call show_str

mov ax, 4c00h
int 21h

dtoc:
push cx
push si
push di
push ax
push dx
push es

mov bx, 0 ;计数

dtoc_div:
mov dx, 0
mov cx, 10
div cx
mov cx, ax
add dx, 30H
push dx
inc bx
jcxz dtoc_end
jmp short dtoc_div

dtoc_end:
mov cx, bx
dtoc_loop:
pop dx
mov ds:[si], dl
inc si
loop dtoc_loop

mov byte ptr ds:[si], 0 ;末尾添加字符串结束标志

pop es
pop dx
pop ax
pop di
pop si
pop cx
ret

show_str:
push cx
push si
push di
push ax
push dx
push es

;先清屏
push cx
mov ax, 0b800h
mov es, ax
mov di, 0
mov cx, 2000
s_clear:
mov word ptr es:[di], 0
add di, 2
loop s_clear

pop cx
mov al, 160
mul dh
mov di, ax
mov al, 2
mul dl
add di, ax

mov ax, 0b800h
mov es, ax
mov al, cl

s:
mov ch, 0
mov cl, ds:[si] ;
jcxz show_str_end ;

mov es:[di], cl
mov es:[di+1], al

inc si
add di, 2
jmp short s
show_str_end:
pop es
pop dx
pop ax
pop di
pop si
pop cx
ret

code ends
end start

课程设计1

首先☝️根据实验7造表

子函数有,show_str数据显示、divdw除法防止溢出、dtoc读取数据转化成十进制保存

思路:

  1. 基本思路:先读取数据制表table -> 再从规范的表中读取数据 -> 读取到的数据丢给显示函数
  2. 显示函数dtoc 调用 divdw 循环取余数存入data2 -> show_str读取 data 数据显示
  3. 数据显示dl定位列 、dh定位行、cl保存数据样式
  4. 数据读取bx定位数据段table行,bx +idata定位四组数据的列
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
assume cs:code, ds:data, es:table

data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
; 以上是 21 年的 21 个字符串 (偏移地址 0 ~ 83)

dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
; 以上是 21 年公司总收入的 21 个 dword 型数据 (偏移地址 84 ~ 167)

dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
; 以上是 21 年公司雇员人数的 21 个 word 型数据 (偏移地址 168 ~ 209)
data ends

table segment
db 21 dup ('year summ ne ?? ')
table ends

data2 segment
db 16 dup(0)
data2 ends

stack segment
db 32 dup(0)
stack ends
code segment
start:
mov ax, stack
mov ss, ax
mov sp, 32
mov ax, data
mov ds, ax
mov ax, table
mov es, ax

mov bx, 0 ;行
mov si, 0 ;年、收入
mov di, 0 ;雇员

mov cx, 21

s:
mov ax, ds:[si]
mov es:[bx+0], ax
mov ax, ds:[si+2]
mov es:[bx+2], ax

mov ax, ds:[si+84]
mov es:[bx+5], ax
mov dx, ds:[si+86] ;高16位存入dx
mov es:[bx+7], dx

div word ptr ds:[di+168]
mov es:[bx+0Dh], ax

mov ax, ds:[di+168]
mov es:[bx+0Ah], ax

add bx, 16
add si, 4
add di, 2
loop s

;显示表格----------------
;先清屏
mov ax, 0b800h
mov es, ax
mov di, 0
mov cx, 2000
s_clear:
mov word ptr es:[di], 0
add di, 2
loop s_clear

mov ax, table
mov es, ax
mov ax, data2
mov ds, ax

mov bx, 0;table 的 行 +16
mov dh, 0;显示 的 行
mov dl, 0;显示 的 列/ 用di来暂存因为里面dx用的有点乱
mov di, 0;显示 的 列
mov cx, 21

s_show:
push cx

;年份一个字节代表一个年数,不用转化ASCII码
mov si, 0
mov ax, es:[bx]
mov ds:[si], ax
mov ax, es:[bx+2]
mov ds:[si+2], ax
mov byte ptr ds:[si+4], 0

mov ax, di
mov dh, al
mov dl, 0
mov cl, 2
call show_str

;收入一个字节代表一个数字,转化为ASCII码
mov si, 0
mov ax, es:[bx+5]
mov dx, es:[bx+7]
call dtoc

mov ax, di
mov dh, al
mov dl, 20
mov cl, 2
call show_str

;雇员一个字节代表一个数字,转化为ASCII码
mov si, 0
mov ax, es:[bx+10]
mov dx, 0
call dtoc

mov ax, di
mov dh, al
mov dl, 40
mov cl, 2
call show_str

;显示平均收入
mov si, 0
mov ax, es:[bx+13]
mov dx, 0
call dtoc

mov ax, di
mov dh, al
mov dl, 60
mov cl, 2
call show_str

pop cx
add bx, 16
inc di
loop s_show

mov ax, 4c00h
int 21h
;-----------------------
show_dtoc:
call dtoc

call show_str
ret
dtoc:
push bx
push cx
push si
push di
push ax
push dx
push es

mov bx, 0 ;计数

dtoc_div:
mov cx, 10
call divdw

add cx, 30H
push cx
inc bx

mov cx, ax ; 把低16位商交给cx
or cx, dx ; 与高16位商进行OR运算
jcxz dtoc_end ; 如果两者都为0,cx就会是0,触发跳转结束循环

jmp short dtoc_div ; 否则继续下一轮除法

dtoc_end:
mov cx, bx
dtoc_loop:
pop dx
mov ds:[si], dl
inc si
loop dtoc_loop

mov byte ptr ds:[si], 0 ;

pop es
pop dx
pop ax
pop di
pop si
pop cx
pop bx
ret

show_str:
push cx
push si
push di
push ax
push dx
push es

mov al, 160
mul dh
mov di, ax
mov al, 2
mul dl
add di, ax

mov ax, 0b800h
mov es, ax
mov al, cl

show_s:
mov ch, 0
mov cl, ds:[si] ;
jcxz show_str_end ;

mov es:[di], cl
mov es:[di+1], al

inc si
add di, 2
jmp short show_s
show_str_end:
pop es
pop dx
pop ax
pop di
pop si
pop cx
ret

divdw:
push bx
push ax;保存低16位

mov ax, dx
mov dx, 0
div cx
mov bx, ax
pop ax;恢复低16位

div cx
;此时ax中存储了低16位的商,bx中存储了高16位的商,dx存余数
mov cx, dx
mov dx, bx

pop bx
ret


code ends
end start


第十一章 标志寄存器

标志寄存器 flag

第6位ZF: add、sub、mul、div、and等大部分运算指令影响ZF,pop、push、mov传送指令不影响

第2位PF:记录相关指令执行之后结果所有bit中1的个数是否为偶数

第7位SF:符号位检查结果是否为负

第0位CF : 进位借位标识符

第11位OF:CF是无符号数运算有意义的标志位,OF是有符号数运算的有意义标志位

第10位DF:方向标志位控制

adc和sbb指令

adc带进位加法指令

1
2
3
;ax高16位,bx低16位,执行这个数加 0020 F000H
add bx, 0F000H
adc ax, 0020H

sbb带借位减法同理

1
2
3
;ax高16位,bx低16位,执行这个数减 0020 F000H
sub bx, 0F000H
sbb ax, 0020H

cmp指令

cmp:cmp ax, bx (ax - bx)相当于减法但是不保存结果只改变寄存器

结合标志寄存器的内容对结果进行分析:

  1. sf = 1 , of = 0 没溢出且结果为负 ax < bx
  2. sf = 1, of = 1溢出且结果为负(正向溢出正数减负数)ax < bx
  3. sf = 0, of = 0 没溢出且结果为正ax > bx
  4. sf = 0, of = 1 溢出且结果为正(负溢出负数减正数) ax > bx

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

指令 含义 检测标志位
je 等于转移 zf = 1
jne 不等于转移 zf = 0
jb 低于转移 cf = 1
jnb 不低于转移 cf = 0
ja 高于转移 cf = 0 且 zf = 0
jna 不高于转移 cf = 1 且 zf = 1
jbe 低于等于转移
jae 高于等于转移

检测点11.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
;统计F000:0处32个字节大小在(32,128)的数据个数

mov ax, 0f000h
mov ds, ax

mov bx, 0
mov dx, 0
mov cx, 32
s:
mov al, [bx]
cmp al, 32
jna s0
cmp al, 128
jnb s0
inc dx
s0:
inc bx
loop s

DF标志和串传送指令

串传送指令movsb相当于:mov es:[di], byte ptr ds:[si] ,df = 0 si、di递增,否则递减,同理字传送指令movsw传送一个字节si、di递增或者递减 2

配合rep使用:

1
2
3
4
5
	rep movsb
;等价于
s:
movsb
loop s

cld将df设为0, std将df设为1

pushf和popf

将标志寄存器压入栈

debug中标志寄存器的表示

实验11

编写一个子程序将包含任意字符且以零结尾的字符串中的小写字母转换变成大写字母,ds:si指向字符串首地址

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
41
42
43
44
45
46
47
assume cs:code

data segment
db "Beginner's All-purpose Symbolic Instruction Code", 0
data ends

code segment
begin:
mov ax, data
mov ds, ax
mov si, 0
call letterc

mov ax, 4c00h
int 21h

letterc:
push si
push ax
pushf
s:
mov al, [si]
cmp al, 0
je endlett

cmp al, 'a'
jb skiplett

cmp al, 'z'
ja skiplett

and al, 11011111b
mov [si], al

skiplett:
inc si
jmp s
endlett:
popf
pop ax
pop si
ret


code ends
end begin

第十二章 内中断

中断处理过程

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

在8086PC机中中断向量表制定存放在内存地址为 0 处,从0000:0000到0000:03FF,一个表项占两个字高地址存放段地址,低地址存放偏移地址,一般情况下,从 0200 : 02FF的256个字节的空间对应的中断向量表项是空的,操作系统和其他程序都不占用

iret

等价于

1
2
3
pop IP
pop CS
popf

安装

修改除法溢出0号中断,使用movsb指令写入int_0程序

  1. 安装int_0
  2. int_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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
assume cs:code

code segment
start:
mov ax, cs
mov ds, ax
mov si, offset int_0 ;设置复制源地址

mov ax, 0
mov es, ax
mov di, 200h ;设置写入地址

mov cx, offset int_0_end - offset int_0

cld
rep movsb

;设置中断向量
mov ax, 0
mov es, ax
mov word ptr es:[0 * 4], 200h ;偏移地址
mov word ptr es:[0 * 4 + 2], 0 ;段地址

mov ax, 4c00h
int 21h

int_0: jmp short int_0_start
db "overflow!" ;db写入之后如果不jmp跳过,cpu会把这段字符解析为指令执行造成错误
int_0_start:
mov ax, cs
mov ds, ax
mov si, 202h ;由于jmp指令写入占两个字节,所以字符串的起始地址为202h

mov ax, 0b800h
mov es, ax
mov di, 12 * 160 + 36 * 2

mov cx, 9

s:
mov al, [si]
mov es:[di], al
inc si
add di, 2
loop s

mov ax, 4c00h
int 21h

int_0_end: nop
code ends
end start

TF标志

执行完一条指令之后如果TF = 1,则cpu会转去执行其他中断处理程序,故在进入中断处理程序之前会将TF设为0

响应中断的特殊情况

例在设置ss栈顶指令之后,cpu不会响应中断

实验12 编写0号中断处理程序

编写0号处理程序,除法溢出的时候,在屏幕正中间显示字符串”divide error!”,然后返回 dos

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
41
42
43
44
45
46
47
48
49
50
51
assume cs:code
code segment
start:
mov ax, cs
mov ds, ax
mov si, offset int_0
mov ax, 0
mov es, ax
mov di, 200h

mov cx, offset int_0_end - offset int_0

cld
rep movsb

mov ax, 0
mov es, ax
mov word ptr es:[0*4], 200h
mov word ptr es:[0*4+2], 0

mov ax, 4c00h
int 21h

int_0:
jmp int_0_start
db "divide error!"

int_0_start:
mov ax, cs
mov ds, ax
mov si, 202h

mov ax, 0b800h
mov es, ax
mov di, 12*160+32*2

mov cx, 13
s:
mov al, [si]
mov es:[di], al
inc si
add di, 2
loop s

mov ax, 4c00h
int 21h
int_0_end:
nop

code ends
end start

第十三章 int 指令

int指令

int n :n表示中断类型码,表示直接调用n型中断

BIOS和DOS提供的中断例程

BIOS中主要包含:

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

操作系统DOS也提供了中断例程,从操作系统的角度看,DOS的中断例程就是操作系统向程序员提供的编程资源。

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

  1. 开机后,CPU一加电,初始化(CS) = 0FFFH,(IP)= 0,自动从该单元执行程序。FFFF:0处有一条跳转指令,CPU执行该指令之后,转去执行BIOS中的硬件系统检测和初始化程序。
  2. 初始化程序建立BIOS所支持的中断向量表,将BIOS提供的中断例程的入口地址登记在中断向量表中。由于BIOS所提供的中断例程固化到ROM程序,所以只需要将入口地址登记在中断向量表中即可。
  3. 硬件系统监测和初始化完成之后,调用 int 19h 进行操作系统的引导,将计算机交由操作系统控制
  4. DOS启动之后,除完成其他工作之外,还将它所提供的中断例程装入内存,建立相关中断向量表

BIOS中断例程的应用

int 10 中断例程是 BIOS 提供的中断例程,BIOS和DOS提供的中断例程,都用ah来传递内部子程序的编号

1
2
3
4
5
mov ah, 2 ;置光标
mov bh, 0 ;第0页
mov dh, 5 ;行号
mov dl, 12 ;列号
int 10h

ah = 2 表示调用第10h号中断例程的2号子程序,功能为设置光标位置

1
2
3
4
5
6
mov ah, 9 ;在光标位置显示字符
mov al, 'a' ; 字符
mov bl, 7 ;颜色属性
mov bh, 0 ;第0页
mov cx, 3 ;重复次数
int 10h

ah = 9 表示在光标位置显示字符

实验 13 编写、应用中断例程

1. 编写安装 int 7ch 中断例程,功能显示一个用0结束的字符串,中断例程安装在0:200处

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
assume cs:code

code segment
start:
mov ax, cs
mov ds, ax
mov si, offset show_string
mov ax, 0
mov es, ax
mov di, 200h

mov cx, offset show_string_end - offset show_string

cld
rep movsb

mov ax, 0
mov es, ax
mov word ptr es:[7ch*4], 200h
mov word ptr es:[7ch*4+2], 0

mov ax, 4c00h
int 21h

show_string:
push ax
push si
push di
push es
push dx

mov ax, 0b800h
mov es, ax

mov al, 160
mul dh
mov di, ax

mov al, dl
mov ah, 0
add, ax, ax
add di, ax

mov bl, cl

show:
mov al, ds:[si]
cmp al, 0
je show_string_out

mov es:[di], al
mov es:[di+1], bl

inc si
add di, 2

jmp show

show_string_out:
pop dx
pop es
pop di
pop si
pop ax
iret

show_string_end:
nop
code ends
end start

2. 编写并安装 int 7ch中断例程,功能为完成loop指令的功能

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
41
42
43
44
assume cs:code

code segment

start:
mov ax, cs
mov ds, ax
mov si, offset lp

mov ax, 0
mov es, ax
mov di, 200h

mov cx, offset lp_end - offset lp

cld
rep movsb

mov ax, 0
mov es, ax
mov es:[7ch*4], 200h
mov es:[7ch*4+2], 0

mov ax, 4c00h
int 21h

lp:
push bp
mov bp, sp

dec cx
jcxz lp_ret

add word ptr [bp+2], bx

lp_ret:
pop bd
iret

lp_end:
nop

code ends
start end

3. 分别在程序第2,4,6,8行显示4句英文诗,补全程序

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
41
42
assume cs:code

code segment

s1: db 'Good,better,best,','$'
s2: db 'Never let it rest,','$'
s3: db 'Till good is better,','$'
s4: db 'And better,best.','$'

s: dw offset s1, offset s2, offset s3, offset s4
row: db 2,4,6,8

start:
mov ax,cs
mov ds,ax

mov bx,offset s
mov si,offset row
mov cx,4

ok:
mov bh,0

mov dh,[si] ; 取行号:2、4、6、8
mov dl,0 ; 第 0 列
mov ah,2
int 10h ; 设置光标位置

mov dx,[bx] ; 取字符串偏移地址
mov ah,9
int 21h ; 显示以 $ 结尾的字符串

add bx,2 ; s 表中每个地址占 2 字节
inc si ; row 表中每个行号占 1 字节

loop ok

mov ax,4c00h
int 21h

code ends
end start

第十四章 端口

端口的读写

  1. 访问内存
1
mov ax, ds:[8]
  • CPU通过地址线将地址信息8发出
  • CPU通过控制线发出内存读命令,选中存储器芯片
  • 存储器将8号单元中的数据通过数据线送入CPU
  1. 访问端口
1
in al, 60h
  • CPU通过地址线将地址信息60h发出
  • CPU通过控制线发出端口读命令,选中端口所在芯片
  • 端口芯片将60h端口中的数据通过数据线送入CPU

注意:in 和 out指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据

1
2
3
4
5
6
7
;0-255内的端口进行读写
in al, 20h ;从 20h 端口读入一个字节
out 20h, al

mov dx, 3f8h
in al, dx
out dx, al

CMOS RAM 芯片

  1. 128字节的RAM中,内部时钟占用 0 ~ 0dh 单元来保存时间信息,其余大部分用于保存系统配置信息,供BIOS程序读取
  2. 该芯片内部有两个端口 70h 和 71h
  3. 70h为地址端口,存放要访问的 RAM中单元的地址;71h为数据端口,存放从选定的单元中读取的数据,或者写入

shl 和 shr 指令

逻辑位移指令

将最后移除的一位写在CF中

1
2
3
4
5
6
mov al, 01001000b
shl al, 1 ;al逻辑左移一位
;但移动位数大于1 必须放在cl中
;即
mov cx, 3
shl al, cl

CMOS RAM 中存储的时间信息

年、月、日、时、分、秒,长度均为一个字节,存放单元为:

9 、8、 7、 4、 2、 0均以BCD码形式存放

编程实现在屏幕中间显示当前月份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
assume cs:code
code segment
start:
mov al, 8
out 70h, al
in al, 71h

mov ah, al
mov cl, 4
shr ah, cl
and al, 00001111b

add ah, 30h
add al, 30h

mov bx, 0b800h
mov es, bx
mov byte ptr es:[160*12+40*2], ah
mov byte ptr es:[160*12+40*2+2], al

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

实验14 访问CMOS RAM

编程,以“年/月/日 时:分:秒”的格式,显示当前的日期、时间

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
assume cs:code,ds:data

data segment
cmos_addr db 09h, 08h, 07h, 04h, 02h, 00h
sign db '/','/',' ',':',':','$'
data ends

code segment

start:
mov ax, data
mov ds, ax

mov si, 0
mov cx, 6

show_time:
mov al, cmos_addr[si]
out 70h, al

in al, 71h

mov ah, al

mov al, ah
mov cl, 4
shr al, cl
add al, 30h
mov dl, al
mov ah, 02h
int 21h

mov al, ah
and al, 00001111b
add al, 30h
mov dl, al
mov ah, 02h
int 21h

mov dl, sign[si]
cmp dl, '$'
je finish

mov ah, 02h
int 21h

inc si
loop show_time

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

第十五章 外中断

外中断信息

外中断源一共有以下两类:

  1. 可屏蔽中断
    IF=1时响应中断,这也解释了触发中断过程中将IF和TF设置成0的原因,同时可以通过

    1
    2
    sti ;设置IF = 1
    cti ;设置IF = 0
  2. 不可屏蔽中断
    CPU在执行完当前指令之后,立即响应,发生中断,此中断的的类型码固定为2,故不需要取终端类型码

PC机键盘的处理过程

  1. 键盘输入
    按下一个按键产生一个扫描码,送入端口号为60h的寄存器,松开按键时产生一个断码也放入60h寄存器,断码=通码 + 80h

  2. 输入到达端口之后,相关芯片向CPU发出中断类型码为 9 的可屏蔽中断信息,如果IF=1则响应中断

  3. int9中断例程,首先读取60h端口的扫描码,如果是字符键的扫描码,就把他对应的字符码送入内存BIOS键盘缓冲区;如果是控制键和切换键的扫描码,则将其转化为状态字节写入内存中存储状态字节的单元

编写 int9 中断例程

编程在屏幕中间依次显示字符a~z

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
41
assume cs:code
stack segment
db 128 dup(0)
stack ends

code segment
start:
mov ax, stack
mov ss, ax
mov sp, 128

mov ax, 0b800h
mov es, ax
mov ah, 'a'

s:
mov es:[160*12+40*2], ah
call delay
inc ah
cmp ah, 'z'
jna s
mov ax, 4c00h
int 21h
delay:
push ax
push dx
mov dx, 1000h
mov ax, 0
s1:
sub ax, 1
sbb dx, 1
cmp ax, 0
jne s1
cmp dx, 0
jne s1
pop dx
pop ax
ret

code ends
end start

按下ESC之后改变颜色

1
2
3
4
5
6
7
8
9
10
;模拟int指令的调用功能,调用入口地址在ds:0、ds:2中的中断例程的程序为
pushf ;标志寄存器入栈(1)

pushf
pop ax
and ah, 11111100b
push ax
popf ;IF = 0 、 TF = 0

call dword ptr ds:[0] ;CS,IP入栈;(IP) = ((ds)*16 + 0), (CS) = ((ds)*16 + 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
assume cs:code, ds:data

stack segment
db 128 dup(0)
stack ends

data segment
dw 0, 0 ; 保存原来的 int 9 中断向量
data ends

code segment

start:
mov ax, stack
mov ss, ax
mov sp, 128

mov ax, data
mov ds, ax

; 保存原来的 int 9 中断向量
mov ax, 0
mov es, ax

push es:[9*4]
pop ds:[0]

push es:[9*4+2]
pop ds:[2]

; 设置新的 int 9 中断向量
cli
mov word ptr es:[9*4], offset int9
mov word ptr es:[9*4+2], cs
sti

; 显示字符
mov ax, 0b800h
mov es, ax
mov ah, 'a'

s:
mov es:[160*12+40*2], ah
call delay

inc ah
cmp ah, 'z'
jna s

; 恢复原来的 int 9 中断向量
mov ax, 0
mov es, ax

cli
push ds:[0]
pop es:[9*4]

push ds:[2]
pop es:[9*4+2]
sti

mov ax, 4c00h
int 21h

delay:
push ax
push dx

mov dx, 1000h
mov ax, 0

s1:
sub ax, 1
sbb dx, 1
cmp ax, 0
jne s1
cmp dx, 0
jne s1

pop dx
pop ax
ret

; 新的 int 9 键盘中断处理程序
int9:
push ax
push bx
push es
push ds

in al, 60h ; 读取键盘扫描码

; 调用原来的 int 9 中断处理程序
pushf

mov bx, data
mov ds, bx

call dword ptr ds:[0]

; ESC 的扫描码是 01h
cmp al, 1
jne int9ret

mov ax, 0b800h
mov es, ax

; 改变屏幕中央字符的属性字节
inc byte ptr es:[160*12+40*2+1]

int9ret:
pop ds
pop es
pop bx
pop ax
iret

code ends
end start

实验15 安装int9中断例程

安装一个新的int9中断例程,功能:在DOS下,按下“A”键之后,除非不在松开手,否则显示满屏A,其他按键照常处理(断码 = 通码 + 80h)

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
assume cs:code, ds:data, ss:stack

stack segment
db 128 dup(0)
stack ends

data segment
dw 0, 0
data ends

code segment

start:
mov ax, stack
mov ss, ax
mov sp, 128

mov ax, data
mov ds, ax

mov ax, 0
mov es, ax

; 保存原 int9 中断向量
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2]

; 安装新的 int9 中断例程
cli
mov word ptr es:[9*4], offset int9
mov word ptr es:[9*4+2], cs
sti

delay:
jmp delay

int9:
push ax
push bx
push cx
push dx
push si
push di
push ds
push es

; 读取键盘扫描码
in al, 60h

; 调用原来的 int9 中断例程,保证其他按键功能不变
pushf

mov bx, data
mov ds, bx

call dword ptr ds:[0]

; 判断是否是 A 键松开
cmp al, 9Eh
jne int9ret

; 如果是 A 键松开,则填满屏幕
mov ax, 0B800h
mov es, ax

mov di, 0
mov cx, 2000

mov ah, 02h
mov al, 'a'

s:
mov es:[di], ax
add di, 2
loop s

int9ret:
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax

iret

code ends
end start

第十六章 直接定址表

描述了单元长度的标号

程序中使用的 code, s, start等标号,仅仅代表了内存单元的地址,但是还可以使用一种标号,不仅代表内存单元地址还表示了内存单元的长度(数据标号)

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
code segment
;普通标号
a: db 1, 2, 3, 4, 5, 6, 7, 8
b: dw 0
;....

;既代表内存单元又代表了内存单元的长度
a db 1, 2, 3, 4, 5, 6, 7, 8
b dw 0
;a描述了地址code:0和从这个地址开始,以后的内存单元都是字节单元
;----
mov al, a[si]

mov ax, b
;相当于 mov ax, cs:[8]

mov b, 2
;相当于 mov word ptr cs:[8], 2

inc b
;相当于 inc word ptr cs:[8]

;---
a db 1, 2, 3, 4, 5, 6, 7
b dw 0
c dw a, b (c dd a, b)
;相当于 c dw offset a, offset b (c, offset a, seg a, offset b, seg b)
; seg 取段地址

注意:在其他段中使用某个段中定义的数据标号,需要在assume中将这段定义

直接定址表

编写子程序,以十六进制的形式在屏幕中间显示给定的字节型数据(建立映射关系)

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
showbyte: 
jmp short show

table db '0123456789ABCDEF'

show:
push bx
push es

mov ah, al
shr ah, 1
shr ah, 1
shr ah, 1
shr ah, 1
and al, 00001111b

mov bl, ah
mov bh, 0
mov ah, table[bx]

mov bl, al
mov bh, 0
mov al, table[bx]

mov es:[160*12+40*2+2], al

pop es
pop bx
ret

实验16 编写包含多个子程序的中断例程

安装一个新的int 7ch 中断例程,未显示输出提供如下功能的子程序

  1. 清屏
  2. 设置前景色
  3. 设置背景色
  4. 向上滚动一行

入口参数说明如下:

  1. ah寄存器传递功能码,0表示清屏,1表示设置前景色,2表示设置背景色,3表示向上滚动一行
  2. 对于1、2功能,用al传递颜色属性
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
assume cs:code

code segment

start:
mov ax, cs
mov ds, ax
mov si, offset int7ch

; 将 int7ch 中断例程复制到 0000:0200
mov ax, 0
mov es, ax
mov di, 200h

mov cx, offset int7ch_end - offset int7ch
cld
rep movsb

; 设置 int 7ch 中断向量
mov ax, 0
mov es, ax

cli
mov word ptr es:[7ch*4], 200h
mov word ptr es:[7ch*4 + 2], 0
sti

mov ax, 4c00h
int 21h

; -------------------------------
; int 7ch 中断例程
; AH = 0:清屏
; AH = 1:设置前景色,AL = 颜色
; AH = 2:设置背景色,AL = 颜色
; AH = 3:向上滚动一行
; -------------------------------

int7ch:
jmp short set

table dw 200h + offset sub1 - offset int7ch
dw 200h + offset sub2 - offset int7ch
dw 200h + offset sub3 - offset int7ch
dw 200h + offset sub4 - offset int7ch

set:
push bx

cmp ah, 3
ja sret

mov bl, ah
mov bh, 0
add bx, bx

call word ptr cs:[200h + offset table - offset int7ch + bx]

sret:
pop bx
iret

; AH = 0,清屏
sub1:
push bx
push cx
push es

mov bx, 0b800h
mov es, bx
mov bx, 0
mov cx, 2000

sub1s:
mov byte ptr es:[bx], ' '
mov byte ptr es:[bx+1], 07h
add bx, 2
loop sub1s

pop es
pop cx
pop bx
ret

; AH = 1,设置前景色
; AL = 前景色,取值 0~7
sub2:
push bx
push cx
push es

mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000

sub2s:
and byte ptr es:[bx], 11111000b
or byte ptr es:[bx], al
add bx, 2
loop sub2s

pop es
pop cx
pop bx
ret

; AH = 2,设置背景色
; AL = 背景色,取值 0~7
sub3:
push bx
push cx
push es

mov cl, 4
shl al, cl

mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000

sub3s:
and byte ptr es:[bx], 10001111b
or byte ptr es:[bx], al
add bx, 2
loop sub3s

pop es
pop cx
pop bx
ret

; AH = 3,向上滚动一行
sub4:
push cx
push si
push di
push es
push ds

mov si, 0b800h
mov es, si
mov ds, si

mov si, 160
mov di, 0
cld

mov cx, 24

sub4s:
push cx
mov cx, 160
rep movsb
pop cx
loop sub4s

; 清除最后一行
mov cx, 80
mov si, 0

sub4s1:
mov byte ptr [160*24 + si], ' '
mov byte ptr [160*24 + si + 1], 07h
add si, 2
loop sub4s1

pop ds
pop es
pop di
pop si
pop cx
ret

int7ch_end:
nop

code ends
end start

第十七章 使用BIOS进行键盘输入和磁盘读写

int9 中断例程对键盘输入的处理

按下按键,引发键盘中断,CPU执行int9中断,读取60h端口A键的通码,然后检测状态字节,检查是否有Shift、ctrl等切换键按下,如果没有,就把a键的扫描码和对应的ASCII码写入键盘缓冲区,高字节存储扫描码,低字节存储ASCII码

int 16h中断历程读取键盘缓冲区内容

1
2
3
mov ah, 0
int 16h
;从键盘缓冲区读取一个输入,并将其从缓冲区删除
  1. 检测键盘缓冲区是否有数据
  2. 如果没有重复1
  3. 读取缓冲区第一个字单元中的键盘输入
  4. 将扫描码放入ah,ASCII放入al
  5. 将已读取的数据从缓冲区删除

应用int 13h 中断例程对磁盘进行读写

入口参数

  1. ah = int 13h的功能号 (2表示读扇区 3表示写扇区)
  2. al = 读取的扇区数
  3. ch = 磁道号
  4. cl = 扇区号
  5. dh = 磁头号
  6. dl = 驱动器号 软驱从0开始,0:软驱A,1:软驱:B;硬盘从80h开始,80h:硬盘C,81h:硬盘D
  7. es:[bx] 指向接受从扇区读入数据的内存区

例如:将 0:200中的内容写入0面0道1扇区

1
2
3
4
5
6
7
8
9
10
11
12
13
mov ax,0
mov es,ax
mov bx,200h

mov al, 1
mov ch, 0
mov cl, 1
mov dl, 0
mov dh, 0

mov ah, 3
int 13h

实验17 编写包含多个功能子程序的中断例程

1面 = 80道 , 1道 = 18扇区,用统一编号的方式对磁盘进行管理从0~2879

逻辑扇区号 = (面号 * 80 + 磁道号)* 18 + 扇区号 -1

安装一个新的int7ch中断例程,实现通过逻辑扇区号对软盘进行读写

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
assume cs:code

code segment

start:
mov ax, cs
mov ds, ax
mov si, offset int7ch

; 将 int7ch 复制到 0000:0200
mov ax, 0
mov es, ax
mov di, 200h

mov cx, offset int7ch_end - offset int7ch
cld
rep movsb

; 设置 int 7ch 中断向量
mov ax, 0
mov es, ax

cli
mov word ptr es:[7ch*4], 200h
mov word ptr es:[7ch*4 + 2], 0
sti

mov ax, 4c00h
int 21h


; ------------------------------------------------
; int 7ch
;
; 参数:
; ah = 0 读
; ah = 1 写
; dx = 逻辑扇区号
; es:bx = 数据缓冲区
;
; 功能:
; 将逻辑扇区号转换成 面号/磁道号/扇区号
; 然后调用 int 13h 完成读写
; ------------------------------------------------

int7ch:
push ax
push bx
push cx
push dx
push si
push di

cmp ah, 1
ja int7ch_ret

mov si, ax ; 保存功能号,ah=0读,ah=1写

; dx = 逻辑扇区号
; 面号 = 逻辑扇区号 / 1440
; 余数 = 逻辑扇区号 % 1440
mov ax, dx
mov dx, 0
mov cx, 1440
div cx ; ax = 面号,dx = 余数

mov di, ax ; di 保存面号

; 磁道号 = 余数 / 18
; 扇区号 = 余数 % 18 + 1
mov ax, dx
mov dx, 0
mov cx, 18
div cx ; ax = 磁道号,dx = 扇区号 - 1

; 设置 int 13h 参数
mov ch, al ; ch = 磁道号
mov cl, dl
inc cl ; cl = 扇区号

mov ax, di
mov dh, al ; dh = 面号
mov dl, 0 ; dl = 驱动器号,0 表示 A 软驱

; 恢复功能号
mov ax, si
cmp ah, 0
je int7ch_read

int7ch_write:
mov ah, 3 ; int 13h 写扇区
jmp short doint13h

int7ch_read:
mov ah, 2 ; int 13h 读扇区

doint13h:
mov al, 1 ; 读/写 1 个扇区
int 13h ; 注意:这里是 int 13h,不是 int 7ch

int7ch_ret:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
iret

int7ch_end:
nop

code ends
end start

课程设计2

1
2
3
4
5
6
7
8
第 1 步:写一个最简单的 boot 程序,只显示菜单
第 2 步:写 install 程序,把 boot 写入软盘引导扇区
第 3 步:测试从软盘启动后能显示菜单
第 4 步:实现按键选择
第 5 步:实现 1 reset pc
第 6 步:实现 2 start system
第 7 步:实现 3 clock
第 8 步:实现 4 set clock

1

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
assume cs:code

code segment

menu1 db 'Boot Menu', 0
menu2 db '1) reset pc', 0
menu3 db '2) start system', 0
menu4 db '3) clock', 0
menu5 db '4) set clock', 0

start:
mov ax, 0b800h
mov es, ax
mov cx, 2000
mov bx, 0
clear:
mov byte ptr es:[bx], ' '
mov byte ptr es:[bx], 07h
add bx, 2
loop clear

;显示菜单
mov dh, 8
mov dl, 30
mov si, offset menu1
call show_string

mov dh, 10
mov dl, 30
mov si, offset menu1
call show_string

mov dh, 11
mov dl, 30
mov si, offset menu1
call show_string

mov dh, 12
mov dl, 30
mov si, offset menu1
call show_string

show_string:
push si
push ds
push ax
push es
push dx

mov ax, 0b800h
mov es, ax

mov al, 160
mul dh
mov bx, ax

mov al, dl
mov ah, 0
add ax, ax
add dx, ax

show_s:
mov al, cs:[si]
cmp al, 0
je show_ret

mov es:[dx], al
mov es:[dx+1], 07h
add dx, 2
jmp show_s

show_ret:
pop dx
pop es
pop ax
pop ds
pop si
ret

code ends
end start

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
assume cs:code

MAIN_ADDR equ 7e00h
MAIN_SECTORS equ 8

code segment

start:
; 写入第 1 扇区:boot_start
mov ax, cs
mov es, ax
mov bx, offset boot_start

mov ah, 03h
mov al, 1
mov ch, 0
mov cl, 1
mov dh, 0
mov dl, 0
int 13h

jc error

; 写入后续扇区:main_start
mov ax, cs
mov es, ax
mov bx, offset main_start

mov ah, 03h
mov al, MAIN_SECTORS
mov ch, 0
mov cl, 2
mov dh, 0
mov dl, 0
int 13h

jc error

; 写入成功,显示 S
mov ah, 2
mov dl, 'S'
int 21h

mov ax, 4c00h
int 21h

error:
mov ah, 2
mov dl, 'E'
int 21h

mov ax, 4c00h
int 21h


; ============================================================
; 第一级引导程序
; BIOS 会把它加载到 0000:7C00
; 作用:把主程序从软盘第 2 扇区开始读入 0000:7E00
; ============================================================

boot_start:
cli
mov ax, 0
mov ss, ax
mov sp, 7c00h
sti

mov ax, 0
mov es, ax
mov bx, MAIN_ADDR

mov ah, 02h
mov al, MAIN_SECTORS
mov ch, 0
mov cl, 2
mov dh, 0
mov dl, 0
int 13h

jc boot_start

; 跳转到主程序 0000:7E00
db 0eah
dw MAIN_ADDR
dw 0000h

; 填充到 510 字节
db 510 - ($ - boot_start) dup (0)
dw 0aa55h


; ============================================================
; 第二级主程序
; 会被加载到 0000:7E00
; ============================================================

main_start:
mov ax, 0
mov ds, ax

call show_menu

main_loop:
mov ah, 0
int 16h

cmp al, '1'
je reset_pc

cmp al, '2'
je start_system

cmp al, '3'
je clock

cmp al, '4'
je set_clock

jmp main_loop


; ============================================================
; 显示主菜单
; ============================================================

show_menu:
call clear_screen

mov dh, 8
mov dl, 30
mov si, offset menu1 - offset main_start + MAIN_ADDR
call show_string

mov dh, 10
mov dl, 30
mov si, offset menu2 - offset main_start + MAIN_ADDR
call show_string

mov dh, 11
mov dl, 30
mov si, offset menu3 - offset main_start + MAIN_ADDR
call show_string

mov dh, 12
mov dl, 30
mov si, offset menu4 - offset main_start + MAIN_ADDR
call show_string

mov dh, 13
mov dl, 30
mov si, offset menu5 - offset main_start + MAIN_ADDR
call show_string

ret


; ============================================================
; 功能 1:重启电脑
; ============================================================

reset_pc:
db 0eah
dw 0000h
dw 0ffffh


; ============================================================
; 功能 2:启动硬盘系统
; 从 C 盘 0 道 0 面 1 扇区读取系统引导扇区到 0000:7C00
; ============================================================

start_system:
mov ax, 0
mov es, ax
mov bx, 7c00h

mov ah, 02h
mov al, 1
mov ch, 0
mov cl, 1
mov dh, 0
mov dl, 80h
int 13h

jc start_system

db 0eah
dw 7c00h
dw 0000h


; ============================================================
; 功能 3:clock
; 显示 CMOS 时间
; Esc 返回主菜单
; F1 改变颜色
; ============================================================

clock:
call clear_screen

clock_loop:
call show_clock

; 检查是否有键按下
mov ah, 1
int 16h
jz clock_loop

; 读取按键
mov ah, 0
int 16h

; Esc 返回菜单
cmp al, 1bh
je clock_ret

; F1 改变颜色
cmp ah, 3bh
jne clock_loop

inc byte ptr ds:[color - offset main_start + MAIN_ADDR]
jmp clock_loop

clock_ret:
call show_menu
jmp main_loop


; ============================================================
; 功能 4:set clock
; 暂时占位,后面再写设置 CMOS 时间
; ============================================================

set_clock:
call clear_screen

mov dh, 12
mov dl, 25
mov si, offset set_msg - offset main_start + MAIN_ADDR
call show_string

mov ah, 0
int 16h

call show_menu
jmp main_loop


; ============================================================
; show_clock
; 显示格式:yy/mm/dd hh:mm:ss
; ============================================================

show_clock:
push ax
push bx
push cx
push dx
push si
push di
push bp
push es

mov ax, 0b800h
mov es, ax

; 显示在第 12 行,第 30 列
mov di, 160*12 + 30*2

mov si, offset cmos_addr - offset main_start + MAIN_ADDR
mov bp, offset sign - offset main_start + MAIN_ADDR

mov cx, 6

show_time:
; 读取 CMOS
mov al, ds:[si]
out 70h, al
in al, 71h

mov bl, al

; 显示十位
mov al, bl
push cx
mov cl, 4
shr al, cl
pop cx

add al, 30h
mov es:[di], al

mov al, ds:[color - offset main_start + MAIN_ADDR]
mov es:[di+1], al

add di, 2

; 显示个位
mov al, bl
and al, 00001111b
add al, 30h

mov es:[di], al

mov al, ds:[color - offset main_start + MAIN_ADDR]
mov es:[di+1], al

add di, 2

; 显示分隔符
mov al, ds:[bp]
cmp al, '$'
je show_clock_ret

mov es:[di], al

mov al, ds:[color - offset main_start + MAIN_ADDR]
mov es:[di+1], al

add di, 2

inc si
inc bp

loop show_time

show_clock_ret:
pop es
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret


; ============================================================
; 清屏
; ============================================================

clear_screen:
push ax
push bx
push cx
push es

mov ax, 0b800h
mov es, ax

mov bx, 0
mov cx, 2000

clear_s:
mov byte ptr es:[bx], ' '
mov byte ptr es:[bx+1], 07h
add bx, 2
loop clear_s

pop es
pop cx
pop bx
pop ax
ret


; ============================================================
; show_string
; 参数:
; dh = 行
; dl = 列
; ds:si = 字符串地址
; ============================================================

show_string:
push ax
push bx
push dx
push si
push es

mov ax, 0b800h
mov es, ax

; 计算行偏移:160 * dh
mov al, 160
mul dh
mov bx, ax

; 计算列偏移:dl * 2
mov al, dl
mov ah, 0
add ax, ax
add bx, ax

show_s:
mov al, ds:[si]
cmp al, 0
je show_ret

mov es:[bx], al
mov byte ptr es:[bx+1], 07h

inc si
add bx, 2
jmp show_s

show_ret:
pop es
pop si
pop dx
pop bx
pop ax
ret


; ============================================================
; 数据区
; ============================================================

menu1 db 'Boot Menu', 0
menu2 db '1) reset pc', 0
menu3 db '2) start system', 0
menu4 db '3) clock', 0
menu5 db '4) set clock', 0

set_msg db 'set clock is not finished. press any key...', 0

cmos_addr db 09h, 08h, 07h, 04h, 02h, 00h
sign db '/', '/', ' ', ':', ':', '$'
color db 07h

main_end:
nop

code ends
end start
-------------到底咯QAQ嘎嘎-------------