《深入理解计算机系统(CSAPP)》第7章 链接 - 学习笔记

写在前面的话:此系列文章为笔者学习CSAPP时的个人笔记,分享出来与大家学习交流,目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记,在复习回看时发现部分内容存在一些小问题,因时间紧张来不及再次整理总结,希望读者理解。


《深入理解计算机系统(CSAPP)》第3章 程序的机器级表示 - 学习笔记_友人帐_的博客-CSDN博客

《深入理解计算机系统(CSAPP)》第5章 优化程序性能 - 学习笔记_友人帐_的博客-CSDN博客

《深入理解计算机系统(CSAPP)》第6章 存储器层次结构 - 学习笔记_友人帐_的博客-CSDN博客

《深入理解计算机系统(CSAPP)》第7章 链接- 学习笔记_友人帐_的博客-CSDN博客

《深入理解计算机系统(CSAPP)》第8章 异常控制流 - 学习笔记_友人帐_的博客-CSDN博客

《深入理解计算机系统(CSAPP)》第9章虚拟内存 - 学习笔记_友人帐_的博客-CSDN博客


第七章 链接

将各种代码和数据片段连接起来,确定被连接块的运行时位置,并且修改代码和数据块中的各种位置,组合成为一个可执行文件,这个文件可被加载(复制)到内存并执行。

好处:①模块化:程序可以编写为一个较小的源文件的集合,而不是一个整体巨大的一团;可以构建公共函数库;③效率高:分开编译,使得修改某一个源文件后,仅需重新编译这一个文件,然后重新链接,不需要重新编译其他源文件,节省时间;可以将公共函数聚合为单个文件,而可执行文件和运行内存映像只包含他们实际使用的函数的代码,节省空间。

**编译过程的链接部分:**驱动程序运行链接器程序ld将所有的.o文件以及一些必要的系统目标文件组合起来,创建一个可执行目标文件。

可执行目标文件的执行过程:

./prog即可执行prog可执行目标文件,shell调用操作系统中一个叫做加载器(loader)的函数,将可执行文件prog中的代码和数据复制到内存,然后将控制转移到整个程序的开头。

**链接器要完成的任务:**符号解析、重定位

1. 三种目标文件(模块)

  • **可重定位目标文件(.o):**包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。每一个.o文件是由一个源(.c)文件生成的。

  • **可执行目标文件(.out):**包含二进制代码和数据,其形式可以被直接复制到内存并执行。

  • **共享目标文件(.so):**一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接。在Windows中称为动态链接库(Dynamic Link Libraries, DLL)

一个目标模块(object module)就是一个字节序列;

一个目标文件(object file)就是一个以文件形式存放在磁盘中的目标模块。

以上均称为ELF二进制文件

2. ELF可执行可链接格式

目标文件是按照特定的目标文件格式来组织的,各个系统的目标文件格式都不相同。现代x86-64 Linux和Unix系统使用可执行可链接格式(Execut-able and Linkable Format,ELF)。

在这里插入图片描述

2.1 链接视图

不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。夹在ELF头和节头部表之间的都是节。

在这里插入图片描述

  • ELF头(ELF header)

    • 开头是16字节的序列

      • 描述生成该文件的系统的字的大小和字节顺序(大端、小端)。
    • 剩余部分:ELF头的大小、目标文件的类型(.o, .exec, .so)、机器类型(如x86-64)、节头部表(section header table)的位置,节头部表中条目的大小和数量。

