C++编译知识笔记(二)——Linux ELF文件解析

2023-10-27


编译器编译源代码后生成的文件叫做目标文件,也就是.o文件,里面包含了可执行的机器码和数据等,这里我们就以Linux平台为例来详细分析下目标文件里面存放的内容。目标文件从结构上讲,和可执行文件基本是一样的,主要区别仅仅是没有经过链接,从而可能有些符号或者地址还没有被调整,但整体是差不多的。这篇文章会以一个目标文件为实例来详细聊一下Linux下的目标文件里究竟有什么,以及它和可执行文件所用到的格式ELF(Executable Linkable Format)。

一、ELF格式概述

Linux下面的可执行文件格式是ELF,是COFF(Common Object File Format,早期的类UNIX系统使用)格式的变种,虽然名字就是可执行文件格式,但不光是可执行文件是这个格式,目标文件也是按这种文件类型来保存的,不光如此,Linux下面一共有四类文件是按照ELF的格式来保存的,如下:

ELF文件类型 说明 示例
可重定位文件(Relocatable) 这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库可可以归为这一类 Linux的.o目标文件
可执行文件(Excutable File) 这类文件包含了可直接执行的程序,比如ELF可执行文件,Linux下一般都没有扩展名 /bin/bash文件
共享目标文件(Shared Object File) .so文件,这类文件包含了代码和数据,链接器可以使用这种文件跟其他的目标文件和共享目标文件链接,产生新的目标文件。动态链接器则可以将这种共享文件目标文件与可执行文件结合,作为进程映像的一部分来运行 /lib/glibc-2.5.so
核心转储文件(Core Dump File) 当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件 core dump文件

对于一个熟练的linux平台的c/c++开发人员,这些文件类型应该都不陌生。另外,前面提到过,.a的静态链接库可以理解为.o的打包,因此本质上也属于ELF类型。

先不考虑具体的格式,我们已经知道目标文件里保存了我们执行程序所需要的内容,那么显然里面应该有执行所需要的指令和数据,这是最基本的,除此之外,还有链接时所需要的一些信息,比如符号表、调试信息、字符串等。一般目标文件将这些信息按不同的属性和类型,以“段”(section或segment)的形式存储,比如代码段、数据段等。

下图用一个简单的示例直观的表示了程序编译后的ELF目标文件格式(省略了一些段):
在这里插入图片描述
首先会是一个文件头,它描述了这个ELF文件的属性,包括是否是可执行文件、大端还是小端、文件适应的目标硬件架构等。文件头内容如下:
在这里插入图片描述
文件头中还有一个段表,包含了各个段的信息,段表如下:
在这里插入图片描述
可以看到一共有13各个段,而后面要用到的ojbdump -h命令则会省略一些不关键的辅助性质的段。

文件头后面就是各个段的具体内容。.开头的段都是系统保留段,应用程序也可以使用一些非保留名字来创建自定义段。图里的.text是代码段,保存了编译后的机器码,.data是数据段,保存已经初始化的全局静态变量和局部静态变量,而.bss段则是保存未初始化的全局变量和局部静态变量,这个比较令人困惑的bss的名字主要是因为历史原因。多提一句,这里说的静态变量指的是声明周期是整个程序的变量,在c/c++程序里,所有的全局变量和static 修饰的局部变量属于这一类型,至于全局变量是否由static关键字修饰影响的只是这个全局变量的可见性,加了static的全局变量只在这个编译单元可见,各个编译单元可以有同名的static全局变量。不要把静态变量和static关键字搞混了。

二、常见段及对应用途

除了上面提到的数据代码bss段等,这里贴一下《程序员的自我修养》一书中的一个ELF文件常见段和对应用途总结表:

