2025-03-26  2025-04-07    7020 字  15 分钟
AOS
  • 文件: fat32.inc

幸运的是我们不用自己编写这个文件, 不幸的是我们需要编写生成这个文件的程序

我们需要先做些准备, 方便未来的开发

在后续中, 我会使用/dev/sda代指磁盘或磁盘镜像, 如果你使用的是U盘, 那么我建议你修改可以临时修改磁盘的权限, 使得所有用户都可以读写磁盘 sudo chmod 666 /dev/sda

同时我为hexdump命令创建了一个alias, 方便查看磁盘数据

1alias hexdump='echo "---------- 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F -------------------" && hexdump -C'

例如我格式化这个磁盘为FAT32分区, 并查看前512字节, 可以得到如下的结果

 1# akvicor @ office in ~ [0:43:45] 
 2$ sudo mkfs.vfat /dev/sda
 3mkfs.fat 4.2 (2021-01-31)
 4
 5# akvicor @ office in ~ [0:45:01] 
 6$ hexdump -n 512 /dev/sda
 7---------- 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F -------------------
 800000000  eb 58 90 6d 6b 66 73 2e  66 61 74 00 02 20 20 00  |.X.mkfs.fat..  .|
 900000010  02 00 00 00 00 f8 00 00  20 00 40 00 00 00 00 00  |........ .@.....|
1000000020  e0 51 b8 03 80 3b 00 00  00 00 00 00 02 00 00 00  |.Q...;..........|
1100000030  01 00 06 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
1200000040  80 00 29 3d ec 7d d6 4e  4f 20 4e 41 4d 45 20 20  |..)=.}.NO NAME  |
1300000050  20 20 46 41 54 33 32 20  20 20 0e 1f be 77 7c ac  |  FAT32   ...w|.|
1400000060  22 c0 74 0b 56 b4 0e bb  07 00 cd 10 5e eb f0 32  |".t.V.......^..2|
1500000070  e4 cd 16 cd 19 eb fe 54  68 69 73 20 69 73 20 6e  |.......This is n|
1600000080  6f 74 20 61 20 62 6f 6f  74 61 62 6c 65 20 64 69  |ot a bootable di|
1700000090  73 6b 2e 20 20 50 6c 65  61 73 65 20 69 6e 73 65  |sk.  Please inse|
18000000a0  72 74 20 61 20 62 6f 6f  74 61 62 6c 65 20 66 6c  |rt a bootable fl|
19000000b0  6f 70 70 79 20 61 6e 64  0d 0a 70 72 65 73 73 20  |oppy and..press |
20000000c0  61 6e 79 20 6b 65 79 20  74 6f 20 74 72 79 20 61  |any key to try a|
21000000d0  67 61 69 6e 20 2e 2e 2e  20 0d 0a 00 00 00 00 00  |gain ... .......|
22000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
23*
24000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
2500000200

或许你看到这些会觉得看不懂, 但没关系, 后续会展开介绍

最开始的jmp

首先看文件开头 eb, 这是jmp指令的机器码, jmp指令表示cpu接下来要跳转到另一个地址继续执行. 我们看下面这个例子

boot.asm中, 我们让汇编编译器将这段代码的起始地址设置在0x7c00, 然后编写了一行jmp指令跳转到0x7c04

可以看到编译后的结果是eb 02, eb代表jmp的机器码, 02代表跳转偏移, 那么为什么是02呢?

首先因为我们将这段代码的起始地址设置在0x7c00, 那么jmp这条指令的地址就是0x7c00, 而eb 02(也就是jmp short 0x7c04自身)占2个字节, 那么0x7c04-0x7c00-2 = 2, 因此偏移就是2

这么设计的原理涉及到cpu的运行逻辑, 当cpu获取到这条指令后, 会将ip寄存器调整为下以条指令的地址, 编译器知道这条指令编译后多长, 自然知道下一条指令的起始地址, 在本实例中就是0x7c02, 那么当然这个地址距离0x7c04的偏移就是2

 1# akvicor @ office in ~/workspace/test [0:55:07] 
 2$ cat boot.asm 
 3[bits 16]
 4org 0x7c00
 5
 6jmp short 0x7c04
 7
 8
 9# akvicor @ office in ~/workspace/test [0:55:10] 
