Linux系统ELF可执行文件格式详解(Qt代码分解文件)

前言

本文主要讲解ELF文件格式分解讲解。并且会使用c代码将信息打印出来方便读者理解elf格式,并且手写一个ELF文件解析工具。文章中的执行环境为ubuntu 22.04 x64操作系统中编译的ELF文件,因此我们按照x64的来解析文件。

一、ELF文件类型

  • ET_NONE: 未知类型。该标记的文件类型不确定或者还未定义。
  • ET_REL: 重定位文件(目标文件, xxx.o )。可重定位文件是还未链接到可执行文件的独立代码,即编译后的 .o 文件。
  • ET_EXEC: 可执行文件。这类文件也成为程序,是一个进程开始的入口。
  • ET_DYN: 共享目标文件,即动态链接文件,.so 文件。在程序运行时被装载并链接到程序的进程镜像中。
  • ET_CORE: 核心文件。在程序崩溃或者进程传递了一个SIGSEGV信号(分段违规)时,会在核心文件中记录整个进程的镜像信息。可以使用GDB读取这类文件来辅助调试并查找程序崩溃的原因。

二、ELF文件解析

其实我的理解,ELF文件主要分为四个部分(个人理解)1. ELF头 2. program 头表。3. section头表 4.section 段
然后还有一个program段其实是由多个section组成,如下图(这个图很重要):
在这里插入图片描述
然后对于ELF文件我们可以使用两种不同的视图来分析ELF文件,1. 链接视图。2. 执行视图。两个表示的是同一个文件,只是看数据的角度不一样而已。
在这里插入图片描述

2.1 ELF Header解析

我们先看看ELF 头部的数据结构。

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf64_Half	e_type;			/* Object file type */
  Elf64_Half	e_machine;		/* Architecture */
  Elf64_Word	e_version;		/* Object file version */
  Elf64_Addr	e_entry;		/* Entry point virtual address  程序入口点 */
  Elf64_Off	e_phoff;		/* Program header table file offset , Program 头表的偏移位置,可以通过这个偏移获取 Program的表数据*/
  Elf64_Off	e_shoff;		/* Section header table file offset , Section 头表的偏移位置,可以通过这个偏移获取 Section 的表数据*/
  Elf64_Word	e_flags;		/* Processor-specific flags */
  Elf64_Half	e_ehsize;		/* ELF header size in bytes */
  Elf64_Half	e_phentsize;		/* Program header table entry size ,Program 头部表的大小*/
  Elf64_Half	e_phnum;		/* Program header table entry count ,Program 有多少项数据*/
  Elf64_Half	e_shentsize;		/* Section header table entry size ,Section  头部表的大小 */
  Elf64_Half	e_shnum;		/* Section header table entry count ,Section 有多少项数据*/
  Elf64_Half	e_shstrndx;		/* Section header string table index , 字符串的索引,表示字符串在第几个Section段中*/
} Elf64_Ehdr;

在这里插入图片描述

2.2 Section视图解析

2.2.1 视图表结构如下

typedef struct
{
  Elf64_Word	sh_name;		/* Section name (string tbl index) */
  Elf64_Word	sh_type;		/* Section type */
  Elf64_Xword	sh_flags;		/* Section flags */
  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf64_Off	sh_offset;		/* Section file offset */
  Elf64_Xword	sh_size;		/* Section size in bytes */
  Elf64_Word	sh_link;		/* Link to another section */
  Elf64_Word	sh_info;		/* Additional section information */
  Elf64_Xword	sh_addralign;		/* Section alignment */
  Elf64_Xword	sh_entsize;		/* Entry size if section holds table */
} Elf64_Shdr;

在这里插入图片描述

2.2.2 sh_name(节名字)

表示节区的名字。
获取节区名字的方法步骤如下:

  1. 获取ELF Header中的e_shstrndx,字符串节区的索引
  2. 中Section Header(e_shstrndx)中以及拿到对应节区
  3. 找到对应strtab区的表头,
  4. 从表头的偏移量中拿到真正的字符串存储区域。
  5. 根据sh_name的便宜两种拿到对应的字符串。

对应的获取代码如下:

// 获取shdr表头
Elf64_Shdr *shdr = reinterpret_cast<Elf64_Shdr *>(fileBuffer + ehdr->e_shoff);
// shdr[ehdr->e_shstrndx] 获取对应字符串节
//  shdr[ehdr->e_shstrndx].sh_offset 获取该节的偏移量量拿到真正的字符串存储节
char *strtab = reinterpret_cast<char *>(fileBuffer + shdr[ehdr->e_shstrndx].sh_offset);
// 根据字符串存储节拿到对应的数据
std::string res(strtab + item.sh_name);

