0


使用 FAT12 文件系统实现简单的 Boot 加载 Loader 到内存

系统环境:
OS:CentOS Stream release 9 (cmd: cat /etc/redhat-release)
Linux Kernel:Linux 5.14.0-142.el9.x86_64 (cmd: uname -a)

1. 前言

经过上一篇文章,我们已经清楚的了解了

FAT12

文件系统的结构以及它的工作原理,那么在本文就基于之前我们写的最基本的

boot

程序

imboot.S

之上,将其修改为

FAT12

文件系统的引导扇区类型,并为其添加相应的功能,来实现加载存放在

FAT12

文件系统中的

Loader

程序的功能。由于本文需要用到上一篇文章《FAT12 文件系统》中的内容,笔者建议也打开上篇文章对照着看。话不多说,do it.

2. 修改为 FAT12 文件系统类型引导扇区

相信各位还能清楚的记得

FAT12

文件系统类型的引导扇区是什么(如果忘记了,快点击 “上一篇” 查看),首先

FAT12

的引导扇区占用了

1

个扇区,也就是

512 B

,它不仅包含了

FAT12

文件系统的结构信息,同时包含

boot

程序,并以最后两个字节为

0x55

0xAA

作为引导扇区结束的标识。

这次我们就另外新建一个文件吧,将其命名为

imboot-v0.2.S

,代表这是我们的第二个版本的

boot

程序。

[imaginemiracle@imos-ws imboot-v0.2]$ vim imboot-v0.2.S

我们来根据上一篇中的

MBR

表格来编写第一部分代码:

org 0x7c00; 定义栈指针
BaseOfStack     equ     0x7c00

BaseOfLoader    equ     0x1000
OffsetOfLoader  equ     0x00; 与 BaseOfLoader 组合成为 Loader 程序的物理地址
; BaseOfLoader <<4+ OffsetOfLoader =0x10000

RootDirSectors          equ     14; 根目录扇区总数
SectorNumOfRootDirStart equ     19; 根目录起始扇区号
SectorNumOfFAT1Start    equ     1; FAT1 表起始扇区号
SectorBalance           equ     17; 用于平衡文件或目录的起始簇号与数据区起始簇号的差值

; BS_JmpBoot
jmp     short Label_Boot_Start
nop

BS_OEMName              db      'IMboot  '; 长度必须为 8,不足以空格填充
BPB_BytesPerSec         dw      512
BPB_SecPerClus          db      1
BPB_RsvdSecCnt          dw      1
BPB_NumFATs                     db      2
BPB_RootEntCnt          dw      224; RootDirSectors *512/32=224
BPB_TotSec16            dw      2880;1440*1024/512=2880(fp:1.44MB)
BPB_Media               db      0xf0
BPB_FATSz16             dw      9
BPB_SecPerTrk           dw      18
BPB_NumHeads            dw      2
BPB_HiddSec             dd      0
BPB_TotSec32            dd      0
BS_DrvNum               db      0
BS_Reserved1            db      0
BS_BootSig              db      0x29
BS_VolID                dd      0
BS_VolLab               db      'boot loader'; 必须 11bit,不足用空格补齐
BS_FileSysType          db      'FAT12   '; 必须 8bit,不足用空格补齐

2.1. org 0x7c00

org

Origin

的缩写,作用为指定程序的起始地址。与之前一样,我们需要将

boot

程序的地址指定到

0x7c00

的位置,

BIOS

运行后会跳入

0x7c00

这个地址,如果不指定则默认程序的起始地址为

0x0000

,那么

BIOS

将不能正常加载到我们的

boot

程序。

2.2. Loader 程序位置的计算

equ

之前也解释过,事实上就是让一个字符串标识符来代表一个具体的值,这里的有两个与

Loader

相关的定义,

BaseOfLoader equ 0x1000

OffsetOfLoader equ 0x00

,分别表示

Loader

程序的基地址和偏移地址(这里的地址均指的是物理地址)。通过基地址加上偏移地址的计算得到

Loader

的实际地址(这里的加,并不是求和,可以理解为拼接的意思)。计算方法如下:

// BaseOfLoader == 0x1000// OffsetOfLoader == 0x00
BaseOfLoader <<4+ OffsetOfLoader =0x10000

2.3. 根目录扇区总数的计算

根目录扇区总数

RootDirSectors

14

也是通过计算得到。

计算方法:(BPB_RootEntCnt *32+ BPB_BytesPerSec -1)/ BPB_BytesPerSec
实际计算:(224*32+512-1)/512=14

这里加上的

BPB_BytesPerSec - 1

也就是

512 - 1

的目的是为了使当所有的根目录项不能填满完整的扇区时该公式也能够成立。简单来讲就是当根目录项存在不能占满一个扇区时,为其分配一块完整的扇区,以避免根目录缺失或不存在。

2.4. 根目录起始扇区号和 FAT1 表起始扇区号的计算

根目录起始扇区号

SectorNumOfRootDirStart

的值为

19

。该值是通过当前区域之前的所有占用扇区数相加得到。

计算方法:MBR + FAT1 + FAT2
实际计算:1+9+9=19

在这里插入图片描述

FAT1

表起始扇区号

SectorNumOfFAT1Start

的值为

1

。在

FAT1

之前只有引导扇区

MBR

占用了

1

个扇区,那么

FAT1

的起始扇区号应该为

1

2.5. SectorBalance

可能你比较好奇这里为什么定义了一个

SectorBalance

值为

17

,它是做什么用的,好像

17

在之前也没有见过。不要慌,这就解释。

我们知道数据区的起始扇区号应该为

33

(根目录起始扇区号 + 根目录扇区总数 =

19 + 14

=

33

)。

ok,还记得在上篇文章中,我们在索引

imboot.txt

文件在数据区的存放位置时是否因为数据区的起始簇对于根目录项中记录的起始簇值为

2

,因此,我们特意将记录的

7

号簇,在计算的时候减了

2

。如果忘记的话,没关系,笔者将上篇文章的有关部分截图贴在这里了,帮你回忆。

这是图片

那么看到这里,其实已经大概清楚了这个

17

是做什么的了。它主要用来在计算数据区的簇号(扇区号)时使用,这样避免在计算时再减去

2

,使得代码更见简洁易读。它有个优雅的名字叫做 “数据区平衡”。

3. 增加软盘读取功能

3.1. 代码实现

为了能够读取

FAT12

格式软盘,我们必须在代码中实现软盘的读取功能。具体代码如下:

; 读取磁盘扇区模块(函数),每次读取一个扇区
Func_ReadOneSector:
push            bp
mov             bp, sp
sub             esp,2
mov             byte    [bp -2], cl
push            bx
mov             bl,[BPB_SecPerTrk]
div                bl          ; 默认被除数保存在 ax 中,ax/bl 商保存在 al,余数保存在 ah 中
inc             ah          ; inc 加一指令
mov             cl, ah    
mov             dh, al
shr             al,1; shr 逻辑右移
mov             ch, al
and             dh,1
pop             bx
mov             dl,[BS_DrvNum]
Label_Go_On_Reading:
mov             ah,2
mov             al, byte        [bp -2]int13h
jc              Label_Go_On_Reading
add             esp,2
pop             bp
ret

