ELF: Executable and Linking Format part1

OBJECT FILES

preface

文件格式分析可以借助具体例子学习,用010editor的ELF Template加载任意一个ELF文件

可以通过010editor分析出的文件格式找到对应的二进制数据。

File Format


ELF header描述了文件的组织结构。
Sections保存了目标文件Linking View的大量信息:指令、数据、符号表和重定位信息等。
Program header table告诉系统如何创建一个进程映像。
Section header table包含了描述文件sections的信息。每一个section在表中都有一个条目,每一个条目包含了section name、section size等信息。

Data Representation

ELF Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define EI_NIDENT 16

typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;

e_ident ELF文件的magic
e_type 标识目标文件的类型。

ET_REL 1 Relocatable file
ET_EXEC 2 Executable file
ET_DYN 3 Shared object file

e_machine 文件运行在哪种体系结构上。
e_entry 程序入口点。
e_phoff program header table的文件偏移。
e_shoff section header table的文件偏移。
e_flags 处理器相关标识。
e_ehsize ELF header的大小。
e_phentsize 文件的program header table一个条目的大小,所有条目一样大小。
e_phnum program header table中条目的个数。
e_shentsize section header table中一个section header的大小,所有section header一样大小。
e_shnum section header table中条目的个数。
e_shstrndx section header table中和section名字字符串表相关的条目的索引。

Sections

Section Header

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;

sh_name 指向section header string table section的索引,表示section的名字。
sh_type 指明了section的内容和语义。
sh_flags 描述section的各项属性。
sh_addr 如果section将会出现在一个进程的内存映像中,这个成员给出section的起始地址。
sh_offset section在文件中的偏移地址。
sh_size section的大小。类型为SHT_NOBITS的section的size不为0,但它实际不占文件空间。
sh_link section header table index link。
sh_info 额外的信息,含义取决于section的类型。
sh_addralign 有些section有地址对齐方面的约束。比如 section 中有个 doubleword 型的数据,系统就必须保证整个 section 是 doubleword 对齐的,即 sh_addr 对 sh_addralign 取模为 0。目前,此成员的取值只允许是 0 和 2 的幂数。取值 0 和 1 代表 section 没有对齐方面的约束。
sh_entsize 有些section存放的表项大小固定的表,如符号表。如表项大小不固定,此成员为0。

Section Types, sh_type
SHT_NULL 表示此头部是不活动的,不对应一个section。
SHT_PROGBITS 存放着程序定义的信息,格式和含义只由程序决定。
SHT_SYMTAB & SHT_DYNSYM 保存着符号表。
SHT_STRTAB 保存着字符串表。
SHT_RELA 通过显式加数存放重定位条目。
SHT_HASH 存放一个符号哈希表。
SHT_DYNAMIC 存放动态链接的相关信息。
SHT_NOTE 存放文件的注释信息。
SHT_NOBITS 不占用文件的空间,其他方面类似于SHT_PROGBITS。
SHT_REL 存放没有显式加数的重定位条目。
SHT_SHLIB 保留section。
SHT_LOPROC through SHT_HIPROC
SHT_LOUSER 留给程序使用的保留索引值的下限。
SHT_HIUSER 留给程序实用的保留索引值的上限。

Section Attribute Flags, sh_flags
SHF_WRITE section包含在进程执行期间可写的数据。
SHF_ALLOC section在进程执行期间占据内存。一些section不在目标文件的内存映像中,这个属性就是off。
SHF_EXECINSTR section包含可执行的机器指令。
SHF_MASKPROC 这个掩码中的所有比特位留作处理器相关的语义。

Special Sections

.bss 保存程序内存映像中未初始化的数据。程序开始时,系统把这些数据初始化为0。
.comment 保存版本控制信息。
.data and .datal 保存程序内存映像中已经初始化的数据。
.debug 保存符号的调试信息。
.dynamic 保存动态链接信息。
.dynstr 保存动态链接需要的字符串。
.dynsym 保存动态链接符号表。
.fini 保存进程退出代码所需执行的指令。
.got 保存全局偏移表。
.hash 保存符号哈希表。
.init 保存程序初始化代码所执行的指令。在main之前调用。
.interp 保存程序解释器的路径名。
.line 保存符号调试用到的行号。
.note 按照一定格式保存注释信息。
.plt 保存过程链接表。
.relname and .relaname 保存重定位的相关信息。
.rodata and .rodatal 保存进程映像中不可写segment里的只读数据。
.shstrtab 保存section的名字。
.strtab 保存字符串表,这些字符串通常代表符号表中表项的名字。
.symtab 保存一个符号表。
.text 保存了程序的执行指令。
名字中包含.前缀的section是系统保留的。

