1. 在 FAT12 根目录中查找目标文件
在前面课程的学习中,我们知道主引导程序有一个 512 字节的限制,如何突破这种限制呢?我们想到的办法是:再写一个程序(LOADER)放到存储介质中,在主引导程序中将该程序加载到内存中,并将控制权转交给该程序(jump)。
那如何找到存储介质中的程序(LOADER)呢?答案就是需要借助一个文件系统(FAT12),将该程序放到软盘中(软盘的文件系统格式是 FAT12),根据文件系统数据组织的方式便能方便的找到这个程序。那如何具体的实现呢,请看本节。
1.1 根目录区的大小和位置
其中:BPB_RootEntCnt 表示根目录区的目录项的个数(0xE0,224)
RootEntry 表示根目录区每个目录项的大小(32 Bytes)
BPB_BytsPerSec 表示每扇区的字节数(512 Bytes)
224 * 32 = 7168Bytes 7168 / 512 = 14扇区
1.2 FAT12 文件系统中的根目录区
根目录区由目录项构成,每一个目录项代表根目录中的一个文件索引。
文件名 8 字节,扩展名 3 字节,未占用的部分用 “空格” 代替,ascii = 0x20
在 FAT12 中,1簇 = 1扇区。
目录项中的关键成员:
-
- DIR_Name
文件名(用于判断是否为目标文件)
-
- DIR_FstClus
文件数据起始存储位置(用于确定读取位置)
-
- DIR_FileSize
文件大小(用于确定读取的字节数)
1.3 实验:读取 FAT12 文件系统的根目录信息
— 步骤:
-
- 创建 RootEntry 结构体类型(根据前面目录项的那个表格创建)
-
- 使用文件流顺序读取每个项的内容
- 解析并打印相关的信息
【实验1:读取根目录区的目录项】 (实验的环境为 Qt)
1 #include <QtCore/QCoreApplication>
2 #include <QFile>
3 #include <QDataStream>
4 #include <QDebug>
5 #include <QVector>
6 #include <QByteArray>
7 #include <stdio.h>
8
9 #pragma pack(push)
10 #pragma pack(1)
11
12 struct Fat12Header
13 {
14 char BS_OEMName[8];
15 ushort BPB_BytsPerSec;
16 uchar BPB_SecPerClus;
17 ushort BPB_RsvdSecCnt;
18 uchar BPB_NumFATs;
19 ushort BPB_RootEntCnt;
20 ushort BPB_TotSec16;
21 uchar BPB_Media;
22 ushort BPB_FATSz16;
23 ushort BPB_SecPerTrk;
24 ushort BPB_NumHeads;
25 uint BPB_HiddSec;
26 uint BPB_TotSec32;
27 uchar BS_DrvNum;
28 uchar BS_Reserved1;
29 uchar BS_BootSig;
30 uint BS_VolID;
31 char BS_VolLab[11];
32 char BS_FileSysType[8];
33 };
34
35 struct RootEntry
36 {
37 char DIR_Name[11];
38 uchar DIR_Attr;
39 uchar reserve[10];
40 ushort DIR_WrtTime;
41 ushort DIR_WrtDate;
42 ushort DIR_FstClus;
43 uint DIR_FileSize;
44 };
45
46 #pragma pack(pop)
47
48 void PrintHeader(Fat12Header& rf, QString p)
49 {
50 QFile file(p);
51
52 if( file.open(QIODevice::ReadOnly) )
53 {
54 QDataStream in(&file);
55
56 file.seek(3);
57
58 in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf));
59
60 rf.BS_OEMName[7] = 0;
61 rf.BS_VolLab[10] = 0;
62 rf.BS_FileSysType[7] = 0;
63
64 qDebug() << "BS_OEMName: " << rf.BS_OEMName;
65 qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec;
66 qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus;
67 qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt;
68 qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs;
69 qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt;
70 qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16;
71 qDebug() << "BPB_Media: " << hex << rf.BPB_Media;
72 qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16;
73 qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk;
74 qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads;
75 qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec;
76 qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32;
77 qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum;
78 qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1;
79 qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig;
80 qDebug() << "BS_VolID: " << hex << rf.BS_VolID;
81 qDebug() << "BS_VolLab: " << rf.BS_VolLab;
82 qDebug() << "BS_FileSysType: " << rf.BS_FileSysType;
83
84 file.seek(510);
85
86 uchar b510 = 0;
87 uchar b511 = 0;
88
89 in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510));
90 in.readRawData(reinterpret_cast<char*>(&b511), sizeof(b511));
91
92 qDebug() << "Byte 510: " << hex << b510;
93 qDebug() << "Byte 511: " << hex << b511;
94 }
95
96 file.close();
97 }
98
99 RootEntry FindRootEntry(Fat12Header& rf, QString p, int i)
100 {
101 RootEntry ret = {{0}};
102
103 QFile file(p);
104
105 if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
106 {
107 QDataStream in(&file);
108
109 file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry));
110
111 in.readRawData(reinterpret_cast<char*>(&ret), sizeof(ret));
112 }
113
114 file.close();
115
116 return ret;
117 }
118
119 void PrintRootEntry(Fat12Header& rf, QString p)
120 {
121 for(int i=0; i<rf.BPB_RootEntCnt; i++)
122 {
123 RootEntry re = FindRootEntry(rf, p, i);
124
125 if( re.DIR_Name[0] != '\0' )
126 {
127 qDebug() << i << ":";
128 qDebug() << "DIR_Name: " << re.DIR_Name; //这里打印字符串其实越界了,不过RootEntry的reserve会终止打印,reserve初始化为0
129 qDebug() << "DIR_Attr: " << re.DIR_Attr;
130 qDebug() << "DIR_WrtDate: " << re.DIR_WrtDate;
131 qDebug() << "DIR_WrtTime: " << re.DIR_WrtTime;
132 qDebug() << "DIR_FstClus: " << re.DIR_FstClus;
133 qDebug() << "DIR_FileSize: " << re.DIR_FileSize;
134 }
135 }
136 }
137
138
139 int main(int argc, char *argv[])
140 {
141 QCoreApplication a(argc, argv);
142 QString img = "F:\\data.img";
143 Fat12Header f12;
144
145 qDebug() << "Read Header:";
146
147 PrintHeader(f12, img);
148
149 qDebug() << endl;
150
151 qDebug() << "Print Root Entry:";
152
153 PrintRootEntry(f12, img);
154
155 qDebug() << endl;
156
157 return a.exec();
158 }
读取根目录区的目录项1
打印结果中出现的 0 和 2 两项,是由于虚拟软盘 data.img 是在 FreeDos 中格式化,然后挂载到 linux 中写入文件导致的,是一些没有意义的垃圾数据。
改变打印格式,验证上面的说法:
1 #include <QtCore/QCoreApplication>
2 #include <QFile>
3 #include <QDataStream>
4 #include <QDebug>
5 #include <QVector>
6 #include <QByteArray>
7
8 #pragma pack(push)
9 #pragma pack(1)
10
11 struct Fat12Header
12 {
13 char BS_OEMName[8];
14 ushort BPB_BytsPerSec;
15 uchar BPB_SecPerClus;
16 ushort BPB_RsvdSecCnt;
17 uchar BPB_NumFATs;
18 ushort BPB_RootEntCnt;
19 ushort BPB_TotSec16;
20 uchar BPB_Media;
21 ushort BPB_FATSz16;
22 ushort BPB_SecPerTrk;
23 ushort BPB_NumHeads;
24 uint BPB_HiddSec;
25 uint BPB_TotSec32;
26 uchar BS_DrvNum;
27 uchar BS_Reserved1;
28 uchar BS_BootSig;
29 uint BS_VolID;
30 char BS_VolLab[11];
31 char BS_FileSysType[8];
32 };
33
34 struct RootEntry
35 {
36 char DIR_Name[11];
37 uchar DIR_Attr;
38 uchar reserve[10];
39 ushort DIR_WrtTime;
40 ushort DIR_WrtDate;
41 ushort DIR_FstClus;
42 uint DIR_FileSize;
43 };
44
45 #pragma pack(pop)
46
47 void PrintHeader(Fat12Header& rf, QString p)
48 {
49 QFile file(p);
50
51 if( file.open(QIODevice::ReadOnly) )
52 {
53 QDataStream in(&file);
54
55 file.seek(3);
56
57 in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf));
58
59 rf.BS_OEMName[7] = 0;
60 rf.BS_VolLab[10] = 0;
61 rf.BS_FileSysType[7] = 0;
62
63 qDebug() << "BS_OEMName: " << rf.BS_OEMName;
64 qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec;
65 qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus;
66 qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt;
67 qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs;
68 qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt;
69 qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16;
70 qDebug() << "BPB_Media: " << hex << rf.BPB_Media;
71 qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16;
72 qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk;
73 qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads;
74 qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec;
75 qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32;
76 qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum;
77 qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1;
78 qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig;
79 qDebug() << "BS_VolID: " << hex << rf.BS_VolID;
80 qDebug() << "BS_VolLab: " << rf.BS_VolLab;
81 qDebug() << "BS_FileSysType: " << rf.BS_FileSysType;
82
83 file.seek(510);
84
85 uchar b510 = 0;
86 uchar b511 = 0;
87
88 in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510));
89 in.readRawData(reinterpret_cast<char*>(&b511), sizeof(b511));
90
91 qDebug() << "Byte 510: " << hex << b510;
92 qDebug() << "Byte 511: " << hex << b511;
93 }
94
95 file.close();
96 }
97
98 RootEntry FindRootEntry(Fat12Header& rf, QString p, int i)
99 {
100 RootEntry ret = {{0}};
101
102 QFile file(p);
103
104 if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
105 {
106 QDataStream in(&file);
107
108 file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry));
109
110 in.readRawData(reinterpret_cast<char*>(&ret), sizeof(ret));
111 }
112
113 file.close();
114
115 return ret;
116 }
117
118 void PrintRootEntry(Fat12Header& rf, QString p)
119 {
120 for(int i=0; i<rf.BPB_RootEntCnt; i++)
121 {
122 RootEntry re = FindRootEntry(rf, p, i);
123
124 if( re.DIR_Name[0] != '\0' )
125 {
126 qDebug() << i << ":";
127 qDebug() << "DIR_Name: " << hex << re.DIR_Name; //采用十六进制打印输出
128 qDebug() << "DIR_Attr: " << hex << re.DIR_Attr;
129 qDebug() << "DIR_WrtDate: " << hex << re.DIR_WrtDate;
130 qDebug() << "DIR_WrtTime: " << hex << re.DIR_WrtTime;
131 qDebug() << "DIR_FstClus: " << hex << re.DIR_FstClus;
132 qDebug() << "DIR_FileSize: " << hex << re.DIR_FileSize;
133 }
134 }
135 }
136
137 int main(int argc, char *argv[])
138 {
139 QCoreApplication a(argc, argv);
140 QString img = "F:\\data.img";
141 Fat12Header f12;
142
143 qDebug() << "Read Header:";
144
145 PrintHeader(f12, img);
146
147 qDebug() << endl;
148
149 qDebug() << "Print Root Entry:";
150
151 PrintRootEntry(f12, img);
152
153 qDebug() << endl;
154
155 return a.exec();
156 }
读取根目录区的目录项2
2. 介绍 FAT 表
2.1 FAT 表 - FAT12 的数据组织核心
(1)FAT1 和 FAT2 是相互备份的关系,数据内容完全一致
(2)FAT 表是一个关系图,记录了文件数据的先后关系
(3)每一个 FAT 表项占用 12 比特
(4)FAT 表的前 2 个表项规定不使用
2.2 FAT 表中的先后关系
(1)以簇(扇区)为单位存储文件数据
(2)每个表项( vec[i] )表示文件数据的实际位置(簇)
-
-
- DIR_FstClus 表示文件第 0 簇(扇区)的位置
- vec[DIR_FstClus] 表示文件第 1 簇(扇区)的位置
- vec[vec[DIR_FstClus]] 表示文件第 2 簇(扇区)的位置
- ......
-
2.3 FAT12 数据物理组织示意
2.4 FAT12 数据逻辑组织示意
2.5 实验:加载 FAT12 中的文件数据
— 步骤:
-
- 在根目录区查找目标文件对应的项
- 获取目标文件的起始簇号和文件大小
- 根据 FAT 表中记录的逻辑先后关系读取数据
3. 小贴士
3.1 小贴士一
(1)FAT 表中的每个表项只占用 12 比特(1.5字节)
(2)FAT 表一共记录了 BPB_BytsPerSec * 9 * 2 / 3 个表项
(3)可以使用一个 short 表示一个表项的值
(4)如果表象值大于等于 0xFF8 ,则说明已经到达最后一个簇
(5)如果表项值等于 0xFF7 ,则说明当前簇已经损坏
3.2 小贴士二
(1)数据区起始簇()号为33,地址为 0x4200
(2)数据区起始地址所对应的编号为 2(不为 0)
(3)因此,DIR_FstClus 对应的地址为:
-
- 0x4200 + ( DIR_FstClus - 2 ) * 512
【编程实验:读取指定文件内容】
4. 小结
(1)FAT12 根目录区记录了文件的起始簇号和长度
(2)通过查找根目录区能够确定是否存在目标文件
(3)FAT12 文件数据的组织使用了单链表的思想
— 文件数据离散的分布于存储介质中
— 文件数据通过 FAT 项进行关联
注:本文整理于《狄泰12月提升计划》课程内容
狄泰QQ群:199546072
本人QQ号:502218614