热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入解析ELF文件格式与静态链接技术

本文详细探讨了ELF文件格式及其在静态链接过程中的应用。在C/C++代码转化为可执行文件的过程中,需经过预处理、编译、汇编和链接等关键步骤。最终生成的可执行文件不仅包含系统可识别的机器码,还遵循了严格的文件结构规范,以确保其在操作系统中的正确加载和执行。

简介

C/C++ 代码在变成可执行文件之前需要经历预处理、编译、汇编以及链接这几个步骤,最终生成的可执行文件包含了能够被系统处理的机器码。可执行文件必须按照特定的格式进行组织才能被系统加载、执行,所以可执行文件是特定于操作系统的。对于 Linux 来说是 ELF(Executable Linkable Format) 格式的文件,Windows 是 PE(Portable) 格式。对于 Java 代码,编译生成的 Class 文件也是有着特定的格式,才能被 JVM 执行。

一个程序一般由多个文件组成,文件之间会有变量和函数的引用,每个文件各自编译生成中间文件后必须经过链接才能生成最终的可执行文件。根据链接方式的不同可以分为静态链接和动态链接,静态链接是在链接期间重定位所有的符号引用,而动态链接则是在装载或者执行期间进行。

本文主要分析 Linux 下 ELF 文件的格式以及静态链接的过程。

目标文件的格式

源代码被编译生成的文件叫做目标,目标文件与可执行文件的格式是类似的,只是还没有经历链接,其中包含的有些地址还没有被调整。

目标文件中包含机器码、数据、符号表以及调试信息等,这些属性按照不同的段(Section ) 进行存储。段就是一定长度的的区域,不同的属性放在不同名字的段,具体如下所示:

可以看出,代码放在了名为 .text 的段,变量 global_init_var static_var 放在了名为 .data 的段,变量 global_uninit_var static_var 放在名为 .bss 的段。.bss 段存放的是未初始化的全局变量和局部静态变量。

上图的 EFL 文件除了几个段,还有文件头(File Header),其中包含了文件是否可执行、是静态链接还是动态链接以及目标硬件、操作系统等信息,还包括一个段表,段表是一个数组结构,描述了文件中各个段在文件中的偏移位置及段的属性等。用 readelf -h 可以读取上面代码编译后目标文件的头信息,如下图:

从上图可以看到,其中包含了文件的魔数(Magic) 、字长(class)、CPU 类型等信息,如果是可执行文件,还包括程序的入口地址。Start of section headers 的值是段表的偏移量。

目标文件中除了上面介绍的代码段和数据段,还有很多其它段,readelf -S 命令可以查看段表的信息,如下图:

可以看出,上面的目标文件总共有 12 个段,第一个为无效段,实际上是 11 个段。其中有字符串表 .strtab、符号表 .symtab 以及注释信息 .comment 等。还有一个段是 .rela.txt 段,这个是重定位表,在静态链接过程中需要用到。

静态链接

在了解了 ELF 文件的结构之后,接下来介绍静态链接的过程。以下面的代码为例:

/* a.c */
extern int shared;int main()
{int a = 100;swap(&a, &shared);
}/* b.c */
int shared = 1;void swap(int *a, int *b)
{*a ^= *b ^= *a ^= *b;
}

在上面的代码中,b.c 定义了全局符号,分别是变量 shared 和函数 swap,a.c 定义了一个全局符号 main。在 a.c 中引用了 b.c 里面的 sharedswap。用 gcc -c -fno-stack-protector a.c b.c 编译这两个文件之后(-fno-stack-protector 是关闭堆栈保护功能),生成了两个目标文件 a.ob.o,下一步就是要把这两个文件链接在一起,形成最终的可执行文件。

空间与地址分配

静态链接的第一步是把多个目标文件进行合并,一般采用相似段合并的方式。通过扫描所有的输入目标文件,并且获得它们各个段的长度、属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。多个目标文件合并后如下图所示:

符号地址的确定