节:

  • .text:已编译程序的机器代码

  • .rodata:只读数据。(如printf里的格式串和switch的跳转表等)

  • .data:数据节,可读可写。已初始化的全局和静态变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不出现在.bss节中。

  • .bss:未初始化或初始化为0的全局和静态变量。仅有节头,节本身不占磁盘空间,仅仅是一个占位符。区分已初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。

  • .symtab:符号表,存放在程序中的函数和全局/静态变量的信息、节的名称和位置。符号表是一个结构体的数组,每个条目包括符号的名称、大小和位置,由汇编器生成符号表!(当不同文件中有重名的符号,编译器会自动输出不同的名称,比如将两个x分别表示为x.1和x.2)

  • .rel.text:可重定位代码,存放.text 节的可重定位信息、在可执行文件中需要修改的指令和指令地址。

  • .rel.data:可重定位数据,存放.data 节的可重定位信息、在合并后的可执行文件中需要修改的指针数据的地址。

  • .debug:调试符号表,符号调试的信息(其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件)。

节头表Section header table:每个节的在文件中的偏移量、大小等。

2.2 执行视图

区别:①增加了段头表;②将各节合并为段。

  • 段头表/程序头表:页面大小,虚拟地址内存段(节),段大小

在这里插入图片描述

加载时:

shell调用操作系统中loader函数,将可执行文件中的代码段(.data, .bss)和数据段(.init, .text, .rodata)复制到内存,然后将控制转移到整个程序的开头。

在这里插入图片描述

下图展示了编译、链接时,可重定位目标文件中各节的重组情况,以及OS加载到内存时的情况。

在这里插入图片描述

传统:可执行程序载入内存的固定位置为0x400000(64位程序)或0x8048000(32位程序),易受攻击。

改进:现代链接器都是非固定地址的连接,程序可以加载到内存任意位置。readelf看到的信息是text段的vaddr为0,这样在加载时使用动态地址,可以防止攻击。

在这里插入图片描述

3. 链接器符号

函数、全局变量或静态变量才有符号。

三种不同的链接器符号

  • 全局符号:由模块m定义、能被其他模块引用的全局符号。非静态(non-static)的C函数和非静态的全局变量。
  • 外部符号:由其他模块定义,且被模块m引用的全局符号。
  • 本地/局部符号:只被模块m定义和引用的符号。例如带static属性的C函数和全局变量。这些符号在模块m中任何位置都可见,但是不能被其他模块引用。(注意:本地符号≠局部变量,局部变量存储在栈中)

4. 链接步骤1-符号解析

符号解析:将每个符号引用与输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。

当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用符号的定义,就输出一条错误信息并终止。

4.1 处理局部符号

局部静态C变量:存储在.bss(未初始化或初始化为0的全局和静态变量)或.data(已初始化的全局和静态变量)

局部非静态变量:栈

编译器在.data为每个x的定义分配空间。.bss中的符号运行时在内存中分配这些变量,初始值为0。

为重复的局部符号在符号表中创建唯一名称:如两个x->x.1和x.2

4.2 解析多重定义的全局符号

链接器的输入是一组可重定位目标模块。如果多个模块定义同名的全局符号,会采取相应策略。下面是Linux编译系统采用的方法。

在编译时,编译器向汇编器输出每个全局符号,分为强(strong)、弱(weak)两种类型,而汇编器把这个信息隐含地编码在可重定位目标文件的符号表里。

  • 强符号:函数和已初始化的全局变量。
  • 弱符号:未初始化的全局变量。

处理多重定义的符号名的规则:

  • 规则1:不允许有多个同名的强符号。否则:链接器错误

  • 规则2:如果有一个强符号和多个弱符号同名,那么选择强符号。对弱符号的引用将被解析为强符号。

  • 规则3:如果有多个弱符号同名,那么从这些弱符号中任意选择一个。
    在这里插入图片描述

解决方法:尽量避免使用全局变量;使用static静态变量;定义时初始化;使用extern声明引用的外部全局符号。

5. 链接步骤2-重定位

将多个输入模块中的代码节和数据节合并为单个节。将符号从它们在.o文件中的相对位置重新定位到可执行文件中的最终绝对内存位置(分配运行时地址)。用它们的新位置,更新所有对这些符号的引用。

在这里插入图片描述

5.1 重定位条目

当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。

