文件: fat32.inc
幸运的是我们不用自己编写这个文件, 不幸的是我们需要编写生成这个文件的程序
我们需要先做些准备, 方便未来的开发
在后续中, 我会使用 /dev/sda 代指磁盘或磁盘镜像, 如果你使用的是U盘, 那么我建议你修改可以临时修改磁盘的权限, 使得所有用户都可以读写磁盘 sudo chmod 666 /dev/sda
同时我为hexdump命令创建了一个alias, 方便查看磁盘数据
alias hexdump='echo "---------- 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------" && hexdump -C'例如我格式化这个磁盘为FAT32分区, 并查看前512字节, 可以得到如下的结果
# akvicor @ office in ~ [0:43:45]
$ sudo mkfs.vfat /dev/sda
mkfs.fat 4.2 (2021-01-31)
# akvicor @ office in ~ [0:45:01]
$ hexdump -n 512 /dev/sda
---------- 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------
00000000 eb 58 90 6d 6b 66 73 2e 66 61 74 00 02 20 20 00 |.X.mkfs.fat.. .|
00000010 02 00 00 00 00 f8 00 00 20 00 40 00 00 00 00 00 |........ .@.....|
00000020 e0 51 b8 03 80 3b 00 00 00 00 00 00 02 00 00 00 |.Q...;..........|
00000030 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 80 00 29 3d ec 7d d6 4e 4f 20 4e 41 4d 45 20 20 |..)=.}.NO NAME |
00000050 20 20 46 41 54 33 32 20 20 20 0e 1f be 77 7c ac | FAT32 ...w|.|
00000060 22 c0 74 0b 56 b4 0e bb 07 00 cd 10 5e eb f0 32 |".t.V.......^..2|
00000070 e4 cd 16 cd 19 eb fe 54 68 69 73 20 69 73 20 6e |.......This is n|
00000080 6f 74 20 61 20 62 6f 6f 74 61 62 6c 65 20 64 69 |ot a bootable di|
00000090 73 6b 2e 20 20 50 6c 65 61 73 65 20 69 6e 73 65 |sk. Please inse|
000000a0 72 74 20 61 20 62 6f 6f 74 61 62 6c 65 20 66 6c |rt a bootable fl|
000000b0 6f 70 70 79 20 61 6e 64 0d 0a 70 72 65 73 73 20 |oppy and..press |
000000c0 61 6e 79 20 6b 65 79 20 74 6f 20 74 72 79 20 61 |any key to try a|
000000d0 67 61 69 6e 20 2e 2e 2e 20 0d 0a 00 00 00 00 00 |gain ... .......|
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200或许你看到这些会觉得看不懂, 但没关系, 后续会展开介绍
最开始的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
# akvicor @ office in ~/workspace/test [0:55:07]
$ cat boot.asm
[bits 16]
org 0x7c00
jmp short 0x7c04
# akvicor @ office in ~/workspace/test [0:55:10]
$ nasm boot.asm
# akvicor @ office in ~/workspace/test [0:55:11]
$ hexdump boot
---------- 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------
00000000 eb 02 |..|
00000002既然知道了 eb 是 jmp 的机器码, 那么回到我们一开始看到的前512字节中, 开头两个是 eb 58 , 那么可知跳转后的地址为 0x02+0x58 = 0x5a , 为什么是 0x5a ? 看完后面的FAT32结构你就明白了
FAT结构
我定睛一看 eb 58 后面的而 90 , 就知道他是汇编指令 nop 的机器码, 这是因为MBR为了对齐或其他某些历史遗留原因
那么我们将 58 加上指令自身的2字节, 就是 5a , 我们只看这两个之间的数据, 也就是jmp跳过的这部分, 同时忽略nop指令的机器码
---------- 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------
00000000 6d 6b 66 73 2e 66 61 74 00 02 20 20 00 | mkfs.fat.. .|
00000010 02 00 00 00 00 f8 00 00 20 00 40 00 00 00 00 00 |........ .@.....|
00000020 e0 51 b8 03 80 3b 00 00 00 00 00 00 02 00 00 00 |.Q...;..........|
00000030 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 80 00 29 3d ec 7d d6 4e 4f 20 4e 41 4d 45 20 20 |..)=.}.NO NAME |
00000050 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盘
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分区
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 1 29.8G 0 disk
└─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 的内容
那么我们的分区和格式化方式是什么呢? 什么是分区?
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 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 甩过去, 就会看到下面信息
$ hexdump -s 512 -n 512 /dev/sda
---------- 0 1 2 3 4 5 6 7 8 9 A B C D E F--------------------
00000200 52 52 61 41 00 00 00 00 00 00 00 00 00 00 00 00 |RRaA............|
00000210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000003e0 00 00 00 00 72 72 41 61 d5 be 1d 00 02 00 00 00 |....rrAa........|
000003f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000400开头的
52 52 61 41表明这是FSInfo的第一个签名字段3E4-3E7偏移处的
72 72 41 61是第二个签名字段, 再次确认这是有效的FSInfo3E8-3EB偏移处的
d5 be 1d 00在十进制下为1949397, 表明有这么多空闲簇可用, 简单计算下1949397*32*512/1024/1024/1024 ~= 29.8GiB3EC-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:表示坏簇0x0FFFFFF8至0x0FFFFFFF:表示文件结束标记(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.
|
| DIR_NTRes | 12 | 1 | Optional flags that indicates case information of the SFN.
|
| 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)的数据
---------- 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------
00004000 f8 ff ff 0f ff ff ff 0f f8 ff ff 0f ff ff ff 0f |................|
00004010 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
我们查看一下数据
---------- 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------
0074a000 41 6c 00 6f 00 61 00 64 00 65 00 0f 00 ab 72 00 |Al.o.a.d.e....r.|
0074a010 2e 00 62 00 69 00 6e 00 00 00 00 00 ff ff ff ff |..b.i.n.........|
0074a020 4c 4f 41 44 45 52 20 20 42 49 4e 20 00 00 e3 61 |LOADER BIN ...a|
0074a030 8a 52 8a 52 00 00 e3 61 8a 52 03 00 11 00 00 00 |.R.R...a.R......|
0074a040 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:
0x0000DIR_FstClusLO 0x1A:
0x0003DIR_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
然后我们就能发现我们的文件
---------- 0 1 2 3 4 5 6 7 8 9 A B C D E F -------------------
0074b000 54 68 69 73 20 69 73 20 6c 6f 61 64 65 72 2e 73 |This is loader.s|
0074b010 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0074b020 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的汇编代码直接引用就可以了
// @Author : Akvicor
// @Created Time : 2021-08-09 10:46:34
// @Description : generate fat32.inc
#include <stdio.h>
#include <stdlib.h>
unsigned char * buffer = NULL;
char * template =
";----------------------------------------------- [DEF]\n"
"\n"
"%%define FAT32_BYTES_PER_SECTOR 0x%08X\n"
"%%define FAT32_SECTORS_PER_CLUSTER 0x%08X\n"
"%%define FAT32_BYTES_PER_CLUSTER 0x%08X\n"
"%%define FAT32_FAT_REGION 0x%08X\n"
"%%define FAT32_ROOT_REGION 0x%08X\n"
"\n"
";----------------------------------------------- [OEM Manufacturer; offset: 0x03; length: 8 Bytes]\n"
"\n"
"BPB_OEMName DB \"AKVICOR \" ; 8 Bytes\n"
"\n"
";----------------------------------------------- [BIOS Parameter Block; offset: 0x0b; length: 25 Bytes]\n"
"\n"
"; The number of bytes, in little-endian\n"
"BPB_BytesPerSec DW 0x%04X ; offset: 0x0b[11]; length: 2 Bytes\n"
"; Number of Sectors per Cluster\n"
"BPB_SecPerClus DB 0x%02X ; offset: 0x0d[13]; length: 1 Bytes\n"
"; Number of Reserved Sectors\n"
"BPB_RsvdSecCnt DW 0x%04X ; offset: 0x0e[14]; length: 2 Bytes\n"
"; Number of File Allocation Tables\n"
"BPB_NumFATs DB 0x%02X ; offset: 0x10[16]; length: 1 Bytes\n"
"; Root Entries. The total number file name\n"
"; entires than can be store in the root folder\n"
"; of the volume.\n"
"; For FAT32 volumes, this number is set to zero\n"
"BPB_RootEntCnt DW 0x%04X ; offset: 0x11[17]; length: 2 Bytes\n"
"; 0 For FAT32\n"
"BPB_TotSec16 DW 0x%04X ; offset: 0x13[19]; length: 2 Bytes\n"
"; Media Type: F0 removable,\n"
"; F8 fixed media, i.e. hard disk\n"
"BPB_Media DB 0x%02X ; offset: 0x15[21]; length: 1 Bytes\n"
"; For FAT32, this field should be zero\n"
"BPB_FATSz16 DW 0x%04X ; offset: 0x16[22]; length: 2 Bytes\n"
"; Sectors per track\n"
"BPB_SecPerTrk DW 0x%04X ; offset: 0x18[24]; length: 2 Bytes\n"
"; Number of heads\n"
"BPB_NumHeads DW 0x%04X ; offset: 0x1A[26]; length: 2 Bytes\n"
"; Count of hidden sectors preceeding the\n"
"; partition that contains the FAT volume\n"
"BPB_HiddSec DD 0x%08X ; offset: 0x1C[28]; length: 4 Bytes\n"
"; Total count of sectors\n"
"BPB_TotSec32 DD 0x%08X ; offset: 0x20[32]; length: 4 Bytes\n"
"\n"
";----------------------------------------------- [Extended BIOS Parameter Block; offset: 0x24; length: 54 Bytes]\n"
"\n"
"; 32-bit count of sectors occupied by one FAT\n"
"BPB_FATSz32 DD 0x%08X ; offset: 0x24[36]; length: 4 Bytes\n"
"; Bits [0-3 ]: Zero based number of active FAT\n"
"; Bits [4-6 ]: Reserved\n"
"; Bits [7 ]:\n"
"; 0: The FAT is mirrored at runtime into all FATs\n"
"; 1: Only one FAT is active\n"
"; Bits [8-15]: Reserved\n"
"BPB_ExtFlags DW 0x%04X ; offset: 0x28[40]; length: 2 Bytes\n"
"; High Byte: Major revision number\n"
"; The sample value prevents windows\n"
"; from loading this FAT partition\n"
"BPB_FSVer DW 0x%04X ; offset: 0x2A[42]; length: 2 Bytes\n"
"; Cluster number of the first cluster\n"
"; of the root directory\n"
"BPB_RootClus DD 0x%08X ; offset: 0x2C[44]; length: 4 Bytes\n"
"; File system info. Usually 1\n"
"BPB_FSInfo DW 0x%04X ; offset: 0x30[48]; length: 2 Bytes\n"
"; If not zero, indicates the sector number\n"
"; in the reserved area of the volume of a\n"
"; copy of the boot record\n"
"BPB_BkBootSec DW 0x%04X ; offset: 0x32[50]; length: 2 Bytes\n"
"; Reserved\n"
"BPB_Reserved TIMES 12 DB 0 ; offset: 0x34[52]; length: 12 Bytes\n"
"; Drive Number\n"
"BS_DrvNum DB 0x%02X ; offset: 0x40[64]; length: 1 Bytes\n"
"; Reserved\n"
"BS_Reserved1 DB 0x%02X ; offset: 0x41[65]; length: 1 Bytes\n"
"; Boot signature\n"
"BS_BootSig DB 0x%02X ; offset: 0x42[66]; length: 1 Bytes\n"
"; Volume ID\n"
"BS_VolID DD 0x%08X ; offset: 0x43[67]; length: 4 Bytes\n"
"; Volume Label\n"
"BS_VolLab DB \"AOS BOOT\" ; offset: 0x47[71]; length: 11 Bytes\n"
"; Volume ID\n"
"BS_FilSysType DB \"FAT32 \" ; offset: 0x52[82]; length: 8 Bytes\n"
"\n"
";----------------------------------------------- [Bootstrap Code; offset: 0x5A[90]; length: 420 Bytes]\n"
;
int read_file(char * filename, unsigned char * buffer, unsigned int size) {
if(buffer == NULL) {
printf("buffer is NULL!\n");
return -1;
}
FILE *file = fopen(filename, "r");
if(file == NULL) {
printf("open file failed!\n");
return -2;
}
fread(buffer, size, 1, file);
fclose(file);
}
int convert_to_fat32(unsigned char * buffer) {
if(buffer == NULL) {
printf("buffer is NULL!\n");
return -1;
}
// Bytes per Cluster
unsigned int bytes_per_sector = *(unsigned short*)(buffer+0x0b);
unsigned int sectors_per_cluster = *(unsigned char*)(buffer+0x0d);
unsigned int bytes_per_cluster = bytes_per_sector * sectors_per_cluster;
// FAT Region
unsigned int reserved_sectors_num = *(unsigned short*)(buffer+0x0e);
unsigned int fat_region = reserved_sectors_num;
// Root Directory Region
unsigned int fat_size = *(unsigned int*)(buffer+0x24);
unsigned int fat_num = *(unsigned char*)(buffer+0x10);
unsigned int total_fat_sectors = fat_size * fat_num;
unsigned int root_directory_region = reserved_sectors_num + total_fat_sectors;
printf(template,
bytes_per_sector,
sectors_per_cluster,
bytes_per_cluster,
fat_region,
root_directory_region,
*(unsigned short*)(buffer+0x0b),
*(unsigned char*)(buffer+0x0d),
*(unsigned short*)(buffer+0x0e),
*(unsigned char*)(buffer+0x10),
*(unsigned short*)(buffer+0x11),
*(unsigned short*)(buffer+0x13),
*(unsigned char*)(buffer+0x15),
*(unsigned short*)(buffer+0x16),
*(unsigned short*)(buffer+0x18),
*(unsigned short*)(buffer+0x1a),
*(unsigned int*)(buffer+0x1c),
*(unsigned int*)(buffer+0x20),
*(unsigned int*)(buffer+0x24),
*(unsigned short*)(buffer+0x28),
*(unsigned short*)(buffer+0x2a),
*(unsigned int*)(buffer+0x2c),
*(unsigned short*)(buffer+0x30),
*(unsigned short*)(buffer+0x32),
*(unsigned char*)(buffer+0x40),
*(unsigned char*)(buffer+0x41),
*(unsigned char*)(buffer+0x42),
*(unsigned int*)(buffer+0x43)
);
}
int main(int argc, char *argv[]){
if (argc < 2) {
printf("用法: %s <设备路径>\n", argv[0]);
printf("例如: %s /dev/sdb\n", argv[0]);
return 1;
}
buffer = (unsigned char *)calloc(512, sizeof(unsigned char));
if (buffer == NULL) {
printf("内存分配失败\n");
return 1;
}
read_file(argv[1], buffer, 512);
convert_to_fat32(buffer);
free(buffer);
return 0;
}注意除了生成的文件中, 除了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盘或镜像文件路径
gcc gen_fat32_inc.c -o gen_fat32_inc
sudo ./gen_fat32_inc /dev/sda > fat32.inc至此, boot和loader阶段用到的两个inc文件就正式完成了, 后面就正式迈入boot汇编代码编写了