Loading

6.828 lab1 ELF

ELF

ELF 是Executable and Linking Format的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的一部分。Unix/Linux下的可执行二进制文件、目标代码文件、共享库文件和core dump文件都属于ELF文件。

ELF文件分如下几类

  • 目标文件(可重定位文件)
    下图描述了ELF文件的大致布局
    avatar

左边是ELF的链接视图,可以理解为是目标代码文件的内容布局。右边是ELF的执行视图,可以理解为可执行文件的内容布局。
注意目标代码文件的内容是由section组成的,而可执行文件的内容是由segment组成的。

  • Segment 而文件载入内存执行时,是以segment组织的,每个segment对应ELF文件中program header table中的一个条目,用来建立可执行文件的进程映像。
  • Section我们写汇编程序时,用.text,.bss,.data这些指示,都指的是section,比如.text,告诉汇编器后面的代码放入.text section中。目标代码文件中的section和section header table中的条目是一一对应的。section的信息用于链接器对代码重定位。

为了彻底搞清楚ELF文件格式,我们需要搞清楚ELF Header ,program Header,和section Header

ELF header

readelf命令可以读出目标文件的ELF Header ,以下是kernel的ELF Header

#vaddr 虚拟地址 #paddr  实地址
Program Header:
    LOAD off    0x00001000 vaddr 0xf0100000 paddr 0x00100000 align 2**12
         filesz 0x00007dac memsz 0x00007dac flags r-x
    LOAD off    0x00009000 vaddr 0xf0108000 paddr 0x00108000 align 2**12
         filesz 0x0000b6a8 memsz 0x0000b6a8 flags rw-
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
         filesz 0x00000000 memsz 0x00000000 flags rwx
(base) yaokai@Denny:~/code/lab$ readelf -a  obj/kern/kernel
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2 s complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V #操作系统
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x10000c  #程序入口
  Start of program headers:          52 (bytes into file)
  Start of section headers:          86816 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         3
  Size of section headers:           40 (bytes)
  Number of section headers:         15
  Section header string table index: 14

在操作系统程序中也有相应的ELF定义,lab1代码中ELF定义如下,与readelf命令的结果一一对应。

struct Elf {
	uint32_t e_magic;	// must equal ELF_MAGIC
	uint8_t e_elf[12];
	uint16_t e_type;
	uint16_t e_machine;
	uint32_t e_version;
	uint32_t e_entry;
	uint32_t e_phoff;
	uint32_t e_shoff;
	uint32_t e_flags;
	uint16_t e_ehsize;
	uint16_t e_phentsize;
	uint16_t e_phnum;
	uint16_t e_shentsize;
	uint16_t e_shnum;
	uint16_t e_shstrndx;
};
  • e_magic :
  • e_elf[12]: 描述了ELF文件内容如何解码等信息
  • e_type:2字节,以下取值有意义
 ET_NONE, 0, No file type
 ET_REL, 1, Relocatable file(可重定位文件,通常是文件名以.o结尾,目标文件)
 ET_EXEC, 2, Executable file (可执行文件)
 ET_DYN, 3, Shared object file (动态库文件,你用gcc编译出的二进制往往也属于这种类型,惊讶吗?)
 ET_CORE, 4, Core file (core文件,是core dump生成的吧?)
 ET_NUM, 5,表示已经定义了5种文件类型
 ET_LOPROC, 0xff00, Processor-specific
 ET_HIPROC, 0xffff, Processor-specific
  • e_version 2字节,描述了ELF文件的版本号,合法取值如下
 EV_NONE, 0, Invalid version
 EV_CURRENT, 1, Current version,通常都是这个取值。
 EV_NUM, 2, 表示已经定义了2种版本号
  • e_entry ,(32位4字节,64位8字节),执行入口点,如果文件没有入口点,这个域保持0。
  • e_phoff, (32位4字节,64位8字节),program header table的offset,如果文件没有PH,这个值是0。
  • e_shoff, (32位4字节,64位8字节), section header table 的offset,如果文件没有SH,这个值是0。
  • e_flags, 4字节,特定于处理器的标志,32位和64位Intel架构都没有定义标志,因此eflags的值是0。
  • e_ehsize, 2字节,ELF header的大小,32位ELF是52字节,64位是64字节。
  • e_phentsize,2字节。program header table中每个入口的大小。
  • e_phnum, 2字节。如果文件没有program header table, e_phnum的值为0。- e_phentsize乘以e_phnum就得到了整个program header table的大小。
  • e_shentsize, 2字节,section header table中entry的大小,即每个section header占多少字节。
  • e_shnum, 2字节,section header table中header的数目。如果文件没有section header table, e_shnum的值为0。e_shentsize乘以e_shnum,就得到了整个section header table的大小。
  • e_shstrndx, 2字节。section header string table index. 包含了section header table中section name string table。如果没有section name string table, e_shstrndx的值是SHN_UNDEF.

