上一篇文章记录了如何在FAT12中找到自己写入的文件以及文件内容,那么这篇文章就开始记录下一个环节,如何读取软盘并在软盘中找到 Loader 文件。
DOS可以识别的引导盘
既然引导扇区需要有BPB等头信息才能被微软识别,那我们就先加上它,让程序一开头变成下面的形式。
jmp short LABEL_START ; Start to boot.
nop ; 这个 nop 不可少
; 下面是 FAT12 磁盘的头
BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节
BPB_BytsPerSec DW 512 ; 每扇区字节数
BPB_SecPerClus DB 1 ; 每簇多少扇区
BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区
BPB_NumFATs DB 2 ; 共有多少 FAT 表
BPB_RootEntCnt DW 224 ; 根目录文件数最大值
BPB_TotSec16 DW 2880 ; 逻辑扇区总数
BPB_Media DB 0xF0 ; 媒体描述符
BPB_FATSz16 DW 9 ; 每FAT扇区数
BPB_SecPerTrk DW 18 ; 每磁道扇区数
BPB_NumHeads DW 2 ; 磁头数(面数)
BPB_HiddSec DD 0 ; 隐藏扇区数
BPB_TotSec32 DD 0 ; wTotalSectorCount为0时这个值记录扇区数
BS_DrvNum DB 0 ; 中断 13 的驱动器号
BS_Reserved1 DB 0 ; 未使用
BS_BootSig DB 29h ; 扩展引导标记 (29h)
BS_VolID DD 0 ; 卷序列号
BS_VolLab DB 'OrangeS0.02' ; 卷标, 必须 11 个字节
BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节
加上这段代码后,程序运行的效果没有变化,但是,现在的软盘已经能够被DOS以及Linux识别了。
一个最简单的Loader
要写代码加载Loader入内存,首先要有Loader。所以,我们先写一个最简单的Loader,让它显示一个字符,然后进行死循环,这样,如果加载成功并成功交出控制权的话,就可以看到这个字符。
那么我们现在新建一个文件 loader.asm,具体代码如下所示。
; 编译命令:nasm loader.asm -o Loader.bin
org 0100h
mov ax, 0B800h
mov gs, ax
mov ah, 0Fh ; 0000:黑底 1111:白字
mov al, 'L'
mov [gs:(80 * 0 + 39) * 2], ax ; 屏幕第 0 行,第 39 列
jmp $ ; 到此停住
需要注意的是,虽然代码编译出的二进制代码加载到内存的任意位置都可以正确执行,但我们要扩展它,为了将来的执行不会出现问题,要保证把它放入某个段内偏移0x100的位置。
加载Loader入内存
加载一个软盘上的文件入内存,免不了要读软盘,这时就用要到BIOS中断 int 13h。它的用法如下表所示。
中断号 | 寄存器 | 作用 | |
13h | ah=00h | dl=驱动器号(0表示A盘) | 复位软驱 |
ah=02h | al=要读扇区数 | 从磁盘将数据读入 es:bx 指向的缓冲区中 | |
ch=柱面(磁道)号 | cl=起始扇区号 | ||
dh=磁头号 | dl=驱动器号(0表示A盘) | ||
es:bx -> 数据缓冲区 |
从上表中可以看到,中断需要的参数不是之前提到的从第0扇区开始的扇区号,而是柱面号、磁头号以及在当前柱面上的扇区号3个分量,所以需要我们自己来转换一下。对于1.44MB的软盘来讲,总共有两面(磁头号0和1),每个面80个磁道(磁道号0~79),每个磁道有18个扇区(扇区号1~18)。下面的公式就是软盘容量的由来:
2 × 80 × 18 × 512 = 1.44MB
于是,磁头号、柱面(磁道)号和起始扇区号可以用下图所示的方法来计算。
好了,有了上述内容的讲解,我们现在已经可以从软盘中读取指定扇区的内容了。那么我们以上一篇文章使用的软盘 fd.img 为例,进行软盘读取过程测试,看是否能够读取成功。我们读取第33扇区的内容,因为上一篇文章里面记录了第33扇区是数据区的第一个簇,创建的 RIVER.TXT 文件内容就保存在这个扇区内。那么我们就读取这个扇区,然后将读取的扇区内容放入指定内存,查看读取操作是否成功。
新建读取软盘文件 readFd.asm,具体代码如下所示。
; 编译命令:nasm readFd.asm -o readFd.com
org 0100h
; 计算柱面(磁道)号、磁头号、起始扇区号,计算第 33 扇区
mov ax, 33
mov bl, 18
div bl
inc ah
mov cl, ah ; 起始扇区号
mov dh, al
shr al, 1
mov ch, al ; 柱面号
and dh, 1 ; 磁头号
mov dl, 0 ; 驱动号,0 表示 A 盘
mov ah, 02h
mov al, 1 ; 读取 1 个扇区
; 读取的 1 个扇区放入事先申明的内存区域
mov bx, 09000h
mov es, bx ; es:bs -> 09000:0000,读取扇区内容放入该数据缓冲区
mov bx, 0
int 13h ; 调用 BIOS 中断 int 13h 读取软盘
mov ax, 4c00h
int 21h
上述代码将第 33 扇区的内容读入段地址 09000h ,偏移为0处。将该文件编译为 readFd.com 文件,使用 OSFMount 软件将该COM文件拷贝到 fd.img 软盘镜像中,然后启动Bochs虚拟机,进入软盘A,执行 readFd.com。执行完成后,使用 debug 命令,查看内存 09000:0000 内容,可以看到内存内容已经变成读入的扇区内容了。
好了,读取软盘已经知道怎么做了,那么接下来就要开始在软盘中寻找 Loader.bin了。我们先将上面的 loader.asm 文件编译成 Loader.bin,然后使用 OSFMount 软件将 Loader.bin 拷贝到 fd.img 软盘进行中,拷贝完成后,可以使用二进制查看器查看一下现在的 fd.img 文件内容。
可以看到 Loader.bin 已经拷贝到 fd.img 文件中了,只不过文件名变成了大写,那么接下来进行查找时也应该使用大写进行查看。
新建在软驱中查找 Loader.bin 文件 search.asm,具体代码如下所示。
; 编译命令:nasm search.asm -o search.com
org 0100h
SectorNoOfRootDirectory equ 19 ; 根目录第一个扇区号,19
RootDirSectors equ 14 ; 根目录占用空间,14
BaseOfLoader equ 09000h ; 读取软盘扇区加载到的位置 -- 段地址
OffsetOfLoader equ 0100h ; 读取软盘扇区加载到的位置 -- 偏移地址
jmp short Search
LoaderFileName db "LOADER BIN" ; 需要查找文件名
LoaderFileNameLength equ 11 ; 查找文件名长度
wRootDirSizeForLoop dw RootDirSectors ; 根目录占用的扇区数,在循环读取扇区中会递减至零
wSectorNo dw SectorNoOfRootDirectory ; 读取的扇区号,从 19 开始递增至最后一个根目录扇区
Search:
; 如果根目录扇区已经读完,则跳转到结束 End
mov ax, [wRootDirSizeForLoop]
cmp ax, 0
je End
; 读扇区
mov ax, [wSectorNo]
call ReadSector
; 比较查找文件名
call Cmp_FileName
cmp ax, 0
je .notSearch
cmp ax, 1
je .foundIt
.notSearch:
; 当前扇区未找到,读取下一个根目录扇区
add word [wSectorNo], 1 ; 扇区号 +1,下次读取扇区号
sub word [wRootDirSizeForLoop], 1 ; 读取扇区数 -1,已读取一个扇区
jmp Search
.foundIt:
mov ax, 0B800h
mov gs, ax
mov ah, 0Fh ; 0000:黑底 1111:白字
mov al, 'F'
mov [gs:0], ax ; 屏幕第 0 行,第 0 列
mov ax, BaseOfLoader
mov es, ax
mov ax, [wSectorNo]
mov [es:OffsetOfLoader], ax ; 扇区编号赋值
mov [es:OffsetOfLoader + 2], bx ; 段偏移地址赋值
jmp End
; 比较查找的文件名称函数
; ax:是否找到,0:未找到,1:找到
; bx:文件名称所在扇区段偏移地址
Cmp_FileName:
mov bx, 0
mov ax, BaseOfLoader
mov es, ax
.cmp_start:
cmp bx, 512
je .notMatch
mov cx, LoaderFileNameLength
mov si, LoaderFileName
mov di, 0
.cmp:
lodsb
cmp al, byte es:[bx + OffsetOfLoader + di]
je .equal
jne .notEqual
.equal:
inc di
cmp di, LoaderFileNameLength
je .match
loop .cmp
.notEqual:
add bx, 32
jmp .cmp_start
.match:
mov ax, 1
jmp .cmp_ret
.notMatch:
mov ax, 0
mov bx, 0
jmp .cmp_ret
.cmp_ret:
ret
End:
mov ax, 4c00h
int 21h
; 根据扇区编号读取扇区内容到指定位置函数
; ax:读取扇区编号
ReadSector:
push bx
push cx
push dx
push es
mov bl, 18
div bl
inc ah
mov cl, ah ; 起始扇区号
mov dh, al
shr al, 1
mov ch, al ; 柱面号
and dh, 1 ; 磁头号
mov dl, 0 ; 驱动号,0 表示 A 盘
mov ah, 02h
mov al, 1 ; 读取 1 个扇区
; 读取的 1 个扇区放入事先申明的内存区域
mov bx, BaseOfLoader
mov es, bx ; es:bs -> BaseOfLoader:OffsetOfLoader
mov bx, OffsetOfLoader
int 13h ; 调用 BIOS 中断 int 13h 读取软盘
pop es
pop dx
pop cx
pop bx
ret
上述代码实现读取软盘内容,查找文件所在扇区的功能。这里将读取软盘的功能封装成函数 ReadSector,比较字符串功能封装成函数 Cmp_FileName,这样在代码结构上就更加清晰了。如果找到了 Loader.bin 文件,则在屏幕第一行第一列打印一个黑底白字的字符‘F’,并且将文件所在的软盘扇区保存在地址 09000:100 位置的第一个和第二个字节处,将偏移位置保存在 09000:100 位置的第三个和第四个字节处。
好了现在将该文件编译成 search.com 文件,使用 OSFMount 软件将该COM文件拷贝到 fd.img 软盘镜像中,然后启动Bochs虚拟机,进入软盘A,执行 search.com。执行完成后,可以看到屏幕上出现了一个黑底白字的字符‘F’,说明找到了 Loader.bin文件。再使用 debug 命令,查看内存 09000:100 内容,获取文件所在的扇区和偏移位置。
从上图中可以看出,Loader.bin 文件在 0x14(十进制:20)扇区,偏移位置是 0x40。现在我们使用二进制查看器查看 fd.img 镜像文件,第20扇区的偏移量是 20×512=10240,转换为十六进制为:0x2800。让我们到这里,在偏移 0x40 处查看一下内容,可以看到文件名 Loader.bin 保存在此处。
好了,到这里为止,我们已经可以读取软盘内容和在软盘中进行文件查找了,那么接下来要做的事情就是读取文件内容。这次就先记录到这里,读取文件内容放到下次记录。
公众号