【注意,您可至这里浏览全文: http://yihect.juliantec.info/julblog//post/4/28

 ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V 系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。可以说,ELF是构成众多xNIX系统的基础之一,所以作为嵌入式Linux系统乃至内核驱动程序开发人员,你最好熟悉并掌握它。

其实,关于ELF这个主题,网络上已经有相当多的文章存在,但是其介绍的内容比较分散,使得初学者不太容易从中得到一个系统性的认识。为了帮助大家学习,我这里打算写一系列连贯的文章来介绍ELF以及相关的应用。这是这个系列中的第一篇文章,主要是通过不同工具的使用来熟悉ELF文件的内部结构以及相关的基本概念。后面的文章,我们会介绍很多高级的概念和应用,比方动态链接和加载,动态库的开发,C语言Main函数是被谁以及如何被调用的,ELF格式在内核中的支持,Linux内核中对ELF section的扩展使用等等。

好的,开始我们的第一篇文章。在详细进入正题之前,先给大家介绍一点ELF文件格式的参考资料。在ELF格式出来之后,TISC(Tool Interface Standard Committee)委员会定义了一套ELF标准。你可以从这里(http://refspecs.freestandards.org/elf/)找到详细的标准文档。TISC委员会前后出了两个版本,v1.1和v1.2。两个版本内容上差不多,但就可读性上来讲,我还是推荐你读 v1.2的。因为在v1.2版本中,TISC重新组织原本在v1.1版本中的内容,将它们分成为三个部分(books):

a) Book I

介绍了通用的适用于所有32位架构处理器的ELF相关内容

b) Book II

介绍了处理器特定的ELF相关内容,这里是以Intel x86 架构处理器作为例子介绍

c) Book III

介绍了操作系统特定的ELF相关内容,这里是以运行在x86上面的 UNIX System V.4 作为例子介绍

值得一说的是,虽然TISC是以x86为例子介绍ELF规范的,但是如果你是想知道非x86下面的ELF实现情况,那也可以在http://refspecs.freestandards.org/elf/中找到特定处理器相关的Supplment文档。比方ARM相关的,或者MIPS相关的等等。另外,相比较UNIX系统的另外一个分支BSD Unix,Linux系统更靠近 System V 系统。所以关于操作系统特定的ELF内容,你可以直接参考v1.2标准中的内容。

这里多说些废话:别忘了 Linus 在实现Linux的第一个版本的时候,就是看了介绍Unix内部细节的书:《The of the Unix Operating System》,得到很多启发。这本书对应的操作系统是System V 的第二个Release。这本书介绍了操作系统的很多设计观念,并且行文简单易懂。所以虽然现在的Linux也吸取了其他很多Unix变种的设计理念,但是如果你想研究学习Linux内核,那还是以看这本书作为开始为好。这本书也是我在接触Linux内核之前所看的第一本介绍操作系统的书,所以我极力向大家推荐。(在学校虽然学过操作系统原理,但学的也是很糟糕最后导致期末考试才四十来分,记忆仿佛还在昨天:))

好了,还是回来开始我们第一篇ELF主题相关的文章吧。这篇文章主要是通过使用不同的工具来分析对象文件,来使你掌握ELF文件的基本格式,以及了解相关的基本概念。你在读这篇文章的时候,希望你在电脑上已经打开了那个 v1.2 版本的ELF规范,并对照着文章内容看规范里的文字。

首先,你需要知道的是所谓对象文件(Object files)有三个种类:

1) 可重定位的对象文件(Relocatable file)

这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file)。我们可以使用 ar 工具将众多的 .o Relocatable object files 归档(archive)成 .a 静态库文件。如何产生 Relocatable file,你应该很熟悉了,请参见我们相关的基本概念文章和JulWiki。另外,可以预先告诉大家的是我们的内核可加载模块 .ko 文件也是 Relocatable object file。

2) 可执行的对象文件(Executable file)

这我们见的多了。文本编辑器vi、调式用的工具gdb、播放mp3歌曲的软件mplayer等等都是Executable object file。你应该已经知道,在我们的 Linux 系统里面,存在两种可执行的东西。除了这里说的 Executable object file,另外一种就是可执行的脚本(如shell脚本)。注意这些脚本不是 Executable object file,它们只是文本文件,但是执行这些脚本所用的解释器就是 Executable object file,比如 bash shell 程序。

3) 可被共享的对象文件(Shared object file)

这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。动态库在发挥作用的过程中,必须经过两个步骤:

a) 链接编辑器(link editor)拿它和其他Relocatable object file以及其他shared object file作为输入,经链接处理后,生存另外的 shared object file 或者 executable file。

b) 在运行时,动态链接器(dynamic linker)拿它和一个Executable file以及另外一些 Shared object file 来一起处理,在Linux系统里面创建一个进程映像。

以上所提到的 link editor 以及 dynamic linker 是什么东西,你可以参考我们基本概念中的相关文章。对于什么是编译器,汇编器等你应该也已经知道,在这里只是使用他们而不再对他们进行详细介绍。为了下面的叙述方便,你可以下载test.tar.gz包,解压缩后使用"make"进行编译。编译完成后,会在目录中生成一系列的ELF对象文件,更多描述见里面的 README 文件。我们下面的论述都基于这些产生的对象文件。