2.2.3 sh_type(节类型)

类型有以如下,具体的每总类型的节点分析在第三章中讲解。

* Legal values for sh_type (section type).  */

#define SHT_NULL	  0		/* Section header table entry unused */
#define SHT_PROGBITS	  1		/* Program data */
#define SHT_SYMTAB	  2		/* Symbol table */
#define SHT_STRTAB	  3		/* String table */
#define SHT_RELA	  4		/* Relocation entries with addends */
#define SHT_HASH	  5		/* Symbol hash table */
#define SHT_DYNAMIC	  6		/* Dynamic linking information */
#define SHT_NOTE	  7		/* Notes */
#define SHT_NOBITS	  8		/* Program space with no data (bss) */
#define SHT_REL		  9		/* Relocation entries, no addends */
#define SHT_SHLIB	  10		/* Reserved */
#define SHT_DYNSYM	  11		/* Dynamic linker symbol table */
#define SHT_INIT_ARRAY	  14		/* Array of constructors */
#define SHT_FINI_ARRAY	  15		/* Array of destructors */
#define SHT_PREINIT_ARRAY 16		/* Array of pre-constructors */
#define SHT_GROUP	  17		/* Section group */
#define SHT_SYMTAB_SHNDX  18		/* Extended section indices */
#define	SHT_NUM		  19		/* Number of defined types.  */
#define SHT_LOOS	  0x60000000	/* Start OS-specific.  */
#define SHT_GNU_ATTRIBUTES 0x6ffffff5	/* Object attributes.  */
#define SHT_GNU_HASH	  0x6ffffff6	/* GNU-style hash table.  */
#define SHT_GNU_LIBLIST	  0x6ffffff7	/* Prelink library list */
#define SHT_CHECKSUM	  0x6ffffff8	/* Checksum for DSO content.  */
#define SHT_LOSUNW	  0x6ffffffa	/* Sun-specific low bound.  */
#define SHT_SUNW_move	  0x6ffffffa
#define SHT_SUNW_COMDAT   0x6ffffffb
#define SHT_SUNW_syminfo  0x6ffffffc
#define SHT_GNU_verdef	  0x6ffffffd	/* Version definition section.  */
#define SHT_GNU_verneed	  0x6ffffffe	/* Version needs section.  */
#define SHT_GNU_versym	  0x6fffffff	/* Version symbol table.  */
#define SHT_HISUNW	  0x6fffffff	/* Sun-specific high bound.  */
#define SHT_HIOS	  0x6fffffff	/* End OS-specific type */
#define SHT_LOPROC	  0x70000000	/* Start of processor-specific */
#define SHT_HIPROC	  0x7fffffff	/* End of processor-specific */
#define SHT_LOUSER	  0x80000000	/* Start of application-specific */
#define SHT_HIUSER	  0x8fffffff	/* End of application-specific */

获取方法如下:

#define SHT_CASE_TYPE(XX) case SHT_##XX: res=#XX;break;
std::string ELFRead::getSectionType(Elf64_Shdr item)
{
    std::string res = "";
    switch(item.sh_type){
        SHT_CASE_TYPE(NULL)
        SHT_CASE_TYPE(PROGBITS)
        SHT_CASE_TYPE(SYMTAB)
        SHT_CASE_TYPE(STRTAB)
        SHT_CASE_TYPE(RELA)
        SHT_CASE_TYPE(HASH)
        SHT_CASE_TYPE(DYNAMIC)
        SHT_CASE_TYPE(NOTE)
        SHT_CASE_TYPE(NOBITS)
        SHT_CASE_TYPE(REL)
        SHT_CASE_TYPE(SHLIB)
        SHT_CASE_TYPE(DYNSYM)
        SHT_CASE_TYPE(INIT_ARRAY)
        SHT_CASE_TYPE(FINI_ARRAY)
        SHT_CASE_TYPE(PREINIT_ARRAY)
        SHT_CASE_TYPE(GROUP)
        SHT_CASE_TYPE(SYMTAB_SHNDX)
        SHT_CASE_TYPE(NUM)
        SHT_CASE_TYPE(LOOS)
        SHT_CASE_TYPE(GNU_ATTRIBUTES)
        SHT_CASE_TYPE(GNU_HASH)
        SHT_CASE_TYPE(GNU_LIBLIST)
        SHT_CASE_TYPE(CHECKSUM)
        SHT_CASE_TYPE(LOSUNW)
        SHT_CASE_TYPE(SUNW_COMDAT)
        SHT_CASE_TYPE(SUNW_syminfo)
        SHT_CASE_TYPE(GNU_verdef)
        SHT_CASE_TYPE(GNU_verneed)
        SHT_CASE_TYPE(GNU_versym)
        SHT_CASE_TYPE(LOPROC)
        SHT_CASE_TYPE(HIPROC)
        SHT_CASE_TYPE(LOUSER)
        SHT_CASE_TYPE(HIUSER)
    }
    return res;
}