段名 内容
.text 存放编译生成的机器码
.rodata 存放只读数据,一般是程序中的只读静态变量和字符串常量
.data 保存已经初始化的全局静态变量和局部静态变量
.bss 存储未初始化以及初始化为0的全局静态变量和局部静态变量
.rodata1 也是只读数据段,存放字符串常量,全局 const 变量,该段和 .rodata 类似。
.comment 存放编译器版本信息,比如 “GCC:GNU4.2.0”
.debug 调试信息
.dynamic 动态链接信息
.hash 符号哈希表
.line 调试时的行号表,即源代码行号与编译后指令的对应表
.note 额外的编译器信息,比如程序的公司名、发布版本号等
.strtab String Table 字符串表,用于存储 ELF 文件中用到的各种字符串
.symtab Symbol Table 符号表,从这里可以找到文件中的各个符号
.shstrtab 各个段的名称表,实际上是由各个段的名字组成的一个字符串数组
.plt 和 .got 动态链接的跳转表和全局入口表
.init 和 .fini 程序初始化和终结代码段

三、目标文件内容解析

下面以一个修改过的《程序员的自我修养》一书中的例子来实际看看目标文件的各个段的情况

int printf(const char* format , ...);

int global_init_var = 1;
int global_uninit_var;

void func1(int i){
	printf("%d\n", i);
}
int main(void){
	static int static_init_var = 2;
	static int static_uninit_var;
    static const int static_const_init_var = 3;
	static const int static_const_uninit_var;
	const int const_init_var = 4;
	int init_var = 5;
	int uninit_var;
	func1(static_init_var + static_uninit_var + static_const_uninit_var + static_const_uninit_var + init_var + uninit_var);
	return init_var;
}

gcc -c test.c编译得到text.o,经objdump命令查看段情况如下:
在这里插入图片描述
可以看到有id从0-5的6个段(省略了非关键的段),使用objdump -s -d test.o命令可以看到各段存放的内容按16进制展示如下:
在这里插入图片描述
而3(.bss)因为没有实际内容所以不包含在里面,其中4、5、6是辅助功能用到的段,这里先不讨论,下面我们看下0 1 2 3三个段,也就是代码段.text,只读数据段.rodata,数据段.data,以及.bss段。

3.1 代码段.text

代码段里保存的都是机器码,用objdump -s -d test.o命令可以得到反汇编之后的汇编代码,内容如下:
在这里插入图片描述
这里不过多解释汇编语句,可以看到内容对应我们写的两个函数。

3.2 只读数据段.rodata

.rodata,根据字面意思就很好理解,read only data,和.data段类似,但是是保存只读的静态常量,
在这里插入图片描述
可以看到有两个只读数据,因为字节序(大端小端)的关系字节的顺序和我们的习惯顺序是反着的,0x25640a00是"%d\n"对应的asc2妈加上一个结束的\0,而0x03000000则是对应的static const int static_const_init_var = 3;

只有静态变量或者常量才有必要提前定义在数据段里,所以可以看到const int const_init_var这种并不会保存在数据段里,而是直接在指令里写死临时分配在栈上,可以参考text段的汇编代码。

3.3 数据段.data

数据段保存已经初始化的全局静态变量和局部静态变量,0x01000000和0x02000000分别对应int global_init_var = 1;和static int static_init_var = 2;
在这里插入图片描述

3.4 .bss段

.bss段(Block Started by Symbol)则保存未初始化的全局变量和局部静态变量,实际上只是place holder,不会保存实际内容,可以说是通过.bss段给变量预留空间,不需要占用ELF文件的空间,加载到内存里才会实际占用空间。上面的例子里我们也能看.bss段在列表里,但并没有.bss段的内容。需要注意的一种特殊情况是初始化为0也有可能被编译器当成未初始化放在.bss段里以节省空间。

可以总结为:

  • Uninitialized global/static data
  • “Block Started by Symbol”
  • “Better Save Space”
  • Has section header but occupies no space

这个名字不像其他段那么直观,有兴趣进一步深入了解的可以参照【参考4】和【参考5】。

