ELF文件详解

一、ELF概述

1、ELF的定义

ELF(Executable and Linkable Format)文件是一种目标文件格式,常见的ELF格式文件包括:可执行文件、可重定位文件(.o)、共享目标文件(.so)、核心转储文件等。

ELF主要用于Linux平台,Windows下是PE/COFF格式。       

2、ELF文件的结构

一个完整的ELF文件一般会包括如下几个内容:ELF头、Section头、Program头和Section。

其中由Section头组成的集合称为Section头表,由Program头组成的集合称为Program头表。注意:数个连续的头称之为头表,头表是虚拟出来的定义,文件中不存在头表,只有头。

一个Section头指向一个Section,Section头中包括所指向Section的名字、类型、其在ELF文件中的偏移地址、大小等信息。

一个Program头指向一个Segment,Program头中包括所指向Segment的类型、其在ELF文件中的偏移地址、大小,映射到内存的虚拟地址等信息。一个Segment由一系列连续的Section构成,连续的Section拥有相同的权限,如只读、读写、可读可执行等;

一个ELF头内包含有:Section头表的在ELF文件中的偏移地址、单个Section头的大小、Section头表中Section头的个数;Program头表的在ELF文件中的偏移地址、单个Program头的大小、Program头表中Program头的个数;该ELF文件的类型,若是可执行文件的话,还包含的有程序的入口地址。

3、头的表示方法及其含义

1)变量及其大小:

2)ELF头

#define EI_NIDENT 16
 
struct Elf32_Ehdr            //共52个字节    //Ehdr表示ELF header
{
  unsigned char  e_ident[EI_NIDENT];
  Elf32_Half e_type;        //类型包括:可执行文件、可重定向文件、共享目标文件等
  Elf32_Half e_machine;     //有X86、arm之类
  Elf32_Word e_version;
  Elf32_Addr e_entry;       //可执行程序的入口地址
  Elf32_Off e_phoff;        //Program头表的偏移地址
  Elf32_Off e_shoff;        //Section头表的偏移地址
  Elf32_Word e_flags;
  Elf32_Half e_ehsize;      //本结构体的size
  Elf32_Half e_phentsize;   //单个Program头的size
  Elf32_Half e_phnum;       //Segment头表中Segment头的个数
  Elf32_Half e_shentsize;   //单个Section头的szie
  Elf32_Half e_shnum;       //Section头表中Section头的个数
  Elf32_Half e_shstrndx;    //储存Section名字集合的Section的下标,指".shstrtab"的下标
};

2)Section头

struct Elf32_Shdr              //共40个字节    //Shdl表示Section header
{
    Elf32_Word sh_name;        //所指向Section的名字,如".text"、".data"、".bss"等
    Elf32_Word sh_type;        //所指向Section的类型,如:符号表、字符串表等
    Elf32_Word sh_flags;       
    Elf32_Addr sh_addr;
    Elf32_Off sh_offset;       //所指向Section在ELF文件中的偏移量
    Elf32_Word sh_size;        //所指向Section的size
    Elf32_Word sh_link;        //和其关联的Section头的下标索引
    Elf32_Word sh_info;
    Elf32_Word sh_addralign;   //字节对齐
    Elf32_Word sh_entsize;
};
 

3)Program头

struct Elf32_phdr            //32个字节    //phdr表示Program header
{
    Elf32_Word p_type;       //如PT_LOAD表示,对应Segment可被加载到内存中
    Elf32_Off p_offset;      //Segment在ELF文件中的偏移量
    Elf32_Addr p_vaddr;      //Segment映射到内存后的虚拟地址
    Elf32_Addr p_paddr;      //Segment映射到内存后的物理地址,此时与虚拟地址相同
    Elf32_Word p_filesz;     //Segment在ELF文件中占用的size
    Elf32_Word p_memsz;      //Segment映射到内存后占用的size
    Elf32_Word p_flage;      //读、写、执行权限
    Elf32_Word p_align;      //字节对齐,p_vaddr和p_paddr对p_align取模后为0
};

更详细内容请参考:ELF文件格式解析

4、实例解析

可执行文件中Program头表是必须的,可重定向文件(.o)中Section头表是必须的,共享目标文件(.so)中两者都是必须的。

1)可重定向文件分析

ELF头信息如下所示:

在此文件中,可看到其类型为REL, 即可重定向文件。其中Program头的个数为0,Section头的个数为8个,没有程序入口地址。

下图是8个Section头的详细信息:

其中Addr在此处被填充为了0的原因是,其目前并不需要被加载到内存中,在链接的时候才会被填充。

根据上述各Section的偏移量及size可推断出其在该可重定向文件中空间布局,如下表所示:

偏移量(Off)大小(size)Section备注
0x00x34ELF头0x34表示十进制的52,刚好为ELF头的大小
0x340x2a.text 
0x600x38.data 
0x980x0.bss 
0x980x30.shstrtab 
0xc80x140Section头表一个Section头的大小为40个字节,共8个头,大小为0x140
0x2080x80.symtab 
0x2880x28.strtab 
0x2b00x10.rel.text 

下面详述上面几种类型的Section:

Ⅰ .shstrtab

.shstrtab中存放着各个Section的名字。

Ⅱ .strtab

.symtab中存放着程序中用到的符号的名字。

Ⅲ .bss

程序中未初始化的全局变量都会被归类到bss段,并在程序加载的时候被初始化为0。

在加载.bss的时候和.data一样,都属于可读可写的数据,但在ELF文件中.data需要占用一段内存空间来保存变量的初始化值,而.bss却不需要。

也就是说,.bss只占用一个Section头的大小,而不需要对应的Section。如上表中可以看出.bss所描述Section的size为0。

Ⅳ .rel.text

.rel.text用于告诉链接器,哪些地方需要重定向。

Ⅴ .symtab

.symtab内存放着程序中用到的符号,包括变量符号、函数符号,如printf、main等。

.symtab有如下定义:

struct Elf32_sym                //
{
    Elf32_Word st_name;         //符号的名字
    Elf32_Addr st_value;        //符号相对于其所在Section偏移的相对地址
    Elf32_Word st_size;         //符号的size
    unsigned char st_info;      //低四位表示符号的作用范围(全局或局部),高四位表示符号的类型(变量、函数等)
    unsigned char st_other;
    Elf32_Half st_shndx;        //该符号的值在哪个Section下存储
};

实例:

以上图中的data_items为例,其Ndx为3,表示其在第3个Section,即.data。data_items的value值为00000000,表示其相对于.data的偏移地址为0,即data_itms在.data的开头。

_start的value也为00000000,表示其在.text的开头,也即整个代码的入口是_start。

2)可执行文件分析

可执行文件的ELF头信息如下所示:

相对于可重定向文件来说,其类型变为了EXEC,少了两个Section header,多了两个Program头,并且有可执行程序的入口地址。

6个Section头如下所示:

从图中可以看出,.text和.data的Addr不再为0,有了实际的值,这便是在链接过程中装载上的。

.bss段因为没有使用到,所以被删除掉了。

.rel.text在链接之后,便完成了自己的使命,也就被删除掉了。

根据上述各Section的偏移量及size可推断出其在该可执行文件中空间布局,如下表所示:

偏移量(Off)大小(size)Section备注
0x00x34ELF头0x34表示十进制的52,刚好为ELF头的大小
0x340x40Program头表一个Program头的大小为32字节,共2个头,大小为0x40
0x740x2a.text 
0xa00x38.data 
0xd80x27.shstrtab 
0x1000xf0Section头表一个Section头的大小为40个字节,共6个头,大小为0xf0
0x1f00xa0.symtab 
0x2900x40.strtab 

2个Program头如下所示:

结合Program头和Section的空间布局表可以看出,ELF头、Program头表和Section头表共同组成了第一个Segment;.data单独组成了另一个Segment。

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,这也正是程序的入口地址。
 

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

5、可执行ELF文件的装载过程

 

 

附录

1)Section与Segment的英文含义

section和segment都有部分的意思,但

section指的“部分”是不同质的,如:The TOEFL is divided into three sectiond, namely listening, structure and reading.在这里托福考试是由三部分组成的,这三部分是不一样的,即不同质的。

而segment指的“部分”是同质的,如:I want the middle segment of the rope. 我想要中间那段绳...

2)段错误(Segment Error)

当程序试图访问不允许访问的内存位置,或试图以不允许的方式访问内存位置(例如尝试写入只读位置,或覆盖部分操作系统)时会发生段错误。

常见的段错误,包括:

1)使用未经初始化及或已经释放的指针地址

2)访问受系统保护的内存地址

3)写入只读的内存地址

4)数组越界

5)堆栈溢出

参考资料:

1.ELF文件解析和加载(附代码)

2.ELF文件格式解析(完)

3.ELF文件详解—初步认识

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值