2.2.4 sh_link和sh_info

这两字段比较特殊需要根据sh_type的类型他们的作用是不一样的。

在这里插入图片描述

sh_typesh_linksh_info
SHT_DYNAMIC表示这个节中的字符串节区的索引0
SHT_HASH哈希表所应用的符号表的节头索引0
SHT_REL
SHT_RELA
SHT_SYMTAB
SHT_DYNSYM

2.2.4 sh_flags (节权限标识)

W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)

std::string ELFRead::getSectionFlags(Elf64_Shdr item)
{
    std::string res = "";
    if(item.sh_flags & SHF_WRITE){
        res += "W";
    }
    if(item.sh_flags & SHF_ALLOC){
        res += "A";
    }
    if(item.sh_flags & SHF_EXECINSTR){
        res += "X";
    }
    if(item.sh_flags & SHF_MERGE){
        res += "M";
    }
    if(item.sh_flags & SHF_STRINGS){
        res += "S";
    }
    if(item.sh_flags & SHF_INFO_LINK){
        res += "S";
    }
    if(item.sh_flags & SHF_LINK_ORDER){
        res += "S";
    }
    if(item.sh_flags & SHF_OS_NONCONFORMING){
        res += "O";
    }
    if(item.sh_flags & SHF_GROUP){
        res += "G";
    }
    if(item.sh_flags & SHF_TLS){
        res += "T";
    }
    if(item.sh_flags & SHF_COMPRESSED){
        res += "C";
    }
    if(item.sh_flags & SHF_MASKOS){
        res += "o";
    }
    if(item.sh_flags & SHF_MASKPROC){
        res += "P";
    }
    return res;
}

2.2.4 sh_addr

2.2.5 sh_offset (节偏移量)

我们可以根据这个偏移量找到对应的节的信息。

2.2.6 sh_size( 节的大小)

表示该节的总大小。

2.2.7 sh_entsize(节的item数量)

如果这个节是一个列表节点那么就会告诉我们在这个节区数据中有多少个item子项。

2.3 Program视图解析

Program各个字段如下:

