- 文件:
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
既然知道了eb
是jmp
的机器码, 那么回到我们一开始看到的前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
:表示坏簇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)的数据
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 国际许可协议 进行许可。转载请注明原作者与文章出处。