程序的链接

2023-11-19

程序的链接是一个非常实际的问题,他建立在很实际的问题之上,不从程序员的角度去思考问题,则是从软件的角度去思考如何复用错综复杂的代码。因为,这个问题的本质是我们没有给底层的硬件一个完整的可按顺序执行的程序,我们在前几章虽然讨论了指令流的问题,但是都是基于一个给定的按顺序执行的指令流,我们没有考虑这个按序到来的指令流是从何而来的。事实上,我们基本不会按照一个顺序的方法去构建我们的程序,也就是我们更少的去使用面向过程的方法去编写我们的代码,转而去使用面向对象的思想,更多的考虑代码复用以及内存如何寻址的问题。

简而言之,如果我们只是面向过程的进行编程,只使用物理内存去进行程序的运行,那样的话我们可能根本就不需要链接这个东西,但是我们并不直接使用硬件,因为这样会更多的考虑一些细节的实现,我们也不进行过程化的编程,因为即使使用过程化编程的思想,我们在构建项目的时候也会对其进行模块化的分割,只要我们进行了分割或是代码的不断复用,那么我们就会得不到一个按照顺序到来的指令流,因此链接对于我们现代的计算机和操作系统是非常必要的。

而对于程序员来说,我们不必了解链接的底层细节,但是对于我们来说了解链接是怎么回事,以及我们参数以及函数的作用域是非常比较的,他不但能帮助我们避免一些隐式的错误,还会让我们更容易的进行编程。

程序的转换处理过程

我们一个程序要经历预处理、编译、汇编、链接再到最后的运行几个阶段。

链接

与书上不同,我们先去探究链接究竟是什么,做了什么,然后再去讨论,这个过程中所需以及生成的东西。(以下这段都是个人见解,没有任何的理论依据)

链接是为了把一堆按照我们规定的顺序写出的代码组织起来的东西,所以他的工作就更像是为分离在项目里面的各个文件进行穿针引线,而他穿针引线的依据就是我们在文件里面写出来的组织方法,所以他要找到各个文件之间的关系,最重要的就是找到互相引用的地方,这也是链接最重要的工作,然后要把他们组织再一次,这是另一项工作,把整个项目按照我们最初讨论的那种样子,组织成为一个可以按照顺序执行的指令流再加载到内存。

因此,我们从上述的描述就可以得出,链接程序主要做了两件事,一件事是找到互相引用的地方,我们称之为符号解析,然后产生一张符号表,用来记录需要链接或者说可能会需要被链接的符号。第二项工作是组织这个项目,我们称之为重定位,我们靠重定位去从另一个文件中引用另一个文件的变量或函数,这就是链接的两个主要工作。

符号解析:

  • 程序定义和引用符号(函数、全局变量、静态变量)
  • 符号的定义由编译器保存在.o 文件关联的符号表中
  • 连接器把每个符号引用和定义关联起来

重定位:

  • 将独立的代码和数据节合并到单个节中(可以分配绝对地址)
  • 将符号从.o文件中的相对位置重新定位到可执行文件中的最终绝对地址:符号定义有了绝对地址(逻辑地址或虚拟地址)
  • 将这些符号的所有符号更新到其新位置(符号引用有了绝对地址)

目标文件

我们知道了链接究竟是怎么回事,再来回头看他生成的这些东西就有迹可循了。

目标文件有三种形式:

  • 可重定位目标文件。用来在编译时与其他可重定位目标文件合并起来,去创建一个可执行目标文件;(.o)
  • 可执行目标文件。其形式可以直接复制到内存并执行;(a.out, .bin, 无后缀)
  • 共享目标文件。可以在加载或运行时被动态地加载进内存并链接;(.a, .so)

可重定位目标文件

然后再来了解三种不同的目标文件。

一个典型的ELF文件结构如图所示:

ELF头:

  • 以一个16字节的序列开始,这个序列描述了生成该文件的系统的字宽和字节顺序(大小端存储模式);
  • ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。包括ELF头的大小、目标文件的类型、及其类型、节头部表的文件偏移地址,以及节头部表中条目的大小和数量;

节头部表:

  • 描述各节的位置和大小
  • 其中目标文件中每个节都有一个固定大小的条目

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

.rodata:只读数据(printf中的语句串或跳转表)

.data:已初始化的全局和静态C变量;局部C变量保存在栈中(因为不需要链接,所以不出现在这里)

.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量,目标文件中这个节不占实际的空间。

.symtab:一个符号表,存放在程序中定义和引用的函数和全局变量的信息。(不包括局部变量)