10$ nasm boot.asm
11
12# akvicor @ office in ~/workspace/test [0:55:11] 
13$ hexdump boot
14---------- 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F -------------------
1500000000  eb 02                                             |..|
1600000002

既然知道了ebjmp的机器码, 那么回到我们一开始看到的前512字节中, 开头两个是eb 58, 那么可知跳转后的地址为0x02+0x58 = 0x5a, 为什么是0x5a? 看完后面的FAT32结构你就明白了

FAT结构

我定睛一看eb 58后面的而90, 就知道他是汇编指令nop的机器码, 这是因为MBR为了对齐或其他某些历史遗留原因

那么我们将58加上指令自身的2字节, 就是5a, 我们只看这两个之间的数据, 也就是jmp跳过的这部分, 同时忽略nop指令的机器码

1---------- 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F -------------------
200000000           6d 6b 66 73 2e  66 61 74 00 02 20 20 00  |   mkfs.fat..  .|
300000010  02 00 00 00 00 f8 00 00  20 00 40 00 00 00 00 00  |........ .@.....|
400000020  e0 51 b8 03 80 3b 00 00  00 00 00 00 02 00 00 00  |.Q...;..........|
500000030  01 00 06 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
600000040  80 00 29 3d ec 7d d6 4e  4f 20 4e 41 4d 45 20 20  |..)=.}.NO NAME  |
700000050  20 20 46 41 54 33 32 20  20 20                    |  FAT32         |

我们一看, 这不是巧了吗, 这不就是FAT32分区的参数信息吗

03-0A - 8B - BPB_OEMName - OEM名字

8个字节 6d 6b 66 73 2e 66 61 74 也就是 mkfs.fat

也就是OEM名字, 我使用mkfs.vfat创建的分区, 这个工具就将名字配置为mkfs.fat

0B-0C - 2B - BPB_BytesPerSec - 字节每扇区

2个字节 00 02, 也就是0x0200(注意小端顺序), 也就是512个字节

也就是说我们的这个分区分配的扇区大小是512字节

0D - 1B - BPB_SecPerClus - 扇区每簇

1个字节 20, 也就是0x20, 也就是32个扇区

也就是说我们的这个分区的每个簇里都有32个扇区, 那么每个簇的大小就是16KiB

0E-0F - 2B - BPB_RsvdSecCnt - 保留的

2个字节 20 00, 也就是0x0020(注意小端顺序), 也就是32个扇区

保留的扇区是从0扇区开始算的32个扇区, 注意这里的保留并非是留着给未来用, 而是指保留给FAT32格式自身使用, 例如第0扇区, 也就是引导扇区就是保留的扇区之一

10 - 1B - BPB_NumFATs - FAT数量

1个字节 02, 也就是0x02, 也就是2个FAT表

FAT表也就是簇的占用情况, 大致逻辑和链表类似, 从ROOT查到起始簇后, 结合FAT表可以查到文件占用的每个簇的下一个簇, 这样就能以正确顺序读取出文件的全部内容

11-12 - 2B - BPB_RootEntCnt - 根目录项数

这个在FAT32中无用, 可以忽略

13-14 - 2B - BPB_TotSec16 - 总扇区数

这个在FAT32中无用, 可以忽略

15 - 1B - BPB_Media - 介质类型

根据下表可知f8代表硬盘, 也就是U盘

描述
0xF0 1.44MB 软盘
0xF8 硬盘
0xF9 3.5 英寸 720KB 软盘
0xFA 备用软盘类型
0xFB 备用软盘类型
0xFC 1.2MB 5.25 英寸软盘
0xFD 360KB 5.25 英寸软盘
0xFE 180KB 5.25 英寸软盘
0xFF 160KB 5.25 英寸软盘

16-17 - 2B - BPB_FATSz16 - 每个 FAT 表占用的扇区数

这个在FAT32中无用, 可以忽略

18-19 - 2B - BPB_SecPerTrk - 每磁道扇区数

适用于CHS寻址模式

