【嵌入式相关知识复习】01 附elf格式文件详解

一、ELF文件格式介绍

目标文件在不同的系统或平台上具有不同的命名格式,在Unix和X86-64 Linux上称为ELF(Executable and Linkable Format, ELF)。

ELF文件格式提供了两种不同的视角,在汇编器和链接器看来,ELF文件是由Section Header Table描述的一系列Section的集合,而执行一个ELF文件时,在加载器(Loader)看来它是由Program Header Table描述的一系列Segment的集合。

左边是从汇编器和链接器的视角来看这个文件,开头的ELF Header描述了体系结构和操作系统等基本信息,并指出Section Header Table和Program Header Table在文件中的什么位置,Program Header Table在汇编和链接过程中没有用到,所以是可有可无的,Section Header Table中保存了所有Section的描述信息。右边是从加载器的视角来看这个文件,开头是ELF Header,Program Header Table中保存了所有Segment的描述信息,Section Header Table在加载过程中没有用到,所以是可有可无的。注意Section Header Table和Program Header Table并不是一定要位于文件开头和结尾的,其位置由ELF Header指出,上图这么画只是为了清晰。

  1. .text:代码段,已编译程序的机器代码;该部分在程序运行前就已知其大小,通常属于只读数据,某些架构允许代码段可写。
  2. .rodata:只读数据,通常用于存放常量。
  3. .data:数据段,已初始化的全局变量和静态变量。
  4. .bss:未初始化的全局变量和静态变量,以及所有被初始化为0的全局或静态变量。目标文件在这个节中不占据实际的硬盘存储空间,仅仅是一个占位符。运行时,在内存中分配这些变量,初始值为0。
  5. .symtab:一个符号表,他存放在程序中定义和引用的函数和全局变量的信息。
  6. .rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。
  7. .rel.data:被模块引用或定义的所有全局变量的重定位信息。一般而言,任何已初始化的全局变量,如果他的初始化值是一个全局变量地址或者外部定义函数的地址,都需要被修改。
  8. .debug:一个调式符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译器驱动程序时,才会得到这张表。
  9. .line:原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。
  10. .strtab:一个字节表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串就是以NULL结尾的字符串的序列。

我们在汇编程序中用.section声明的Section会成为目标文件中的Section,此外汇编器还会自动添加一些Section(比如符号表)。Segment是指在程序运行时加载到内存的具有相同属性的区域,由一个或多个Section组成,比如有两个Section都要求加载到内存后可读可写,就属于同一个Segment。有些Section只对汇编器和链接器有意义,在运行时用不到,也不需要加载到内存,那么就不属于任何Segment。

目标文件需要链接器做进一步处理,所以一定有Section Header Table;可执行文件需要加载运行,所以一定有Program Header Table;而共享库既要加载运行,又要在加载时做动态链接,所以既有Section Header Table又有Program Header Table。

二、具体代码分析

下面用readelf工具读出目标文件的ELF Header和Section Header Table,然后我们逐段分析。

接下来我们来看Section Header Table格式

从Section Header中读出各Section的描述信息,其中.text和.data是我们在汇编程序中声明的Section,而其它Section是汇编器自动添加的。Addr是这些段加载到内存中的地址(我们讲过程序中的地址都是虚拟地址),加载地址(存储地址)要在链接时填写,现在空缺,所以是全0。Off和Size两列指出了各Section的文件地址,比如.data从文件地址0x60开始,一共0x38个字节,回去翻一下程序,.data中定义了14个4字节的整数,一共是56个字节,也就是0x38个。根据以上信息可以描绘出整个目标文件的布局。

起始文件地址

Section或Header

0

ELF Header

0x34

.text

0x60

.data

0x98

.bss

0x98

.shstrtab

0xc8

Section Header Table

0x208

.symtab

0x288

.strtab

0x2b0

.rel.text


这个文件不大,我们直接用hexdump或者使用010 Editor工具把目标文件的字节全部打印出来看。

.shstrtab和.strtab这两个Section中存放的都是ASCII码:

可见.shstrtab中保存着各Section的名字,.strtab中保存着程序中用到的符号的名字。每个名字都是以'\0'结尾的字符串。

我们知道,C语言的全局变量如果在代码中没有初始化,就会在程序加载时用0初始化。这种数据属于.bss段,在加载时它和.data段一样都是可读可写的数据,但是在ELF文件中.data段需要占用一部分空间保存初始值,而.bss段则不需要。也就是说,.bss段在文件中只占一个Section Header而没有对应的Section,程序加载时.bss段占多大内存空间在Section Header中描述。在我们这个例子中没有用到.bss段,以后我们会看到这样的例子。

我们继续分析readelf输出的最后一部分,是从.rel.text和.symtab这两个Section中读出的信息。

.rel.text告诉链接器指令中的哪些地方需要重定位。

.symtab是符号表。Ndx列是每个符号所在的Section编号,例如data_items在第3个Section里(也就是.data),各Section的编号见Section Header Table。Value列是每个符号所代表的地址,在目标文件中,符号地址都是相对于该符号所在Section的相对地址,比如data_items位于.data段的开头,所以地址是0,_start位于.text段的开头,所以地址也是0,但是start_loop和loop_exit相对于.text段的地址就不是0了。从Bind这一列可以看出_start这个符号是GLOBAL的,而其它符号是LOCAL的,GLOBAL符号是在汇编程序中用.globl指示声明过的符号。

三、反汇编代码分析

通过使用objdump工具可以把程序中的机器指令进行反汇编(Disassemble),得到其汇编代码。

可执行文件header的变化:


section header的变化:

.text和.data的加载地址分别改成了0x08048074和0x080490a0。.bss段没有用到,所以被删掉了。.rel.text段就是用于链接过程的,链接完了就没用了,所以也删掉了。


多出来的两个program header:

多出来的Program Header Table描述了两个Segment的信息。.text段和前面的ELFHeader、Program Header Table一起组成一个Segment(FileSiz指出总长度是0x9e),.data段组成另一个Segment(总长度是0x38)。

VirtAddr列指出第一个Segment加载到虚拟地址0x0804 8000(注意在x86平台上后面的PhysAddr列是没有意义的),第二个Segment加载到地址0x0804 90a0。

Flg列指出第一个Segment的访问权限是可读可执行,第二个Segment的访问权限是可读可写。

最后一列Align的值0x1000(4K)是x86平台的内存页面大小。在加载时要求文件中的一页对应内存中的一页,对应关系如下图所示。

这个可执行文件很小,总共也不超过一页大小,但是两个Segment必须加载到内存中两个不同的页面,因为MMU的权限保护机制是以页为单位的,一个页面只能设置一种权限。此外还规定每个Segment在文件页面内偏移多少加载到内存页面仍然偏移多少,比如第二个Segment在文件中的偏移是0xa0,在内存页面0x0804 9000中的偏移仍然是0xa0,所以是从0x0804 90a0开始,这样规定是为了简化链接器和加载器的实现。从上图也可以看出.text段的加载地址应该是0x0804 8074,也正是_start符号的地址和程序的入口地址。


原来目标文件符号表中的Value都是相对地址,现在都改成绝对地址了。此外还多了三个符号__bss_start、_edata和_end,这些是在链接过程中添进去的,加载器可以利用这些信息把.bss段初始化为0。

再看一下反汇编的结果:

到此为止ELF文件的问题已介基本介绍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值