关于Windows COFF符号表的理解
在《编译、链接与库》一书中第5.5节《大家都有符号表》,有以下内容:
在输出结果的最左列是符号的编号,也是符号在符号表中的下标。接着是符号的大小,即符号所表示的对象所占用的空间。第三列是符号所在的位置,……
SimpleSection.c
源程序代码:
int printf(const char *format, ...);
int global_init_var = 84;
int global_uninit_var;
void func1(int i)
{
printf("%d\n", i);
}
int main(void)
{
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}
命令行命令:
cl /c /Za .\SimpleSection.c
dumpbin /ALL .\SimpleSection.obj > .\SimpleSection.txt
关于符号表的输出摘录:
COFF SYMBOL TABLE
000 01047558 ABS notype Static | @comp.id
001 80010190 ABS notype Static | @feat.00
002 00000001 ABS notype Static | @vol.md
003 00000000 SECT1 notype Static | .drectve
Section length 18, #relocs 0, #linenums 0, checksum 0
005 00000000 SECT2 notype Static | .debug$S
Section length 94, #relocs 0, #linenums 0, checksum 0
007 00000000 SECT3 notype Static | .data
Section length C, #relocs 0, #linenums 0, checksum AC5AB941
009 00000000 SECT3 notype External | global_init_var
00A 00000004 UNDEF notype External | global_uninit_var
00B 00000000 SECT4 notype Static | .text$mn
Section length 64, #relocs 5, #linenums 0, checksum D696A53
00D 00000000 UNDEF notype () External | printf
00E 00000000 SECT4 notype () External | func1
00F 00000030 SECT4 notype () External | main
010 00000000 SECT4 notype Label | $LN3
011 00000030 SECT4 notype Label | $LN3
012 00000000 SECT5 notype Static | .xdata
Section length 10, #relocs 0, #linenums 0, checksum 434E1581
014 00000000 SECT5 notype Static | $unwind$func1
015 00000000 SECT6 notype Static | .pdata
Section length 18, #relocs 6, #linenums 0, checksum 5710F00F
017 00000000 SECT6 notype Static | $pdata$func1
018 00000008 SECT5 notype Static | $unwind$main
019 0000000C SECT6 notype Static | $pdata$main
01A 00000004 SECT3 notype Static | $SG6143
01B 00000008 SECT3 notype Static | ?static_var@?1??main@@9@9 (`main'::`2'::static_var)
01C 00000000 SECT7 notype Static | .bss
Section length 4, #relocs 0, #linenums 0, checksum 0
01E 00000000 SECT7 notype Static | ?static_var2@?1??main@@9@9 (`main'::`2'::static_var2)
01F 00000000 SECT8 notype Static | .chks64
Section length 40, #relocs 0, #linenums 0, checksum 0
String Table Size = 0x8F bytes
Summary
4 .bss
40 .chks64
C .data
94 .debug$S
18 .drectve
18 .pdata
64 .text$mn
10 .xdata
可是对照global_init_var
那一行的信息,第二列是00000000
,如果这一列表示符号的大小,那显示的应该是00000004
,这显然与书上说的矛盾,这让我很困惑。
对照.data
中的数据:
SECTION HEADER #3
.data name
0 physical address
0 virtual address
C size of raw data
200 file pointer to raw data (00000200 to 0000020B)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0300040 flags
Initialized Data
4 byte align
Read Write
RAW DATA #3
00000000: 54 00 00 00 25 64 0A 00 55 00 00 00 T...%d..U...
54 00 00 00
的十进制是84,那么这个是变量global_init_var
,而25 64 0A 00
这个显然是字符串"%d\n"
了,55 00 00 00
是变量static_var
。
再次结合之前讲到的关于ELF文件关于符号的内容:
符号值(st_value)我们前面已经介绍过,每个符号都有一个对应的值,如果这个符号是一个函数或变量的定义,那么符号的值就是这个函数或变量的地址……在目标文件中,如果是符号的定义并且该符号不是“COMMON块”类型的,则st_value表示该符号在段中的偏移。即符号所对应的函数或变量位于由st_shndx指定的段,偏移st_value的位置。这也是目标文件中定义全局变量的符号的最常见情况……
那么可以初步断定这一列表示的意义实际上是变量或函数在该段中的偏移量,再结合代码段看看:
SECTION HEADER #4
.text$mn name
0 physical address
0 virtual address
64 size of raw data
20C file pointer to raw data (0000020C to 0000026F)
270 file pointer to relocation table
0 file pointer to line numbers
5 number of relocations
0 number of line numbers
60500020 flags
Code
16 byte align
Execute Read
RAW DATA #4
00000000: 89 4C 24 08 48 83 EC 28 8B 54 24 30 48 8D 0D 00 .L$.H.?.T$0H...
00000010: 00 00 00 E8 00 00 00 00 48 83 C4 28 C3 CC CC CC ...?...H.?锰烫
00000020: CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 烫烫烫烫烫烫烫烫
00000030: 48 83 EC 38 C7 44 24 20 01 00 00 00 8B 05 00 00 H.?荄$ ........
00000040: 00 00 8B 0D 00 00 00 00 03 C8 8B C1 03 44 24 20 .........??D$
00000050: 03 44 24 24 8B C8 E8 00 00 00 00 8B 44 24 20 48 .D$$.辱.....D$ H
00000060: 83 C4 38 C3 .??
RELOCATIONS #4
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- ------
0000000F REL32 00000000 1A $SG6143
00000014 REL32 00000000 D printf
0000003E REL32 00000000 1E ?static_var2@?1??main@@9@9 (`main'::`2'::static_var2)
00000044 REL32 00000000 1B ?static_var@?1??main@@9@9 (`main'::`2'::static_var)
00000057 REL32 00000000 E func1
将目标文件反汇编:
dumpbin /disasm .\SimpleSection.obj > .\SimpleSection.asm
反汇编文件内容:
Microsoft (R) COFF/PE Dumper Version 14.29.30040.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file .\SimpleSection.obj
File Type: COFF OBJECT
func1:
0000000000000000: 89 4C 24 08 mov dword ptr [rsp+8],ecx
0000000000000004: 48 83 EC 28 sub rsp,28h
0000000000000008: 8B 54 24 30 mov edx,dword ptr [rsp+30h]
000000000000000C: 48 8D 0D 00 00 00 lea rcx,[$SG6143]
00
0000000000000013: E8 00 00 00 00 call printf
0000000000000018: 48 83 C4 28 add rsp,28h
000000000000001C: C3 ret
000000000000001D: CC int 3
000000000000001E: CC int 3
000000000000001F: CC int 3
0000000000000020: CC int 3
0000000000000021: CC int 3
0000000000000022: CC int 3
0000000000000023: CC int 3
0000000000000024: CC int 3
0000000000000025: CC int 3
0000000000000026: CC int 3
0000000000000027: CC int 3
0000000000000028: CC int 3
0000000000000029: CC int 3
000000000000002A: CC int 3
000000000000002B: CC int 3
000000000000002C: CC int 3
000000000000002D: CC int 3
000000000000002E: CC int 3
000000000000002F: CC int 3
main:
0000000000000030: 48 83 EC 38 sub rsp,38h
0000000000000034: C7 44 24 20 01 00 mov dword ptr [rsp+20h],1
00 00
000000000000003C: 8B 05 00 00 00 00 mov eax,dword ptr [?static_var2@?1??main@@9@9]
0000000000000042: 8B 0D 00 00 00 00 mov ecx,dword ptr [?static_var@?1??main@@9@9]
0000000000000048: 03 C8 add ecx,eax
000000000000004A: 8B C1 mov eax,ecx
000000000000004C: 03 44 24 20 add eax,dword ptr [rsp+20h]
0000000000000050: 03 44 24 24 add eax,dword ptr [rsp+24h]
0000000000000054: 8B C8 mov ecx,eax
0000000000000056: E8 00 00 00 00 call func1
000000000000005B: 8B 44 24 20 mov eax,dword ptr [rsp+20h]
000000000000005F: 48 83 C4 38 add rsp,38h
0000000000000063: C3 ret
Summary
4 .bss
40 .chks64
C .data
94 .debug$S
18 .drectve
18 .pdata
64 .text$mn
10 .xdata
可以看到func1确实是以00000000
开头,main以00000030
开头,那么可以确定之前的结论是正确的。对于变量和函数来说,符号表输出的第二列是其在段内的偏移量。而对于global_uninit_var
这样的(未定义的)变量才表示是符号的大小,即符号所表示的对象所占用的空间:
// ...
long long global_uninit_var;
int global_uninit_var2;
// ...
int main()
{
// ...
}
00A 00000008 UNDEF notype External | global_uninit_var
00B 00000004 UNDEF notype External | global_uninit_var2