Section Header

Section header table是一个header集合,每个节的Section Headers 中有相应Section的信息。lab1中的Section header定义及解释如下如下

struct Secthdr {
	uint32_t sh_name; //section名称
	uint32_t sh_type; //类型
	uint32_t sh_flags; //
	uint32_t sh_addr; //执行时section所在的虚拟地址
	uint32_t sh_offset; //文件内的偏移
	uint32_t sh_size;  //大小
	uint32_t sh_link;  
	uint32_t sh_info;  //额外信息
	uint32_t sh_addralign; //按几个字节对齐
	uint32_t sh_entsize; //每条记录占的字节数
};

  • sh_name,4字节,是一个索引值,在shstrtable(section header string table,包含section name的字符串表,也是一个section)中的索引。第二讲介绍ELF文件头时,里面专门有一个字段e_shstrndx,其含义就是shstrtable对应的section header在section header table中的索引。
  • sh_type,4字节,描述了section的类型,常见的取值如下:
    • SHT_NULL 0,表明section header无效,没有关联的section。
    • SHT_PROGBITS 1,section包含了程序需要的数据,格式和含义由程序解释。
    • SHT_SYMTAB 2, 包含了一个符号表。当前,一个ELF文件中只有一个符号表。SHT_SYMTAB提供了用于(link editor)链接编辑的符号,当然这些符号也可能用于动态链接。这是一个完全的符号表,它包含许多符号。
    • SHT_STRTAB 3,包含一个字符串表。一个对象文件包含多个字符串表,比如.strtab(包含符号的名字)和.shstrtab(包含section的名称)。
    • SHT_RELA 4,重定位节,包含relocation入口,参见Elf32_Rela。一个文件可能有多个Relocation Section。比如.rela.text,.rela.dyn。
    • SHT_HASH 5,这样的section包含一个符号hash表,参与动态连接的目标代码文件必须有一个hash表。目前一个ELF文件中只包含一个hash表。讲链接的时候再细讲。
    • SHT_DYNAMIC 6,包含动态链接的信息。目前一个ELF文件只有一个DYNAMIC section。
    • SHT_NOTE 7,note section, 以某种方式标记文件的信息,以后细讲。
    • SHT_NOBITS 8,这种section不含字节,也不占用文件空间,section header中的sh_offset字段只是概念上的偏移。
    • SHT_REL 9, 重定位节,包含重定位条目。和SHT_RELA基本相同,两者的区别在后面讲重定位的时候再细讲。
    • SHT_SHLIB 10,保留,语义未指定,包含这种类型的section的elf文件不符合ABI。
    • SHT_DYNSYM 11, 用于动态连接的符号表,推测是symbol table的子集。
    • SHT_LOPROC 0x70000000 到 SHT_HIPROC 0x7fffffff,为特定于处理器的语义保留。
    • SHT_LOUSER 0x80000000 and SHT_HIUSER 0xffffffff,指定了为应用程序保留的索引的下界和上界,这个范围内的索引可以被应用程序使用。
  • sh_flags, 32位占4字节, 64位占8字节。包含位标志,用 readelf -S 可以看到很多标志。常用的有:
    名称 代码 功能
    SHF_WRITE 0x1 进程执行的时候,section内的数据可写。
    SHF_ALLOC 0x2 进程执行的时候,section需要占据内存。
    SHF_EXECINSTR 0x4 节内包含可以执行的机器指令
    SHF_STRINGS 0x20 包含0结尾的字符串
    SHF_MASKOS 0x0ff00000 这个mask为OS特定的语义保留8位
    SHF_MASKPROC 0xf0000000 这个mask包含的所有位保留(也就是最高字节的高4位),为处理器相关的语义使用。
  • sh_addr, 对32位来说是4字节,64位是8字节。如果section会出现在进程的内存映像中,给出了section第一字节的虚拟地址。
  • sh_offset,对于32位来说是4字节,64位是8字节。section相对于文件头的字节偏移。对于不占文件空间的section(比如SHT_NOBITS),它的sh_offset只是给出了section逻辑上的位置。
  • sh_size,section占多少字节,对于SHT_NOBITS类型的section,sh_size没用,其值可能不为0,但它也不占文件空间。
  • sh_link,含有一个section header的index,该值的解释依赖于section type。如果是SHT_DYNAMIC,sh_link是string table的section header index,也就是说指向字符串表。如果是SHT_HASH,sh_link指向symbol table的section header index,hash table应用于symbol table。如果是重定位节SHT_REL或SHT_RELA,sh_link指向相应符号表的section header index。如果是SHT_SYMTAB或SHT_DYNSYM,sh_link指向相关联的符号表,暂时不解。对于其它的section type,sh_link的值是SHN_UNDEF。
  • sh_info,存放额外的信息,值的解释依赖于section type。如果是SHT_REL和SHT_RELA类型的重定位节,sh_info是应用relocation的节的节头索引。如果是SHT_SYMTAB和SHT_DYNSYM,sh_info是第一个non-local符号在符号表中的索引。推测local symbol在前面,non-local symbols紧跟在后面,所以文档中也说,sh_info是最后一个本地符号的在符号表中的索引加1。对于其它类型的section,sh_info是0。
  • sh_addralign,地址对齐,如果一个section有一个doubleword字段,系统在载入section时的内存地址必须是doubleword对齐。也就是说sh_addr必须是sh_addralign的整数倍。只有2的正整数幂是有效的。0和1说明没有对齐约束。
  • sh_entsize,有些section包含固定大小的记录,比如符号表。这个值给出了每个记录大小。对于不包含固定大小记录的section,这个值是0。

