资源:
Linking is the process of collecting and combining various pieces of code and data into a single file that can be loaded (copied) into memory and executed.
Linking can be performed at compile time, when the source code is translated into machine code; at load time, when the program is loaded into memory and executed by the loader; and even at run time, by application programs.
Linkers play a crucial role in software development because they enable separate compilation.
分开编译的好处:如果其中一个模块修改了,只需重新编译该模块然后链接整个应用程序,而无需重新编译其他部分。
Most compilation systems provide a compiler driver that invokes the language preprocessor, compiler, assembler, and linker, as needed on behalf of the user.
示例:
过程(第一章讲过):
1、The driver first runs the C preprocessor (cpp), which translates the C source file main.c into an ASCII intermediate file main.i. 预处理器会根据 #
字符修改 C语言代码,如,在 #include
的命令处,会插入 stdio.h
的内容。
2、The driver runs the C compiler (cc1), which translates main.i into an ASCII assembly-language file main.s.
3、The driver runs the assembler (as), which translates main.s into a binary relocatable object file main.o. The driver goes through the same process to generate sum.o.
4、It runs the linker program ld, which combines main.o and sum.o, along with the necessary system object files, to create the binary executable object file prog.
Static linkers such as the Linux ld program take as input a collection of relocatable object files and command-line arguments and generate as output a fully linked executable object file that can be loaded and run.
The input relocatable object files consist of various code and data sections, where each section is a contiguous sequence of bytes.
Instructions are in one section, initialized global variables are in another section, and uninitialized variables are in yet another section. (节的介绍在后面)
To build the executable, the linker must perform two main tasks:
Symbol resolution
Object files define and reference symbols, where each symbol corresponds to a function, a global variable, or a static variable. The purpose of symbol resolution is to associate each symbol reference with exactly one symbol definition. (符号解析,为了将符号引用和符号的定义相关联)
Relocation
Compilers and assemblers generate code and data sections that start at address 0.
The linker relocates these sections by associating a memory location with each symbol definition, and then modifying all of the references to those symbols so that they point to this memory location.(将符号和内存地址关联)
The linker blindly performs these relocations using detailed instructions, generated by the assembler, called relocation entries. (后面介绍)
(编译器编译的时候不知道符号在运行时的实际地址,这里用相对地址,节中的数据地址为相对该节开始地址的偏移,在链接重定位后才知道符号的绝对地址)
Object files are merely collections of blocks of bytes.
Some of these blocks contain program code, others contain program data, and others contain data structures that guide the linker and loader. (后面有介绍)
A linker concatenates blocks together, decides on run-time locations for the concatenated blocks, and modifies various locations within the code and data blocks.
Linkers have minimal understanding of the target machine. The compilers and assemblers that generate the object files have already done most of the work.
Object files come in three forms:
Relocatable object file
Contains binary code and data in a form that can be combined with other relocatable object files at compile time to create an executable object file. (7.1 节第三步,链接前生成的 .o 文件,每个源文件生成自己的 .o,还需要链接才能生成可执行文件)
Executable object file
Contains binary code and data in a form that can be copied directly into memory and executed.
(7.1 节第4步,链接后生成的可执行文件)
Shared object file
A special type of relocatable object file that can be loaded into memory and linked dynamically, at either load time or run time. (共享目标文件,可在加载或者运行时动态的链接)
Object files are organized according to specific object file formats, which vary from system to system.
本书以 ELF(Executable and Linkable Format) 格式为例讨论。
ELF 格式如下:
各部分内容的介绍:
1、ELF header
,提供关于这个二进制文件的信息,如 word size, byte ordering, file type(.o, exec, .so), machine type,etc.
2、.text
已编译程序的机器代码。
3、.bss
原始的含义是 block started by symbol
,但为了好记可以当作 Better Save Space
的缩写。
4、.symtab
符号表包含函数,全局变量和静态变量。每个符号都有一个条目(一个结构体),包含该符号的信息。
5、.rel.text
节,包含 .text
节中需要重定位的信息;在重定位生成可执行文件后才能确定地址的指令的地址。如在符号表中的函数,其地址为相对地址,只有重定位后才知道绝对地址。
6、.rel.data
节,包含 .data
节中需要重定位的信息,如某个已初始化的全局变量存在 .data
节中,该全局变量(符号)在符号表中的地址为相对地址,在链接器重定位后才能知道在内存中的地址。
7、Section header table
节,包含不同节的起始位置信息。(offsets and sizes of each section)
Each relocatable object module, m, has a symbol table that contains information about the symbols that are defined and referenced by m.
In the context of a linker, there are three different kinds of symbols:
注意 local symbols
不是程序的局部变量,程序的非静态局部变量是在运行时存储在栈上。
示例(a pair of functions in the same module define a static local variable x):
1 int f()
2 {
3 static int x = 0;
4 return x;
5 }
6
7 int g()
8 {
9 static int x = 1;
10 return x;
11 }
In this case, the compiler exports a pair of local linker symbols with different names to the assembler. 如可能用 x.1
表示函数 f
中的变量 x
,用 x.2
表示函数 g
中的x
。
Symbol tables are built by assemblers, using symbols exported by the compiler into the assembly-language .s file.
ELF 格式的符号表包含在 .symtab
节中,符号表的条目结构见下图:
name
是符号名字的字符串在 .strtab
中的字节偏移量。
value
是符号的地址,对于 relocatable modules
是在对应节中的偏移量;如果是可执行文件,则是绝对地址。
Each symbol is assigned to some section of the object file, denoted by the section field, which is an index into the section header table.
有三个伪节在节表头中没有索引:
ABS
不该被重定位的符号
UNDEF
未定义的符号,即在其他 module 中定义,但在本 module 中使用的符号
COMMON
未初始化的数据,即未被分配位置。
For COMMON symbols, the value field gives the alignment requirement, and size gives the minimum size.
注意:
1、COMMON 伪节 和 .bss 节的区别:
2、上面三个伪节只在可重定位文件中有,在可执行文件中没有。
示例:
用 GNU READELF 程序查看 main.o
文件的最后三个符号表条目:
1、 符号 main
是一个函数(FUNC),大小为 24 字节,该符号被分配在 .text
节中(NDx 为1,根据图 7.3 中表可知,索引为 1 的节为 .text
节),其在 .text
节中位置偏移量为 0(Value),该函数是全局的函数(Bind),Num
表示该条目的索引,8代表是第 9 个条目,前面还有 8 个符号表条目。
2、 符号 array
是一个全局的对象,8字节,位于 .data
节偏移量为 0 的位置处。
3、 符号 sum
是引用的外部符号,因此在这里无位置和大小的参数。
链接器进行符号解析的过程:将每个引用和该符号在可重定位符号表中的唯一 符号定义关联起来。
The compiler allows only one definition of each local symbol per module.
如果链接器在当前模块中未找到符号定义,则会当作该符号在其他模块中定义,生成一个 linker symbol table entry,然后让链接器去处理,如果链接时不能在其他模块中找到该符号的定义,则打印错误信息并结束链接。
Linux 编译系统对链接时发现多个模块定义了相同名字的全局变量的处理:
All compilation systems provide a mechanism for packaging related object modules into a single file called a static library, which can then be supplied as input to the linker.
链接生成可执行文件时,链接器只复制静态库中程序中用到的模块。
Linux 系统中,静态库以一种 archive
的文件格式存在磁盘中。
An archive is a collection of concatenated relocatable object files, with a header that describes the size and location of each member object file.
Archive filenames are denoted with the .a suffix
.
例子见书中示例
符号解析时,链接器按照编译时输入在命令行中的位置从左到右扫描可重定位文件和归档文件(archives)。
链接器在扫描过程中,将那些要被组合起来生成可执行文件的 relocatable 文件归到 E
集合中。
链接时将还未解析的符号放到 U
集合中。(如引用了,但在别的文件中定义的符号)
链接时将那些在前面的输入文件中已经定义了的符号放到 D
集合中。
从上述过程可以看出,链接时对命令行中输入文件的顺序有要求,库文件必须放在需要用到它的可重定向目标文件的后面,否则会链接失败。
在链接器完成符号解析的步骤后,知道了每个输入模块中代码(.text
节)和 数据(.data
节)的大小。然后开始执行重定位过程,即将所有的输入模块合并起来然后为每个符号分配运行时的地址。该过程包含以下两个步骤:
Relocating sections and symbol definitions
将所有相同类型的节(如 .data
节)合并为一个新的同类型聚合节用于可执行文件。然后为聚合节分配 run-time memory addresses。
Relocating symbol references within sections
链接器将代码(.rel.text
)和数据(.rel.data
)节中的符号引用指向正确的地址。 (前面讲过有两个节存放需要重定位的信息)
汇编器生成目标模块(object mudule)时,不知道代码和数据最终将被存在内存中的什么地方,因此当汇编器遇到一个不知道地址的引用时,会生成一个重定位条目(relocation entry),告诉链接器在合并目标文件以生成可执行文件时怎么修改这个引用的地址。
Figure 7.9 shows the format of an ELF relocation entry.
Figure 7.13 summarizes the kinds of information in a typical ELF executable file.
可执行文件的格式和可重定位文件基本相似,有几处不同如下:
ELF
表头均描述文件的基本信息,但可执行文件中也包含程序的入口点(entry point),即程序运行时执行第一条指令的地址。
可执行文件多了一个 Segment header table
节
可执行文件中多了 .init
节,该节定义程序的初始化代码需要调用的函数 _init
。 初始化代码是程序最先运行的一段代码,在 main 函数之前运行。
可执行文件中不需要重定位信息的节 .rel.text
和 .rel.data
。
.text
,.rodata
和 .data
节的内容是一样的,但可执行文件中这些节被重定位到最终运行时的内存地址。
loading :通过加载器(loader) 复制可执行文件的代码和数据到内存中,然后依据 入口点 (entry point)跳到第一条指定的地方来运行程序的过程。
Every running Linux program has a run-time memory image similar to the one in Figure 7.15.
.data
和 .bss
中数据。malloc
库时用到,向上增长。静态库的缺点:
A shared library is an object module that, at either run time or load time, can be loaded at an arbitrary memory address and linked with a program in memory.
该过程被称为动态链接,由动态链接器实现。
动态库在 Linux 的为 .so
的文件,在微软的操作系统中为 DLL
文件。
共享库动态链接过程:
共享库特点:
In any given file system, there is exactly one .so file for a particular library.
这个库中的代码和数据被所有引用该库的可执行文件共享。
在生成可执行文件的过程中,不会复制共享库的代码或数据;只是复制一些重定位和符号表的信息,为了在程序加载到内存时能引用共享库中的数据和代码。
加载器加载和运行可执行程序时,如果看到可执行程序中有一个 .interp
节(包含动态链接器的路径名),则会加载并允许动态链接器(一个共享库)来执行如下重定位过程:
动态链接器完成重定位后,将控制权交给应用程序,动态库的位置将在程序执行期间保持不变。
上述链接过程是在程序加载时链接。(dynamic linking at load-time)
动态库也能在程序运行时进行动态链接。(dynamic linking at run-time)
一个共享库能被多个运行的进程使用。
PIC:不需要重定位就能被加载的代码称为 PIC ( positionin dependent code )。
PIC Data References:
目标模块的数据段和代码段之间的距离在运行时是常量,保持不变。
library interpositioning:拦截对共享库的调用,然后执行自己的代码。
作用:
Using interpositioning, you could trace the number of times a particular library function is called, validate and trace its input and output values, or even replace it with a completely different implementation.
方法:
对于一个需要被打桩的函数,创建一个和该函数原型相同的包装函数(wrapper function),通过特殊的 interpositioning mechanism 来欺骗系统调用包装函数而非目标函数。
Interpositioning can occur at compile time, link time, or run time as the program is being loaded and executed.
不同阶段库打桩的实现:库打桩机制