初音未来の消失

elf 文件结构浅析

ELF

ELF文件分类

  1. 可执行文件,保存了执行程序的完整代码
  2. 可重定向文件,相比于可执行文件,其中拥有相应的数据和代码,但缺少了部分代码,这部分代码通过调用其他的目标文件(动态链接库)来执行。
  3. 共享目标文件,通常为.so文件,保存了供各个程序所共享的代码。

结构

文件头

elf的文件头定义了elf文件所对应的魔数、架构、程序起始地址、段表起始地址、节表起始地址、版本等等,通过 readelf -h 命令可以进行查看。

其结构所对应的定义可以在 /usr/include/elf.h 中找到,分为 Elf32_EhdrElf64_Ehdr

typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf32_Half    e_type;                 /* Object file type */
  Elf32_Half    e_machine;              /* Architecture */
  Elf32_Word    e_version;              /* Object file version */
  Elf32_Addr    e_entry;                /* Entry point virtual address */
  Elf32_Off     e_phoff;                /* Program header table file offset */
  Elf32_Off     e_shoff;                /* Section header table file offset */
  Elf32_Word    e_flags;                /* Processor-specific flags */
  Elf32_Half    e_ehsize;               /* ELF header size in bytes */
  Elf32_Half    e_phentsize;            /* Program header table entry size */
  Elf32_Half    e_phnum;                /* Program header table entry count */
  Elf32_Half    e_shentsize;            /* Section header table entry size */
  Elf32_Half    e_shnum;                /* Section header table entry count */
  Elf32_Half    e_shstrndx;             /* Section header string table index */
} Elf32_Ehdr;
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 */
  Elf64_Off     e_shoff;                /* Section header table file offset */
  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 */
  Elf64_Half    e_phnum;                /* Program header table entry count */
  Elf64_Half    e_shentsize;            /* Section header table entry size */
  Elf64_Half    e_shnum;                /* Section header table entry count */
  Elf64_Half    e_shstrndx;             /* Section header string table index */
} Elf64_Ehdr;

具体解析可以查看这篇文章:
https://www.cnblogs.com/jiqingwu/p/elf_explore_2.html

主要关注以下几个变量:

  1. e_entry 程序起始地址
  2. e_ehsize:ELF Header结构大小
  3. e_phoff、e_phentsize、e_phnum:描述 Program Header Table 的偏移、大小、结构。
  4. e_shoff、e_shentsize、e_shnum:描述 Section Header Table 的偏移、大小、结构。
    e_shstrndx:这一项描述的是字符串表在Section Header Table中的索引,如果没有section name string table, e_shstrndx 的值是SHN_UNDEF

视图

elf文件提供了两种视图,分别适用于不同的层面,也包含两个不同的表,分别在不同的时期起不同的作用。

  1. 在链接的时候,只关心 section 的部分
  2. 在运行的时候,只关心 segment 的部分

根据如下的表,其实 segment 和 section 是包含与被包含的关系。

链接视图

概念

以节(section)为单位进行描述,在链接层面使用到的一种视图。

由于每一个节在程序中所起到的作用都不同,编译器通过不同的节来区分不同代码或者数据在程序中的权限以及作用。

linux中查看elf文件的节:
readelf --sections [elf_name]

表中标明了每个节所开放的权限以及起始地址。

Section Header Table
typedef struct
{
  Elf32_Word    sh_name;                /* Section name (string tbl index) */
  Elf32_Word    sh_type;                /* Section type */
  Elf32_Word    sh_flags;               /* Section flags */
  Elf32_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf32_Off     sh_offset;              /* Section file offset */
  Elf32_Word    sh_size;                /* Section size in bytes */
  Elf32_Word    sh_link;                /* Link to another section */
  Elf32_Word    sh_info;                /* Additional section information */
  Elf32_Word    sh_addralign;           /* Section alignment */
  Elf32_Word    sh_entsize;             /* Entry size if section holds table */
} Elf32_Shdr;
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;
  1. sh_addr 每个 section 要装载在内存中的虚拟内存地址
  2. sh_offset 每个 section 相对于elf文件的偏移
  3. sh_size 每个 section 的大小
几个重要的节
  1. 符号表(.dynsym)
    符号表包含用来定位、重定位程序中符号定义和引用的信息。也就是经过修饰之后的函数名和变量名。

  2. 字符串表(.dynstr)
    存放程序执行中要使用的字符串。

执行视图

概念

以段(segment)为单位进行描述,在执行层面使用到的一种视图。

执行视图更偏向于解决操作系统段页分配的问题。

试想,如果每一个节(section)单独占用一个页(page),那么容易产生内零头,浪费操作系统资源。

linux中查看elf文件的段:
readelf --segments [elf_name]

如上所示,通过将多个权限及属性完全相同的节(section)分配到一个段(segment)中实行统一管理,操作系统页表中的内零头现象得到了很大的改善。

在目标文件中,program header不是必须的,我们用gcc生成的目标文件也不包含program header。

Program Header Table
typedef struct
{
  Elf32_Word    p_type;                 /* Segment type */
  Elf32_Off     p_offset;               /* Segment file offset */
  Elf32_Addr    p_vaddr;                /* Segment virtual address */
  Elf32_Addr    p_paddr;                /* Segment physical address */
  Elf32_Word    p_filesz;               /* Segment size in file */
  Elf32_Word    p_memsz;                /* Segment size in memory */
  Elf32_Word    p_flags;                /* Segment flags */
  Elf32_Word    p_align;                /* Segment alignment */
} Elf32_Phdr;
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;

装载elf文件步骤

shell 调用 fork() 函数创建一个新进程,新进程调用 execve() 执行elf文件,shell进程等待elf进程执行结束之后,再等待用户的新输入。

详情见 https://www.cnblogs.com/linhaostudy/p/8855238.html#autoid-1-0-0

参考资料

https://www.cnblogs.com/linhaostudy/p/8855238.html#autoid-0-0-0
http://bdxnote.blog.163.com/blog/static/844423520154502440229/
http://bdxnote.blog.163.com/blog/static/844423520154502440229/

退出移动版