目录
前言
1.问题背景
(1)学习王爽老师《汇编语言》(第四版) 第十七章时,需要编程来读写磁盘。因为书中讲解的软盘如今已很少被使用,如果买软盘及软驱,我不确定是否真的能运行,且学完这节课后就基本无用,实属资源浪费。起初我打算使用同样属于磁盘的U盘来作为替代。这些程序要使用BIOS的13H号中断例程,但是它们在我使用的(64位win10系统)DosBox(虚拟dos环境的软件)中无法正确运行。
(2)我又将读写U盘改为读写笔记本里的固态硬盘,仍然无法读取,返回值一直是FF。
(3)后来我尝试了使用01F0H-0HF7H硬盘端口读取硬盘数据,同样无法运行。
(4)再后来,尝试了在虚拟机中运行dos系统,然后在实模式下运行读写磁盘的程序,结果是仍然不行。
(5)最终,我无功而返。唯一能做的就是将这耗费整整五天得来的探索经验总结下来,后人可以少走弯路。
2.小结
这几天的探索经验总结如下。
(1)无论是在64位win10系统下的DosBox中,还是在dos系统虚拟机中运行,都无法通过 01f0H-01f7H端口 读写硬盘(包括U盘)的数据。
(2)无论是在64位win10系统下的DosBox中,还是在dos系统虚拟机中运行,都可以通过BIOS的13H中断例程 读取磁盘的数据,但不能写入数据(原因不明)。
(3)学会了如何在64位win10系统下,使用虚拟机运行dos系统,并运行汇编程序。(接下来详细介绍)
在虚拟机软件与DOS系统版本的选择方面,我认为比较好的方案是:
①.使用VMware公司的虚拟机软件,因为它提供了官方的免费正版可供使用,比较稳定可靠;
②.使用FreeDos这个开源dos操作系统,因为它开源且与微软的MS-DOS完全兼容;
3.本文概述
本文按照操作顺序,详细介绍:
1.如何下载安装VMware虚拟机软件;
2.如何下载DOS操作系统的安装包;
3.如何创建虚拟机并安装Dos操作系统;
4.如何在dos虚拟机中运行汇编程序。
一、下载、安装VMware虚拟机软件
1.下载
(1)访问官网
打开 VMware官网 后界面如图。
(2)找到产品搜索框
点击顶部菜单栏中的“应用 平台”,之后将右侧的滚动条(注意!是橙色圈出来的那个!)下拉到底部。效果如图。
然后点击图中的“所有产品”。页面跳转后,将页面下拉,会在左边看到搜索框,如图。
(3)搜索workstation软件
在搜索框中搜索“workstation”,回车,如图。
(4)下载workstation player
① 选择“workstation player”产品
点击“workstation player”,页面跳转后,点击download。(player是免费版,Pro是收费版)
②选择版本号
如下页面,点击“下载”。
③ 选择对应操作系统的版本
点击下载,如图。(我使用的是64位windows系统)
文件大小584MB,下载速度Ok,几分钟之内下载完成。
2.安装VMware虚拟机
双击运行安装程序,一路“下一步”。
在这一步可以选择安装位置,如图。
这里的“增强型键盘驱动程序”,我查了一下,似乎是这样的:它的作用是为了增强安全性,但普通使用可以不安装,以后需要用了可以随时安装。于是我决定不勾选。
之后正常安装,需要等待几分钟,即可安装完成。桌面图标如下。
二、下载dos操作系统的安装文件
1.关于dos
虚拟机也像真实的物理机一样,需要操作系统。我想要在虚拟机上运行dos系统,于是现在需要一个dos系统的安装文件。
dos系统是微软开发的,不过早就已经停止更新了。而dos系统在一些领域还有实际作用(比如运行经典dos程序等),因而一些开源社区还在持续维护dos系统。微软的MS-Dos我没有找到开源的安装镜像文件。FreeDos是一个开源的dos系统,完全兼容微软的MS-Dos,正好方便学习。
FreeDos这个网站上有开源的dos系统安装包,可以下载使用(目前最新版是2022年2月发布的)。
2.下载FreeDos安装文件
(1)访问FreeDos
进入 FreeDos 网站首页,如图。然后页面下拉,点击下载,跳转页面。
(2)版本选择
因为是学习汇编语言使用,只需要最核心的系统功能(而不需要各种扩展功能及附带的游戏等),可以选择 FreeDos 1.3 Floppy Edition 版本(这是一个软盘安装版本),压缩包只有20.6MB。
(3)关于软盘安装
下载后打开压缩包,发现其中有三个文件夹:120m 、 144m、720k 。这个名称是指每一张软盘的容量大小。这个软盘版本的安装程序,自然就是使用软盘来安装操作系统。如果是在真实物理机上使用软盘安装操作系统,就需要一张一张地按顺序插入软盘(插入到软驱中)。现在我们是要使用VMware创建虚拟机,然后在虚拟机上模拟这个过程,因此也会需要一张一张地按照顺序插入软盘,只不过在虚拟机上只需要用鼠标点而不需要操作真实的软盘。
为了操作方便(减少换软盘的次数),这里就选择容量最大的软盘,也就是“144m”这个文件夹,记住它的存放地址,一会儿要用到。
现在可以进行下一步了。
三、创建一个运行dos系统的虚拟机
1.创建新的虚拟机
双击运行workstation player,选择“创建新虚拟机”。如图。
2. 选择创建方式
选择创建一个空白的硬盘,稍后安装操作系统。
这是因为我们下载的是软盘版本的安装文件(img格式),如果下载的是ISO格式的安装文件,那么这一步就应该选择“安装光盘映像文件”。
3.选择操作系统
操作系统的版本选择:其它、MS-DOS。
4.给虚拟机起个名字
5.指定磁盘容量
默认的2GB已经很够用了,然后选择 单个文件 。
6.添加一个软驱
自定义硬件,添加一个软驱。
7.设置软驱
设置软驱,勾选启动时连接软驱。
软盘映像文件的地址,选择FreeDos的地址中“144m”文件夹的“x86Boot.img”文件。
这一步是将“x86Boot.img”这张软盘插入软驱,并且设置在虚拟机开机后自动读取软盘内容,开始安装操作系统。
8.虚拟机开机,开始安装FreeDos
现在,将虚拟机开机,就会自动从软驱中开始执行FreeDos的安装程序。
一路输入Y(表示Yes)即可,当出现下面这行提示的时候,表示需要插入另一张虚拟软盘。
9.切换软盘
FreeDos的“144m”文件夹中,一共有6张软盘,如图。现在已经放在虚拟软驱中的是第一张(x86BOOT.img)。
接下来,另外五张要依次插入虚拟软驱中,软盘切换的方法如下:
① Ctrl+alt将鼠标切换回windows界面中,菜单栏player-管理-虚拟机设置。
② 在虚拟机的设置窗口右侧,将软盘镜像文件换成下一个img文件,然后回到dos系统界面按任意键,dos就会读取这张软盘的内容。
③ 接下来重复操作,直到“X86DSK05.img”软盘也读取完毕,此时会有一行提示说这张软盘“not bootable floppy”。这时需要再重新换回文件夹中的第一张软盘(x86Boot.img),再次读取完成后,FreeDos系统就安装完成了。 界面如下。
现在dos系统已经安装完成,关机以后,就可以删除软驱中的镜像文件了。不删除也没事。
四、在dos虚拟机中运行汇编程序
现在已经有了能运行dos操作系统的虚拟机,接下来准备运行一个测试程序。
那么,就先写一个简单的测试程序吧。
1.写一段测试程序
程序很简单,就是在屏幕上显示一个字符串。代码如下。
assume cs:code
data segment
db 'Hello,Dos!',0
data ends
code segment
start:
mov ax,data ;ds:si指向字符串源地址
mov ds,ax
mov si,0
mov ax,0B800H ;es:di指向目标地址
mov es,ax
mov di,160*3+32*2
s: ;显示字符串
mov cl,ds:[si]
jcxz ok
movsb
mov es:[di],07H
inc di
jmp s
ok: ;程序返回
mov ax,4c00H
int 21H
code ends
end start
2.关于虚拟机访问物理机的文件
以上这个测试程序是在windows系统中编写及保存的,现在要让虚拟机能够访问这些程序(exe文件或者是asm文件)。
我探索了很久也无法实现在dos系统中读取物理机中的文件(试过了网上的各种方法,包括以管理员身份运行、共享文件夹、磁盘映射等等,均无果),于是决定另辟蹊径。既然dos虚拟机可以读取软盘,那么我们就可以将写好的程序打包成软盘镜像文件,然后在虚拟机的软驱中加载就可以了。
具体的设置方法如下。
3.下载img镜像文件制作工具
(1)访问WinImage官网
访问 WinImage官网 下载30天试用版,安装。
(2)新建一个镜像文件
① 新建一个软盘镜像文件,大小就选择1.44MB。
② 然后,将要测试的asm文件拖拽到WinImage界面的软盘镜像文件中,然后将masm.exe 和 link.exe这两个工具也拖拽进去。这样就可以在dos系统中编译、运行。当然也可以在windows系统中先编译、链接为.exe文件,再直接将exe文件拖拽到软盘镜像文件中。
③ 为了方便调试,记得将debug32.exe也拖拽到软盘镜像文件中。
注意,平时在win10系统下的DosBox中,我使用debug.exe工具进行调试,但是在FreeDos中,就要使用debug32.exe工具进行调试。如果使用debug.exe工具,在FreeDos中运行它时系统会提示说不是正确的dos版本。debug32.exe也是开源工具,可以在“debug.exe”所在的目录中找一下,如果没有,就百度一个吧。
(3)保存镜像文件
这里注意,FreeDos能识别的软盘镜像文件只有img和flp两个格式,因此保存镜像的时候,文件名的后缀要写flp,下边的文件类型选“vfd/flp那个”,如图。这个文件的保存地址下一步要用到。
4.在dos中运行软盘中的程序
(1)为虚拟机添加软驱
运行VMware,在虚拟机关机状态,进入设置界面,添加一个软驱,勾选“启动时连接”,软盘映像文件的地址就设置为上一步保存的那个flp文件的地址。
注意,要运行的程序所在的软盘文件,对应的软驱是“软盘2”,也就是会在盘符B下。
(2)访问新软盘
将虚拟机开机,进入后默认在盘符A下。这个A盘就是启动盘,对应着第一个软驱。(如果安装完FreeDos之后,已经将安装盘从软驱中清空的话,那么这里的默认盘符就是C。)而上一步中新建的软驱,对应盘符B。因此在命令行输入 b:回车,就进入了B盘下。注意,要在英文状态下输入冒号!
(3)编译、连接
现在在命令行输入 dir回车(这表示列出当前目录下的文件及文件夹),可以看到刚才打包的文件。
接着,就像之前在DosBox中一样,编译、链接。然后就可以运行了!
(假设文件名为“1.asm”,则编译命令为 masm 1; 回车,链接命令为 link 1; 回车,运行命令为 1.exe 回车))
运行效果如下。
(4)debug32调试
如果要使用debug32工具调试程序,那么首先进入到debug32.exe工具所在的目录,然后输入 debug32 回车 ,即可开始调试,如图。
5.编程读取软盘数据
在dos虚拟机中,可以在程序中读取软盘的数据。只要在另一个软驱中(最多配置2个软驱)放置一个空的flp镜像文件,那么就相当于插入了一张空白软盘,然后就可以读取数据了。只能读软盘,不能读硬盘,软盘也是只能读不能写,我也很想知道为什么。
附上一段读取软盘数据的程序。
assume cs:code ;测试:编程读取软盘数据
data segment
db 512 dup(41H)
data ends
stack segment
db 16 dup(0)
stack ends
code segment
start:
mov ax,stack ;初始化 设置栈顶
mov ss,ax
mov sp,20H
mov ax,data ;es:bx指向接收磁盘数据的区域
mov es,ax
mov bx,0
call test_read
mov ax,4C00H
int 21H
test_read: ;功能:读取磁盘数据到内存es:bx处
mov al,1 ;读取的扇区个数
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov dl,0 ;驱动器号 0代表软盘A
mov dh,1 ;磁头号,即面号
mov ah,02H ;读命令
int 13H
ret
五、一些可能有用的代码
在探索过程中整理了一些代码,也许读到本文的人能用到吧。
1.访问硬盘端口读写数据
在使用13H读写磁盘失败后,我搜索到了另外一种读写硬盘数据的方法,就是通过访问 01F0H-01F7H端口来读写硬盘(共有CHS LBA24 LBA48三种模式)。虽然这个方法我的电脑也不能运行成功,不过还是将代码整理在这里吧。
test3: ;功能:LBA48方式读取硬盘数据
;参数:ds:bx指向接收硬盘数据的内存首地址
push ax
push dx
push cx
;1.写两次0x1f1端口: 0
mov al,0
mov dx,01F1H
out dx,al
out dx,al
;2.写两次0x1f2端口: 第一次要读的扇区数的高8位,第二次低8位
mov al,0H
mov dx,01F2H
out dx,al
mov al,01H
out dx,al
;3.写0x1f3: LBA参数的24~31位
mov al,0
mov dx,01F3H
out dx,al
;4.写0x1f3: LBA参数的0~7位
mov al,1
mov dx,01F3H
out dx,al
;5.写0x1f4: LBA参数的32~39位
mov al,0
mov dx,01F4H
out dx,al
;6.写0x1f4: LBA参数的8~15位
mov al,0
mov dx,01F4H
out dx,al
;7.写0x1f5: LBA参数的40~47位
mov al,0
mov dx,01F5H
out dx,al
;8.写0x1f5: LBA参数的16~23位
mov al,0
mov dx,01F5H
out dx,al
;9.写0x1f6: 7~5位,010,第4位0表示主盘,1表示从盘,3~0位,0
mov al,01000000B
mov dx,01F6H
out dx,al
;10.写0x1f7: 0x24为读, 0x34为写
mov al,24H
mov dx,01F7H
out dx,al
;11.读取数据
mov dx,01F0H
in ax,dx
mov ds:[bx],ax
pop cx
pop dx
pop ax
ret
;==============================================
test2: ;chs方式访问硬盘数据
push ax
push dx
push cx
;1.向01F1H 写入0
mov al,0
mov dx,01F1H
out dx,al
out dx,al
;2.向01F2H写入要访问的扇区个数
mov al,1
mov dx,01F2H
out dx,al
;3.写入扇区号
mov al,0
mov dx,01F3H
out dx,al
;4.写入柱面低8位
mov al,0
mov dx,01F4H
out dx,al
;5.写入柱面高8位
mov al,0
mov dx,01F5H
out dx,al
;6.写入 7~5位,101,第4位0表示主盘,1表示从盘,3~0位,磁头号
mov al,10110000B
mov dx,01F6H
out dx,al
;7.传送读写命令
mov al,20H
mov dx,01F7H
out dx,al
;8.等待读写完成
mov dx,01F7H
s_test2:
in al,dx
and al,00010000B
cmp al,10H
jne s_test2
;9.读取数据
mov dx,01F0H
mov cx,256
s2_test2:
in ax,dx
mov ds:[bx],ax
add bx,2
loop s2_test2
pop cx
pop dx
pop ax
ret
;==============================================
test1: ;功能:LBA24方式读取U盘中1个扇区的数据,并存入ds:bx处
;参数:ds:bx指向接收硬盘数据的内存首地址
;1.将访问的扇区个数送入端口
mov al,01H ;参数:要访问的扇区个数
mov dx,01F2H
out dx,al
;2.将访问的起始扇区的逻辑扇区号送入端口
mov al,02H ;参数:逻辑扇区号0-7位
mov dx,01F3H
out dx,al
mov al,00H ;参数:逻辑扇区号8-15位
mov dx,01F4H
out dx,al
mov al,00H ;参数:逻辑扇区号16-23位
mov dx,01F5H
out dx,al
mov al,0F0H ;参数:高4位 1 读写模式(1位LBA) 1 硬盘选择(0为主硬盘) ;低4位 逻辑扇区号24-27位
mov dx,01F6H
out dx,al
;3.将读/写指令送入端口
mov al,20H ;参数:20H是读入数据;30H是写出数据
mov dx,01F7H
out dx,al
;4.读取硬盘已就绪的状态字节
mov dx,01F7H
ready:
in al,dx
and al,88H
cmp al,08H
jne ready
;5.从端口读取/写入数据
mov cx,256
mov dx,01F0H
s_test1:
in ax,dx
mov es:[bx],ax
add bx,2
loop s_test1
ret
2.保存、复现屏幕内容
这是书上的内容,以后如果换了运行环境,可以重新拿来做个测试。代码如下。
saveScreen: ;功能:将当前屏幕上的内容保存在磁盘上
;参数:ah 功能号(3表示写扇区);al写入的扇区个数
; ch 磁道号;cl 扇区号(从1开始);
; dh 磁头号(面);dl 驱动器号(81H为硬盘D盘)
; es:bx指向要写入磁盘的数据首地址
;返回:操作成功: ah=0,al=写入的扇区个数;
; 操作失败:ah=出错代码
push ax
push es
push bx
push cx
push dx
mov ax,0B800H ;参数:es:bx指向要写入磁盘的数据首地址
mov es,ax
mov bx,0
mov al,8 ;参数:写入的扇区个数(一屏幕4000字节,一个扇区512字节,所以需要8个扇区)
mov ch,0 ;参数:磁道号
mov cl,1 ;参数:扇区号,从1开始
mov dh,1 ;参数:磁头号(面)
mov dl,81H ;参数:驱动器号(D盘为81H)
mov ah,3 ;参数:功能号 3表示写扇区
int 13H ;调用int 13H中断例程
pop dx
pop cx
pop bx
pop es
pop ax
ret
;===================================================
readScreen: ;功能:将磁盘中的内容显示在屏幕上
;参数:ah 功能号(2表示读扇区);al 读取的扇区个数;
; ch 磁道号; cl 扇区号(从1开始);
; dh 磁头号(面);dl 驱动器号(81H为D盘)
; es:bx指向用于接收磁盘读入的数据的内存区
;返回:操作成功:ah=0 al=读入的扇区数;
; 操作失败:ah=出错代码
push ax
push bx
push cx
push dx
push es
mov ax,0B800H ;参数:es:bx指向用于接收磁盘读入数据的内存区
mov es,ax
mov bx,0
mov al,8 ;参数:读取的扇区个数(一屏幕4000字节,需要8个扇区)
mov ch,0 ;参数:磁道号
mov cl,1 ;参数:扇区号,从1开始
mov dh,1 ;参数:磁头号(面)
mov dl,81H ;参数:驱动器号,81H为D盘
mov ah,2 ;参数:功能号,2表示读扇区
int 13H ;调用int 13H中断例程
pop es
pop dx
pop cx
pop bx
pop ax
ret
总结
这可以说是学习汇编语言以来花费时间最长的一篇博文了。耗时耗力的主要原因,是书籍内容与时代脱节,导致我不得不进行了许多摸索(网上的资料我找过了),绕了很大一个弯,浪费了许多宝贵时间,才最终发现无法走通。一路学习到这最后一章,我对王爽老师的独特教学方法很佩服、很信任。但是这一次的尝试摸索,不禁使我对这本2019年12月才修订出版过第四版的教材产生了一些失望。
由于第十七章的学习无法完成,后面的课程设计也无法完成了。原本还想继续写几篇关于汇编语言的博文,但现在兴趣索然,故这一篇大概会是这一系列关于汇编语言的博文的最后一篇了。2022.11