第7章--链接

链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行预加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。

一、编译器驱动程序

大多数编译系统提供编译器驱动程序,它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。

二、静态链接

为了构造可执行文件,连接器必须完成两个主要任务:

  • 符号解析。目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量(即C语言中任何以static属性声明的变量)。符号解析的目的是将每个符号引用正好和一个符号定义关联起来。
  • 重定位。编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得他们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别的执行这样的重定位。

三、目标文件

目标文件有三种形式:

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

四、可重定位目标文件

一个典型的ELF可重定位目标文件包含下面几个节:

.text: 已编译程序的机器代码。

.rodata: 只读数据,比如printf语句中的格式串和开关语句中的跳转表。

.data: 已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不出现在.bss节中。

.bss: 未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间。

.symtab: 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。

.rel.text: 一个.text节中位置的列表,通常省略。

.rel.data: 被模块引用或定义的所有全局变量的重定位信息。

.debug: 一个调试符号表,只有以-g选项调用编译器驱动程序时,才会得到这张表。

.line: 原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。

.strtab: 一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。

五、符号和符号表

每个可重定位目标模块m都有一个符号表,它包含m定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:

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

六、符号解析

链接器解析符号引用的方法是将每个引用与他输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。

1. 链接器如何解析多重定义的全局符号

在编译时,编译器向汇编器输出每个全局符号,或者是强或者是弱,而汇编器把这个信息隐含地编码在可重定位目标文件的符号表里。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。

根据强弱符号的定义,Linux链接器使用下面的规则来处理多重定义的符号名:

  • 规则1:不允许有多个同名的强符号。
  • 规则2:如果有一个强符号和多个弱符号同名,那么选择强符号。
  • 规则3:如果有多个弱符号同名,那么从这些弱符号中任意选择一个。

2. 与静态库链接

实际上,所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库,他可以用做链接器的输入。当链接器构造一个输出的可执行文件时,它只复制静态库里被应用程序引用的目标模块。

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

-static参数告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,他可以加载到内存并运行,在加载时无需更进一步的链接。-lverctor参数是libvector.a的缩写,-L.参数告诉链接器在当前目录下查找libvector.a。

3. 链接器如何使用静态库来解析引用

虽然静态库很有用,但是他们同时也是一个程序员疑惑的源头,原因在于Linux链接器使用他们解析外部引用的方式。在符号解析阶段,链接器从左到右按照他们在编译器驱动程序命令行上出现的顺序来扫描可重定位目标文件和存档文件。

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

七、重定位

1. 重定位条目

当汇编器生成一个目标模块时,他并不知道数据和代码最终将放在内存中的什么位置。他也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以,无论何时汇编器遇到对最终位置未知的目标引用,他就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中。已初始化数据的重定位条目放在.rel.data中。

以下结构体展示了ELF重定位条目的格式。offset是需要被修改的引用的节偏移。symbol标识被修改引用应该指向的符号。type告知链接器如何修改新的引用。addend是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。

typedef struct {
	long offset;
	long type:32;
         symbol:32;
	long addend;
} Elf64_Rela;
  • R_X86_64_PC32。重定位一个使用32位PC相对地址的引用。一个PC相对地址就是距程序计数器(PC)的当前运行时值的偏移量。
  • R_X86_64_32。重定位一个使用32位绝对地址的引用。通过绝对寻址,CPU直接使用在指令中编码的32位值作为有效地址,不需要进一步修改。

八、可执行目标文件

可执行目标文件的格式类似于可重定位目标文件的格式。如下所示,ELF头描述文件的总体格式。它还包含程序的入口点,也就是当程序运行时要执行的第一条指令的地址。.text、.rodata和.data节与可重定位目标文件中的节是相似的,除了这些节已经被重定位到他们最终的运行时内存地址以外。.init节定义了一个小函数,叫做_init,程序的初始化代码会调用它。因为可执行文件是完全链接的(已被重定位),所以它不再需要.rel节。