3.5 重定位表(Reloacation Table)相关段.rela.xxx

重定位表是用于链接阶段的重定位的,在独立地生成每个编译单元的时候很多变量和函数的地址是没法确定的,需要在链接阶段进行修正,后续静态链接会详细说明这个过程,这里先看下重定位表的结构,每一个需要重定位操作的段都会对应一个重定位表段,比如.text对应了一个.rela.text,可以用objdump -r命令查看,可以看到上面的示例程序有两个重定位表段,分别是.text的和.eh_frame的。
在这里插入图片描述
以printf的调用,也就是.text重定位表中的第2行为例,这行是说OFFSET为1b的地方需要后续链接阶段重定位,.text段的1b位置正是对printf的call指令的寻址部分,也就是printf的地址需要重定位,重定位类型为R_X86_64_PC32,这是一种相对寻址的重定位类型,后续聊静态链接的时候再展开讲。

3.6 字符串表.strtab和.shstrtab

ELF文件中用到了很多字符串,比如段名、变量名等,通常由.strtab和.shstrtab两个段保存,分别为字符串表(string table)段表字符串表(section header string table),前者用来保存普通的字符串,比如符号的名字,后者用来保存段表中用到的字符串,比如段名。因为字符串是变长的,这里采取的是连续保存并用\0分割,通过offset来获取。

我们可以用readelf命令打出来:
在这里插入图片描述
在这里插入图片描述
这里贴个ascii码表方便对照着看:
在这里插入图片描述
比如.strtab这个字符串,2e7374 72746162就是对应内容,前后各有一个\0,因此有一个为9的offset就可以获取到。

3.7 符号表.symtab

要将多个目标文件链接在一起,本质上就是将各个目标文件的内容合并后并且能保证运行时互相的变量访问和函数调用正常,也就是对内外部函数和变量的访问都能找到正确的地址,在链接中将函数和变量统称为符号,函数名或变量名就是符号名,整个链接过程的核心就是根据符号来确定正确的地址。每一个目标文件都会有一个相应的符号表(Symbol Table,.symtab段),记录了目标文件所用到的所有符号,注意是所用到的所有,不管包含在内部的符号还是外部符号。每个定义的符号有一个对应的值,叫符号值,对于变量和函数来说,就是他们的地址。
在这里插入图片描述
符号有不同的类型,上图说明了:
(1)func1和main的类型是T,说明是在.text段,且全局可见。
(2)global_init_var的类型是D,表明是全局可见且在.data段的。
(3)global_uninit_var的类型是C,表明是全局可见的在common块的。
(4)printf的类型是U,说明是未定义的,该符号在编译单元外部。
(5)static_const_init_var的类型是r,说明在.rodata段。
(6)static_const_uninit_var和static_uninit_var的类型是b,说明在.bss段。
(7)static_init_var的类型是d,表明是局部可见且在.data段,大小写表明了可见性。

【参考】
1.《程序员的自我修养—链接、装载与库》
2.https://stackoverflow.com/questions/64626917/global-variables-and-the-data-section
3.https://stackoverflow.com/questions/1856599/when-to-use-static-keyword-before-global-variables
4.https://www.cnblogs.com/idorax/p/6400210.html
5.https://en.wikipedia.org/wiki/.bss

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

