王爽汇编笔记(第三版)
1. 测试环境
1.1 DosBox
1 安装
简介:模拟dos环境的一个软件
下载地址:https://www.dosbox.com/download.php?main=1
安装步骤:下一步......
问题1:
'debug' 不是内部或外部命令,也不是可运行的程序或批处理文件。
debug : 无法将“debug”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
windows,win10,win11找不到debug如何运行
请点击链接查看解决方案进行解决:https://www.cnblogs.com/yunmuq/p/16593901.html
2 使用
debug命令
命令不区分大小写,比如R命令,输入R
或r
都是一样的
R命令
(1)查看寄存器
(2)修改寄存器
D命令
(1)查看内存10000H处的数据
左上角第一个00
就是10000H处的数据
E命令
(1)写10000H处开始的往后10个字节的内存数据
(2)一个个的进行修改,将10001 10002 10003 10004 10005的内存数据依次修改为 11 22 33 44 55
(3)修改字符串
最右侧显示的就是字符串,内存中存储的是字符对应的ascii值(16进制)
U命令
将内存中的机器指令翻译成汇编指令
T命令
执行一条机器指令
1. 使用E命令将机器码输入到1000:0内存处
机器码 对应的汇编指令
680100 mov ax,0001
b90200 mov cx,0002
01c8 add ax,cx
2. 再修改CS:IP的地址指向到1000:0内存处
3. 执行t命令
- 第一次: ax = 0001
- 第二次: cx = 0002
- 第三次: ax = 0003
A命令
以汇编指令的格式在内存中写入一条机器指令
P命令
- 执行loop循环
- 执行int中断
G命令
-
将程序运行到指定地址处(在不想用
-t
命令单步执行时 ) -
-g 076a:b
Q命令
- 退出debug
3 Debug中标志寄存器
标志 | 值为1的标记 | 值为0的标记 |
---|---|---|
of | OV | NV |
sf | NG | PL |
zf | ZR | NZ |
pf | PE | PO |
cf | CY | NC |
df | DN | UP |
2. 汇编指令
指令书写不区分大小写
汇编指令 | 备注 |
---|---|
mov | mov 寄存器,数据 mov 寄存器,寄存器 mov 寄存器,内存单元 mov 内存单元,寄存器 mov 段寄存器,寄存器 mov 寄存器,段寄存器 mov word/byte 内存单元,立即数 |
add | 两数相加 |
adc | 带进位相加 举例1:adc ax,1 ==> ax + 1+ cf |
sub | 两数相减 |
sbb | sbb是带借位减法指令,它利用了CF位上记录的借位值。 指令格式:sbb操作对象1,操作对象2 功能:操作对象1=操作对象1-操作对象 2-CF 比如指令 sbbax,bx实现的功能是:(ax)=(ax)-(bx)-CF |
jmp | (1) 设置IP寄存器的值 (2) 可以同时设置CS和IP的值 使用举例: jmp word ptr (改变IP的值) jmp dword ptr(改变CS:IP的值,高2字节存放CS的值,低2字节存放IP的值) jmp short 标号 (IP = IP+8位位移) jmp near ptr 标号(IP = IP+16位位移) jmp far ptr 标号 (CS=标号所在段的段地址,IP=标号所在段中的偏移地址) jmp 寄存器 |
call | (1) 设置IP寄存器的值 (2) 可以同时设置CS和IP的值 - call 标号(16位位移) - call far ptr 标号(将CS和IP的值入栈,CS:IP = 标号所在的段地址:偏移地址) - call 16位寄存器:比如 call ax - call word ptr 内存单元地址(IP 入栈,jmp word ptr 内存单元地址中的值) - call dword ptr 内存单元地址(push cs,push ip, jmp dword ptr 内存单元地址) |
ret | (1) 设置IP寄存器的值 - 指令用栈中的数据,从而实现近转移:pop IP |
retf | - 用栈中的数据,修改CS和IP的内容,从而实现远转移(假如栈有4个字节,低2字节存IP,高2字节存CS)。 pop IP pop Cs |
jcxz | (1) 有条件的转移指令(if (cx == 0) { 执行 jcxz 标号 指令}) jcxz 标号 |
push | push 寄存器 push 段寄存器 push 内存单元 操作的【字型】数据 两个字节 |
pop | pop 寄存器 pop 段寄存器 pop 内存单元 操作的【字型】数据 两个字节 |
inc | 加1 |
dec | 减1 |
loop | 循环 |
and | 按位与 |
or | 按位或 |
cmp | 比较指令 无符号比较: cmp ax,bx ![]() 有符号比较: cmp ah, bh sf=1,of=0,(ah) < (bh) sf=1,of=1,(ah) > (bh) sf=0,of=1,(ah) < (bh) sf=0,of=0,(ah) >= (bh) ![]() |
movsb | 字节长度 配合df标志位寄存器,对di和si寄存器加减1 |
movsw | 双字节长度 配合df标志位寄存器,对di和si寄存器加减2 |
cld | DF=0 |
tld | DF=1 |
sti | IF=1 |
cli | IF=0 |
rep | 重复指令,结合cx中的值,什么时候cx=0,什么时候结束 |
pushf | 将标志寄存器的值压栈 |
popf | 从栈中弹出数据,送入标志寄存器中 |
iret | 返回 说明:和(pop IP ,pop Cs , popf) 功能一样 |
in | 从端口读入数据 |
out | 从端口写入数据 |
shl | 逻辑左移 - 将最后移出的一位存入cf中 - 最低位用0补充 如果移动位数大于1,将移动位数放入cl中,举例如下: - mov al, 01000101b - mov cl,3 - shl al, cl (将al左移3位) |
shr | 逻辑右移 和左移同理,只不过移动的方向相反 |
mul | 两数相乘【mul指令用法说明】 |
div | 两数相除 【div指令用法说明】 |
mul指令用法说明
乘数位宽 | 其中一个数默认存放的寄存器 | 另外一个数存放在的位置 | 乘积结果 |
---|---|---|---|
8bit | AL | 8位寄存器或内存【字节】单元 | AX |
16bit | AX | 16位寄存器或内存【字】单元 | 高2字节放在DX,低2字节放在AX中 |
div指令用法说明
除数位宽 | 被除数位宽 | 被除数存放的寄存器 | 【商】存放的寄存器 | 【余数】存放的寄存器 |
---|---|---|---|---|
8bit | 16bit | AX | AL | AH |
16bit | 32bit | DX(高16位) + AX(低16位) | AX | DX |
3. 寄存器
通用寄存器 | 备注 |
---|---|
AX | |
BX | (1) 通常用于存储内存单元的【偏移地址】 |
CX | (1) 通常存放程序的字节长度 (2) 存放loop指令用到的次数 |
DX | |
SP | (1) 默认存放:栈顶的偏移地址 |
SI(source index 源变址寄存器) | |
DI (destination index 目的变址寄存器) | |
段寄存器 | 备注 |
---|---|
CS | (1) 通常存放代码的内存单元的【段地址】 |
DS | (1) 通常存放访问内存的【段地址】 |
SS | (1) 默认存放栈顶的段地址 |
ES | |
标志寄存器 | 备注 |
---|---|
flag | ![]() |
ZF: 零标志位
PF: 奇偶标志位(1个数为偶数,PF=1,反之,PF=0)
SF: 符号标志位(它记录相关指令执行后,其结果是否为负。如果结果为负,SF=1;如果非负,SF=0。)
CF: 进位标志位(用于记录进位值和减法时的借位值)
OF: 溢出标志位(记录了有符号数运算的结果是否发生了溢出。如果发生溢出,OF=1;如果没有,OF=0。)
DF: 方向标志位(控制每次操作后si、di的增减。df=0 每次操作后 si、di 递增; df=1 每次操作后 si、di 递减。)
mov\push\pop 等转移指令对flag无影响
add\sub\mul\div\inc\or\and 计算指令对flag有影响
4. 伪指令
伪指令 | 备注 |
---|---|
segment和ends | (1) 成对使用的,用于标识一个段的代码 |
end | (1) 标识全部程序结束,编译器的工作就结束了 (2) 还可以指定程序入口的作用 |
assume | (1) 将寄存器和程序段关联起来 |
db | (1) 定义字节数据(define byte), 1个字节 |
dw | (1) 定义字型数据(define word), 2个字节 |
dd | (1) 双字(double word)型数据,4个字节 |
dup | (1) 用于数据的重复的操作符 举例: (1) db 3 dup (0) ; 定义了3个字节,他们的值都是0,相当于 db 0,0,0 (2) db 3 dup(0.1.2); 定义了9个字节,它们是 0、1、2、0、1、2、0、1、2,相当于 db 0,1,2,0,1,2,0,1,2. |
offset | 取得【标号】的偏移地址 |
5. 寻址方式
注意:
- 如过寻址用到 bp 寄存器,如果没有显示说明,默认的段寄存器是ss
寻址用的寄存器
BX SI DI DP
只有这四个可以用在 `[...]`中进行内存单元寻址,比如:
6. 中断
1 中断码
一个字节的数据,也就是256个中断码
中断码 | ||
---|---|---|
除法错误 | 0 | 内中断 |
单步执行 | 1 | 内中断 |
into指令 | 4 | 内中断 |
int指令 | int n(n是字节型,中断码) | 内中断 |
2. 内中断
3. 外中断
3.1 可屏蔽中断
可以不响应的中断
IF = 1 ; 则CPU在执行完当前指令后响应中断,引发中断过程
IF = 0 ; 则不响应中断。
3.2 不可屏蔽中断
中断类型码固定为 2
4. 中断向量表
- 中断向量就是中断处理程序的入口地址,所以中断向量表就是一个中断程序的入口地址列表
- 存放地址:
0000:0000 到 0000:03FF
,总共1024个内存单元 - 一个中断向量占用
4
个字节,高16位存放段地址
, 低16位存放偏移地址
7. 端口
对 0~255
以内的端口进行读写时:
in al,20h ;从20n端口读入一个字节
out 20h,al ;往20h端口写入一个字节
对 256~65535
的端口进行读写时,端口号放在dx中:
mov dx,3fBh ;将端口号3f8h送入dx
in al,dx ;从3f8h端口读入一个字节
out dx,al ;向3f8h端口写入一个字节
8. 课后检测答案
第1章:基础知识
检测点1.1
第2章:寄存器
检测点2.1
1、写出每条汇编指令执行后相关寄存器的值。
2、只能使用止前学过的汇编指令,最多使用4条指令,编程计算2的4次方。
mov al, 2
add al, al
add al, al
add al, al
检测点2.2
一个段的偏移地址范围是:64KB(0000 ~ FFFF)
(1)给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围 00010H
到 1000FH
。
(2)有一数据存放在内存20000H单元中,现给定段地址为SA ,若想用偏移地址寻到此单元。则SA应满足的条件是:最小值为 1001H ,最大值为 2000H 。
20000H - 0000H = 20000H
20000H - FFFFH = 10001H
最小值其实应该是10001H,但这里是问的段地址SA,那么将10001 / 16(向右移动4位)成为1000H,但是1000H又不满足条件,只能加个1,成为1001H
(3)思考题:如果段地址(SA) 小于1001H
或 大于2000H
,那么就找寻不到 20000H
单元的内存数据
检测点2.3
下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?
mov ax,bx
sub ax,ax
jmp ax
第一个问题:4次
第二个问题:
(1) 读取第一条指令到指令缓冲区,这时 IP 修改为下一条指令的字节地址
(2) 读取第二条指令到指令缓冲区,这时 IP 修改为下一条指令的字节地址
(3) 读取第三条指令到指令缓冲区,这时 IP 修改为下一条指令的字节地址
(4) jmp 指令有修改IP的作用,所以第三条指令执行后,又从新修改了IP的地址为ax的值,这时ax=0,所以最后IP的值是0
第三个问题:0
第3章:寄存器(内存访问)
检测点3.1
(1) 在 Debug
中,用 “-d 0:0 1f”
查看内存,结果如下。
0000:0000 70 80 F0 30 EF 60 30 E2-00 80 80 12 66 20 22 60
0000:0010 62 26 E6 D6 CC 2E 3C 3B-AB BA 00 00 26 06 66 88
下面的程序执行前,AX=0, BX=0
, 写出每条汇编指令执行完后相关寄存器中的值。
mov ax, 1 AX=0001H
mov ds, ax DS=0001H
mov ax, [0000] AX=2662H (DS<==>内存段地址,这条命令相当于将0001:0000内存地址对应的【字】数据赋值给ax, x86内存是小端序存储,所以是2662)
mov bx, [0001] BX=E626H
mov aX, bx AX=E626H
mov ax, [0000] AX=2662H
mov bx, [0002] BX=D6E6H
add ax, bx AX=FD48H
add ax, [0004] AX=2C14H (2ECCH + FD48H = 12C14H,超过16位,将1截取掉)
mov ax, 0 AX=0000H
mov al, [0002] AX=00E6H
mov bx, 0 BX=0000H
mov bl, [000C] BX=0026H
add al, bl AX=000CH (E6H + 26H = 10CH,超过8位,将前面的1截取掉)
提示,注意 ds 的设置,
(2) 内存中的情况如图 3.6 所示
①②
CS | IP | DS | AX | BX | |
---|---|---|---|---|---|
初始值 | 2000H | 0 | 1000H | 0 | 0 |
指令序列 | |||||
mov ax, 6622H |
2000H | 0003H | 1000H | 6622H | 0 |
jmp 0ff0:0100 |
0FF0H | 0100H | 1000H | 6622H | 0 |
mov ax, 2000H |
0FF0H | 0103H | 1000H | 2000H | 0 |
mov ds,ax |
0FF0H | 0105H | 2000H | 2000H | 0 |
mov ax, [0008] |
0FF0H | 0108H | 2000H | C389H | 0 |
mov ax, [0002] |
0FF0H | 010BH | 2000H | EA66H | 0 |
③ 没有区别,通过段寄存器 CS 和 DS的指向地址进行区分
检测点3.2
mov ax,1000H
mov ds,ax
#
mov ax,2000H
mov ss,ax
mov sp,0010H (空栈,F+1=10H)
#
push[0]
......
(2) 补全下面的程序,使其可以将10000H -- 1000FH中的8个字,逆序复制到20000H~2000FH中
mov ax,2000H
mov ds,ax
#
mov ax,1000H
mov ss,ax
mov sp,0000H (栈地址:1000:0000,这次是要将10000-1000F中的数据复制到20000-2000F中)
#
pop [E]
......
实验任务2
(1) 使用 Debug,将下面的程序段写入内存,逐条执行,根据指令执行后的实际运行情况填空。
mov ax,ffff
mov ds,ax
mov ax,2200
mov ss,ax
mov sp,0100
mov ax,[0] ;ax= C0EA ; [FFFF:0] = C0EA
add ax,[2] ;ax= C0FC ; [FFFF:2] = 0012
mov bx,[4] ;bx= 30F0 ; [FFFF:4] = 30F0
add bx,[6] ;bx= 6021 ; [FFFF:6] = 2F31
# 此时SP = 0100
push aX ;sp= 00FE ; 修改的内存单元的地址是: 2200:00FE 内容为:COFC
push bx ;sp= 00FC ; 修改的内存单元的地址是: 2200:00FC 内容为:6021
pop аx ;sp= 00FE ; ax= 6021
Pop bx ;sp= 0100 ; bx= C0FC
push [4] ;sp= 00FE ; 修改的内存单元的地址是: 2200:00FE 内容为:30F0
push [6] ;sp= 00FC ; 修改的内存单元的地址是: 2200:00FC 内容为:2F31
#
NOTE:
(1)软件版本或者环境不一样,ffff段地址开始的内存单元中的数据有可能不一样
(2)修改的内存单元地址这里写的是“字型”地址
(3)SP填写的是指令执行后的地址
第4章
实验3 编程、编译、链接、跟踪
(1) 这里将文件名保存为 p94.asm, 可执行文件名称是 p94.exe
文件名称:p94.asm
assume cs:codesg
codesg segment
mov ax,2000h
mov ss,ax
mov sp,0
add sp,10
pop ax
pop bx
push ax
push bx
pop ax
pop bx
mov ax,4c00h
int 21h
codesg ends
end
# 生成exe文件
> masm p94.asm;
> link p94.obj;
(2) debug跟踪p94.exe
这里提前将2000:0 - 2000:F 的内容依次填充为: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
mov ax,2000h ; ax=2000
mov ss,ax ; ss=2000
mov sp,0 ; sp=0000
add sp,10 ; sp=000A
pop ax ; ax=1211, sp=000C
pop bx ; bx=1413, sp=000E
push ax ; ax=1211, sp=000C
push bx ; bx=1413, sp=000A
pop ax ; ax=1413, sp=000C
pop bx ; bx=1211, sp=000E
(3) PSP的头两个字节是CD 20
,用Debug加载tl.exe(这里是p94.exe),查看PSP 的内容.
CS = 076A, 那么PSP的段地址是:076A - 10 = 075A, PSP总共占用256个字节,也就是075A:0000 - 075A:00F0
第5章
实验4 [bx]和 loop 的使用
(1) 编程,向内存 0:200-0:23F
依次传送数据 0-63(3FH)
。
; 向内存0:200~0:23F内存单元依次存入0~63
assume cs:codesg
codesg segment
mov ax,0020h
mov ds,ax
mov bx,0
mov dx,0
mov cx,40h
s: mov [bx],dx
inc bx
inc dx
loop s
mov ax,4c00h
int 21h
codesg ends
end
(2) 编程,向内存 0:200 ~ 0:23F
依次传送数据 0~63(3FH)
,程序中只能使用9条指令,9条指令中包括“mov ax,4c00h”和“int 21h”。
; 向内存 `0:200 ~ 0:23F` 依次传送数据 `0~63(3FH)`,程序中只能使用9条指令,9条指令中包括“mov ax,4c00h”和“int 21h”。
; 其实将上面 dx相关的两条指令去掉即可
assume cs:codesg
codesg segment
mov ax,0020h
mov ds,ax
mov bx,0
mov cx,40h
s: mov [bx],bx
inc bx
loop s
mov ax,4c00h
int 21h
codesg ends
end
(3) 下面的程序的功能是将“mov ax,4c00h”之前的指令复制到内存0:200处,补全程序。上机调试,跟踪运行结果。
; 下面的程序的功能是将“mov ax,4c00h”之前的指令复制到内存0:200处
assume cs:code
code segment
mov ax, code ; 标号code和cs指向同一个地址(代码段地址)
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx,18h ; 这段程序占用24(18h)个字节
s: mov al,[bx]
mov es:[bx], al
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
第6章
检测点6.1
(1) 下面的程序实现依次用内存 0:0~0:15单元中的内容改写程序中的数据,完成程序:
......
mov cs:[bx], ax
......
(2) 下面的程序实现依次用内存0:0-0:15单元中的内容改写程序中的数据,数据的传送用栈来进行。栈空间设置在程序内。完成程序:
......
start: mov ax,cs ; 栈的段地址是cs
mov ss,ax
mov sp,24H ; 初始化栈顶地址0:24H(dw 开辟了18个字的空间=36字节)
......
s: push [bx] ; 入栈
pop cs:[bx] ; 出栈
......
实验5 编写、调试具有多个段的程序
(1) 将下面的程序编译、连接,用 Debug 加载、跟踪,然后回答问题
① CPU 执行程序,程序返回前,data段中的数据为多少?
保持不变(因为入栈、出栈的操作正好相反)
② CPU 执行程序,程序返回前,cs = 076C
、ss = 076B
、ds = 076A
(环境不一样,这个值也会不一样 )
③ 设程序加载后,code段的段地址为 X
,则data段的段地址为X-2
,stack段的段地址为X-1
(2) 将下面的程序编译、连接,用 Debug 加载、跟踪,然后回答问题
①②③同上
④ 对于如下定义的段:
name segment
......
name ends
如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为 (N/16+1)*16 [说明:N/16只取整数部分]
(3) 将下面的程序编译、连接,用 Debug 加载、跟踪,然后回答问题
①②同上
③ 设程序加载后,code段的段地址为 X
,则data段的段地址为X+3
,stack段的段地址为X+4
这是因为code segment程序段占用的实际空间为34Byte,但是内存分配的空间需要是16的倍数,所以最后2个字节也需要占用16个字节的空间
(4) 如果将(1)、(2)、(3)题中的最后一条伪指令“end start”改为“end”(也就是说,不指明程序的入口),则哪个程序仍然可以正确执行?请说明原因。
第3个程序会正常执行,因为不知名程序的入口,系统会顺序执行
(5) 程序如下,编写code段中的代码,将a段和b段中的数据依次相加,将结果存到c段中。
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 bx, 0
mov cx, 8
s: mov al, [bx]
add es:[bx], al
inc bx
loop s
; 存储
mov bx, 0
mov cx, 8
mov ax, c
mov ds, ax
s0: mov al, es:[bx]
mov [bx], al
inc bx
loop s0
; 返回
mov ax,4c00h
int 21h
code ends
end start
(6) 程序如下,编写代码段中的代码,用推指令将a段中的前8个字型数据,逆序存储到b 段中。
assume cs:code
a segment
dw 1,2,3,4,5,6,7,8,9,0ah, 0bh,0ch,0dh,0eh,0fh,0ffh
a ends
b segment
db 0,0,0,0,0,0,0,0
b ends
code segment
start: mov ax, b
mov ss, ax
mov sp, 10H ; 初始化栈顶
mov ax, a
mov ds, ax
mov bx, 0
s: push [bx]
add bx,2
loop s
; 返回
mov ax,4c00h
int 21h
code ends
end start
第7章 更灵活的定位内存地址的方法
实验6 实践课程中的程序
(1)
略
(2) 编程,完成问题7.9中的程序。
assume cs:codesg,ss:stacksg,ds:datasg
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
datasg segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
datasg ends
codesg segment
start: mov ax,stacksg
mov ss,ax
mov sp,10H
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s1: push cx ; 将外循环次数入栈
mov si,0
mov cx,4
s2: mov al,[bx+si+3]
and al,11011111b ; 将小写字母改为大写字母
mov [bx+si+3],al
inc si
loop s2
add bx,10h ; 移动到下一行
pop cx ; 将外循环次数出栈
loop s1
; 返回
mov ax,4c00h
int 21h
codesg ends
end start
第8章 数据处理的两个基本问题
实验7 寻址方式在结构化数据访问中的应用
assume cs:codesg
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个字符串
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型数据
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型数据
data ends
table segment
db 21 dup('year sum ne ?? ')
table ends
codesg segment
; 源数据内存
start: mov ax,data
mov es,ax ; es用于存储源数据的段地址
mov di,0
mov si,0
; table内存
mov ax,table
mov ds,ax
mov bx,0 ; bx用于定位表格的行
; 循环数量
mov cx,21
; 年份(4字节)
s: mov al,es:[0+di] ; 年份的第1个字节
mov [bx+0],al
mov al,es:[1+di] ; 年份的第2个字节
mov [bx+1],al
mov al,es:[2+di] ; 年份的第3个字节
mov [bx+2],al
mov al,es:[3+di] ; 年份的第4个字节
mov [bx+3],al
; 空格(1字节, 这里用个0用于区分,也可以用其它)
mov al,0
mov [bx].4,al
; 收入(4字节)
mov ax,es:[54h+di] ;54h是收入的初始地址
mov [bx].5,ax
mov ax,es:[56h+di]
mov [bx].7,ax
; 空格(1字节, 这里用个0用于区分,也可以用其它)
mov al,0
mov [bx].9,al
; 雇员数量(2字节)
mov ax,es:[0a8h+si] ;a8h是雇员数量的初始地址
mov [bx].10,ax
; 空格(1字节, 这里用个0用于区分,也可以用其它)
mov al,0
mov [bx].12,al
; 人均收入(2字节) = 收入/雇员数量
mov ax,es:[54h+di] ; 收入低2位字节存放在ax中
mov dx,es:[56h+di] ; 收入高2位字节存放在dx中
div word ptr es:[0a8h+si] ;
mov [bx].13,ax ; 平均收入整数部分
; 空格(1字节, 这里用个0用于区分,也可以用其它)
mov al,0
mov [bx].15,al
add bx,16
add di,4
add si,2
loop s
; 返回
mov ax,4c00h
int 21h
codesg ends
end start
第9章 转移指令的原理
检测点9.1
(1) 程序如下 : 若要使程序中的jmp 指令执行后,CS:IP指向程序的第一条指令,在data 段中应该定义哪些数据?
assume cs:codesg
data segment
; 要使jmp跳转到start处,那就需要ds:[bx+1]对应内存单元的值是0,也就是下标位1和2的内存单元中的值是0
; 三种方式都行
;db 3 dup(0)
;dw 0,0
dd 0
data ends
codesg segment
start: mov ax,data
mov ds,ax
mov bx,0
jmp word ptr [bx+1]
; 返回
mov ax,4c00h
int 21h
codesg ends
end start
(2) 程序如下,补全程序,使jmp指令执行后,CS:IP指向程序的第一条指令
assume cs:code
data seqment
dd 12345678H
data ends
code segment
start:mov ax, data
mov ds, ax
mov bx, 0
mov [bx], 0 ; 低2字节存放的是IP的值
mov [bx+2], cs ; 高2字节存放的是CS的值
jmp dword ptr ds:[0]
; 返回
mov ax,4c00h
int 21h
code ends
end start
(3) 用Debug查看内存,结果如下:
2000:1000 BE 00 06 00 00 00 ...
则此时,CPU 执行指令:
mov ax, 2000H
mov es,ax
jmp dword ptr es:[1000H]
后,(CS) = ?,(IP) = ?
CS = 0006H # 高2字节存放的是CS的值
IP = 00BEH # 低2字节存放的是IP的值
检测点9.2
补全编程,利用 jcxz
指令,实现在内存 2000H
段中查找第一个值为 0
的字节,找到后,将它的偏移地址存储在 dx中。
assume cs:code
code segment
start: mov ax,2000H
mov ds,ax
mov bx,0
; 补全代码
s: mov cl,ds:[bx]
mov ch,0
jcxz ok
inc bx
jmp short s
ok: mov dx,bx
; 返回
mov ax,4c00h
int 21h
code ends
end start
检测点9.3
补全编程,利用 loop
指令,实现在内存 2000H
段中查找第一个值为 0
的字节,找到后,将它的偏移地址存储在 dx
中。
assume cs:code
code segment
start: mov ax,2000H
mov ds,ax
mov bx,0
s: mov cl,[bx] ; 这里初始化cx的值,如果cx=0
mov ch,0
; 补全代码
inc cx ; 这里 cx = cx + 1 cx = 1
inc bx
loop s ; 这里 cx = cx - 1 cx = 0(结束循环)
ok; dec bx ;dec抬令的功能和inc相反,dec bx进行的操作为:(bx)=(bx)-1
mov dx,bx
; 返回
mov ax,4c00h
int 21h
code ends
end start
实验8 分析一个奇怪的程序
assume cs:code
code segment
; 返回
mov ax,4c00h
int 21h
start: mov ax,0 ; 结合下图会更清晰一些
s: nop ; 经过第一轮执行后,此处的机器码变为:EBF6, F6是补码形式,转换成源码后的值是:-10(10进制)
nop ; 这里的考察知识点 【jmp short 标号】 = 标号处的地址 - jmp 指令后的第一个字节地址
; -10 = 标号处的地址 - 10(这时s 处的指令是 EBF6 ),所以标号 = 0
; 也就是跳转到 最上面的 mov ax,4c00h处(返回代码),也就是说从s1开始往后的代码不会被执行到
mov di, offset s ; di = 0008H
mov si, offset s2 ; si = 0020H
mov ax,cs:[si] ; ax = EBF6H
mov cs:[di],ax ; 此指令执行后,标号【s】开始的两个nop占用的两个字节被EBF6取代)
s0: jmp short s ; jmp 0008
s1: mov ax,0 ;
int 21h
mov ax,0
s2: jmp short s1
nop
code ends
end start
结合下图:
结论:可以正常执行,但是从s1开始往后的代码不会被执行
实验9 根据材料编程
; 根据资料显示,一屏铺满需要用到4000(25行 * 160列)个字节,现在要让'welcome to masm!',这16个字节分成三行显示,并显示在中间
; 那么中间开始处的字节位置是在第12行的第72(下标是71字节)列开始
; 计算: B8000H + 6E0H(11*160) + 48H((160-16/2)) - 8H(从左到右的字节下标是从0开始算的) = b8720H
; 那么段地址就是:b872H
assume cs:code
data segment
db 'welcome to masm!' ; 显示的文字
db 02h,24h,71h ; 绿色=00000010b=02h, 绿底红色=00100100b=24h, 白底蓝色=01110001b=71h
data ends
stack segment
db 0,0 ; 用于存放外层循环数量
stack ends
code segment
start: mov dx, 0b872h ; 中间位置开始的段地址
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 10h
mov di,0
mov cx,3
s1: push cx
mov es,dx
mov bx,0
mov si,0
mov cx,16
s2: mov al,[bx]
mov es:[si],al
mov al,ds:[16+di] ; 颜色
mov es:[si+1], al
inc bx
add si,2
loop s2
inc di
pop cx
add dx,0ah ; 下一行
loop s1
; 返回
mov ax,4c00h
int 21h
code ends
end start
第10章
检测点10.1
......
mov ax, 1000H
push ax ; 相当于CS=1000H(在使用retf的情况下)
mov ax, 0
push ax ; 相当于IP=0H(在使用retf的情况下)
retf
......
检测点10.2
AX中的数值是6
原因是:系统读取第二条指令后,IP指向下一条指令的字节地址,也就是:6,但是call 指令执行时有入栈和跳转两重作用,
(1)先将IP值入栈,这时将6压入栈中
(2)跳转到标号s所在的指令处,执行pop ax 出栈操作,就是将6存入到ax中,所以ax的值是6
检测点10.3
AX中的数值是1010H
原因是:系统读取第二条指令后,IP指向下一条指令的字节地址,也就是:8H,但是(call far ptr) 指令执行时有入栈和跳转两重作用,
(1)先将 CS=1000H 入栈
(2)再将 IP=8H 入栈
(3)pop ax ; 8H出栈,ax=8H
(4)add ax,ax ; ax=8H+8H=10H
(5)pop bx ; 1000H出栈,bx=1000H
(6)add ax,bx ; ax = 1000H + 10H = 1010H
检测点10.4
AX中的数值是 0BH
(1)mov ax, 6 ; ax=6H
(2)call ax ; 两步操作:(1)这时IP=5H(下一条指令的字节地址)入栈,(2) 跳转到字节等于6的指令
(3)mov bp,sp ; sp是栈顶的位置
(4)add ax,[bp] ; [bp这里用bp寄存器,那么内存单元寻址的段地址用的是ss寄存器中的值],也就是:ss:[bp]
(5)5H + 6H = 0BH
检测点10.5
(1) 下面的程序执行后,ax中的数值为多少?(注意:用cal 指令的原理来分析,不要在 Debug 中单步跟踪来验证你的结论。对于此程序,在Debug中单步跟踪的结果,不能代表 CPU 的实际执行结果。)
AX中的数值是 3
结合下图进行分析:注意每条指令的开始字节地址
......
mov ax,stack
mov ss,ax
mov sp,16
mov ds,ax ; 现在栈ss和ds指向同一个内存地址
call word ptr ds:[0EH] ; 执行两步操作:(1) push ip=0011(ss:[14]) (2) 跳转到ds:[0EH]指令处,也就是ss:[14],0Eh就是14的16进制
; 相当于没有跳转,和顺序执行效果一样,所以经过3个inc ax后,ax=3
(2) 下面的程序执行后,ax和bx中的数值为多少?
结论:ax=1, bx=0
结合下图进行分析:注意每条指令的开始字节地址
......
mov word ptr ss:[0],offset s ; 标号【s】字节起始地址是:001AH, SS:[0]=001Ah
mov ss:[2],cs ; ss:[2] = 076bH
call dword ptr ss:[0] ; (1) PUSH CS(076BH),SS:[14] = 076BH, PUSH IP(0019h),SS:[12]=0019H
; (2) call 双字节后,CS:IP=(SS:[2]):(SS:[0])=(076bH):(001AH)
......
mov ax,offset s ; ax=001AH
sub ax,ss:[0ch(12)] ; ax=ax-0019H=001Ah-0019H=1H
mov bx,cs ; bx=076bH
sub bx,ss:[0eh(14)] ; bx=076bH-076bH=0H
......
第11章
检测点11.1
写出下面每条指令执行后,ZF、PF、SF等标志位的值
sub al,al ;al=0 ZF=1 PF=1 SF=0
mov al,1 ;al=1无影响 ZF=1 PF=1 SF=0
push ax ;无影响 ZF=1 PF=1 SF=0
pop bx ;bx=1无影响 ZF=1 PF=1 SF=0
add al,bl ;al=2 ZF=0 PF=0 SF=0
add al,10 ;al=12 ZF=0 PF=1 SF=0
mul al ;ax=144 ZF=0 PF=1 SF=0
检测点11.2
写出下面每条指令执行后,ZF、PF、SF、CF、OF等标志位的值
有符号数范围是:-128--127
无符号数范围是:0--255
sub al,al ;al=0 ZF=1 PF=1 SF=0 CF=0 OF=0
mov al,10H ;al=10H无影响 ZF=1 PF=1 SF=0 CF=0 OF=0
add al,90H ;al=A0H ZF=0 PF=1 SF=1 CF=0 OF=0
mov al,80H ;al=80H无影响 ZF=0 PF=1 SF=1 CF=0 OF=0
add al,80H ;al=100H ZF=1 PF=1 SF=0 CF=1 OF=1
mov al,0FCH ;al=FCH无影响 ZF=1 PF=1 SF=0 CF=1 OF=1
add al,05H ;aL=101H ZF=0 PF=0 SF=0 CF=1 OF=0
mov al,7DH ;al=70H无影响 ZF=0 PF=0 SF=0 CF=1 OF=0
add al,0BH ;al=88H ZF=0 PF=1 SF=1 CF=0 OF=0
检测点11.3
(1) 补全下面的程序,统计F000:0处 32个字节中,大小在[32,128]的数据的个数。
这道题要找的数字要满足以下条件:32 <= N <= 128
......
cmp al,32
jb s0 ; 小于32,不符合条件,下一个
cmp al,128
ja s0 ; 大于128,不符合条件,下一个
......
(2) 补全下面的程序,统计F000:0处 32个字节中,大小在(32,128)的数据的个数。
这道题要找的数字要满足以下条件:32 < N < 128
......
cmp al,32
jna s0 ; 不高于32,不符合条件,下一个
cmp al,128
jnb s0 ; 不低于128,不符合条件,下一个
......
检测点11.4
mov ax,0 ; ax=0
push ax ; 入栈
popf ; 标志寄存器所有位=0
mov ax,0fff0h ; 对标志寄存器没有影响,所有位还是0
add ax,0010h ; 结果:1 0000 0000 0000 0000B,如果按照无符号相加有进位:CF=1,按照有符号相加有溢出: OF=1, 不管怎么样,结果是0,那么ZF=1
; 那么标志寄存器数据形式:0000 0000 0100 0101B = 45H
pushf ; 将45H入栈
pop ax ; ax=45H
and al,11000101B ; 和01000101B按位与,al=0100 0101B
and ah,00001000B ; 和00000000B按位与,ah=0000 0000B
结果ax=45h
实验11
assume cs:code
datasg segment
db "Beginner's All-purpose Symbolic Instruction Code.",0
datasg ends
code segment
begin: mov ax,datasg
mov ds,ax
mov si,0
call letterc
; 返回
mov ax,4c00h
int 21h
letterc: mov al, ds:[si]
cmp al, 0
je s1
cmp al,97 ; [97,122] 小写字母十进制数值范围
jb s0 ; 小于97,不符合条件,下一个
cmp al,128
ja s0 ; 大于128,不符合条件
and al,11011111B ; 将小写字母转大写
mov ds:[si], al
s0: inc si
jmp short letterc
s1: ret
code ends
end begin
第12章
检测点12.1
(1) 3号中断源对应的中断处理程序入口地址为:0070:018B
(2) 存储N号中断源对应的中断处理程序入口的偏移地址的内存单元的地址为: 4N
, 存储N号中断源对应的中断处理程序入口的段地址的内存单元的地址为: 4N+2
实验12 编写0号中断的处理程序
编写0号中断的处理程序,使得在除法溢出发生时,在屏幕中间显示字符串“divide error!”,然后返回到 DOS。
要求:仔细跟踪调试,在理解整个过程之前,不要进行后面课程的学习。
assume cs:code
code segment
begin: mov ax,cs
mov ds,ax
mov si,offset db0
mov ax,0
mov es,ax
mov di,200h ; 将中断程序放在此处
mov cx, offset db0end - offset db0start ; 设置中断程序的字节长度
cld ; df位=0
rep movsb
; 设置中断向量表
mov ax,0
mov es,ax
mov word ptr es:[0],200h
mov word ptr es:[2],0
; 除法溢出代码
call divTest
mov ax,4c00h
int 21h
divTest: push ax
push bx
mov ax,1000H
mov bl,1H
div bl ;产生除法溢出,触发0号中断
pop bx
pop ax
ret
db0: jmp short db0start
db 'divide error!'
db0start: mov ax,cs
mov ds,ax
mov si,202h ;
mov ax, 0b800h ; 屏幕缓冲区
mov es, ax
mov di, 12 * 160 + 34 * 2
mov cx,13
s1: mov al,ds:[si]
mov es:[di], al
mov al,2h
mov es:[di+1],al
inc si
add di,2
loop s1
mov ax,4c00h
int 21h
db0end: nop
code ends
end begin
第13章
检测点13.1
(1) 在上面的内容中,我们用 7ch中断例程实现 loop 的功能,则上面的 7ch 中断例程所能进行的最大转移位移是多少?
最大位移是FFFFH,因为用bx存储位移,bx是16位寄存器
(2) 用7ch中断例程完成 jmp near ptr s
指令的功能,用bx向中断例程传送转移位移.
应用举例:在屏幕的第12行,显示data段中以0结尾的字符串
; 使用int 7ch 中断例程实现 jmp near ptr s 的功能
assume cs:code
data segment
db 'conversation',0
data ends
code segment
; 将程序安装在 0:200h处
start: mov ax,cs
mov ds,ax
mov si,offset jpstart
mov ax,0
mov es,ax
mov di,200h ; 将中断程序放在此处
mov cx, offset jpend - offset jpstart ; 设置中断程序的字节长度
cld ; df位=0
rep movsb
; 设置中断向量表 ([7ch*4+2]):([7ch*4])
mov ax,0
mov es,ax
mov word ptr es:[7ch*4], 200h
mov word ptr es:[7ch*4 + 2], 0h
mov ax,data
mov ds,ax
mov si,0
mov ax,0b800h
mov es,ax
mov di,12*160 + 68 ; 需求没说是否放在第12行中间,这里放在中间
s: cmp byte ptr [si],0
je ok
mov al,[si]
mov es:[di],al
mov byte ptr es:[di+1],2h ; 绿色
inc si
add di,2
mov bx,offset s - offset ok
int 7ch
ok: mov ax,4c00h
int 21h
; 中断历程
jpstart: push bp
mov bp, sp
add [bp+2],bx
pop bp
iret ; pop ip | pop cs | popf
jpend: nop
code ends
end start
检测点13.2
判断下面说法的正误:
(1) 我们可以编程改变FFFF:0处的指令,使得CPU 不去执行 BIOS 中的硬件系统检测和初始化程序。
错误,此指令是固化在ROM中的
(2) int 19h 中断例程,可以由 DOS 提供
错误,int 19h是引导操作系统的,这时候还没有dos系统呢,无法提供int 19h
实验13 待完成?
第14章
检测点 14.1
(1) 编程,读取CMOS RAM的2号单元的内容
......
mov al,2
out 70h,al
in al,71h
......
(2) 编程,向CMOSRAM的2号单元写入0。
......
mov al,2
out 70h,al
mov al,0
out 71h,al
......
检测点 14.2
编程,用加法和移位指令计算(ax)=(ax) * 10.
提示,(ax) * 10 = (ax) * 2+(ax) * 8.
mov ax,1
mov bx,ax
shl ax,1
mov cl,3 ; 乘以8相当于左移3次
shl bx,cl
add ax,bx
实验14 访问CMOS RAM
编程,以“年/月/日 时:分:秒”的格式,显示当前的日期、时间。
注意:CMOS RAM 中存储着系统的配置信息,除了保存时间信息的单元外,不要向其他的单元中写入内容,否则将引起一些系统错误。
assume cs:code
data segment
db 9,8,7,4,2,0 ; CMOS中'年月日时分秒的储存单元编号'
db '// :: '
data ends
code segment
start: mov ax,data
mov ds,ax
; 将数值显示在屏幕中间
mov bx,0b800h
mov es,bx
mov si,0
mov di,0
mov cx,6
s: push cx ; cx入栈保留原值,避免和下面的cl冲突
mov al,ds:[si]
out 70h,al
in al,71h
; 用ah和al分别表示10进制的十位、个位
mov ah,al
and al,00001111b
mov cl,4
shr ah,cl
add al,30h
add ah,30h
mov byte ptr es:[160*12+64+di],ah
mov byte ptr es:[160*12+64+1+di],2h
mov byte ptr es:[160*12+64+2+di],al
mov byte ptr es:[160*12+64+3+di],2h
mov al,ds:[6+si]
mov byte ptr es:[160*12+64+4+di],al ; 间隔字符
mov byte ptr es:[160*12+64+5+di],2h
inc si
add di,6
pop cx ;
loop s
; 返回
mov ax,4c00h
int 21h
code ends
end start
第15章
编程1
; 在屏幕中间依次显示a~z,在按下ESC时改变颜色
; 思路:
; 1. 保存原有9号中断例程的入口地址
; 2. 设置9号新的入口地址
; 3. 依次显示a~z(间隔时间适当的长一些)
; 4. 在显示之间触发ESC中断,执行我们写的新的中断例程,用于改变a~z的颜色
; 5. 程序执行完返回之前,恢复原有9号中断例程的入口地址
assume cs:code
stack segment
db 128 dup(0)
stack ends
data segment
dw 0,0 ; 用于保存原有9号中断的入口地址
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
; 保存9号中断例程原地址
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2]
; 设置新的9号中断例程地址
cli ; IF=0,禁止其它的可屏蔽中断;
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs
sti ; IF=1, 允许可屏蔽中断
mov bx,0b800h
mov es,bx
mov cx,26
mov al,'a'
s: mov es:[12 * 160 + 80],al
call dalay
inc al
loop s
; 程序执行完之前,恢复原有的9号中断地址
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
; 返回
mov ax,4c00h
int 21h
; 新的9号中断例程
int9: push ax ; 保护上下文
push bx
push es
in al,60h ; 从60h端口获取键盘输入字符
; 引发原来的9号中断
pushf ; 此处需要两次pushf操作,第一次是引发中断时的正常操作(标志寄存器入栈),第二次是模拟中断时的需要,为了改变标志寄存器的值
pushf
pop bx
and bh, 11111100b
push bx
popf
call dword ptr ds:[0]
cmp al,1 ; ESC的通码是1
jne int9ret
; 改变颜色
mov ax,0b800h
mov es,ax
inc byte ptr es:[12 * 160 + 80 + 1]
int9ret:pop es
pop bx
pop ax
iret
; 延迟80 0000H次,根据各自机器的情况,设置不同的次数
dalay: push dx
push ax
mov dx,80h
mov ax,0
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop ax
pop dx
ret
code ends
end start
检测点15.1
(1) 精简代码
pushf
call dword ptr ds:[0]
(2) 优化代码
.......
; 设置新的9号中断例程地址
cli ; IF=0,禁止其它的可屏蔽中断;
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs
sti ; IF=1, 允许可屏蔽中断
.......
编程2 安装新的9号中断
; 安装9号中断例程,目的是在按下F1时改变全屏的颜色
; 思路:
; 1. 将原来的9号中断例程的入口地址放在0:200处
; 2. 将中断例程代码存放在0:204处
assume cs:code
stack segment
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp, 128
; 拷贝9号中断例程
; ds:[si] --> es:[di]
push cs
pop ds
mov si,offset int9
mov ax,0
mov es,ax
mov di,204h
mov cx, offset int9end-offset int9
cld
rep movsb
; 保存原有9号中断例程入口地址
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h]
; 设置9号中断例程新的入口地址
cli
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0
sti
; 返回
mov ax,4c00h
int 21h
int9: push ax
push es
push di
push cx
; 获取键盘输入
in al,60h
; 模拟触发原9号中断
pushf
call dword ptr cs:[200h] ; 执行中断时,此时(cs)=0
; f1的通码是3b
cmp al,3bh
jne int9ret
mov ax,0b800h
mov es,ax
mov di,0
mov cx,2000
s: inc byte ptr es:[di+1]
add di,2
loop s
int9ret: pop cx
pop di
pop es
pop ax
iret
int9end: nop
code ends
end start
实验15
; 安装一个新的int9中断例程,功能:在DOS下,按下“A”键后,除非不再松开,如果松开,就显示满屏幕的“A”,其他的键照常处理。
assume cs:code
stack segment
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp, 128
; 拷贝9号中断例程
; ds:[si] --> es:[di]
push cs
pop ds
mov si,offset int9
mov ax,0
mov es,ax
mov di,204h
mov cx, offset int9end-offset int9
cld
rep movsb
; 保存原有9号中断例程入口地址
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h]
; 设置9号中断例程新的入口地址
cli
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0
sti
; 返回
mov ax,4c00h
int 21h
int9: push ax
push es
push di
push cx
; 获取键盘输入
in al,60h
; 模拟触发原9号中断
pushf
call dword ptr cs:[200h] ; 执行中断时,此时(cs)=0
; A的通码是1E,断码是1E+80h
cmp al,1Eh + 80h
jne int9ret
mov ax,0b800h
mov es,ax
mov di,0
mov cx,2000
s: mov byte ptr es:[di], 'A'
add di,2
loop s
int9ret: pop cx
pop di
pop es
pop ax
iret
int9end: nop
code ends
end start
第16章 直接定址表
检测点16.1
下面的程序将code段中a处的8个数据累加,结果存储到b处的双字中,补全程序。
assume cs:code
code segment
a dw 1,2,3,4,5,6,7,8
b dd 0
start:mov si,0 ;使用si访问a段数据
mov cx,8 ;传输8次
s:mov ax,a[si] ;将a[si]中的数据取出放入ax中
add word ptr b[0],ax ;将ax加到b中
adc word ptr b[2],0 ;加上进位
add si,2 ;si+2访问下一个数据(字型数据)
loop s
mov ax,4c00h
int 21h
code ends
end start
检测点16.2
下面的程序将 data
段中 a
处的8
个数据累加,结果存储到b
处的字中,补全程序。
assume cs:code,es:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start:mov ax,data
mov es,ax
mov si,0
mov cx,8
s:mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
实验 16 编写包含多个功能子程序的中断例程
安装一个新的 int7ch 中断例程,为显示输出提供如下功能子程序。
(1) 清屏:
(2) 设置前景色;
(3) 设置背景色;
(4)向上滚动一行
入口参数说明如下。
(1) 用ah寄存器传递功能号:0表示清屏,1表示设置前景色,2表示设置背景色,3 表示向上滚动一行:
(2) 对于1、2号功能,用al传送颜色值,(al)∈{0,1,2,3,4,5,6,7}。
assume cs:code
stack segment
db 128 dup(0)
stack ends
code segment
; 1.拷贝中断例程代码到0:204处
; 2.将7ch号入口地址保存在0:200处
; 3.设置7ch入口地址为:0:204处
start:mov ax,stack
mov ss,ax
mov sp,128
; 拷贝7ch号中断例程
; ds:[si] --> es:[di]
push cs
pop ds
mov si,offset int7ch
mov ax,0
mov es,ax
mov di,204h
mov cx, offset int7chEnd-offset int7ch
cld
rep movsb
; 保存原有7ch号中断例程入口地址
push es:[7ch*4]
pop es:[200h]
push es:[7ch*4+2]
pop es:[202h]
; 设置7ch号中断例程新的入口地址
cli
mov word ptr es:[7ch*4],204h
mov word ptr es:[7ch*4+2],0
sti
; 中断测试
mov ah,1 ; 设置前景色
mov al,2h ; 绿色
int 7ch
; 返回
mov ax,4c00h
int 21h
org 204h
int7ch: jmp short set
table dw sub1,sub2,sub3,sub4
set: push bx
cmp ah,3
ja sret ; 如果大于3,退出
mov bl,ah
mov bh,0
shl bx,1 ; 由于偏移地址使用两个字节,所以功能号*2=子程序的偏移地址
call word ptr table[bx] ; 注意此处,table <==> cs:[2],但是在中断程序中,cs的段地址是0,程序安装在0:204处,所以需要加上204
sret: pop bx
iret
; (0) 清屏
sub1: push ax
push es
push cx
push di
mov ax,0b800h
mov es,ax
mov cx,2000
mov di,0
sub1s:mov byte ptr es:[di],' '
add di,2
loop sub1s
pop di
pop cx
pop es
pop ax
ret
; (1) 设置前景色
sub2: push bx
push es
push cx
mov bx,0b800h
mov es,bx
mov cx,2000
mov bx,1
sub2s:and byte ptr es:[bx],11111000b
or es:[bx],al
add bx,2
loop sub2s
pop cx
pop es
pop bx
ret
; (2) 设置背景色
sub3: push bx
push es
push cx
mov bx,0b800h
mov es,bx
mov cx,2000
mov bx,1
sub3s:and byte ptr es:[bx],10001111b
or es:[bx],al
add bx,2
loop sub3s
pop cx
pop es
pop bx
ret
; (3) 向上滚动一行
; 1. 将n+1行的内容复制到n行
; 2. 最后一行清空
; es:si --> ds:di
sub4: push ax
push es
push ds
push si
push di
push cx
mov ax,0b800h
mov es,ax
mov ds,ax
mov si,160
mov di,0
cld
mov cx,24
sub4s:push cx
mov cx,160 ; 总共复制24*160
rep movsb
pop cx
loop sub4s
mov cx,80
mov si,0
sub4s1: mov byte ptr es:[160 * 24 + si],' '
add si,2
loop sub4s1
pop cx
pop di
pop si
pop ds
pop es
pop ax
ret
int7chEnd:nop
code ends
end start
第17章 使用Bios进行键盘的输入和磁盘读写
检测点17.1
“在 int 16h 中断例程中,一定有设置 IF=1 的指令。”,这种说法是对的吗?
结论:正确
1.先说下IF的作用,IF的值用于确定 CPU 是否响应可屏蔽中断,IF=1 时响应中断,IF=0 时不响应中断。
2.当键盘缓冲区为空时,16h 中断例程循环等待,直到缓冲区中有数据。
此时,如果键盘输入数据,则会调用 9h 中断将键盘数据写入键盘缓冲区。此时,需设置 IF=1 响应可屏蔽中断 9h。所以,这种说法正确。
编程1 字符串输入
; 字符的入栈、出栈和显示
; (ah)=子程序号,0=入栈,1=出栈,2=显示
; (dh)=行号,(dl)=列号
; (ds:[si])=字符栈的空间
; int16h中断,(ah)=扫描码,(al)=ASCII码
; 退格键通码=0EH,Enter键的通码=1CH
assume cs:code
data segment
db 128 dup(0)
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov dh,24 ;行号
mov dl,2 ;列号
call getstr
; 返回
mov ax,4c00h
int 21h
getstr: push ax
getstrs:
mov ah,0
int 16h
cmp al,20h
jb nochar ; ASCII码小于20h,不是字符(控制功能键)
mov ah,0 ;字符入栈
call charstack
mov ah,2 ;字符显示
call charstack
jmp short getstrs ;循环等待用户输入
nochar:
cmp ah,0eh
je backspace ; 退格键
cmp ah,1ch
je enter ; enter键
jmp short getstrs
backspace:
mov ah,1 ; 字符出栈
call charstack
mov ah,2 ; 字符显示
call charstack
jmp short getstrs ; 循环等待用户输入
enter:
mov al,0 ; 输入enter,在字符串后加入0,表明字符串结束
mov ah,0 ; 入栈
call charstack
mov ah,2 ; 字符显示
call charstack
pop ax ;对应的是 call getstr
ret
charstack:
jmp short charstart
table dw charpush,charpop,charshow
top dw 0
charstart:
push ax ;上下文
push bx
push es
push di
push dx
cmp ah,2
ja sret
mov bl,ah
mov bh,0
shl bx,1
jmp word ptr table[bx]
;字符入栈
charpush:
mov bx,top
mov [si][bx],al
inc top
jmp sret
;字符出栈
charpop:
cmp top,0
je sret
dec top
mov bx,top
mov al,[si][bx]
jmp sret
;字符显示
charshow:
mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh ;(ax) = 24 * 160
mov di,ax
shl dl,1
mov dh,0
add di,dx
mov bx,0
charshows:
cmp bx,top
jne noempty
mov byte ptr es:[di], ' '
jmp sret
noempty:
mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],' ' ;显示字符右侧显示空格,用于输入
inc bx
add di,2
jmp charshows
sret:
pop dx
pop di
pop es
pop bx
pop ax
ret
code ends
end start
实验 读/写软盘
1、使用【软碟通】创建软盘映射文件
(1) 新建--软盘映像
(2) 无系统--确认
(3) 另存为--自定义名称.IMA
我这里是a.IMA
(4) 打开dosbox安装目录下的--DOSBox 0.74-3 Options.bat,进行挂载
(5) 重新打开dosbox,显示挂载成功
(6) 使用int13h读取软盘
; 读软盘到0:200
assume cs:code
code segment
start:
mov ax,0
mov es,ax
mov bx,200h
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov al,1 ;读取的扇区数,这里读取一个扇区(512字节)
mov ah,2 ;int13h中断例程的2号子程序
int 13h
mov ax,4c00h
int 21h
code ends
end start
可以看到已经将a.ima文件中的内容读取到0:200内存处了
注意:
虽然读取内容成功,但是使用debug 单步调试,会看到执行int 13h中断例程是不成功的,下入也是一样的结果
不影响使用,至于原因,后续有时间再研究。
(7) 使用int13h写入软盘
; p310.asm(名称自定义)
; 将0:200内存数据写入到软盘
assume cs:code
code segment
start:
mov ax,0
mov es,ax
mov bx,200h
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov al,1
mov ah,3 ;int13h中断例程的3号子程序
int 13h
mov ax,4c00h
int 21h
code ends
end start
执行完写入程序后,需要将dosbox关掉,就会看到写入的内容
实验 将当前屏幕上的内容保存在磁盘(软盘)上
; 将当前屏幕上的内容保存在磁盘上
assume cs:code
code segment
start:
mov ax,0
mov es,ax
mov bx,200h
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov al,8 ;写入扇区数
mov ah,3 ;int13h中断例程的3号子程序
int 13h
mov ax,4c00h
int 21h
code ends
end start
执行完,将dosbox关掉就可以看到a.IMA中的数据已经更新了
实验17 安装新的 int 7ch 中断例程,实现通过逻辑扇区号对软盘进行读写
assume cs:code
code segment
start:
; 拷贝新的中断例程到0:200处
; (ds:[si])-->(es:[di])
mov ax, cs
mov ds, ax
mov si, offset int7ch ; 设置 ds:si 指向源地址
mov ax, 0
mov es, ax
mov di, 200h ; 设置 es:di 指向目的地址
mov cx, offset int7chend - offset int7ch ; 设置 cx 为传输长度
cld ; 设置传输方向为正
rep movsb
; 设置中断向量表
mov ax, 0
mov es, ax
cli
mov word ptr es:[7ch * 4], 200h
mov word ptr es:[7ch * 4 + 2], 0
sti
; 测试
mov cx,0
mov es,cx
mov bx,600h ; 此处存放到0:600h处,本来想存储到027a处,但是调试后程序运行不了
; 大家也可以找一块能用的内存地址存储也行
mov ah,0 ; 读
mov dh,0
mov dl,0 ; 逻辑区号0 = 0面0道1扇区
int 7ch
mov ax, 4c00h
int 21h
; 新的 int 7ch 中断例程,实现通过逻辑扇区号对软盘进行读写
;
; 逻辑扇区号=(面号*80 + 磁道号)*18 + 扇区号 - 1
; int():描述性运算符,取商
; rem():描述性运算符,取余数
; 面号=int(逻辑扇区号/1440)
; 磁道号=int(rem(逻辑扇区号/1440)/18)
; 扇区号=rem(rem(逻辑扇区号/1440)/18)+1
;
; 参数说明:
; (1) 用 ah 寄存器传递功能号:0 表示读,1 表示写;
; (2) 用 dx 寄存器传递要读写的扇区的逻辑扇区号;
; (3) 用 es:bx 指向存储读出数据或写入数据的内存区。
int7ch:
cmp ah, 1
ja int7chret ; 当 ah 传递的功能号大于 1 则转移
push di
push cx
push dx
push si
mov di,ax ; 子程序编号
; 求面号
mov ax,dx
mov dx,0
mov cx,1440
div cx ; ax=商(面号),dx=余数
mov si,ax
; 求磁道号
mov ax,dx
mov cl,18
div cl ; al=商(磁道号),ah=余数
mov ch,al ; 磁道号
; 求扇区号
mov cl,ah
inc cl ; 扇区号
mov dx,si
mov dh,dl ; 磁头号(面号)
mov dl,0 ; 驱动器号
mov ax,di
mov al,1 ; 读写的扇区数
add ah,2 ; 0+2=2(调用【读】子程序) 1+2=3(调用【写】子程序)
int 13h ; 调用 int 13h 来对磁盘进行读写
int7chret:
pop si
pop dx
pop cx
pop di
iret
int7chend:
nop
code ends
end start
0:600 可以看到,数据已经读进来了
课程设计 2
assume cs:code
code segment
start:
;***************************************************************
; 安装程序
; 功能:
; - 将引导扇区写入软盘0面0道1扇区中
; - 将主程序写入到软盘0面0道2-33扇区中
; -
; 使用dosbox中的debug工具,查看代码大概有个32kb的大小,所以按理说
; 应该用65个扇区存放主程序,但是用8个、32个、64个扇区都可以正常启动
; 没有想明白?
;***************************************************************
mov bx,cs
mov es,bx
;引导程序
mov bx,offset guide
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov al,1 ;扇区数
mov ah,3 ;写操作(int13h中断例程的3号子程序)
int 13h
;主程序
mov bx,offset main
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)
mov ch,0 ;磁道号
mov cl,2 ;扇区号
mov al,32 ;扇区数
mov ah,3 ;写操作(int13h中断例程的3号子程序)
int 13h
mov ax,4c00h
int 21h
;***************************************************************
; 引导程序
;
; 功能:
; - 给栈留200h(512)字节的空间
; - 将主程序加载到7c00+200h(512)=7f00h(0:7f00h)处
; - 上面的512字节正好一个扇区
; 补充:
; - BIOS会将此段程序从软盘中加载到0:7c00h处,并将CS:IP->0:7C00H
;***************************************************************
guide:
mov bx,0
mov ss,bx
mov sp,7f00h
mov es,bx
mov bx,7f00h
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)
mov ch,0 ;磁道号
mov cl,2 ;扇区号
mov al,32 ;扇区数量
mov ah,2 ;读操作(int13h中断例程的2号子程序)
int 13h
; 跳转到0:7f00h
sub bx,bx
push bx
mov bx,7f00h
push bx
retf
;***************************************************************
; 主程序
; 将被引导程序加载到 0:7f00h处
; 补充:
; (1) 隐藏光标
; - int 10h 的 1h 功能描述:设置光标的形状。
; - 通过将光标的起始和结束扫描线设置为相同的值(例如 3Fh,即63),可以使得光标不可见。
; - AH = 01h (功能号)
; - CH = 光标的起始扫描线(设置为 3Fh 以隐藏光标)
; - CL = 光标的结束扫描线(设置为 3Fh 以隐藏光标)
;***************************************************************
org 7f00h
main:
; 隐藏光标
mov cx,03f3fh
mov ah,1
int 10h
mov ax,cs
mov ds,ax
mov ax,0b800h
mov es,ax
jmp short menu_list
;***************************************************************
; (0)列出功能选项
; - 1) reset pc
; - 2) start system
; - 3) clock
; - 4) set clock
;***************************************************************
menu1 db '1) reset pc'
menu2 db '2) start system'
menu3 db '3) clock'
menu4 db '4) set clock'
menu5 db 'Please enter 1-4 below:'
menu_addr dw menu1,menu2,menu3,menu4,menu5
menu_len dw menu2-menu1,menu3-menu2,menu4-menu3,menu5-menu4,menu_addr-menu5
table dw reset_pc,guid_system,show_clock,set_clock
menu_list:
call cls
mov dx,160*10+58
mov bx,0
mov cx,5
_lp_menu:
push cx
mov si,menu_addr[bx]
mov di,dx
mov cx,menu_len[bx]
_lp_cp:
mov al,[si]
mov es:[di],al
inc si
add di,2
loop _lp_cp
add bx,2
add dx,160
pop cx
loop _lp_menu
mov dh,15
mov dl,29
mov bh,'>'
mov bl,0
call char_show
mov dl,30
mov bh,'_'
mov bl,80h
call char_show
; 键盘输入
mov dh,15
mov dl,30
call keyword_input
;cmp al,'1'
;jb short menu_list
;cmp al,'4'
;ja short menu_list
sub bh,bh
; 为什么减去31H呢? bl现在是上一步用户输入的功能编号[1,2,3,4]
; 比如用户输入的3,3的ascii码是33h,那么这里需要让33h转成数字3,那么就需要减去30h(48)
; 还需要减去1,因为这里需要获取子程序的地址所在的字单元地址
; 子程序编号: 1 2 3 4
; 子程序地址所在字单元地址: 0 2 4 6
; (字单元地址) = ((编号)-1) * 2
sub bl,31h
shl bl,1
call cls
call word ptr table[bx]
jmp short menu_list
;***************************************************************
; (1)重新启动计算机
; 操作流程:
; - 设置(cs:ip)=FFFF:0
;***************************************************************
reset_pc:
mov ax,0ffffh
push ax
sub ax,ax
push ax
retf
;***************************************************************
; (2)引导操作系统
; 操作流程:
; - 将0面0磁道1扇区512字节内容拷贝到0:7c00处
; - 设置(cs:ip)=0:7c00
;***************************************************************
guid_system:
sub bx,bx
mov es,bx
mov bx,7c00h
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov al,1 ;读取扇区数量
mov ah,2 ;int13h中断例程的2号子程序(读操作)
int 13h
sub bx,bx
push bx
mov bx,7c00h
push bx
retf
;***************************************************************
; (3)进入时钟程序
; 备注:
; - CMOS的端口:70h和71h,70h是地址端口,71h为数据端口
; - 9,8,7,4,2,0 CMOS中'年月日时分秒的储存单元编号'
; 操作流程:
; - 分别使用端口70h和71h读取处时间数据
; - 用ah和al分别表示10进制的十位、个位
; - 时间之间分别加上分隔符
; - 将字符显示在屏幕中间
;***************************************************************
cmos_unit db 9,8,7,4,2,0 ; CMOS中'年月日时分秒的储存单元编号'
cmos_delimiter db '// :: '
cmos_desc db 'ESC: Main menu F1: Change color'
show_clock:
call cmos_manaul
mov dl,00000111b
clock_init:
mov bx,0
mov di,bx
mov cx,6
jmp get_cmos_date
change_color:
mov bx,0
mov di,bx
inc dl
mov cx,6
get_cmos_date:
;push cx ;上下文
mov al,cmos_unit[bx]
out 70h,al
in al,71h
; 用ah和al分别表示10进制的十位、个位
;
mov ah,al
and al,00001111b
shr ah,1
shr ah,1
shr ah,1
shr ah,1
add al,30h
add ah,30h
mov byte ptr es:[160*12+64+di],ah
mov byte ptr es:[160*12+64+1+di],dl
mov byte ptr es:[160*12+64+2+di],al
mov byte ptr es:[160*12+64+3+di],dl
mov al,cmos_delimiter[bx]
mov byte ptr es:[160*12+64+4+di],al ; 间隔字符
mov byte ptr es:[160*12+64+5+di],dl
inc bx
add di,6
;pop cx
loop get_cmos_date
mov ah,1
int 16h
je short continue_clock
sub ah,ah
int 16h
cmp ah,1 ; ESC
je short clock_ret
cmp ah,3bh ; F1
je short change_color
; 循环读取时间
continue_clock:
loop clock_init
cmos_manaul:
mov cx,34
mov bx,0
mov di,0
_lp_desc:
mov al,cmos_desc[bx]
mov es:[160*14+46+di],al
inc bx
add di,2
loop _lp_desc
ret
clock_ret:
ret
;***************************************************************
; (4)设置时间
; 备注:
; - CMOS的端口:70h和71h,70h是地址端口,71h为数据端口
; - 9,8,7,4,2,0 CMOS中'年月日时分秒的储存单元编号'
; - 时间数据是以BCD码方式存放
; 操作流程:
; - 分别使用端口70h和71h读取处时间数据
; - 用ah和al分别表示10进制的十位、个位
; - 时间之间分别加上分隔符
; 额外补充:
; BCD码
; > 是以4位二进制数表示十进制数码的编码方法,如下所示
; > 十进制数码: 0 1 2 3 4 5 6 7 8 9
; > 对应的BCD码: 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001
; > 比如,数值26,用BCD码表示为:00100110
; 通码:
; -> : 4Dh
; <- : 4Bh
; Enter : 1h
;
; 在dosbox环境不能修改cmos ram中的数据,需要在物理机器上才可以
;***************************************************************
set_clock_desc db 'ESC: Main menu ->: Move Right <-: Move Left Enter: Confirm modify'
set_clock_top db 0,0
set_clock_data db 18 dup(0) ; 用于存储修改的时间数据
set_clock_cursor_index db 0,1,3,4,6,7,9,10,12,13,15,16 ;光标的下标
set_clock:
call set_clock_manaul
set_clock_init:
mov bx,0
mov di,0
mov cx,6
_lp_set:
mov byte ptr es:[160*12+64+di],'_'
mov byte ptr es:[160*12+64+2+di],'_'
mov al,cmos_delimiter[bx]
mov es:[160*12+64+4+di],al ; 间隔字符
inc bx
add di,6
loop _lp_set
; 光标
cursor:
mov dh,12
; mov dl,32
mov dl,set_clock_top[1]
add dl,32
; mov bh,'_'
; mov bl,80h
mov bl,11100101b
call char_style
listen:
mov ah,1
int 16h
sub ah,ah
int 16h
cmp ah,1 ; ESC
je short set_clock_ret
cmp ah,4bh ; <-
je move_left
cmp ah,4dh ; ->
je move_right
cmp ah,1ch ; Enter
je clock_enter
cmp al,30h
jb listen
cmp al,39h
ja listen
mov bl,set_clock_top[1]
sub bh,bh
mov set_clock_data[bx],al
mov dh,12
mov dl,bl
add dl,32
mov bh,al
mov bl,00000111b
call clear_cursor
call char_show
jmp move_right
set_clock_ret:
ret
move_right:
mov bl,set_clock_top[0]
cmp bl,11
je jmp_inc
inc bl
mov set_clock_top[0],bl
jmp_inc:
sub bh,bh
mov bl,set_clock_cursor_index[bx]
mov set_clock_top[1],bl
call clear_cursor
jmp cursor
move_left:
mov bl,set_clock_top[0]
cmp bl,0
je jmp_dec
dec bl
mov set_clock_top[0],bl
jmp_dec:
sub bh,bh
mov bl,set_clock_cursor_index[bx]
mov set_clock_top[1],bl
call clear_cursor
jmp cursor
clock_enter:
push di
mov cx,6
mov bx,0
mov di,0
_lp_enter:
mov al,set_clock_data[bx]
sub al,30h
mov ah,al
shl ah,1
shl ah,1
shl ah,1
shl ah,1
mov al,set_clock_data[bx+1]
sub al,30h
or ah,al
mov al,cmos_unit[di]
out 70h,al
mov al,00010010b
out 71h,al
add bx,3
inc di
loop _lp_enter
pop di
jmp set_clock_ret
clear_cursor:
mov cx,17
mov di,1
_lp_cc:
mov byte ptr es:[160*12+64+di],00000111b
add di,2
loop _lp_cc
ret
set_clock_manaul:
mov cx,73
mov bx,0
mov di,0
_lp_set_desc:
mov al,set_clock_desc[bx]
mov es:[160*14+8+di],al
inc bx
add di,2
loop _lp_set_desc
ret
;***************************************************************
; 键盘输入:字符的入栈、出栈和显示
; (ah)=子程序号,0=入栈,1=出栈,2=显示
; (dh)=行号,(dl)=列号
; (ds:[si])=字符栈的空间
; int16h中断,(ah)=扫描码,(al)=ASCII码
; 退格键通码=0EH,Enter键的通码=1CH
; ASCII码小于20h,不是字符(控制功能键)
;***************************************************************
keyword_input:
push ax
jmp short getstrs
charqueue db 8 dup(0)
top dw 0
getstrs:
sub ah,ah
int 16h
cmp al,20h
jb nochar
mov ah,0
call charstack
mov ah,2
call charstack
jmp short getstrs
nochar:
cmp ah,0eh
je backspace ; 退格键
cmp ah,1ch
je enter ; enter键
jmp short getstrs
backspace:
mov ah,1 ; 字符出栈
call charstack
mov ah,2 ; 字符显示
call charstack
jmp short getstrs ; 循环等待用户输入
enter:
mov bx,top
dec bx
mov bl,charqueue[bx]
cmp top,0
je jmp_ret
dec top
jmp_ret:
pop ax
ret
charstack:
jmp short charstart
chartable dw charpush,charpop,charshow
charstart:
push ax
push bx
push di
push dx
cmp ah,2
ja sret
mov bl,ah
mov bh,0
shl bx,1
jmp word ptr chartable[bx]
;字符入栈
;最多输入1个字符
charpush:
cmp top,1
je sret
mov bx,top
mov charqueue[bx],al
inc top
jmp sret
;字符出栈
charpop:
cmp top,0
je sret
dec top
jmp sret
;字符显示
charshow:
mov al,160
mov ah,0
mul dh ;(ax) = 24 * 160
mov di,ax
shl dl,1
mov dh,0
add di,dx
mov bx,0
charshows:
cmp bx,top
jne noempty
mov byte ptr es:[di], '_'
or byte ptr es:[di+1],10000000b
mov byte ptr es:[di+2],' '
jmp sret
noempty:
mov al,charqueue[bx]
mov es:[di],al
and byte ptr es:[di+1],01111111b
inc bx
add di,2
jmp charshows
sret:
pop dx
pop di
pop bx
pop ax
ret
;***************************************************************
; 屏幕显示字符
; 参数说明:
; - (dh)=行 [0-24]
; - (dl)=列 [0-79]
mov dh,12; - (bl)=样式
; - (bh)=32cii码
; ds:[si] -> es:[di]
;***************************************************************
char_show:
mov al,160
mul dh
mov di,ax
mov al,2
mul dl
add di,ax
mov es:[di],bh
or byte ptr es:[di+1],bl
ret
char_style:
mov al,160
mul dh
mov di,ax
mov al,2
mul dl
add di,ax
or byte ptr es:[di+1],bl
ret
;***************************************************************
; 清屏
;***************************************************************
cls:
mov cx,2000
sub di,di
cls_s:
mov byte ptr es:[di],' '
mov byte ptr es:[di+1],00000111b ;黑底白字
add di,2
loop cls_s
ret
code ends
end start
使用vmware启动这个小系统-winteros
(1) 新建虚拟机 window xp(win10也可以)(vm用的版本是17,其它版本应该也可以)
然后,就下一步下一步....
(2) 添加软盘驱动
打开虚拟机设置---添加软盘驱动器---完成
现在没有软盘映像文件,所以先点击【创建】
(3) 将创建a.flp作为软盘,使用dosbox进行挂载(挂载路径自定义)
(4) 运行上面的代码,将引导程序和主程序分别写入1扇区和2~66扇区(执行后,将dosbox关掉,避免因为进程占用,导致文件内容不刷新,写入不成功)
(5) 写入成功后,在刚才vmware创建软盘映像文件的地方,选择写入成功后的文件,然后确定
(6) 启动虚拟机