2个字节 20 00, 也就是0x20, 也就是每磁道32个扇区

1A-1B - 2B - BPB_NumHeads - 磁头数

2个字节 40 00, 也就是0x40, 也就是64个磁头

1C-1F - 4B - BPB_HiddSec - 隐藏扇区数

4个字节 00 00 00 00, 也就是0

该字段用于 LBA(逻辑块寻址),主要用于 FAT 分区在磁盘中的偏移计算,特别是在 多分区磁盘逻辑分区 结构中。

  • 软盘(无 MBR):0
  • 硬盘(带 MBR,单分区):通常 63(MBR 占 63 扇区)
  • 硬盘(带 MBR,多分区):值随分区结构变化

可以看到一般软盘里这个值才是0, 为什么我们32G的U盘还是0呢

需要注意的是, 正常的格式化方式, 应该是先分区, 再格式化成制定格式

也就是下面这个样子, 可以看到分区在sda磁盘下的sda1分区

1NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
2sda           8:0    1 29.8G  0 disk 
3└─sda1        8:1    1 29.8G  0 part 

在这种情况下使用hexdump -n 512 /dev/sda1就能看到这个值变成了00 08 00 00, 也就是2048, 也就是1048576字节

那么我们跳过这些字节, 看看偏移是否正确.

通过hexdump -s 1048576 -n 512 /dev/sda再查看, 发现就是hexdump -n 512 /dev/sda1的内容

那么我们的分区和格式化方式是什么呢? 什么是分区?

1NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
2sda           8:0    1 29.8G  0 disk 

你没看错, 只有这两行, 没有那个sda1. 什么分区? 可以吃吗?

20-23 - 4B - BPB_TotSec32 - 总扇区数

FAT32下的扇区总数

4个字节 e0 51 b8 03, 也就是0x03B851E0, 也就是每磁道62411232个扇区, 也就是62411232*512/1024/1024/1024 ~= 29.8GiB, 就是U盘的大小

24-27 - 4B - BPB_FATSz32 - FAT 表大小

4个字节 80 3b 00 00, 也就是0x00003B80, 也就是15232个扇区, 也就是15232*512/1024 = 7616KiB

那么这里我们通过FAT表粗略算一下U盘大小

FAT表用4字节表示某个簇的下一个簇, 也就是说U盘的每个簇都有一个FAT项, 已知1个簇有32个扇区, 1个扇区512字节.

那么15232*512/4*32*512 = 31943819264字节, 31943819264/1024/1024/1024 ~= 29.8GiB, 就是U盘的大小

28-29 - 2B - BPB_ExtFlags - 扩展标志

2个字节, 也就是00 00

  • 低字节(0x00)通常用于标识文件系统的某些特性,但具体含义可能由实现进行扩展。
  • 高字节(0x00)通常是保留的,意味着该标志位尚未被使用或被用于其他功能。

2A-2B - 2B - BPB_FSVer - 文件系统版本

2个字节, 也就是00 00

  • 低字节存储 次版本号(Minor Version)。
  • 高字节存储 主版本号(Major Version)。

2C-2F - 4B - BPB_RootClus - 根目录所在的簇号

4个字节 02 00 00 00, 也就是0x00000002, 也就是2号簇

30-31 - 2B - BPB_FSInfo - 文件系统信息扇区的位置

2个字节, 也就是01 00, 也就是0x0001, 也就是1号扇区(注意还有个0号)

包含了关于文件系统的一些重要信息,如空闲簇计数、下一个可用簇等

我们直接一个hexdump -s 512 -n 512 /dev/sda甩过去, 就会看到下面信息

1$ hexdump -s 512 -n 512 /dev/sda
2---------- 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F--------------------
300000200  52 52 61 41 00 00 00 00  00 00 00 00 00 00 00 00  |RRaA............|
400000210  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
5*
6000003e0  00 00 00 00 72 72 41 61  d5 be 1d 00 02 00 00 00  |....rrAa........|
7000003f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
800000400
  • 开头的52 52 61 41表明这是FSInfo的第一个签名字段
  • 3E4-3E7偏移处的72 72 41 61是第二个签名字段, 再次确认这是有效的FSInfo
  • 3E8-3EB偏移处的d5 be 1d 00在十进制下为1949397, 表明有这么多空闲簇可用, 简单计算下1949397*32*512/1024/1024/1024 ~= 29.8GiB
  • 3EC-3EF偏移处的02 00 00 00在十进制下为2, 表明下一个空闲簇为2号,一般FAT32从簇2开始存储数据
  • 最后的55 aa是引导扇区结束标记, 但我觉得可能只是某些历史遗留原因, 毕竟这玩意和引导关系也不大

