深入理解计算机系统——第七章 Linking

资源:

视频课程
视频课件
《深入理解计算机系统》第七章–链接
《深入理解计算机系统》CSAPP——第七章

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.

分开编译的好处:如果其中一个模块修改了,只需重新编译该模块然后链接整个应用程序,而无需重新编译其他部分。

7.1 Compiler Drivers

Most compilation systems provide a compiler driver that invokes the language preprocessor, compiler, assembler, and linker, as needed on behalf of the user.

示例:
fig 7.1


static linking
The compilation system

过程(第一章讲过):
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> 的命令处,会插入 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.

7.2 Static Linking

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.

7.3 Object Files

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) 格式为例讨论。

7.4 Relocatable Object Files

ELF 格式如下:


Typical ELF relocatable object file

各部分内容的介绍:

ELF relocatable object fileELF relocatable object file

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)


7.5 Symbols and Symbol Tables

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:
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 节中,符号表的条目结构见下图:
ELF symbol table entry

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 节的区别:

COMMON 伪节 和 .bss 节的区别

2、上面三个伪节只在可重定位文件中有,在可执行文件中没有。


示例:

fig 7.1

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 是引用的外部符号,因此在这里无位置和大小的参数。

7.6 Symbol Resolution

链接器进行符号解析的过程:将每个引用和该符号可重定位符号表中的唯一 符号定义关联起来。

The compiler allows only one definition of each local symbol per module.

如果链接器在当前模块中未找到符号定义,则会当作该符号在其他模块中定义,生成一个 linker symbol table entry,然后让链接器去处理,如果链接时不能在其他模块中找到该符号的定义,则打印错误信息并结束链接。

7.6.1 How Linkers Resolve Duplicate Symbol Names

Linux 编译系统对链接时发现多个模块定义了相同名字的全局变量的处理:

  1. 编译阶段,编译器将所有的全局符号分类为 strong 或者 weak 再给汇编器。函数和初始化后的全局变量为 strong 符号,未初始化的全局变量为 weak 符号。
  2. 汇编生成符号表时含有 strong 和 weak 的信息。
  3. 链接时根据 strong 和 weak 信息处理 重名的全局符号:
    规则1:不允许存在多个同名的 strong 符号;
    规则2:如果有一个 strong 符号和多个 weak 符号,选择 strong 符号;
    规则3:多个 weak 符号,则随便选择一个 weak 符号;(容易产生 bug)

7.6.2 Linking with Static Libraries

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.

例子见书中示例

7.6.3 How Linkers Use Static Libraries to Resolve References

符号解析时,链接器按照编译时输入在命令行中的位置从左到右扫描可重定位文件归档文件(archives)

链接器在扫描过程中,将那些要被组合起来生成可执行文件的 relocatable 文件归到 E集合中。

链接时将还未解析符号放到 U 集合中。(如引用了,但在别的文件中定义的符号)

链接时将那些在前面输入文件已经定义了的符号放到 D 集合中。

  • 对命令行中的每个文件 f,如果是可重定位目标文件,则放入 E 集合中,并根据该文件中符号的状态跟新 U 和 D 的集合,然后处理下一个文件。
  • 如果文件 f归档文件,链接器会尝试将 U 中的符号归档文件中已经定义的符号进行匹配,如果在归档文件成员 m 中找到 U 中符号的定义,则将该符号转到 D 中,将 m 加入 E 集合中。通过这种方式扫描归档文件中所有的可重定位目标文件最后将没有用到的目标文件丢弃,然后处理后面的输入文件
  • 如果链接器扫描完所有的输入文件U 是非空的,则打印错误然后终止链接;否则将 E 中所有的文件构建生成可执行文件

从上述过程可以看出,链接时对命令行中输入文件的顺序有要求,库文件必须放在需要用到它的可重定向目标文件的后面,否则会链接失败。

7.7 Relocation

