ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

编译链接全链路解析:ELF形成到可执行加载的发展历程

编译链接全链路解析:ELF形成到可执行加载的发展历程 目录一、目标文件二、ELF文件三、可执行文件形成四、可执行文件加载五、结语一、目标文件编译和链接这两个步骤在Windows下被IDE封装的很完美一般通过一键构建即可完成编译链接但一旦遇到错误尤其是链接相关的错误大多数情况下就束手无策了。在Linux中可以通过gcc编译器来完成编译链接这一系列操作如下图所示编译的过程其实就是将程序的源代码翻译成CPU能够直接运行的机器代码。hello.c#includestdio.h void run(); int main() { printf(hello world!\n); run(); return 0; }code.c#includestdio.h void run() { printf(running...\n); }例如这里就可以通过调用gcc -c来分别编译hello.ccode.c这两个源文件。gcc -c hello.c gcc -c code.c可以看到在编译之后会生成两个扩展名为.o的文件它们被称为目标文件。可通过file命令查看文件类型。file hello.o通过file命令查看hello.o文件结果如下图所示目标文件是一个二进制文件文件的格式为ELFELF是对二进制代码的一种封装。二、ELF文件要理解编译链接的细节就不得不了解ELF文件ELF文件有以下4种类型1、可重定位文件即xxx.o文件包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。2、可执行文件即可执行程序。3、共享目标文件即xxx.so文件。4、内核转储存放当前进程的执行上下文用于dump信号触发。一个ELF文件通常由以下四部分组成ELF头描述文件的主要特性位于文件的开始位置主要目的是定位文件的其他部分。程序头表列举了所有有效的段和它们的属性。表里记着每个段开始的位置、位移和长度。这些段都紧密放在二进制文件中需要段表的描述信息才能把每个段分割开。节头表包含对节的描述。节ELF文件的基本组成单位包含了特定类型的数据。ELF文件的各种信息和数据都存储在不同的节中如代码节存储了可执行代码数据节存储了全局变量和静态数据等。最常见的节1、代码节(.text)用于保存机器指令是程序的主要执行部分。2、数据节(.data)保存已初始化的全局变量和局部静态变量。size code.o执行size命令可以查看code.o文件的内存布局大小结果如下所示在ELF文件中内存通常被划分为以下几个不同的段1、text代码段用于存放程序的可执行机器指令即编译后的代码以及只读常量这部分通常是只读的。2、data已初始化数据段用于存放程序中已经初始化的全局变量和静态变量。3、bss未初始化数据段用于存放程序中未初始化或初始化为0的全局变量和静态变量。4、dectextdatabss字节数总和的十进制表示。5、hexdec数值的十六进制表示。三、可执行文件形成ELF形成可执行文件的步骤如下1、将多份C/C源代码翻译成为目标.o文件动静态库(ELF)2、将多份.o文件section进行合并四、可执行文件加载一个ELF会有多种不同的Section在加载到内存的时候也会进行Section合并形成segment合并原则相同属性比如可读可写可执行需要加载时申请空间等这样即便是不同的Section在加载到内存中可能会以segment的形式加载到一起具体合并原则被记录在了ELF的程序头表中readelf -S hello # 查看可执行程序的section如上图所示可以看到ELF文件被划分为30个节(Section)节头表本身从文件偏移0x5d40处开始存放节头表就是一张目录它记录了文件中每一个节的名字、类型、在文件中的偏移量、大小以及权限等属性。readelf -l hello # 查看section合并的segment如上图所示readelf -l hello命令可查看ELF可执行文件的程序头表以及节到段的映射。section合并的主要原因是为了减少页面碎片提高内存使用效率。如果不进行合并假设页面大小为4096字节(内存块基本大小、加载、管理的基本单位)如果.text部分为4097字节.init部分为512字节那么它们将占用3个页面而合并后它们只需2个页面。此外操作系统在加载程序时会将具有相同属性的section合并成一个大的segment这样就可以实现不同的访问权限从而优化内存管理和权限访问控制。程序头表和节头表其实是ELF文件提供2个不同的视图来理解这两个部分链接视图对应节头表文件结构的粒度更细将文件按功能模块的差异进行划分静态链接分析的时候一般关注的是链接视图能够理解ELF文件中包含的各个部分的信息。为了空间布局上的效率将来在链接目标文件时链接器会把很多节(section)合并规整成可执行的段(segment)、可读写的段、只读段等。合并后空间利用率就高了。执行视图对应程序头表用于告诉操作系统如何加载可执行文件完成进程内存的初始化。一个可执行程序的格式中一定会有program header table。链接视图和执行视图一个在链接时作用一个在运行加载时作用。如上图所示从链接视图来看命令readelf -S hello.o可以帮助查看ELF文件的节头表.text节保存了程序代码指令的代码节.data节保存了初始化的全局变量和局部静态变量等数据.rodata节保存了只读的数据如一行C语言代码中的字符串。由于.rodata节是只读的所以只能存在于一个可执行文件的只读段中。因此只能是在text段中找到.rodata节.BSS节为未初始化的全局变量和局部静态变量预留位置.symtab节Symbol Table符号表就是源码里面那些函数名、变量名和代码的对应关系.got.pit节为全局偏移表-过程链接表.got节保存了全局偏移表.got节和.pit节一起提供了对导入的共享库函数的访问入口由动态链接器在运行时进行修改。使用readelf命令查看.so文件可以看到该节。从执行视图来看告诉操作系统哪些模块可以被加载进内存加载进内存之后哪些分段是可读可写哪些分段是只读哪些分段是可执行的可以在ELF头中找到文件的基本信息以及可以看到ELF头是如何定位程序头表和节头表的。可通过以下命令查看code.o这个可重定位文件的主要信息readelf -h code.o # 查看目标文件结果如下图所示对于ELF HEADER它的主要作用是用于定位文件的其他部分。五、结语本文主要围绕编译链接ELF文件从形成到可执行加载展开介绍系统梳理了从编译、链接到加载执行的全链路机制。以目标文件为切入点分析了编译器生成的中间产物及其可重定位属性深入剖析了ELF文件的内部结构如节头表、程序头表及关键节的组成与语义。通过这一完整链条的梳理不仅是建立对ELF文件格式的体系化认知也是深入理解进程地址空间、动态链接机制的必要条件
返回列表