.rel.text:.text节中需要重定位的信息,在合并生成可执行文件时需要修改的指令的指针;

.rel.data:.data节的重定位信息,在合并生成可执行文件时需要修改的数据的指针;

.debug:调试符号表

.line:原始C源程序中的行号和.text节中机器指令之间的映射

.strtab:字符串表

ELF可执行目标文件

符号和符号表

按照书上的顺序结构,在这里讨论符号和符号表。

每个可重定位目标模块都有一个符号表,包含三种不同的符号:

  • 由模块定义并能被其他模块引用的全局符号,全局符号对应于非静态的C函数和全局变量;
  • 由其他模块定义并被该模块引用的全局符号,这些符号称为外部符号,对应于其他模块中定义的非静态C函数和全局变量;
  • 只被模块定义和引用的局部符号,他们对应于带static属性的C函数和全局变量,这些符号在模块内部可见,但是不能被其他模块引用;

认识到本地链接器符号和本地程序变量不同是很重要的,,symtab中的符号表不包含对应于本地非静态程序变量的任何符号,这些符号在运行时在栈中被管理。

而带static属性的本地过程变量不在栈中管理,编译器在.data或.bss中分配空间,并在符号表中创建一个有唯一名字的本地链接器符号。(这节读多了会觉得很乱,因为他翻译的时候用词不是很准确,会导致理解起来很费劲)

符号解析

链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。(想知道更详细的建议自己看,因为没什么好总结的)

对于多重定义的全局符号,我们使用强弱符号将其区别:函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。

按照如下规则处理多重定义的符号:

  • 不允许有多个同名的强符号
  • 如果有一个强符号和多个若符号同名,那么选择强符号
  • 如果有多个弱符号同名,那么从这么些弱符号中任意选择一个

重定位

在符号解析之后,合并输入模块,并为每个符号分配运行时的地址,(关联代码中的符号引用和符号定义)

  • 重定位节及其节内定义的符号
  • 重定位代码节和数据节中的符号引用

汇编器遇到最终位置未知的目标引用,就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。

重定位条目格式:

两种重定位类型:

  • R_X86_64_PC32:重定位一个使用32位PC相对地址的使用;
  • R_X86_64_32:重定位一个使用32位绝对地址的引用;

重定位算法:

 

可执行文件的存储器映像

加载可执行目标文件

静态链接

首先讲讲什么是静态库,所有的编译系统都提供一种机制,将所有相关的目标打包成为一个单独的文件,称为静态库,我们可以在程序中引用一个静态库中的内容而非自己实现。

静态库相关的函数可以被编译为独立的目标模块,然后封装成一个单独的静态库文件。

链接库在解析引用的时候从左到右按照它们在编译器驱动程序命令行上出现的顺序来扫描可重定位目标文件和存档文件。在扫描中,链接器维护一个可重定位目标文件的集合E,一个未解析的符号集合U,以及一个在前面输入文件中已经定义的符号集合D,如果f是一个目标文件就把它添加到E修改U和D来反映f中的符号定义和引用。如果f是一个存档文件,那么链接器就尝试匹配U中未解析的符号和有存档文件定义的符号。如果某个存档文件成员m,定义了一个符号来解析U中的引用,那么就将m添加到E中,并且修改U和D来反映m中的符号定义和引用。对存档文件中所有的成员目标文件都依次进行这个过程,直到U和D不再发生变化。此时任何不包含在E中的成员目标文件都简单地被丢弃。

最后如果U非空,就会输出一个错误并终止,否则回合并和重定位目标文件,构建输出的可执行文件。

在命令行中,如果定义一个符号的库出现在引用这个符号的目标文件之前,那么引用就不能被解析,链接会失败。因此如果库不是相互独立的,就必须对他们排序。

动态链接

静态库仍然有缺点:

  • 如果更新版本,必须重新链接;
  • 很多标准函数的复用造成资源浪费;

共享库是一个目标模块,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。

两种使用模式:

  • 程序加载过程中加载和链接共享库(加载后、执行前)
  • 程序执行过程中加载和链接共享库(无需再编译时将库链接到应用中)

位置无关代码

共享库也有缺点,但是相较于静态库缺点没有那么明显,我们的烦恼来源于更高层面的困扰,我们对于每个进程来说给予了他独自占有虚拟内存空间的假象,但是我们作为管理者,我们不能自己也相信这种假象,否则就真变成了全是假象。我们要思考更为深层次的问题,对于多个进程使用一个相同的共享库,我们在这个时候显然是要让其共享一个副本,但是他会使地址空间的使用效率严重降低,因为即使一个进程不使用这个库,空间依然会为他分配,同时更难的是对其管理,我们很大程度上要为其加入很多的标志位才能保证其正常使用。而当修改或者创建一个新库的时候,我们会更加头痛,要在内存中为其重新寻找位置,进而无数个共享库堆积在内存中,产生内碎片也严重影响了地址空间的使用,这为我们操作系统的管理带来了很大的负担。

