主引导程序是BIOS后运行的第一个程序,位于第0扇区,以55aa为结束符,大小不超过512字节
512字节能完成操作系统功能么?
限制:主引导程序的代码量不能超过512字节!
突破限制的思路:
主引导程序:
1、完成最基本的初始化工作
2、从存储介质中加载程序到内存中
3、将控制权交由新加载的程序执行。
存储介质 0扇区Boot...Program...找到程序加载到内存控制权交给内存
内存 Boot-jmp->Program
问题:主引导程序如何加载存储介质中的其它程序?
文件系统:
存储介质上组织文件数据的方法(数据组织的方式):FAT12文件格式
数据区...根目录区...FAT2(12是一样的)...FAT1(表:数据之间的关系)...引导扇区(主引导程序)
文件系统示例:
FAT12是DOS时代的早期文件系统
FAT12结构非常简单,一直沿用于软盘。因为我们的课是基于虚拟软盘
FAT12的基本组织单位:
字节(Byte):基本数据单位
扇区(Sector):磁盘中的最小数据单元,1个扇区512字节
簇(Cluster):一个或多个扇区
解决方案:
使用FAT12对软盘(data.img)进行格式化
编写可执行程序(Loader),并将其拷贝到软盘中
主引导程序(Boot)在文件系统中查找Loader,根据FAT12文件系统规定查找目标程序,查找到之后就将它复制到内存中,并且跳转到第一条指令来执行。
将Loader复制到内存中,并跳转到入口执行。
试验:往软盘中写入文件:
原材料:FreeDos(操作系统),Bochs,bximage
步骤:
创建虚拟软盘data.img
在FreeDos中进行格式化(文件系统FAT12)
将data.img(有文件系统)挂载到linux中,并写入文件。
先创建虚拟软盘:
bximage
fd
1.44
data.img
将虚拟软盘插入到freedos中,并且盘符是b盘
floppyb: 1.44="data.img", status=inserted
bochs
c
c什么意思?
format B:
格式化我们的虚拟软驱B盘,之后data.img就拥有了文件系统。FAT12文件系统。B盘是虚拟软盘
B: bochs
dir bochs
挂载:
sudo mount -o loop data.img /mnt/hgfs
创建两个文件:
sudo gedit test.txt
sudo gedit loader.bin
将文件拷贝到虚拟软驱:
sudo cp test.txt /mnt/hgfs/test.txt
sudo cp loader.bin /mnt/hgfs/loader.bin
虚拟软盘从当前linux里边给卸载掉:
sudo umount /mnt/hgfs
dos系统:
dir 有两个文件
看看:type test.txt
type loader.bin
现在有虚拟软盘data.img(二进制文件):里面有文件系统,还有两个文件
文件系统和文件格式本质是一样的,都是数据组织的方式,如果将数据组织的方式直接用于存储介质比如直接用于软盘,硬盘,那么这个数据组织的方式就是文件系统。如果将数据组织的方式应用与操作系统之下的一个文件,那么这个数据组织的方式就是文件格式。
做完了第一步第二步。下来是熟悉FAT12文件系统,这样才能直接在主引导程序里面去加载目标程序了。
下一步的工作:
Boot查找目标文件(Loader),并读取文件的内容!并加载到内存里面
深入了解FAT12文件系统:
FAT文件系统由引导区,FAT表,根目录项表和文件数据区
文件扇区位置 长度 内容
0 1(512B) 引导程序
1 9(4608B) FAT表1
10 9(4608B) FAT表2
19 14(9728B) 目录文件项
33 ---- 文件数据
FAT12的主引导区:存储介质的第0扇区
主引导区存储的比较重要的信息是文件系统的类型,文件系统逻辑扇区总数,每簇包含的扇区数等,主引导区最后以0x55AA两个字节作为结束,共占用一个扇区。意味着对于FATA12来说,主引导程序和文件系统相关的信息是合并的一起放在第0扇区里面的。
表:第0扇区存放的文件系统相关的信息
实验:读取data.img中的文件系统信息 :加深理解
步骤:
创建Fat12Header结构体类型
使用文件流读取前512字节的内容
解析并打印相关的信息。
#include <QtCore/QCoreApplication>
#include <QFile> #include <QDataStream> #include <QDebug> #pragma pack(push) #pragma pack(1) 对齐方式:1字节 struct Fat12Header { char BS_OEMName[8]; ushort BPB_BytsPerSec; uchar BPB_SecPerClus; ushort BPB_RsvdSecCnt; uchar BPB_NumFATs; ushort BPB_RootEntCnt; ushort BPB_TotSec16; uchar BPB_Media; ushort BPB_FATSz16; ushort BPB_SecPerTrk; ushort BPB_NumHeads; uint BPB_HiddSec; uint BPB_TotSec32; uchar BS_DrvNum; uchar BS_Reserved1; uchar BS_BootSig; uint BS_VolID; char BS_VolLab[11]; char BS_FileSysType[8]; }; #pragma pack(pop) void PrintHeader(Fat12Header& rf, QString p) { QFile file(p); p是路径,创建一个文件对象 if( file.open(QIODevice::ReadOnly) ) { QDataStream in(&file); file.seek(3); in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf)); rf.BS_OEMName[7] = 0; 字符数组最后赋0值 rf.BS_VolLab[10] = 0; rf.BS_FileSysType[7] = 0; qDebug() << "BS_OEMName: " << rf.BS_OEMName; qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec; qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus; qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt; qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs; qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt; qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16; qDebug() << "BPB_Media: " << hex << rf.BPB_Media; qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16; qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk; qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads; qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec; qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32; qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum; qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1; qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig; qDebug() << "BS_VolID: " << hex << rf.BS_VolID; qDebug() << "BS_VolLab: " << rf.BS_VolLab; qDebug() << "BS_FileSysType: " << rf.BS_FileSysType; file.seek(510); uchar b510 = 0; uchar b511 = 0; in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510)); in.readRawData(reinterpret_cast<char*>(&b511), sizeof(b511)); qDebug() << "Byte 510: " << hex << b510; qDebug() << "Byte 511: " << hex << b511; } file.close(); } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Fat12Header f12; PrintHeader(f12, "E:\\data.img"); return a.exec();}
实验结论:
1、FreeDos中的format程序在格式化软盘的时候自动在第0扇区生成了一个主引导程序,这个主引导程序只打印一个字符串。
2、文件格式和文件系统都是用于定义数据如何存放的规则,只要遵循这个规则就能够成功读写目标数据。
小结:
主引导程序的代码量不能超过512字节。
可以通过主引导程序加载新程序的方式突破限制。
加载新程序需要依赖于文件系统。
FAT12是一种早期用于软盘的简单文件系统。
FAT12文件系统的重要信息存储于0扇区。
5、主引导程序扩展下
主引导程序有个512字节的限制,为了突破这个限制,在写一个程序,并且将这个程序放到存储介质中,主引导程序加载这个新的程序并且将控制权交给他来执行,那问题是怎么到存储介质中找这个新的程序呢?答案是需要一个文件系统,有了文件系统我们就可以方便的将写好的新程序放到软盘中,有了文件系统我们也可以根据文件系统数据组织的方式来方便的找到这个程序了。
问题:如何在FAT12根目录中查找是否存在目标文件程序?
根目录区的大小和位置:起始于19扇区
19扇区 14(7168B) 目录文件项
大小:14扇区
BPB_RootEntEntCnt*sizeof(RootEntry)/BPB_BytsPerSec
BPB_RootEntEntCnt(文件格式头信息,根目录区有多少文件项)*sizeof(RootEntry)(每个文件项占得字节32字节)/BPB_BytsPerSec (每个扇区大小512)
FAT文件系统中的根目录区:
根目录区是由目录项构成,每个目录项代表根目录中的一个文件索引。
簇是比扇区更大的概念,但是FAT12里边1簇就是一个扇区。
试验:读取FAT12文件系统的根目录信息。
步骤:
创建RootEntry结构体类型。根据上面表格
使用文件流顺序读取每个目录项的内容。
解析并打印相关的信息。
试验读取根目录信息:上个结构体下边
struct RootEntry
{ char DIR_Name[11];
uchar DIR_Attr;
uchar reserve[10];
ushort DIR_WrtTime;
ushort DIR_WrtDate;
ushort DIR_FstClus;
uint DIR_FileSize; };
RootEntry FindRootEntry(Fat12Header& rf, Qstring p, int i)
{ RootEntry ret={{0}};
QFile file(p);
if( file.open(QIODevice::ReadOnly) && (0<=i)&&(i<rf.BPB_RootEntCnt)
{ QDataStream in(&file);
file.seek(19*rf.BPB_BytsPerSec+i*sizeof(RootEntry)); 第i个文件项
in.readRawData(reinterpret_cast<char*>(&ret),sizeof(ret));
}
file.clese();
return ret;
}
//根据文件名查找对应目录项
RootEntry FindRootEntry(Fat12Header& rf, Qstring p, QString fn)
{ RootEntry ret={{}};
for(int i=0;i<rf.BPB_RootEntCnt;i++)
{ RootEntry re=FindRootEntry(rf,p,i);
if(re.DIR_Name[0] !='\0')
{ int d=fn.lastIndexof(".");
QString name=QString(re.DIR_Name).trimmed();
if(d>=0)
{ QString n=fn.mid();
QString p=fn.min(d+1);
if(name.startsWith(n)&&name.endsWith(p))
{ ret=re;
break;}
}
}
else{
if(fn==name)
{ret=re;
break;}}
}
return ret;
}
void PrintRootEntry(Fat12Header& rf, QString p)
{ for(int i=0;i<rf.BPB_RootEntCnt; i++)
{ RootEntry re=FindRootEntry(rf, p, i);
if(re.DIR_Name[0] != "\0")
{
qDebug<<i<<":";
qDebug<<"dir_name:"<<re.DIR_Name;
qDebug<<"dir_attr:"<<re.DIR_Attr;
qDebug<<"dir_wrt:"<<re.DIR_WrtDate;
qDebug<<"dir_wrttime:"<<re.DIR_WrtTime;
qDebug<<"dir_fstclus:"<<re.DIR_FstClus;
qDebug<<"dir_wrttime:"<<re.DIR_FileSize;
}
}
}
qDebug()<<"Print Root Entry:";
PrintRootEntry(f12,img);
使用:
RootEntry re=FindRootEntry(f12,img,"LOADER.BIN");
qDebug<<"dir_name:"<<re.DIR_Name;
qDebug<<"dir_attr:"<<re.DIR_Attr;
qDebug<<"dir_wrt:"<<re.DIR_WrtDate;
qDebug<<"dir_wrttime:"<<re.DIR_WrtTime;
qDebug<<"dir_fstclus:"<<re.DIR_FstClus;
qDebug<<"dir_wrttime:"<<re.DIR_FileSize;
多出来两个文件项,不是合法的,大部分都是F,起始于第0处,在操作中,在firedos中对软盘进行格式化,完了将软盘挂载到linux中,并且从linux中拷贝了文件到软盘中,所以产生附加的不合法的目录文件项。
目录项中的关键成员:
DIR_Name:文件名(用于判断是否为目标文件)
DIR_FstClus:文件数据起始存储位置(用于判断读取位置)
DIR_FileSize:文件大小(用于确定读取的字节数)
如果一个文件占多个簇,那么他们的目录项不一定是连续存储的,可能离散存储的,所以就需要FAT表
FAT表-FAT12的数据组织核心:标识簇之间的向后关系,关系图记录了文件数据的先后关系。
FAT1和FAT2是相互备份的关系,数据内容完全一致。
FAT表是一个关系图,记录了文件数据的先后关系。
每一个FAT表项占用12比特。1.5字节
FAT表的前2个表项规定不使用。
FAT表中的先后关系:
以簇(扇区)为单位存储文件数据。
每个表项(vec[i]:FAT表中第i个表项)表示文件数据的实际位置(簇)
DIR_FstClust表示文件第0簇(扇区)的位置,
vec[DIR_FstClus]表示文件第1簇(扇区)的位置。
vec[vec[DIR_FstClus]]表示文件第2簇(扇区)的位置。
数据区中的每一簇在FAT表中都有一个对应的FAT表项.
试验:加载FAT12中的文件数据
步骤:
在根目录区查找目标文件对应的项。
获取目标文件的起始簇号和文件大小。
根据FAT表中记录的逻辑先后关系读取数据。
小贴士:
FAT表中的每个表项只占用12比特(1.5字节)
FAT表一共记录了BPB_BytsPerSec*9*2/3个表项(除以1.5)
可以使用一个short表示一个表项的值
如果表项值大于等于0xFF8,则说明已经到达最后一个簇。
如果表项值等于0xFF7,则说明当前簇已经损坏。
小贴士2
数据区起始簇(扇区)号为33,地址为0x4200 (偏移为2的数据就是此地址处的数据)
数据区起始地址所对应的编号为2(不为0)(对应了fat表第0表项和第一表项不能使用了(两个512字节?))
因此,DIR_FstClus对应的地址为:
0x4200+(DIR_FstClus-2)*512 //在计算第一簇的地址时,减去2
试验:读取指定文件的内容
#include <QtCore/QCoreApplication>#include <QFile>#include <QDataStream>#include <QDebug>#include <QVector>#include <QByteArray>#pragma pack(push)#pragma pack(1)struct Fat12Header{ char BS_OEMName[8]; ushort BPB_BytsPerSec; uchar BPB_SecPerClus; ushort BPB_RsvdSecCnt; uchar BPB_NumFATs; ushort BPB_RootEntCnt; ushort BPB_TotSec16; uchar BPB_Media; ushort BPB_FATSz16; ushort BPB_SecPerTrk; ushort BPB_NumHeads; uint BPB_HiddSec; uint BPB_TotSec32; uchar BS_DrvNum; uchar BS_Reserved1; uchar BS_BootSig; uint BS_VolID; char BS_VolLab[11]; char BS_FileSysType[8];};struct RootEntry{ char DIR_Name[11]; uchar DIR_Attr; uchar reserve[10]; ushort DIR_WrtTime; ushort DIR_WrtDate; ushort DIR_FstClus; uint DIR_FileSize;};#pragma pack(pop)void PrintHeader(Fat12Header& rf, QString p){ QFile file(p); if( file.open(QIODevice::ReadOnly) ) { QDataStream in(&file); file.seek(3); in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf)); rf.BS_OEMName[7] = 0; rf.BS_VolLab[10] = 0; rf.BS_FileSysType[7] = 0; qDebug() << "BS_OEMName: " << rf.BS_OEMName; qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec; qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus; qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt; qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs; qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt; qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16; qDebug() << "BPB_Media: " << hex << rf.BPB_Media; qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16; qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk; qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads; qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec; qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32; qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum; qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1; qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig; qDebug() << "BS_VolID: " << hex << rf.BS_VolID; qDebug() << "BS_VolLab: " << rf.BS_VolLab; qDebug() << "BS_FileSysType: " << rf.BS_FileSysType; file.seek(510); uchar b510 = 0; uchar b511 = 0; in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510)); in.readRawData(reinterpret_cast<char*>(&b511), sizeof(b511)); qDebug() << "Byte 510: " << hex << b510; qDebug() << "Byte 511: " << hex << b511; } file.close();}RootEntry FindRootEntry(Fat12Header& rf, QString p, int i){ RootEntry ret = {{0}}; QFile file(p); if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) ) { QDataStream in(&file); file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry)); in.readRawData(reinterpret_cast<char*>(&ret), sizeof(ret)); } file.close(); return ret;}RootEntry FindRootEntry(Fat12Header& rf, QString p, QString fn){ RootEntry ret = {{0}}; for(int i=0; i<rf.BPB_RootEntCnt; i++) { RootEntry re = FindRootEntry(rf, p, i); if( re.DIR_Name[0] != '\0' ) { int d = fn.lastIndexOf("."); QString name = QString(re.DIR_Name).trimmed(); if( d >= 0 ) { QString n = fn.mid(0, d); QString p = fn.mid(d + 1); if( name.startsWith(n) && name.endsWith(p) ) { ret = re; break; } } else { if( fn == name ) { ret = re; break; } } } } return ret;}void PrintRootEntry(Fat12Header& rf, QString p){ for(int i=0; i<rf.BPB_RootEntCnt; i++) { RootEntry re = FindRootEntry(rf, p, i); if( re.DIR_Name[0] != '\0' ) { qDebug() << i << ":"; qDebug() << "DIR_Name: " << hex << re.DIR_Name; qDebug() << "DIR_Attr: " << hex << re.DIR_Attr; qDebug() << "DIR_WrtDate: " << hex << re.DIR_WrtDate; qDebug() << "DIR_WrtTime: " << hex << re.DIR_WrtTime; qDebug() << "DIR_FstClus: " << hex << re.DIR_FstClus; qDebug() << "DIR_FileSize: " << hex << re.DIR_FileSize; } }}QVector<ushort> ReadFat(Fat12Header& rf, QString p){ QFile file(p); int size = rf.BPB_BytsPerSec * 9; uchar* fat = new uchar[size]; QVector<ushort> ret(size * 2 / 3, 0xFFFF); if( file.open(QIODevice::ReadOnly) ) { QDataStream in(&file); file.seek(rf.BPB_BytsPerSec * 1);
in.readRawData(reinterpret_cast<char*>(fat), size);
//将FAT表第i+1字节的低四位取出来,左移8位,低8位就空出来了,和第i个字节或运算 ,只取i+1字节的低四位作为第i字节的高四位,
//两个fat表项对应三个字节,
//i+1的高四位作为j+1项的低四个字节的,第i+1字节右移四位取它的高四位,第i+2字节左移四位那么低四位就空出来了,这低四位就用于i+1字节的高四位组在一起,于是得到第i+1项。
for(int i=0, j=0; i<size; i+=3, j+=2) { ret[j] = static_cast<ushort>((fat[i+1] & 0x0F) << 8) | fat[i]; ret[j+1] = static_cast<ushort>(fat[i+2] << 4) | ((fat[i+1] >> 4) & 0x0F); } } file.close(); delete[] fat; return ret; } QByteArray ReadFileContent(Fat12Header& rf, QString p, QString fn) { QByteArray ret; RootEntry re = FindRootEntry(rf, p, fn); if( re.DIR_Name[0] != '\0' ) { QVector<ushort> vec = ReadFat(rf, p); QFile file(p); if( file.open(QIODevice::ReadOnly) ) { char buf[512] = {0}; QDataStream in(&file); int count = 0; ret.resize(re.DIR_FileSize); for(int i=0, j=re.DIR_FstClus; j<0xFF7; i+=512, j=vec[j]) {file.seek(rf.BPB_BytsPerSec * (33 + j - 2));
//512*文件数据在第33扇区,起始偏移为2
in.readRawData(buf, sizeof(buf)); for(uint k=0; k<sizeof(buf); k++) { if( count < ret.size() ) { ret[i+k] = buf[k]; count++; } } } } file.close(); } return ret; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QString img = "E:\\data.img"; Fat12Header f12; qDebug() << "Read Header:"; PrintHeader(f12, img); qDebug() << endl; qDebug() << "Print Root Entry:"; PrintRootEntry(f12, img); qDebug() << endl; qDebug() << "Print File Content:"; QString content = QString(ReadFileContent(f12, img, "DELPHI.DT")); qDebug() << content; return a.exec();}
将三个字节表示的两个fat表项组合:
小结:FAT12根目录区记录了文件名,起始簇号和长度。
通过查找根目录区能够确定是否存在目标文件。
FAT12文件数据的组织使用了单链表的思想: 文件数据离散的分布于存储介质中,文件数据通过FAT项进行关联。
我们首先读取了fat12文件系统中的根目录区。
