链接概述

经预处理器->编译器->汇编器处理后,源文件可被转化为一组可重定位目标文件,链接器将它们组合起来形成可执行文件。

每个可重定位目标文件由不同的“代码节”和“数据节”组成,每一个节都是一个连续的字节序列。由于每个可重定位目标文件的生成是独立的,因此组合时会出现两个问题:

一是若某个可重定位目标文件中使用了外部的全局变量,而此变量定义于另一个可重定位目标文件中,怎么办?

二是生成目标文件时如何确定地址。链接器生成的可执行文件在实际运行时,需要由加载器将其代码及数据复制到内存,再将控制转移到此程序的开头,若每个链接前的目标文件都以0为起始地址,势必会造成重合,怎么办?

这就是链接器要做的两件事:符号解析与重定位

符号解析:目标文件中可能会定义或引用一定的函数,全局变量或静态变量,这些都是所谓的“符号”,符号解析的目的就是把符号的定义和引用关联起来。

重定位:如上所述,第二个问题的解决方案就是重定位,链接器会把符号的定义与内存位置相关联,重定位时将符号的引用指向此内存位置。

可重定位目标文件

下图为典型的可重定位目标文件结构:

可重定位目标文件的内容以“节”的形式存在。

ELF头描述一些系统的和文件本身的信息。

节头部表描述不同节的位置和大小。

简略讲述几个重要的节:

.text节存放代码,.rodata节存放只读数据,.data节存放已初始化的全局和静态变量,.bss节存放未初始化或被初始化为0的全局和静态变量,此节不占用实际空间,只有在运行时才会被分配实际内存空间,另外局部变量在运行时被存放于栈中,不会出现在某个节中。.symtab节存放了符号表(符号表由汇编器构造)。

注意:静态局部变量不存放在栈中,而是会出现在符号表中。若同一目标文件中存在两个同名的静态局部变量,编译器会向汇编器输出两个不同名字的符号。

符号解析

当编译器遇到一个不是当前模块定义的符号时,会生成一个符号表条目,并交给链接器处理。链接器会在所有输入模块中寻找其定义,找不到时报错。

若出现多个目标文件定义相同名字的全局变量怎么办?

Linux系统的方法:编译器向汇编器输出全局变量对应的符号时,会标记是“强”符号(函数和已初始化的全局变量)还是“弱”符号(未初始化的全局变量),注意,静态变量独属于本模块,没有多重定义的问题,无需标记。汇编器将此信息编码在符号表内,链接器在链接时根据以下规则进行处理:

1.不允许有多个同名的强符号

2.如果有一个强符号和多个弱符号同名,选强符号。

3.若多个弱符号同名,随机选一个。

给个例子:

此时,假设x的地址是0x601020,y的地址是0x601024,运行程序后,赋值x时会覆盖到y,导致发生错误。

静态链接

所有相关的函数被编译为独立的目标模块,并被打包成一个单独的文件后,此文件被称为静态库,可以用做链接器的输入,当链接器构造可执行文件时,只会复制静态库里被应用程序引用的目标模块,这么做比起将每个独立的目标模块单独输入到链接器,显然可以简化链接器输入时的步骤,当然,对运行时内存的改善无优化,对于具有引用了相同标准目标模块的程序,运行时每次都需从静态库中拷贝这些目标模块。在Linux中静态库以“存档”的形式(一组连接起来的可重定位目标文件的集合)存放于磁盘,文件名后缀是.a。

举个例子: 

左边是两个独立的目标模块,右边的main函数要调用它们,此时先为左边两个函数创建一个静态库:

随后编译和链接输入文件main.o和libvector.a:

注意顺序:链接器从左到右来扫描可重定位文件和存档文件,我的理解是,定义必须出现在引用后面。另外,库一般放在命令行的结尾。

比如上面的main2.o中调用了addvec和multvec,所以放前面,随后出现libvector.a。

重定位

链接器在符号解析完成后开始进行重定位:将相同类型的节合并,为新的节和其中定义的符号赋予新的运行时内存地址,而对于各个节中的符号引用,则会通过“重定位条目”来重定位。

重定位条目:当汇编器遇到最终位置未知的目标引用,会生成重定位条目,告诉链接器在合并成可执行文件时如何修改引用,它存放于.rel.text节中,已初始化数据的重定位条目存放于.rel.data节中。

上图为一个典型的重定位条目格式,offset是节偏移,symbol标识引用应该指向的符号,type标识如何修改引用,addend是有符号常数,用于对引用作偏移调整。引用的重定位有两种修改方式:R_X86_64_PC32和R_X86_64_32,前者代表以相对引用重定位,后者代表以绝对引用方式重定位。