利用上一步收集到的数据,进行符号解析与重定位、调整代码中的地址等。利用命令 ld a.o b.o -e main -o aba.ob.o 链接(-e main 是将 main 函数作为程序的入口),生成可执行文件 ab。链接前后段的地址信息如下所示:

上图是 a.ob.o 以及链接后的 ab 的地址信息。其中 Size 是段的大小, VMA 是虚拟地址。对于 a.ob.o.text 段来说,大小分别是 0000002c0000004b, 加起来正好是 ab.text 段的大小 00000077。另外, a.ob.o 的 VMA 都是 00000000,此时它们还没有分配地址,而在 ab 中,地址变为 00000000004000e8,这就是分配的虚拟地址,当 ab 被加载到内存中后, .text 段的起始地址便是这个。

段的地址被确定后,内部函数和变量的地址也就确定了,因为在每个段内,符号的表示是一个相对于段起始位置的偏移量。当段的起始位置被确定后,每个符号只要在偏移量的基础上加上这个起始位置的地址就行。但是对于引用的外部符号来说,它们的地址还不得知,需要经过符号解析和重定位的过程。

符号解析与重定位

在 a.c 中引用了变量 shared 和函数 swap ,单独编译 a.c 的时候并不知道 b.c 这个文件,所以在 a.o 中,用到 shared 的地方用 0 地址代替,等到链接阶段,能够确定这个变量的地址了,再把地址进行调整。

这里的问题是链接器如何知道哪些指令需要被调整呢?这就用到上面提到过的重定位表,命令 objdump -r a.o 可以查看 a.o 中的重定位表,如下图:

每一个需要被重定位的地方叫做一个重定位入口,可以看到,a.o 中需要重定位的两个符号 sharedswap。将重定位入口的地址进行修正,才能完成链接过程,最终生成的可执行文件便可以被系统正常运行。

总结

代码从文本形式到最终的可执行文件需要经历多个过程,其中链接主要做的是多个目标文件的合并以及符号的解析与重定位,最终生成特定格式的可执行文件。本文大概地介绍了 ELF 文件的结构和静态链接的主要步骤,更详细的内容可以查看相关书籍深入了解。

参考

  • 《程序员的自我修养:链接、装载与库》
  • 《深入理解计算机系统》

如果我的文章对您有帮助,不妨点个赞支持一下(^_^)



推荐阅读
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 主要用了2个类来实现的,话不多说,直接看运行结果,然后在奉上源代码1.Index.javaimportjava.awt.Color;im ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • MQTT技术周报:硬件连接与协议解析
    本周开发笔记重点介绍了在新项目中使用MQTT协议进行硬件连接的技术细节,涵盖其特性、原理及实现步骤。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 本文详细分析了Hive在启动过程中遇到的权限拒绝错误,并提供了多种解决方案,包括调整文件权限、用户组设置以及环境变量配置等。 ... [详细]
  • 本文详细介绍了macOS系统的核心组件,包括如何管理其安全特性——系统完整性保护(SIP),并探讨了不同版本的更新亮点。对于使用macOS系统的用户来说,了解这些信息有助于更好地管理和优化系统性能。 ... [详细]
  • 本文详细介绍了 Apache Jena 库中的 Txn.executeWrite 方法,通过多个实际代码示例展示了其在不同场景下的应用,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 本文介绍了如何通过 Maven 依赖引入 SQLiteJDBC 和 HikariCP 包,从而在 Java 应用中高效地连接和操作 SQLite 数据库。文章提供了详细的代码示例,并解释了每个步骤的实现细节。 ... [详细]
  • 使用Vultr云服务器和Namesilo域名搭建个人网站
    本文详细介绍了如何通过Vultr云服务器和Namesilo域名搭建一个功能齐全的个人网站,包括购买、配置服务器以及绑定域名的具体步骤。文章还提供了详细的命令行操作指南,帮助读者顺利完成建站过程。 ... [详细]
author-avatar
zengqingwei1220
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有