每一个ELF文件通常包含许多Seciton,文件中也就存在许多Section Header
使用readelf命令可以查看文件中所有的Section Header,

系统预定义了一些节名,这些节具有特殊的类型和含义

  • .bss:包含程序运行时未初始化的数据(全局变量和静态变量)。当程序运行时,这些数据初始化为0。 其类型为SHT_NOBITS,表示不占文件空间。SHF_ALLOC + SHF_WRITE,运行时要占用内存的。
  • .comment 包含版本控制信息(是否包含程序的注释信息?不包含,注释在预处理时已经被删除了)。类型为SHT_PROGBITS。
    .data和.data1,包含初始化的全局变量和静态变量。 类型为SHT_PROGBITS,标志为SHF_ALLOC + SHF_WRITE(占用内存,可写)。
  • .debug,包含了符号调试用的信息,我们要想用gdb等工具调试程序,需要该类型信息,类型为SHT_PROGBITS。
  • .dynamic,类型SHT_DYNAMIC,包含了动态链接的信息。标志SHF_ALLOC,是否包含SHF_WRITE和处理器有关。
  • .dynstr,SHT_STRTAB,包含了动态链接用的字符串,通常是和符号表中的符号关联的字符串。标志 SHF_ALLOC
  • .dynsym,类型SHT_DYNSYM,包含动态链接符号表, 标志SHF_ALLOC。
  • .fini,类型SHT_PROGBITS,程序正常结束时,要执行该section中的指令。标志SHF_ALLOC + SHF_EXECINSTR(占用内存可执行)。现在ELF还包含.fini_array section。
  • .got,类型SHT_PROGBITS,全局偏移表(global offset table),以后会重点讲。
  • .hash,类型SHT_HASH,包含符号hash表,以后细讲。标志SHF_ALLOC。
  • .init,SHT_PROGBITS,程序运行时,先执行该节中的代码。SHF_ALLOC + SHF_EXECINSTR,和.fini对应。现在ELF还包含.init_array section。
  • .interp,SHT_PROGBITS,该节内容是一个字符串,指定了程序解释器的路径名。如果文件中有一个可加载的segment包含该节,属性就包含SHF_ALLOC,否则不包含。
  • .line,SHT_PROGBITS,包含符号调试的行号信息,描述了源程序和机器代码的对应关系。gdb等调试器需要此信息。
  • .note Note Section, 类型SHT_NOTE,以后单独讲。
  • .plt 过程链接表(Procedure Linkage Table),类型SHT_PROGBITS,以后重点讲。
  • .relNAME,类型SHT_REL, 包含重定位信息。如果文件有一个可加载的segment包含该section,section属性将包含SHF_ALLOC,否则不包含。NAME,是应用重定位的节的名字,比如.text的重定位信息存储在.rel.text中。
  • .relaname 类型SHT_RELA,和.rel相同。SHT_RELA和SHT_REL的区别,会在讲重定位的时候说明。
  • .rodata和.rodata1。类型SHT_PROGBITS, 包含只读数据,组成不可写的段。标志SHF_ALLOC。
  • .shstrtab,类型SHT_STRTAB,包含section的名字。有读者可能会问:section header中不是已经包含名字了吗,为什么把名字集中存放在这里? sh_name 包含的是.shstrtab 中的索引,真正的字符串存储在.shstrtab中。那么section names为什么要集中存储?我想是这样:如果有相同的字符串,就可以共用一块存储空间。如果字符串存在包含关系,也可以共用一块存储空间。
  • .strtab SHT_STRTAB,包含字符串,通常是符号表中符号对应的变量名字。如果文件有一个可加载的segment包含该section,属性将包含SHF_ALLOC。字符串以\0结束, section以\0开始,也以\0结束。一个.strtab可以是空的,它的sh_size将是0。针对空字符串表的非0索引是允许的。
  • symtab,类型SHT_SYMTAB,Symbol Table,符号表。包含了定位、重定位符号定义和引用时需要的信息。符号表是一个数组,Index 0 第一个入口,它的含义是undefined symbol index, STN_UNDEF。如果文件有一个可加载的segment包含该section,属性将包含SHF_ALLOC。

