CSAPP 第7章——链接 读书笔记

链接:将各种代码数据片段收集并组合成为一个单一文件的过程,该文件可被加载(复制)到内存并执行。该过程可被执行于编译加载运行时,由链接器的程序自动执行。

编译:源代码被翻译成机器代码;加载:程序被加载器加载到内存并执行;运行:由应用程序来执行。)

7.1编译驱动程序

功能:代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。

作用过程(都是由驱动程序运行完成):

  1. 预处理器(gcc)将C的源程序main.c翻译成一个ASCLL码的中间文件main.i
  2. 编译器(ccl)将main.i翻译成一个ASCLL汇编语言文件main.s(也是可重定位的);
  3. 汇编器(as)将main.s翻译成一个可重定位目标文件main.o
  4. 链接器(ld)将main.o以及其他可重定位目标文件、系统必要的目标文件组合,创建可执行目标文件

7.2静态链接

功能:以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。

必须完成的两个任务:

  1. 符号解析:将每个符号引用刚好和一个符号定义联系起来。
  2. 重定位:链接器通过把每个符号定义与一个存储器位置联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储器位置。

7.3目标文件

三种形式

  1. 可重定位目标文件:包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件;
  2. 可执行目标文件:包含二进制代码和数据,其形式可以被直接复制到内存并执行
  3. 共享目标文件:一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接

7.4可重定位目标文件(由以下节构成)

  1. ELF:以一个16 字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。剩下的部分包含帮助链接器语法分析和解释目标文件的信息。
  2. .text: 已编译程序的机器代码。
  3. .rodata: 只读数据。
  4. .data: 已初始化的全局和静态C变量
  5. .bss: 未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量
  6. .syrntab: 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。
  7. .rel.text: 一个.text文件节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
  8. .rel.data: 被模块引用或定义的所有全局变最的重定位信息。
  9. .debug: 一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。
  10. .line:原始C源程序中的行号和 .text节中机器指令之间的映射。(-g运行才会得到。)
  11. .strtab: 一个字符串表,其内容包括 .symtab和.debug 节中的符号表,以及节头部中的节名字。字符串表就是以 null 结尾的字符串的序列。
  12. 节头部表:描述目标文件的节。

7.5符号和符号表

符号种类

  1. 由模块m定义并能被其他模块引用的全局符号。全局链接器符号对应于非静态的函数和全局变量。
  2. 由其他模块定义并被模块m引用的全局符号。这些符号称为外部符号,对应千在其他模块中定义的非静C函数和全局变量。
  3. 只被模块m定义和引用的局部符号。它们对应于带 static 属性的C函数和全局变量。 这些符号在模块m中任何位置都可见,但是不能被其他模块引用。

每个符号都被分配到目标文件的某个节,由 section 字段表示,该字段也是一个到节头部表的索引。有三个特殊的伪节 (pseudosection) , 它们在节头部表中是没有条目的:ABS 代表不该被重定位的符号; UNDEF 代表未定义的符号,也就是在本目标模块中引用,但是却在其他地方定义的符号; COMMON 表示还未被分配位置的未初始化的数据目标(未初始化的全局变量,不包括初始化为0)。对于 COMMON 符号, value 字段给出对齐要求,而 size 给出最小的大小。注意,只有可重定位目标文件中才有这些伪节,可执行目标文件中是没有的。

7.6符号解析

强弱符号定义函数和已初始化的全局变量强符号未初始化的全局变量弱符号

处理多重定义的符号名规则

  1. 不允许有多个同名的强符号(强符号只能定义一次)。
  2. 如果有一个强符号和多个弱符号同名,那么选择强符号
  3. 如果有多个弱符号同名,那么从这些弱符号中任意选择一个

静态库将所有相关的目标模块打包成为一个单独的文件, 可以用做链接器的输入。当链接器构造一个输出的可执行文件时,它只复制静态库里被应用程序引用的目标模块

存档文件:静态库以一种称为存档 (archive) 的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置 。存档文件名由后缀 .a 标识。

-static 参数:告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,它可以加载到内存并运行,在加载时无须更进一步的链接

-L. 参数:告诉链接器在当前目录下查找紧跟的文件。

一些建议

  1. 将库放在命令行的结尾
  2. 如果各个库的成员是相互独立(也就是说没有成员引用另一个成员定义的符号),那么这些库就可以按照任何顺序放置在命令行的结尾处。
  3. 如果库不是相互独立的,那么它们必须排序,使得对于每个被存档文件的成员外部引用的符号s,在命令行中至少有一个s的定义是在对s的引用之后的,如果可行,也可将其合并。

易错点

7.7重定位

组成

  1. 重定位节和符号定义:链接器将所有相同类型的节合并为同一类型的新的聚合节。
  2. 重定位节中的符号引用:链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址器,这一步依赖千可重定位目标模块中称为重定位条目的数据结构。

