ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V 系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。可以说,ELF是构成众多xNIX系统的基础之一,所以作为嵌入式Linux系统乃至内核驱动程序开发人员,你最好熟悉并掌握它。
首先,你需要知道的是所谓对象文件(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系统里面创建一个进程映像。
可以说,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,比方满足调试的目的、满足动态链接与加载的目的等等。
这个结果显示了 sum.o 中包含的所有9个sections。因为sum.o仅仅是参与link editor链接的可重定位文件,而不参与最后进程映像的构建,所以Addr(sh_addr)为0。后面你会看到可执行文件以及动态库文件中大部分sections的这一字段都是有某些取值的。Off(sh_offset)表示了该section离开文件头部位置的距离。Size(sh_size)表示section的字节大小。ES(sh_entsize)只对某些形式的sections 有意义。比方符号表 .symtab section,其内部包含了一个表格,表格的每一个条目都是特定长度的,那这里的这个字段就表示条目的长度10。Al(sh_addralign)是地址对齐要求。另外剩下的两列Lk和Inf,对应着条目结构中的字段sh_link和字段sh_info。它们中记录的是section head table 中的条目索引,这就意味着,从这两个字段出发,可以找到对应的另外两个 section,其具体的含义解释依据不同种类的 section 而不同,后面会介绍。
注意上面结果中的 Flg ,表示的是对应section的相关标志。比方.text section 里面存储的是代码,所以就是只读的(X);.data和.bss里面存放的都是可写的(W)数据(非在堆栈中定义的数据),只不过前者存的是初始化过的数据,比方程序中定义的赋过初值的全局变量等;而后者里面存储的是未经过初始化的数据。因为未经过初始化就意味着不确定这些数据刚开始的时候会有些什么样的值,所以针对对象文件来说,它就没必要为了存储这些数据而在文件内多留出一块空间,因此.bss section的大小总是为0。后面会看到,当可执行程序被执行的时候,动态连接器会在内存中开辟一定大小的空间来存放这些未初始化的数据,里面的内存单元都被初始化成0。可执行程序文件中虽然没有长度非0的 .bss section,但却记录有在程序运行时,需要开辟多大的空间来容纳这些未初始化的数据。
.strtab section 中存储着的都是以字符 为分割符的字符串,这些字符串所表示的内容,通常是程序中定义的函数名称、所定义过的变量名称等等。。。当对象文件中其他地方需要和一个这样的字符串相关联的时候,往往会在对应的地方存储 .strtab section 中的索引值。比方下面将要介绍的符号表 .symtab section 中,有一个条目是用来描述符号 gv_inited 的,那么在该条目中就会有一个字段(st_name)记录着字符串 gv_inited 在 .strtab section 中的索引 7 。 .shstrtab 也是字符串表,只不过其中存储的是 section 的名字,而非所函数或者变量的名称。
字符串表在真正链接和生成进程映像过程中是不需要使用的,但是其对我们调试程序来说就特别有帮助,因为我们人看起来最舒服的还是自然形式的字符串,而非像天书一样的数字符号。前面使用objdump来反汇编 .text section 的时候,之所以能看到定义了函数 sum_func ,那也是因为存在这个字符串表的原因。当然起关键作用的,还是符号表 .symtab section 在其中作为中介,下面我们就来看看符号表。