前言
先说结论:ISO文件(光驱映像)本身没有格式。有格式的东西是光盘。
本质上和FLP文件(软驱映像)格式一样,ISO文件的格式可以认为是Flat Binary。
光盘定义
通常说硬盘的时候,一个扇区是512字节。但是在光盘上,一个扇区是2048字节。
光盘有虚拟扇区的定义,意义在于模拟硬盘,软盘等。一个虚拟扇区大小是512字节。
ISO-9660标准没有特别规定只使用何种字节序,但是会明确指出某个字段会使用何种字节序。某些字段甚至会给出小端序和大端序两份数值。
ISO-9660标准
在ISO-9660标准(即CDFS文件系统)下,光盘的前16个扇区不使用,第0x10个扇区开始,每个扇区存放一个卷描述符(Volume Descriptor)。
卷描述符的前7个字节的定义是一致的。
- +0x000处的一个字节是一个整数,用于描述该卷描述符的类型。
- +0x001处的五个字节是一个字符串,是一个签名标识符,应当是
CD001
。
- +0x006处的一个字节用于描述版本,目前只有一个版本,就是
1
。
卷描述符类型
卷描述符有以下类型:
值 |
描述 |
0 |
启动记录 |
1 |
主要卷描述符 |
2 |
补充卷描述符 |
3 |
卷分区描述符 |
4-254 |
保留未用 |
255 |
终止描述符 |
一般而言,启动记录必须在第0x11个扇区上,主要卷描述符必须在第0x10个扇区上。终止描述符用于表示光盘里的最后一个卷描述符。
启动记录
目前而言,光盘启动记录至今就只有一种规范,即El Torito
规范,即便是UEFI也遵守该规范来启动程序。
如无说明,则整数以小端序表示。
遵守El Torito
规范的启动记录所在扇区内容不多。
从第0x07至第0x26个字节,是一个EL TORITO SPECIFICATION
字符串。第0x27至第0x46个字节保留不用。
第0x47至第0x4A个字节是一个32位的整数,用于描述启动目录(Booting Catalog)所在扇区。
启动目录
启动目录本质是“启动目录项”的数组,每个项均为32字节。
第一项为“验证项”(Validation Entry):
偏移量 |
类型 |
描述 |
0x00 |
字节 |
项标识符,验证项的标识符必须为0x01 |
0x01 |
字节 |
平台标识符,0表示80x86,1表示PowerPC,2表示Mac |
0x02 |
16位字 |
保留不用 |
0x04 |
字符串 |
该光盘的制造商/开发者 |
0x1C |
16位整数 |
校验和 |
0x1E |
16位整数 |
0xAA55,注意校验和包含了0xAA55 |
随后是初始项/默认项(Initial/Default Entry):
偏移量 |
类型 |
描述 |
0x00 |
字节 |
启动指示量:0x88表示可启动,0x00表示不可启动 |
0x01 |
字节 |
启动媒介类型。高四位保留不用,低四位表示类型:0为光盘模式(无模拟),1为模拟1.2M软盘,2为模拟1.44M软盘,3为模拟2.88M软盘,4为模拟硬盘 |
0x02 |
16位整数 |
启动段选择子。对于平坦内存模型的处理器上,表示启动地址/16。为0时,则段选择子为0x7C0 |
0x04 |
字节 |
系统类型 |
0x05 |
字节 |
保留 |
0x06 |
16位整数 |
启动时固件加载到内存里的模拟扇区数(一个扇区512字节) |
0x08 |
32位整数 |
启动扇区号 |
0x0C |
字节数组 |
保留不用 |
在初始项之后是节(Section),每个节带一个节头(Section Header)。但需要注意的是启动记录里未必有节。
偏移量 |
类型 |
描述 |
0x00 |
字节 |
节头指示量: 0x90表示这是个节,0x91表示这是最后一节 |
0x01 |
字节 |
平台标识符 |
0x02 |
16位字 |
该节中有多少个项 |
0x04 |
字符串 |
节ID |
节里放的是节项(Section Entry),内容和初始项相似,重复内容不在赘述,可参考初始项。
偏移量 |
类型 |
描述 |
0x00 |
字节 |
启动指示量:0x88表示可启动,0x00表示不可启动 |
0x01 |
字节 |
启动媒介类型。低四位表示类型;第4位保留不用;第5位表示该项有节项拓展(Section Entry Extension);第6位表示映像包含ATAPI驱动;第7位表示映像包含SCSI驱动 |
0x02 |
16位整数 |
启动段选择子 |
0x04 |
字节 |
系统类型 |
0x05 |
字节 |
保留 |
0x06 |
16位整数 |
启动时固件加载到内存里的模拟扇区数 |
0x08 |
32位整数 |
启动扇区号 |
0x0C |
字节 |
选择标准:0表示无标准,1表示IBM的语言与版本信息,其他值保留不用 |
0x0D |
字节数组 |
发行者的选择标准信息,如果0x13个字节不够,可以使用节项拓展 |
节项拓展定义如下:
偏移量 |
类型 |
描述 |
0x00 |
字节 |
节项拓展指示量:必须是0x44 |
0x01 |
字节 |
第五位置位时表示后面还有节项拓展,复位表示无节项拓展。其余位保留不用 |
0x02 |
字节数组 |
发行者的选择标准信息,如果0x1E个字节不够,可以继续使用节项拓展 |
简单总结一下,El Torito
标准下,一个启动目录里,每个项均为32个字节,以验证项(Validation Entry)开头,紧跟一个初始项/默认项(Initial/Default Entry)。启动目录里可能会有一个或多个节,节头的指示量会表示这是否是最后一个节,以及该节之内有多少个节项。每个节项可能会有一个或多个扩展项,见第1个字节的第5位,节项扩展的第1节第5位会指示是否位最后一个扩展项。
最后,由于默认光盘启动的入口是0x7C0:0
,不是0:0x7C00
,因此如果你的实模式启动代码要兼容光盘的话,必须用远跳把cs
段选择子切到0x0上。EFI启动无需考虑这一条。
主要卷描述符
如果需要枚举光盘里的文件,就有必要解析这个卷描述符了。
偏移量 |
类型 |
描述 |
0x000 |
字节 |
卷描述符类型标识符,此处为0x00 |
0x001 |
字符串 |
卷描述符标识符,必须为CD001 |
0x006 |
字节 |
版本,一直为0x01 |
0x007 |
字节 |
保留不用,一直为0 |
0x008 |
字符串 |
系统标识符 |
0x028 |
字符串 |
卷标识符 |
0x048 |
|
保留不用,一直为0 |
0x050 |
32位整数-小大端 |
卷内的扇区数量 |
0x058 |
|
保留不用,一直为0 |
0x078 |
16位整数-小大端 |
卷内磁盘数量 |
0x07C |
16位整数-小大端 |
卷内磁盘序号 |
0x080 |
16位整数-小大端 |
扇区大小 |
0x084 |
32位整数-小大端 |
路径表大小 |
0x08C |
32位小端整数 |
小端路径表扇区号 |
0x090 |
32位小端整数 |
小端可选路径表扇区号 |
0x094 |
32位大端整数 |
大端路径表扇区号 |
0x098 |
32位大端整数 |
大端可选路径表扇区号 |
0x09C |
目录结构 |
根目录 |
0x0BE |
字符串 |
卷集 |
0x13E |
字符串 |
出版社 |
0x1BE |
字符串 |
数据准备者 |
0x23E |
字符串 |
应用 |
0x2BE |
字符串 |
版权所有者 |
0x2E3 |
字符串 |
摘要 |
0x308 |
字符串 |
引用 |
0x32D |
字符串 |
创建时间 |
0x33E |
字符串 |
修改时间 |
0x34F |
字符串 |
过期时间 |
0x360 |
字符串 |
有效时间 |
0x371 |
字节 |
文件结构版本 |
0x372 |
字节 |
保留不用,一直为0 |
0x373 |
|
随意使用,ISO不使用 |
0x573 |
|
由ISO标准保留 |
目录
我们具体看+0x09C
偏移的根目录项,其类型为目录结构,定义如下:
偏移量 |
类型 |
描述 |
0x00 |
字节 |
记录长度 |
0x01 |
字节 |
扩展属性长度 |
0x02 |
32位整数-小大端 |
扇区号 |
0x0A |
32位整数-小大端 |
数据大小 |
0x12 |
字节数组 |
日期 |
0x19 |
字节 |
文件标志位 |
0x1A |
字节 |
Interleaved模式下,文件大小单位。非Interleaved模式下保留不用 |
0x1B |
字节 |
Interleaved模式下,文件间隙大小。非Interleaved模式下保留不用 |
0x1C |
16位整数-小大端 |
卷序列号 |
0x20 |
字节 |
文件名长度 |
0x21 |
字符串 |
文件名 |
可变 |
字节 |
2字节对齐:如果字符串长度为奇数,则该字节不用 |
可变 |
|
系统使用:ISO-9660标准的扩展定义,如SUSP协议,RRIP协议等 |
每个目录项最小34字节,最大254个字节,以2字节对齐紧凑排列。ISO-9660不允许目录结构跨扇区,故扇区结尾不足以存放一个目录结构时,需要把目录结构延后到下一个扇区存储。
前两个目录结构均为34字节:前者的文件名长度为0,表示当前目录,即.
;后者的文件名长度为1,表示上一个目录,即..
。
后续目录结构存放的目录之下的所有文件。
文件标志位定义如下:
位 |
描述 |
0 |
文件是否隐藏 |
1 |
文件是否为目录 |
2 |
文件是为“关联文件” |
3 |
文件是否包含扩展属性 |
4 |
文件是否包含所有者权限信息 |
5,6 |
保留不用 |
7 |
该目录项未完成描述一个文件,比如超过4G的文件 |
文件日期的结构如下:
字节 |
描述 |
0 |
年-1900 |
1 |
月 |
2 |
日 |
3 |
时 |
4 |
分 |
5 |
秒 |
6 |
相对于格林威治时区的偏移量,以15分钟为单位 |
路径表
ISO-9660还定义了个路径表,这张表里包含的是目录关联表,不包含文件。一式两份,大小端各一份,定义如下:
偏移量 |
类型 |
描述 |
0x0 |
字节 |
目录名长度 |
0x1 |
字节 |
扩展属性长度 |
0x2 |
32位整数 |
扇区号 |
0x6 |
16位整数 |
上级目录索引 |
0x8 |
字符串 |
目录名 |
可变 |
字节 |
2字节对齐:如果字符串长度为偶数,则该字节不用 |
每个路径表项最小10字节,以2字节对齐紧凑排列。ISO-9660允许路径表结构跨扇区。
注意:目录索引以1为底。
由于索引号是16位数,以1为底,故标准的ISO-9660光盘不得存放多于65536个目录。
扇区存储的内容为目录结构表。
查找文件
由于存在着两套结构,因此查找文件的时候也有两个套路。
一种方式是递归式地照着目录结构顺路径序查找。
另一种方式是根据路径表逆路径序查找。
两者的理论时间复杂度均为准矩形复杂度,但前者的时间复杂度是$O(x\cdot\log(y+z))$,而后者是$O(x\cdot\log(y)+\log(z))$,其中$x$是路径深度,$y$为目录数量,$z$为文件数量。(注意这里已经按名称排过序了,因此可以结合strncmp
函数来二分查找)
在理论层面上,显然路径表要有优势。在实践层面上,由于路径表的体积比目录结构要小得多,走路径表来查找文件的方式可以更好地利用缓存来加速搜索。
ISO-13346标准
ISO-13346即UDF光盘文件系统,但篇幅所限,本文并不想讲这个标准。
ISO-13346通常可以和ISO-9660兼容,但仅支持ISO-9660的光盘读取器在枚举文件的时候往往只会枚举到一个README.TXT,里面写着:
This disc contains a "UDF" file system and requires an operating system
that supports the ISO-13346 "UDF" file system specification.
ISO-13346标准与El Torito
兼容,因此固件仍能以老套路加载ISO-13346光盘里的OS启动器,但这个光盘里的OS启动器必须要能正确解析ISO-13346格式。
总结
光盘有ISO-9660和ISO-13346两种文件格式,前者为CDFS文件系统,后者为UDF文件系统。
以光盘启动时,入口地址一般为0x7C0:0
,而非0:0x7C00
。因此如果你的启动器需要兼容光盘启动,需要用远跳指令修正cs
段选择子。
本文未讲述UEFI启动时El Torito
的工作原理,以后和ISO-13346一并解析。