文章目录

  • 1.起源
    • 1.1 高级语言 vs 汇编语言 vs 机器语言
    • 1.2 机器语言和 ELF 文件
  • 2. 编译(从 section 到 segment)
    • 2.1 为什么定义两种不同视角的概念
    • 2.2 section 和 segment 是什么
      • 2.2.1 section
      • 2.2.2 segment
  • 3. 链接以后我便成为了你
    • 3.1 链接发展历史
      • 3.1.1 静态链接 + 静态装入
      • 3.1.2 静态链接 + 动态装入
      • 3.1.3 动态链接 + 动态装入
    • 3.2 动态链接本质
  • 碎碎念
  • 参考资料

1.起源

1.1 高级语言 vs 汇编语言 vs 机器语言

平时写代码的过程中,都知道高级语言需要编译成机器语言才能被执行,但是原因是什么呢?

你是否对既定的事实也抱有好奇的态度。很多时候,它是什么远没有它为什么重要。

语言本身其实是一种沟通的工具:

  • 高级语言是写给人看的,通过阅读高级语言(go、c、c++ 等)编写的源码,可以方便的理解一段程序的处理逻辑。
  • 汇编语言是机器语言便于记忆的书写格式,可理解为助记符。
  • 机器语言是用二进制代码表示的计算机能直接识别和执行的一种机器指指令系统令的集合。

你现在看到的和所写的其实是一代又一代人的智慧结晶。想象一下现在让你再回到基于机器指令写代码的年代,直接写最右边 code.o 的代码,你会不会抓狂,反正我是头大了……

1.2 机器语言和 ELF 文件

并非 ELF 文件需要我们,而是我们需要 ELF 文件。既然写好的高级语言需要编译成机器语言才能够被机器运行,那我们可以合理的推测 ELF 文件中必然包含翻译过后的机器语言。

注:ELF 是 the Executable and Linkable Format 的缩写。

问:除了被翻译后的机器语言,ELF 文件还需要包括什么呢?或者由你设计 ELF 文件你会如何设计的?

答:既然机器语言是 ELF 文件最细粒度的组成部分,那么我们能否对其进行抽象,将具有相同属性的机器语言进行聚合。 所以一个完整的 ELF 文件其实包括以下几个部分。

  • ELF 文件 Header —— 用来标识自己
  • 按照 Segment 规则分类的机器语言 —— 供系统运行 ELF 文件时使用的(由 Program Header Table 进行描述)
  • 按照 Section 规则分类的机器语言 —— 供编译器链接时使用的(由 Section Header Table 进行描述)