ELF头

段头部表

.init: 初始化代码。

.text: 已编译程序的机器代码。

.rodata: 只读数据,比如printf语句中的格式串和开关语句中的跳转表。

.data: 已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不出现在.bss节中。

.bss: 未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间。

.symtab: 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。

.debug: 一个调试符号表,只有以-g选项调用编译器驱动程序时,才会得到这张表。

.line: 原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。

.strtab: 一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。

节头部表

九、动态链接共享库

共享库是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接,是有一个叫做动态链接器的程序来执行的。共享库也称为共享目标,在Linux系统中通常用.so后缀来表示。

共享库是以两种不同的方式来“共享”的。首先,在任何给定的文件系统中,对于一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库的内容那样被复制和嵌入到引用他们的可执行的文件中。其次,在内存中,一个共享库的.text节的一个副本可以被不同的正在运行的进程共享。

为了构造共享库libvector.so,我们调用编译器驱动程序,给编译器和链接器如下特殊指令:

gcc -fpic -shared -o libvector.so addvec.c multvec.c

-fpic选项指示编译器生成与位置无关的代码。-shared选项指示链接器创建一个共享的目标文件。

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

到目前为止,我们已经讨论了在应用程序被加载后执行前时,动态链接器加载和链接共享库的情景。然而,应用程序还可能在他运行时要求动态链接器加载和链接某个共享库,而无需在编译时将那些库链接到应用中。

Linux系统为动态链接器提供了一组简单的接口,允许应用程序在运行时加载和链接共享库。

#include <dlfcn.h>
void *dlopen(const char *filename, int flag);  // 加载和链接共享库filename
void *dlsym(void *handle, char *symbol);  // 获取共享库中符号symbol地址
int dlclose(void *handle);  // 卸载共享库
const char *dlerror(void);  // 获取最近发生的错误 

十一、位置无关代码

可以加载而无需重定位的代码称为位置无关代码(PIC)。用户对GCC使用-fpic选项指示GNU编译系统生成PIC代码。共享库的编译必须总是使用该选项。

1. PIC数据引用

编译器通过运用以下这个有趣的事实来生成对全局变量的PIC引用:无论我们在内存中的何处加载一个目标模块(包含共享目标模块),数据段与代码段的距离总是保持不变。因此,代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量,与代码段和数据段的绝对内存位置是无关的。

想要生成对全局变量PIC引用的编译器利用了这个事实,他在数据段开始的地方创建了一个表,叫做全局偏移量表(GOT)。在GOT中,每个被这个目标模块引用的全局数据目标(过程或全局变量)都有一个8字节条目。编译器还为GOT中每个条目生成一个重定位记录。在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标的正确的绝对地址。每个引用全局目标的目标模块都有自己的GOT。

2. PIC函数调用

假设程序调用一个由共享库定义的函数。编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候在解析它。不过,这种方法并不是PIC,因为它需要链接器修改调用模块的代码段,GNU编译系统使用了一种很有趣的技术来解决这个问题,称为延时绑定,将过程地址的绑定推迟到第一次调用该过程时。

使用延时绑定的动机是对于一个像libc.so这样的共享库输出的成百上千个函数中,一个典型的应用程序只会使用其中很少的一部分。把函数地址的解析推迟到它实际被调用的地方,能避免动态链接器在加载时进行成百上千个其实并不需要的重定位。第一次调用过程的运行时开销很大,但是其后的每次调用都只会花费一条指令和一个间接的内存引用。

十二、库打桩机制

Linux链接器支持一个很强大的技术,称为库打桩,它允许你截获对共享库函数的调用,取而代之执行自己的代码。使用打桩机制,你可以追踪对某个特殊库函数的调用次数,验证和追踪他的输入和输出值,或者甚至把它替换成一个完全不同的实现。

