ELF文件格式
左图是链接试图、右图是执行视图。链接视图强调链接器对目标文件及其Section的拼接过程,比如ELF文件、ELF文件映射都是链接视图。执行视图强调在实际执行中的ELF组织形式。《SGX初始化中ELF文件解析》
注意这两者是有变化的,这是两套类似的副本,但前者是ELF文件,后者是实际执行的ELF副本,后者由前者提取组成。简单描述一下ELF程序加载的例子,我们首先将各种目标文件(包括可重定位文件、可执行文件、共享目标文件)拼接成ELF文件,由链接器完成。之后我们试图将ELF文件给跑起来(其中所包含的进程建立过程不做讲述,简单说就是会用到do_fork,进而使用clone或者fork)。
ELF文件中包含了许许多多的Section。下图就是一个ELF文件中的所有section,我这里使用了Swithless代码例子,Enclave.so,使用readelf读出来的内容。《Enclave文件布局信息》
There are 34 section headers, starting at offset 0x184d8:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 000002a8
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.build-i NOTE 00000000000002c4 000002c4
0000000000000024 0000000000000000 A 0 0 4
[ 3] .gnu.hash GNU_HASH 00000000000002e8 000002e8
0000000000000030 0000000000000000 A 4 0 8
[ 4] .dynsym DYNSYM 0000000000000318 00000318
0000000000000090 0000000000000018 A 5 1 8
[ 5] .dynstr STRTAB 00000000000003a8 000003a8
000000000000004d 0000000000000000 A 0 0 1
[ 6] .gnu.version VERSYM 00000000000003f6 000003f6
000000000000000c 0000000000000002 A 4 0 2
[ 7] .gnu.version_d VERDEF 0000000000000408 00000408
0000000000000038 0000000000000000 A 5 2 8
[ 8] .rela.dyn RELA 0000000000000440 00000440
0000000000000168 0000000000000018 A 4 0 8
[ 9] .plt PROGBITS 0000000000001000 00001000
0000000000000010 0000000000000010 AX 0 0 16
[10] .plt.got PROGBITS 0000000000001010 00001010
0000000000000010 0000000000000008 AX 0 0 8
[11] .text PROGBITS 0000000000001020 00001020
0000000000008600 0000000000000000 AX 0 0 16
[12] .nipx PROGBITS 0000000000009620 00009620
0000000000000a28 0000000000000000 AX 0 0 16
[13] .rodata PROGBITS 000000000000b000 0000b000
00000000000000e4 0000000000000000 A 0 0 32
[14] .niprod PROGBITS 000000000000b100 0000b100
00000000000008c0 0000000000000000 A 0 0 64
[15] .eh_frame_hdr PROGBITS 000000000000b9c0 0000b9c0
00000000000004ec 0000000000000000 A 0 0 4
[16] .eh_frame PROGBITS 000000000000beb0 0000beb0
0000000000001704 0000000000000000 A 0 0 8
[17] .fini_array FINI_ARRAY 000000000000ed80 0000dd80
0000000000000020 0000000000000008 WA 0 0 8
[18] .data.rel.ro PROGBITS 000000000000eda0 0000dda0
00000000000000b0 0000000000000000 WA 0 0 32
[19] .dynamic DYNAMIC 000000000000ee50 0000de50
0000000000000170 0000000000000010 WA 5 0 8
[20] .got PROGBITS 000000000000efc0 0000dfc0
0000000000000038 0000000000000008 WA 0 0 8
[21] .data PROGBITS 000000000000f000 0000e000
00000000000000a0 0000000000000000 WA 0 0 32
[22] .nipd PROGBITS 000000000000f0a0 0000e0a0
0000000000000008 0000000000000000 WA 0 0 4
[23] .bss NOBITS 000000000000f0c0 0000e0a8
00000000000006d0 0000000000000000 WA 0 0 32
[24] .comment PROGBITS 0000000000000000 0000e0a8
0000000000000024 0000000000000001 MS 0 0 1
[25] .note.sgxmeta NOTE 0000000000000000 0000e0cc
000000000000501c 0000000000000000 0 0 4
[26] .debug_aranges PROGBITS 0000000000000000 000130e8
0000000000000060 0000000000000000 0 0 1
[27] .debug_info PROGBITS 0000000000000000 00013148
0000000000000d8e 0000000000000000 0 0 1
[28] .debug_abbrev PROGBITS 0000000000000000 00013ed6
0000000000000265 0000000000000000 0 0 1
[29] .debug_line PROGBITS 0000000000000000 0001413b
0000000000000483 0000000000000000 0 0 1
[30] .debug_str PROGBITS 0000000000000000 000145be
0000000000000d65 0000000000000001 MS 0 0 1
[31] .symtab SYMTAB 0000000000000000 00015328
0000000000001da0 0000000000000018 32 311 8
[32] .strtab STRTAB 0000000000000000 000170c8
00000000000012ca 0000000000000000 0 0 1
[33] .shstrtab STRTAB 0000000000000000 00018392
0000000000000142 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
但是,只有其中一部分Section是在实际跑的过程中会用到的,它们所在的Segment类型常见的有PT_LOAD、PT_TLS等等,里面的内容比如是这个样子。
Elf 文件类型为 DYN (共享目标文件)
Entry point 0x9c90
There are 11 program headers, starting at offset 64
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x0000000000000268 0x0000000000000268 R 0x8
INTERP 0x00000000000002a8 0x00000000000002a8 0x00000000000002a8
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000005a8 0x00000000000005a8 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000009048 0x0000000000009048 R E 0x1000
LOAD 0x000000000000b000 0x000000000000b000 0x000000000000b000
0x00000000000025b4 0x00000000000025b4 R 0x1000
LOAD 0x000000000000dd80 0x000000000000ed80 0x000000000000ed80
0x0000000000000328 0x0000000000000a10 RW 0x1000
DYNAMIC 0x000000000000de50 0x000000000000ee50 0x000000000000ee50
0x0000000000000170 0x0000000000000170 RW 0x8
NOTE 0x00000000000002c4 0x00000000000002c4 0x00000000000002c4
0x0000000000000024 0x0000000000000024 R 0x4
GNU_EH_FRAME 0x000000000000b9c0 0x000000000000b9c0 0x000000000000b9c0
0x00000000000004ec 0x00000000000004ec R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x000000000000dd80 0x000000000000ed80 0x000000000000ed80
0x0000000000000280 0x0000000000000280 R 0x1
Section to Segment mapping:
段节...
00
01 .interp
02 .interp .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_d .rela.dyn
03 .plt .plt.got .text .nipx
04 .rodata .niprod .eh_frame_hdr .eh_frame
05 .fini_array .data.rel.ro .dynamic .got .data .nipd .bss
06 .dynamic
07 .note.gnu.build-id
08 .eh_frame_hdr
09
10 .fini_array .data.rel.ro .dynamic .got
我们可以看到不是所有的Section都被添加到Segment并被运行起来。比如symtab、strtab节就只存在于ELF文件中,在实际跑的过程中并不存在。而dynsym、dynstr节是存在于Segment,并在实际跑的过程中存在的。
symtab节包含了所有的符号,它的作用主要是为了链接、调试的时候我们能找到符号。而dynsym是symtab的子集,并且dynsym只保留了需要重定位的符号,因为需要重定位的符号只有加载运行的时候才能知道它们的具体位置。而其他不需要重定位的符号,dynsym一概不存,因为我们在汇编层面只会用到符号的虚拟地址,只要虚拟地址确定的、不需要重定位的,我们没必要存,同时节约内存空间。《动态链接库中的.symtab和.dynsym》
以enclave.signed.so为例子,Enclave文件映射保留了Enclave ELF文件的所有内容。而PT_LOAD类型的代码映射到了ELRANGE,其中包括.text、.data、.dynsym等等《Enclave虚拟内存视图》
回到刚才提的ELF加载的例子中,我们已经将ELF文件链接好了。
那么我们首先会打开ELF文件,把EFL文件映射到进程的地址空间。《ELF格式及加载过程简介》
紧接着检测ELF文件格式是否正常。
之后开辟一块匿名映射(匿名映射是指该映射并不指向某个具体文件)用作需要真正执行的代码的虚拟地址空间,然后将PT_LOAD等需要加载的类型加载到这个匿名映射空间,这个匿名映射空间就是真正会用到的代码的空间。
对于程序会用到的动态库,我们要用一个结构体soinfo记录下so的基本信息。并且完成动态库的加载,以及动态库中所有重定向符号的重定向。重定向的过程如图所示。
对所有的动态段(PT_DYNAMIC)中的每个动态节【ElfW(Dyn)】遍历,将其中的符号表偏移、重定位表偏移、重定位表大小、重定位表项大小、PLT重定位偏移、PLT大小进行记录,其中表的大小除以表项的大小就是表项的个数。然后遍历每一个重定位表项,每一个重定位表项指向一个虚拟空间地址(比如.text节中的某个数据符号、函数符号),要做的就是将符号表项、PLT项的实际位置都记录到重定位表项所指地址。这样以后当访问那个虚拟空间地址时候(比如.text节中的某个数据符号、函数符号),我们可以很方便的找到重定位的符号、PLT项的具体位置。这里也体现了dynsym中的重定位符号不能舍弃,但是symtab中的其他地址固定的符号可以舍弃《SGX的ELF文件映射配置重定位项》
ELF格式细节
ELF的布局大致如下图所示。
1. Headers
包括ELF头、程序头、节头。其中ELF头有信息可以指向程序头和节头。程序头可以指向程序项,节头可以指向节项。程序头是在ELF文件中是可选的(在实际执行的副本上是必须有的),往往一个Segment是包含多个Section的。
1.1. ELF头
readelf -h
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x9c90
程序头起点: 64 (bytes into file)
Start of section headers: 99544 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 11
Size of section headers: 64 (bytes)
Number of section headers: 34
Section header string table index: 33
结构体如下《Chapter 7 Object File Format》
//The ELF header has the following structure, defined in sys/elf.h:
#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;
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
1.2. Program头
程序头是在ELF文件中是可选的(在实际执行的副本上是必须有的),往往一个Segment是包含多个Section的。
//A program header has the following structure, defined in sys/elf.h:
typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
typedef struct {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
} Elf64_Phdr;
1.3. Section头
//A section header has the following structure, defined in sys/elf.h:
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;
typedef struct {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
Table 7-11 ELF Special Section Indexes
Name | Value |
---|---|
SHN_UNDEF | 0 |
SHN_LORESERVE | 0xff00 |
SHN_LOPROC | 0xff00 |
SHN_BEFORE | 0xff00 |
SHN_AFTER | 0xff01 |
SHN_HIPROC | 0xff1f |
SHN_ABS | 0xfff1 |
SHN_COMMON | 0xfff2 |
SHN_HIRESERVE | 0xffff |
2. Section中与重定位相关的节
enclave.signed.so所有的节可以参考前面的图。这里讲与重定位相关的节。
重定位过程的图这里再放一份。目前认为重定位过程是对ELF文件映射进行操作的过程。但最终可以帮助实际执行的ELF副本完成符号地址查找。
通过.dynamic节(存于PT_DYNAMIC的段中),我们可以查到所有的重定位节,比如.rela、.rel、.plt节,rela节相比于rel节多了个addend(常量补齐)。.dynamic节内容例子如下。
Dynamic section at offset 0xde50 contains 19 entries:
标记 类型 名称/值
0x000000000000001a (FINI_ARRAY) 0xed80
0x000000000000001c (FINI_ARRAYSZ) 32 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x2e8
0x0000000000000005 (STRTAB) 0x3a8
0x0000000000000006 (SYMTAB) 0x318
0x000000000000000a (STRSZ) 77 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0xefc0
0x0000000000000007 (RELA) 0x440
0x0000000000000008 (RELASZ) 360 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffc (VERDEF) 0x408
0x000000006ffffffd (VERDEFNUM) 2
0x0000000000000018 (BIND_NOW)
0x000000006ffffffb (FLAGS_1) 标志: NOW PIE
0x000000006ffffff0 (VERSYM) 0x3f6
0x000000006ffffff9 (RELACOUNT) 13
0x0000000000000000 (NULL) 0x0
重定位表内容例子如下
重定位节 '.rela.dyn' at offset 0x440 contains 15 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
00000000ed80 000000000008 R_X86_64_RELATIVE 1020
00000000ed88 000000000008 R_X86_64_RELATIVE 1030
00000000ed90 000000000008 R_X86_64_RELATIVE 1050
00000000ed98 000000000008 R_X86_64_RELATIVE 1070
00000000eda8 000000000008 R_X86_64_RELATIVE 1083
00000000edb8 000000000008 R_X86_64_RELATIVE 10e5
00000000edc8 000000000008 R_X86_64_RELATIVE 1114
00000000edd8 000000000008 R_X86_64_RELATIVE 1143
00000000ede8 000000000008 R_X86_64_RELATIVE 11ab
00000000efd8 000000000008 R_X86_64_RELATIVE 0
00000000efe8 000000000008 R_X86_64_RELATIVE 9f6b
00000000f030 000000000008 R_X86_64_RELATIVE f030
00000000f038 000000000008 R_X86_64_RELATIVE 8d20
00000000efe0 000100000006 R_X86_64_GLOB_DAT 0000000000000000 _Z9pcl_entryPvS_ + 0
00000000eff0 000200000006 R_X86_64_GLOB_DAT 0000000000000000 ippcpSetCpuFeatures + 0
符号表内容例子如下
Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Z9pcl_entryPvS_
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND ippcpSetCpuFeatures
3: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS enclave.so
4: 000000000000b100 1656 OBJECT GLOBAL DEFAULT 14 g_global_data@@enclave.so
5: 0000000000009c90 0 FUNC GLOBAL DEFAULT 12 enclave_entry@@enclave.so
Symbol table '.symtab' contains 316 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000000002a8 0 SECTION LOCAL DEFAULT 1
......
29: 0000000000000000 0 SECTION LOCAL DEFAULT 29
30: 0000000000000000 0 SECTION LOCAL DEFAULT 30
31: 0000000000000000 0 FILE LOCAL DEFAULT ABS Enclave_t.c
32: 0000000000001083 98 FUNC LOCAL DEFAULT 11 sgx_ecall_repeat_ocalls
......
310: 0000000000001571 194 FUNC LOCAL DEFAULT 11 sgx_thread_setwait_untrus
311: 0000000000009c90 0 FUNC GLOBAL DEFAULT 12 enclave_entry
312: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Z9pcl_entryPvS_
313: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS enclave.so
314: 000000000000b100 1656 OBJECT GLOBAL DEFAULT 14 g_global_data
315: 0000000000000000 0 NOTYPE WEAK DEFAULT UND ippcpSetCpuFeatures
重定位过程要做的事情就是对所有重定位表项关联的符号、地址进行重定位,注意不是所有的符号或地址。
这里再回顾一下重定位具体的做法。
对所有的动态段(PT_DYNAMIC)中的每个动态节【ElfW(Dyn)】遍历,将其中的符号表偏移、重定位表偏移、重定位表大小、重定位表项大小、PLT重定位偏移、PLT大小进行记录,其中表的大小除以表项的大小就是表项的个数。然后遍历每一个重定位表项,每一个重定位表项指向一个虚拟空间地址(比如.text节中的某个数据符号、函数符号),要做的就是将符号表项、PLT项的实际位置都记录到重定位表项所指地址。
最终的目的是,以后当访问那个虚拟空间地址时候(比如.text节中的某个数据符号、函数符号),我们可以很方便的找到重定位的符号、PLT项的具体位置。
SGX中Enclave ELF文件解析
SGX初始化一开始会对Enclave.so进行解析,并对解析结果进行保存,方便验证文件完整性,并方便后续Enclave文件映射中的可加载部分放到ELRANGE中去。《SGX初始化中ELF文件解析》
其中的过程包括了:
- 对ELF文件的头elf_hdr进行一个验证【validate_elf_header】
- 对EFI文件中每一个动态链接信息Segment(PT_DYNAMIC)中的每个动态项(dyn_entry,动态节),将这个动态项记录到m_dyn_info数组中,解析和保存的结果供后续使用。【parse_dyn】
- 检查是否存在未定义的符号symbol,此外,将一些关键的保留的符号存到ElfParser的类成员变量m_sym_table。【check_symbol_table】
- 检查之前parse_dyn扫描出来的动态节是否有任何不正确的重定位类型。【validate_reltabs】
- 通过查找.ctor节的Section Header,判断是否支持.ctor节(constructor缩写)。该节的存在,会导致全局初始化器无法正常被调用。【has_ctor_section】
- 将PT_LOAD、PT_TLS段记录下来,未来需要加载到ELRANGE【build_regular_sections】
SGX的.note.sgxmeta节
.note.sgxmeta节的布局 | 说明 |
namesz | name的大小 |
metadata size | metadata的大小 |
type | 类型 |
name | 存储了name |
metadata | 存储了metadata |
这里面存储了SGX的metadata元数据。
metadata里面包含了对Enclave文件各项细节的记录,比如Enclave虚拟大小、SECS属性(里面记录X特性支持情况)、签名结构体等等。《SGX中的X特性、SGX获取元数据》
typedef struct _metadata_t
{
uint64_t magic_num; /* The magic number identifying the file as a signed enclave image */
uint64_t version; /* The metadata version */
uint32_t size; /* The size of this structure */
uint32_t tcs_policy; /* TCS management policy */
uint32_t ssa_frame_size; /* The size of SSA frame in page */
uint32_t max_save_buffer_size; /* Max buffer size is 2632 */
uint32_t desired_misc_select;
uint32_t tcs_min_pool; /* TCS min pool*/
uint64_t enclave_size; /* enclave virtual size */
sgx_attributes_t attributes; /* XFeatureMask to be set in SECS. */
enclave_css_t enclave_css; /* The enclave signature */
data_directory_t dirs[DIR_NUM];
uint8_t data[18592];
}metadata_t;
SGX的ELF文件映射配置重定位项
基本就是前面讲的重定位过程。