所以我们使用位置无关代码来解决这个问题,他可以加载到内存的任意位置而无需链接器修改。

我们对于位置无关代码的讨论分成两个类型:数据引用和函数调用;

PIC数据引用

对于数据节,我们利用数据段和代码段的距离固定这一事实为数据引用构建了全局偏移表,然后在表中再去寻找相应的地址。

PIC函数调用

我们在这里提出两个数据结构的概念去实现这个问题,一个是过程链接表(PLT)、另一个是全局偏移表(GOT),我们对其调用时第一次会去PLT中寻找,查找其PLT[2]项,如果没有链接过就会去GOT表中跳转查找,然后返回压栈跳转到PLT[0],最后实现其动态链接。而之后的链接就会直接通过PLT[2]的条目跳转到相应位置。

库打桩

胖胖的人过年之前告诉我这里是重点,我看了半天也不觉得这里会是重点,库打桩允许我们截获对共享库函数的调用,取而代之执行自己的代码,总而言之是一种功能很强大的东西。

它能够在编译、链接、运行时进行打桩。(不想看里面的睿智代码)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

程序的链接 的相关文章

  • InstaPy:“错误,无法确定 64 位 Linux 的正确文件名”

    有人知道如何解决或解决这个问题吗 来自控制台的堆栈跟踪 执行后报告错误 InstaPy Version 0 6 9 Workspace in use home zanettra InstaPy Error unable to determi
  • 构建 makefile 依赖/继承树

    如果我解释得不好或者问了一些明显的问题 我很抱歉 但我是 Linux 内核的新手 而且有点深入 我们有一个嵌入式 Linux 系统 它附带一个 文档非常糟糕的 SDK 其中包含数百个文件夹stuff 大多数文件夹包含rules make m
  • git在Windows和Linux之间切换后强制刷新索引

    我有一个Windows和Linux共享的磁盘分区 格式 NTFS 它包含一个 git 存储库 约 6 7 GB 如果我只使用Windows or 只使用Linux操作 git 存储库一切正常 但是每次切换系统的时候git status命令将
  • 在进行堆转储后,如何在发生 OutOfMemoryError 时重新启动 JVM?

    我知道关于 XX HeapDumpOnOutOfMemoryError https stackoverflow com q 542979 260805JVM 参数 我也知道 XX OnOutOfMemoryError cmd args cm
  • Linux 中热插拔设备时检测设备是否存在

    我正在运行 SPIcode http lxr free electrons com source drivers spi spi omap2 mcspi c在熊猫板上 我想知道其中的哪个功能code http lxr free electr
  • 在Linux中断上下文中运行用户线程

    我正在编写一些定制的应用程序 并允许更改 Linux 内核中的中断处理程序代码 我有一个用户线程正在等待中断发生 如果发生中断 那么我要做的第一件事就是执行该用户线程 有什么办法让它发挥作用吗 Thanks 创建一个字符设备 这就是内核所做
  • 如何以编程方式从Linux中的进程名称获取进程ID

    在我的项目中 我们使用 ACE 自适应通信环境 中间件来编写可在 Windows 和 Linux 上运行的独立于操作系统的代码 要求是从进程名称中获取进程 ID 由于 ACE 不支持这一点 因此我们必须使用特定于平台的宏来分离 Window
  • Linux shell 脚本:十六进制数字到二进制字符串

    我正在 shell 脚本中寻找一些简单的方法来将十六进制数字转换为 0 和 1 字符的序列 Example 5F gt 01011111 是否有任何命令或简单的方法来完成它 或者我应该为其编写一些开关 echo ibase 16 obase
  • 从多线程程序中调用 system()

    我们正在开发一个用 C 编写的多线程内存消耗应用程序 我们必须执行大量的 shellscript linux 命令 并获取返回码 读完之后article http www linuxprogrammingblog com threads a
  • 如何在特定 systemd 服务重新启动时触发自定义脚本运行

    我想知道如何安排自定义脚本在重新启动服务时运行 我的用例是 每当重新启动 Tomcat 服务时 我都必须运行多个命令 我想知道是否有一种方法可以编写脚本并安排它在重新启动 Tomcat 服务时运行 我已将 tomcat 脚本设置为 syst
  • 设置 Apache POI 的路径

    我想创建 Excel 文件并使用 java 程序在该文件中写入数据 That is here http www techbrainwave com p 554我在 java 文件所在的位置提取了 Apache POI 并将该路径包含在路径变
  • 在主目录中安装库

    在 Linux Ubuntu 中 我尝试运行一个工具 但它显示错误 库丢失 我无权在系统中安装任何内容 或者根本无法从我的用户帐户执行 sudo 是否可以在我的主目录 没有 sudo 中安装缺少的库 在我的例子中为 libstdc so 6
  • SONAR - 使用 Cobertura 测量代码覆盖率

    我正在使用声纳来测量代码质量 我不知道的一件事是使用 Cobertura 测量代码覆盖率的步骤 我按照以下步骤操作http cobertura sourceforge net anttaskreference html http cober
  • QFileDialog::getSaveFileName 和默认的 selectedFilter

    我有 getSaveFileName 和一些过滤器 我希望当用户打开 保存 对话框时选择其中之一 Qt 文档说明如下 可以通过将 selectedFilter 设置为所需的值来选择默认过滤器 我尝试以下变体 QString selFilte
  • ansible 重新启动 2.1.1.0 失败

    我一直在尝试创建一个非常简单的 Ansible 剧本 它将重新启动服务器并等待它回来 我过去在 Ansible 1 9 上有一个可以运行的 但我最近升级到 2 1 1 0 并且失败了 我正在重新启动的主机名为 idm IP 为 192 16
  • Unix 命令列出包含字符串但*不*包含另一个字符串的文件

    如何递归查看包含一个字符串且不包含另一个字符串的文件列表 另外 我的意思是评估文件的文本 而不是文件名 结论 根据评论 我最终使用了 find name html exec grep lR base maps xargs grep L ba
  • 为什么我收到“无法进行二进制日志记录”的信息。在我的 MySQL 服务器上?

    当我今天启动 MySQL 服务器并尝试使用以下命令进行一些更改时用于 MySQL 的 Toad http www quest com toad for mysql 我收到此消息 MySQL 数据库错误 无法进行二进制日志记录 消息 交易级别
  • 从 PL/SQL 调用 shell 脚本,但 shell 以 grid 用户而非 oracle 身份执行

    我正在尝试使用 Runtime getRuntime exec 从 Oracle 数据库内部执行 shell 脚本 在 Red Hat 5 5 上运行的 Oracle 11 2 0 4 EE CREATE OR REPLACE proced
  • awk 子串单个字符

    这是columns txt aaa bbb 3 ccc ddd 2 eee fff 1 3 3 g 3 hhh i jjj 3 kkk ll 3 mm nn oo 3 我可以找到第二列以 b 开头的行 awk if substr 2 1 1
  • 是否可以在Linux上将C转换为asm而不链接libc?

    测试平台为Linux 32位 但也欢迎 Windows 32 位上的某些解决方案 这是一个c代码片段 int a 0 printf d n a 如果我使用 gcc 生成汇编代码 gcc S test c 然后我会得到 movl 0 28 e