32-33 - 2B - BPB_BkBootSec - 备份引导扇区的位置

2个字节, 也就是06 00, 也就是0x0006, 也就是6号扇区

直接一个hexdump -s 3072 -n 512 /dev/sda甩过去(跳过6*512=3072), 就会发现他和hexdump -n 512 /dev/sda一模一样

34-3F - 12B - BPB_Reserved - 保留空间

没有用, 未来可期

40 - 1B - BS_DrvNum - 驱动器号

用于指定 BIOS 中断 13h 所使用的驱动器号

1个字节, 也就是80, 也就是硬盘

  • 0x00:表示软盘驱动器
  • 0x80:表示硬盘驱动器

41 - 1B - BS_Reserved1 - 保留

没有用, 未来可期

42 - 1B - BS_BootSig - 扩展引导签名

数值为0x29, 表明后面跟随着卷序列号、卷标签和文件系统类型等重要信息。

43-46 - 4B - BS_VolID - 卷序列号

序号 3d ec 7d d6, 每次格式化数值都不同

47-51 - 11B - BS_VolLab - 卷标签

卷标签 4e 4f 20 4e 41 4d 45 20 20 20 20, ASCII转译过来就是NO NAME

52-59 - 8B - BS_FilSysType - 文件系统类型

文件系统类型 46 41 54 33 32 20 20 20, ASCII转译过来就是FAT32

5A-1FD - 420B - BootStrap Code - 启动代码

这420字节就是我们编写的boot代码存放的位置了

还记得开头计算出的JMP跳转后的位置0x5a吗, 就是指的这里.

1FE-1FF - 2B - 引导扇区结束标记

固定为55 aa, 表示引导扇区结束

FAT条目

每个条目占4字节, 前两个条目被保留, 也就是对应启动扇区,FAT表等占用的簇

  • 0x00000000:表示未使用的簇
  • 0x0FFFFFFF:表示坏簇或文件的最后一个簇
  • 0x0FFFFFF7:表示坏簇
  • 0x0FFFFFF80x0FFFFFFF:表示文件结束标记(EOF)
  • 其他值:下一个簇的簇号

Root Directory 条目

每个条目32字节

Field name Offset Size Description
DIR_Name 0 11 Short file name (SFN) of the object.
DIR_Attr 11 1 File attribute in combination of following flags. Upper 2 bits are reserved and must be zero.
  • 0x01: ATTR_READ_ONLY (Read-only)
  • 0x02: ATTR_HIDDEN (Hidden)
  • 0x04: ATTR_SYSTEM (System)
  • 0x08: ATTR_VOLUME_ID (Volume label)
  • 0x10: ATTR_DIRECTORY (Directory)
  • 0x20: ATTR_ARCHIVE (Archive)
  • 0x0F: ATTR_LONG_FILE_NAME (LFN entry)
DIR_NTRes 12 1 Optional flags that indicates case information of the SFN.
  • 0x08: Every alphabet in the body is low-case.
  • 0x10: Every alphabet in the extensiton is low-case.