下面是它的基本思想:给定一个需要打桩的目标函数,创建一个包装函数,它的原型与目标函数完全一样。使用某种特殊的打桩机制,你就可以欺骗系统调用包装函数而不是目标函数了。包装函数通常会执行他自己的逻辑,然后调用目标函数,再将目标函数的返回值传递给调用者。

1. 编译时打桩

图7-20展示了如何使用C预处理器在编译时打桩。mymalloc.c中包装函数调用目标函数,打印追踪记录,并返回。本地的malloc.h头文件指示预处理器用对相应包装函数的调用替换掉对目标函数的调用。像下面这样编译和链接这个程序:

gcc -DCOMPILETIME -c mymalloc.c
gcc -I. -o intc int.c mymalloc.o

2. 链接时打桩

Linux静态链接器支持用–wrap f标志进行链接时打桩。这个标志告诉链接器,把对符号f分引用解析成__wrap_f(前缀是两个下划线),还要把对符号__real_f(前缀是两个下划线)的引用解析为f。图7-21给出我们示例程序的包装函数。

3. 运行时打桩

编译时打桩需要能够访问程序的源代码,链接时打桩需要能够访问程序的可重定位对象文件。不过,有一种机制能够在运行时打桩,他只需要能够访问可执行目标文件。这个很厉害的机制基于动态链接器的LD_PRELOAD环境变量。

如果LD_PRELOAD环境变量被设置为一个共享库路径名的列表(以空格或分号分隔),那么当你加载和执行一个程序,需要解析为定义的引用时,动态链接器会先搜索LD_PRELOAD库,然后才搜索任何其他的库。有了这个机制,当你加载和执行任意可执行文件时,可以对任何共享库中的任何函数打桩,包括libc.so。

十三、处理目标文件的工具