举个详细的例子帮助理解:

上图是一个main函数的汇编代码,里面有定义一个全局变量array,也有引用一个外部函数,sum。

先来看全局符号sum,其重定位条目如下:

假设链接器已经确定运行时.text节的地址为0x4004d0,此时:

节偏移为r.offset=0xf,因此,sum引用的内存地址位于0x4004d0+0xf=0x4004df处,即汇编代码中的第6行,位于e8后面,注意0xe8是指令的操作码。

r.symbol代表sum定义处的运行时地址,假设为0x4004e8。

r.type = R_X86_64_PC32,代表此时为相对引用。

r.addend用作偏移调整,这里是-4,我个人的理解是这代表了sum引用的内存地址到下一条指令地址的偏差,比如这里sum引用的内存地址是0x4004df,PC要执行的下一条指令的地址为0x4004e3(汇编代码中第8行)

综上,可以这么理解:由重定位条目sum我们可以定位到需要修改的引用地址为0x4004df,由r.addend可以定位到下一条指令的地址0x4004df-(-4)=0x4004e3,这个地址与sum定义处的运行时地址相差0x4004e8-0x4004e3=0x5。因此重定位时,汇编代码第6行的指令会被改为:

注意,这里0x5被写成05 00 00 00,这说明这里的表示方法是“小端法”(高字节位放高地址,低字节位放低地址)

实际运行时,执行到这条call指令时,PC值为0x4004e3(即下一条指令的地址),call指令执行时,这里用PC的值和05 00 00 00决定跳转后的地址,步骤如下:

1.将PC压栈

2.PC+0x5 = 0x4004e8->PC

此时会跳转到sum定义处的地址。

再来看全局符号array,其重定位条目如下:

r.type=R_X86_64_32,说明这里用的是绝对引用。

对array的引用位于汇编代码第4行,这是一条mov指令,起始于0x4004d0+0x9=0x4004d9处,它有1字节的操作码0xbf。

r.offset=0xa,告诉链接器要修改位于0x4004d0+0xa=0x4004da处的array引用地址

假设已经确定array定义地址为0x601018,结合第三章知识点,个人理解如下:由于这里是一条mov指令,不像call指令一样,call指令执行时是根据下一条指令地址(存于PC)来决定当前call指令中的跳转值的,是相对跳转,而mov指令是数据传送指令,直接将数据存入寄存器,无需作相对跳转,因此相应的r.addend=0,也因此重定位时直接修改此指令中的数据值即可,以下是重定位后对应的汇编代码:

可执行目标文件

典型的可执行目标文件的结构图:

它与之前的可重定位目标文件类似,具体不再赘述。

上图是可执行目标文件的程序头部表,前两行对应代码段,flags r-x代表有读/执行权限,此段开始于0x400000处,总共占内存大小为0x69c个字节

数据段有读/写权限,开始于0x600df8处,总共占内存大小为0x230个字节,可以推出.data节始于0x600df8-0xdf8=0x600000处,目标文件中大小为0x228字节,为什么会比总共占内存小0x230-0x228=8个字节?因为.bss节的数据只是占位符,位于可重定位目标文件时不占空间,链接成可执行文件后会为其初始化,分配内存空间。

加载可执行目标文件

运行可执行目标文件,是通过调用加载器来实现的,加载器会将程序复制到内存并运行。

上图是程序运行时的内存映像,具体不作解释,看书即可。