DIR_CrtTimeTenth 13 1 Optional sub-second information corresponds to DIR_CrtTime. The time resolution of DIR_CrtTime is 2 seconds, so that this field gives a count of sub-second and its valid value range is from 0 to 199 in unit of 10 miliseconds. If not supported, set zero and do not change afterwards.
DIR_CrtTime 14 2 Optional file creation time. If not supported, set zero and do not change afterwards.
DIR_CrtDate 16 2 Optional file creation date. If not supported, set zero and do not change afterwards.
DIR_LstAccDate 18 2 Optional last accesse date. There is no time information about last accesse time, so that the resolution of last accesse time is 1 day. If not supported, set zero and do not change afterwards.
DIR_FstClusHI 20 2 Upeer part of cluster number. Always zero on the FAT12/16 volume.
DIR_WrtTime 22 2 Last time when any change is made to the file (typically on closeing).
DIR_WrtDate 24 2 Last data when any change is made to the file (typically on closeing).
DIR_FstClusLO 26 2 Lower part of cluster number. Always zero if the file size is zero.
DIR_FileSize 28 4 Size of the file in unit of byte. Not used when it is a directroy and the value must be always zero.

如何从FAT32中查找文件

这里通过一个简单的示例, 讲解一下如何从FAT32文件系统中查找文件

偏移计算公式

基本计算逻辑就是计算出扇区数量, 然后乘以扇区大小BPB_BytesPerSec也就是0x0200

数据区

数据区就是真实存储文件数据的起始位置

数据区起始位置 = (BPB_RsvdSecCnt + BPB_FATSz32 * BPB_NumFATs) * BPB_BytesPerSec

某个簇的起始位置