所以,无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中。已初始化数据的重定位条目放在.rel.data中。

在这里插入图片描述

ELF中两种最基本的重定位类型:

  • R_X86_64_PC32。32位PC相对地址的引用。

  • R_X86_64_32。32位绝对地址的引用。
    在这里插入图片描述

5.2 重定位算法

算法是给机器的,直接理解透彻即可,这里实际上并不难,仅仅是简单的加减法罢了,不要被纸老虎吓到。

第1行和第2行在每个节s以及与每个节相关联的重定位条目r上迭代执行。

为了使描述具体化,假设每个节s是一个字节数组,每个重定位条目r是一个类型为Elf64_Rela的结构,

另外,还假设当算法运行时,链接器已经为每个节(用ADDR(s)表示)和每个符号都选择了运行时地址(用ADDR(r.symbol)表示)。

第3行计算的是需要被重定位的4字节引用的数组s中的地址。如果这个引用使用的是PC相对寻址,就用5-9行来重定位;使用的是绝对寻址,就用11-13行重定位。

在这里插入图片描述

以下图main函数为例:

在这里插入图片描述

其反汇编代码:

在这里插入图片描述

main函数引用了两个全局符号:array和sum。为每个引用,汇编器产生一个重定位条目,显示在引用的后面一行上。这些重定位条目告诉链接器对sum的引用要使用32位PC相对地址进行重定位,而对array的引用要使用32位绝对地址进行重定位。

5.2.1 重定位PC相对引用

将引用对象地址 - rip(PC、下一条指令地址)差的补码,以小尾顺序写入待修改字段

call指令开始于节偏移0xe的地方,包括1字节的操作码0xe8,后面跟着的是对目标sum的32位PC相对引用的占位符。

在这里插入图片描述

重定位条目r由4个字段组成:

r.offset = Oxf (段内偏移量)
r.symbol = sum
r.type   = R_X86_64_PC32
r.addend = -4

已经假设当算法运行时,链接器为每个节(用ADDR(s)表示)和每个符号都选择了运行时地址(用ADDR(r.symbol)表示)。

ADDR(s) = ADDR(.text) = 0x4004d0
ADDR(r.symbol) = ADDR(sum) = 0x4004e8

①首先计算出引用的运行时所处地址(找到占位符的地址)

在这里插入图片描述

(sum所在段的段地址 + sum在段内的偏移量 = sum运行时的地址)

②更新引用,使其在运行时指向sum程序

通过计算其与下一条指令的地址(pc中存放)之间的相对位置即**refptr*,在运行时通过将%rip(pc)来加上*refptr,跳转到sum函数实际所在的位置进行执行。

在这里插入图片描述

在运行时,call指令将存放在地址0x4004de处。当cPU执行call指令时,PC的值为0x4004e3,即紧随在call指令之后的指令的地址。为了执行这条指令,CPU执行以下的步骤:

① 将PC压入栈中

② PC ← PC + 0x5 = Ox4004e3+0x5 = Ox4004e8

因此,要执行的下一条指令就是sum例程的第一条指令。

(以图里数据来理解更容易)

注意:填入的*refptr是小端序的5的补码。
在这里插入图片描述

5.2.2 重定位绝对引用

直接将引用对象的地址按小尾顺序写入待修改字段

在这里插入图片描述

mov指令开始于节偏移量0x9的位置,包括1字节操作码0xbf,后面跟着对array的32位绝对引用的占位符。

重定位条目r由4个字段组成:

r.offset = Oxa
r.symbol = array
r.type   = R_X86_64_32
r.addend = 0

这些字段告诉链接器要修改从偏移量0xa开始的绝对引用,这样在运行时它将会指向array的第一个字节。现在,假设链接器已经确定
ADDR(r.symbol) = ADDR(array) = 0x601018

(直接将array的地址写入占位符即可)

在这里插入图片描述