这里在转入执行时涉及栈的保存,并且在返回时又有栈的还原,这已经和

C

语言中的函数操作一样了,因此可以将这段代码块视为一个功能函数,其作用为读取软盘扇区,每次读取长度为一个扇区(

512B

)。

3.2. INT 13h 中断

有关软盘操作,实际上是利用

BIOS

中的中断服务程序

INT 13h

来实现的,在之前的文章中也有介绍过,忘了没关系再瞅两眼就想起来了。而本文中所用到的是

INT 13h

中断的主功能号

AH = 02h

实现软盘扇区的读取操作。其对应寄存器的详细功能如下:
中断服务主功能号

AH

值功能描述

AL

功能

CH

功能

CL

功能

DH

功能

DL

功能

ES:BX
INT 13h
02h

读取软盘扇区读入的扇区数 (必须非

0

)磁道号 (柱面号) 的低

8


扇区号

1~63 (bit 0~5)

;
磁道号 (柱面号) 的高

2

位 (bit 6~7,只对硬盘有效)磁头号驱动器号(如果操作的是硬盘驱动器,

bit 7

必须被置位)数据缓冲区

3.3. 传入的参数与 INT 13h 的配置参数转换

Func_ReadOneSector

函数需要用到三个寄存器作为参数传递使用:

  • AX 保存待读取的磁盘起始扇区号
  • CL 保存读入的扇区数量
  • ES:BX 保存目标缓冲区起始地址 (读取磁盘内容的保存位置)。

由于

Func_ReadOneSector

函数传入的磁盘扇区号是逻辑块寻址 (

Logical Block Address——LBA

),而

INT 13h

AH = 02h

中断服务只能识别柱面/磁头/扇区

(Cylinder/Head/Sector——CHS)

,因此在两者之间需要转换,转换方法如下:

请添加图片描述

3.4. 代码详解

OK,在了解了这些之后,笔者这小段函数的代码逐行解释,以帮助各位快速理解。
首先必须清楚一点,在跳转到

Func_ReadOneSector

执行前,一定会为

AX

CL

ES:BX

