第七章——链接
0.总结
- 链接编译时可以采用静态链接或动态链接。
- 连接器主要任务:符号解析和重定位。
- 多个目标文件可定义相同的符号,可以被连接到一个单独的静态库。
- 链接器可以生成部分链接的可执行文件
- 动态链接器通过加载共享库和重定位程序中的引用来完成链接任务。
1.编译器驱动程序
- 编译系统提供的调用预处理器、编译器、汇编器和链接器来构造目标文件的程序。
2.静态链接
3.目标文件
三种形式:
1、可重定位目标文件;
2、可执行目标文件;
3、共享目标文件;
4.可重定位目标文件
ELF格式
5.符号和符号表
- 每个可重定位目标模块m都有一个符号表,它包含m所定义和引用的符号的信息。
有三种不同的符号:
1、m定义 & 能被其他模块引用的全局符号;
2、其他模块定义 &被模块m引用的全局符号(外部符号);
3、只被模块m定义 & 引用的本地符号;
6.符号解析
- 将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来。
规则
1.不允许有多个强符号。
2.如果有一个强符号和多个弱符号,那么选择强符号。
3.如果有多个弱符号,那么从这些弱符号中任意选择一个。
与静态库链接
所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库(Linux下是存档文件,Windows下是lib),只拷贝静态库里被应用程序引用的目标模块。
链接时加上-static参数告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,它可以加载到存储器并执行,在加载时无需更进一步的链接。
7.重定位
- 重定位条目
- 重定位符号引用
8.可执行目标文件
- C程序开始时是一组ASCII文本文件,已经被转化为一个二进制文件,且这个二进制文件包含加载程序到存储器并运行它所需的所有信息。
9.加载可执行目标文件
- 将程序拷贝到存储器并运行的过程叫做加载。
- 在32位Linux系统中,代码段总是从地址0x08048000处开始。
- 数据段是在接下来的下一个4KB对齐的地址处。运行时堆在读/写段之后接下来的第一个4KB对齐的地址处,并通过调用malloc库往上增长。
- 还有一个段是为共享库保留的。
- 用户栈总是最大的合法用户地址开始,向下增长的(向低存储器地址方向增长)。从栈的上部开始的段是为操作系统驻留存储器的部分(内核)的代码和数据保留的。
10.动态链接共享库
- 共享库是一个目标,在运行时,可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接器(dynamic linker)的程序来执行的。
- 通常用.so后缀来表示,称为DLL(动态链接库)。
共享库是以两种不同的方式来“共享”的
1.在任何给定的文件系统中,对于一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个.so文件中的代码和数据。
2.一种共享方式就是隐式链接,基本的思路:当创建可执行文件时,静态执行一些链接,然后在程序加载时,动态完成链接过程。
3.一种共享方式就是“显式链接”,应用程序被加载时,动态链接器加载和链接共享库的情景。应用程序还可能在它运行时要求动态链接器加载和链接任意共享库,而无需在编译时链接那些库到应用中。
11.从应用程序中加载和连接共享库
void *dlopen( const char *file, int mode );//将共享目标文件打开并且映射到内存中,并且返回句柄
void *dlsym( void *restrict handle, const char *restrict name );//回一个指向被请求入口点的指针
char *dlerror();//返回 NULL 或者一个指向描述最近错误的 ASCII 字符串的指针
char *dlclose( void *handle );//关闭句柄并且取消共享目标文件的映射
12.处理目标文件的工具
AR:创建静态库,插入、删除、列出和提取成员。
STRINGS:列出一个目标文件中所有可打印的字符串。
STRIP:从目标文件中删除符号的信息。
NM:列出一个目标文件的符号表中定义的符号。
SIZE:目标文件中节的名字和大小。
READELF:显示一个目标文件的完整结构,包括ELF头中的编码的所有信息。包含SIZE和NM的功能。
OBJDUMP:所有二进制工具之母,能够显示一个目标文件中所有的信息。它最大的作用是反汇编.text节中的二进制指令。
LDD:列出一个可执行文件在运行时所需要的共享库。