-
最初的链接概念早在高级编程语言出现之前就已存在
-
最早程序员用机器语言编写程序,并记录在纸带或卡片上,穿孔表示0,未穿孔表示1(但一修改一条语句,所有的语句就得重新编写)
-
用符号表示跳转位置,无需修改jmp指令的跳转目标
-
汇编语言出现
-
用助记符表示操作码
-
用符号表示位置
-
用助记符表示寄存器
-
最终进行汇编,链接
-
-
高级程序设计语言
-
程序越来越复杂,需多人开发不同的程序模块
-
子函数(程序)起始地址和变量起始地址是符号定义(definition)
-
调用子程序(函数或过程)和使用变量即是符号的引用(reference)
-
一个模块定义的符号可以被另一个模块引用
-
最终须编译链接,链接时须在符号引用处填入定义处的地址
-
-
链接操作的步骤
-
确定符号引用关系(符号解析)——>符号和定义的关系
-
合并相关.o文件
-
确定每个符号的地址
-
在指令中填入新地址
也就是说,链接过程分为两部分:1. 符号解析(1),2. 重定位(2,3,4)
-
-
使用链接的好处
-
模块化
-
一个程序可以分成很多源程序文件
-
可构建共享函数库,如数学,libc.a(多人开发,代码重用,开发效率高)
-
-
效率高
-
时间上,可分开编译:只需重新编译被修改的源程序文件,然后重新链接
-
空间上,无需包含共享库所有代码:源文件中无需包含共享库函数的源码,只要直接调用即可(例如,只要直接调用printf()函数,无需包含其源码)可执行文件和运行时的内存中只需包含所调用函数的代码而不需要包含整个共享库
-
-
-
链接的本质:合并相同的 "section" 。
在还没有链接之前,可重定位目标文件中对应的地址位置都是从0开始排起,
[gyhlf@localhost ycsapp]$ objdump -d virtualTest.o
//此处的程序只是简单的输出"hello,cpp";
virtualTest.o: file format elf64-x86-64
//可以很容易看出代码的地址位置为虚拟地址空间,如下面main函数的地址为0,接下来0,1,4,9……位置
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 00 00 00 00 mov $0x0,%edi
9: e8 00 00 00 00 callq e <main+0xe>
e: b8 00 00 00 00 mov $0x0,%eax
13: 5d pop %rbp
14: c3 retq
而在链接之后,经过符号解析和重定位之后,映射到了虚拟地址空间后,地址就发生了变化,而这刚好也是链接实现的过程,反汇编后得到的代码地址如下
000000000040052d <main>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: bf e0 05 40 00 mov $0x4005e0,%edi
400536: e8 d5 fe ff ff callq 400410 <puts@plt>
40053b: b8 00 00 00 00 mov $0x0,%eax
400540: 5d pop %rbp
400541: c3 retq
400542: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
400549: 00 00 00
40054c: 0f 1f 40 00 nopl 0x0(%rax)