链接器完成符号解析的步骤后,知道了每个输入模块中代码(.text 节)和 数据(.data 节)的大小。然后开始执行重定位过程,即将所有的输入模块合并起来然后为每个符号分配运行时的地址。该过程包含以下两个步骤:

  • Relocating sections and symbol definitions
    将所有相同类型的节(如 .data 节)合并为一个新的同类型聚合节用于可执行文件。然后为聚合节分配 run-time memory addresses。

  • Relocating symbol references within sections
    链接器将代码(.rel.text )和数据(.rel.data)节中的符号引用指向正确的地址。 (前面讲过有两个节存放需要重定位的信息)

7.7.1 Relocation Entries

汇编器生成目标模块(object mudule)时,不知道代码和数据最终将被存在内存中的什么地方,因此当汇编器遇到一个不知道地址的引用时,会生成一个重定位条目(relocation entry),告诉链接器在合并目标文件以生成可执行文件时怎么修改这个引用的地址

Figure 7.9 shows the format of an ELF relocation entry.
ELF relocation entry.

7.8 Executable Object Files

Figure 7.13 summarizes the kinds of information in a typical ELF executable file.
Typical ELF executable object file

可执行文件的格式和可重定位文件基本相似,有几处不同如下:

  1. ELF 表头均描述文件的基本信息,但可执行文件中也包含程序的入口点(entry point),即程序运行时执行第一条指令的地址

  2. 可执行文件多了一个 Segment header table

  3. 可执行文件中多了 .init 节,该节定义程序的初始化代码需要调用的函数 _init。 初始化代码是程序最先运行的一段代码,在 main 函数之前运行。

  4. 可执行文件中不需要重定位信息的节 .rel.text.rel.data

  5. .text.rodata.data 节的内容是一样的,但可执行文件中这些节被重定位到最终运行时的内存地址

7.9 Loading Executable Object Files

loading :通过加载器(loader) 复制可执行文件代码数据内存中,然后依据 入口点 (entry point)跳到第一条指定的地方来运行程序的过程。

Every running Linux program has a run-time memory image similar to the one in Figure 7.15.


Linux x86-64 run-time memory image

  1. 代码段(code segment):在 Linux x86-64 系统中,代码段的起始地址为 0x400000。
  2. 数据段(data segment):.data.bss 中数据。
  3. 运行时堆(heap):调用 malloc 库时用到,向上增长。
  4. 预留给共享库使用的区域。
  5. 用户栈(user stack):起始地址为最大的合法地址(largest legal user address, 2 48 − 1 2^{48} - 1 2481),然后向下增长。
  6. 用户栈以上的区域,起始地址为 2 48 2^{48} 248,是预留给内核(kernel)中的代码和数据使用的。

7.10 Dynamic Linking with Shared Libraries

静态库的缺点:

  1. 更新库时需要重新链接程序
  2. 运行时库中代码被复制到 text segment of each running process,浪费内存资源。

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文件。

共享库动态链接过程:
共享库动态链接过程


共享库特点:

  1. In any given file system, there is exactly one .so file for a particular library.

  2. 这个库中的代码数据所有引用该库的可执行文件共享。

  3. 在生成可执行文件的过程中,不会复制共享库的代码或数据;只是复制一些重定位和符号表的信息,为了在程序加载到内存时能引用共享库中的数据和代码。

  4. 加载器加载和运行可执行程序时,如果看到可执行程序中有一个 .interp 节(包含动态链接器的路径名),则会加载并允许动态链接器(一个共享库)来执行如下重定位过程:

    • 重定位用到的动态库的代码和数据内存段
    • 重定位可执行程序中定义在动态库中引用。
  5. 动态链接器完成重定位后,将控制权交给应用程序,动态库的位置将在程序执行期间保持不变

上述链接过程是在程序加载链接。(dynamic linking at load-time)

7.11 Loading and Linking Shared Libraries from Applications

动态库也能在程序运行时进行动态链接。(dynamic linking at run-time)

7.12 Position-Independent Code (PIC)

一个共享库能被多个运行的进程使用。

PIC:不需要重定位就能被加载的代码称为 PIC ( positionin dependent code )。

PIC Data References:
目标模块的数据段和代码段之间的距离在运行时是常量,保持不变。

7.13 Library Interpositioning

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.

不同阶段库打桩的实现:库打桩机制

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值