make所产生的文件,包括 sub.o/sum.o/test.o/libsub.so/test 等等都是ELF对象文件。至于要知道它们都属于上面三类中的哪一种,我们可以使用 file 命令来查看:

 

[yihect@juliantec test]$ file sum.o sub.o test.o libsub.so test
sum.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
sub.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
test.o:    ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
libsub.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
test:      ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped

 

结果很清楚的告诉我们他们都属于哪一个类别。比方 sum.o 是应用在x86架构上的可重定位文件。这个结果也间接的告诉我们,x86是小端模式(LSB)的32位结构。那对于 file 命令来说,它又能如何知道这些信息?答案是在ELF对象文件的最前面有一个ELF文件头,里面记载了所适用的处理器、对象文件类型等各种信息。在TISCv1.2的规范中,用下面的图描述了ELF对象文件的基本组成,其中ELF文件头赫然在目。

ELF 文件头

等等,为什么会有左右两个很类似的图来说明ELF的组成格式?这是因为ELF格式需要使用在两种场合:

a) 组成不同的可重定位文件,以参与可执行文件或者可被共享的对象文件的链接构建;

b) 组成可执行文件或者可被共享的对象文件,以在运行时内存中进程映像的构建。

所以,基本上,图中左边的部分表示的是可重定位对象文件的格式;而右边部分表示的则是可执行文件以及可被共享的对象文件的格式。正如TISCv1.2规范中所阐述的那样,ELF文件头被固定地放在不同类对象文件的最前面。至于它里面的内容,除了file命令所显示出来的那些之外,更重要的是包含另外一些数据,用于描述ELF文件中ELF文件头之外的内容。如果你的系统中安装有 GNU binutils 包,那我们可以使用其中的 readelf 工具来读出整个ELF文件头的内容,比如:

 

[yihect@juliantec test]$ readelf -h ./sum.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian   Version:                           1 (current)   OS/ABI:                            UNIX - System V   ABI Version:                       0   Type:                              REL (Relocatable file)   Machine:                           Intel 80386   Version:                           0x1   Entry point address:               0x0   Start of program headers:          0 (bytes into file)   Start of section headers:          184 (bytes into file)   Flags:                             0x0   Size of this header:               52 (bytes)   Size of program headers:           0 (bytes)   Number of program headers:         0   Size of section headers:           40 (bytes)   Number of section headers:         9   Section header string table index: 6  

 

这个输出结果能反映出很多东西。那如何来看这个结果中的内容,我们还是就着TISCv1.2规范来。在实际写代码支持ELF格式对象文件格式的时候,我们都会定义许多C语言的结构来表示ELF格式的各个相关内容,比方这里的ELF文件头,你就可以在TISCv1.2规范中找到这样的结构定义(注意我们研究的是针对x86架构的ELF,所以我们只考虑32位版本,而不考虑其他如64位之类的):

ELF 文件头结构

这个结构里面出现了多种数据类型,同样可以在规范中找到相关说明:

ELF 相关数据类型

在我们以后一系列文章中,我们会着重拿实际的程序代码来分析,介时你会在头文件中找到同样的定义。但是这里,我们只讨论规范中的定义,暂不考虑任何程序代码。在ELF头中,字段e_machine和e_type指明了这是针对x86架构的可重定位文件,最前面有个长度为16字节的字段中有一个字节表示了它适用于32bits机器,而不是64位的。除了这些之外,另外ELF头还告诉了我们其他一些特别重要的信息,分别是:

a) 这个sum.o的进入点是0x0(e_entry),这表面Relocatable objects不会有程序进入点。所谓程序进入点是指当程序真正执行起来的时候,其第一条要运行的指令的运行时地址。因为Relocatable objects file只是供再链接而已,所以它不存在进入点。而可执行文件test和动态库.so都存在所谓的进入点,你可以用 readelf -h 看看。后面我们的文章中会介绍可执行文件的e_entry指向C库中的_start,而动态库.so中的进入点指向 call_gmon_start。这些后面再说,这里先不深入讨论。

b) 这个sum.o文件包含有9个sections,但却没有segments(Number of program headers为0)。

那什么是所谓 sections 呢?可以说,sections 是在ELF文件里头,用以装载内容数据的最小容器。在ELF文件里面,每一个 sections 内都装载了性质属性都一样的内容,比方:

1) .text section 里装载了可执行代码;

2) .data section 里面装载了被初始化的数据;

3) .bss section 里面装载了未被初始化的数据;

4) 以 .rec 打头的 sections 里面装载了重定位条目;

5) .symtab 或者 .dynsym section 里面装载了符号信息;

6) .strtab 或者 .dynstr section 里面装载了字符串信息;

7) 其他还有为满足不同目的所设置的section,比方满足调试的目的、满足动态链接与加载的目的等等。

一个ELF文件中到底有哪些具体的 sections,由包含在这个ELF文件中的 section head table(SHT)决定。在SHT中,针对每一个section,都设置有一个条目,用来描述对应的这个section,其内容主要包...(后续省略。。。)

 

【注意,您可至这里浏览全文: http://yihect.juliantec.info/julblog//post/4/28