typedef struct
{
  Elf64_Word	p_type;			/* Segment type */
  Elf64_Word	p_flags;		/* Segment flags */
  Elf64_Off	p_offset;		/* Segment file offset */
  Elf64_Addr	p_vaddr;		/* Segment virtual address */
  Elf64_Addr	p_paddr;		/* Segment physical address */
  Elf64_Xword	p_filesz;		/* Segment size in file */
  Elf64_Xword	p_memsz;		/* Segment size in memory */
  Elf64_Xword	p_align;		/* Segment alignment */
} Elf64_Phdr;
  • p_type:类型有以下内容,常见的程序头类型有五种:PT_LOAD, PT_DYNAMIC, PT_NOTE, PT_INTERP, PT_PHDR。
    注意其中PT_LOAD一个可执行文件至少包含一个 PT_LOAD 类型的段。这类段是可装载的段,将会被装载或映射到内存中。例如,存放程序代码的text段和存放全局变量的动态链接信息的data段会被映射到内存中,根据p_align中的值进行内存对齐。text 段(代码段)权限一般设置为PF_X| PF_R(读和可执行),通常将data段的权限设置为`PF_W | PF_R)(读和写)。
#define	PT_NULL		0		/* Program header table entry unused */
#define PT_LOAD		1		/* Loadable program segment */
#define PT_DYNAMIC	2		/* Dynamic linking information */
#define PT_INTERP	3		/* Program interpreter */
#define PT_NOTE		4		/* Auxiliary information */
#define PT_SHLIB	5		/* Reserved */
#define PT_PHDR		6		/* Entry for header table itself */
#define PT_TLS		7		/* Thread-local storage segment */
#define	PT_NUM		8		/* Number of defined types */
#define PT_LOOS		0x60000000	/* Start of OS-specific */
#define PT_GNU_EH_FRAME	0x6474e550	/* GCC .eh_frame_hdr segment */
#define PT_GNU_STACK	0x6474e551	/* Indicates stack executability */
#define PT_GNU_RELRO	0x6474e552	/* Read-only after relocation */
#define PT_GNU_PROPERTY	0x6474e553	/* GNU property */
#define PT_LOSUNW	0x6ffffffa
#define PT_SUNWBSS	0x6ffffffa	/* Sun Specific segment */
#define PT_SUNWSTACK	0x6ffffffb	/* Stack segment */
#define PT_HISUNW	0x6fffffff
#define PT_HIOS		0x6fffffff	/* End of OS-specific */
#define PT_LOPROC	0x70000000	/* Start of processor-specific */
#define PT_HIPROC	0x7fffffff	/* End of processor-specific */
#define PT_CASE_TYPE(XX) case PT_##XX: res=#XX;break;
std::string ELFRead::getSegmentType(Elf64_Phdr item)
{
    std::string res="";
    switch (item.p_type) {
        PT_CASE_TYPE(NULL);
        PT_CASE_TYPE(LOAD);
        PT_CASE_TYPE(DYNAMIC);
        PT_CASE_TYPE(INTERP);
        PT_CASE_TYPE(NOTE);
        PT_CASE_TYPE(SHLIB);
        PT_CASE_TYPE(PHDR);
        PT_CASE_TYPE(TLS);
        PT_CASE_TYPE(NUM);
        PT_CASE_TYPE(LOOS);
        PT_CASE_TYPE(GNU_EH_FRAME);
        PT_CASE_TYPE(GNU_STACK);
        PT_CASE_TYPE(GNU_RELRO);
        PT_CASE_TYPE(GNU_PROPERTY);
        PT_CASE_TYPE(SUNWBSS);
        PT_CASE_TYPE(SUNWSTACK);
        PT_CASE_TYPE(HISUNW);
        PT_CASE_TYPE(LOPROC);
        PT_CASE_TYPE(HIPROC);
    }
    return res;
}

p_offset: 该segment的数据在文件中的偏移地址(相对文件头)
p_vaddr: segment数据应该加载到进程的虚拟地址
p_paddr: segment数据应该加载到进程的物理地址(如果对应系统使用的是物理地址)
p_filesz: 该segment数据在文件中的大小
p_memsz: 该segment数据在进程内存中的大小。注意需要满足p_memsz>=p_filesz,多出的部分初始化为0,通常作为.bss段内容
p_flags: 进程中该segment的权限(R/W/X)

#define PF_X		(1 << 0)	/* Segment is executable */
#define PF_W		(1 << 1)	/* Segment is writable */
#define PF_R		(1 << 2)	/* Segment is readable */
#define PF_MASKOS	0x0ff00000	/* OS-specific */
#define PF_MASKPROC	0xf0000000	/* Processor-specific */

p_align: 该segment数据的对齐,2的整数次幂。即要求p_offset % p_align = p_vaddr。

三、ELF各个节分析

根据不同类型的节他的存储的内存是不一样的。我们可以通过Section header中获取到该节的内容(通过Section Header中提供的sh_offset和sh_size,即通过偏移量和所占大小就能够拿到文件中某一段的数据这个数据就是整个Section的数据)

3.1 .text 节

该节保存的是代码指令。因此节的类型为SHT_PROGBITS。该类型官方定义为

3.2 .rodata 节

.rodata 保存了只读的数据。例如代码中的字符串。

printf("Hello world\n");

因为.rodata节是只读的,它存在于只读段中。因此,.rodata是在text段而不是data段。该节类型同样也是SHT_PROGBITS。

3.3 .plt 节

该节保存的是动态链接器从共享库导入的函数所必须的相关代码。因为保存的是代码,同样也存在与text段中,且节类型为SHT_PEOGBITS

3.4 .data 节

.data 节存在于data段,保存的是初始化的全局变量等数据,节类型也为SHT_PROGBITS。

3.5 .bss 节

.bss节保存的是未经初始化的全局变量数据,也是data段的一部分。占用空间不超过4字节,仅表示这个节本身的空间。程序加载时数据被初始化为0,在程序执行期间可以进行赋值。由于该节没有保存实际的数据,因此节类型为SHT_NOBITS。

3.6 .got.plt节

.got 节保存了全局偏移表。.got 节和.plt 节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。该节与程序执行有关,因此节类型为SHT_PROGBITS。

3.7 .dynsym 节

该节保存的是动态链接相关的导入导出符号,该节保存在text段中,节类型被标记为SHT_DYNSYM。

3.8 .dynstr 节

该节保存的是动态符号字符串表,是三种字符串表之一。表中的字符串以空字符为终止符,代表了符号的名称。
三种字符串表

1. .dynstr
2. .shstrtab
3. .strtab

elf load
https://github.com/malisal/loaders/blob/master/elf/elf.c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三雷科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值