ELF是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储的标准文件格式。
1.ELF文件类型
ELF的文件类型主要有三种,可重定位的目标文件、可执行的目标文件、可被共享的目标文件。
(1)可重定位的目标文件
由汇编器生成的.o文件,链接器拿一个或者一些可重定位的目标文件作为输入,经过链接处理后,生成一个可执行的目标文件或者一个可被共享的对象文件.so,可以使用ar工具将众多的.o文件归档archive成.a静态库文件。
(2)可执行的目标文件
文件编辑器vi、调试工具gdb等都是可执行的目标文件,此类文件规定了如何利用exec()创建一个程序的进程映像。当然shell脚本可不是执行的目标文件,他们只是文本文件,需要解析器执行。
(3) 可被共享的目标文件
如动态库文件.so,如果拿前面的静态库来生成可执行程序,则每个可执行程序都会有一份代码拷贝,如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间,另外如果把他们放到linux系统上一起运行,也会浪费掉宝贵的物理内存,如果将静态库换成动态库,那么问题就不会出现。
动态库在发挥作用的过程中,必须经过两个步骤,:1.链接器拿他和其他可重定位的文件.o文件以及其他.so文件作为输入,经过链接处理后,生成另外的可共享的目标文件.so文件或者可执行的目标文件。2.在运行时,动态链接库拿它和一个可执行的目标文件以及另外一些可共享的目标文件.so来一起处理,在linux系统里面创建一个进程映像。
2. 视图下的ELF文件
链接视图 | 执行视图 |
ELF头部 | ELF头部 |
程序头部表(可选) | 程序头部表 |
节区1 | 节区1 |
... |
|
节区n | 节区n |
... | ... |
节区头部表 | 节区头部表(可选) |
ELF的链接视图和执行视图是不一样的,ELF文件格式主要用于两种场合,1.组成可重定位文件,用于链接成可执行文件或者动态库,2.组成可执行文件或者动态库,在运行时需要内存中进程映像构建。
因此,左边表示的可重定位的文件格式,右边则表示可执行文件或者可被共享的对象文件格式。
ELF文件头被固定地放在不同类对象文件的最前面,我们可以使用file命令来看文件属于哪种ELF文件。
以上都显示了hello1.o 、hello_main.o都为可重定位文件(relocatable),libmyhello.so是可被共享文件(shared object),helloworld为可执行文件(executable)
3.ELF头部
可以用 readelf -h 文件
读取ELF文件头的信息。
显示文件头大小52字节
3.1 进入点
hello_main.o的进入点(Entry point address)为0x0,说明没有进入点(所谓的进入点,就是程序执行时,第一条指令地址)。可重定位文件主要用于链接,不需要提供进入点。
但是可执行文件及动态库存在进入点。
可执行文件helloworld的进入点e_entry执行C库的_start
动态库libmyhello.so的进入点指向call_gmon_start
3.2 节区
hello_main.o有20个section节区,但program headers的数量为0
Number of section headers: 20
Number of program headers: 0
可执行文件helloworld、动态库libmyhello.so的program headers的数量不为0
helloworld
Number of program headers: 9
libmyhello.so
Number of program headers: 7
继续说section,section是在ELF里面用以装载内容数据的最小容器,每一个section里面都装载了性质属性一样的内容,比如
(1)text section里装载了可执行代码。
(2)data section里装载了被初始化的数据。
(3)bss section里装载了未被初始化的数据。
(4)以 .rec 打头的section里面装载了重定位条目。
(5)symtab或者dynsym section里面装载了符号信息。
(6)strtab或者dynstr section里面装载了字符串信息。
(7)其他
查看可重定位文件hello_main.o的section表的内容,执行 readelf -S hello_main.o ,如下图
19个节区,由于hello_main.o仅仅参与链接的可重定位文件,而不参与最后 的进程映像的构建,所以19个节区的addr都为0。
offset表示了该section与文件头部位置的距离,size表示section的字节大小,entsizie只对某些形式的sections有意义,如.symtab section,其内部都包含了一个表格,表格的
每一个条目都是特定长度的,此时就是表示条目的长度为10,Align是地址对齐要求,flag里的标志,比如X是可执行的,W是可写的,bss和data都是可写,A(allocable)表示可分配的
可以使用 readelf -x secnum 文件名 命令来打印不同的section的内容
3.2.1 ELF .text section
readelf -x secnum 文件名 看到的节区的机器码,可读性差,因此我们可以换个工具来看,使用objdump,执行
objdump -d -j .text 文件名
3.2.2 ELF .data section
修改代码,增加初始化的数据
重新编译后,再看.o文件
3.2.3 ELF .strtab section
.strtab的序号是19,因此执行readelf -x 19 hello_main.o
该分区主要存的函数名称、变量名称等
3.2.4 ELF .symtab section
符号表节区对调试来说比较有用,执行 readelf -x 18 hello_main.o,得到的结果可读性极差
但是可以使用readelf -s 18 hello_main.o (小写s),看到了所有函数名、变量名。
在实际中常常用来测试有没有编译到某个函数等,如
4.执行视图下的ELF内容
以上第三部分专门讲了链接视图,接下来第四部分我们讲下执行视图
readelf -l helloworld 显示helloworld程序头信息,共有8个segment, R代表可读,E代表可执行,
其中,类型 INTERP 的 segment 只包含一个section,即.interp,包含了动态连接过程中所使用的解析器路径和名称,在linux里面,这个解释器实际是/lib/。
我们的程序需要用到动态链接库.so时,在shell键入命令执行时,内核会创建一个新的进程,在这个新进程的进程空间里面加载进可执行程序的代码段和数据段后,
也会加载进动态连接器(/lib/ld-linux.so符号链接指向的那个程序),加载完毕,动态连接器才将控制传递给应用程序的main函数。