随机推荐

  • Java Web快速开发(1)Java 项目构建

    文章目录 Java 项目构建 1 Java 版本发展 2 集成开发环境 3 Java发布形式 1 扩展参数说明 2 JVM 内存回收机制 3 Java 程序分析调测工具 4 Maven Java 项目构建 1 Java 版本发展 EJB E
  • 流媒体之推流和拉流

    推流 将直播内容推送至服务器的过程 拉流 为服务器已有直播内容 用指定地址进行拉取的过程 什么是推流 推流 指的是把采集阶段封包好的内容传输到服务器的过程 其实就是将现场的视频信号传到网络的过程 推流 对网络要求比较高 如果网络不稳定 直播
  • QPixmap的深拷贝和浅拷贝

    在最近的项目中用到了QPixmap 涉及多线程操作QPixmap对象 为了简单 比如在线程1中构造了一个QPixmap对象 传入线程2 这时调用的函数为operator const QPixmap pixmap 对于不了解QPixmap类的
  • 静态分析基础技术

    静态分析基础技术 原文链接 https hvnt3r top 2019 01 静态分析基础技术 知识点 我觉得安全圈的思路都是差不多的 跟渗透测试一样 对一个恶意软件的分析也需要前期的信息收集阶段来帮助我们对目标有一个大致的了解和认识 方便
  • 使用mui button提交ajax请求后台失败的原因

    在input内输入单号 人工点击按钮触发事件ajax请求后台 一切正常 但是 如果在input内输入完单号 用安卓或苹果手机的软键盘回车键事件调用ajax请求后台 就会有问题 原因如下 mui提供的按钮默认是放在
  • 华为机试题97-记负均正

    描述 首先输入要输入的整数个数n 然后输入n个整数 输出为n个整数中负数的个数 和所有正整数的平均值 结果保留一位小数 0即不是正整数 也不是负数 不计入计算 如果没有正数 则平均值为0 数据范围 1 n 2000 输入的整数都满足 val
  • 配置本地docker与镜像并上传到服务器使用

    1 在本地创建基础容器 官网 1 卸载旧版本 如果有 sudo apt get remove docker docker engine docker io containerd runc 2 设置Docker的存储库并从中安装 更新包索引并
  • SQL优化技巧

    本文以Mysql数据库为例 总结数据库优化方法 一 数据库优化四个层面 Mysql数据库优化 可以从以下四个层面优化 硬件 系统配置 数据库表结构 sql语句及索引 优化效果 SQL语句及索引 gt 数据库表结构 gt 系统配置 gt 硬件
  • CentOS8 同步时间chrony ntpdate已无法使用

    CentOS8 同步时间chrony ntpdate已无法使用 在CentOS8中 已使用chrony替代ntp 首先安装chrony 使用dnf安装 dnf install y chrony 也可以用yum安装 yum install y
  • Linux权限、用户组、用户

    前提 这里以Centos7为例 首先进入 目录 并输入 ls l cd ls l 分析 根据上图我们拿其中一条来说 drwxr xr x 14 root root 3440 Aug 14 07 31 dev 第一个字母 d 代表这个文件的含
  • 俄罗斯担忧开发者无法访问开源代码,为什么开源如此重要?

    俄乌冲突背景下 俄国媒体发文称 世界第一开源软件平台GitHub正在考虑限制俄罗斯软件开发者访问开源软件源代码储存库 此外 数据库巨头 Oracle 也宣布已经暂停了在俄罗斯的所有业务 这些消息迅速刷屏 原因之一便是现在的 IT 世界已离不
  • Win10环境下配置VScode远程开发ssh-remote(免密登录)

    问题背景 在开发或者做实验的过程中 我们经常会用到远程服务器或者Github的项目 而我服务器上的项目只能在Jupyter Notebook上编写 或许可以在其他IDE上写 但我不知道 而我们喜欢用的IDE比如Pycharm 轻量级的VSc
  • C语言之指针知识大总结

    文章目录 一 地址 二 指针与指针变量 三 指针的作用 四 初学指针时常见的错误 五 通过调用函数修改主调函数中的值 六 指针与一维数组 七 使用函数操作一维数组 八 指针变量所占字节数 九 静态数组的缺陷 十 malloc函数 十一 动态
  • ElasticSearch 6.3版本(ES)查询人名关键字不拆词查询

    ElasticSearch 6 3版本 ES 查询关键字不拆词查询 类似mysql 的 like 语句 mysql的sql语法类似如下 采用大量like和locate语法 进行模糊查询 导致查询一个需要8秒多 通过ES优化后 总的查询在1秒
  • postman中进行SHA1或MD5签名

    大部分接口为了防御重放攻击 往往使用SHA1或者MD5对请求进行签名 例如 我们有如下请求 Request URL http xx xx xx xx nonce 123 timestamp 123 Body xxx xxx signatur
  • ESP8266(果云科技的开发板源码)

    ESP8266 果云科技的开发板源码 TOC 最近调试果云科技的老板子ESP8266开发板 比较老了 调试串口输入很麻烦 因为API函数很麻烦 源码已上传 请下载 下面是代码 File uart c Copyright 2013 2016
  • jxls Excel表格导出( 模板导出多个sheet)

    使用模板来导出excel表格 1 使用jxls core jar包来实现 jxls core不支持POI4以上的版本 1 1 maven
  • 在微服务项目中,Spring Security 比 Shiro 强在哪?

    Spring Security 和Shiro的区别
  • LDAP协议

    1 LDAP是Lightweight Directory Access Protocol的缩写 顾名思义 它是指轻量级目录访问协议 这个主要是相对另一目录访问协议X 500而言的 LDAP略去了x 500中许多不太常用的功能 且以TCP I
  • 程序的链接

    程序的链接是一个非常实际的问题 他建立在很实际的问题之上 不从程序员的角度去思考问题 则是从软件的角度去思考如何复用错综复杂的代码 因为 这个问题的本质是我们没有给底层的硬件一个完整的可按顺序执行的程序 我们在前几章虽然讨论了指令流的问题