C++编译知识笔记(二)——Linux ELF文件解析 的相关文章

  • Qt - 无法让 lambda 工作[重复]

    这个问题在这里已经有答案了 我有以下功能 我想在其中修剪我的std set
  • 添加对共享类的多个 WCF 服务的服务引用

    我正在尝试将我的 WCF Web 服务拆分为几个服务 而不是一个巨大的服务 但是 Visual Studio Silverlight 客户端 复制了两个服务共享的公共类 这是一个简单的例子来说明我的问题 在此示例中 有两个服务 两者都返回类
  • 当我单击 C# 中的“取消”按钮时重定向到新页面(Web 部分)

    Cancel button tc new TableCell btnCancel new Button btnCancel Text Cancel btnCancel Click new EventHandler btnCanel Clic
  • 在 OpenCL 中将函数作为参数传递

    是否可以在 OpenCL 1 2 中将函数指针传递给内核 我知道可以用C实现 但不知道如何在OpenCL的C中实现 编辑 我想做这篇文章中描述的同样的事情 在 C 中如何将函数作为参数传递 https stackoverflow com q
  • ASP .NET MVC,创建类似路由配置的永久链接

    我需要帮助在 MVC 网站中创建类似 URL 路由的永久链接 Slug 已设置为 www xyz com profile slug 代码为 routes MapRoute name Profile url profile slug defa
  • 在 Xcode4 中使用 Boost

    有人设置 C Xcode4 项目来使用 Boost 吗 对于一个简单的 C 控制台应用程序 我需要在 Xcode 中设置哪些设置 Thanks 用这个来管理它 和这个
  • TextBox 焦点的 WinForms 事件?

    我想添加一个偶数TextBox当它有焦点时 我知道我可以用一个简单的方法来做到这一点textbox1 Focus并检查布尔值 但我不想那样做 我想这样做 this tGID Focus new System EventHandler thi
  • 获取从属性构造函数内部应用到哪个属性的成员?

    我有一个自定义属性 在自定义属性的构造函数内 我想将属性的属性值设置为属性所应用到的属性的类型 是否有某种方式可以访问该属性所应用到的成员从我的属性类内部 可以从 NET 4 5 using CallerMemberName Somethi
  • 如何用 kevent() 替换 select() 以获得更高的性能?

    来自Kqueue 维基百科页面 http en wikipedia org wiki Kqueue Kqueue 在内核和用户空间之间提供高效的输入和输出事件管道 因此 可以修改事件过滤器以及接收待处理事件 同时每次主事件循环迭代仅使用对
  • 转到 C# WPF 中的第一页

    我正在 WPF 中使用导航服务 为了导航到页面 我使用 this NavigationService Navigate new MyPage 为了返回我使用 this NavigationService GoBack 但是如何在不使用的情况
  • 是否有与 C++11 emplace/emplace_back 函数类似的 C# 函数?

    从 C 11 开始 可以写类似的东西 include
  • Xamarin Android:获取内存中的所有进程

    有没有办法读取所有进程 而不仅仅是正在运行的进程 如果我对 Android 的理解正确的话 一次只有一个进程在运行 其他所有进程都被冻结 后台进程被忽略 您可以使用以下代码片段获取当前正在运行的所有 Android 应用程序进程 Activ
  • 两组点之间的最佳匹配

    I ve got two lists of points let s call them L1 P1 x1 y1 Pn xn yn and L2 P 1 x 1 y 1 P n x n y n 我的任务是找到它们点之间的最佳匹配 以最小化它
  • C# 创建数组的数组

    我正在尝试创建一个将使用重复数据的数组数组 如下所示 int list1 new int 4 1 2 3 4 int list2 new int 4 5 6 7 8 int list3 new int 4 1 3 2 1 int list4
  • 如何排列表格中的项目 - MVC3 视图 (Index.cshtml)

    我想使用 ASP NET MVC3 显示特定类型食品样本中存在的不同类型维生素的含量 如何在我的视图 Index cshtml 中显示它 an example 这些是我的代码 table tr th th foreach var m in
  • 32位PPC rlwinm指令

    我在理解上有点困难rlwinmPPC 汇编指令 旋转左字立即然后与掩码 我正在尝试反转函数的这一部分 rlwinm r3 r3 0 28 28 我已经知道什么了r3 is r3在本例中是一个 4 字节整数 但我不确定这条指令到底是什么rlw
  • gdb查找行号的内存地址

    假设我已将 gdb 附加到一个进程 并且在其内存布局中有一个文件和行号 我想要其内存地址 如何获取文件x中第n行的内存地址 这是在 Linux x86 上 gdb info line test c 56 Line 56 of test c
  • 运行代码首先迁移更新数据库时出错

    我在迁移到数据库时遇到问题 并且似乎找不到我遇到的错误的答案 System MissingMethodException Method not found System Data Entity Migrations Builders Tab
  • 同时从多个流中捕获、最佳方法以及如何减少 CPU 使用率

    我目前正在编写一个应用程序 该应用程序将捕获大量 RTSP 流 在我的例子中为 12 个 并将其显示在 QT 小部件上 当我超过大约 6 7 个流时 问题就会出现 CPU 使用率激增并且出现明显的卡顿 我认为它不是 QT 绘制函数的原因是因
  • 如何查明CONFIG_FANOTIFY_ACCESS_PERMISSIONS是否启用?

    我想利用fanotify 7 http man7 org linux man pages man7 fanotify 7 html我遇到的问题是在某些内核上CONFIG FANOTIFY ACCESS PERMISSIONS不起作用 虽然C