纠结如我,画图的时候就在想是不是所有 ELF 文件的布局都像上图所示,ELF header 和 Program Header Table 在文件头,Section Header Table 在文件尾?查看了几个编译后的 ELF 文件有些是符合上述规则,有些则不然。所以结论其实是:(ps 英文翻译水平实在不咋地,截图貌似解释的会更清楚一些

2. 编译(从 section 到 segment)

2.1 为什么定义两种不同视角的概念

因为 ELF 文件是一类文件的简称,所以抽象出来的是更加通用的能力而非针对某个具体类型的定制化能力。ELF 包括以下四类:

  • 如果是可执行文件,则必须包含 Program Header Table,用于在程序运行时创建内存中的程序镜像
  • 如果是链接时被使用的文件,则必须包含 Section Header Table,用于在链接时合并相似的 section

2.2 section 和 segment 是什么

2.2.1 section

其实吧,我觉得 section 简单理解就是将源码编译以后的指令、数据等按照某种类型、属性特征进行划分存储的方式。比如:

  • bss section

    • 类型 :SHT_NOBITS 变量存储的值不会占用文件磁盘空间
    • 属性:SHF_ALLOC + SHF_WRITE 运行时需要分配存储空间且具有写权限
  • data section
    • 类型:SHT_PROGBITS 该 section 保存被进程定义的数据,其意义和格式由进程解释
    • 属性:SHF_ALLOC + SHF_WRITE 运行时需要分配存储空间且具有写权限
  • text section
    • 类型:SHT_PROGBITS 该 section 保存被进程定义的数据,其意义和格式由进程解释
    • 属性:SHF_ALLOC + SHF_EXECINSTR 运行时需要分配存储空间且具有执行权限

类型解释英文参考:

SHT_NOBITS:A section of this type occupies no space in the file but otherwise resemebles SHT_PROGBITS. Although this section contains no bytes, the sh_offset member contains the conceptual file offset.

SHT_PROGBITS:The sections holds information defined by the program, whose format and meaning are determined solely by the program.

2.2.2 segment

一个项目包括多个源文件,从宏观上看,编译、链接是将多个源码文件编译后的 *.o 链接成一个 .out 可执行文件的过程;从微观上看,编译其实生成 sections 数组的过程,链接其实就是合并多个 sections 数组并将其映射成 segments 的过程,具体 sections 的合并过程如下图所示。

思考:为什么做多个 .o 文件的相同属性做合并产生 .out 可执行文件,而不是连续拼接多个 .o 为一个 .out 可执行文件

**为什么需要 segments 映射 **

  • 因为程序需要被解释为进程才能够被利用,而程序被解释为进程的过程就是将磁盘上的程序加载到内存,所以操作系统加载程序就是按照 segment header table 内容进行存储镜像加载的。(ps 其实有点像说明书

sections 到 segments 的映射

  • 猜测 sections 到 segments 的映射是在链接器的内部是有一定规则实现的,但是我暂时没有找到规律点也木有查到相关资料,此处留一个疑问吧,一个具体的映射实例如下图所示:

3. 链接以后我便成为了你

segment 其实是一个逻辑的概念,本质就是一个映射关系的数据结构,记录了 segment 和 section 的关系。而链接过后 section 便以 segment 的属性被使用,也就是链接以后我(section)便成为了你(segment)。

注:此处强行文艺了一波,其实 section 和 segment 的映射关系就是 1:1 或者 n : 1 或者 0:1(ps 嗯,就是 0,比如上图的 00 这里虽然有 segment 但是没有实际的 section,仅做保留和占位

3.1 链接发展历史

存储空间和 CPU 是限制计算机运行程序数量和速度的两类瓶颈因素。而链接的发展就是围着这如何充分利用存储空间这一核心点进行的

3.1.1 静态链接 + 静态装入

静态链接、静态装入的做法是将所有目标文件链接成一个可执行文件,随后在创建进程时将该可执行文件全部加载到内存。

注:数据其实包括了数据和指令两个部分……

上图的策略的缺点:

  • 浪费磁盘存储空间
  • 浪费内存存储空间

注:A、B 程序即使依赖了一个相同的第三方库 S,这个 S 也需要在磁盘和内存中各存储一份

3.1.2 静态链接 + 动态装入

静态链接、动态装入的做法是将所有目标文件链接成一个可执行文件,但是在创建进程的时候采用了动态装入的策略,即一个函数只有当它被调用时,其所在的模块才会被装入内存。

上图的策略的缺点:

  • 仍旧浪费了磁盘存储空间
  • 部分节省了内存的存储空间

注:对于动态装入的策略,对于那些没有被使用到的代码是永远不会被装入内存的。

问:什么是没有被使用的,没有被使用不就应该在链接的时候不会被链接进来才对?

答:此处应该说的不是模块,应该是异常分支的路径,比如一段代码里有大量的错误处理函数,其实这部分错误处理原则上来说是不会被加载到内存中的。

3.1.3 动态链接 + 动态装入

动态链接、动态装入的做法

  • 动态链接:是对程序依赖的动态库链接时,不链接真正执行的指令,而是用 Stub(桩)的策略,在程序真正运行时才会去翻译 Stub(桩)为真正的指令(ps 这个思想在很多程序语言的设计上也有,比如 golang 的 Interface、rpc 通信中的 mock client
  • 动态装入:仍旧是按需加载指令或数据到内存中

此处,假设 add 这个模块为进程 1,进程 2 都依赖的一个共享库,这样在其实在对于赖 add 这个模块的程序编译的时候,仅编译 Stub(桩)到 ELF 文件中。而运行时则采用:

  • 先在内存中查找是否有其他程序依赖了这个共享库,如有,则直接翻译对应指令;如无,则进行下一步查找
  • 从磁盘上加载共享库到内存中使用

上面策略的优点:

  • 最大限度的节省了内存和磁盘空间
  • 采用共享库的策略,但是需要额外指定共享库的存储路径

3.2 动态链接本质

如下图所示进程 1 和进程 2 采用了动态链接的策略,使用了相同的共享库,但是这个库被两个进程定位不同的地址上,在进程 1 中,库从 9k 开始,在进程 2 中,库从 17k 开始。

由于库是共享的,所以

  • 不能采用写时复制的策略,为两个进程分别一份库的数据到内存,这违反了共享库节省存储空间的策略。

  • 不能采用装载的时候的重定位策略,将库重定位到进程 1 中 9k 对应的地址空间中,这会导致进程 2 无法完成动态加载的过程

一个更通用的解决是:在编译共享库的时候,用一个特殊的编译选项告知编译器,不要使用绝对地址的指令。而是,只能使用相对地址。即,使用向前 (向后)跳转 N 个字节的指令。无论共享库被加载虚拟地址空间中的什么位置。这种指令都是可以正确执行的。

所以,动态链接的核心就是共享库,而共享库其实就是如何编译出与地址无关的指令。( ps 在代码中不要使用绝对地址,而是使用相对偏移量的代码

碎碎念

写在最后面,因为最近花了很多力气去看 ELF 相关的东西,但是好像实际工作中使用它的场景很少。所以最近就一直在思考是不是花同样的力气去看其他的知识会更好,不过最近偶然看到《允许自己虚度时光》里的一段话突然就想通了。「不必对每件事情都期待结果,享受满足好奇的过程就好。」

原文如下:

我慢慢明白了为什么我不快乐,因为我总是期待一个结果。看一本书期待它让我变得深刻,吃饭游泳期待它让我一斤斤瘦下来,发一条短信期待它被回复,对人好期待它回应也好,写一个故事说一个心情期待它被关注被安慰,参加一个活动期待换来充实丰富的经历。这些预设的期待如果实现了,长舒一口气。如果没实现呢?自怨自艾。可是小时候也是同一个我,用一个下午的时间看蚂蚁搬家,等石头开花,小时候不期待结果,小时候哭笑都不打折。

参考资料

  • 入理解计算机系统(3.1)------汇编语言和机器语言
  • 程序的链接和装入及 Linux 下动态连接的实现

关于 ELF 文件想知道的事相关推荐

  1. 网络服务器最基本的是文件,你可能想知道的15个网络常用基础知识

    原标题:你可能想知道的15个网络常用基础知识 网络是一个复杂的系统,涉及知识很多.现在腾正小超人给大家分享15个常用的网络基础知识: 1) 如何查看本机所开端口 用netstat -a -n命令查看! ...

  2. APP推广前,你应该知道的事

    前言:随着中国互联网渗透率的逐步提高,中国广告主的广告投放重心逐渐向互联网迁移.互联网广告投放占比上升趋势明显.调查表明,随着用户逐步向移动端迁徙,广告主也正快速接受甚至追捧移动广告,广告主在移动端的 ...

  3. 比尔·盖茨:关于新冠疫苗你需要知道的事

    这些天我被问得最多的一个问题是:世界何时才能回到去年12月新冠病毒大流行之前的状态?我的答案始终如一:当我们得到一种近乎完美的特效药的时候,或者当地球上几乎所有人都接种了新冠疫苗的时候. 前者不太可能 ...

  4. “真希望我第一次创业时就知道的事”

    我们常常事后诸葛亮.当你回头看看之前做过的项目或努力,你总能更好地意识到什么事重要.什么事不重要. 在创业上也是一样的.在一项事业上奋斗了一两年或更多的时间后,你总能更好地意识到一些事是值得担心的,而 ...

  5. eBay跨境电商建议指南:eBay开店之前你就该知道的事

    做eBay跨境电商的卖家千千万万,到底要怎么做才能在这个平台大卖呢?这是东哥最近私信很多人都在问的一个问题,东哥作为eBay老鸟,不得不说在这个问题上真的有很多建议想跟新手卖家们提一提.所以今天东哥的 ...

  6. CVer最想知道的,简单分析下《2020年度中国计算机视觉人才调研报告》

    文章首发于CVer最想知道的,简单分析下<2020年度中国计算机视觉人才调研报告> 最近闲来无事,老潘以一名普通算法工程师的角度,结合自身以及周围人的情况,理性也感性地分析一下极市平台前些 ...

  7. 产品经理面试必须知道的事

    最近"产品经理面试太难了吧!"这句话一直在我耳边缠绕.其实我觉得还好吧,我只会觉得你可能是没技巧.下面金老师给大家分享一下字节跳动产品经理招聘的面试干货. 应该没有人不知道字节跳动 ...

  8. 大数据软件的真假分辨,消费者一定要知道的事!

    大数据软件的真假分辨,消费者一定要知道的事! 大数据时代,各种各样的新东西出现,让大家的生活都变的更便利,快捷了.但是一个新的好东西的出现,势必会引来大量的不良商家开始争相模仿.恶意竞争,打乱了市场动 ...

  9. printf 格式串和参数不匹配的后果(你想知道的C语言 1.10)

    Q: 如下代码的输出结果是多少? #include <stdio.h> #include <unistd.h> #include <fcntl.h>int main ...

最新文章

  1. 博世力士乐液压_A10VSO71DFR1/31RPPA12N00力士乐柱塞泵原装现货
  2. Android Studio使用OpenCV后,使APP不安装OpenCV Manager即可运行
  3. edHat linux光盘引导,Red Hat Linux 9光盘启动安装过程
  4. Java正则表达式简单用法
  5. iOS设计模式-生成器
  6. matlab cam orbit,Matlab的绘图函数
  7. js为操作radio
  8. 记录一次svn报错:[Previous operation has not finished; run 'cleanup' if it was interrupted] 的排错过程
  9. 基于Udp的Socket网络编程
  10. 剑指Offer丑数问题
  11. 前端那些事之Nuxt.js
  12. 不要再危言耸听!家用电脑辐射全揭秘
  13. HCIE Security 二层攻击防范 备考笔记(幕布)
  14. Android四大组件每个组件的作用?它们都可以开启多进程吗?
  15. 路孚特:300天350个版本,旗舰移动产品“0”到“1”的交付之路
  16. js几种加密/解密方法
  17. 如何在电脑/手机上将HTML文件转换为PDF?
  18. 数据以及空值数据处理方法
  19. win7 计算机打不开搜狗,Win7电脑搜狗输入法不见了如何解决?
  20. win10办公局域网共享文件夹方法

热门文章

  1. LibQQt系列之十《QQt 功能介绍》
  2. 如何学习前端技术,你需要的
  3. confluence重置admin密码
  4. FreeRTOS学习---“信号量”篇
  5. Android 4编程入门经典—开发智能手机与平板电脑应用
  6. Linux系统编程—文件—write函数
  7. 【自考】网络经济与企业管理之初识总结
  8. Linux命令查看运行服务,在Linux系统中查看所有正在运行的服务
  9. 哈尔滨理工大学软件与微电子学院程序设计竞赛(同步赛)K
  10. 学习一下改变人生的32句励志名言