String Table

字符串表section保存着一些空字节结尾的字符串。
目标文件用symbol string table(.strtab)的字符串表示symbol的名字,用section header string table(.shstrtab这个table是由ELF头部中的e_shstrndx成员指定)的字符串表示section的名字(section header的sh_name成员保存的索引)。可以通过索引引用字符串表中的字符串。

Symbol Table

目标文件符号表保存着定位和重定位一个程序的符号定义和引用所需的信息。
符号表表项的数据结构:

1
2
3
4
5
6
7
8
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;

st_name 这个成员保存了一个指向目标文件symbol string table的索引。
st_value 符号的值,取决于上下文,可能是一个绝对值,一个地址等等。
st_size 许多符号有相应的大小,例如一个数据对象的大小就是它所包含的字节数。
st_info 指明了符号的类型和约束属性。

1
2
3
#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b, t) (((b)<<4)+((t)&0xf))

st_other 目前为0,没有定义含义。
st_shndx 每个符号表项都与一些section有关,这个成员保存着相关section header table的索引。
一个符号的约束决定了链接的可见性和行为。

Symbol Binding, ELF32_ST_BIND

Name Value
STB_LOCAL 0
STB_GLOBAL 1
STB_WEAK 2
STB_LOPROC 13
STB_HIPROC 15

STB_LOCAL Local符号在包含它们的目标文件之外是不可见的。相同名字的Local符号可以在不同的文件中存在,而不会相互冲突。
STB_GLOBAL Global符号对要链接到一起的所有目标文件都是可见的。如果一个文件中定义了一个Global符号,那么另一个文件中无需定义,可直接引用此Global符号。
STB_WEAK Weak符号类似于Global符号,只是它约束的定义优先级较低。
STB_LOPROCSTB_HIPROC 此范围内的取值留作处理器相关的语义。

Symbol Types, ELF32_ST_TYPE

Name Value
STT_NOTYPE 0
STT_OBJECT 1
STT_FUNC 2
STT_SECTION 3
STT_FILE 4
STT_LOPROC 13
STT_HIPROC 15

STT_NOTYPE 未指定符号类型。
STT_OBJECT 符号对应一个数据对象,比如变量、数组等。
STT_FUNC 符号对应一个函数或其它可执行的代码。
STT_SECTION 符号对应一个section。这种类型的符号表项位于符号表最前面,用于重定位,通常具有STB_LOCAL约束。
STT_FILE 此符号的名字也就是目标文件对应源文件的名字。
STT_LOPROCSTT_HIPROC 此范围间的取值留作处理器相关的语义。

Symbol Values
符号表项在不同的目标文件类型中对st_value成员的解释稍有不同。
1)可重定位文件:如果符号对应section的索引是SHN_COMMON,那么st_value保存的是符号的对齐约束条件。
2)可重定位文件:如果是一个已经定义的符号,st_value保存着该符号在对应section中的偏移,即从st_shndx标识的section开始处的一个偏移量。
3)可执行目标文件和共享目标文件:为了使文件中的符号对动态链接器来说更加有用,st_value保存着一个虚拟地址。section偏移(文件解释)让位给虚拟地址(内存解释),因为在这种情况下,section偏移计数已经不重要了。
除了上面提到的,符号表的取值含义对不同的目标文件来说是类似的,程序也就能够采用高效的方法来访问数据。

Relocation

重定位是将符号引用和符号定义链接到一起的过程。比如,当一个程序调用一个函数,相关的调用指令在执行时必须把控制权传递到正确的目的地址。换句话说,就是可重定位文件必须包含一些信息,用来描述怎样修改它们的section内容,从而使可执行目标文件和共享目标文件掌握正确的信息用于创建进程的程序映像。重定位表项的数据结构如下所示:

1
2
3
4
5
6
7
8
9
10
typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;

typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;

r_offset 指出重定位操作的位置,对于可重定位文件,它的值是从section的开始处到重定位作用的存储单元的字节偏移量。对于可执行目标文件或共享目标文件,它的值是重定位作用的存储单元的虚拟地址。
r_info 指出重定位作用的符号在符号表中的索引和重定位的类型。比如,一个调用指令的重定位表项将保存被调用函数的符号表索引,如果索引是 STN_UNDEF,即未定义的符号表索引,那么重定位使用 0 作为该符号的值。重定位的类型是处理器相关的。当代码访问重定位表项的重定位类型和符号表索引时,需要将ELF32_R_TYPE宏和 ELF32_R_SYM宏分别作用于表项的r_info成员。

1
2
3
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t))

r_addend 指定一个常量加数(addend),用于计算可重定位字段存储的值。