很久很久以前……人们编写程序时,将所有源代码都写在同一个文件中,发展到后来一个程序源代码的文件长达数百万行,以至于这个地方的人类已经没有能力维护这个程序了。人们开始寻找新的办法
在最开始的时候,程序员(当时程序员的概念应该跟现在相差很大了)先把一个程序在纸上写好,当然当时没有很高级的语言,用的都是机器语言,甚至连汇编语言都没有。当程序须要被运行时,程序员人工将他写的程序写入到存储设备上,
程序的第一条指令就是一条跳转指令,它的目的地址是第5条指令
程序并不是一写好就永远不变化的,它可能会经常被修改。比如我们在第1条指令之后、第5条指令之前插入了一条或多条指令,那么第5条指令及后面的指令的位置将会相应地往后移动,原先第一条指令的低4位的数字将需要相应地调整。在这个过程中,程序员需要人工重新计算每个子程序或跳转的目标地址。当程序修改的时候,这些位置都要重新计算,十分繁琐又耗时,并且很容易出错。这种重新计算各个目标的地址过程被叫做重定位(Relocation)。
先驱者发明了汇编语言,这相比机器语言来说是个很大的进步。汇编语言使用接近人类的
各种符号和标记来帮助记忆,比如指令采用两个或三个字母的缩写,记住“jmp”比记住0001XXXX是跳转(jump)指令容易得多了;汇编语言还可以使用符号来标记位置。
我们把刚开始第5条指令开始的子程序命名为“foo”,那么第一条指令的汇编就是:
jmp foo
当然人们可以使用这种符号命名子程序或跳转目标以后,不管这个“foo”之前插入或减少了多少条指令导致“foo”目标地址发生了什么变化,汇编器在每次汇编程序的时候会重新计算“foo”这个符号的地址,然后把所有引用到“foo”的指令修正到这个正确的地址。
整个过程不需要人工参与,对于一个有成百上千个类似的符号的程序,程序员终于摆脱了这种低级的繁琐的调整地址的工作,
符号(Symbol)这个概念随着汇编语言的普及迅速被使用,它用来表示一个地址,这个地址可能是一段子程序(后来发展成函数)的起始地址,也可以是一个变量的起始地址。
有了汇编语言以后,生产力大大提高了。人们开始将代码按照功能或性质划分,分别形成不同的功能模块,不同的模块之间按照层次结构或其他结构来组织。
在一个程序被分割成多个模块以后,这些模块之间最后如何组合形成一个单一的程序是须解决的问题。模块之间如何组合的问题可以归结为模块之间如何通信的问题,最常见的属于静态语言的C/C++模块之间通信有两种方式,一种是模块间的函数调用,另外一种是模块间的变量访问。函数访问须知道目标函数的地址,变量访问也须知道目标变量的地址,所以这两种方式都可以归结为一种方式,那就是模块间符号的引用。模块间依靠符号来通信类似于拼图版,定义符号的模块多出一块区域,引用该符号的模块刚好少了那一块区域,两者一拼接刚好完美组合(见图)。这个模块的拼接过程就是本书的一个主题:链接(Linking)。
这种基于符号的模块化的一个直接结果是链接过程在整个程序开发中变得十分重要和突出。
编译器、链接器更为复杂,功能更为强大,但从原理上来讲,它的工作无非就是把一些指令对其他符号地址的引用加以修正。链接过程主要包括了地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等这些步骤。
符号决议有时候也被叫做符号绑定(Symbol Binding)、名称绑定(Name Binding)、名称决议(Name Resolution),甚至还有叫做地址绑定(Address Binding)、指令绑定(Instruction Binding)的,大体上它们的意思都一样,但从细节角度来区分,它们之间还是存在一定区别的,比如“决议”更倾向于静态链接,而“绑定”更倾向于动态链接,
比如我们在程序模块main.c中使用另外一个模块func.c中的函数foo()。我们在main.c模块中每一处调用foo的时候都必须确切知道foo这个函数的地址,但是由于每个模块都是单独编译的,在编译器编译main.c的时候它并不知道foo函数的地址,所以它暂时把这些调用foo的指令的目标地址搁置,等待最后链接的时候由链接器去将这些指令的目标地址修正。
如果没有链接器,须要我们手工把每个调用foo的指令进行修正,则填入正确的foo函数地址。当func.c模块被重新编译,foo函数的地址有可能改变时,那么我们在main.c中所有使用到foo的地址的指令将要全部重新调整。这些繁琐的工作将成为程序员的噩梦。使用链接器,你可以直接引用其他模块的函数和全局变量而无须知道它们的地址,
因为链接器在链接的时候,会根据你所引用的符号 foo,自动去相应的func.c模块查找foo的地址,然后将main.c模块中所有引用到foo的指令重新修正,让它们的目标地址为真正的foo函数的地址。这就是静态链接的最基本的过程和作用。
结合CPU指令来了解这个过程。假设我们有个全局变量叫做var,它在目标文件A里面。我们在目标文件B里面要访问这个全局变量,比如我们在目标文件B里面有这么一条指令:
movl $0x2a, var
这条指令就是给这个var变量赋值0x2a,相当于C语言里面的语句var =42。然后我们编译目标文件B,得到这条指令机器码,如图所示。
由于在编译目标文件B的时候,编译器并不知道变量var的目标地址,所以编译器在没法确定地址的情况下,将这条mov指令的目标地址置为0,等待链接器在将目标文件A和B链接起来的时候再将其修正。我们假设A和B链接后,变量var的地址确定下来为0x1000,那么链接器将会把这个指令的目标地址部分修改成0x10000。这个地址修正的过程也被叫做重定位(Relocation),每个要被修正的地方叫一个重定位入口(Relocation Entry)。重定位所做的就是给程序中每个这样的绝对地址引用的位置“打补丁”,使它们指向正确的地址。
#include
int main()
{
static int var = 42;
printf("%d",var);
}
编译
gcc -c hello.c -o hello.o
查看 汇编指令
objdump -s -d hello.o
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <main+0xa>
a: 89 c6 mov %eax,%esi
c: bf 00 00 00 00 mov $0x0,%edi
11: b8 00 00 00 00 mov $0x0,%eax
16: e8 00 00 00 00 callq 1b <main+0x1b>
1b: 5d pop %rbp
1c: c3 retq
查看 符号表
readelf -a hello.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 var.2178
7: 0000000000000000 0 SECTION LOCAL DEFAULT 7
8: 0000000000000000 0 SECTION LOCAL DEFAULT 8
9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
10: 0000000000000000 29 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
链接
gcc hello.o -o hello.out
查看 汇编指令
objdump -s -d hello.out
000000000040052d <main>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 8b 05 fd 0a 20 00 mov 0x200afd(%rip),%eax # 601034 <var.2178>
400537: 89 c6 mov %eax,%esi
400539: bf e0 05 40 00 mov $0x4005e0,%edi
40053e: b8 00 00 00 00 mov $0x0,%eax
400543: e8 c8 fe ff ff callq 400410 <printf@plt>
400548: 5d pop %rbp
400549: c3 retq
40054a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
查看 符号表
readelf -a hello.out
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400238 0 SECTION LOCAL DEFAULT 1
2: 0000000000400254 0 SECTION LOCAL DEFAULT 2
3: 0000000000400274 0 SECTION LOCAL DEFAULT 3
4: 0000000000400298 0 SECTION LOCAL DEFAULT 4
5: 00000000004002b8 0 SECTION LOCAL DEFAULT 5
6: 0000000000400318 0 SECTION LOCAL DEFAULT 6
7: 0000000000400358 0 SECTION LOCAL DEFAULT 7
8: 0000000000400360 0 SECTION LOCAL DEFAULT 8
9: 0000000000400380 0 SECTION LOCAL DEFAULT 9
10: 0000000000400398 0 SECTION LOCAL DEFAULT 10
11: 00000000004003e0 0 SECTION LOCAL DEFAULT 11
12: 0000000000400400 0 SECTION LOCAL DEFAULT 12
13: 0000000000400440 0 SECTION LOCAL DEFAULT 13
14: 00000000004005c4 0 SECTION LOCAL DEFAULT 14
15: 00000000004005d0 0 SECTION LOCAL DEFAULT 15
16: 00000000004005e4 0 SECTION LOCAL DEFAULT 16
17: 0000000000400618 0 SECTION LOCAL DEFAULT 17
18: 0000000000600e10 0 SECTION LOCAL DEFAULT 18
19: 0000000000600e18 0 SECTION LOCAL DEFAULT 19
20: 0000000000600e20 0 SECTION LOCAL DEFAULT 20
21: 0000000000600e28 0 SECTION LOCAL DEFAULT 21
22: 0000000000600ff8 0 SECTION LOCAL DEFAULT 22
23: 0000000000601000 0 SECTION LOCAL DEFAULT 23
24: 0000000000601030 0 SECTION LOCAL DEFAULT 24
25: 0000000000601038 0 SECTION LOCAL DEFAULT 25
26: 0000000000000000 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
28: 0000000000600e20 0 OBJECT LOCAL DEFAULT 20 __JCR_LIST__
29: 0000000000400470 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
30: 00000000004004a0 0 FUNC LOCAL DEFAULT 13 register_tm_clones
31: 00000000004004e0 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
32: 0000000000601038 1 OBJECT LOCAL DEFAULT 25 completed.6355
33: 0000000000600e18 0 OBJECT LOCAL DEFAULT 19 __do_global_dtors_aux_fin
34: 0000000000400500 0 FUNC LOCAL DEFAULT 13 frame_dummy
35: 0000000000600e10 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
37: 0000000000601034 4 OBJECT LOCAL DEFAULT 24 var.2178
38: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
39: 0000000000400708 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
40: 0000000000600e20 0 OBJECT LOCAL DEFAULT 20 __JCR_END__
41: 0000000000000000 0 FILE LOCAL DEFAULT ABS
42: 0000000000600e18 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
43: 0000000000600e28 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
44: 0000000000600e10 0 NOTYPE LOCAL DEFAULT 18 __init_array_start
45: 00000000004005e4 0 NOTYPE LOCAL DEFAULT 16 __GNU_EH_FRAME_HDR
46: 0000000000601000 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_
47: 00000000004005c0 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
48: 0000000000601030 0 NOTYPE WEAK DEFAULT 24 data_start
49: 0000000000601038 0 NOTYPE GLOBAL DEFAULT 24 _edata
50: 00000000004005c4 0 FUNC GLOBAL DEFAULT 14 _fini
51: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
53: 0000000000601030 0 NOTYPE GLOBAL DEFAULT 24 __data_start
54: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
55: 00000000004005d8 0 OBJECT GLOBAL HIDDEN 15 __dso_handle
56: 00000000004005d0 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
57: 0000000000400550 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
58: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 25 _end
59: 0000000000400440 0 FUNC GLOBAL DEFAULT 13 _start
60: 0000000000601038 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
61: 000000000040052d 29 FUNC GLOBAL DEFAULT 13 main
62: 0000000000601038 0 OBJECT GLOBAL HIDDEN 24 __TMC_END__
63: 00000000004003e0 0 FUNC GLOBAL DEFAULT 11 _init
我们可以看到:
链接前
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <main+0xa>
400531: 8b 05 fd 0a 20 00 mov 0x200afd(%rip),%eax # 601034 <var.2178>
8b 05 = mov
fd 0a 20 = 0x 200afd = 601034
链接过程的本质就是要把多个不同的目标文件之间相互“粘”到一起,在链接中,目标文件之间相互拼合实
际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。
比
如目标文件B要用到了目标文件A中的函数“foo”,那么我们就称目标文件A定义(Define)了函数“foo”,称目标文件B引用(Reference)了目标文件A中的函数“foo”。这两个概念也同样适用于变量。每个函数或变量都有自己独特的名字,才能避免链接过程中不同变量和函数之间的混淆。在链接中,我们将函数和变量统称为符号(Symbol),函数名或变量名就是符号名(Symbol Name)。
每一个目标文件都会有一个相应的符号表(Symbol Table),
ELF文件中的符号表往往是文件中的一个段,段名一般叫“.symtab”,结构定义如下:
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
符号类型和绑定信息( st_info ) 该成员低4位表示符号的类型( Symbol Type ),高28位表示符号绑定信息( Symbol Binding ),如表下面两个表所示。
符号所在段( st_shndx )如果符号定义在本目标文件中,那么这个成员表示符号所在的段在段表中的下标;但是如果符号不是定义在本目标文件中,或者对于有些特殊符号, sh_shndx 的值有些特殊,如下表所示
符号值( st_value ) 我们前面已经介绍过,每个符号都有一个对应的值,如果这个符号是一个函数或变量的定义,那么符号的值就是这个函数或变量的地址,更准确地讲应该按下面这几种情况区别对待。
在目标文件中,如果是符号的定义并且该符号不是“COMMON块”类型的(即st_shndx不为SHN_COMMON,具体请参照“深入静态链接”一章中的“COMMON块”),则st_value表示该符号在段中的偏移。即符号所对应的函数或变量位于由st_shndx指定的段,偏移st_value的位置。这也是目标文件中定义全局变量的符号的最常见情况,比如SimpleSection.o中的“ func1 ”、“ main ”和“ global_init_var ”。
在目标文件中,如果符号是“COMMON块”类型的(即 st_shndx 为SHN_COMMON),则st_value表示该符号的对齐属性。比如SimpleSection.o中的“ global_uninit_var ”。在可执行文件中,st_value表示符号的虚拟地址。这个虚拟地址对于动态链接器来说十分有用。我们将在后面讲述动态链接器。
#include
int global_init_var = 11;
int global_uninit_var;
const int const_g =22;
void show( int i )
{
printf( "%d\n", i );
}
int main()
{
static int static_var = 33;
static int static_var2;
const int const_l =44;
int a_local = 1;
int b_local;
show(a_local);
return -1;
}
[dev1@localhost test01]$ gcc -c hello.c -o hello.o
[dev1@localhost test01]$ readelf -a hello.o
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 1024 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 64 (字节)
节头数量: 13
字符串表索引节头: 12
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000048 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000320
0000000000000048 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000088
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 00000090
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 00000090
0000000000000008 0000000000000000 A 0 0 4
[ 6] .comment PROGBITS 0000000000000000 00000098
000000000000002e 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000c6
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000c8
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000368
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000120
0000000000000198 0000000000000018 11 11 8
[11] .strtab STRTAB 0000000000000000 000002b8
0000000000000065 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000398
0000000000000061 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
There are no section groups in this file.
本文件中没有程序头。
重定位节 '.rela.text' 位于偏移量 0x320 含有 3 个条目:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000011 00050000000a R_X86_64_32 0000000000000000 .rodata + 4
00000000001b 000f00000002 R_X86_64_PC32 0000000000000000 printf - 4
00000000003d 000e00000002 R_X86_64_PC32 0000000000000000 show - 4
重定位节 '.rela.eh_frame' 位于偏移量 0x368 含有 2 个条目:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + 21
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.2185
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.2184
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
13: 0000000000000000 4 OBJECT GLOBAL DEFAULT 5 const_g
14: 0000000000000000 33 FUNC GLOBAL DEFAULT 1 show
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
16: 0000000000000021 39 FUNC GLOBAL DEFAULT 1 main
No version information found in this file.
[dev1@localhost test01]$ gcc hello.o -o hello.out
[dev1@localhost test01]$ readelf -a hello.out
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x400440
程序头起点: 64 (bytes into file)
Start of section headers: 6672 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 9
节头大小: 64 (字节)
节头数量: 30
字符串表索引节头: 29
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003f 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400358 00000358
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000048 0000000000000018 AI 5 23 8
[11] .init PROGBITS 00000000004003e0 000003e0
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400400 00000400
0000000000000040 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400440 00000440
00000000000001b2 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 00000000004005f4 000005f4
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000400600 00000600
0000000000000018 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 0000000000400618 00000618
000000000000003c 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400658 00000658
0000000000000114 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000008 WA 0 0 8
[20] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000601000 00001000
0000000000000030 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000601030 00001030
000000000000000c 0000000000000000 WA 0 0 4
[25] .bss NOBITS 000000000060103c 0000103c
000000000000000c 0000000000000000 WA 0 0 4
[26] .comment PROGBITS 0000000000000000 0000103c
000000000000002d 0000000000000001 MS 0 0 1
[27] .symtab SYMTAB 0000000000000000 00001070
0000000000000678 0000000000000018 28 48 8
[28] .strtab STRTAB 0000000000000000 000016e8
000000000000021c 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 00001904
0000000000000108 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
There are no section groups in this file.
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000076c 0x000000000000076c R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x000000000000022c 0x0000000000000238 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x0000000000000618 0x0000000000400618 0x0000000000400618
0x000000000000003c 0x000000000000003c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x00000000000001f0 0x00000000000001f0 R 1
Section to Segment mapping:
段节...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
Dynamic section at offset 0xe28 contains 24 entries:
标记 类型 名称/值
0x0000000000000001 (NEEDED) 共享库:[libc.so.6]
0x000000000000000c (INIT) 0x4003e0
0x000000000000000d (FINI) 0x4005f4
0x0000000000000019 (INIT_ARRAY) 0x600e10
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x600e18
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x400318
0x0000000000000006 (SYMTAB) 0x4002b8
0x000000000000000a (STRSZ) 63 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x601000
0x0000000000000002 (PLTRELSZ) 72 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x400398
0x0000000000000007 (RELA) 0x400380
0x0000000000000008 (RELASZ) 24 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x400360
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x400358
0x0000000000000000 (NULL) 0x0
重定位节 '.rela.dyn' 位于偏移量 0x380 含有 1 个条目:
偏移量 信息 类型 符号值 符号名称 + 加数
000000600ff8 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
重定位节 '.rela.plt' 位于偏移量 0x398 含有 3 个条目:
偏移量 信息 类型 符号值 符号名称 + 加数
000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000601028 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 69 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400238 0 SECTION LOCAL DEFAULT 1
2: 0000000000400254 0 SECTION LOCAL DEFAULT 2
3: 0000000000400274 0 SECTION LOCAL DEFAULT 3
4: 0000000000400298 0 SECTION LOCAL DEFAULT 4
5: 00000000004002b8 0 SECTION LOCAL DEFAULT 5
6: 0000000000400318 0 SECTION LOCAL DEFAULT 6
7: 0000000000400358 0 SECTION LOCAL DEFAULT 7
8: 0000000000400360 0 SECTION LOCAL DEFAULT 8
9: 0000000000400380 0 SECTION LOCAL DEFAULT 9
10: 0000000000400398 0 SECTION LOCAL DEFAULT 10
11: 00000000004003e0 0 SECTION LOCAL DEFAULT 11
12: 0000000000400400 0 SECTION LOCAL DEFAULT 12
13: 0000000000400440 0 SECTION LOCAL DEFAULT 13
14: 00000000004005f4 0 SECTION LOCAL DEFAULT 14
15: 0000000000400600 0 SECTION LOCAL DEFAULT 15
16: 0000000000400618 0 SECTION LOCAL DEFAULT 16
17: 0000000000400658 0 SECTION LOCAL DEFAULT 17
18: 0000000000600e10 0 SECTION LOCAL DEFAULT 18
19: 0000000000600e18 0 SECTION LOCAL DEFAULT 19
20: 0000000000600e20 0 SECTION LOCAL DEFAULT 20
21: 0000000000600e28 0 SECTION LOCAL DEFAULT 21
22: 0000000000600ff8 0 SECTION LOCAL DEFAULT 22
23: 0000000000601000 0 SECTION LOCAL DEFAULT 23
24: 0000000000601030 0 SECTION LOCAL DEFAULT 24
25: 000000000060103c 0 SECTION LOCAL DEFAULT 25
26: 0000000000000000 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
28: 0000000000600e20 0 OBJECT LOCAL DEFAULT 20 __JCR_LIST__
29: 0000000000400470 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
30: 00000000004004a0 0 FUNC LOCAL DEFAULT 13 register_tm_clones
31: 00000000004004e0 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
32: 000000000060103c 1 OBJECT LOCAL DEFAULT 25 completed.6355
33: 0000000000600e18 0 OBJECT LOCAL DEFAULT 19 __do_global_dtors_aux_fin
34: 0000000000400500 0 FUNC LOCAL DEFAULT 13 frame_dummy
35: 0000000000600e10 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
37: 0000000000601040 4 OBJECT LOCAL DEFAULT 25 static_var2.2185
38: 0000000000601038 4 OBJECT LOCAL DEFAULT 24 static_var.2184
39: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
40: 0000000000400768 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
41: 0000000000600e20 0 OBJECT LOCAL DEFAULT 20 __JCR_END__
42: 0000000000000000 0 FILE LOCAL DEFAULT ABS
43: 0000000000600e18 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
44: 0000000000600e28 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
45: 0000000000600e10 0 NOTYPE LOCAL DEFAULT 18 __init_array_start
46: 0000000000400618 0 NOTYPE LOCAL DEFAULT 16 __GNU_EH_FRAME_HDR
47: 0000000000601000 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_
48: 00000000004005f0 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
49: 0000000000601030 0 NOTYPE WEAK DEFAULT 24 data_start
50: 000000000060103c 0 NOTYPE GLOBAL DEFAULT 24 _edata
51: 00000000004005f4 0 FUNC GLOBAL DEFAULT 14 _fini
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
53: 000000000040052d 33 FUNC GLOBAL DEFAULT 13 show
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
55: 0000000000601030 0 NOTYPE GLOBAL DEFAULT 24 __data_start
56: 0000000000400610 4 OBJECT GLOBAL DEFAULT 15 const_g
57: 0000000000601044 4 OBJECT GLOBAL DEFAULT 25 global_uninit_var
58: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
59: 0000000000400608 0 OBJECT GLOBAL HIDDEN 15 __dso_handle
60: 0000000000400600 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
61: 0000000000400580 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
62: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 25 _end
63: 0000000000400440 0 FUNC GLOBAL DEFAULT 13 _start
64: 0000000000601034 4 OBJECT GLOBAL DEFAULT 24 global_init_var
65: 000000000060103c 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
66: 000000000040054e 39 FUNC GLOBAL DEFAULT 13 main
67: 0000000000601040 0 OBJECT GLOBAL HIDDEN 24 __TMC_END__
68: 00000000004003e0 0 FUNC GLOBAL DEFAULT 11 _init
Version symbols section '.gnu.version' contains 4 entries:
地址:0000000000400358 Offset: 0x000358 Link: 5 (.dynsym)
000: 0 (*本地*) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5) 0 (*本地*)
Version needs section '.gnu.version_r' contains 1 entries:
地址:0x0000000000400360 Offset: 0x000360 Link: 6 (.dynstr)
000000: 版本: 1 文件:libc.so.6 计数:1
0x0010:名称:GLIBC_2.2.5 标志:无 版本:2
Displaying notes found at file offset 0x00000254 with length 0x00000020:
所有者 Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 2.6.32
Displaying notes found at file offset 0x00000274 with length 0x00000024:
所有者 Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: d80924a11e18a61d0bfa7beeae080dea26eb0b72
[dev1@localhost test01]$
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.2185
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.2184
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
13: 0000000000000000 4 OBJECT GLOBAL DEFAULT 5 const_g
14: 0000000000000000 33 FUNC GLOBAL DEFAULT 1 show
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
16: 0000000000000021 39 FUNC GLOBAL DEFAULT 1 main
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
在目标文件中,如果符号是“COMMON块”类型的(即 st_shndx 为SHN_COMMON),则st_value表示该符号的对齐属性。比如SimpleSection.o中的“ global_uninit_var ”。在可执行文件中,st_value表示符号的虚拟地址。这个虚拟地址对于动态链接器来说十分有用。我们将在后面讲述动态链接器。
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
6: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.2185
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.2184
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
Ndx | name | section |
---|---|---|
ABS | hello.c | 文件类型 |
4 | static_var2.2185 | .bss |
3 | static_var.2184 | .data |
3 | global_init_var | .data |
COM | global_uninit_var | 未初始化全局变量 |
5 | const_g | .rodata |
1 | show | .text |
UND | printf | 在外部 |
1 | main | .text |
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000048 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000320
0000000000000048 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000088
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 00000090
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 00000090
0000000000000008 0000000000000000 A 0 0 4
[ 6] .comment PROGBITS 0000000000000000 00000098
000000000000002e 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000c6
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000c8
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000368
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000120
0000000000000198 0000000000000018 11 11 8
[11] .strtab STRTAB 0000000000000000 000002b8
0000000000000065 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000398
0000000000000061 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
6: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.2185
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.2184
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
13: 0000000000000000 4 OBJECT GLOBAL DEFAULT 5 const_g
14: 0000000000000000 33 FUNC GLOBAL DEFAULT 1 show
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
16: 0000000000000021 39 FUNC GLOBAL DEFAULT 1 main
约在20世纪70年代以前,编译器编译源代码产生目标文件时,符号名与相应的变量和函数的名字是一样的。比如一个汇编源代码里面包含了一个函数foo,那么汇编器将它编译成目标文件以后,foo在目标文件中的相对应的符号名也是foo。当后来UNIX平台和C语言发明时,已经存在了相当多的使用汇编编写的库和目标文件。这样就产生了一个问题,那就是如果一个C程序要使用这些库的话,C语言中不可以使用这些库中定义的函数和变量的名字作为符号名,否则将会跟现有的目标文件冲突。比如有个用汇编编写的库中定义了一个函数叫做main,那么我们在C语言里面就不可以再定义一个main函数或变量了。
为了防止类似的符号名冲突,UNIX下的C语言就规定,C语言源代码文件中的所有全局的变量和函数经过编译以后,相对应的符号名前加上下划线“_”。而比如一个C语言函数“ foo ”,那么它编译后的符号名就是 “_foo ”;
这种简单而原始的方法的确能够暂时减少多种语言目标文件之间的符号 冲突的概率,但还是没有从根本上解决符号冲突的问题。比如同一种语 言编写的目标文件还有可能会产生符号冲突,当程序很大时,不同的模 块由多个部门(个人)开发,它们之间的命名规范如果不严格,则有可 能导致冲突。于是像C++这样的后来设计的语言开始考虑到了这个问 题,增加了名称空间(Namespace)的方法来解决多模块的符号冲突问 题。
但是随着时间的推移,很多操作系统和编译器被完全重写了好几遍,比如UNIX也分化成了很多种,整个环境发生了很大的变化,上面所提到的跟Fortran和古老的汇编库的符号冲突问题已经不是那么明显了。在现在的Linux下的GCC编译器中,默认情况下已经去掉了在C语言符号前加“”的这种方式;但是Windows平台下的编译器还保持的这样的传统,比如Visual C++编译器就会在C语言符号前加“”,GCC在Windows平台下的版本(cygwin、mingw)也会加“_”。GCC编译器也可以通过参数选项“ -fleading-underscore ”或 “-fno-leading-underscore ”来打开和关闭是否在C语言符号前加上下划线。
众所周知,强大而又复杂的C++拥有类、继承、虚机制、重载、名称空间等这些特性,它们使得符号管理更为复杂。最简单的例子,两个相同名字的函数func(int)和func(double),尽管函数名相同,但是参数列表不同,这是C++里面函数重载的最简单的一种情况,那么编译器和链接器在链接过程中如何区分这两个函数呢?为了支持C++这些复杂的特性,人们发明了符号修饰(Name Decoration)或符号改编(Name Mangling)的机制,
首先出现的一个问题是C++允许多个不同参数类型的函数拥有一样的名字,就是所谓的函数重载;
我们引入一个术语叫做函数签名(Function Signature),函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息。函数签名用于识别不同的函数,就像签名用于识别不同的人一样,函数的名字只是函数签名的一部分。由于上面6个同名函数的参数类型及所处的类和名称空间不同,我们可以认为它们的函数签名不同。在编译器及链接器处理符号时,它们使用某种名称修饰的方法,使得每个函数签名对应一个修饰后名称(Decorated Name)。
函数签名在GCC编译器下,相对应的修饰后名称如下表所示
GCC的基本C++名称修饰方法如下:所有的符号都以“_Z”开头,对于嵌套的名字(在名称空间或在类里面的),后面紧跟“N”,然后是各个名称空间和类的名字,每个名字前是名字字符串长度,再以“E”结尾。比如 N::C::func 经过名称修饰以后就是 _ZN1N1C4funcE 。对于一个函数来说,它的参数列表紧跟在“E”后面,对于int类型来说,就是字母“i”。
c++ 和c不同,感兴趣的可以写个c++的代码查看下,利用我们第二部分讲解的指令方法,对比下.cpp和.c文件编译出的不同
C++为了与C兼容,在符号的管理上,C++有一个用来声明或定义一个C的符号的“extern “C””关键字用法:
extern ”C” {
int func(int);
int var;
}
C++编译器会将在extern “C” 的大括号内部的代码当作C语言代码处理。所以很明显,上面的代码中,C++的名称修饰机制将不会起作用。
参考
1、《程序员的自我修养链接装载与库》