\kernel\kernel文件的Section Headers 如下

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        f0100000 001000 001acd 00  AX  0   0 16
  [ 2] .rodata           PROGBITS        f0101ae0 002ae0 0006bc 00   A  0   0 32
  [ 3] .stab             PROGBITS        f010219c 00319c 004291 0c   A  4   0  4
  [ 4] .stabstr          STRTAB          f010642d 00742d 00197f 00   A  0   0  1
  [ 5] .data             PROGBITS        f0108000 009000 009300 00  WA  0   0 4096
  [ 6] .got              PROGBITS        f0111300 012300 000008 00  WA  0   0  4
  [ 7] .got.plt          PROGBITS        f0111308 012308 00000c 04  WA  0   0  4
  [ 8] .data.rel.local   PROGBITS        f0112000 013000 001000 00  WA  0   0 4096
  [ 9] .data.rel.ro.loca PROGBITS        f0113000 014000 000044 00  WA  0   0  4
  [10] .bss              PROGBITS        f0113060 014060 000648 00  WA  0   0 32
  [11] .comment          PROGBITS        00000000 0146a8 00002a 01  MS  0   0  1
  [12] .symtab           SYMTAB          00000000 0146d4 0007d0 10     13  66  4
  [13] .strtab           STRTAB          00000000 014ea4 0003fc 00      0   0  1
  [14] .shstrtab         STRTAB          00000000 0152a0 00007d 00      0   0  1

我们需要重点关注以下几个节

  • .text 程序的可执行指令
  • .rodata 只读数据
  • .data 数据部分保存程序的初始化数据,例如全局变量
posted @ 2021-02-03 11:20  冰下熔岩  阅读(154)  评论(0)    收藏  举报