寄存器分别保存好需要传入的值。

  • 第一行:将上一个函数的栈基址寄存器 bp 的值压栈进行保存;
  • 第二行:将栈顶寄存器 sp 的值保存进栈基址寄存器 bp,即使 bp 指向栈顶;(此时 bp == sp
  • 第三行:将栈顶寄存器 esp 的值减 2,即使 esp 向下移动两个字节,目的为了在栈中申请两个字节的空间,用于保存之后的数值;
  • 第四行:将寄存器 cl 中的值保存到刚刚开辟的栈的空间中;
  • 第五行:将寄存器 bx 压栈保存;(此时 esp 将会向下移动 bx 大小的单位,即向下移动 16 bit,两个字节)
  • 第六行:将 BPB_SecPerTrk 的值保存到寄存器 bl 中;(此时 bl == 18
  • 第七行:用 ax 的值除以 bl 的值,并将结果的商保存到 al 中,余数保存到 ah 中;(此处的结果需根据传入的 ax 的值决定)
  • 第八行:将 ah 自加一;(由于磁道内的起始扇区号是从 1 开始计数,因此需加一操作)
  • 第九行:将 ah 的值保存到 cl 中,即将计算后的起始扇区号写入 cl 寄存器,以待中断服务使用;
  • 第十行:将 al 的值保存到 dh 中;
  • 第十一行:将 al 寄存器的值逻辑右移 1
  • 第十二行:将 al 寄存器的值保存到 ch,即将计算后的磁道号(柱面号)写入 ch 寄存器,以待中断服务使用;
  • 第十三行:将 dh 寄存器的值和 0x1 做逻辑与操作,计算出磁头号,以待中断服务使用;
  • 第十四行:将数据弹出栈,并保存到 bx 中;
  • 第十五行:将 BS_DrvNum 的值保存到 bl 中;
  • 第十六行:定义标号 Label_Go_On_Reading
  • 第十七行:将 2 写入 ah 寄存器,表示准备使用 INT 13h02h 号主功能;
  • 第十八行:将栈中 bp - 2 位置的值保存到 al 中,即最开始传入参数的读入扇区数量保存到 al,以待中断服务使用;
  • 第十九行:开启中断 int 13h
  • 第二十行:条件跳转语句,当检测到 CF == 1 时跳转到 Label_Go_On_Reading 处,即检测还没读取完时继续配置中断读取;(当数据读取成功时 CF 标志位会被自动复位,即使 CF = 0
  • 第二十一行:将 esp 向上移动两个字节,释放之前开辟的栈空间;
  • 第二十二行:将 bp 弹出栈;
  • 第二十三行:调用 ret 指令恢复主调函数现场,即栈指针恢复到进入前的状态。

总的来讲,

Func_ReadOneSector

模块的功能就是将指定的扇区内容读取到

ES:BX

指定的位置处。

4. 目标文件搜索功能

4.1. 代码实现

文件的搜索功能则是基于上面实现的软盘读取功能函数实现的,具体代码如下:

;====== 查找 imloader.bin 文件
Label_Imboot_Begin:
mov             word    [SectorNo], SectorNumOfRootDirStart ; SectorNumOfRootDirStart  19

Label_Search_In_Root_Dir_Begin:
cmp             word    [RootDirSizeForLoop],0; RootDirSizeForLoop == RootDirSectors ==14
jz              Label_No_LoaderBin
dec             word    [RootDirSizeForLoop]; RootDirSizeForLoop 自减
mov             ax,0x00
mov             es, ax
mov             bx,0x8000
mov             ax,[SectorNo]
mov             cl,1
call            Func_ReadOneSector
mov             si, LoaderFileName
mov             di,0x8000
cld
mov             dx,0x10

Label_Search_For_LoaderBin:
cmp             dx,0
jz              Label_Goto_Next_Sector_In_Root_Dir
dec             dx
mov             cx,11

Label_Cmp_FileName:
cmp             cx,0
jz              Label_FileName_Found
dec             cx
lodsb
cmp             al, byte        [es:di]
jz              Label_Go_On
jmp             Label_Different

Label_Go_On:
inc             di
jmp             Label_Cmp_FileName

Label_Different:
and             di,0x0ffe0
add             di,0x20
mov             si, LoaderFileName
jmp             Label_Search_For_LoaderBin

Label_Goto_Next_Sector_In_Root_Dir:
add             word    [SectorNo],1
jmp             Label_Search_In_Root_Dir_Begin

4.2. Inter 标志寄存器 EFLAGS Register

标志寄存器

EFLAGS Register

ZF

为零标志位)
在这里插入图片描述

4.3. CMP 与 JZ 指令

CMP

指令:cmp 目的操作数, 源操作数
CMP结果ZFCF目的操作数 < 源操作数01目的操作数 > 源操作数01目的操作数 = 源操作数10

JZ

指令:当

ZF

1

时发生跳转。

4.4. CLD 指令

cld

指令与

std

指令是一对相对的操作指令,这两个指令均是,来操作方向标志位

DF (Direction Flag)

cld

DF

复位,即使

DF = 0

std

则使

DF

置位,即使

DF = 1

。通过使用

cld

std

指令控制方向标志

DF

的值,从而决定内存地址是增加(

DF = 0

时向高地址增加)还是减小(

DF = 1

时向低地址减小)。

4.5. LODSB 指令

与之同一系列的指令为LODSB/LODSW/LODSD/LOD,与之相对系列的指令为STOSB/STOSW/STOSD/STOSQ。他们具体的功能如下:
指令描述

DF = 0
DF = 1

lodsb将

ds:si

寄存器指向地址处的数据取出并保存到

al

寄存器中

si = si + 1
si = si - 1

lodsw将

ds:si

寄存器指向地址处的数据取出并保存到

ax

寄存器中

si = si + 1
si = si - 1

lodsd将

ds:esi

寄存器指向地址处的数据取出并保存到

eax

寄存器中

esi = esi + 1
esi = esi - 1

lodsq将

ds:rsi

寄存器指向地址处的数据取出并保存到

rax

寄存器中

rsi = rsi + 1
rsi = rsi - 1

stosb将

al

寄存器中的值取出保存到

es:di

指向的地址处

di = di + 1
di = di - 1

stosw将

ax

寄存器中的值取出保存到

es:di

指向的地址处

di = di + 1
di = di - 1

stosd将

eax

寄存器中的值取出保存到

es:edi

指向的地址处

edi = edi + 1
edi = edi - 1

stosq将

rax

寄存器中的值取出保存到

es:rdi

指向的地址处

rdi = rdi + 1
rdi = rdi - 1

⭐特别小知识:
需要注意的是,其中目的字符串必须保存在附加段中,即必须是

es:di


而源字符串允许使用段跨越前缀来修饰,但偏移地址必须是

si

,因此上述

si

的前缀段可以是

es

也可以是

ds

4.6. 代码详解

好了,现在我们已经掌握了足够的知识可以完全看懂这段代码了。接下来将逐行分析这段代码:(仅指有效代码行,定义标号的地方就不再说明了)

  • 第一行:代码开始首先获取根目录的起始扇区号,并将其保存到临时变量 SectorNo 中;

🚩Label_Search_In_Root_Dir_Begin:

  • 第二行:用 cmp 指令比较临时变量 RootDirSizeForLoop0 的结果;(当前代码 RootDirSizeForLoop 的初始值为 0[注]:cmp 指令,实际相当于做减法操作,并根据结果不同将会修改标志寄存器 PSW 中的 ZF 和 CF。
  • 第三行:当 RootDirSizeForLoop 的值等于 0 时发生跳转到 Label_No_LoaderBin 处执行;[注]:由于当前还未实现 Loader 程序并无法加载并读取,因此这里先让其执行检测无 Loader 的情况。
  • 第四行:RootDirSizeForLoop 自减 1
  • 第五行:将 0x00 保存到 ax 寄存器中;
  • 第六行:将 ax 的值保存到 es 段寄存器中,初始化段寄存器 es
  • 第七行:将 0x8000 保存到 bx 寄存器中,由 esbx 组成的 es:bx == es << 4 + bx == 0x8000 将表示读取数据的保存地址,调用 Func_ReadOneSector 函数前用寄存器传参,虽然这里并不是参数传递作用,而是为了给 INT 13h 中断服务使用;
  • 第八行:将根目录的起始扇区号 19 写入 ax 寄存器中,将作为参数给 Func_ReadOneSector 函数使用;
  • 第九行:为 cl 寄存器写入 1,函数传参,表示要读取的扇区数量为 1
  • 第十行:调用 Func_ReadOneSector 函数,转入其地址执行;[注]:此时,Func_ReadOneSector 函数已经将读取的目录扇区内容写入以 0x8000 为起始地址的内存空间中了。
  • 第十一行:将 LoaderFileName 的内容保存到 si 寄存器中,LoaderFileName 是定义的一个字符串,值为 “IMLOADERBIN”;( si 保存之后需要比较的字符串)
  • 第十二行:将 0x8000 保存到 di 寄存器中;(di 保存要读取字符的地址)
  • 第十三行:执行 cld 指令,将 DF 置为 0;(之后将遵循地址增加的方向)
  • 第十四行:将 0x10 保存到 dx 寄存器中;(dx 保存每个扇区能存储的根目录项的数量,用于计数功能,512 ÷ 32 = 16 = 0x10

🚩Label_Search_For_LoaderBin:

  • 第十五行:用 cmp 比较 dx 的值与 0 的大小;(参考本文 4.3 小节)
  • 第十六行:检测 cmp 的结果改变的 ZF,当 ZF = 1 时转入 Label_Goto_Next_Sector_In_Root_Dir 处执行;(ZF = 1 时,表示 dx == 0,表示当前扇区已读取完,但仍未匹配到文件名)
  • 第十七行:dx 自减 1;(计数功能,表示还有 dx 个目录项未读)
  • 第十八行:将 11 保存到 cx 寄存器中;(目录项中用于保存文件名的长度为 11 bit,用于计数功能,表示还有 cx 个字符未匹配到)

🚩Label_Cmp_FileName:

  • 第十九行:用 cmp 比较 cx 的值与 0 的大小;(参考本文 4.3 小节)
  • 第二十行:检测 cmp 的结果改变的 ZF,当 ZF = 1 时转入 Label_FileName_Found 处执行;(ZF = 1 时,表示 cx == 0,表示文件名的所有字符都已经匹配到)
  • 第二十一行:cx 自减 1;(计数功能,表示还有 cx 个字符未匹配到)
  • 第二十二行:执行 lodsb 指令,读取 ds:di 指向地址处的数据,保存到 al 中,并将 si 加一(因为 DF = 0,若 DF = 1,则 si 减一);(读取扇区中目录项的一个字节)
  • 第二十三行:用 cmp 指令比较 ales:di,字符匹配;
  • 第二十四行:检测 cmp 的结果改变的 ZF,当 ZF = 1 时跳转到 Label_Go_On 处执行;
  • 第二十五行:直接跳转至 Label_Different 处执行,由于未匹配成功,则准备匹配下一个目录项;

🚩Label_Go_On:

  • 第二十六行:di 加一;(准备读取下一个字节内存单元)
  • 第二十七行:直接跳转至 Label_Cmp_FileName 处执行,继续读取字符与文件名的下一个字符比较;

🚩Label_Different:

  • 第二十八行:用当前字符地址 di 按位与上 0xffe0,并将结果保存到 di 中,从而获取当前目录项的起始地址;
  • 第二十九行:为 di 加上 0x20 (32),使得 di 指向下一个目录项的起始地址;
  • 第三十行:将 LoaderFileName 的内容保存到 si 寄存器中,LoaderFileName 是定义的一个字符串,值为 “IMLOADERBIN”;(重置 si
  • 第三十一行:直接跳转至 Label_Search_For_LoaderBin 处执行,准备读取下一个目录项;

🚩Label_Goto_Next_Sector_In_Root_Dir:

  • 第三十二行:为临时变量 SectorNo1,扇区索引号加 1,表示切换到下一个扇区;
  • 第三十三行:直接跳转至 Label_Search_In_Root_Dir_Begin 处执行。

5. 提示目标文件不存在功能

当程序并没有找到

Loader

文件时,我们应该让它提示我们点什么,不然我们并不知道这时候在发生些什么,因此需要实现一个能够在没有找到目标文件时提示我们的功能。

5.1. 代码实现

具体代码如下:

;====== display on screen : ERROR: Loader not found
Label_No_LoaderBin:
mov             ax,0x1301
mov             bx,0x008c
mov             dx,0x100
mov             cx,24
push    ax
mov             ax, ds
mov             es, ax
pop             ax
mov             bp, NoLoaderMessage
int0x10
jmp             $

5.2. INT 10h,AH = 13h 中断服务

这段代码使用了

BIOS

中的

INT 10h

,主功能号

AH = 13h

的中断服务,其配置详情在之前的文章中也有提到过,不过相信各位也不想翻过去看吧,这里再次贴出来(你说你记住了?我不信!🙃)
中断服务主功能号

AH

值功能描述

AL

功能

BH

功能

BL

功能

CH

功能

CL

功能

DH

功能

DL

功能

ES:BP
INT 10h
13h

显示一行字符串写入模式:

00h

: 字符串属性由

BL

控制,长度由

CX

控制(单位: Byte),不改变光标位置;

01h

: 同上,但显示结束更新光标到字符串结尾处;

02h

: 字符串由其最后的字节控制,

CX

控制单位变为

Word

,不改变光标位置;

03h

: 同

02h

,但更新光标位置到字符串结尾页码字符属性和颜色属性:

  • bit 0~2:字体颜色(0:黑1:蓝2:绿3:青4:红5:紫6:棕7:白)
  • bit 3:字体亮度(0:默认1:高亮)
  • bit 4~6:背景颜色(颜色的数值同上)
  • bit 7:字体闪烁(0:关闭1:使能)字符串长度字符串长度光标所在行号光标所在列号要显示字符串的内存地址

    5.3. 代码详解

这段代码只使用了中断功能,而且只打印

NoLoaderMessage

代表的字符串而已,所以很简单。
🚩Label_No_LoaderBin:

  • 第一行:为 ax 赋值为 0x1301;(即 ah = 13hal = 01hal 功能参考上表)
  • 第二行:为 bx 赋值为 0x008c;(即 bh = 00hbl = 8chbl 控制的属性参考上表,算了直接解释吧,bl = 1000_1100b,即,bit 7 = 1,bit 3 = 1,bit 0~2 = 100b = 4,故字符串属性为,字符高亮且闪烁,颜色为红色)
  • 第三行:为 dx 赋值为 0x0100;(即 dh = 01hdl = 00hdx 控制光标的行列,这里表示第 1 行,第 0 列)[注]:行和列的起始号均为 0。
  • 第四行:为 cx 赋值为 24;(表示字符串长度)
  • 第五行:将 ax 寄存器压栈保存;
  • 第六行:将 ds 的值保存到 ax 中;
  • 第七行:将 ax 的值保存到 es 中;(用 ds 初始化 es,用于数据的访存)
  • 第八行:pop 出栈,出栈的数据保存到 ax 中;
  • 第九行:将 NoLoaderMessage 字符串地址保存到 bp 寄存器中;(中断会显示 es:bp 保存的内容)
  • 第十行:开启中断 INT 10h
  • 第十一行:跳转到当前指令地址执行——死循环。

6. FAT 表项解析功能

当匹配到

Loader

程序后,从根目录项中只能获取文件存储的起始簇号,而后续内容需要根据

FAT

表中对应的表项查询,并一次将文件在数据区的存储扇区加载到内存中,这里便涉及到

FAT

表项的解析工作,那么需要我们添加一个可以用来解析

FAT

表项的功能模块。

6.1. 代码实现

具体代码如下:

Func_GetFATEntry:
push                es
push            bx
push            ax
mov             ax,00
mov             es, ax
pop             ax
mov             byte    [Odd],0
mov             bx,3
mul             bx
mov             bx,2
div             bx
cmp             dx,0
jz              Label_Even
mov             byte    [Odd],1

Label_Even:
xor             dx, dx
mov             bx,[BPB_BytesPerSec]
div             bx
push            dx
mov             bx,0x8000
add             ax, SectorNumOfFAT1Start
mov             cl,2
call            Func_ReadOneSector

pop             dx
add             bx, dx
mov             ax,[es:bx]
cmp             byte    [Odd],1
jnz             Label_Even_2
shr             ax,4

Label_Even_2:
and             ax,0x0fff
pop             bx
pop             es
ret
Func_GetFATEntry

模块的功能是根据当前

FAT

表项的索引号找出下一个

FAT

表项,调用该模块时需要使用

ax

寄存器作为参数输入提供给该模块,

ax

保存当前的

FAT

表项号。

6.2. MUL 和 DIV 指令

mul

是乘法指令,必须要求做乘法的两个数位宽必须相同,即要么都是

8 bit

,要么都是

16 bit

其中一个默认的操作数被保存在

ax

寄存器中,若选择做

8 bit

的乘法则保存到

al

中即可。
使用方式:

mov     ax,6
mov        bx,2
mul        bx

如上示例则表示

ax * bx == 6 * 2

。操作的结果默认选择高

16

位保存到

dx

中,低

16

位保存在

ax

中。

div

是除法指令。必须要求做除法的两个数位宽必须相同,即要么都是

8 bit

,要么都是

16 bit

。这一点同

mul

一样。

其中被除数默认保存在

ax

中(若超过

16 bit

,则高

16 bit

保存在

dx

,低

16 bit

放在

ax

中。

当除数为

8 bit

时,计算结果的商将保存到

al

中,余数保存到

ah

中。

当除数为

16 bit

时,计算结果的商保存在

ax

中,余数保存到

dx

中。

使用格式:

mov         ax,7
mov         bx,2
div         bx

上述示例表示

ax ÷ bx = 7 ÷ 2 = 3.5

ax == 3

dx == 1

。(

ax

是表示

16 bit

的数)

6.3. XOR 指令

xor

是异或指令。使用格式如下:

xor        dest, src

其功能是将

dest

src

按位做逻辑异或操作,并将结果保存到

dest

中。异或的基本运算如下:(异或符号:⊕)
xyx⊕y000011101110
或者也可以更简单的理解为,相同异或则为

0

,不同异或则为

1

6.4. FAT12 的 FAT 表项的读取规律

通过笔者的上一篇文章《FAT12 文件系统》我们已经很清楚的了解到

FAT12

文件系统的结构了,其中

FAT

表的每个表项由一个完整的字节和另一个字节的一半组成。我们再来看一下上篇文章中的这个图,复习一下如何查看

FAT12

文件系统中的表项吧。

请添加图片描述

相信大家看到就会想起表项如何组合了吧,如果还是没想起来,那再翻回去再看一下,本文就不再赘述。

在这里插入图片描述
这张图是一个

FAT12

文件结构打开后的样子,直接跳转至

FAT1

表的位置。这里笔者将其中前

5

个表项画了出来,由于目录项存储的

FAT

表项的有效起始号为

2

,因此我们可以不用关心

0

1

号表项。可以很清楚的看到每一个表项是由两个字节组成的(需要经过变换)。

那么我们可以使用一个

16 bit

的寄存器一次读入两个字节的数据,再进行处理,现在用

2

号表项和

3

号表项作为示例。

先不考虑如何找到表项的起始地址,这里我们直接将组成

2

号表项的

03h 40h

读入

ax

寄存器中,由于

Inter

采用的是大端存储,因此读入到

ax

寄存器后,

ax

保存的值将会是

4003h

。我们只需要**将

ax

按位逻辑与上

0FFFh

,就能得到我们需要的

FAT[2]

**。

再来看

3

号表项,将组成

3

号表项的

40h 00h

读入

ax

寄存器中,因为大端存储的缘故,

ax

中保存的值将会是

0040h

,我们只需要**将

ax

右移

4

位,则得到了

FAT[3]

**。

其实你发现了,原来这还是有规律的,**偶数表项依照

FAT[2]

的处理方法,而奇数项依照

FAT[3]

的处理方法**,我们将轻而易举的获取到对应表项的值。

6.5. 代码详解

这段代码没有涉及到特别多的知识,因此这里就直接解释代码含义。

**首先明确一点,该模块需要

ax

做参数传入,

ax

保存当前

FAT

表项的索引号。**

🚩Func_GetFATEntry:

  • 第一、二、三行:分别将 esbxax 保存的值压入栈中存储;
  • 第四行:将 ax 保存的值清零;
  • 第五行:用 ax 寄存器初始化 es 段寄存器;
  • 第六行:pop 将一个数据弹出栈,并将其保存到 ax 中;(栈中数据遵循先进后出,这时 ax 保存的值正是压栈之前 ax 的值)
  • 第七行:将 Odd 变量赋值为 0;(Odd 为定义的一个变量,初始值为 0
  • 第八行:为 bx 寄存器赋值为 3
  • 第九行:将 ax 的值乘上 bx,并将结果分别保存到 dx(高 16 bit) 和 ax(低 16 bit) 中。
  • 第十行:为 bx 寄存器赋值为 2
  • 第十一行:将 ax 的值除上 bx 的值,ax 保存除法的 dx 保存除法的余数;(此时 ax 保存的是当前表项在 FAT 表中的字节偏移)
  • 第十二行:用 cmp 比较 dx0 的大小;(当 dx == 0 时将会将 ZF 置位,即 ZF = 1
  • 第十三行:为临时变量 Odd 变量赋值为 1

我们都清楚了在开始时

ax

保存的是当前

FAT

表项的索引号,经过上述步骤将

ax * 3 / 2

,在这里解释一下为什么需要先乘上

3

再除上

2

首先我们知道的是每个表项长度为

12 bit

,也就是

1.5

个字节,若要计算当前表项的地址偏移那么需要知道从

FAT

表开始到当前表项的长度有多少,也就是说用当前表项索引 x 每个表项长度 = 当前表项长度偏移 ,而每个表项长度为

1.5

,那么给表项先乘上

3

再除以

2

,事实上相当于给当前表项乘以

1.5

,目的为了计算当前表项的长度偏移。

其中

Odd

是定义的一个变量,用于标志当前表项是索引的奇偶性,初始值为

0
  • 偶数表项:Odd0
  • 奇数表项:Odd1

🚩Label_Even:

  • 第十四行:dxdx 自己做异或操作,等价与清零操作;(dx = 0
  • 第十五行:将 BPB_BytesPerSec 的值赋值给 bxBPB_BytesPerSec 表示每个扇区的大小 512 B
  • 第十六行:用 ax 除以 bx,即用 当前表项的字节偏移 ÷ 每个扇区大小 = 当前表项的扇区偏移ax 此时将会保存 当前表项的扇区偏移dx 保存计算的余数,即保存当前表项所在 扇区的字节偏移
  • 第十七行:将 dx 压栈保存;
  • 第十八行:将 0x8000 赋值给 bx,以待 Func_ReadOneSector 函数中的 INT 10h,AH = 02h 使用,表示读取的扇区保存到的地址;
  • 第十九行:为 ax 加上 SectorNumOfFAT1Start,即用当前表项的 扇区号偏移 + FAT 表起始扇区号 = 当前表项的起始扇区号,将结果赋值给 ax,以待 Func_ReadOneSector 函数使用,表示要读入的扇区起始号;
  • 第二十行:为 cl 赋值为 2,以待 Func_ReadOneSector 函数使用,表示要读入的扇区数量;
  • 第二十一行:调用 Func_ReadOneSector
  • 第二十二行:弹出栈中数据保存到 dx 中,dx 此时保存 当前表项在所在扇区的字节偏移
  • 第二十三行:为 bx 加上 dxbx 此时保存的当前表项所在扇区起始字节一直到下一个扇区的内容,加上 dx``````表项在当前扇区的字节偏移,即得到了当前表项的具体地址,将其保存在 bx 中;
  • 第二十四行:读取 es:bxax 中,读取长度为 16 bit,正好读取到组成当前表项的两个字节内容;
  • 第二十五行:比较 Odd1 是否相同;
  • 第二十六行:若不相同则跳转到 Label_Even_2 处执行,若相同则继续;
  • 第二十七行:此行只会在 Odd = 1 时即表示表项为奇数项时执行,将 ax 右移 4 位;
  • 第二十八行:ax 按位逻辑与上 0x0FFF,则得到了 12 bit 的表项;
  • 第二十九、三十行:将之前保存的 bxes 值弹出栈再保存回 bxes中。
  • 第三十一行:执行 ret恢复栈指针回到主调指令处执行。

这里需要读入

两个扇区

,这是为了当一个表项横跨两个扇区时,依然能够读取完整,即当这个表项由一个扇区的最后一个字节和下一个扇区的第一个字节组成。

7. 加载 imLoader.bin 文件到内存模块

我们已经实现了 “读取软盘扇区功能” 和 “解析 FAT 表项功能”,接下来则可以利用这两个模块实现将

imLoader.bin

文件的数据从软盘扇区读取并保存到指定地址处,完成文件加载到内存的过程。

7.1. 代码实现

来看具体代码:

Label_FileName_Found:
mov             ax, RootDirSectors
and             di,0x0ffe0
add             di,0x01a
mov             cx, word        [es:di]
push            cx
add             cx, SectorBalance
add             cx, ax
mov             ax, BaseOfLoader
mov             es, ax
mov             bx, OffsetOfLoader
mov             ax, cx

Label_Go_On_Loading_File:
push            ax
push            bx
mov             ah,0x0e
mov             al,'.'
mov             bl,0x0fint0x10
pop             bx
pop             ax

mov             cl,1
call            Func_ReadOneSector
pop             ax
call            Func_GetFATEntry
cmp             ax,0x0fff
jz              Label_File_Loaded
push            ax
mov             dx, RootDirSectors
add             ax, SectorBalance
add             ax, dx
add             bx,[BPB_BytesPerSec]
jmp             Label_Go_On_Loading_File

Label_File_Loaded:
jmp             $

总的来讲,这个模块会根据根目录项中和

FAT

表项从而索引到数据区保存到

imLoader.bin

文件扇区,并将扇区按顺序以扇区为单位的读入到指定地址处。

7.2. INT 10h,AH = 0Eh 中断服务

在这段代码中使用了

INT 10h,AH = 0Eh

中断服务,该中断的功能是在屏幕上显示一个字符。具体参数说明如下:
中断服务主功能号

AH

值功能描述

AL

功能

BL

功能

INT 10h
0Eh

显示一个字符待显示的字符前景色

7.3. 代码详解

需要注意的一点是,此模块是由 “目标文件搜索模块” 调用,只有当在目录项中匹配到

imLoader.bin

这个文件名时才会跳转到此处执行。

; 目标文件搜索模块部分代码
Label_Cmp_FileName:
cmp             cx,0
jz              Label_FileName_Found
dec             cx
lodsb
cmp             al, byte        [es:di]
jz              Label_Go_On
jmp             Label_Different

Label_Go_On:
inc             di
jmp             Label_Cmp_FileName

可以看到此处

di

寄存器一直存储着目录项中文件名的字符地址,倘若匹配成功,跳转入

Label_FileName_Found

也就是当前正在解释的模块,会将文件加载到内存。注意!**此时的

di

寄存器将保存的是目录项中文件名的最后一个字节的地址,由于目录项文件名长度固定为

11 bit

,则此时的

di

相当于目录项起始地址向后偏移

11 bit

的地址。**

由于此模块中代码涉及栈操作较多,为了避免读者们搞混,便于大家理解,这里笔者将会在栈环境改变时以图示方式表述当时栈环境。

🚩Label_FileName_Found:

  • 第一行:将 RootDirSectors 的值赋值给 axRootDirSectors 表示根目录部分的扇区总数,值为 14
  • 第二行:将 di 按位逻辑与上 0xFFE0di 原本保存的目录项中文件名最后一个字节的地址,此步骤计算后 di 将会得到目录项的起始地址;
  • 第三行:为 di 加上 0x01A,即加上 26,将得到目录项保存的文件起始簇的起始地址;[注]:起始第二行和第三行完全可以用一步替代,直接 mov di, 0x00F0 替代上面两行完全是等价的。不过上面的更直观一些。
  • 第四行:读取 es:di 地址处的数据,保存到 cx,读取长度 16 bit,此处数据为文件起始簇号;
  • 第五行:将 cx 的值压栈保存,以待之后使用;此时栈环境变化如下:

在这里插入图片描述

cx

此时保存的是文件的起始簇号。

  • 第六行:为 cx 加上 SectorBalance,为了得到正确的扇区偏移,这里并不用真正的根目录起始扇区号 SectorNumOfRootDirStart = 19 做为操作数,而是使用选用 SectorBalance = 17,这个原因在上文已经解释过了,是因为根目录项的文件起始簇号 2 ,对应的是数据区的第一个扇区;
  • 第七行:再为 cx 加上 ax 保存的根目录扇区总数,则得到了文件在整个 FAT12 文件结构中的扇区号;
  • 第八行:将 BaseOfLoader 需要加载文件的基地址赋值给 ax
  • 第九行:将 ax 的值赋值给段寄存器 es,由于段寄存器不能直接用内存赋值只能用寄存器赋值;
  • 第十行:将 OffsetOfLoader 加载文件的偏移地址赋值给 bx,稍后会提供给 Func_ReadOneSector 使用;
  • 第十一行:将 cx 保存的文件起始扇区号赋值给 ax,稍后会提供给 Func_ReadOneSector 使用;

🚩Label_Go_On_Loading_File:

  • 第十二、十三行:将 axbx 压栈保存;此时栈环境的变化如下:

在这里插入图片描述

  • 第十四行:将 0x0E 赋值给 ah 寄存器,准备使用中断的 0Eh 主功能号;
  • 第十五行:将待显示的字符 '.' 保存到 al 寄存器中;
  • 第十六行:将 0x0F 赋值给 bl
  • 第十七行:开启中断 int 10h,将调用中断服务打印字符到屏幕上;
  • 第十八、十九行;分别将栈中元素弹出栈,并分别保存到 bxax中;此时栈环境的变化如下:

在这里插入图片描述

  • 第二十行:将 1 赋值给 cl 寄存器,准备调用 Func_ReadOneSector,表示读入 1 个扇区;
  • 第二十一行:调用 Func_ReadOneSector,读取一个扇区内容到 BaseOfLoader : OffsetOfLoader 地址处;
  • 第二十二行:将栈中元素弹出栈,并保存到 ax 中,此时栈环境变化如下:

在这里插入图片描述

  • 第二十三行:调用 Func_GetFATEntry,读取 ax 保存的文件簇号对应的 FAT 表项值,结果保存到 ax 中;
  • 第二十四行:比较读取的 FAT 表项值 ax 是否为 0x0FFF,即检查文件是否结束;
  • 第二十五行:如果文件结束,则跳转到 Label_File_Loaded 处执行;
  • 第二十六行:将 ax 压栈保存,此时栈环境变化如下:

在这里插入图片描述

  • 第二十七行:将根目录扇区总数 RootDirSectors = 14 赋值给 dx
  • 第二十八行:为 ax 加上用来计算使用的 “根目录起始扇区号” SectorBalance
  • 第二十九行:为 ax 加上保存根目录扇区总数的 dx,则得到了指向文件下一个存储扇区位置的 ax
  • 第三十行:为 bx 加上每扇区大小 BPB_BytesPerSec,让待加载地址向后移动一个扇区,准备加载文件的下一个扇区数据;
  • 第三十一行:跳回到 Label_Go_On_Loading_File 处执行,再加载文件的一个扇区到指定地址;

🚩Label_File_Loaded:
第三十二行:跳转到当前指令执行——死循环。

这行指令原本应该表示

imLoader.bin

已经加载完成,准备跳入加载地址执行,但暂时我们先不这样做,我们先让它死循环,因为我们的

imLoader.bin

还没有实现,因此先等等。

8. imboot 程序的其它变量的定义以及写入引导扇区结尾标志

在上文中使用了诸多并没有见过的临时变量和字符串如:

SectorNo

Odd

NoLoaderMessage

等等,都是在文件最后定义的,具体代码如下:

;====== tmp variable
RootDirSizeForLoop              dw      RootDirSectors
SectorNo                        dw      0
Odd                             db      0;====== display message
IMBootMessage:                  dd      "The imboot is working!"
NoLoaderMessage:                db      "ERROR: Loader not found."
LoaderFileName:                 db      "IMLOADERBIN",0; 这里的 0 表示该字符串以 0 结尾,'\0'==0。

times 510-($ - $$)             db 0
dw         0xaa55; BIOS会检测软盘的第 0 磁头第 1 扇区最后两个字节是否为 0x55aa(代码中写成 0xaa55 是由于x86 使用小端存储) ,以确定这个扇区是否是引导扇区

千万不能忘记在整个引导扇区结尾处需要以

0x55AA

作为结束标志,否则

BIOS

将不能识别出该扇区是引导扇区。

9. 目前为止的完整程序

为了大家实验方便,笔者贴出到目前为止的完整代码如下:

org 0x7c00; 定义栈指针
BaseOfStack             equ     0x7c00

BaseOfLoader            equ     0x1000
OffsetOfLoader          equ     0x00; 与 BaseOfLoader 组合成为 Loader 程序的物理地址
; BaseOfLoader <<4+ OffsetOfLoader =0x10000

RootDirSectors                  equ     14; 根目录扇区
SectorNumOfRootDirStart         equ     19; 根目录起始扇区号
SectorNumOfFAT1Start            equ     1; FAT1 表起始扇区号
SectorBalance                   equ 17; 用于平衡文件或目录的起始簇号与数据区起始簇号的差值

; BS_JmpBoot
jmp     short Label_Boot_Start
nop

BS_OEMName              db      'IMboot  '
BPB_BytesPerSec         dw      512
BPB_SecPerClus          db      1
BPB_RsvdSecCnt          dw      1
BPB_NumFATs             db      2
BPB_RootEntCnt          dw      224; RootDirSectors *512/32=224
BPB_TotSec16            dw      2880;1440*1024/512=2880(fp:1.44MB)
BPB_Media               db      0xf0
BPB_FATSz16             dw      9
BPB_SecPerTrk           dw      18
BPB_NumHeads            dw      2
BPB_HiddSec             dd      0
BPB_TotSec32            dd      0
BS_DrvNum               db      0
BS_Reserved1            db      0
BS_BootSig              db      0x29
BS_VolID                dd      0
BS_VolLab               db      'boot loader'; 必须 11bit,不足用空格补齐
BS_FileSysType          db      'FAT12   '; 必须 8bit,不足用空格补齐

Label_Boot_Start:

mov             ax, cs  ; 将代码寄存器 cs 的段基地址设置到 ds、es、ss寄存器
mov             ds, ax
mov             es, ax
mov             ss, ax
mov             sp, BaseOfStack

; 清屏

mov             ax,0600h
mov             bx,0700h
mov             cx,0h
mov             dx,0184fh
int10h

; 设置光标位置

mov             ax,0200h
mov             bx,0000h
mov             dx,0000h
int10h

; 在屏幕上打印字符串 "The imboot is working!"

mov             ax,1301h
mov                 bx,000fh
mov             dx,0000h
mov             cx,22
push            ax
mov             ax, ds
mov             es, ax
pop             ax
mov             bp, IMBootMessage
int10h

; 软盘驱动器复位

xor             ah, ah
xor             dl, dl
int13h

;jmp $
jmp             Label_Imboot_Begin

; 填充 0 到结尾前 2 个字节

; 读取磁盘扇区模块(函数),每次读取一个扇区
Func_ReadOneSector:
push            bp
mov             bp, sp
sub             esp,2
mov             byte    [bp -2], cl
push            bx
mov             bl,[BPB_SecPerTrk]
div             bl                                      ; 默认被除数保存在 ax 中,ax/bl 商保存在 al,余数保存在 ah 中
inc             ah                                      ; inc 加一指令
mov             cl, ah
mov             dh, al
shr             al,1; shr 逻辑右移
mov             ch, al
and             dh,1
pop             bx
mov             dl,[BS_DrvNum]
Label_Go_On_Reading:
mov             ah,2
mov             al, byte        [bp -2]int13h
jc              Label_Go_On_Reading             ; 检测 CF =1 时发生跳转,当中断服务程序读取完成后会将 CF 置为 0
add             esp,2
pop             bp
ret

;====== 查找 imloader.bin 文件
Label_Imboot_Begin:
mov             word    [SectorNo], SectorNumOfRootDirStart ; SectorNumOfRootDirStart ==19

Label_Search_In_Root_Dir_Begin:
cmp             word    [RootDirSizeForLoop],0; RootDirSizeForLoop == RootDirSectors ==14
jz              Label_No_LoaderBin
dec             word    [RootDirSizeForLoop]; RootDirSizeForLoop 自减
mov             ax,0x00
mov             es, ax
mov             bx,0x8000
mov             ax,[SectorNo]
mov             cl,1
call            Func_ReadOneSector
mov             si, LoaderFileName
mov             di,0x8000
cld
mov             dx,0x10

Label_Search_For_LoaderBin:
cmp             dx,0
jz              Label_Goto_Next_Sector_In_Root_Dir
dec             dx
mov             cx,11

Label_Cmp_FileName:
cmp             cx,0
jz              Label_FileName_Found
dec             cx
lodsb
cmp             al, byte        [es:di]
jz              Label_Go_On
jmp             Label_Different

Label_Go_On:
inc             di
jmp             Label_Cmp_FileName

Label_Different:
and             di,0x0ffe0
add             di,0x20
mov             si, LoaderFileName
jmp             Label_Search_For_LoaderBin

Label_Goto_Next_Sector_In_Root_Dir:
add             word    [SectorNo],1
jmp             Label_Search_In_Root_Dir_Begin

;====== display on screen : ERROR: Loader not found
Label_No_LoaderBin:
mov             ax,0x1301
mov             bx,0x008c
mov             dx,0x100
mov             cx,24
push            ax
mov             ax, ds
mov             es, ax
pop             ax
mov             bp, NoLoaderMessage
int0x10
jmp             $

;====== get FAT Entry
Func_GetFATEntry:
push            es
push            bx
push            ax
mov             ax,00
mov             es, ax
pop             ax
mov             byte    [Odd],0
mov             bx,3
mul             bx
mov             bx,2
div             bx
cmp             dx,0
jz              Label_Even
mov             byte    [Odd],1

Label_Even:
xor             dx, dx
mov             bx,[BPB_BytesPerSec]
div             bx
push            dx
mov             bx,0x8000
add             ax, SectorNumOfFAT1Start
mov             cl,2
call            Func_ReadOneSector

pop             dx
add             bx, dx
mov             ax,[es:bx]
cmp             byte    [Odd],1
jnz             Label_Even_2
shr             ax,4

Label_Even_2:
and             ax,0x0fff
pop             bx
pop             es
ret

;====== found imloader.bin name in root director structLabel_FileName_Found:
mov             ax, RootDirSectors
and             di,0x0ffe0
add             di,0x01a
mov             cx, word        [es:di]
push            cx
add             cx, SectorBalance
add             cx, ax
mov             ax, BaseOfLoader
mov             es, ax
mov             bx, OffsetOfLoader
mov             ax, cx

Label_Go_On_Loading_File:
push            ax
push            bx
mov             ah,0x0e
mov             al,'.'
mov             bl,0x0fint0x10
pop             bx
pop             ax

mov             cl,1
call            Func_ReadOneSector
pop             ax
call            Func_GetFATEntry
cmp             ax,0x0fff
jz              Label_File_Loaded
push            ax
mov             dx, RootDirSectors
add             ax, SectorBalance
add             ax, dx
add             bx,[BPB_BytesPerSec]
jmp             Label_Go_On_Loading_File

Label_File_Loaded:;jmp    $
jmp             BaseOfLoader:OffsetOfLoader

;====== tmp variable
RootDirSizeForLoop      dw      RootDirSectors
SectorNo                dw      0
Odd                     db      0;====== display message
IMBootMessage:          dd      "The imboot is working!"
NoLoaderMessage:        db      "ERROR: Loader not found."
LoaderFileName:         db      "IMLOADERBIN",0; 这里的 0 表示该字符串以 0 结尾,'\0'==0。

times 510-($ - $$)     db         0
dw         0xaa55; BIOS会检测软盘的第 0 磁头第 1 扇区最后两个字节是否为 0x55aa(代码中写成 0xaa55 是由于x86 使用小端存储) ,以确定这个扇区是否是引导扇区

10. 测试无 imLoader 程序的效果

到目前为止,我们所完成的代码是可以运行的,其结果就是没有检测到

imLoader.bin

文件会输出我们定义好的错误信息。

首先将编写好的代码编译:

[imaginemiracle@imos-ws imboot-v0.2]$ nasm imboot-v0.2.S -o imboot-v0.2.bin

使用

bximage

创建一个新的软盘,这里笔者命名为

imboot-v0.2.img

,创建软盘步骤参考上一篇文章的第三小节《制作软盘镜像文件》。

将编译生成的

imboot-v0.2.bin

写入新创建的软盘镜像

imboot-v0.2.img

[imaginemiracle@imos-ws imboot]$ ddif=./imboot-v0.2/imboot-v0.2.bin of=../img/imboot-v0.2.img bs=512count=1conv=notrunc
[注]:这里的文件路径根据自己文件的路径填写。

修改

boch

的配置文件

bochsrc

,将其中

floppya

项中的软盘路径修改为新创建的软盘的绝对路径:
在这里插入图片描述
运行

boch

,选择默认的

6
[imaginemiracle@imos-ws bochs-run]$ bochs -f bochsrc

等待黑色窗口出现后,在命令行输入

c

并回车,让其加载并运行软盘镜像。

在这里插入图片描述

这个时候便会看到我们的

boot

程序正常在运行,不过并没有找到与

imLoader.bin

文件名匹配的目录项,并打印如下的红色闪烁提示信息,警告我们。

请添加图片描述

11. 编写 imLoader 程序

猜测大家应该和笔者一样累了吧,你们看了这么多字,我敲了这么多字,坚持啊!🌠接下来的任务就很简单了。

11.1. imLoader 完整代码

这里的

Loader

代码很简单,几乎和上一篇文章中的代码相同,同样使用

INT 10h,AH = 13h

中断服务,将一个字符串输出到屏幕上,然后根据

ax

bx

cx

dx

的控制输出位置和字符属性。

org             10000h

mov             ax, cs
mov             ds, ax
mov             es, ax
mov             ax,0x00
mov             ss, ax
mov             sp,0x7c00;====== 利用中断服务在屏幕上输出 "imLoader is running... (^v^)" 的字样

mov             ax,1301h
mov             bx,000fh
mov             dx,0200h               ; 设置光标在第 2 行
mov             cx,28
push            ax
mov             ax, ds
mov             es, ax
pop             ax
mov             bp, ImLoaderRunning
int10h

jmp             $

;====== imLoaderRunning

ImLoaderRunning:        db      "imLoader is running... (^v^)"

这段代码就不再做过多的解释了,相信经过这么多学习,以大家聪明的头脑一定没问题的(如果还是有问题的话,欢迎在文章下留言提问,也更加欢迎其他读者来做解答)。

11.2. 将 imLoader 程序保存到 imboot-v0.2.img 软盘镜像中

首先将编写好的代码进行编译:

[imaginemiracle@imos-ws imLoader]$ nasm imLoader.S -o imLoader.bin

由于我们之前使用

dd

命令将

FAT12

文件系统结构的

imboot.bin

写入到

imboot-v0.2.img

软盘镜像中,此过程已经将软盘镜像格式化为

FAT12

类型,因此这里就可以直接使用

mount

命令将其挂载到指定目录访问了。

首先创建一个目录用于挂载使用

[imaginemiracle@imos-ws img]$ mkdir imboot_fat

imboot-v0.2.img

软盘镜像挂载到新建目录:

[imaginemiracle@imos-ws img]$ sudomount imboot-v0.2.img imboot_fat/ -t vfat -o loop
[imaginemiracle@imos-ws img]$ cd imboot_fat/

参数解释:

-t vfat

指定挂载的文件系统类型,

-o loop

将文件描述为磁盘分区。

将编译好的

imLoader.bin

文件拷贝到挂载目录:

[imaginemiracle@imos-ws imboot_fat]$ sudocp../../imLoader/imLoader.bin ./

执行

sync

命令强制同步磁盘,并取消挂载:

[imaginemiracle@imos-ws imboot_fat]$ sync[imaginemiracle@imos-ws imboot_fat]$ cd..[imaginemiracle@imos-ws img]$ sudoumount imboot_fat/

11.3. 挂载时可能会遇到的错误

若挂载时遇到这样的错误,则表示系统不能识别当前的文件系统类型。

mount: ****: wrong fs type, bad option, bad superblock on /dev/loop0, missing codepage or helper program, or other error.

这个时候应该是你修改过代码开头部分的某些指定字符串内容,若是这样,一定要按要求设定,每个名字必须符合

FAT12

每一项指定的长度,若不够必须使用空格填充,若超过请删除至合规长度,否则系统将无法识别到该文件系统类型。

12. 修改 imboot 程序并写入 imboot-v0.2.img 软盘镜像

如上已经将

imLoader

程序完成了,不过还不要着急去运行,还有一件事情没做,还记得在

boot

代码中加载完

imLoader

后要跳转到

imLoader

的加载地址去执行,而当时还没有

Loader

程序暂时在那块写的是

jmp $

死循环,这时候我们需要修改过来了。

12.1. 需要修改的代码部分

Label_File_Loaded

中的跳转指令跳转到

imLoader

的加载地址即可。

Label_File_Loaded:;jmp    $
jmp             BaseOfLoader:OffsetOfLoader

这样写的寻址方式为

BaseOfLoader << 4 + OffsetOfLoader

,即

0x1000 << 4 + 0x00 == 0x10000

12.2 编译修改后的文件

使用

nasm

编译

imboot-v0.2.S

文件。

[imaginemiracle@imos-ws imboot-v0.2]$ nasm imboot-v0.2.S -o imboot-v0.2.bin

12.3. 写入镜像

将编译生成的二进制文件写入镜像。

[imaginemiracle@imos-ws imboot-v0.2]$ ddif=imboot-v0.2.bin of=../../img/imboot-v0.2.img bs=512count=1conv=notrunc

13. 运行 bochs

因为之前已经修改过配置文件了,这里直接运行

bochs

,运行方法就不再讲了,直接看结果吧!(看到微笑就表示成功了哦!)

在这里插入图片描述
怎么说呢,经过这么多努力深入了解了

bootloader

的工作流程,并成功按照标准的流程完成自己的

bootloader

程序,此刻的心情应该是向上图中的表情一样吧,面带微笑。不过一切才刚刚开始~

#本文完

觉得这篇文章对你有帮助的话,就留下一个赞吧^v^*
请尊重作者,转载还请注明出处!感谢配合~
[作者]: Imagine Miracle
[版权]: 本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
[本文链接]: https://blog.csdn.net/qq_36393978/article/details/126369492


本文转载自: https://blog.csdn.net/qq_36393978/article/details/126369492
版权归原作者 Imagine Miracle 所有, 如有侵权,请联系我们删除。

“使用 FAT12 文件系统实现简单的 Boot 加载 Loader 到内存”的评论:

还没有评论