CSAPP阅读笔记-链接-第七章-P464-P500的更多相关文章

  1. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第七章:在Direct3D中绘制(二)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第七章:在Direct3D中绘制(二) 代码工程地址: https:/ ...

  2. JavaScript DOM编程艺术-学习笔记(第七章)

    第七章: 1.dom方法创建并且插入标签:(这种方法并没有改变文档的物理内容,而是在改变dom树) ①创建元素节点:createElement(); ②内部前插入:appendChild() ③创建文 ...

  3. Linux内核分析 读书笔记 (第七章)

    第七章 链接 1.链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行. 2.链接可以执行于编译时,也就是在源代码被翻译成机器代码时:也可以执行于 ...

  4. Kafka 权威指南阅读笔记(第三章,第四章)

    Kafka 第三章,第四章阅读笔记 Kafka 发送消息有三种方式:不关心结果的,同步方式,异步方式. Kafka 的异常主要有两类:一种是可重试异常,一种是无需重试异常. 生产者的配置: acks ...

  5. 《深入理解java虚拟机》读书笔记六——第七章

    第七章 虚拟机类加载机制 1.类加载的时机 虚拟机的类加载机制: 虚拟机把描述类的数据从class文件中加载到内存,并对数据进行校验.转换解析和初始化,最终形成了可以被虚拟机直接使用的Java类型,这 ...

  6. CSAPP阅读笔记-gcc常用参数初探-来自第三章3.2的笔记-P113

    gcc是一种C编译器,这次我们根据书上的代码尝试着使用它. 使用之前,先补充前置知识.编译器将源代码转换为可执行代码的流程:首先,预处理器对源代码进行处理,将#define指定的宏进行替换,将#inc ...

  7. CSAPP阅读笔记-struct, union, 数据对齐-来自第三章3.9的笔记-P183-P191

    1.数据对齐 为什么要对齐:通俗点解释就是CPU对数据访问时,每次都是取固定数量的字节数,假如一次取4个字节,若有个int存在0x01-0x04,则一次就能取出,若存在0x03-0x06,则需要分两次 ...

  8. CSAPP阅读笔记-汇编语言初探(控制类指令)-来自第三章3.6的笔记-P135-P163

    1.正溢出与负溢出: 首先,一个正数与一个负数相加,不可能溢出,因为结果的绝对值一定小于两个加数的绝对值,既然两个加数能合理表示出来,结果一定也能合理表示出来. 其次,正溢出是由于两个很大的正数相加, ...

  9. CSAPP阅读笔记-汇编语言初探(数据传送类指令)-来自第三章3.2-3.3的笔记-P115-P128

    1.如何由机器代码生成汇编代码? objdump -d再加上文件名即可直接在终端看到由反汇编器恢复的汇编代码.注意,文件名并不一定得是.o文件,任何可执行文件都可以. 结果如下: 仅列举了反汇编tes ...

随机推荐

  1. getUserMedia API

    getUserMedia API 特别有趣的一个 API,能够调用电脑的摄像头,未来这个 API 将被广泛用来让浏览器和用户之间互动. 如果 <video> 标签和 Canvas 现结合 ...

  2. C++笔记-类层次结构

    多重继承 一个类采用多个直接基类的情况称为多重继承,采用一个基类就是单继承. 两个基类中可能会出现名字一样的成员函数 在派生类中就必须消解这两个基类的成员函数带来的歧义性,最好的方法:在派生类中重新定 ...

  3. TimerTask

    当TimerTask实例通过schedule方法使用之后,不能通过schedule方法调用第二次,如果重复使用就会抛异常TimerTask is scheduled already. 当你重新想利用这 ...

  4. c语言中的const的作用及解析

    有时候我们希望定义这样一种变量,它的值不能被改变,在整个作用域中都保持固定.例如,用一个变量来表示班级的最大人数,或者表示缓冲区的大小.为了满足这一要求,可以使用const关键字对变量加以限定: co ...

  5. jQuery 插件开发——PopupLayer(弹出层)

    导读:上次写了一篇关于GridView的插件开发方法,上几天由于工作需要,花了一天左右的事件封装了popupLayer(弹出层)插件.今天有时间就记录一下自己的开发思想与大家分享下,同时也算是对这段时 ...

  6. C/C++使用心得:enum与int的相互转换

    如何正确理解enum类型? 例如: enum Color { red, white, blue}; Color x; 我们应说x是Color类型的,而不应将x理解成enumeration类型,更不应将 ...

  7. DotNetBar for Windows Forms 12.2.0.7_冰河之刃重打包版

    关于 DotNetBar for Windows Forms 12.2.0.7_冰河之刃重打包版 --------------------11.8.0.8_冰河之刃重打包版-------------- ...

  8. Java与C++比较

    本文仅从片面的角度比较Java与C++的一些特性,如有错误的地方,请指正. 语言特性上的一些差异: 1.Java没有无符号整数,C++/C#都有. 2.Java中不存在指针.Java的引用是功能弱化的 ...

  9. Tarjan+LCA【洛谷P2783】 有机化学之神偶尔会做作弊

    [洛谷P2783] 有机化学之神偶尔会做作弊 题目背景 XS中学化学竞赛组教练是一个酷爱炉石的人. 有一天他一边搓炉石一边监考,而你作为一个信息竞赛的大神也来凑热闹. 然而你的化竞基友却向你求助了. ...

  10. keras调用预训练模型分类

    在网上看到一篇博客,地址https://www.pyimagesearch.com/2017/03/20/imagenet-vggnet-resnet-inception-xception-keras ...