csapp第7章《链接》基本概念

何为链接?

我们写的c语言程序是一个.c,但是这个.c到.exe过程经历了什么呢?首先是预处理从.c -->> .i ,然后是变成汇编语言 .i -->> .s ,接着是将汇编语言变成机器可以识别的二进制文件 .s -->>.o ,接下来的一步就是这章的内容了,也就是将其他的.o文件链接(合并)到我们写的程序的.o文件中,最后生成.exe。

合并各种代码段、数据段的过程就叫做链接,就比如说,你通过#include<stdio.h>引用了printf()函数,但是编译器如何将printf()的定义放到你当前的.c中去呢? 就是通过链接

  • 链接可以发生在编译时,也就是在源代码进行编译成机器代码的时候
  • 链接也可以发生在加载时,也就是程序被加载器加载到内存并执行时
  • 链接也可以发生在运行时,也就是由程序自发的执行

这儿着重说下发生在编译时的静态链接

静态链接

1. 静态链接时链接器需要做什么?

  • 符号解析

    首先说一下符号的概念,我们在编写c程序的时候,我们往往会在使用一个变量前先声明它,为什么要这么做? 因为对于编译器来说,如果想要使用一个变量,那么就必须去这个变量的地址取值,如果你在使用之前没有声明,那么编译器就不知道这个变量在哪,先不说运行,首先过不了编译器这一关。这个变量就可以看成一个符号,一个符号可以对应于一个函数、一个全局变量或一个静态变量符号解析的目的就是为了将一个符号的信息包装成一个结构体(例如Elf64_Symbol)放到.symtab节(符号表)中去,并且把这个符号的内容放到特定的节中,方便被引用 后面会说明节的概念

  • 重定位

    重定位就是重新定位符号的引用地址(也就是修改Elf64_Symbol结构体中的value,对.o文件来说,这个value是节偏移,对于.exe来说,这个value是绝对地址),例如,当cpu执行movl (xxx) %eax,当cpu想从某个内存取值到寄存器的时候,那么这个内存地址就是明确知道的了,而重定位就是干这个活的。因为符号刚合并好,但是还没给他们重新分配运行时地址,就需要分配运行时地址这么一个过程了!

2. 链接需要的.o文件到底是个什么玩意?

在这里插入图片描述

引用书上的一张图,如图,.o可重定位目标文件由几个叫做节的单位构成,这里说一下其中比较重要的几个节:

  • .text节。用来存放已经编译好的程序的机器代码
  • .data节。已经初始化的全局变量和静态变量
  • .bss节。为初始化的全局和静态变量,这里说明一下,函数中的局部变量是不会被放到这些节中的,因为局部变量是程序在运行时运行在相应进程中的用户堆栈中的!执行会后会自动释放的,不属于符号
  • .symtab节。用来存放程序中定义的符号的信息(例如Elf64_Symbol结构体数组)

3. 有哪些符号?

对于1个.c文件,可用的变量除了局部变量,有静态变量(本地变量)、非静态变量(全局变量,可悲外部调用)、外部变量(通过extern声明)

符号解析的细节:

在知道了符号是什么,有哪些符号,.o文件的构造之后,就可以来看符号解析的细节了。

符号解析,说通俗点,也就是整合各个.o文件的符号,也就是为每一个符号的引用提供定义,让每一个符号被引用的时候知道这个符号在哪,内容是什么等信息。

链接器通过维护3个集合:

  • E(可重定位目标文件集合 .o,这个集合的文件会合并形成.exe)
  • U(引用时找不到定义的符号集合)
  • D(引用过并且定义过的符号集合)

我们知道,链接是一个合并.o文件中符号的过程,所以,链接器的输入就是已有的一些.o文件,当第一个.o文件进入链接器,这个.o文件加入集合E,并且链接器根据集合U来解析所需要的符号定义,若满足U中引用但未定义的符号,则加入集合D,并且从U中删除这个定义过的符号

(这一点是很重要的,链接器是按需解析符号定义的,这么做可能会导致一个问题,就是当一个外部的符号在被引用之前先被解析了,因为U中没有对这个符号的引用,所以这个符号并不会加入到D,当后面再需要引用这个符号的时候,链接器就会把这个符号加入U,并且这个符号一直存在于U中,不会被消除,最后会导致程序错误)。