N的起始位置 = (((N - 2) * BPB_SecPerClus) * BPB_BytesPerSec + (数据区起始位置)

FAT

FAT1就紧邻在保留区域

偏移 = BPB_RsvdSecCnt * BPB_BytesPerSec

FAT2就紧邻FAT1, 就是FAT1偏移+FAT1大小

偏移 = (BPB_RsvdSecCnt + BPB_FATSz32) * BPB_BytesPerSec

FAT的大小均为BPB_FATSz32 * BPB_BytesPerSec

查找文件

以查找loader.bin为例

计算出FAT1表位置 BPB_RsvdSecCnt * BPB_BytesPerSec = 0x0020 * 0x0200 = 0x4000

计算出数据区位置 (BPB_RsvdSecCnt + BPB_FATSz32 * BPB_NumFATs) * BPB_BytesPerSec = (0x20 + 0x1D18 * 0x02) * 0x0200 = 0x74A000

我们查看FAT1(0x4000)的数据

1---------- 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F -------------------
200004000  f8 ff ff 0f ff ff ff 0f  f8 ff ff 0f ff ff ff 0f  |................|
300004010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

我们接下来看第三个簇, 根据BPB_RootClus可知, 第三个簇也就是2号簇被用于Root Directory, 也就是文件目录, 同时它显示为f8 ff ff 0f, 表示目前Root Directory只占用了1个簇的空间

那么我们知道了Root Directory的簇号, 根据公式计算出位置(((N - 2) * BPB_SecPerClus) * BPB_BytesPerSec + (数据区起始位置) = (((2 - 2) * 0x08) * 0x0200 + 0x74A000 = 0x74A000

我们查看一下数据

1---------- 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F -------------------
20074a000  41 6c 00 6f 00 61 00 64  00 65 00 0f 00 ab 72 00  |Al.o.a.d.e....r.|
30074a010  2e 00 62 00 69 00 6e 00  00 00 00 00 ff ff ff ff  |..b.i.n.........|
40074a020  4c 4f 41 44 45 52 20 20  42 49 4e 20 00 00 e3 61  |LOADER  BIN ...a|
50074a030  8a 52 8a 52 00 00 e3 61  8a 52 03 00 11 00 00 00  |.R.R...a.R......|
60074a040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

我们发现一个loader.bin居然有两个条目, 根据每个条目的DIR_Attr可以看出, 第一个是长文件名条目(ATTR_LONG_FILE_NAME), 第二个是短文件名条目(ATTR_ARCHIVE). 这是为了兼容较旧的系统, 在旧系统中会忽略长文件名(LFN), 只显示短文件名(SFN)

长文件名条目中, 使用UTF-16编码, 因此每个字符占用2字节. 如果文件名过长, 可能需要多个长文件名条目才能存下, 在此暂不考虑

为了方便, 我们来分析短文件名(SFN)的那一条, 通过上面Root Directory 条目表格, 找出涉及文件所在簇号和大小的条目

  • DIR_FstClusHI 0x14: 0x0000
  • DIR_FstClusLO 0x1A: 0x0003
  • DIR_FileSize 0x1C: 0x00000011

拼接后, 我们发现文件在3号簇, 也就是第四个簇

那么我们再回到FAT表看第四个簇, 它显示为ff ff ff 0f, 也就是0FFFFFFF, 也就是文件的最后一个簇, 也就是说我们的loader.bin仅用1个簇的空间就存下了. 同时也说明, 一个文件无论多小, 都至少占用1个簇的空间

我们再次根据簇号, 计算文件实际存储的位置 (((N - 2) * BPB_SecPerClus) * BPB_BytesPerSec + (数据区起始位置) = (((3 - 2) * 0x08) * 0x0200 + 0x74A000 = 0x74B000

然后我们就能发现我们的文件

1---------- 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F -------------------
20074b000  54 68 69 73 20 69 73 20  6c 6f 61 64 65 72 2e 73  |This is loader.s|
30074b010  0a 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
40074b020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

只要根据文件的大小0x00000011, 也就是17字节, 就能准确的读取出文件内容了

生成汇编小工具

到这里, 我们已经明白了FAT32的结构, 也知道了如何通过FAT32查找并读取文件内容, 显然我们就需要在汇编代码中用到FAT扇区的那些数据

总不能每次用U盘时, 都要用人眼记录核对一下他的FAT32的参数吧? 这种事情交给程序来干就好了.

那么, 我们就用C编写一个工具, 就叫做gen_fat32_inc.c

这段代码的逻辑就是根据偏移将每个部分提取出来, 并生成到inc文件中, 这样boot和loader的汇编代码直接引用就可以了

  1// @Author : Akvicor
  2// @Created Time : 2021-08-09 10:46:34
  3// @Description : generate fat32.inc
  4
  5#include <stdio.h>
  6#include <stdlib.h>
  7
  8unsigned char * buffer = NULL;
  9
 10char * template =
 11";----------------------------------------------- [DEF]\n"
 12"\n"
 13"%%define FAT32_BYTES_PER_SECTOR     0x%08X\n"
 14"%%define FAT32_SECTORS_PER_CLUSTER  0x%08X\n"
 15"%%define FAT32_BYTES_PER_CLUSTER    0x%08X\n"
 16"%%define FAT32_FAT_REGION           0x%08X\n"
 17"%%define FAT32_ROOT_REGION          0x%08X\n"
 18"\n"
 19";----------------------------------------------- [OEM Manufacturer; offset: 0x03; length: 8 Bytes]\n"
 20"\n"
 21"BPB_OEMName               DB    \"AKVICOR \"              ; 8 Bytes\n"
 22"\n"
 23";----------------------------------------------- [BIOS Parameter Block; offset: 0x0b; length: 25 Bytes]\n"
 24"\n"
 25"; The number of bytes, in little-endian\n"
 26"BPB_BytesPerSec           DW    0x%04X                  ; offset: 0x0b[11]; length: 2  Bytes\n"
 27"; Number of Sectors per Cluster\n"
 28"BPB_SecPerClus            DB    0x%02X                    ; offset: 0x0d[13]; length: 1  Bytes\n"
 29"; Number of Reserved Sectors\n"
 30"BPB_RsvdSecCnt            DW    0x%04X                  ; offset: 0x0e[14]; length: 2  Bytes\n"
 31"; Number of File Allocation Tables\n"
 32"BPB_NumFATs               DB    0x%02X                    ; offset: 0x10[16]; length: 1  Bytes\n"
 33"; Root Entries. The total number file name\n"
 34"; entires than can be store in the root folder\n"
 35"; of the volume.\n"
 36"; For FAT32 volumes, this number is set to zero\n"
 37"BPB_RootEntCnt            DW    0x%04X                  ; offset: 0x11[17]; length: 2  Bytes\n"
 38"; 0 For FAT32\n"
 39"BPB_TotSec16              DW    0x%04X                  ; offset: 0x13[19]; length: 2  Bytes\n"
 40"; Media Type: F0 removable,\n"
 41"; F8 fixed media, i.e. hard disk\n"
 42"BPB_Media                 DB    0x%02X                    ; offset: 0x15[21]; length: 1  Bytes\n"
 43"; For FAT32, this field should be zero\n"
 44"BPB_FATSz16               DW    0x%04X                  ; offset: 0x16[22]; length: 2  Bytes\n"
 45"; Sectors per track\n"
 46"BPB_SecPerTrk             DW    0x%04X                  ; offset: 0x18[24]; length: 2  Bytes\n"
 47"; Number of heads\n"
 48"BPB_NumHeads              DW    0x%04X                  ; offset: 0x1A[26]; length: 2  Bytes\n"
 49"; Count of hidden sectors preceeding the\n"
 50"; partition that contains the FAT volume\n"
 51"BPB_HiddSec               DD    0x%08X              ; offset: 0x1C[28]; length: 4  Bytes\n"
 52"; Total count of sectors\n"
 53"BPB_TotSec32              DD    0x%08X              ; offset: 0x20[32]; length: 4  Bytes\n"
 54"\n"
 55";----------------------------------------------- [Extended BIOS Parameter Block; offset: 0x24; length: 54 Bytes]\n"
 56"\n"
 57"; 32-bit count of sectors occupied by one FAT\n"
 58"BPB_FATSz32               DD    0x%08X               ; offset: 0x24[36]; length: 4  Bytes\n"
 59"; Bits [0-3 ]: Zero based number of active FAT\n"
 60"; Bits [4-6 ]: Reserved\n"
 61"; Bits [7   ]:\n"
 62";  0: The FAT is mirrored at runtime into all FATs\n"
 63";  1: Only one FAT is active\n"
 64"; Bits [8-15]: Reserved\n"
 65"BPB_ExtFlags              DW    0x%04X                   ; offset: 0x28[40]; length: 2  Bytes\n"
 66"; High Byte: Major revision number\n"
 67"; The sample value prevents windows\n"
 68"; from loading this FAT partition\n"
 69"BPB_FSVer                 DW    0x%04X                   ; offset: 0x2A[42]; length: 2  Bytes\n"
 70"; Cluster number of the first cluster\n"
 71"; of the root directory\n"
 72"BPB_RootClus              DD    0x%08X               ; offset: 0x2C[44]; length: 4  Bytes\n"
 73"; File system info. Usually 1\n"
 74"BPB_FSInfo                DW    0x%04X                   ; offset: 0x30[48]; length: 2  Bytes\n"
 75"; If not zero, indicates the sector number\n"
 76"; in the reserved area of the volume of a\n"
 77"; copy of the boot record\n"
 78"BPB_BkBootSec             DW    0x%04X                   ; offset: 0x32[50]; length: 2  Bytes\n"
 79"; Reserved\n"
 80"BPB_Reserved              TIMES 12 DB 0                  ; offset: 0x34[52]; length: 12 Bytes\n"
 81"; Drive Number\n"
 82"BS_DrvNum                 DB    0x%02X                     ; offset: 0x40[64]; length: 1  Bytes\n"
 83"; Reserved\n"
 84"BS_Reserved1              DB    0x%02X                     ; offset: 0x41[65]; length: 1  Bytes\n"
 85"; Boot signature\n"
 86"BS_BootSig                DB    0x%02X                     ; offset: 0x42[66]; length: 1  Bytes\n"
 87"; Volume ID\n"
 88"BS_VolID                  DD    0x%08X               ; offset: 0x43[67]; length: 4  Bytes\n"
 89"; Volume Label\n"
 90"BS_VolLab                 DB    \"AOS    BOOT\"            ; offset: 0x47[71]; length: 11 Bytes\n"
 91"; Volume ID\n"
 92"BS_FilSysType             DB    \"FAT32   \"               ; offset: 0x52[82]; length: 8  Bytes\n"
 93"\n"
 94";----------------------------------------------- [Bootstrap Code; offset: 0x5A[90]; length: 420 Bytes]\n"
 95;
 96
 97int read_file(char * filename, unsigned char * buffer, unsigned int size) {
 98  if(buffer == NULL) {
 99    printf("buffer is NULL!\n");
100    return -1;
101  }
102  FILE *file = fopen(filename, "r");
103  if(file == NULL) {
104    printf("open file failed!\n");
105    return -2;
106  }
107  fread(buffer, size, 1, file);
108  fclose(file);
109}
110
111int convert_to_fat32(unsigned char * buffer) {
112  if(buffer == NULL) {
113    printf("buffer is NULL!\n");
114    return -1;
115  }
116
117  // Bytes per Cluster
118  unsigned int bytes_per_sector = *(unsigned short*)(buffer+0x0b);
119  unsigned int sectors_per_cluster = *(unsigned char*)(buffer+0x0d);
120  unsigned int bytes_per_cluster = bytes_per_sector * sectors_per_cluster;
121
122  // FAT Region
123  unsigned int reserved_sectors_num = *(unsigned short*)(buffer+0x0e);
124  unsigned int fat_region = reserved_sectors_num;
125
126  // Root Directory Region
127  unsigned int fat_size = *(unsigned int*)(buffer+0x24);
128  unsigned int fat_num = *(unsigned char*)(buffer+0x10);
129  unsigned int total_fat_sectors = fat_size * fat_num;
130  unsigned int root_directory_region = reserved_sectors_num + total_fat_sectors;
131
132  printf(template,
133      bytes_per_sector,
134      sectors_per_cluster,
135      bytes_per_cluster,
136      fat_region,
137      root_directory_region,
138      *(unsigned short*)(buffer+0x0b),
139      *(unsigned char*)(buffer+0x0d),
140      *(unsigned short*)(buffer+0x0e),
141      *(unsigned char*)(buffer+0x10),
142      *(unsigned short*)(buffer+0x11),
143      *(unsigned short*)(buffer+0x13),
144      *(unsigned char*)(buffer+0x15),
145      *(unsigned short*)(buffer+0x16),
146      *(unsigned short*)(buffer+0x18),
147      *(unsigned short*)(buffer+0x1a),
148      *(unsigned int*)(buffer+0x1c),
149      *(unsigned int*)(buffer+0x20),
150      *(unsigned int*)(buffer+0x24),
151      *(unsigned short*)(buffer+0x28),
152      *(unsigned short*)(buffer+0x2a),
153      *(unsigned int*)(buffer+0x2c),
154      *(unsigned short*)(buffer+0x30),
155      *(unsigned short*)(buffer+0x32),
156      *(unsigned char*)(buffer+0x40),
157      *(unsigned char*)(buffer+0x41),
158      *(unsigned char*)(buffer+0x42),
159      *(unsigned int*)(buffer+0x43)
160    );
161}
162
163int main(int argc, char *argv[]){
164  if (argc < 2) {
165    printf("用法: %s <设备路径>\n", argv[0]);
166    printf("例如: %s /dev/sdb\n", argv[0]);
167    return 1;
168  }
169
170  buffer = (unsigned char *)calloc(512, sizeof(unsigned char));
171  if (buffer == NULL) {
172    printf("内存分配失败\n");
173    return 1;
174  }
175
176  read_file(argv[1], buffer, 512);
177
178  convert_to_fat32(buffer);
179
180  free(buffer);
181  return 0;
182}

注意除了生成的文件中, 除了FAT32的必要数据结构, 还额外生成了5个汇编的define常量

  • FAT32_BYTES_PER_SECTOR: 字节每扇区
  • FAT32_SECTORS_PER_CLUSTER: 扇区每簇
  • FAT32_BYTES_PER_CLUSTER: 字节每簇
  • FAT32_FAT_REGION: FAT的起始扇区号
  • FAT32_ROOT_REGION: ROOT Directory的起始扇区号

只需要运行一下, 就可以生成需要的文件了, 注意/dev/sda替换为自己的U盘或镜像文件路径

1gcc gen_fat32_inc.c -o gen_fat32_inc
2sudo ./gen_fat32_inc /dev/sda > fat32.inc

至此, boot和loader阶段用到的两个inc文件就正式完成了, 后面就正式迈入boot汇编代码编写了

除另有声明外本博客文章均采用 知识共享 (Creative Commons) 署名 4.0 国际许可协议 进行许可转载请注明原作者与文章出处