第1章

通用格式设计

1 序


二进制文件的格式一般采用结构 文件头 + 核心内容

  • 文件头:标识文件的基本信息,其设计方法基本一致。
  • 核心内容:这部分是真正的文件数据内容。

2 目录


通用格式设计 的子部分

1.1 文件头



描述了文件的整体信息,常见的字段有魔数、检验码、版本号、文件大小等。

顾名思义,即是将文件的整体信息通常放在文件的开头。

在少数情况下,也会将文件头放在文件的尾部,也就是‘文件尾’,但是一般还是叫做 文件头

1 魔数


魔数作为文件格式的标识,一般用于识别文件是否程序所能处理的文件。其值可以随意选取,主要看设计者自身的喜好。

例如 zip 格式的魔数就是 “PK\x03\x04”,其中 PK 就是设计者 Philip Katz 的名字首字母。

一般程序在分析文件时,会先判断魔数的值是否匹配,不匹配就表明文件格式不正确。

需要注意的是,假如魔数正确,文件格式并非一定能够读取正确,还需要进一步判断。

2 检验码


文件头通常还会有个检验码,用于检验文件是否完整并且没有经过修改的。这个检验码可以使用 crc, 可以使用 md5,也可以使用其它算法。

这也是检查文件格式的正确性中的重要一步,因为即便是魔数相同,校验码所采用的算法不同,和校验码所作的位置不同,所计算的校验码,基本上是不一的,所以进一步就,能区分出文件格式是否正确。

3 版本号


文件头通常还会包含版本号。

版本号不同,就表明文件格式发生变化,可能变化很小,也可能变化很大;可能是某些字段的值在解释上发生变化,也可能是直接添加了一些结构。所以导致文件的读取方式可能会有所不同。

版本号有时只是单独一个数字,不断往上递增。有时也会拆分成两个数字,为主版本号和次版本号。主版本号修改,通常表示文件格式发生大变动。而次版本号修改,通常只是表示添加了一些小功能。

4 字节顺序


字节顺序在文件格式设计中至关重要。

字节顺序分为大端字节序和小端字节序。不同的机器字节序有可能不同,设计文件格式时需要考虑文件用什么字节序保存数据的。

读取和存储的字节序不一致就会导致程序出错。

5 通用结构


通用结构定义如下:

struct FileHeader{
    uint8 mMagic[4];    //魔数
    uint8 mHash[16];    //检验码
    uint32 mEndian;     //字节序
    uint32 mVersion;    //文件版本
    ...
};

1.2 段节格式



分段(或叫分节,后续默认叫法为分段)的二进制文件格式是比较常见的,比如编译器 GCC 输出的目标文件一般为 ELF 的分段文件。

1 整体结构


这类文件一般采用 文件头 + 分段 的结构:

file header 
section 0
section 1
....
section N

文件头设计方法在 文件头 中介绍,本章节主要描述分段式存储数据的方法。

2 分段方法


一般有两种结构:

  • 分散式:在分区的头部的描述分段的大小等信息。
  • 段表式:存在统一的结构描述分段的大小、相对偏移等信息。

分散式

分散式分结构在分区的头部的描述分段的大小等信息,然后各个分段首尾相连的,分段写入。

分段的每一个段的结构通常是:

tag + length
section data

tag 和 length 合起来是分区头部,描述分段中的数据标识,后面紧跟着体数据。

tag 一般是一个整数,用来标识分区的类型。不同的分区用不同的 tag 值表示,不同的分区种类可以使用不同的读取方式。

整体结构如下:

file header 
section 0
            tag     //section 0 的标识
            length  //section 0 的数据长度
            data[]  //section 0 的数据
section 1
....
section N

段表式

段表式的结构是有一个描述分段信息的段表,后面存储各个分区的数据。

主要优点就是,可以通过段描述表(简称段表)快速的访问分段的内容,其格式如下。

section table
section 0
section 1
....
section N

段表一般描述分段的类型、数据长度、相对偏移等,结构如下。

kind
length
offset

C 语言定义如下:

struct SegTabItem{
    uint32 kind;    //类型
    uint32 length;  //数据长度
    uint32 offset;  //相对偏移
};

整体结构如下:

file header 
section table
                kind    //section 0 的类型
                length  //section 0 的数据长度
                offset  //section 0 的相对偏移
                kind    //section 1 的类型
                length  //section 1 的数据长度
                offset  //section 1 的相对偏移
                ...
                kind    //section N 的类型
                length  //section N 的数据长度
                offset  //section N 的相对偏移
section 0
section 1
....
section N

1.3 TLV格式



1 简介


TLV 是一种可变的格式,由三个域构成:标识域(Tag)+长度域(Length)+值域(Value),简称TLV格式。

其中:

  • T 可以理解为 Tag 或 Type ,用于标识标签或者编码格式信息;
  • L 定义数值的长度;
  • V 表示实际的数值。

T 和 L 的长度固定,一般是2或4个字节,V 的长度由 Length 指定。

  • T 和 L 一般都是整数值。
  • V 可以存储整数、浮点、字符串、字节串,其类型是由格式定义者根据 T 的不同值,指定不同的类型。

2 基本结构


data 0
    T = 1
    L = 1
    V = 1
data 1
    T = 1
    L = 1
    V = 2
...
data N
    T = 1
    L = 1
    V = n

当然V中的数据也是可以嵌套,至于嵌套几层看设计者的规定的。

结构如下:

data 0
    T = 1
    L = 3
    V =[
            data x
            T = 1
            L = 1
            V = 1
        ]
data 1
    T = 1
    L = 1
    V = 2
...
data N
    T = 1
    L = 1
    V = n