当重定位结束后,在加载的时候,加载器会把这些节中的字节直接复制到内存,不再进行任何修改地执行这些指令。

6. 函数打包

6.1 静态链接静态库

静态库(.a存档文件):将所有相关的目标模块打包成为一个单独的文件。是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置。可以用做链接器的输入。当链接器构造一个输出的可执行文件时,它只复制静态库里被应用程序引用的目标模块。

// 创建静态库	ar -rs lib库.a .o .o
ar -rs libc.a atoi.o printf.o ... random.o

在这里插入图片描述

静态库缺点:

  • 在存储的可执行文件中存在重复(例如每个程序都需libc)
  • 在运行的可执行文件中存在重复
  • 系统库的小错误修复要求每个应用程序显式地重新链接

6.2 动态链接共享库

共享库,也称动态链接库(DLL),后缀(Linux-.so, windows-.dll),在运行或加载时被动态地加载并链接到程序中。共享库载入内存后,可以由多个进程共享。

链接方式:

  • 编译时链接:GCC编译时链接

  • 加载时链接:当可执行文件首次加载和运行时进行动态链接,通常由动态链接器(ld-linux.so)自动处理。

  • 运行时链接:在程序开始运行后(通过程序语句)进行动态链接。在linux中,通过调用dlopen()接口完成。

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/26050.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java单元测试( Hamcrest 断言)

java单元测试( Hamcrest 断言) 单元测试特征: 1 范围狭窄 2 限于单一类或方法 3 体积小 为什么要编写单元测试? 为了防止错误(很明显!) 而且还可以提高开发人员的生产力,因为单元测试: (1) 帮助实施——在…

力扣sql中等篇练习(二十八)

力扣sql中等篇练习(二十八) 1 每个城市最高气温的第一天 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # Write your MySQL query statement below SELECT w.city_id,MIN(w.day) day,w.degree FROM Weather w INNER JOIN (SELECT city_id,MAX(degr…

【算法学习系列】07 - 无序数组中的局部最小值问题

文章目录 说明约束条件简单说下思路解决方案随机无序数组样本生成器算法实现验证代码进行大样本随机测试验证算法正确性 说明 在算法中,局部最小值是指一个函数在一个局部范围内的最小值。 具体而言,如果一个函数在一个小区间内的取值都比该区间内的其他…

C++程序员的待遇怎么样?我来谈谈学好C++的五个关键点

有个学弟跟我谈到这样一个问题:现在我看到网上很多人都在讲,说这个做C程序员,尤其是本科毕业计算机专业,然后步入社会之后就能拿到月入过万。但是为什么自己找的这个工作啊,普遍在月薪六七千块钱左右,也就是…

利用OpenCV处理图像

OpenCV是非常流行的图像处理库,下面介绍一下其对图像的基本操作。 1. 安装与环境 安装还有点儿复杂的,但百度几篇博客基本能解决,这里就不多说了。 安装好后,要在工程中使用OpenCV的头文件和库,需要在CMakeLists.tx…

码住!IC设计常用工具合集!

芯片设计过程中,选择和使用适合的工具是非常重要的。芯片设计工具通常分为三类:EDA工具、模拟仿真工具和布局工具。 一、EDA工具 EDA工具是芯片设计的核心,它包括原理图绘制、逻辑综合、门级仿真工具和物理版图编辑等,可以帮助设计…

基于springboot+Redis的前后端分离项目(一)-【黑马点评】

🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码:eh11 基于session和redis实现登录 (一)前言(二)导入资源(三)短信…

每日学术速递5.26

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Text2NeRF: Text-Driven 3D Scene Generation with Neural Radiance Fields 标题:Text2NeRF:具有神经辐射场的文本驱动 3D 场景生成 作者:Jingb…

Python程序设计基础:标识符、变量与赋值、输入输出

