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

news2024/9/27 22:08:44

写在前面的话:此系列文章为笔者学习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()接口完成。

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python中安装nio和ngl

Python中安装nio和ngl 介绍 nio和ngl是Python中用于网络编程和HTTP协议的模块。它们可以帮助开发人员快速地创建网络应用程序和RESTful API。 nio扩展了Python的Socket模块,并提供了一组高级的网络编程接口,比如异步IO和事件驱动编程。ngl则提供了一组…

就业内推 | 国企、运营商有岗,CCNP以上

01 中电鸿信信息科技有限公司 🔷招聘岗位:网络工程师 🔷职责描述: 1.主要负责云网类项目的网络方案规划、评审、集成交付及参照相关集成标准对硬件集成商交付的资源池基础网络进行验收; 2.完成公司交办的其他工作&am…

chatgpt赋能python:Python中如何根据值提取键

Python中如何根据值提取键 在编程中,我们经常需要在Python中查找一个字典中的值,然后返回它的键。这种操作在各种应用程序中经常被使用,例如数据库,社交媒体应用程序或者网上商店。 在Python中,这个过程非常简单&…

chatgpt赋能python:Python中如何优雅地分行

Python中如何优雅地分行 Python是一门优雅而又简洁的编程语言,它没有繁琐的语法规则和冗余的表达式。然而,随着程序规模的增大,代码行数也随之增多。为了使代码更加易读和易维护,良好的代码分行是必不可少的。 在Python中&#…

Python安全和防护:如何保护Python应用程序和用户数据的安全

章节一:引言 在当今数字化时代,数据安全是一个极其重要的话题。随着Python的广泛应用和越来越多的人使用Python构建应用程序,保护Python应用程序和用户数据的安全变得尤为重要。本文将介绍一些关键的Python安全问题,并提供一些保…

如何提高AI绘画的出图质量?

先上几张本人随便跑的图,虽说算不上多惊艳,但也是什么都不设置达不到的效果。文末附上本人常用的一些优质大模型。 本人不喜欢细枝末节讲一堆,以下只说重点,如果有一些名词及操作不明白,可以去查对应资料(应…

移动端布局之流式布局1(百分比布局)

移动端布局之流式布局1 流式布局(百分比布局)基础案例:京东移动端首页搭建相关文件夹结构设置视口标签以及引入初始化样式normalize.css引入我们的css初始化文件与首页css body设置index.css app布局和app内容填充index.htmlindex.css 搜索模…

python+django+vue协同过滤算法的电影推荐评分系统nzf73

用户:登录,注册,修改密码,修改个人信息,电影搜索,电影评分,电影评论, 推荐:个性化推荐电影(协同过滤),热门推荐 管理员:用户管理,电影管理,评论管理 电影是本…

chatgpt赋能python:Python中同一个类中方法互相调用的意义

Python中同一个类中方法互相调用的意义 在Python中,类是一个非常重要的编程概念。类是由属性和方法组成的,其中方法是类中非常重要的部分。在类中的方法中,有时候我们需要调用其他方法。在本文中,我们将讨论Python中同一个类中方…

Leetcode11 盛最多水的容器

Leetcode11 盛最多水的容器 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/container-with-most-water/description 博主Github:https://github.com/GDUT-Rp/LeetCode 题目: 给定一个长度为 n…

科技创新盛典:全国科技者工作日激荡创新思维

⭐ 全国科技工作者日的由来⭐ 全国科技工作者日LOGO⭐ 科技工作者界定⭐ 历年主题⭐ 2023年全国科技工作者日 今天我要和大家分享一个令人激动和振奋的消息——全国科技者工作日!这是一个特殊的日子,为我们所有投身于科技创新的人们而设立,让…

Linux更改SSH端口,并解决SSHD服务重启失败的问题

环境:Linux Centos 7 1.进入sshd配置文件:vi /etc/ssh/sshd_config 2、找到“#Port 22”这行,删掉注释符#,将端口改为(想要变成的端口号 如:2022): 3.重启sshd服务: sy…

【C++】程序的内存模型 - 内存四区代码区,全局区,栈区,堆区,new运算符

文章目录 1. 程序运行前1.1 代码区1.2 全局区 2. 程序运行后2.1 栈区2.2 堆区 3. new运算符 本阶段开始主要针对C面向对象编程技术,探讨C中的核心和精髓。 C程序在执行时,将内存大方向划分为4个区域: 代码区:存放函数体的二进制…

chatgpt赋能python:Python中拼接字符串的最佳方法

Python中拼接字符串的最佳方法 在Python编程中,拼接字符串是一个非常常见的任务。无论是将多个字符串连接成一个字符串,还是将变量值插入到字符串中,我们都需要拼接字符串。本文将介绍Python中拼接字符串的几种方法,并为你推荐最…

Vue登录界面精美模板分享

文章目录 🐒个人主页🏅Vue项目常用组件模板仓库📖前言:🎀源码如下: 🐒个人主页 🏅Vue项目常用组件模板仓库 📖前言: 本篇博客主要提供vue组件之登陆组件源码…

车辆CAN信号,依据DBC文件解析流程

CAN信号解析流程 1.车辆CAN对应dbc文件 DBC文件是一种用于描述CAN(Controller Area Network)数据通信协议的文件格式,DBC文件中包含了CAN数据的信号定义、编码方式、单位、范围等信息,可以用于解析和生成CAN数据帧。 一个DBC文件…

数据结构与算法06:递归和简单的排序

目录 【递归】 【排序】 冒泡排序 插入排序 选择排序 【每日一练:K 个一组翻转链表】 【递归】 递归是将一些有规律的重复问题分解为同类的子问题的方法,也就是在函数中自己调用自己。比较经典的递归代码就是 斐波那契数列,实现方式如…

特征选择及特征提取

特征 什么是特征: 举个例子:一个妹子很好看,好看的在哪里?腿长(特征1),白(特征2),性格开朗(特征3) 那么可以概括为好看妹子的特征是…

修改element Plus的主题样式

安装element plus 安装icon pnpm install element-plus pnpm install element-plus/icons-vue main.ts配置 icon的使用https://element-plus.gitee.io/zh-CN/component/icon.html#%E7%BB%93%E5%90%88-el-icon-%E4%BD%BF%E7%94%A8 import { createApp } from vue import ./sty…

用chatGPT来NEW个对象让“码农”的节日不再仅仅只有1024(赶鸭子上架式的成长、无效不得不立的flag)

用chatGPT来NEW个对象让“码农”的节日不再仅仅只有1024 前言一、大部分的成长都是赶鸭子上架二、节日是为了告诉自己不孤单三、做不到也要立下的flag四、New个对象吧1.php定义一个科技工作者形象2.python定义一个科技工作者形象3.javascript定义一个科技工作者形象 总结 前言 …