复合文档的二进制存储格式.docx
- 文档编号:9837592
- 上传时间:2023-02-06
- 格式:DOCX
- 页数:22
- 大小:126.83KB
复合文档的二进制存储格式.docx
《复合文档的二进制存储格式.docx》由会员分享,可在线阅读,更多相关《复合文档的二进制存储格式.docx(22页珍藏版)》请在冰豆网上搜索。
复合文档的二进制存储格式
复合文档的二进制存储格式研究(word,xls,ppt...)
前言
复合文档(CompoundDocument)是一种不仅包含文本而且包括图形、电子表格数据、声音、视频图象以及其它信息的文档。
可以把复合文档想象成一个所有者,它装着文本、图形以及多媒体信息如声音和图象。
目前建立复合文档的趋势是使用面向对象技术,在这里,非标准信息如图像和声音可以作为独立的、自包含式对象包含在文档中。
MicrosoftWindows就是使用这种技术,叫做“OLE2storagefileformat”或“MicrosoftOfficecompatiblestoragefileformat”。
当然Excel、Word等都是用这种格式存储的。
本文主要研究复合文档的二进制结构。
目录
第一章 仓库与流(StoragesandStreams)
第二章 扇区与扇区链(SectorsandSectorChains)
第三章 复合文档头(CompoundDocumentHeader)
第四章 扇区配置(SectorAllocation)
第五章 短流(Short-Streams)
第六章 目录(Directory)
第七章 Excel文件实例剖析
第一章 仓库与流
复合文档的原理就像一个文件系统(文件系统:
如FAT与NTFS)。
复合文档将数据分成许多流(Streams),这些流又存储在不同的仓库(Storages)里。
将复合文档想象成你的D盘,D盘用的是NTFS(NTFileSystem)格式,流就相当于D盘里的文件,仓库就相当于D盘里的文件夹。
流和仓库的命名规则与文件系统相似,同一个仓库下的流及仓库不能重名,不同仓库下可以有同名的流。
每个复合文档都有一个根仓库(rootstorage)。
例:
第二章 扇区与扇区链
2.1扇区与扇区标识
所有的流又分成更小的数据块,叫做数据扇区(sectors)。
Sectors可能包含控制数据或用户数据。
整个文件由一个头(Header)结构以及其后的所有Sectors组成。
Sectors的大小在头中确定,且每个Sectors的大小都相同。
以下为示意图:
HEADER
SECTOR0
SECTOR1
SECTOR2
SECTOR3
SECTOR4
SECTOR5
SECTOR6
┆
Sectors简单的以其在文件中的顺序列举,一个扇区的索引(从0开始)叫做扇区标识(SID:
sectoridentifier)。
SID是一个有符号的32位的整型值。
如果一个SID的值非负,就表示真正存在的那个Sector;如果为负,就表示特殊的含义。
下表给出有效的特殊SID:
SID Name Meaning
–1 FreeSID 空闲sector,可存在于文件中,但不是任何流的组成部分。
–2 EndOfChainSID SID链的结束标记(见2.2节)
–3 SATSID 此Sector用于存放扇区配置表(SAT)(见4.2节)
–4 MSATSID 此Sector用于存放主扇区配置表(MSAT)(见4.1节)
2.2扇区链与扇区标识链
用于存储流数据的所有Sectors的列表叫做扇区链(SectorChain)。
这些Sectors可以是无序的。
因此用于指定一个流的Sectors的顺序的SID数组就称为SIDchain。
一个SIDchain总是以EndOfChainSID(-2)为结束标记。
例:
一个流由4个Sector组成,其SID链为[1,6,3,5,–2]。
流的SID链是通过扇区配置表构建的(见4.2节),但短流和以下两种内部流除外:
1.主扇区配置表,其从自身构建SID链(每个扇区包含下一个扇区的SID)。
2.扇区配置表,其通过主扇区配置表构建SID链。
第三章 复合文档头
3.1复合文档头的内容
复合文档头在文件的开始,且其大小必定为512字节。
这意味着第一个Sector的开始相对文件的偏移量为512字节。
复合文档头的结构如下:
Offset Size Contents
0 8 复合文档文件标识:
D0HCFH11HE0HA1HB1H1AHE1H
8 16 此文件的唯一标识(不重要,可全部为0)
24 2 文件格式修订号(一般为003EH)
26 2 文件格式版本号(一般为0003H)
28 2 字节顺序规则标识(见3.2):
:
FEHFFH=Little-Endian FFHFEH=Big-Endian
30 2 复合文档中sector的大小(ssz),以2的幂形式存储,sector实际大小为s_size=2ssz字节
(一般为9即512字节,最小值7即128字节)
32 2 short-sector的大小(见5.1),以2的幂形式存储,short-sector实际大
小为s_s_size=2sssz字节(一般为6即64字节,最大为sector的大小)
34 10 Notused
44 4 用于存放扇区配置表(SAT)的sector总数
48 4 用于存放目录流的第一个sector的SID(见6)
52 4 Notused
56 4 标准流的最小大小(一般为4096bytes),小于此值的流即为短流。
60 4 用于存放短扇区配置表(SSAT)的第一个sector的SID(见5.2),或为–2(EndOfChainSID)如不存在。
64 4 用于存放短扇区配置表(SSAT)的sector总数
68 4 用于存放主扇区配置表(MSAT)的第一个sector的SID(见4.1),或为–2(EndOfChainSID)若无附加的sectors。
72 4 用于存放主扇区配置表(MSAT)的sector总数
76 436 存放主扇区配置表(MSAT)的第一部分,包含109个SID。
3.2字节顺序(ByteOrder)
文件数据的二进制存储有两种方法Little-Endian和Big-Endian,但实际应用中只使用Little-Endian方法即:
低位8字节存放在地址的低位,高位8字节存放在地址的高位。
例:
一个32位的整数13579BDFH(转为十进制即324508639),以Little-Endian存放为DFH9BH57H13H,以Big-Endian存放为13H57H9BHDFH。
(H下标表示十六进制)
3.3扇区偏移量
从头中的信息可以计算出一个sector的偏移量(offset),公式为:
sec_pos(SID)=512+SID?
s_size=512+SID?
2ssz
例:
ssz=10andSID=5:
sec_pos(SID)=512+SID?
2ssz=512+5?
210=512+5?
1024=5632.
第四章 扇区配置
4.1主扇区配置表
主扇区配置表(MSAT:
mastersectorallocationtable)是一个SID数组,指明了所有用于存放扇区配置表(SAT:
sectorallocationtable)的sector的SID。
MSAT的大小(SID个数)就等于存放SAT的sector数,在头中指明。
MSAT的前109个SID也存放于头中,如果一个MSAT的SID数多余109个,那么多出来的SID将存放于sector中,头中已经指明了用于存放MSAT的第一个sector的SID。
在用于存放MSAT的sector中的最后一个SID指向下一个用于存放MSAT的sector,如果没有下一个则为EndOfChainSID(-2)。
存放MSAT的sector的内容:
(s_size表示sector的大小)
Offset Size Contents
0 s_size-4 MSAT的(s_size-4)/4个SID的数组
s_size-4 4 下一个用于存放MSAT的sector的SID,或-2(已为最后一个)
最后一个存放MSAT的sector可能未被完全填满,空闲的地方将被填上FreeSID(-1)。
例:
一个复合文档需要300个sector用于存放SAT,头中指定sector的大小为512字节,这说明一个sector可存放128个SID。
MAST有300个SID,前109个放于头中,其余的191个将要占用2个sector来存放。
此例假定第一个存放MSAT的sector为sector1,则sector1包含127个SID。
第128个SID指向一个用于存放MSAT的sector,假定为sector6,则sector6包含剩下的64个SID(最后一个SID为-2,其他的值为-1)。
4.2扇区配置表
扇区配置表(SAT:
sectorallocationtable)是一个SID数组,包含所有用户流(短流除外)和内部控制流(theshort-streamcontainerstream,见5.1,theshort-sectorallocationtable,见5.2,andthedirectory,见7)的SID链。
SAT的大小(SID个数)就等于复合文档中所存在的sector的个数。
SAT的建立就是通过按顺序读取MSAT中指定的sector中的内容。
存放SAT的sector的内容:
(s_size表示sector的大小)
Offset Size Contents
0 s_size SAT的s_size/4个SID的数组
当通过SAT为一个流创建SID链时,SAT数组的当前位置(arrayindex)表示的就是当前的sector,而该位置存放的SID则指向下一个sector。
SAT可能在任意位置包含FreeSID(-1),这些sector将不被流使用。
如果该位置包含EndOfChainSID(-2)表示一个流的结束。
如果sector用于存放SAT则为SATSID(-3),同样用于存放MSAT则为MSATSID(-4)。
一个SID链的起点从用户流的目录入口(directoryentry,见6.2节)或头(内部控制流)或目录流本身获得。
例:
一个复合文档包含一个用于存放SAT的sector(sector1)和2个流。
Sector1的内容如下图:
在位置1其值为-3,表明Sector1是SAT的一部分。
其中一个流为内部目录流,假定头中指定其开始为Sector0,SAT中位置0的值为2,位置2的值为3,位置3的值为-2。
因此目录流的SID链为[0,2,3,–2],即此目录流存放于3个sector中。
目录中包含一个用户流的入口假定为sector10,从图中可看出此流的SID链为[10,6,7,8,9,–2]。
第五章 短流
5.1短流存放流
当一个流的大小小于指定的值(在头中指定),就称为短流(short-stream)。
短流并不是直接使用sector存放数据,而是内含在一种特殊的内部控制流——短流存放流(short-streamcontainerstream)中。
短流存放流象其他的用户流一样:
先从目录中的根仓库入口(rootstorageentry)获得第一个使用的sector,其SID链从SAT中获得。
然后此流将其所占用的sectors分成short-sector,以便用来存放短流。
此处也许较难理解,我们来打个比方:
既然流组成符合文档,而短流组成短流存放流,这两者是相似的。
把短流存放流当作复合文档,那么短流对应流,short-sector对应sector,唯一的不同是复合文档有一个头结构,而短流存放流没有。
short-sector的大小在头中已经指定,因此可根据SID计算short-sector相对于短流存放流的偏移量(offset)。
公式为:
short_s_pos(SID)=SID?
short_s_size=SID?
2sssz
例:
sssz=6andSID=5:
short_s_pos(SID)=SID?
2sssz=5?
26=5?
64=320.
5.2短扇区配置表
短扇区配置表(SSAT:
short-sectorallocationtable)是一个SID数组,包含所有短流的SID链。
与SAT很相似。
用于存放SSAT的第一个sector的SID在头中指定,其余的SID链从SAT中获得。
存放SSAT的sector的内容:
(s_size表示sector的大小)
Offset Size Contents
0 s_size SSAT的s_size/4个SID的数组
SSAT的用法与SAT类似,不同的是其SID链引用的是short-sector。
第六章 目录
6.1目录结构
目录(directory)是一种内部控制流,由一系列目录入口(directoryentry)组成。
每一个目录入口都指向复合文档的一个仓库或流。
目录入口以其在目录流中出现的顺序被列举,一个以0开始的目录入口索引称为目录入口标识(DID:
directoryentryidentifier)。
如下图所示:
DIRECTORYENTRY0
DIRECTORYENTRY1
DIRECTORYENTRY2
DIRECTORYENTRY3
...
目录入口的位置不因其指向的仓库或流的存在与否而改变。
如果一个仓库或流被删除了,其相应的目录入口就标记为空。
在目录的开始有一个特殊的目录入口,叫做根仓库入口(rootstorageentry),其指向根仓库。
目录将每个仓库的直接成员(仓库或流)放在一个独立的红黑树(red-blacktree)中。
红黑树是一种树状的数据结构,本文仅简单介绍一下,详细情况请参考有关资料。
建构一个Red-Blacktree的规则:
1.每个节点(node)的颜色属性不是红就是黑。
2.根节点一定是黑的。
3.如果某个节点是红的,那它的子节点一定是黑的。
4.从根节点到每个叶节点的路径(path)必须有相同数目的黑节点。
ex:
B (用图形来解说第4点,从根节点
/ \ 到最底层的node,你会发现每个
B B path都恰好有3个blacknode)
/\ / \
B B R B
/ /\ /\
R B BR R
/\
R R
注意并不总是执行上述规则。
安全的方法是忽略节点的颜色。
例:
以第一章中的图为例
1.根仓库入口描述根仓库,它不是任何仓库入口的成员,因此无需构建红黑树。
2.根仓库的所有直接成员(“Storage1”,“Storage2”,“Stream1”,“Stream2”,“Stream3”,和“Stream4”)将组成一棵红黑树,其根节点的DID存放于根仓库入口中。
3.仓库Storage1只有一个成员Stream1,Stream1构成一棵红黑树,此树只有一个节点。
Storage1的目录入口包含Stream1的DID。
4.仓库Storage2包含3个成员“Stream21”,“Stream22”,和“Stream23”。
这3个成员将构建一棵红黑树,其根节点的DID存放于Storage2的目录入口中。
这种存放规则将导致每个目录入口都包含3个DID:
1.在包含此目录入口的红黑树中,此目录入口的左节点的DID。
2.在包含此目录入口的红黑树中,此目录入口的右节点的DID。
3.若此目录入口表示一个仓库,则还包含此仓库的直接成员所组成的另一颗红黑树的根节点的DID。
在构建红黑树的过程中,一个节点究竟作为左还是右,是通过比较其名字来判断的。
一个节点比另一个小是指其名字的长度更短,如长度一样,则逐字符比较。
规定:
左节点<根节点<右节点。
6.2目录入口
一个目录入口的大小严格地为128字节,计算其相对目录流的偏移量的公式为:
dir_entry_pos(DID)=DID?
128。
目录入口的内容:
Offset Size Contents
0 64 此入口的名字(字符数组),一般为16位的Unicode字符,
以0结束。
(因此最大长度为31个字符)
64 2 用于存放名字的区域的大小,包括结尾的0。
(如:
一个名字右5个字符则此值为(5+1)?
2=12)
66 1 入口类型:
00H=Empty 03H=LockBytes(unknown) 01H=Userstorage
04H=Property(unknown) 02H=Userstream05H=Rootstorage
67 1 此入口的节点颜色:
00H=Red 01H=Black
68 4 其左节点的DID(若此入口为一个userstorageorstream) 若没有左节点就为-1。
72 4 其右节点的DID(若此入口为一个userstorageorstream), 若没有右节点就为-1。
76 4 其成员红黑树的根节点的DID(若此入口为storage),其他为-1。
80 16 唯一标识符(若为storage)(不重要,可能全为0)
96 4 用户标记(不重要,可能全为0)
100 8 创建此入口的时间标记。
大多数情况都不写。
108 8 最后修改此入口的时间标记。
大多数情况都不写。
116 4 若此为流的入口,指定流的第一个sector或short-sector的SID,若此为根仓库入口,指定短流存放流的第一个sector的SID,
其他情况,为0。
120 4 若此为流的入口,指定流的大小(字节)若此为根仓库入口,指定短流存放流的大小
(字节)其他情况,为0。
124 4 Notused
时间标记(timestamp)是一个符号的64位的整数,表示从1601-01-0100:
00:
00开始的时间值。
此值的单位为10-7秒。
当计算时间标记是要注意闰年。
例:
时间标记值为01AE408B10149C00H
计算步骤 公式 结果
转为十进制 t0= 121,105,206,000,000,000
化成秒的余数 rfrac=t0mod107 rfrac=0
化成秒的整数 t1=t0/107 t1= 12,110,520,600
化成分的余数 rsec=t1mod60 rsec=0
化成秒的整数 t2=t1/60
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 复合 文档 二进制 存储 格式