一、虚拟环境架设
1、安装虚拟机VM6.03(vmware)
2、创建虚拟机,客户机操作系统 类型“其它”MS-DOS,用光盘镜像安装DOS622。
下载地址 http://205.196.120.225/1ycwwydjjw1g/00007135820/filefront/dos6.22.iso.rar
3、使用 WinImage 工具,创建一张1.44M的软盘IMG文件,并加载到虚拟机的软驱中,做为实验软盘。
二、编写代码及测试
注意环节
1、代码长度与磁盘扇区要相匹配,代码如果长度超过了512K,磁盘上就要用2个扇区来存放。
2、7C00H内存空间的问题,由于程序功能2,需要读入C盘1扇区,重写7C00H,所以我们的主程序一定要避开7C00H的使用,在其中只放入引导程序,将主程序写入其它的内存空间,并设置CS:IP到主程序处。
3、由于需要多次安装程序,一次为将代码写入磁盘(安装主程序到磁盘),一次为从磁盘将程序读入内存(从磁盘引导系统),因此如需要在程序中使用直接定址表,则一定要手工指定具体地址(编译器编译时只根据CODE段来计算数据标号,但安装在磁盘上的程序并不是从CODE初始位置开始的,因此需要手工计算他的位置),或者尽量不使用它,以免引起不必要的麻烦(DOS下的调试真的是太麻烦了,出错也不容易查出)。
4、注意栈平衡,注意CALL中使用到的寄存器数据的PUSH和POP,CALL中重写了数据使用程序出错,不易查出。
5、最重要的就是多看教材,实验中所用到的内容都在书中(废话HEHE)。王老师的教材逻辑性很强,是站在学习者角度出发循序渐进编写的(非常佩服),因此要比其它的教材容易入手。我是先看了一遍,只做课中检测点,意在了解书的大致内容,第二遍看书认真把检测点做过的题再分析下,查出了不少错误(有些错误在看了后边章节内容后会有更多的理解),并认真将书中所出现代码一一在虚拟机中调试实验,由其要做好每一个实验程序(其实每个实验王老师都把思路写的非常清晰了,我们需要的只是具体实施)。这些实验全做完后,我觉得我的汇编是入了门了,谢谢王老师。
三、具体代码,简单做了注释(请多指正)
assume cs:code,ss:stack,ds:datas
stack segment
db 128 dup(0)
stack ends
datas segment
mess1 db 'Disk I/O Error!!!!'
mess2 db 'Disk Write OK !!!!'
datas ends
code segment
start:
;==============================安装引导程序==============================
mov ax,stack
mov ss,ax
mov sp,128
push cs
pop ds
push cs
pop es
;往 A: 0面0道1扇处写入程序,共4个扇区7C00=主引导区 7E00=主程序区1 8000=主程序区2 8200=数据区
mov bx,offset RUN
mov ah,3
mov al,4
mov ch,0
mov cl,1
mov dh,0
mov dl,0
int 13h
;对写盘操作后反馈的信息进行处理
push ax ;清屏
mov ax,datas
mov ds,ax
mov si,0
mov ax,0b800h
mov es,ax
mov di,0
mov cx,2000
stas: mov byte ptr es:[di],0
add di,2
loop stas
pop ax
cmp ah,0 ;比较int 13h返回情况,提示是否写盘成功
je stend
mov di,160*12+30*2
mov cx,18
stars:mov al,byte ptr mess1[si]
mov byte ptr es:[di],al
inc si
add di,2
loop stars
jmp staend
stend:
mov di,160*12+30*2
mov cx,18
starss:mov al,byte ptr mess2[si]
mov byte ptr es:[di],al
inc si
add di,2
loop starss
staend: ;主程序安装磁盘完成后设置 CS:IP 到 FFFF:0000,重启计算机
mov ax,0ffffh
mov bx,0
push ax
push bx
retf
mov ax,4c00h
int 21h
;============================磁盘中写入的内容==============================
;============================0面0道1扇区 7C00H =============================
RUN: mov ax,0 ;将主程序1区2区 及数据区读入内存
mov es,ax
mov bx,7e00h
mov ah,2
mov al,3
mov ch,0
mov cl,2
mov dh,0
mov dl,0
int 13h
mov ax,0 ;CS
mov bx,7e00h ;IP
push ax
push bx
retf
db 510-($-RUN) dup(0) ;这两句将代码 引导部分 长度扩充为 512 字节,直接占满 0面0道1扇区
dw 0aa55h
;以上共计512字节,最后以 AA55封闭1扇区
;============================0面0道2--3扇区,自助系统位置 7E00H 8000H=============================
MENU: call clear
mov ax,0b800h
mov es,ax
mov di,160*3+20*2
mov ax,cs
mov ds,ax
mov bx,0
mov cx,10
men2: push cx
push di
mov si,0
mov cx,39
men1: mov al,byte ptr ds:[8200h+bx+si] ;显示 处于8200H处的主菜单
mov byte ptr es:[di],al
add di,2
inc si
loop men1
pop di
pop cx
add di,160
add bx,39
loop men2
GOGO: call input ;功能选择部分,利用 16H 中断,读取键盘缓冲区,根据字符确定 功能
mov ah,0
int 16h
cmp al,'1'
je GO1
cmp al,'2'
je GO2
cmp al,'3'
je GO3
cmp al,'4'
je gog4 ;这里JE 的转移是不能直接转移到GO4的,因为长度超过范围了,因此做了个 gog4:jmp GO4
jmp GOGO
gog4:jmp GO4
GO1: mov ax,0ffffh ;这里使用ret的 pop ip pop cs 功能改变 CS:IP指向需要位置
mov bx,0
push ax
push bx
retf
GO2: call clear
mov ax,0
mov es,ax
mov bx,7c00h
mov ah,2 ;这里将读取C盘1扇区内容到7C00H,如果主程序在这个位置,则全部被改写,不能正确运行,因此我们应该把主程序放在第1扇区之后.
mov al,1
mov ch,0
mov cl,1
mov dh,0
mov dl,80h
int 13h
mov ah,2 ;置光标在 25行 8列 (一直没有解决好光标随字符串移动,有兴趣者可继续研究下)
mov bh,0
mov bl,0
mov dh,0
mov dl,0
int 10h
mov ax,0
mov bx,7c00h
push ax
push bx
retf
GO3:
mov ax,0b800h ;从15行起划个框框美化一下
mov es,ax
mov bx,39
mov di,160*15+20*2
mov cx,2
go3s4: push cx
push di
mov si,0
mov cx,39
go3s2: mov al,byte ptr ds:[8200h+bx+si]
mov byte ptr es:[di],al
add di,2
inc si
loop go3s2
pop di
add di,320
pop cx
loop go3s4
mov bx,83C2H ;BX为 读取COMS的地址 直接定址表,一定要手工指向目标内存
mov di,160*16+31*2
mov cx,6
go3s1: push cx
mov al,byte ptr ds:[bx] ;读取COMS中的时间日期,使用 70H 端口
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 byte ptr es:[di],ah
add di,2
mov byte ptr es:[di],al
add di,2
pop cx
cmp cx,4
ja go31
cmp cx,1
je go33
cmp cx,4
jb go32
go33:mov byte ptr es:[di],0 ;写入美化字符
jmp goend
go31:mov byte ptr es:[di],'/'
jmp goend
go32:mov byte ptr es:[di],':'
jmp goend
goend:add di,2
inc bx
loop go3s1
in al,60h
cmp al,03Bh ;F1改变颜色,这里直接用60H端口读取键扫描码,根据扫描码处理无需改写 INT 9中断即可达到目的,但按次数多了,会报警键盘缓冲区满
jne go3esc
push ax
mov di,160*16+1
mov cx,80
go3col:mov al,byte ptr es:[di]
inc al
and al,00001111B
mov byte ptr es:[di],al
add di,2
loop go3col
pop ax
go3esc:cmp al,81h ;ESC跳出时间日期显示 AX 由上边 60H 端中获取
jne gogo3
jmp MENU
gogo3:mov ax,0
jmp GO3
GO4:
mov di,160*23 ;输入格式提示字符 'INPUT: YY/MM/DD HH:MM:SS'
mov si,838Dh
mov cx,24
go4s: mov al,byte ptr ds:[si]
mov byte ptr es:[di],al
inc si
add di,2
loop go4s
mov bp,0 ;BP 内存变量,存着字符串的长度 所有寄存器都有用了,只有它闲着 这里使用书上代码,定址表会出错,手动指定变量和目标位置即可
mov si,83E0H ;DS:SI 指向字符串存放的内存位置
call GETSTR ;GETSTR 一定要与上面两句配合使用,上边给了他 参数
jmp MENU
clear: push es ;清屏命令
mov ax,0b800h
mov es,ax
mov di,0
mov cx,2000
cles:mov byte ptr es:[di] ,0
mov byte ptr es:[di+1],00000111B
add di,2
loop cles
pop es
ret
input: push es ;用于设置菜单界面的输入提示及光标位置
push cx
push ax
mov ax,0b800h
mov es,ax
mov di,160*24
mov si,8386h
mov cx,7
inps: mov al,byte ptr ds:[si]
mov byte ptr es:[di],al
inc si
add di,2
loop inps
mov ah,2 ;置光标在 25行 8列
mov bh,0
mov bl,0
mov dh,24
mov dl,7
int 10h
pop ax
pop cx
pop es
ret
CHARSK: push bx ;字符串处理 注意定址表的使用方式
push dx
push di
push es
cmp ah,3
ja cret
mov bl,ah
mov bh,0
add bx,bx
jmp word ptr ds:[83C8H+bx] ;跳到功能 0 1 2 3功能入口 0=入栈 1=出栈 2=显示 3=清除
;mov bp,83E0H
pushc:mov bx,bp ;TOP 中存着当前 栈的指针位置 功能0
mov [si][bx],al
inc bp
jmp cret
popc: cmp bp,0 ;TOP =0 则说明栈空了 功能1
je cret
dec bp
mov bx,bp
mov al,[si][bx]
jmp cret
charsh:;mov bx,0b800h ;功能2
;mov es,bx
mov al,160
mov ah,0
mul dh
add dl,dl
mov dh,0
add ax,dx
mov di,ax ;DI=AH*160+AL+AL 就是文字显示的位置
mov bx,0
chars:cmp bx,bp
jne noemp
mov byte ptr es:[di],20h ;如果 栈为空,则显示一个空格
jmp cret
noemp:mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],20h ;显示当前字符后自动将光标输入位置后移一个字符,这个字符用空格表示
inc bx
add di,2
jmp chars
charcl:;mov bx,0b800h ;功能3
;mov es,bx
mov al,160
mov ah,0
mul dh
add dl,dl
mov dh,0
add ax,dx
mov di,ax
mov cx,80
chcls:mov byte ptr es:[di],0
mov byte ptr es:[di+1],00000111B
add di,2
loop chcls
cret:
pop es
pop di
pop dx
pop bx
ret
GETSTR: push ax
getstrs:mov ah,0
int 16h
cmp al,20h ;小于20H说明不是字符
jb nocahr
mov ah,0
call CHARSK ;入栈
mov ah,2
mov dh,24
mov dl,7
call CHARSK ;显示字符在 第25行 第8列
jmp getstrs
nocahr:cmp ah,0eh ;BACKSPACE键
je back
cmp ah,1ch ;ENTER
je enter
jmp getstrs
back: mov ah,1
call CHARSK
mov ah,2
mov dh,24
mov dl,7
call CHARSK
jmp getstrs
enter:mov ah,0
mov al,0 ;输入回车,在字符串尾压入一个0
call CHARSK
call writes ;写COMS
mov ah,3 ;3功能为清屏
mov dh,24
mov dl,7
call CHARSK
gnret:pop ax
ret
writes: push si
push di
mov di,83C2H
mov si,83E0H
mov ax,bp ;把输入的参数写到CMOS中
mov cl,3 ;'YY/MM/DD HH:MM:SS',0 输入字符串+回车补0,共计18位,每3位为一组做为处理,BP为长度18 / 3,为处理6次
div cl
mov cl,al
mov ch,0
jcxz wriend ;如果输入字符串长度低于3,则直接跳走
write: push cx
mov al,[si]
sub al,30h
mov cl,4
shl al,cl
mov ah,[si+1]
sub ah,30h
add ah,al ;处理SI与SI+1,存放在一个字节内 AH中 写入CMOS
mov al,[di] ;DI为CMOS内存地址 定址表
out 70h,al
mov al,ah
out 71h,al
add si,3
inc di
pop cx
loop write
wriend:pop di
pop si
ret
db 1024-($-MENU) dup(0) ;这句将代码 引导部分 长度扩充为 1024 字节,直接占满 0面0道2-3扇区,为准确定位 4扇区的DATA部分奠定基础
;以上共计1024字节
;============================0面0道2扇区,自助系统数据位置8200H=============================
;8200H 为菜单显示区
mnu1 db '********** MASA for GengLei **********',0
mnu2 db '**************************************',0
mnu3 db '***** *****',0
mnu4 db '***** 1 -> Reset PC *****',0
mnu5 db '***** 2 -> Start System *****',0
mnu6 db '***** 3 -> Clock *****',0
mnu7 db '***** 4 -> Set clock *****',0
mnu8 db '***** *****',0
mnu9 db '**************************************',0
mnu0 db '**************************************',0
;标志 8386H
baozi db 'GENG:\>'
mesg db 'INPUT: YY/MM/DD HH:MM:SS'
;83C1H 前封闭为显示数据区 8200H+450
data db 450-($-mnu1) dup(0)
;8200H+450 共计 6 字节 存放 年 月 日 时 分 秒 83C2H
days db 9,8,7,4,2,0
;83C8H 起为功能入口
char dw offset pushc-offset MENU+7E00H,offset popc-offset MENU+7E00H,offset charsh-offset MENU+7E00H,offset charcl-offset MENU+7E00H
db 480-($-char) dup(0)
;83E0H 起为字符
top db 0
code ends
end start
个性签名:一个人在年轻的时候浪费自己的才华与天赋是一件非常可惜的事情
如果觉得这篇文章对你有小小的帮助的话,记得在右下角“点赞”~“评论” “收藏”哦,博主在此感谢!
万水千山总是情,打赏5毛买辣条行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!