两种最基本的重定位类型:

  1. R_X86_64_PC32:重定位一个使用 32 PC 相对地址的引用
  2. R X86_64_32:重定位一个使用 32 位绝对地址的引用。

7.8可执行目标文件

格式:

  1. 类似于可重定位目标文件的格式
  2. 它还包括程序的入口点 (entry point) , 也就是当程运行时要执行的第一条指令的地址
  3. 除了这些节巳经被重定位到它们最终的运行时内存地址以外,.init 节定义了一个小函数,叫做_init, 程序的初始化代码会调用它。
  4. 可执行文件是完全链接的(已被重定位) 所以它不再需要.rel节
  5. ELF 可执行文件被设计得很容易加载到内存,可执行文件的连续的片 (chunk) 被映射到连续的内存段,程序头部表描述了这种映射关系。

7.9加载可执行目标文件

加载:加载器将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程

序的第一条指令或入口点来运行该程序。这个将程序复制到内存并运行的过程叫做加栽。

特点

  1. 每个 Linux 程序都有一个运行时内存映像
  2. Linux x86-64 系统中,代码段总是从地址 Ox400000 处开始,后面是数据段。运行时堆在数据段之后,通过调用 malloc 库往上增长。堆后面的区域是为共享模块保留的。用户栈总是从最大的合法用户地址 (248 -1) 开始,向较小内存地址增长。栈上的区域,从地址248开始,是为内核 (kernel) 中的代码和数据保留的。
  3. .data 段有对齐要求,所以代码段和数据段之间是有间隙的。
  4. 在分配栈、共享库和堆段运行时地址的时候,链接器还会使用地址空间布局随机化,但它们的相对位置是不变的

7.10动态链接共享库

静态库缺点:

  1. 需要定期维护和更新.
  2. 几乎每个C程序都使用标准IO函数,在运行时,这些函数的代码会被复制到每个运行进程的文本段中,这是对稀缺的内存系统资源的极大浪费

概念:致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接, 是由动态链接器来执行的。共享库也称为共享目标, Linux 系统中通常用.so后缀来表示。微软的操作系统大量地使用了共享库,它们称为 DLL(动态链接库)。

两种方式

  1. 任何给定的文件系统中,对于一个库只有一个.so文件,所有引用该库的可执行目标文件共享这个.so文件中的代码与数据,不是像静态库的内容那样被复制和嵌入到引用它们可执行的文件中。
  2. 在内存中,一个共享库的.text节的一个副本可以被不同的正在运行的进程共享

7.11从应用程序中加载和链接共享库

应用:分发软件与构建高性能Web服务器。

接口

#include <dlfcn.h>

void *dlopen(const char *filename, int flag); //加载和链接共享库 filename

void *dlsym(void *handle, char *symbol);// 如果symbol存在,就返回符号的地址

//否则返回 NULL

int dlclose (void *handle);// 如果没有其他共享库还在使用这个共享库

//dlclose 数就卸载该共享库

const char *dlerror(void);

// 描述调用 dlopen dlsym 或者 dlclose 函数时发生的最近的错误

//如果没有错误发生,就返回 NULL

7.12位置无关代码

位置无关代码 (Position-Independent Code, PIC):可以加载而无需重定位的代码称。

用户对 GCC 使用 -fpic 选项指示 GNU 编译系统生成 PIC 代码。共享库的编译必须总

使用该选项

7.13库打桩机制

概念:允许截获对共享库函数的调用,取而代之执行自己的代码。使用打桩机制,可以追踪对某个特殊库函数的调用次数,验证和追踪它的输入和输出值,或者甚至把它替换成一个完全不同的实现

        1.编译时打桩:linux> gee -I. -o inte int.e mymalloe.o

由于有-I.参数,所以会进行打桩,它告诉C预处理器在搜索通常的系统目录之前,先在当前目录中查找 malloc.h 。

        2.链接时打桩

Linux 静态链接器支持用 --wrap 标志进行链接时打桩。这个标志告诉链接器,把对符号f的引用解析成__wrap_f(前缀是两个下划线),还要把对符号__real_£( 前缀是两个下划线)的引用解析为f。

        3.运行时打桩

构建包含malloc和free包装函数的共享库的方法:

linux> gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.e -ldl

编译主程序:

linux> gcc -o intr int.c

7.14处理目标文件的工具

  1. AR: 创建静态库,插入删除列出提取成员。
  2. STRINGS: 列出一个目标文件中所有可打印的字符串
  3. STRIP: 目标文件中删除符号表信息
  4. NM: 列出一个目标文的符号表中定义的符号。
  5. SIZE: 列出目标文件中节的名字和大小
  6. READELF: 显示 个目标文件的完整结构,包括 ELF 头中编码的所有信息。包含SIZE和NM 功能。
  7. OBJDUMP: 所有二进制工具之母。能够显示一个目标文件中所有的信息。它最大的作用是反汇编 .text 节中的二进制指令
  8. LDD: 列出一个可执行文件在运行时所需要的共享库

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>