通过维护这3个集合,当把所有.o文件都解析完之后,链接器会判断集合U是否位空,若为空,则表示所有引用的符号都找到了相应的解析,则表示链接成功,等待合并、重定位操作;若非空,则表示引用的一些符号没有找到相应的符号定义,那么就会输出一个错误并且终止。

重定位的细节:

  1. 合并节 (多个.o -->> 一个.o)

    通过上面的链接过程,我们知道了链接器整合了各个.o文件的符号的定义和引用,让每一个.o文件中对每个符号的引用都找得到相应的符号定义,那么合并节这一步就是将各个.o文件中类型相同的节合并,比如说把多个.o文件的.data节合成一个新的.data节、把多个.text合并成1个.text,这样一来,多个.o文件就变成了一个.o文件了!

2.给符号分配运行时地址(.o -->> .exe)

  • 为什么

    当完成了合并节的过程,对于节中的符号,在变成.exe前本身是没有被分配内存地址,它是存放在硬盘中的一段字节,对于.o程序,它的符号引用是通过符号表条目中属性section(表示第n个节)来获知这个符号在哪个节,并通过value(表示一个节内第n个字节)来获取这个符号的内容

    那么如果当.exe程序在内存中运行想要引用符号的时候,这个符号肯定不能用偏移量来表示,因为cpu不认得你这个偏移量,cpu只认得内存的地址,所以就需要重定位符号地址这个过程了!同样的,对于在.text节中的代码本身也没有被分配运行时的地址,那么当程序被放到内存中运行,那cpu怎么晓得你代码搁哪呢?

  • 怎么做

    重定位符号地址的目的在于给代码节中的符号引用、数据节中的符号定义分配运行时的内存地址,也就是更改代码段中符号引用的地址,并且通过修改符号表中符号条目的value,将其从字节偏移修改成运行时绝对地址

    重定位的类型有许多,这里值讨论2种:

    • R_X86_64_PC32 :32位的PC相对地址,例如call指令,通过将当前PC+offset来指向目标函数指令的起始位置
    • R_X86_64_32:32位的绝对地址,也就是内存中的绝对地址,当cpu运行的程序段中想访问某个符号的时候,就可以直接根据绝对地址在内存中取值了
  • 重定位条目

    Elf64_Rela{offset,type,symbol,addend} ,每一个需要被重定位的符号都会有一个重定位条目,这个条目说明了这个符号应该如何被重定位的一些细节。

    • 其中offset表示符号引用相对于.o文件中.text节(代码段)起始处的字节偏移,也就是需要被重定位的符号的位置相对于代码段开始处的字节偏移量,
    • symbol表示需要重定位的符号名,
    • type表示是重定位成相对地址还是绝对地址。

具体的重定位算法、细节见书P481-482

静态链接库

静态库就是一堆已经编译好的.o文件的集合,后缀一般为.a,这个库可以作为链接器的输入,就像c语言标准静态库中的一些库函数,这个库是会被链接器识别成存档文件,当解析符号的时候,这个库只会提供符号的定义(也就是将符号定义提供给集合U),但库本身是不会加入集合E编译成.exe的

链接的过程中要注意把对库的引入放在引用库符号的.o文件的后面才行!

动态链接库

  • 动态链接库(dynamic linking library)的后缀一般为.so,动态链接的过程发生在运行时或加载时(而非编译时)。
  • 动态库也称共享库,当程序以进程为单位被运行时,动态链接器会把动态库中的符号和程序链接起来。
  • 相对于静态库,动态库链接是不需要把库中的符号复制到.exe中的,而是通过共享的方式直接使用,也就是不同的进程引用相同的虚拟地址来获取动态库中的符号

题外话:用shell运行.exe的过程

  1. shell程序本身是一个进程,当输入命令运行一个.exe的时候,就相当于调用了fork()创建了一个子进程,(这个子进程的各个区域是写时复制的,也就是说子进程的各个区域实际存在的内存地址是和父进程不同的)
  2. 然后子进程通过调用execve()函数来调用加载器,把.exe的代码段、数据段等替换原来的子进程的相应区域,
  3. 最后加载器跳转到_start地址,调用.exe的main函数,当然,有一些信息是按需加载的(按需进行页面调度)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值