from: http://www.mouseos.com/assembly/07.html
7. 从 t.obj 看 windows 的 COFF 文件格式
t.obj 是我们例子中的 object 文件,COFF 格式的全称为:Common Object File Format
我们的 t.obj 是这样来的:
nasm -f win64 t.asm -o t.obj |
将 t.asm 编译成 win64 的 COFF 文件格式。
7.1 t.obj 的结构图
开始处是 t.obj 的 COFF 文件头,它是前面提到过的 IMAGE_FILE_HEADER 结构,接下来的是 section 表,再接下来的是 t.obj 的数据
7.2 COFF 文件头
从 0x00 ~ 0x13 共 20 bytes 是 COFF 文件头,实际上它就是 IMAGE_FILE_HEADER 结构
00000000 64 86 // Machine |
t.obj 是一个 64 位的 COFF 文件,有 2 个 section,并且使用了 symbol table,symbol table 位置在 0xE7 处,这是个 offset 值,基于 t.obj 的开始。symbol 数是 0x0C
和 t.exe 映像文件不同的是,object 文件使用了 symbol table,在 WinNT.h 定义了这个 symbols table 结构:
// typedef struct _IMAGE_SYMBOL { #define IMAGE_SIZEOF_SYMBOL 18 |
这个 symbol table 的大小为 18 个字节,共有 0x0C 个 symbol table,因此整个 symbol table 是 216 个字节,即:从 0x000000E7 ~ 0x000001BE 是整个 symbol table 所在。
7.3 t.obj 的 section table
节表的数据结构前面已经介绍过,这里再重新看看 IMAGE_SECTION_HEADER 结构:
// #define IMAGE_SIZEOF_SHORT_NAME 8 typedef struct _IMAGE_SECTION_HEADER { #define IMAGE_SIZEOF_SECTION_HEADER 40 |
由前面的 IMAGE_FILE_HEADER 结构中的 NumberOfSections = 2 得知,t.obj 有 2 个 section table,t.obj 的 section table 位置在 0x14 ~ 0x63 处,共 80 bytes,下面看看 t.obj 的 section table 的内容:
00000014 2E 64 61 74 61 00 00 00 // name = ".data" 0000003C 2E 74 65 78 74 00 00 00 // name = ".text" |
上面的红色标注部分是 2 个 section 的名称,第 1 个 section 是 .data 节,第 2 个是 .text 节。
注意的是:这 2 个 section 的 VirtualSize 和 VirtualAddress 都是 0,由于 object 文件是未经过 link,因此,VirtualSize 和 VirtualAddress 都是 0 值。
.data 节的 PointerToRawData 是 64,SizeOfRawData 是 29,说明 .data 节在 t.obj 文件中的位置是 0x64 ~ 0x8C 共 41 bytes
.text 节的 PointerToRawData 是 8D, SizeOfRawData 是 3C,说明 .text 节在 t.obj 文件中的位置是 0x8D ~ 0xC8 共 60 bytes。而 .text 使用了 Relocation 表,这个 Relocation 位置在 0xC9 处,NumberOfRelocation = 03 表明,.text 使用 3 个 Relocation 表
7.4 t.obj 链接后的重定位
这里我们来看一看 t.obj 在链接过程中是如何进行重定位的?
下面是 t.obj 的 .text 在未重定位之前的代码(摘自 t.obj 的 .text 节):
0000008D 48 81 EC 28 00 00 00 sub rsp, 0x28 |
上面的红色标注部分显示了:有 3 个地方需要进行重定位的。
(1)我们的源代码是:mov rdx, text , nasm 将它编译为 48 BA 1C 00 00 00 00 00 00 00 ,那么在 link 时,这个地址值 0x1c 是一个 offset 值,需要被重新替换为真实的 text 常量的地址值。
(2)我们的源代码是:mov r8, caption ,nasm 将它编译为 49 B8 00 00 00 00 00 00 00 00,同样地址值 0x00 是不正确的,link 时需替换为真实的 caption 常量的地址值。
(3)源代码是:call MessageBoxA ,nasm 将它编译为 e8 00 00 00 00 ,同样我们需要的是 MessageBoxA 的地址。
于是在 t.obj 中就需要有 3 个 Relocation 表,也就是上面所说的 .text 节使用了 3 个 relocation 表。并指明了 relocation 表在 0xC9 处,从 0xC9 ~ 0xE6 共 30 bytes。
7.4.1 relocation 表结构
在 WinNT.h 定义了 IMAGE_RELOCATION 结构用来描述使用 relocation 表,如下:
// typedef struct _IMAGE_RELOCATION { |
这个结构共 10 bytes,这些域的含义是:
域
|
size
|
描述
|
VritualAddress
|
DWORD
|
指出 section 中的哪个 RVA 地址需要进行 relocate 操作
|
SymbolTableIndex
|
DWORD
|
用来在 Symbol table 中进行索引
|
Type
|
WORD
|
表明 VirtualAddress 是个怎样类型的 address
|
在 WinNT.h 定义了 VirtualAddress 的 type 值,下面是 x64 平台下的定义:
// |
其中,常用的是 ADDR64 类型和 REL32 类型。
IMAGE_REL_AMD64_ADDR64 类型表明:relocation 表中的 VirtualAddress 是个 64 位的 Virtual Address,也就是说:在 link 后,VirtualAddress 将会变成 64 位的地址值,它是直接 (绝对值 ) virtual address,而不是 RVA 值。IMAGE_REL_AMD64_ADDR32 类型表明:relocation 表中的 VirtualAddress 是个 相对值 ,是基于某个 base 值的 32 位的 signed offset 值。
那么下面看看 .text 节所使用的 relocation 表是怎样的?
000000C9 13 00 00 00 // VirtaulAddress 000000D3 1D 00 00 00 // VirtualAddress 000000DD 30 00 00 00 // VirtaulAddress |
● 第 1 个 relocation 表,它的 VirtualAddress 是 0x13,表明:.text 节中的 0x13 处的地址值需要进行 relocate 操作,它是个 64 位的 VA 值,重定位操作是根据SymbolTable[2] 提供的信息进行的,Symbol Table 是 0 索引开始。
● 第 2 个 relocation 表,意义完全和第 1 个 relocation 表一样。
● 第 3 个 relocation 表和上面两个表所不同的是,VirtualAddress 是个 REL32 类型的值它是个 32 位的 offset 值。
7.4.2 Symbol Table 结构
在 WinNT.h 里对 symbol table 的定义为:
// typedef struct _IMAGE_SYMBOL { #define IMAGE_SIZEOF_SYMBOL 18 |
这个结构域的含义是:
域
|
size
|
描述
|
Name
|
8 bytes
|
Symbol 的名称
|
Value
|
DWORD
|
这个 value 辅助性质,取决于 SectionNumber 和 StorageClass
|
SectionNumber
|
SHORT
|
指向哪个 section,以 section table 中 1-based 为索引
|
Type
|
WORD
|
symbol table 的类型
|
StorageClass
|
BYTE
|
存储类别,是定义如何进行重定位的关键
|
NumberOfAuxSymbols
|
BYTE
|
附加 symbol table 个数
|
NumberOfAuxSymbols 指出附加的 symbol table 个数,附加表紧跟着 symbol table 后面。下面看一看 t.obj 所有的 symbol table
000000E7 2E 66 69 6C 65 00 00 00 // Name = ".file" 000000EF 00 00 00 00 // Value 000000F3 FE FF // SectionNumber 000000F5 00 00 // Type 000000F7 67 // StorageClass 000000F8 01 //NumberOfAuxSymbols
0000010B 2E 64 61 74 61 00 00 00 // Name = ".data" 0000011D 29 00 00 00 00 00 00 00 // **** 辅助表 **** 0000012F 2E 74 65 78 74 00 00 00 // Name = ".text"
00000165 00 00 00 00 04 00 00 00 00000176 00 00 00 00 10 00 00 00 00000189 63 61 70 74 69 6F 6E 00 // Name = "caption" 0000019B 74 65 78 74 00 00 00 00 // Name = "text" 000001AD 6D 61 69 6E 00 00 00 00 // Name = "main" |
注意到,第 0 个 symbol table 的 name 是 ".file",它下面接着一个辅助表,结合这个辅助表得出 .file = "t.asm"
第 2 个 symbol table 是 ".data",它下面也接着一个辅助表,指出 .data 的 size 是 0x29
7.4.3 重定位过程
最后,我们来看一看具体的重定位操作,以 mov rdx, text 指令中 text 重定位为例。
源码 nasm 编译后 mov rdx, text =====> 0000009E 48 BA 1C 00 00 00 00 00 00 00 |
link 需要将 0x00000000_0000001c 重新分配正确的地址值,怎么做?
1. link 首先看有哪个 relocation 表需要重定位的。所以先做 relocation 表1 的重定位。
000000C9 13 00 00 00 // VirtaulAddress |
那么,位于 RVA 为 0x13 的地方的地址需要进行重定位,这个 0x13 的地方到底是哪里?答案是:
.text + 0x13 = 0x8d + 0x13 = 0xA0 |
需要重定位的地址在 0xA0 处,.text 节的 base 为 0x8D,这个 0x8D 的值由 .text 节结构的 PointerToRawData 域指出。
而 0xA0 处就是存放 1c 00 00 00 00 00 00 00 的地方。由于 relocation 表指向 SymbolTable[2] 并且指明 VirtualAddress 是个 64 位的 address 值。
2. 查找 SymbolTable[2] 表格
0000010B 2E 64 61 74 61 00 00 00 // Name = ".data" |
SymbolTable[2] 指向 .data 节,最重要的是它的 StoreageClass 是 3,下面是 microsoft 对于 StorageClass 作用的解释:
IMAGE_SYM_CLASS_STATIC | 3 | The offset of the symbol within the section. If the Value field is zero, then the symbol represents a section name. |
也就是说,VirtualAddess 是基于 section 的 offset 的值。即:0x1c 是基于 .data 的 offset 值
3. 生成最终的 virtual address 值
当 link 生成 PE 格式的 t.exe 时。从上一节得出:.data 被加载 virtual address 为 0x00000001_40003000
因此:指令 mov rdx, text 的值最终是
.data + 0x1C = 0x0000000140003000 + 0x1c = 0x000000014000301C |
下面是摘录在 t.exe 映像的 .text 节的数据
0000000140001011: 48 BA 1C 30 00 40 01 00 00 00 mov rdx, 0x000000014000301C |
指令 mov rdx, text 的 text 地址最终被重定位为 0x000000014000301C。