文章目录 一、标识符二、变量与赋值三、输入输出 一、标识符 Python对每个标识符的命名存在要求: 1、每个标识符必须以字母或下划线“_”开头,后跟字母、数字或下划线的任意序列。根据这个规则,以下都是Python中的合法名称:a&…

史上最全测试开发工具推荐(含自动化、性能、稳定性、抓包)

目录 一、UI自动化测试工具 1. uiautomator2 2. Appium 3. ATX-Test 4. Airtest 5. ATXServer2 6. STF 7. Appetizer 二、APP稳定性测试工具 8. UICrawler 9. Maxim 10. AppCrawler 三、APP性能测试工具 11. SoloPi 12. GT 四、抓包工具 13. AnyProxy 14. mi…

【滤波】设计卡尔曼滤波器

本文主要翻译自rlabbe/Kalman-and-Bayesian-Filters-in-Python的第8章节08-Designing-Kalman-Filters(设计卡尔曼滤波器)。 %matplotlib inline#format the book import book_format book_format.set_style()简介 在上一章节中,我们讨论了教…

【自然语言处理】【大模型】ChatGLM-6B模型结构代码解析(单机版)

ChatGLM-6B模型结构代码解析(单机版) ​ 本文介绍ChatGLM-6B的模型结构,代码来自https://huggingface.co/THUDM/chatglm-6b/blob/main/modeling_chatglm.py。 相关博客 【自然语言处理】【大模型】ChatGLM-6B模型结构代码解析(单机版) 【自然语言处理】【大模型】BL…

枚举_源码_分析

枚举源码分析 前言 这是所有Java语言枚举类型的公共基类。关于枚举的更多信息,包括编译器合成的隐式声明方法的描述,可以在Java的第8.9节中找到™ 语言规范。 请注意,当使用枚举类型作为集合的类型或映射中键的类型时,可以使用专…

斩获阿里offer,这份258页面试宝典也太顶了....

测试三年有余,很多新学到的技术不能再项目中得到实践,同时薪资的涨幅很低,于是萌生了跳槽大厂的想法 但大厂不是那么容易进的,前面惨败字节,为此我辛苦准备了两个月,又从小公司开始面试了半个月有余&#…

最系统的网络安全自学笔记+学习路线(超详细)

01 什么是网络安全 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域,都有攻与防两面…

虚拟机类加载机制

目录 1、概述 2、类加载的过程 1、过程总览 2、加载 3、链接-验证 4、链接-准备 5、链接-解析 6、初始化 7、总结 3、类加载的时机 4、类加载器 1、概述 2、类与类加载器 3、三层类加载器 4、双亲委派模型 5、其他加载策略 1、概述 一个Java类会被编译成一个Cl…

游戏封包加密方案解析

当下游戏市场已全面回暖,暑期档临近更将迎来大量新游上线,如此关键节点,游戏厂商应当更加注重游戏安全。 FairGuard发现近期游戏黑灰产攻击角度愈发刁钻,除了常见的内存修改外挂、注入挂,针对游戏封包破解的「脱机挂」…

chatgpt赋能python:Python图片处理教程

Python 图片处理教程 Python 是一种功能强大的编程语言,广泛应用于大量不同的行业和领域。其中之一是图像处理和分析。Python 提供了一个庞大的图像库,其拥有大量的工具和函数。Python 图像库具有高度的可扩展性,可以很容易地将其与其他库集…

Async 使用详解

Spring Boot异步调用Async 在实际开发中,有时候为了及时处理请求和进行响应,我们可能会多任务同时执行,或者先处理主任务,也就是异步调用,异步调用的实现有很多,例如多线程、定时任务、消息队列等&#xf…

【大数据分析】Hbase的基本原理

目录 Hbase 架构ClientZooKeeperMasterRegionServerHRegionStoreMemStoreStoreFileHFileHLog Hbase数据模型关于数据模型的其他概念Name SpaceTableRowColumnTime StampCell Hbase 架构 Client (1).META.表,记录了用户所有表拆分出来的 Regi…