成就更好的自己
本篇是基础技术系列中ELF相关技术的首篇文章。
尽管网上有许多关于ELF相关内容的文章,但总体而言,要么是一些非常基础且重复性强的内容,要么直接深入探讨相对高深的主题,缺乏系统化分析和解释。
接下来,在本系列和专栏后续文章中,我们将以更加通俗的方式对ELF相关内容进行系统化分析。
目录
ELF的由来与作用
ELF文件的结构
ELF文件作执行用途
ELF文件作链接用途
ELF文件结构
ELF的由来与作用
ELF,叫做可执行可链接格式,是可执行文件、目标代码、共享库和核心转储的通用标准文件格式。
它首先在Unix操作系统版本的应用程序二进制接口(ABI)规范中发布,随后在工具接口标准中发布,它很快被不同的Unix系统供应商所接受。
从设计上来说,ELF格式灵活、可扩展且跨平台。它支持不同的字节序和地址大小,因此不会排除任何特定的CPU或指令集架构。这使得它能够被许多不同硬件平台上的许多不同操作系统采用。
请注意,ELF是一种约定的格式,是一种format!所谓的ELF文件是指遵循ELF的格式进行组建的文件,而不是某种特定的文件!
ELF在不同场景下有着不同的存在文件形式和作用,但这些文件都是遵循ELF的格式进行组建的:
- 可执行文件:编译链接出来的可执行文件,通过./就可以运行的那种。
- 目标代码:编译打包出来的.o.a文件,通过链接器链接用的。
- 共享库:编译出来的可重定位.so文件,也执行时进行LinkLoad用的。
- 核心转储:使用gdb调试或段错误时生成的core文件,保存了产生该文件时的某个进程的内存状态映像,用于debug或其他操作。
ELF文件的结构
大多数文章从这里开始就要千篇一律的讲解ELF结构了,这里我先按下不表。为了方便各位看官理解后续内容,我先讲点别的。
ELF之所以叫做可执行可链接格式,就是因为他最主要的作用就是两件事——1.执行2.链接。
ELF文件作执行用途
ELF文件作执行用途的情况一般有这么几个常见场景:
- 不使用动态链接库编出的可执行文件,elf头如下:
- 使用动态链接库编出的可执行文件,elf头如下:
- 编出的动态链接库,elf头如下:
很明显,ELF文件用作执行用途的情况下,不管静态编译还是动态编译出来的,只要他作执行用途,那就会有入口点地址。众所周知,启动一个程序的主要步骤就是创建进程空间(进程控制块什么的)->分配内存页->将可执行文件和动态链接库(可能需要)中的内容(程序与数据)加载到内存->跳转到程序入口点->开始执行程序。
可执行文件和动态链接库都是能被执行的,因此上述过程中的加载待运行的程序与数据到内存这个步骤是ELF文件作为执行用途所必须的。
那程序加载器是如何知道可执行文件中的什么内容应该放到进程内存空间中的什么位置呢?因为用作执行用途的ELF文件中含有一个区域叫做程序头表(program headers tab),这个头表是虚拟出来的概念,是若干程序头(program headers)共同组成的一种表,他会告诉程序加载器将要执行的可执行文件中的各个数据块应该放在进程内存空间的什么位置;可执行文件中所谓的需要进行搬移的各个数据块,就是ELF文件中段(segment)这个概念。
好的,通俗的总结一下:
- ELF文件中的程序头和段是ELF文件用作执行用途的基本信息和概念;
- 程序加载器将ELF文件中的内容加载到内存的时候就是基于段进行加载的;
- 每个段都有一个对应的程序头,用于描述该段的信息,有多少程序头就有多少段;
- 可通过图片中Number of program headers 判断有多少程序头,通过Size of program headers判断每个程序头的长度;
- 这些程序头在ELF文件中的存放是连续的,存放的起始地址偏移就是图片中的程序头起点;
(上述部分内容涉及到程序加载和进程内存模型,属于计算机基础,不知道的建议买一本黑皮书看看,这里不展开)
ELF文件作链接用途
ELF文件作执行用途的情况一般有这么几个常见场景:
- 编出的.o文件或静态链接库
很明显,与作执行用途的ELF文件相比,用作链接用途的ELF文件不能进行执行操作,也就没有入口点地址和程序头地址,也没有必要将文件中的内容加载到内存中;因此也就没有程序表头和段的概念。
用作链接用途的ELF文件通常使用在生成可执行程序的链接过程,这个过程简单来讲就是若干个可重定位可链接文件(.o或.a文件)进行相互链接并统一规划程序或数据的地址空间。
链接器如何在这些可链接文件中定位查找对应的符号并将指定的程序和数据进行地址重定位呢?因为用作链接用途的ELF文件中含有一个区域叫做节头表(section headers tab),这个头表是虚拟出来的概念,是若干节头(section headers)共同组成的一种表,他会告诉程序链接器需要的符号去哪里找,找到之后怎样如何进行地址重定位;可链接文件中在链接过程中具有相同意义和功能的数据放在了一起作为一个数据块,根据数据块的意义和功能不同得到ELF文件中节(section)这个概念。
好的,通俗的总结一下:
- ELF文件中的节头和节是ELF文件用作链接用途的基本信息和概念;
- 程序链接器将ELF文件中的内容进行链接时就是以节为单位进行符号查找等功能的;
- 每个节都有一个对应的节头,用于描述该节的信息,有多少节头就有多少节;
- 可通过图片中Number of section headers 判断有多少节头,通过Size of section headers判断每个节头的长度;
- 这些节头在ELF文件中的存放是连续的,存放的起始地址偏移就是图片中的start of section headers;
(上述部分内容涉及到编译过程,属于编译原理,不知道的建议买一本黑皮书看看,这里不展开)
ELF文件结构
现在看到的就是所有文章中基本都会用到的一张很经典的图,ELF文件结构,为了方便理解我做亿些小小的注释:
ELF文件主要结构如下:
- ELF头:描述整个ELF文件的头,包含了一些ELF文件的基础信息,就是上文中通过readelf –h看到的那些信息,其中前文那些图中的size of this header表示该文件头的长度。该部分在任何ELF文件中都是必须存在的。
- 若干程序头组成的程序头表:包含每个段的描述信息。该部分在没有执行功能的ELF文件中可以不存在。
- 若干节(若干段):ELF文件中的主要内容,由若干节的内容组成,在能够被执行的ELF文件中,也可以说是若干段的内容组成。同时站在两边的视角去看,如果段概念存在的情况下,每个段不重复的包含至少0个节。该部分为ELF文件的主体数据内容,必须存在。
- 若干节头组成的节头表:包含每个节的描述信息。该部分在一般ELF文件中都会存在,除非通过工具人为删除掉。
好的,通俗的总结一下:
一个未经任何人为修改阉割的ELF文件应至少有ELF头+若干节+若干节头组成;若ELF文件可被执行,则由ELF头+若干程序头+若干节+若干节头组成,其中将节(可以不是所有节)划入若干段的范围内。节是站在链接和文件基本构成的角度考虑的,段则是ELF文件具有被执行能力后,站在执行角度考虑的。
举个例子,下图是通过readelf –a得到的,可以看到该ELF文件有8个段,每个段是由那些节映射组成的。(.tbss等.xxx都是节的名称)。
一个ELF文件若人为阉割了节头,具有链接功能的ELF文件就会失去链接功能;具有执行功能的ELF文件就不可查看ELF文件内符号信息等大部分信息,但不会影响执行功能(因为程序头表还在,但动态链接库除外,动态链接库程序头和节头都需要)。
本篇结束,下一篇ELF文件进阶内容。