在Linux系统中有大量可用的工具可以帮助你理解和处理目标文件。

  • AR: 创建静态库,插入、删除、列出和提取成员。
  • STRINGS: 列出一个目标文件中所有可打印的字符串。
  • STRIP: 从目标文件中删除符号表信息。
  • NM: 列出一个目标文件的符号表中定义的符号。
  • SIZE: 列出目标文件中节的名字和大小。
  • READELF: 显示一个目标文件的完整结构,包含ELF头中编码的所有信息。
  • OBJDUMP: 所有二进制工具之母。能够显示一个目标文件中所有的信息。它最大的作用是反汇编.text节中的二进制指令。
  • LDD: 列出一个可执行文件在运行时所需要的共享库。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第4Excel 2010的使用 2023/5/31 1 计算机应用基础第4-中文Excel-全文共74页,当前为第1页。 本学习目标Excel 2010 的基本基本功能; 掌握Excel 2010的启动和退出、工作簿的建立、保存、关闭、重命名等操作; 熟悉各种类型的数据输入、编辑; 熟练掌握单元格和单元格区域、工作表的插入、删除、移动、复制; 掌握工作表的格式设置; 灵活运用公式和函数应用于复杂的计算; 掌握图表的建立和修饰方法; 掌握打印工作表的相关操作,理链接的概念; 了保护和隐藏工作表的途径。 2023/5/31 2 计算机应用基础第4-中文Excel-全文共74页,当前为第2页。 第四 Excel 2010的使用 4.1 Excel 2010概述 4.2 工作簿的基本操作 4.3 工作表的格式设置 4.4 公式与函数 4.5 图表功能 2023/5/31 3 计算机应用基础第4-中文Excel-全文共74页,当前为第3页。 第四 Excel 2010的使用 4.6 数据清单 4.7 工作表的打印和超链接 4.8 工作簿和工作表的保护和隐藏 4.9 小结 4.10 习题 2023/5/31 4 计算机应用基础第4-中文Excel-全文共74页,当前为第4页。 4.1 Excel 2010概述 4.1.1 Excel 2010的基本功能 4.1.2 Excel 2010的启动 4.1.3 Excel 2010的退出 2023/5/31 5 计算机应用基础第4-中文Excel-全文共74页,当前为第5页。 4.1.1 Excel 2010的基本功能 简便的表格编辑 丰富的计算能力 直观的图表表现 直达的数据库操作 统一的数据共享 2023/5/31 6 计算机应用基础第4-中文Excel-全文共74页,当前为第6页。 4.1.2 Excel 2010的启动 1. 双击桌面上的Excel快捷方式图标。 2. 单击【开始】 【所有程序】 【Microsoft Office】 【Microsoft Excel 2010】 3. 利用已打开的工作簿文件启动。 2023/5/31 7 计算机应用基础第4-中文Excel-全文共74页,当前为第7页。 4.1.3 Excel 2010的退出 点击左上角的 ,再选择【关闭】。 点击右上角的【关闭】按钮 。 点击【文件】 【关闭】。 直接按下【Alt】+【F4】组合键。 2023/5/31 8 计算机应用基础第4-中文Excel-全文共74页,当前为第8页。 4.2 工作簿的基本操作 4.2.1 工作簿和工作表的建立、保存和退出 4.2.2 数据输入和编辑 4.2.3 单元格和工作表的插入、删除、复制、移动 4.2.4 工作表的重命名和工作表窗口的拆分和冻结 2023/5/31 9 计算机应用基础第4-中文Excel-全文共74页,当前为第9页。 4.2.1 工作簿和工作表的建立、保存和退出 新工作簿默认初始名称为"工作簿1.xls" 默认设置的新工作簿包含三个工作表 单元格是组成工作表的最小单位 单元格地址的一般格式:"工作表!列号+行号" 活动单元格是指当前正在编辑的单元格 2023/5/31 10 计算机应用基础第4-中文Excel-全文共74页,当前为第10页。 4.2.2 数据输入和编辑 一、工作表中单元格和区域的选定 二、数据输入 三、数据删除 四、数据修改 五、单元格、行、列的插入与删除 2023/5/31 11 计算机应用基础第4-中文Excel-全文共74页,当前为第11页。 4.2.2 数据输入和编辑 一、工作表中单元格和区域的选定 二、数据输入(待续) 数值 日期和间 文本(字符) 逻辑值 2023/5/31 12 计算机应用基础第4-中文Excel-全文共74页,当前为第12页。 4.2.2 数据输入和编辑 二、数据输入(续) 同对多个单元格或对多个工作表表输入相同的数据 数据的有效性检查 自动填充数据 输入批注信息 输入对象型数据 2023/5/31 13 计算机应用基础第4-中文Excel-全文共74页,当前为第13页。 4.2.2 数据输入和编辑 一、工作表中单元格和区域的选定 二、数据输入 三、数据删除 四、数据修改 五、单元格、行、列的插入与删除 2023/5/31 14 计算机应用基础第4-中文Excel-全文共74页,当前为第14页。 4.2.3 单元格和工作表的插入、删除、复制、移动 单元格内容的粘贴: 2023/5/31 15 计算机应用基础第4-中文Excel-全文共74页,当前为第15页。 4.2.3 单元格和工作表的插入、删除、复制、移动 移动或复制一个或多个工作表: 2023/5/31 1