随机推荐

  • 西米支付:微信服务商支付的介绍

    服务商申请条件 1 微信支付服务商面向企业 政府机关 事业单位 社会组织类型主体开放申请 2 申请资料准备 1 业务联系人信息 包含联系人姓名 联系手机 联系邮箱 若联系人非法定代表人 还需提交有效证件照片 2 主体身份信息 营业执照 登记
  • (纯c)数据结构之------>链表(详解)

    目录 一 链表的定义 1 链表的结构 2 为啥要存在链表及链表的优势 二 无头单向链表的常用接口 1 头插 尾插 2 头删 尾删 3 销毁链表 打印链表 4 在pos位置后插入一个值 5 消除pos位置后的值 6 查找链表中的值并且返回它的
  • 【Nginx】解决在Nginx+Vue部署多个前端项目,二级目录不能访问、访问空白的问题

    一 前言 需求 设置访问 www ai com 访问时打开前端代码 tmp zhsf 设置访问 www ai com case search 时 访问时打开另一个前端代码 tmp template 二 实现过程 1 根目录访问 部署使用ng
  • uni-cloud云函数管理公共模块依赖

    1 右键函数文件夹 选中依赖模块 更新依赖 2 完成后
  • Keras中的fit函数训练集,验证集和测试集

    Keras中的fit函数训练集 验证集和测试集 1 Keras fit函数history对象包含两个重要属性 epoch 训练的轮数 history 它是一个字典 包含val loss val acc loss acc四个key 2 关于训
  • 第十三届蓝桥杯大赛软件赛省赛 Python 大学 C 组

    试题 A 排列字母 本题总分 5 分 问题描述 小蓝要把一个字符串中的字母按其在字母表中的顺序排列 例如 LANQIAO 排列后为 AAILNOQ 又如 GOODGOODSTUDYDAYDAYUP 排列后为 AADDDDDGGOOOOPST
  • 拥抱ChatGPT,开启结对咨询模式!

    ChatGPT刮起了一阵旋风 ChatGPT到底能做什么 做到什么程度 真的会让咨询顾问失业吗 带着这样的疑问 我费尽周折 注册了ChatGPT账号 我先从一个大众化的话题开启了与ChatGPT的对话 如何提高软件开发的质量 如果是我回答这
  • 网页文字复制的几种方法

    1 开启网页阅读模式 这种方法适用于Microsoft Edge浏览器中 它有网页阅读功能可以使用 在网址的最前面加上 read 就会进入网页阅读界面 然后选中文字就可以直接进行复制了 2 直接拖拽 一种简单直接的方法 不用进行任何其他操作
  • 如何在服务器上跑python程序

    购买服务器 首先你需要一个服务器 阿里云云翼计划有一个9 9云服务器ECS服务 你怎么买我不管 反正你最后给我搞到一个云服务器 购买的配置界面 由于阿里云现在限量购买 所以这里只是截个图说明而已 主要说明一点公共镜像选择ubuntu14 0
  • 【软件测试】理论知识基础第一章

    前言 骗取自己的救赎 直到和染尘斑驳的玫瑰一起坠入深渊 软件测试 理论知识基础第一章 一 认识软件测试 1 什么是软件测试 二 常见的测试分类 1 阶段划分 2 代码可见度划分 3 扩展 总结 三 模型 1 质量模型 2 W模型 四 软件测
  • Webservice接口的生成及调用

    最近项目上要对接一个Webservice形式的接口 因为以前一直没有对接过这种类型的 所以这次专门查了一些资料学习下 一 Webservice的简单介绍 WebService是一种跨编程语言和跨操作系统平台的远程调用技术 它通过标准通信协议
  • AAA协议tacacs认证简单实验

    实验名称 AAA的tacacs验证 实验目的 在AAA认证服务器上认证客户端telnet登陆路由器 实验拓扑图 主要实验步骤 Router上的配置 Router gt en Router conf t Router config inter
  • 内存超频时序怎么调_超频技术之内存“时序”重要参数设置解说

    超频技术之内存 时序 重要参数设置解说 来源 华强电子网 作者 华仔 浏览 432 时间 2017 05 10 21 48 标签 摘要 相信大多数超频帖子里都会提到内存时序调整 也就是我们经常看到的5 5 5 15 1T 4 5 4 12
  • python爬虫requests源码链家_Python 爬虫 链家二手房(自行输入城市爬取)

    因同事想在沈阳买房 对比分析沈阳各区的房价 让我帮忙爬取一下链家网相关数据 然后打 算记下笔记 用于总结学到的东西 用到的东西 一 爬虫需要会什么 学习东西 首先你要知道它是干嘛的 爬虫 顾名思义就是爬取你所看到的网页内容 小说 新闻 信息
  • 当可变形注意力机制引入Vision Transformer

    GiantPandaCV导语 通过在Transformer基础上引入Deformable CNN中的可变性能力 在降低模型参数量的同时提升获取大感受野的能力 文内附代码解读 引言 Transformer由于其更大的感受野能够让其拥有更强的模
  • 你的键盘多久没测试过了?看看有没有失灵

    键盘是电脑外置设备中易损坏的一种 也是必不可少的电脑配件 特别是程序员每天都需要频繁地使用键盘 键盘常会出现故障 例如失灵或某些字母 数字无法正常输入 这样既浪费时间又令人烦恼 为此 我们在这里提供一个在线测试工具 方便您测试键盘按键是否正
  • 疫情期间科研记录(1)——异步电机矢量控制

    2020年二月至三月 受疫情影响 居家隔离 受毕业以及找工作压力 居家而未敢忘科研 两个月成果如下 在此期间 参考大量文献和往上的代码 基本完善了开题时硕士毕业论文的电机控制系统概念 实现了基于概念的仿真模型 记此文档以备忘 异步电机矢量控
  • 【ARIMA-WOA-LSTM】合差分自回归移动平均方法-鲸鱼优化-长短期记忆神经网络研究(Python代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 1 1 ARIMA模型 1 2 鲸鱼优化算法 1 3 LSTM 模型 2 运行结果 3 参考文献 4 Py
  • js数组对象取出指定元素相同的对象组成新数组

    要处理的数组 name 小明 age 18 address 天上 name 小红 age 19 address 地里 name 小明 age 20 address 叙利亚 name 小明 age 5 address 娘胎 想处理成 name
  • C++编译知识笔记(二)——Linux ELF文件解析

    目录 一 ELF格式概述 二 常见段及对应用途 三 目标文件内容解析 3 1 代码段 text 3 2 只读数据段 rodata 3 3 数据段 data 3 4 bss段 3 5 重定位表 Reloacation Table 相关段 re