ELF文件在加载过程中需要关注的部分

  1. 前言
  2. ELF文件头
  3. 程序头表
  4. 最后
  5. 参考资料

前言

学习一下ELF的加载过程,虽然以前也学过,不过有一些细节的地方很不确定。最近做毕设,要写一段代码,实现把ELF的各个段加载到一处自己设计实现的虚拟内存的功能。所以要处理ELF格式里面的一些成员,需要理解他们其中的各种关系才能继续。

目前我的设计还只有静态编译链接,所以我暂时并不关注节区(sections)以及一些其他特殊操作的部分,重点还是关注段的结构体的各个成员。

所以我会结合资料,并结合自己的理解,对其中需要的部分进行学习。

大概的需要有的印象:

1580984801606

1580987106935

ELF文件头

每个elf程序最开始的内容就长这个样子。

/* ELF Header */
#define EI_NIDENT 16
typedef struct elfhdr {
    unsigned char    e_ident[EI_NIDENT]; /* ELF Identification */
    Elf32_Half    e_type;        /* object file type */
    Elf32_Half    e_machine;    /* machine */
    Elf32_Word    e_version;    /* object file version */
    Elf32_Addr    e_entry;      /* virtual entry point */
    Elf32_Off     e_phoff;      /* program header table offset */
    Elf32_Off     e_shoff;      /* section header table offset */
    Elf32_Word    e_flags;      /* processor-specific flags */
    Elf32_Half    e_ehsize;     /* ELF header size */
    Elf32_Half    e_phentsize;    /* program header entry size */
    Elf32_Half    e_phnum;      /* number of program header entries */
    Elf32_Half    e_shentsize;    /* section header entry size */
    Elf32_Half    e_shnum;      /* number of section header entries */
    Elf32_Half    e_shstrndx;    /* section header table's "section 
                       header string table" entry offset */
} Elf32_Ehdr;

typedef struct {
    unsigned char    e_ident[EI_NIDENT];    /* Id bytes */
    Elf64_Quarter    e_type;            /* file type */
    Elf64_Quarter    e_machine;        /* machine type */
    Elf64_Half       e_version;        /* version number */
    Elf64_Addr       e_entry;         /* entry point */
    Elf64_Off        e_phoff;         /* Program hdr offset */
    Elf64_Off        e_shoff;         /* Section hdr offset */
    Elf64_Half       e_flags;         /* Processor flags */
    Elf64_Quarter    e_ehsize;        /* sizeof ehdr */
    Elf64_Quarter    e_phentsize;     /* Program header entry size */
    Elf64_Quarter    e_phnum;         /* Number of program headers */
    Elf64_Quarter    e_shentsize;     /* Section header entry size */
    Elf64_Quarter    e_shnum;         /* Number of section headers */
    Elf64_Quarter    e_shstrndx;      /* String table index */
} Elf64_Ehdr;

以32位的头部为例说明:

  • e_ident[EI_NIDENT]:主要包括前四字节的魔数,之后记录处理器类型,32/64程序,大小端序等
  • e_type:记录文件类型,可执行文件/共享库/其他
  • e_machine:记录架构
  • e_version:目标文件版本,合法/非法
  • e_entry:记录虚拟内存中程序运行的起始地址
  • e_phoff:程序的段表偏移
  • e_shoff:程序的节区表偏移
  • e_flags:不清楚
  • e_ehsize:elf头部的大小
  • e_phentsize:段表的大小
  • e_phnum:段表里的元素个数
  • e_shentsize:节区表的大小
  • e_shnum:节区表的元素个数
  • e_shstrndx:节区表字符串索引

程序头表

这是一个数组,其中的每一个成员称之为一个程序头,是一个用来描述段结构的结构体。

一个段里包含一个或者多个节。

/* Program Header */
typedef struct {
    Elf32_Word    p_type;        /* segment type */
    Elf32_Off    p_offset;    /* segment offset */
    Elf32_Addr    p_vaddr;    /* virtual address of segment */
    Elf32_Addr    p_paddr;    /* physical address - ignored? */
    Elf32_Word    p_filesz;    /* number of bytes in file for seg. */
    Elf32_Word    p_memsz;    /* number of bytes in mem. for seg. */
    Elf32_Word    p_flags;    /* flags */
    Elf32_Word    p_align;    /* memory alignment */
} Elf32_Phdr;

typedef struct {
    Elf64_Half    p_type;        /* entry type */
    Elf64_Half    p_flags;    /* flags */
    Elf64_Off    p_offset;    /* offset */
    Elf64_Addr    p_vaddr;    /* virtual address */
    Elf64_Addr    p_paddr;    /* physical address */
    Elf64_Xword    p_filesz;    /* file size */
    Elf64_Xword    p_memsz;    /* memory size */
    Elf64_Xword    p_align;    /* memory & file alignment */
} Elf64_Phdr;
  • p_type:段的类型,以及需要如何被解析。等于不同的值有不同的作用,这里我只关注PT_LOAD即1
  • p_flags:段的对应权限,也就是读/写/执行这种。
  • p_offset:该段的偏移,也就是Elf文件的第一个字节到该段第一个字节的偏移
  • p_vaddr:该段被加载到的虚拟地址
  • p_paddr:物理地址,不关注
  • p_filesz:该段在文件映像中占的大小
  • p_memsz:该段在内存映像中占的大小
  • p_align:决定对齐,应该是一个正整数,并且是2的幂次数。p_vaddr 和 p_offset 在对 p_align 取模后应该相等,暂时先不考虑。
  • 按理说p_filesz应该等于p_memsz,为什么还要定义两个成员?
  • 因为段里的有些节区可能在目标文件中并不占空间,而在内存中才占空间,所以两个值可能会出现不相等,最常见的就是.bss节了,也就是未初始化的全局变量存放的位置。

最后

所以,综上,我需要做的工作总结如下:

  • 解析出e_entry,赋值给pc指针
  • 遍历程序表头,根据每个程序头的p_offsetp_vaddrp_memsz将指定位置制定大小的段映射到对应的虚拟内存上。
  • 虚拟内存还没明确怎么设计,准备参考linux的做法

有个问题需要注意一下,因为我发现用readelf查看ELF的程序头表的时候,发现第一个程序头的p_offset一般都是0,然后我用gdb随便加载一个elf看了下,原来ELF文件头也被加载到了虚拟内存中,这跟我原来的认知不太一样,原来一直以为文件头之后并不需要,估计是为了方便页对齐吧。

参考资料

ELF各结构体的源码:https://blog.csdn.net/xuehuafeiwu123/article/details/72963229

ELF 格式解析:https://paper.seebug.org/papers/Archive/refs/elf/Understanding_ELF.pdf


connect 1037178204@qq.com

文章标题:ELF文件在加载过程中需要关注的部分

本文作者:t1an5t

发布时间:2020-02-06, 18:01:20

最后更新:2020-02-29, 21:57:28

原始链接:http://yoursite.com/ELF%E6%96%87%E4%BB%B6%E5%9C%A8%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B%E4%B8%AD%E9%9C%80%E8%A6%81%E5%85%B3%E6%B3%A8%E7%9A%84%E9%83%A8%E5%88%86/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录