第5课 - 主引导程序的扩展(下)
第5课 - 主引导程序的扩展(下)
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 }
打印结果中出现的 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. 介绍 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