今天翻翻老本,翻到一款上天入地的神器 —— readelf,据说用它可以拂开云雾,抽丝剥茧,去伪存真,深入其里。它就像一把精工刀,专用于对ELF格式文件进行外科手术般的解剖,今天我们来见识见识。
先来看看什么是ELF格式文件:
在Linux中,ELF是标准的可执行文件格式,其全称是Executable and Linkable Format。
换句话说:
-
可执行文件(Executable)
-
可重定位文件(Linkable)
他们都属于ELF格式文件。
这种格式主要特点是里面的数据或代码,都是按照段(section)的方式来组织的,请看上面示例代码example的内部景象:
可以看到,这个示例程序里面包含了29个section,其中不乏熟悉的面孔,比如.init、.text、.data等等,这些section都是程序本身的内容,而其他的很多section只是系统加载器在运行程序的时候需要的一些辅助信息。
-S 选项中列出来的信息,有一项是Addr,这是程序运行时对应的段的虚拟地址,可以用这个选项查看可执行文件和可重定位文件的区别,你会发现重定位文件中的全局变量、函数等符号的地址都是0,这也验证了这些符号需要链接定位的编译原理。
另外,如果想知道更加详细的细节,可以使用不同的参数选项的组合。比如查看ELF格式头信息,可以使用:
在上述输出中,重要的信息是:
-
CLASS 表示文件类型,这里是 32位的 ELF 格式。
-
Data 表示文件中的数据是按照什么格式组织(大端或小端)的,不同处理器平台数据组织格式可能就不同,如x86平台为小端存储格式。
-
OS/ABI ,指出操作系统类型,ABI 是 Application Binary Interface 的缩写。
-
Type 表示文件类型。ELF 文件有 3 种类型,一种是如上所示的 Executable file 可执行文件,一种是可重定位文件(Relocatable),另外一种是共享库(Shared Library) 。
-
Machine,指的是机器平台类型,使用ARM平台进行嵌入式开发时,会用到交叉工具链,编译生成的文件机器平台类型就不是Intel x86,而是ARM。
-
Entry pointer address,程序的虚拟地址入口点,因为这还不是可运行的程序,故而这里为零。
使用 -s 可以查看详细的符号表信息:
raedelf -s example
由于以上命令会输出相当多的符号表信息,这里就不贴出来了。
所谓的符号,就是程序中使用到的所有的函数名和全局变量名,由于函数和全局变量默认都是全局可见的,因此他们简称全局符号或者符号。全局符号都是需要重定位的。
执行以上命令之后,将会列举出程序中直接使用和间接调用的所有符号细节。我们甚至可以查看调用的库函数的真正版本,比如程序中使用了printf()函数,但是编译系统会为了某些目的简化为puts()函数来输出。
readelf还有很多有用的选项,帮助我们更精确理解ELF格式的内在细节,这些用法可以在man手册中查到。