ELF文件格式概述

ELF(Executable and linking format)文件格式是Linux系统下的一种常用目标文件(object file)格式,有三种主要类型:

  • 用于执行的可执行文件(executable file),用于提供程序的进程映像,加载的内存执行。 这也是本实验的OS文件类型。
  • 用于连接的可重定位文件(relocatable file),可与其它目标文件一起创建可执行文件和共享目标文件。
  • 共享目标文件(shared object file),连接器可将它与其它可重定位文件和共享目标文件连接成其它的目标文件,动态连接器又可将它与可执行文件和其它共享目标文件结合起来创建一个进程映像。

这里只分析与本实验相关的ELF可执行文件类型。ELF header在文件开始处描述了整个文件的组织。ELF的文件头包含整个执行文件的控制结构,其定义在elf.h中:

  1. struct elfhdr {
  2. uint magic; // must equal ELF_MAGIC
  3. uchar elf[12];
  4. ushort type;
  5. ushort machine;
  6. uint version;
  7. uint entry; // 程序入口的虚拟地址
  8. uint phoff; // program header 表的位置偏移
  9. uint shoff;
  10. uint flags;
  11. ushort ehsize;
  12. ushort phentsize;
  13. ushort phnum; //program header表中的入口数目
  14. ushort shentsize;
  15. ushort shnum;
  16. ushort shstrndx;
  17. };

program header描述与程序执行直接相关的目标文件结构信息,用来在文件中定位各个段的映像,同时包含其他一些用来为程序创建进程映像所必需的信息。可执行文件的程序头部是一个program header结构的数组, 每个结构描述了一个段或者系统准备程序执行所必需的其它信息。目标文件的 “段” 包含一个或者多个 “节区”(section) ,也就是“段内容(Segment Contents)” 。程序头部仅对于可执行文件和共享目标文件有意义。可执行目标文件在ELF头部的e_phentsize和e_phnum成员中给出其自身程序头部的大小。程序头部的数据结构如下表所示:

  1. struct proghdr {
  2. uint type; // 段类型
  3. uint offset; // 段相对文件头的偏移值
  4. uint va; // 段的第一个字节将被放到内存中的虚拟地址
  5. uint pa;
  6. uint filesz;
  7. uint memsz; // 段在内存映像中占用的字节数
  8. uint flags;
  9. uint align;
  10. };

根据elfhdr和proghdr的结构描述,bootloader就可以完成对ELF格式的ucore操作系统的加载过程(参见boot/bootmain.c中的bootmain函数)。

[补充材料]

Link addr& Load addr

Link Address是指编译器指定代码和数据所需要放置的内存地址,由链接器配置。Load Address是指程序被实际加载到内存的位置(由程序加载器ld配置)。一般由可执行文件结构信息和加载器可保证这两个地址相同。Link Addr和LoadAddr不同会导致:

  • 直接跳转位置错误
  • 直接内存访问(只读数据区或bss等直接地址访问)错误
  • 堆和栈等的使用不受影响,但是可能会覆盖程序、数据区域
    注意:也存在Link地址和Load地址不一样的情况(例如:动态链接库)。