第七章 链接

93 阅读7分钟

学习链接相关知识的好处

  • 帮助你构建大型程序
  • 帮助你避免一些危险的编程错误
  • 帮助理解语言的作用域是如何实现的
  • 帮助理解其他重要的系统概念
  • 能够利用共享库

7.1编译器驱动程序

经过一系列操作生成可执行文件

image.png

gcc -Og -o prog main.c sum.c  
Og表示优化程度 -o 是 outputfile的首字母 prog是可执行目标文件的名字

  1. 将C源文件main.c翻译成一个ASCII码的中间文件 cpp -o main.i main.c
  2. 驱动C语言编译器将main.i翻译成一个ASCII汇编语言文件main.s
    cc -S -o main.s main.i 其中-S表示不对文件进行汇编和链接操作
  3. 驱动汇编器(as) 及那个main.s翻译成一个可重定位的目标文件
    as -o main.o main.s //as 表示assembler(汇编器)
  4. 运行链接器ld 将main.o 和 sum.o 以及一些必要的系统目标文件组合起来,创建一个可执行目标文件
    ld -o prog

7.2 静态链接

为了构造可执行文件链接器主要完成的两个任务

  • 符号解析(symbol resolution)
  • 重定位(relocation)

目标文件纯粹是字节块的集合,这些块中,有些包含程序代码,有些包含程序数据,其他的包含引导链接器和加载器的数据结构。链接器将这些块链接起来,确定被连接块的运行时位置,并且修改代码和数据块中的各种位置。

7.3 目标文件

目标文件的三种形式

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

7.4可重定位目标文件

tips

  • 占位符就是先占住一个固定的位置,等着你再往里面添加内容的符号,广泛用于计算机中各类文档的编辑。 格式占位符(%)是在C/C++语言中格式输入函数,如scanf、printf 等函数中使用。 其意义就是起到格式占位的意思,表示在该位置有输入或者输出。 image.png
  • .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函数和全局变量,不能被其他模块引用

符号表条目

image.png 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 // 链接

image.png

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

解析引用必须要有严格的顺序,不然会导致链接失败
链接过程中会维护三个集合

  • 集合E(这个集合中的文件会被合并起来形成可执行文件)
  • 集合U (引用了但未解析的符号)
  • 集合D (在前面的输入文件已经定义的符号)

7.7重定位

重定位将合并输入模块,并未每个符号分配运行时的地址,主要由两个步骤组成

  • 重定位节和符号定义 1.链接器将相同类型的节合并为同一类型的聚合节 2.链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,赋给输入模块定义的每个符号
  • 重定位节中的符号引用 链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址

7.7.1 重定位条目

7.7.2 重定位符号引用

7.8可执行目标文件

image.png 可执行文件得连续的片(chunk)被映射到连续的内存段。程序头部表描述了这种映射关系

image.png 对于任何段s,链接器必须选择一个起始地址vaddr使得vaddr mod align = off mod align

7.9加载可执行目标文件

image.png 执行过程:

  1. 在程序头部表的引导下加载器将可执行文件的片(chunk)复制到代码段和数据段
  2. 加载器跳转到程序的入口点,就是_start函数的地址(在ctr1.o中定义的)
  3. _start函数调用启动函数__libc_start_main,该函数定义在libc.so中.他初始化执行环境,调用main函数,处理main函数的返回值

7.10 动态链接共享库

静态库的缺点:

  • 如果要对库进行更新就需要全部重新链接非常的麻烦
  • 几乎每个C程序都会使用标准I/O函数,这些函数代码会被复制每个运行的进程的文本段造成极大的浪费

image.png 创建和链接共享库

gcc -shared -fpic -o libvector.so addvec.c multvec.c
gcc -o prog21 main2.c ./libvector.so
## 7.11 从应用程序中加载和链接共享库