COFF文件格式
COFF文件就是.obj文件,链接器使用。
1.整体结构
下图显示了COFF文件的整体结构:
从图中可以看出,COFF文件结构比PE文件要简单很多,而二者FileHeader是一致的。
2.局部结构
2.1 文件头
定位:在COFF文件的开头
偏移 | 大小 | 域 | 描述 |
---|---|---|---|
0 | 2 | Machine | 标识目标机器类型的数字 |
2 | 2 | NumberOfSections | 节表项的数目 |
4 | 4 | TimeDateStamp | 时间戳 |
8 | 4 | PointerToSymbolTable | 符号表的文件指针 |
12 | 4 | NumberOfSymbols | 符号表中的元素数目 |
16 | 2 | SizeOfOptionalHeader | COFF文件都为0 |
18 | 2 | Characteristics | 文件属性 |
2.2 节表
定位:必须紧跟在FileHeader之后,否则链接器找不到。
偏移 | 大小 | 域 | 描述 |
---|---|---|---|
0 | 8 | Name | 节区名字,目标文件中如果名字很长,导致8字节存不下, 可以将节名字变成/offset,这种形式,offset对应字符串表中偏移。 |
8 | 4 | VirtualSize | obj文件始终为0 |
12 | 4 | VirtualAddress | obj文件中大多数为0,不为0的话重定位时需要减去 |
16 | 4 | SizeOfRawData | 对于obj文件,节区的大小 |
20 | 4 | PointerToRawData | 节区的文件指针 |
24 | 4 | PointerToRelocations | obj节中重定位项的文件指针,PE有重定位表,不需要这个 |
28 | 4 | PointerToLinenumbers | 一般都为0,微软已不建议obj中保留行号信息 |
32 | 2 | NumberOfRelocations | obj节中重定位项个数 |
34 | 2 | NumberOfLinenumbers | 一般都为0,微软已不建议obj中保留行号信息 |
36 | 4 | Characteristics | 描述节特征的标志 |
一些对于obj文件节区有用的标志字段:
标志 | 值 | 描述 |
---|---|---|
IMAGE_SCN_CNT_CODE | 0x00000020 | 此节包含可执行代码 |
IMAGE_SCN_CNT_INITIALIZED_DATA | 0x00000040 | 此节包含已初始化的数据 |
IMAGE_SCN_CNT_UNINITIALIZED_DATA | 0x00000080 | 此节包含未初始化的数据 |
IMAGE_SCN_LNK_INFO | 0x00000200 | 此节包含注释或者其它信息 |
IMAGE_SCN_LNK_REMOVE | 0x00000800 | 此节不会成为最终形成的映像文件 的一部分 |
IMAGE_SCN_LNK_COMDAT | 0x00001000 | 此节包含 COMDAT 数据 |
IMAGE_SCN_GPREL | 0x00008000 | 此节包含通过全局指针(GP)来引 用的数据 |
IMAGE_SCN_ALIGN_1BYTES | 0x00100000 | 按 1 字节边界对齐数据,有很多类似的标志 这里不全部列举了 |
IMAGE_SCN_LNK_NRELOC_OVFL | 0x01000000 | 此节包含扩展的重定位信息 |
[^IMAGE_SCN_LNK_NRELOC_OVFL]: IMAGE_SCN_LNK_NRELOC_OVFL 标志表明节中重定位项的个数超出了节头中为每个 节保留的 16 位所能表示的范围。如果设置了此标志并且节头中的 NumberOfRelocations 域的值是 0xffff,那么实际的重定位项个数被保存在第一个重 定位项的 VirtualAddress 域(32 位)中。如果设置了 IMAGE_SCN_LNK_NRELOC_OVFL 标志但节中的重定位项的个数少于 0xffff,则表示出现了错误。
[^节名中的$符号]: obj文件的节名字中出现$符号,表示这些个节是连续一组,最终生成可执行文件时会去掉$符号后面的字符,并将这些节合并。
2.3 重定位表
2.3.1 重定位项结构
对于目标文件中的每个节,都有一个由长度固定的记录组成的数组来保存此节的COFF 重定位信息。
定位:由PointerToRelocations指出在文件中的偏移,由NumberOfRelocations指出包含的需要重定位地址的数量。
数组的每个元素格式如下:
偏移 | 大小 | 域 | 描述 |
---|---|---|---|
0 | 4 | VirtualAddress | 需要进行重定位的代码或数据的地址。这是从节开头算起的偏移,加上节的 RVA/Offset 域的值。 |
4 | 4 | SymbolTableIndex | 符号表的索引(从 0 开始),这个符号给出了用于重定位的地址。 |
8 | 2 | Type | 重定位类型,与指令集相关 |
每个重定位项对应一个符号index,利用index可以得到这个符号的具体定义信息。
[^特殊符号]: 如果 SymbolTableIndex 域指定的符号存储类别为 IMAGE_SYM_CLASS_SECTION, 那么这个符号的地址就是节的地址。这个节通常就在同一个文件中,除非这个目标文 件是档案(库)文件的一部分。如果是这种情况的话,那么这个节可能位于档案文件 中与当前目标文件的档案文件成员名称相同的任何其它目标文件中。(与档案文件成 员名称的联系用于链接生成导入表,即.idata 节。)
2.3.2 重定位类型
这里只贴出了x64-86指令集的重定位类型:
枚举 | 值 | 描述 |
---|---|---|
MAGE_REL_AMD64_ABSOLUTE | 0x0000 | 重定位被忽略 |
IMAGE_REL_AMD64_ADDR64 | 0x0001 | 重定位目标的64位 VA |
IMAGE_REL_AMD64_ADDR32 | 0x0002 | 重定位目标的32位 VA |
IMAGE_REL_AMD64_ADDR32NB | 0x0003 | 重定位目标的32位RVA |
IMAGE_REL_AMD64_REL32 | 0x0004 | 相对于重定位目标的32位地址 |
MAGE_REL_AMD64_REL32_x | 0x0005 ~ 0x0009 | 相对于距重定位目标x字节处的32位地址 |
2.4 符号表
2.4.1 普通符号结构
定位:FileHeader -> PointerToSymbolTable字段指出在文件中的偏移。
符号表是一个由记录组成的数组,每个记录长 18 字节。它们或者是标准符号表记录,或者是辅助符号表记录。标准符号表记录定义了一个符号或名称,格式如下:
偏移 | 大小 | 域 | 描述 |
---|---|---|---|
0 | 8 | Name | 符号名字,解析方式在注解 |
8 | 4 | Value | 通常表示目标符号的定义地址,具体由ectionNumber 和StorageClass域决定 |
12 | 2 | SectionNumber | 通常这个带符号整数是节表的索引(从 1 开始),用以标识定义此符号的节 这个字段还包括一些特殊值,如符号未在当前文件中定义的情况 |
14 | 2 | Type | 符号类型:0x20函数,0x0非函数 |
16 | 1 | StorageClass | 存储类别的枚举 |
17 | 1 | NumberOfAuxSymbols | 跟在普通符号后面的辅助符号表项的个数 |
2.4.2 符号名字解析规则
符号名字有8个字节的联合体组成,解析的是否先判断前4个字节组成的整数是否为0:
- 如果不为0,说明这个符号名字长度不超过8字节,这个字节数组就是符号真实名字。
- 如果为0,后面的4字节整数就是一个在字符串表中的索引,真是符号名在字符串表中。
2.4.3 SectionNumber字段的特殊值
通常符号表项中的 SectionNumber 域是节表的索引(从 1 开始)。但是这个域是带符号整数,因此它可以为负值。下面这些小于 1 的值有特殊含义:
枚举 | 值 | 描述 |
---|---|---|
IMAGE_SYM_UNDEFINED | 0 | 外部符号,未在本文件中定义的符号 |
IMAGE_SYM_ABSOLUTE | -1 | 绝对符号(不可重定位),并且不是地址 |
IMAGE_SYM_DEBUG | -2 | 此符号提供普通类型信息或者调试信息,但它并不对应于某一个节 Microsoft 的工具将.file 记录(存储类别为 FILE)设置为这个值 |
2.4.4 StorageClass存储类别
符号表中的StorageClass域指出符号具体的存储类别,2字节无符号整数。这个值影响了value字段的含义,下面贴出一些比较常见符号存储类别:
枚举 | 值 | 描述 |
---|---|---|
IMAGE_SYM_CLASS_EXTERNAL | 0x2 | 如果 SectionNumber域 0(IMAGE_SYM_UNDEFINED),那么 Value 域给出大小 如果 SectionNumber域不为0,value域给出节中的偏移 |
IMAGE_SYM_CLASS_STATIC | 0x3 | 符号在节中的偏移。如果 Value 域为 0,那么此符号表示节名,一般数据会使用这个 |
其实还有一些表示信息的符号,如定义了函数首尾的符号,以及源码文件信息等符号,这里没有贴出。
2.4.5 辅助符号表记录
如果一个普通符号的NumberOfAuxSymbols不为0,就会包含若干个辅助符号项,每个辅助符号都是18个字节,不过域的含义不一定相同,取决于普通符号的一些域。
2.4.5.1 函数定义
如果一个普通符号拥有下列属性:存储类别为 EXTERNAL(2)、Type 域的值表明它是一个函数(0x20)以及 SectionNumber 域的值大于 0,它就标志着函数的开头。能够定义函数的普通符号后面跟着如下格式的辅助符号表记录格式如下:
偏移 | 大小 | 域 | 描述 |
---|---|---|---|
0 | 4 | TagIndex | 相应的.bf(函数开头)记录在符号表中的索引 |
4 | 4 | TotalSize | 函数经编译后生成的可执行代码的大小 如果此函数单独成节,那么根据对齐值的不同,节头中的 SizeOfRawData 域可能大于或等于这个域 |
8 | 4 | PointerToLinenumber | 如果此函数存在行号记录,那么这个值表示它的第一个 COFF 行号记录的文件偏移 如果不存在,那么这个值为 0 |
12 | 4 | PointerToNextFunction | 对应于下一个函数的符号表记录在符号表中的索引 如果此函数是符号表中的最后一个函数,那么这个域的值为 0 |
16 | 2 | 未用 |
2.4.5.2 弱外部符号
弱外部符号表示这个外部符号如果在链接过程中没有被找到,可以使用sym2代替,如果sym2也没有找到,才会出现链接错误。
弱外部符号满足以下条件:储类别是EXTERNAL(2),SectionNumber域的值为IMAGE_SYM_UNDEFINED(0),Value 域的值为0。
此时后面跟的辅助符号信息格式如下:
偏移 | 大小 | 域 | 描述 |
---|---|---|---|
0 | 4 | TagIndex | sym2在符号表中的索引 如果链接时找不到 sym1就用它代替 |
4 | 4 | Characteristics | 如果这个值为IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY,表明链接时不在库中查找 sym1 如果这个值为IMAGE_WEAK_EXTERN_SEARCH_LIBRARY,表明链接时在库中查找 sym1 如果这个值为IMAGE_WEAK_EXTERN_SEARCH_ALIAS,表明sym1 是 sym2 的别名 |
8 | 10 | 未用 |
注意在winnt.h文件中并没有定义这个Characteristics 域,而是用 TotalSize域来代替
2.4.5.3 源码文件符号
此格式的辅助符号表记录跟在存储类别为 FILE(103)的普通符号之后。这个符号表记录的符号名本身应该是.file,而跟着它的辅助记录给出了源文件名,格式如下:
偏移 | 大小 | 域 | 描述 |
---|---|---|---|
0 | 18 | FileName | 表示源文件名的ANSI字符串 |
2.5 字符串表
定位:FileHeader -> PointerToSymbolTable + FileHeader -> NumberOfSymbols * 18
COFF 字符串表开头的4个字节存储的是字符串表的总大小(以字节计),这个大小包括这个域本身。因此如果字符串表中不包含任何字符串时,这个值应该为 4。
大小后面是一些以0结尾的字符串,COFF 符号表中的符号指向这些字符串。
3.特殊的节
3.1 COMDAT节
OMDAT 节是一种可以由多个目标文件定义的节。(节头中的Characteristics 域设置了 IMAGE_SCN_LNK_COMDAT 标志)。
这个有点像ELF中的可合并节,微软文档关于这部分的内容也不全面,记一下,以后用到了再看。