### 回答1: 《Linux+, 第七: Linux 文件系统》是《Linux+认证教程》中的第七,重点介绍了Linux操作系统中的文件系统。在这一中,读者将了Linux文件系统的基本概念、常用的文件系统类型以及文件系统的管理和维护等内容。 首先,本开始介绍了Linux文件系统的基本概念,包括文件和目录的概念以及在Linux中的表示方式。读者将学习到如何使用文件路径和文件名来访问文件和目录。同,还介绍了Linux系统中的特殊目录,如根目录、用户主目录和共享目录等。 接着,本继续讲了常见的文件系统类型。Linux支持多种文件系统类型,如EXT4、XFS、Btrfs等。读者将了到每种文件系统类型的特点和适用场景,并学习如何在Linux中创建和格式化不同类型的文件系统。 然后,本了文件系统的管理和维护。读者将学习如何使用命令行工具来管理和操作文件系统,如创建、删除、复制和移动文件等。同,还介绍了文件系统的权限和属性配置,如设置文件的拥有者和权限等。 最后,本还涉及了文件系统的备份和恢复。读者将学习如何使用备份工具来对文件系统进行备份,以及如何从备份中恢复数据。 通过学习本内容,读者将深入了Linux文件系统的原理和操作方法。这对于Linux系统管理员和开发人员来说非常重要,因为文件系统是Linux操作系统中最基础和核心的部分之一。掌握了文件系统的知识,读者将能够更好地管理和维护Linux系统,并决文件系统相关的问题。 ### 回答2: 《Linux头歌》是一本关于Linux操作系统的技术书籍,第七主要讲述了Linux文件系统的管理和操作。本主要涵盖了以下几个方面的内容。 首先,本介绍了Linux文件系统的基本概念和特点。Linux文件系统采用了类似于树状结构的层次目录结构,以根目录"/"为起点,通过不同的目录和文件来组织和管理数据。同Linux文件系统还具有对文件和目录进行权限管理的功能,确保安全性。 接着,本详细介绍了如何在Linux系统中创建、删除、复制和移动文件和目录。通过命令行工具或图形界面工具,用户可以轻松地对文件和目录进行操作,满足不同的需求。此外,还介绍了文件和目录的属性和权限设置,确保文件的安全性。 然后,本Linux文件系统中的硬链接和软链接的概念和用法。硬链接是指多个文件指向同一个物理存储空间,而软链接是指一个文件指向另一个文件,类似于快捷方式。用户可以根据需要使用不同的链接方式,帮助管理和组织文件。 最后,本还介绍了Linux文件系统的挂载和卸载操作。在Linux系统中,用户可以通过挂载操作将外部设备或远程文件系统与本地文件系统连接起来,以便访问和操作外部文件。同,也可以通过卸载操作安全地断开与外部文件系统的连接。 总之,第七Linux头歌》详细介绍了Linux文件系统的管理和操作技巧,帮助读者更好地理和掌握Linux操作系统的文件系统特性和使用方法。无论是初学者还是有一定经验的用户,都可以从中获得实用的知识和技能,提高对Linux系统的操作能力。 ### 回答3: 《Linux内核设计与实现》是一本经典的Linux内核相关书籍,其中第七主要介绍了Linux内核的关键组成部分——系统调用及库函数。系统调用是操作系统内核提供给用户程序的一组接口,它们允许用户程序通过内核来访问底层系统资源和服务。这一内容对于深入理 Linux 内核的使用和开发具有重要意义。 首先,第七详细介绍了系统调用的实现原理和机制。作者析了系统调用表的结构和功能,并且详细说明了Linux内核是如何通过系统调用号来定位和调用相应的系统调用函数的。这些系统调用函数在内核空间中运行,并能够处理用户程序发起的请求,实现具体的功能。 其次,本还介绍了用户空间和内核空间之间的切换机制。当用户程序发起系统调用的候,会触发用户态和内核态之间的转换,在转换过程中,内核会根据系统调用号找到对应的系统调用函数,并执行相应的操作。这一详细分析了切换过程的细节以及相关的数据结构。 此外,本还对一些常用的系统调用进行了详细介绍,如文件操作相关的系统调用(如open、read、write等),进程管理相关的系统调用(如fork、execve、wait等),以及网络通信相关的系统调用(如socket、bind、connect等)。对于开发者来说,熟悉这些系统调用的使用方法和原理非常重要,能够帮助他们更好地设计和开发Linux内核的应用程序。 总之,第七Linux内核设计与实现》详细介绍了Linux内核的系统调用和库函数的实现原理和机制,以及常用系统调用的使用方法。通过学习这一的内容,读者能够深入理Linux内核的工作原理,对于使用和开发Linux内核应用程序具有指导意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值