在Linux系统中,一个ELF文件主要用来表示3种类型的文件:
可执行文件。操作系统的加载器从硬盘上读取,载入内存中执行;
目标文件(*.o)。链接器读取,用来生成一个可执行文件或者共享库文件;
共享文件(*.so)。在动态链接的时候,由ld-linux.so来读取;
每个ELF文件可以拆成四部分:
1 2 3 4 5 6 7 8 9 --------------------- ELF Header --------------------- Program Header Table --------------------- Sections --------------------- Section Header Table ---------------------
一个Segment可能包含一个或者多个Sections
ELF的描述 定义头文件在include/uapi/linux/elf.h下。
描述ELF Header的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #define EI_NIDENT 16 typedef struct elf32_hdr { 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 elf64_hdr { 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;
描述Program Header Table的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 typedef struct elf32_phdr { 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_phdr { 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;
描述Section Header Table的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 typedef struct elf32_shdr { 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_shdr { 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;
ELF文件头部内容,决定了这个完整的ELF文件的所有信息:
标志这是个ELF文件
版本、文件类型、机器类型等一些基本信息
Program Header Table的开始地址,在整个文件的什么地方
Section Header Table的开始地址,在整个文件的什么地方
一个ELF文件中,存在多个Sections,这些Sections的具体信息,实在Program Header Table或者Section Header Table中进行描述的。如Section Header Table:
假如一个ELF文件中一共存在4个Section:.text、.rodata、.data、.bss,那么在 section header table中。将会有4个Entry来分别描述这4个Section的具体信息(严格来说不止4个Entry,还会有其他的辅助Sections)
具体代码示例 mymath.c:
1 2 3 4 int my_add (int a, int b) { return a + b; }
main.c:
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> extern int my_add (int a, int b) ;int main () { int i = 1 ; int j = 2 ; int k = my_add(i, j); printf ("k = %d \n" , k); }
编译:
1 2 3 4 5 6 7 8 gcc mymath.c --shared -fPIC -o libmymath.so -fPIC参数声明链接库的代码段是可以共享的 -shared参数声明编译为共享库。 gcc main.c -o main -L. -lmymath -L参数指定到哪个附加路径下面去寻找共享库,现在我们指定在当前目录下面寻找; -l参数指定链接的共享库名 -I参数是指明头文件路径
编译出可执行文件main。使用readelf -h main来看一下ELF header的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Position-Independent Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x1050 Start of program headers: 64 (bytes into file) Start of section headers: 18208 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 13 Size of section headers: 64 (bytes) Number of section headers: 37 Section header string table index: 36
这个显示的就是ELF Header描述的所有内容。这个内容与结构体的成员变量是一一对应的。
这个头信息中有显示:Size of this header: 64 (bytes),也就是说ELF Header部分的内容,一共是52字节。
使用od -Ax -t x1 -N 64 main指令来读取main中的字节码
-Ax ,显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;
-t x1,显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);
-N 64,只需要读取 64 个字节;
1 2 3 4 5 000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 000010 03 00 3e 00 01 00 00 00 50 10 00 00 00 00 00 00 000020 40 00 00 00 00 00 00 00 20 47 00 00 00 00 00 00 000030 00 00 00 00 40 00 38 00 0d 00 40 00 25 00 24 00 000040
在结构体中的第一个成员是 unsigned char e_ident[EI_NIDENT];,EI_NIDENT 的长度是 16,代表了 EL header 中的开始 16 个字节,具体含义如下:
0~16字节 :
官方文档如下:
16~31字节 :
03 00:e_type表示ELF的文件类型,0x0003表示 DYN (Position-Independent Executable file)。
可取的类型如下:
1 2 3 4 5 6 7 8 #define ET_NONE 0 #define ET_REL 1 #define ET_EXEC 2 #define ET_DYN 3 #define ET_CORE 4 #define ET_LOPROC 0xff00 #define ET_HIPROC 0xffff
3e 00:e_machine表示目标体系结构类型。0x003e表示EM_X86_64
可取体系类型r如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #define EM_NONE 0 #define EM_M32 1 #define EM_SPARC 2 #define EM_386 3 #define EM_68K 4 #define EM_88K 5 #define EM_486 6 #define EM_860 7 #define EM_MIPS 8 #define EM_MIPS_RS3_LE 10 #define EM_MIPS_RS4_BE 10 #define EM_PARISC 15 #define EM_SPARC32PLUS 18 #define EM_PPC 20 #define EM_PPC64 21 #define EM_SPU 23 #define EM_ARM 40 #define EM_SH 42 #define EM_SPARCV9 43 #define EM_H8_300 46 #define EM_IA_64 50 #define EM_X86_64 62 #define EM_S390 22 #define EM_CRIS 76 #define EM_M32R 88 #define EM_MN10300 89 #define EM_OPENRISC 92 #define EM_ARCOMPACT 93 #define EM_XTENSA 94 #define EM_BLACKFIN 106 #define EM_UNICORE 110 #define EM_ALTERA_NIOS2 113 #define EM_TI_C6000 140 #define EM_HEXAGON 164 #define EM_NDS32 167 #define EM_AARCH64 183 #define EM_TILEPRO 188 #define EM_MICROBLAZE 189 #define EM_TILEGX 191 #define EM_ARCV2 195 #define EM_RISCV 243 #define EM_BPF 247 #define EM_CSKY 252 #define EM_FRV 0x5441 #define EM_ALPHA 0x9026 #define EM_CYGNUS_M32R 0x9041 #define EM_S390_OLD 0xA390 #define EM_CYGNUS_MN10300 0xbeef
32~48字节 :
40 00 00 00 00 00 00 00:e_phoff,Program head table在ELF文件中的偏移量。0x40就是说从第64字节开始。就是从程序头表开始位置。
20 47 00 00 00 00 00 00:e_shoff,Section Header Table在ELF文件中的偏移量。0x4720表示从18208字节开始,就是节头表的开始位置。
48~64字节 :
00 00 00 00:e_flags,保存与文件相关,处理器相关标志
40 00:e_ehsize,ELF Header数据的长度,64字节。
38 00:e_phentsize,Program head table中每个表项entry的长度,0x38为56字节
0d 00:e_phnum,Program head table中有多少个表项entry,0x0d表示一共有13个表项
40 00:e_shentsize,Section header table中每个表项entry的长度,0x40为64字节
25 00:e_shnum,Section header table中有多少个表项entry,0x25表示一共有37个表项
24 00:e_shstrndx,字符串表entry在节区表中的索引,0x24表示第36个entry描述的字符串表这个section
字符串表表项 Entry 在一个 ELF 文件中,存在很多字符串,例如:变量名、Section名称、链接器加入的符号等等,这些字符串的长度都是不固定的,因此用一个固定的结构来表示这些字符串,肯定是不现实的。于是就把这些字符串集中起来,统一放在一起,作为一个独立的 Section 来进行管理。
在文件中的其他地方呢,如果想表示一个字符串,就在这个地方写一个数字索引:表示这个字符串位于字符串统一存储地方的某个偏移位置,经过这样的按图索骥,就可以找到这个具体的字符串了。
如下面这个空间中存储了所有的字符串:
在程序的其他地方,如果想引用字符串 “hello,world!”,那么就只需要在那个地方标明数字 13 就可以了,表示:这个字符串从偏移 13 个字节处开始。
使用指令readelf -S main查看ELF文件中所有的Section信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 There are 37 section headers, starting at offset 0x4720: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000000318 00000318 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.gnu.pr[...] NOTE 0000000000000338 00000338 0000000000000040 0000000000000000 A 0 0 8 [ 3] .note.gnu.bu[...] NOTE 0000000000000378 00000378 0000000000000024 0000000000000000 A 0 0 4 [ 4] .note.ABI-tag NOTE 000000000000039c 0000039c 0000000000000020 0000000000000000 A 0 0 4 [ 5] .gnu.hash GNU_HASH 00000000000003c0 000003c0 000000000000001c 0000000000000000 A 6 0 8 [ 6] .dynsym DYNSYM 00000000000003e0 000003e0 00000000000000c0 0000000000000018 A 7 1 8 [ 7] .dynstr STRTAB 00000000000004a0 000004a0 00000000000000a3 0000000000000000 A 0 0 1 [ 8] .gnu.version VERSYM 0000000000000544 00000544 0000000000000010 0000000000000002 A 6 0 2 [ 9] .gnu.version_r VERNEED 0000000000000558 00000558 0000000000000030 0000000000000000 A 7 1 8 [10] .rela.dyn RELA 0000000000000588 00000588 00000000000000c0 0000000000000018 A 6 0 8 [11] .rela.plt RELA 0000000000000648 00000648 0000000000000030 0000000000000018 AI 6 23 8 [12] .init PROGBITS 0000000000001000 00001000 000000000000001b 0000000000000000 AX 0 0 4 [13] .plt PROGBITS 0000000000001020 00001020 0000000000000030 0000000000000010 AX 0 0 16 [14] .text PROGBITS 0000000000001050 00001050 0000000000000141 0000000000000000 AX 0 0 16 [15] .fini PROGBITS 0000000000001194 00001194 000000000000000d 0000000000000000 AX 0 0 4 [16] .rodata PROGBITS 0000000000002000 00002000 000000000000000d 0000000000000000 A 0 0 4 [17] .eh_frame_hdr PROGBITS 0000000000002010 00002010 0000000000000024 0000000000000000 A 0 0 4 [18] .eh_frame PROGBITS 0000000000002038 00002038 000000000000007c 0000000000000000 A 0 0 8 [19] .init_array INIT_ARRAY 0000000000003dd8 00002dd8 0000000000000008 0000000000000008 WA 0 0 8 [20] .fini_array FINI_ARRAY 0000000000003de0 00002de0 0000000000000008 0000000000000008 WA 0 0 8 [21] .dynamic DYNAMIC 0000000000003de8 00002de8 00000000000001f0 0000000000000010 WA 7 0 8 [22] .got PROGBITS 0000000000003fd8 00002fd8 0000000000000028 0000000000000008 WA 0 0 8 [23] .got.plt PROGBITS 0000000000004000 00003000 0000000000000028 0000000000000008 WA 0 0 8 [24] .data PROGBITS 0000000000004028 00003028 0000000000000010 0000000000000000 WA 0 0 8 [25] .bss NOBITS 0000000000004038 00003038 0000000000000008 0000000000000000 WA 0 0 1 [26] .comment PROGBITS 0000000000000000 00003038 0000000000000012 0000000000000001 MS 0 0 1 [27] .debug_aranges PROGBITS 0000000000000000 00003050 00000000000000f0 0000000000000000 0 0 16 [28] .debug_info PROGBITS 0000000000000000 00003140 0000000000000585 0000000000000000 0 0 1 [29] .debug_abbrev PROGBITS 0000000000000000 000036c5 0000000000000198 0000000000000000 0 0 1 [30] .debug_line PROGBITS 0000000000000000 0000385d 00000000000001da 0000000000000000 0 0 1 [31] .debug_str PROGBITS 0000000000000000 00003a37 0000000000000471 0000000000000001 MS 0 0 1 [32] .debug_line_str PROGBITS 0000000000000000 00003ea8 000000000000013e 0000000000000001 MS 0 0 1 [33] .debug_rnglists PROGBITS 0000000000000000 00003fe6 0000000000000042 0000000000000000 0 0 1 [34] .symtab SYMTAB 0000000000000000 00004028 0000000000000390 0000000000000018 35 19 8 [35] .strtab STRTAB 0000000000000000 000043b8 00000000000001ed 0000000000000000 0 0 1 [36] .shstrtab STRTAB 0000000000000000 000045a5 0000000000000176 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execut0000000000000176 0000000000000000 0 0 1e), 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), D (mbind), l (large), p (processor specific)
第36个Section描述的就是字符串表Section:[36] .shstrtab STRTAB 0000000000000000 000045a5 0000000000000176 0000000000000000 0 0 1
可以看出这个Section在ELF文件中的偏移地址是000045a5,长度是0000000000000176个字节
读取字符串表Section的内容 要想打印字符串表 Section 的内容,就必须知道这个 Section 在 ELF 文件中的偏移地址。
要想知道偏移地址,只能从 Section head table 中第 36 个表项描述信息中获取。
要想知道第 28 个表项的地址,就必须知道 Section head table 在 ELF 文件中的开始地址,以及每一个表项的大小
readelf中Start of section headers: 18208 (bytes into file)可知Section head table 的开始地址位于 ELF 文件的第 18208 个字节处。每个表项的长度是64字节。第28个表项的开始地址是:18208+36*64=20512,也就是说用来描述字符串表这个 Section 的表项,位于 ELF 文件的 20512 字节的位置。
执行指令:od -Ad -t x1 -j 20512 -N 64 main。
其中的 -j 20512 选项,表示跳过前面的 20512 个字节,也就是我们从 main 这个 ELF 文件的 20512 字节处开始读取,一共读 64 个字节。
1 2 3 4 5 0020512 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 0020528 00 00 00 00 00 00 00 00 a5 45 00 00 00 00 00 00 0020544 76 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0020560 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0020576
这64字节的内容,就对应了Elf64_Shdr结构体中的每个成员变量
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct elf64_shdr { 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;
sh_name: Section的名称;
sh_type:表示这个 Section 的类型,3 表示这是一个 string table;
sh_offset: 表示这个 Section,在 ELF 文件中的偏移量。0x000045a5 = 17829,意思是字符串表这个 Section 的内容,从 ELF 文件的 17829 个字节处开始;
sh_size:表示这个 Section 的长度。0x00000176 = 374 个字节,意思是字符串表这个 Section 的内容,一共有 374 个字节。
与使用readelf -S main读取出来完全一致。
执行od -Ad -t c -j 17829 -N 374 main即可看到存储的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0017829 \0 . s y m t a b \0 . s t r t a b 0017845 \0 . s h s t r t a b \0 . i n t e 0017861 r p \0 . n o t e . g n u . p r o 0017877 p e r t y \0 . n o t e . g n u . 0017893 b u i l d - i d \0 . n o t e . A 0017909 B I - t a g \0 . g n u . h a s h 0017925 \0 . d y n s y m \0 . d y n s t r 0017941 \0 . g n u . v e r s i o n \0 . g 0017957 n u . v e r s i o n _ r \0 . r e 0017973 l a . d y n \0 . r e l a . p l t 0017989 \0 . i n i t \0 . t e x t \0 . f i 0018005 n i \0 . r o d a t a \0 . e h _ f 0018021 r a m e _ h d r \0 . e h _ f r a 0018037 m e \0 . i n i t _ a r r a y \0 . 0018053 f i n i _ a r r a y \0 . d y n a 0018069 m i c \0 . g o t \0 . g o t . p l 0018085 t \0 . d a t a \0 . b s s \0 . c o 0018101 m m e n t \0 . d e b u g _ a r a 0018117 n g e s \0 . d e b u g _ i n f o 0018133 \0 . d e b u g _ a b b r e v \0 . 0018149 d e b u g _ l i n e \0 . d e b u 0018165 g _ s t r \0 . d e b u g _ l i n 0018181 e _ s t r \0 . d e b u g _ r n g 0018197 l i s t s \0 0018203
sh_name字段是以字符存储,直接存储在这里,索引是0x0011,第17个字节存储的字符为.shstrta
读取代码段的内容 从readelf -S main的输出中可以看到代码段是位于第14个表项中,加载的虚拟地址是0x0000000000001050,它位于 ELF 文件中的偏移量是 0x00001050,长度是 0x0000000000000141 个字节。
计算这个表项 Entry 的地址:18208+14*64=19104
读取这个表项 Entry,读取指令是 od -Ad -t x1 -j 19104 -N 64 main:
1 2 3 4 5 0019104 a7 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00 0019120 50 10 00 00 00 00 00 00 50 10 00 00 00 00 00 00 0019136 41 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0019152 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0019168
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct elf64_shdr { 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;
sh_name: 表示代码段的名称在字符串表 Section 中的偏移位置。0xa7 = 167 字节,也就是在字符串表 Section 的第 167 字节处,存储的就是代码段的名字。回过头去找一下,看一下是不是字符串 “.text”;
sh_type:表示这个 Section 的类型,1(SHT_PROGBITS) 表示这是代码;
sh_addr:表示这个 Section 加载的虚拟地址是 0x1050,这个值与 ELF header 中的 e_entry 字段的值是相同的;
sh_offset: 表示这个 Section,在 ELF 文件中的偏移量。0x1050 = 4176,意思是这个 Section 的内容,从 ELF 文件的 4176 个字节处开始;
sh_size:表示这个 Section 的长度。0x0141 = 321 个字节,意思是代码段一共有 321 个字节。
与指令 readelf -S main 读取出来的完全一样!
执行readelf -l main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 Elf file type is DYN (Position-Independent Executable file) Entry point 0x1050 There are 13 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8 R 0x8 INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000678 0x0000000000000678 R 0x1000 LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x00000000000001a1 0x00000000000001a1 R E 0x1000 LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x00000000000000b4 0x00000000000000b4 R 0x1000 LOAD 0x0000000000002dd8 0x0000000000003dd8 0x0000000000003dd8 0x0000000000000260 0x0000000000000268 RW 0x1000 DYNAMIC 0x0000000000002de8 0x0000000000003de8 0x0000000000003de8 0x00000000000001f0 0x00000000000001f0 RW 0x8 NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000040 0x0000000000000040 R 0x8 NOTE 0x0000000000000378 0x0000000000000378 0x0000000000000378 0x0000000000000044 0x0000000000000044 R 0x4 GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000040 0x0000000000000040 R 0x8 GNU_EH_FRAME 0x0000000000002010 0x0000000000002010 0x0000000000002010 0x0000000000000024 0x0000000000000024 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000002dd8 0x0000000000003dd8 0x0000000000003dd8 0x0000000000000228 0x0000000000000228 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .got.plt .data .bss 06 .dynamic 07 .note.gnu.property 08 .note.gnu.build-id .note.ABI-tag 09 .note.gnu.property 10 .eh_frame_hdr 11 12 .init_array .fini_array .dynamic .got
显示信息:
这是一个DYN (Position-Independent Executable file)文件
入口地址是0x1050
一共有13个program headers,是从ELF文件的第64个偏移地址开始的。
Section 与 Segment 本质上是一样的,可以理解为:一个 Secgment 由一个或多个 Sections 组成。还可以看到有两个LOAD类型的段
读取二进制字节码:
计算段表项的地址信息,从ELF header中得知的信息如下:
e_phoff:Program header table 位于 ELF 文件偏移 64 个字节的地方。
e_phentsize:每一个表项的长度是 56 个字节;
e_phnum:一共有 13 个表项 Entry;
通过计算,得到可读、可执行的 LOAD 段,位于偏移量 176 字节处(56*2+64)。
执行读取指令:od -Ad -t x1 -j 176 -N 56 main:
1 2 3 4 0000176 01 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 0000192 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000208 78 06 00 00 00 00 00 00 78 06 00 00 00 00 00 00 0000224 00 10 00 00 00 00 00 00
对应的关联:
1 2 3 4 5 6 7 8 9 10 typedef struct elf64_phdr { 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;
p_type: 段的类型,1: 表示这个段需要加载到内存中;
p_offset: 段在 ELF 文件中的偏移地址,这里值为 0,表示这个段从 ELF 文件的头部开始;
p_vaddr:段加载到内存中的虚拟地址 0x00;
p_paddr:段加载的物理地址,与虚拟地址相同;
p_filesz: 这个段在 ELF 文件中,占据的字节数,0x0678 = 1656 个字节;
p_memsz:这个段加载到内存中,需要占据的字节数,0x0678= 1656 个字节。不过有些段是不需要加载到内存中的;
从 ELF 文件的第 1 到 第 1656 个字节,都是属于这个 LOAD 段的内容。
在被执行时,这个段需要被加载到内存中虚拟地址为 0x00 这个地方。
总结
ELF header 描述了文件的总体信息,以及两个 table 的相关信息(偏移地址,表项个数,表项长度);
每一个 table 中,包括很多个表项 Entry,每一个表项都描述了一个 Section/Segment 的具体信息。