学习链接相关知识的好处
- 帮助你构建大型程序
- 帮助你避免一些危险的编程错误
- 帮助理解语言的作用域是如何实现的
- 帮助理解其他重要的系统概念
- 能够利用共享库
7.1编译器驱动程序
经过一系列操作生成可执行文件
gcc -Og -o prog main.c sum.c
Og表示优化程度 -o 是 outputfile的首字母 prog是可执行目标文件的名字
- 将C源文件main.c翻译成一个ASCII码的中间文件
cpp -o main.i main.c - 驱动C语言编译器将main.i翻译成一个ASCII汇编语言文件main.s
cc -S -o main.s main.i其中-S表示不对文件进行汇编和链接操作 - 驱动汇编器(as) 及那个main.s翻译成一个可重定位的目标文件
as -o main.o main.s//as 表示assembler(汇编器) - 运行链接器ld 将main.o 和 sum.o 以及一些必要的系统目标文件组合起来,创建一个可执行目标文件
ld -o prog
7.2 静态链接
为了构造可执行文件链接器主要完成的两个任务
- 符号解析(symbol resolution)
- 重定位(relocation)
目标文件纯粹是字节块的集合,这些块中,有些包含程序代码,有些包含程序数据,其他的包含引导链接器和加载器的数据结构。链接器将这些块链接起来,确定被连接块的运行时位置,并且修改代码和数据块中的各种位置。
7.3 目标文件
目标文件的三种形式
- 可重定位目标文件 包含二进制代码和数据 在编译时期与其他可重定位目标文件合并起来,创建一个可执行目标文件
- 可执行目标文件 包含二进制代码和数据,其形式可以直接复制到内存并执行
- 共享目标文件 一种特殊的可重定位目标文件 可以在加载或者运行时被动态地加载进内存并链接
7.4可重定位目标文件
tips
- 占位符就是先占住一个固定的位置,等着你再往里面添加内容的符号,广泛用于计算机中各类文档的编辑。 格式占位符(%)是在C/C++语言中格式输入函数,如scanf、printf 等函数中使用。 其意义就是起到格式占位的意思,表示在该位置有输入或者输出。
- .text 保存已编译程序的机器代码
- .rodata 只读数据,比如printf语句中的格式串和switch中的跳转表
- .data 已初始化的全局和静态C变量
- .bss 未初始化的全局和静态C变量,已经被初始化为0的全局或静态变量 (这个节不占用实际空间,仅仅时一个占位符,运行时,在内存中分配这些变量)
- CMOON 为初始化的全局变量
- .symtab 一个符号表,存放程序中定义和引用的函数和全局变量的信息
- .rel.text 一个 .text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置
- .rel.data 被模块引用或定义的所有全局变量的重定位信息
- .debug 一个调试符号表
- .line 原始 C 源程序中的行号和 .text 节中机器指令之间的映射。只有以 -g 选项调 用编译器驱动程序时,才会得到这张表。
- .strtab: 一个字符串表,其内容包括 .symtab 和 .debug 节中的符号表,以及节头 部中的节名字。字符串表就是以 null 结尾的字符串的序列。
7.5符号和符号表
三种不同的符号 :
- 由模块m定义并能被其他模块引用的全局符号。全局链接器符号对应非静态的C函数和全局变量
- 由其他模块定义并被m引用的全局符号 这些符号称之为外部符号,对应于在其他模块中定义的非今天C函数和全局变量
- 只被m定义和引用的局部符号。对应于static属性的C函数和全局变量,不能被其他模块引用
符号表条目
name 是字符串表中的字节偏移,指向符号的以 null 结尾的字符串名字。 value 是符号的地址。对于可重定位的模块来说, value 是距定义目标的节的起始位置的偏移。对于可执行目标文件来说,该值是一个绝对运行时地址。 size 是目标的大小(以字节为单位)。 type 通常要么是数据,要么是函数。符号表还可以包含各个节的条目,以及对应原始源 文件的路径名的条目。所以这些目标的类型也有所不同。 binding 字段表示符号是本地的还是全局的。
7.6符号解析
7.6.1链接器如何解析多重定义的全局符号
在编译时,编译器向汇编器输出每个全局符号,或者是strong或者是weak,汇编器把这个信息隐含地编码在可重定位目标文件的符号表里。 函数和已经初始化的全局变量为强符号 未初始化的全局变量为弱符号 linux链接器有如下规则来处理多重定义的符号名
- 不允许有多个同名的强符号
- 在强弱符号同名时,优先选择强符号
- 多个弱符号同名则随机选择
7.6.2 与静态库链接
所有的编译系统都提供了一种机制,将所有的目标模块都打包成为一个单独的文件,这个文件称之为静态库,它可以用作链接器的输入,当链接器构造一个可执行文件时,它只复制静态库里被应用程序引用的目标模块。
创建一个静态库:
gcc -c addvec.c multvec.c // 生成可重定位目标文件但不链接
ar tcs libvector.a addvec.c multvec.c // 生成一个静态库
使用一个静态库
gcc -c main2.c // 同理上面
gcc -static -o prog main2.o ./libvector.a // 链接
7.6.3 链接器如何使用静态库来解析引用
解析引用必须要有严格的顺序,不然会导致链接失败
链接过程中会维护三个集合
- 集合E(这个集合中的文件会被合并起来形成可执行文件)
- 集合U (引用了但未解析的符号)
- 集合D (在前面的输入文件已经定义的符号)
7.7重定位
重定位将合并输入模块,并未每个符号分配运行时的地址,主要由两个步骤组成
- 重定位节和符号定义 1.链接器将相同类型的节合并为同一类型的聚合节 2.链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,赋给输入模块定义的每个符号
- 重定位节中的符号引用 链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址
7.7.1 重定位条目
7.7.2 重定位符号引用
7.8可执行目标文件
可执行文件得连续的片(chunk)被映射到连续的内存段。程序头部表描述了这种映射关系
对于任何段s,链接器必须选择一个起始地址vaddr使得vaddr mod align = off mod align
7.9加载可执行目标文件
执行过程:
- 在程序头部表的引导下加载器将可执行文件的片(chunk)复制到代码段和数据段
- 加载器跳转到程序的入口点,就是_start函数的地址(在ctr1.o中定义的)
- _start函数调用启动函数__libc_start_main,该函数定义在libc.so中.他初始化执行环境,调用main函数,处理main函数的返回值
7.10 动态链接共享库
静态库的缺点:
- 如果要对库进行更新就需要全部重新链接非常的麻烦
- 几乎每个C程序都会使用标准I/O函数,这些函数代码会被复制每个运行的进程的文本段造成极大的浪费
创建和链接共享库
gcc -shared -fpic -o libvector.so addvec.c multvec.c
gcc -o prog21 main2.c ./libvector.so
## 7.11 从应用程序中加载和链接共享库