1 概述 这是一个从0开始手写的 ELF 加载器.
项目链接: https://github.com/Jvlegod/uELF
今天我们来看 ELF Header 的结构.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct { unsigned char e_ident[16 ]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint64_t e_entry; uint64_t e_phoff; uint64_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; } uElf64_Ehdr;
里面有 Section 和 Segment 的区别是.
“节” 是静态编译时概念,“段” 是运行时加载时概念.
名称
对应表
用途
Section(节)
Section Header Table (.shdr
)
给 链接器 用的(比如 .text
、.data
、.bss
、.symtab
)
Segment(段)
Program Header Table (.phdr
)
给 加载器(kernel loader / ld-linux) 用的(比如 PT_LOAD
, PT_DYNAMIC
)
第一步我们需要解析 Elf 的魔数为 0x7F 45 4C 46
表示这是一个正确的 ELF 文件.
1 2 3 4 5 6 7 ... if (memcpy (elf_file->elf_header.e_ident, "\x7f" "ELF" , 4 ) != 0 ) { uELF_ERROR("Not a valid ELF file" ); close(fd); return -1 ; } ...
之后我们可以来看看对于 Linker 和 Exec 文件,他们的 ELF Header 有什么不一样.
主要是 Entry point
和 Program Header
不同(从无到有的过程).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 jvle@jvle-ThinkPad-X1-Carbon-Gen-8:~/Desktop/works/temp/ELF$ ./uelf test /main.o 2025-10-15 00:16:00[] INFO (parse.c:46) ELF Header: 2025-10-15 00:16:00[] INFO (parse.c:47) Entry point: 0x0 2025-10-15 00:16:00[] INFO (parse.c:48) Program Header Offset: 0 2025-10-15 00:16:00[] INFO (parse.c:49) Section Header Offset: 536 2025-10-15 00:16:00[] INFO (parse.c:50) Number of Program Headers: 0 2025-10-15 00:16:00[] INFO (parse.c:51) Number of Section Headers: 13 jvle@jvle-ThinkPad-X1-Carbon-Gen-8:~/Desktop/works/temp/ELF$ ./uelf test /main 2025-10-15 00:17:53[] INFO (parse.c:46) ELF Header: 2025-10-15 00:17:53[] INFO (parse.c:47) Entry point: 0x1060 2025-10-15 00:17:53[] INFO (parse.c:48) Program Header Offset: 64 2025-10-15 00:17:53[] INFO (parse.c:49) Section Header Offset: 14032 2025-10-15 00:17:53[] INFO (parse.c:50) Number of Program Headers: 13 2025-10-15 00:17:53[] INFO (parse.c:51) Number of Section Headers: 31
这里的 Entry point
决定了——当 ELF 被加载并开始执行时,CPU 从哪一条指令开始运行.
简单来说.
当你运行一个 ELF 可执行文件(例如 /bin/ls),内核加载 ELF 后,会:
创建进程;
根据 ELF 的 Program Header 映射 .text, .data, .bss, 栈等;
然后让 CPU 跳转到 e_entry 指定的地址执行。
该程序的第一条指令会被放在虚拟地址为 e_entry 处.
我们可以看一下 Section Header Table
的结构.
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct { uint32_t sh_name; uint32_t sh_type; uint64_t sh_flags; uint64_t sh_addr; uint64_t sh_offset; uint64_t sh_size; uint32_t sh_link; uint32_t sh_info; uint64_t sh_addralign; uint64_t sh_entsize; } uElf64_Shdr;
字段名
对应 ELF 成员
含义
Idx
(索引号)
Section Header 在表中的编号(从 0 开始)
Name
sh_name
+ .shstrtab
解析结果
节名称,例如 .text
, .data
Type
sh_type
节的类型,如 PROGBITS
, SYMTAB
, STRTAB
等
Addr
sh_addr
该节加载到内存时的虚拟地址(仅对可执行文件有效,在 .o
里一般为 0)
Off
sh_offset
该节在文件中的偏移(以字节为单位)
Size
sh_size
该节的总字节大小
EntSz
sh_entsize
每个 entry 的大小(表类型节专用,如符号表/重定位表)
Flags
sh_flags
节的标志位(属性,如可写、可执行、分配到内存等)
Link
sh_link
链接相关索引,含义随类型不同
Info
sh_info
附加信息字段,含义随类型不同
Align
sh_addralign
对齐要求(内存/文件中对齐到多少字节边界)
关于 sh_addr
这里需要进行解释.
文件类型
文件格式类型 (e_type
)
是否已分配虚拟地址
.text
的 sh_addr
含义
目标文件 .o
ET_REL
还没分配
0x00000000
链接器后续决定
可执行文件 .out
ET_EXEC
已分配,固定虚拟地址
0x401000
程序入口/代码段
共享库 .so
ET_DYN
已分配,但可重定位
0x0000000000001000
加载器按需重定位
另外关于两个 size
字段要注意一下区分,有两个大小.
字段
含义
sh_size
整个节(section)的总字节数
sh_entsize
如果节中包含一张表(table),则表中每个“表项”的大小
1 2 3 4 5 6 7 8 .section (总大小 = sh_size) ├── entry0 (大小 = sh_entsize) ├── entry1 (大小 = sh_entsize) ├── entry2 (大小 = sh_entsize) └── ...
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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 $ readelf -S test /main.o There are 13 section headers, starting at offset 0x218: 节头: [号] 名称 类型 地址 偏移量 大小 全体大小 旗标 链接 信息 对齐 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000000000 00000040 0000000000000024 0000000000000000 AX 0 0 1 [ 2] .rela.text RELA 0000000000000000 00000178 0000000000000018 0000000000000018 I 10 1 8 [ 3] .data PROGBITS 0000000000000000 00000064 0000000000000000 0000000000000000 WA 0 0 1 [ 4] .bss NOBITS 0000000000000000 00000064 0000000000000000 0000000000000000 WA 0 0 1 [ 5] .comment PROGBITS 0000000000000000 00000064 000000000000002c 0000000000000001 MS 0 0 1 [ 6] .note.GNU-stack PROGBITS 0000000000000000 00000090 0000000000000000 0000000000000000 0 0 1 [ 7] .note.gnu.pr[...] NOTE 0000000000000000 00000090 0000000000000020 0000000000000000 A 0 0 8 [ 8] .eh_frame PROGBITS 0000000000000000 000000b0 0000000000000038 0000000000000000 A 0 0 8 [ 9] .rela.eh_frame RELA 0000000000000000 00000190 0000000000000018 0000000000000018 I 10 8 8 [10] .symtab SYMTAB 0000000000000000 000000e8 0000000000000078 0000000000000018 11 3 8 [11] .strtab STRTAB 0000000000000000 00000160 0000000000000013 0000000000000000 0 0 1 [12] .shstrtab STRTAB 0000000000000000 000001a8 000000000000006c 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), D (mbind), l (large), p (processor specific) $ readelf -S test /main There are 31 section headers, starting at offset 0x36d0: 节头: [号] 名称 类型 地址 偏移量 大小 全体大小 旗标 链接 信息 对齐 [ 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 0000000000000030 0000000000000000 A 0 0 8 [ 3] .note.gnu.bu[...] NOTE 0000000000000368 00000368 0000000000000024 0000000000000000 A 0 0 4 [ 4] .note.ABI-tag NOTE 000000000000038c 0000038c 0000000000000020 0000000000000000 A 0 0 4 [ 5] .gnu.hash GNU_HASH 00000000000003b0 000003b0 0000000000000024 0000000000000000 A 6 0 8 [ 6] .dynsym DYNSYM 00000000000003d8 000003d8 00000000000000a8 0000000000000018 A 7 1 8 [ 7] .dynstr STRTAB 0000000000000480 00000480 000000000000008d 0000000000000000 A 0 0 1 [ 8] .gnu.version VERSYM 000000000000050e 0000050e 000000000000000e 0000000000000002 A 6 0 2 [ 9] .gnu.version_r VERNEED 0000000000000520 00000520 0000000000000030 0000000000000000 A 7 1 8 [10] .rela.dyn RELA 0000000000000550 00000550 00000000000000c0 0000000000000018 A 6 0 8 [11] .rela.plt RELA 0000000000000610 00000610 0000000000000018 0000000000000018 AI 6 24 8 [12] .init PROGBITS 0000000000001000 00001000 000000000000001b 0000000000000000 AX 0 0 4 [13] .plt PROGBITS 0000000000001020 00001020 0000000000000020 0000000000000010 AX 0 0 16 [14] .plt.got PROGBITS 0000000000001040 00001040 0000000000000010 0000000000000010 AX 0 0 16 [15] .plt.sec PROGBITS 0000000000001050 00001050 0000000000000010 0000000000000010 AX 0 0 16 [16] .text PROGBITS 0000000000001060 00001060 0000000000000127 0000000000000000 AX 0 0 16 [17] .fini PROGBITS 0000000000001188 00001188 000000000000000d 0000000000000000 AX 0 0 4 [18] .rodata PROGBITS 0000000000002000 00002000 0000000000000011 0000000000000000 A 0 0 4 [19] .eh_frame_hdr PROGBITS 0000000000002014 00002014 000000000000003c 0000000000000000 A 0 0 4 [20] .eh_frame PROGBITS 0000000000002050 00002050 00000000000000cc 0000000000000000 A 0 0 8 [21] .init_array INIT_ARRAY 0000000000003db8 00002db8 0000000000000008 0000000000000008 WA 0 0 8 [22] .fini_array FINI_ARRAY 0000000000003dc0 00002dc0 0000000000000008 0000000000000008 WA 0 0 8 [23] .dynamic DYNAMIC 0000000000003dc8 00002dc8 00000000000001f0 0000000000000010 WA 7 0 8 [24] .got PROGBITS 0000000000003fb8 00002fb8 0000000000000048 0000000000000008 WA 0 0 8 [25] .data PROGBITS 0000000000004000 00003000 0000000000000010 0000000000000000 WA 0 0 8 [26] .bss NOBITS 0000000000004010 00003010 0000000000000008 0000000000000000 WA 0 0 1 [27] .comment PROGBITS 0000000000000000 00003010 000000000000002b 0000000000000001 MS 0 0 1 [28] .symtab SYMTAB 0000000000000000 00003040 0000000000000390 0000000000000018 29 19 8 [29] .strtab STRTAB 0000000000000000 000033d0 00000000000001e6 0000000000000000 0 0 1 [30] .shstrtab STRTAB 0000000000000000 000035b6 000000000000011a 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), D (mbind), l (large), p (processor specific)
下面我们来尝试解析 Section Header Table
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ... elf_file->section_headers = malloc (ehdr->e_shnum * sizeof (uElf64_Shdr)); if (!elf_file->section_headers) { uELF_ERROR("Failed to allocate memory for section headers" ); return -1 ; } ... lseek(elf_file->fd, ehdr->e_shoff, SEEK_SET); if (read(elf_file->fd, elf_file->section_headers, ehdr->e_shnum * sizeof (uElf64_Shdr)) != ehdr->e_shnum * sizeof (uElf64_Shdr)) { uELF_ERROR("Failed to read section headers" ); free (elf_file->section_headers); return -1 ; } ...
这里当然还有一个特殊的段叫 节名字符串表
.
于是我们在结构体中新加入字段.
1 2 3 4 5 typedef struct {... uElf64_Shdr *shstrtab_section; char *shstrtab; } uElf64_File;
这里单独对该字段进行抽取.
1 2 3 4 5 6 7 elf_file->shstrtab_section = &elf_file->section_headers[ehdr->e_shstrndx]; elf_file->shstrtab = malloc (elf_file->shstrtab_section->sh_size); if (!elf_file->shstrtab) { uELF_ERROR("Failed to allocate memory for section header string table" ); free (elf_file->section_headers); return -1 ; }
结果进行查看.
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 jvle@jvle-ThinkPad-X1-Carbon-Gen-8:~/Desktop/works/temp/ELF$ ./uelf test /main.o 2025-10-15 17:17:49[] INFO (uELF.c:47) ELF Header: 2025-10-15 17:17:49[] INFO (uELF.c:48) Entry point: 0x0 2025-10-15 17:17:49[] INFO (uELF.c:49) Program Header Offset: 0 2025-10-15 17:17:49[] INFO (uELF.c:50) Section Header Offset: 536 2025-10-15 17:17:49[] INFO (uELF.c:51) Number of Program Headers: 0 2025-10-15 17:17:49[] INFO (uELF.c:52) Number of Section Headers: 13 2025-10-15 17:17:49[] INFO (uELF.c:60) Section Headers (all 13): 2025-10-15 17:17:49[] INFO (uELF.c:61) [Idx] Name Type Addr Off Size EntSz Flags Link Info Align 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 0] NULL 00000000 000000 000000 000000 0 0 0 0 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 1] .text PROGBITS 00000000 000040 000024 000000 6 0 0 1 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 2] .rela.text RELA 00000000 000178 000018 000018 40 10 1 8 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 3] .data PROGBITS 00000000 000064 000000 000000 3 0 0 1 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 4] .bss NOBITS 00000000 000064 000000 000000 3 0 0 1 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 5] .comment PROGBITS 00000000 000064 00002c 000001 30 0 0 1 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 6] .note.GNU-stack PROGBITS 00000000 000090 000000 000000 0 0 0 1 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 7] .note.gnu.property NOTE 00000000 000090 000020 000000 2 0 0 8 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 8] .eh_frame PROGBITS 00000000 0000b0 000038 000000 2 0 0 8 2025-10-15 17:17:49[] INFO (uELF.c:83) [ 9] .rela.eh_frame RELA 00000000 000190 000018 000018 40 10 8 8 2025-10-15 17:17:49[] INFO (uELF.c:83) [10] .symtab SYMTAB 00000000 0000e8 000078 000018 0 11 3 8 2025-10-15 17:17:49[] INFO (uELF.c:83) [11] .strtab STRTAB 00000000 000160 000013 000000 0 0 0 1 2025-10-15 17:17:49[] INFO (uELF.c:83) [12] .shstrtab STRTAB 00000000 0001a8 00006c 000000 0 0 0 1 jvle@jvle-ThinkPad-X1-Carbon-Gen-8:~/Desktop/works/temp/ELF$ ./uelf test /main 2025-10-15 17:18:49[] INFO (uELF.c:47) ELF Header: 2025-10-15 17:18:49[] INFO (uELF.c:48) Entry point: 0x1060 2025-10-15 17:18:49[] INFO (uELF.c:49) Program Header Offset: 64 2025-10-15 17:18:49[] INFO (uELF.c:50) Section Header Offset: 14032 2025-10-15 17:18:49[] INFO (uELF.c:51) Number of Program Headers: 13 2025-10-15 17:18:49[] INFO (uELF.c:52) Number of Section Headers: 31 2025-10-15 17:18:49[] INFO (uELF.c:60) Section Headers (all 31): 2025-10-15 17:18:49[] INFO (uELF.c:61) [Idx] Name Type Addr Off Size EntSz Flags Link Info Align 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 0] NULL 00000000 000000 000000 000000 0 0 0 0 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 1] .interp PROGBITS 00000318 000318 00001c 000000 2 0 0 1 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 2] .note.gnu.property NOTE 00000338 000338 000030 000000 2 0 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 3] .note.gnu.build-id NOTE 00000368 000368 000024 000000 2 0 0 4 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 4] .note.ABI-tag NOTE 0000038c 00038c 000020 000000 2 0 0 4 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 5] .gnu.hash OTHER 000003b0 0003b0 000024 000000 2 6 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 6] .dynsym DYNSYM 000003d8 0003d8 0000a8 000018 2 7 1 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 7] .dynstr STRTAB 00000480 000480 00008d 000000 2 0 0 1 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 8] .gnu.version OTHER 0000050e 00050e 00000e 000002 2 6 0 2 2025-10-15 17:18:49[] INFO (uELF.c:83) [ 9] .gnu.version_r OTHER 00000520 000520 000030 000000 2 7 1 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [10] .rela.dyn RELA 00000550 000550 0000c0 000018 2 6 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [11] .rela.plt RELA 00000610 000610 000018 000018 42 6 24 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [12] .init PROGBITS 00001000 001000 00001b 000000 6 0 0 4 2025-10-15 17:18:49[] INFO (uELF.c:83) [13] .plt PROGBITS 00001020 001020 000020 000010 6 0 0 16 2025-10-15 17:18:49[] INFO (uELF.c:83) [14] .plt.got PROGBITS 00001040 001040 000010 000010 6 0 0 16 2025-10-15 17:18:49[] INFO (uELF.c:83) [15] .plt.sec PROGBITS 00001050 001050 000010 000010 6 0 0 16 2025-10-15 17:18:49[] INFO (uELF.c:83) [16] .text PROGBITS 00001060 001060 000127 000000 6 0 0 16 2025-10-15 17:18:49[] INFO (uELF.c:83) [17] .fini PROGBITS 00001188 001188 00000d 000000 6 0 0 4 2025-10-15 17:18:49[] INFO (uELF.c:83) [18] .rodata PROGBITS 00002000 002000 000011 000000 2 0 0 4 2025-10-15 17:18:49[] INFO (uELF.c:83) [19] .eh_frame_hdr PROGBITS 00002014 002014 00003c 000000 2 0 0 4 2025-10-15 17:18:49[] INFO (uELF.c:83) [20] .eh_frame PROGBITS 00002050 002050 0000cc 000000 2 0 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [21] .init_array OTHER 00003db8 002db8 000008 000008 3 0 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [22] .fini_array OTHER 00003dc0 002dc0 000008 000008 3 0 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [23] .dynamic DYNAMIC 00003dc8 002dc8 0001f0 000010 3 7 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [24] .got PROGBITS 00003fb8 002fb8 000048 000008 3 0 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [25] .data PROGBITS 00004000 003000 000010 000000 3 0 0 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [26] .bss NOBITS 00004010 003010 000008 000000 3 0 0 1 2025-10-15 17:18:49[] INFO (uELF.c:83) [27] .comment PROGBITS 00000000 003010 00002b 000001 30 0 0 1 2025-10-15 17:18:49[] INFO (uELF.c:83) [28] .symtab SYMTAB 00000000 003040 000390 000018 0 29 19 8 2025-10-15 17:18:49[] INFO (uELF.c:83) [29] .strtab STRTAB 00000000 0033d0 0001e6 000000 0 0 0 1 2025-10-15 17:18:49[] INFO (uELF.c:83) [30] .shstrtab STRTAB 00000000 0035b6 00011a 000000 0 0 0 1
4 解析 Symbol Table 首先我们要知道符号表的结构.
1 2 3 4 5 6 7 8 typedef struct { uint32_t st_name; uint8_t st_info; uint8_t st_other; uint16_t st_shndx; uint64_t st_value; uint64_t st_size; } uElf64_Sym;
当我们在解析 Section的时候,发现他的 sh_type 是 .symtab
静态链接符号 或 .dynsym
动态链接符号 标志,则说明这可能是一个符号表.
更多的 sh_type
信息如下.
sh_type
常量名
意义
SHT_NULL
无效节
SHT_PROGBITS
程序数据(机器码、常量等)
SHT_NOBITS
不占空间(如 .bss
)
SHT_SYMTAB
符号表 (静态链接符号)
SHT_STRTAB
字符串表
SHT_DYNSYM
动态符号表 (动态链接符号)
SHT_RELA
/ SHT_REL
重定位表
SHT_NOTE
注释信息(比如 GNU Stack)
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct {... uElf64_Shdr *strtab_section; char *strtab; uElf64_Shdr *dynstr_section; char *dynstr; uElf64_Shdr *symtab_section; char *symtab; uElf64_Shdr *dynsym_section; char *dynsym; } uElf64_File;
我们可能需要获取各个符号的名称,那么我们可以通过 symtab_section
的 sh_link
字段来作为 .strtab
的索引. 另外该节的 sh_info
字段表示本地符号的数量(或者说是第一个全局符号的索引).
同样 dynstr
对应着动态链接的各个符号和信息.
主要的解析部分如下.
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 ... for (int i = 0 ; i < elf_file->elf_header.e_shnum; i++) { if (elf_file->section_headers[i].sh_type == UELF_SHT_SYMTAB) { elf_file->symtab_section = &elf_file->section_headers[i]; elf_file->symtab = malloc (elf_file->section_headers[i].sh_size); if (!elf_file->symtab) { uELF_ERROR("Failed to allocate memory for symbol table" ); return -1 ; } lseek(fd, elf_file->symtab_section->sh_offset, SEEK_SET); if (read(fd, elf_file->symtab, elf_file->section_headers[i].sh_size) != (ssize_t )elf_file->section_headers[i].sh_size) { uELF_ERROR("Failed to read symbol table" ); free (elf_file->symtab); return -1 ; } } else if (elf_file->section_headers[i].sh_type == UELF_SHT_DYNSYM) { elf_file->dynsym_section = &elf_file->section_headers[i]; elf_file->dynsym = malloc (elf_file->section_headers[i].sh_size); if (!elf_file->dynsym) { uELF_ERROR("Failed to allocate memory for dynamic symbol table" ); return -1 ; } lseek(fd, elf_file->dynsym_section->sh_offset, SEEK_SET); if (read(fd, elf_file->dynsym, elf_file->section_headers[i].sh_size) != (ssize_t )elf_file->section_headers[i].sh_size) { uELF_ERROR("Failed to read dynamic symbol table" ); free (elf_file->dynsym); return -1 ; } } } if (elf_file->symtab_section) { elf_file->strtab_section = &elf_file->section_headers[elf_file->symtab_section->sh_link]; elf_file->strtab = malloc (elf_file->strtab_section->sh_size); if (!elf_file->strtab) { uELF_ERROR("Failed to allocate memory for string table" ); return -1 ; } if (lseek(fd, elf_file->strtab_section->sh_offset, SEEK_SET) < 0 ) { uELF_ERROR("lseek failed when reading .strtab" ); return -1 ; } if (read(fd, elf_file->strtab, elf_file->strtab_section->sh_size) != (ssize_t )elf_file->strtab_section->sh_size) { uELF_ERROR("read failed when reading .strtab" ); return -1 ; } } if (elf_file->dynsym_section) { elf_file->dynstr_section = &elf_file->section_headers[elf_file->dynsym_section->sh_link]; elf_file->dynstr = malloc (elf_file->dynstr_section->sh_size); if (!elf_file->dynstr) { uELF_ERROR("Failed to allocate memory for dynamic string table" ); return -1 ; } if (lseek(fd, elf_file->dynstr_section->sh_offset, SEEK_SET) < 0 ) { uELF_ERROR("lseek failed when reading .dynstr" ); return -1 ; } if (read(fd, elf_file->dynstr, elf_file->dynstr_section->sh_size) != (ssize_t )elf_file->dynstr_section->sh_size) { uELF_ERROR("read failed when reading .dynstr" ); return -1 ; } } ...
Program Headers(程序头表) 出现在哪些 ELF 文件类型中,是理解 ELF 加载机制的关键.
文件类型
ELF Header 中的 e_type
值
是否包含 Program Headers
说明
ET_REL
1
否
可重定位目标文件 (.o
、内核模块 .ko
),仅有 Section Headers;供链接器使用。
ET_EXEC
2
是
可执行文件 (a.out
、Linux ELF 可执行程序),包含程序头表,供内核加载。
ET_DYN
3
是
动态共享对象(.so 文件) ,可被动态加载(dlopen
)或作为主程序执行(PIE)。
ET_CORE
4
是
Core Dump 文件 (进程崩溃转储),Program 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 36 typedef enum { UELF_PT_NULL = 0 , UELF_PT_LOAD = 1 , UELF_PT_DYNAMIC = 2 , UELF_PT_INTERP = 3 , UELF_PT_NOTE = 4 , UELF_PT_SHLIB = 5 , UELF_PT_PHDR = 6 , UELF_PT_TLS = 7 , UELF_PT_LOOS = 0x60000000 , UELF_PT_GNU_EH_FRAME= 0x6474e550 , UELF_PT_GNU_STACK = 0x6474e551 , UELF_PT_GNU_RELRO = 0x6474e552 , UELF_PT_GNU_PROPERTY= 0x6474e553 , UELF_PT_LOSUNW = 0x6ffffffa , UELF_PT_SUNWBSS = 0x6ffffffa , UELF_PT_SUNWSTACK = 0x6ffffffb , UELF_PT_LOPROC = 0x70000000 , UELF_PT_HIPROC = 0x7fffffff } uELF_ProgramType; typedef struct { uint32_t p_type; uint32_t p_flags; uint64_t p_offset; uint64_t p_vaddr; uint64_t p_paddr; uint64_t p_filesz; uint64_t p_memsz; uint64_t p_align; } uElf64_Phdr;
这里可以看看我们解析出来的和 readelf -l
解析的区别.
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 2025-10-15 20:04:11[] INFO (uELF.c:328) Program Headers (all 13): 2025-10-15 20:04:11[] INFO (uELF.c:329) [Idx] Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 0] PHDR 000040 00000040 00000040 0002d8 0002d8 4 8 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 1] INTERP 000318 00000318 00000318 00001c 00001c 4 1 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 2] LOAD 000000 00000000 00000000 000628 000628 4 4096 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 3] LOAD 001000 00001000 00001000 000195 000195 5 4096 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 4] LOAD 002000 00002000 00002000 00011c 00011c 4 4096 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 5] LOAD 002db8 00003db8 00003db8 000258 000260 6 4096 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 6] DYNAMIC 002dc8 00003dc8 00003dc8 0001f0 0001f0 6 8 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 7] NOTE 000338 00000338 00000338 000030 000030 4 8 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 8] NOTE 000368 00000368 00000368 000044 000044 4 4 2025-10-15 20:04:11[] INFO (uELF.c:346) [ 9] OTHER 000338 00000338 00000338 000030 000030 4 8 2025-10-15 20:04:11[] INFO (uELF.c:346) [10] OTHER 002014 00002014 00002014 00003c 00003c 4 4 2025-10-15 20:04:11[] INFO (uELF.c:346) [11] OTHER 000000 00000000 00000000 000000 000000 6 16 2025-10-15 20:04:11[] INFO (uELF.c:346) [12] OTHER 002db8 00003db8 00003db8 000248 000248 4 1 $ readelf -l test /main Elf 文件类型为 DYN (Position-Independent Executable file) Entry point 0x1060 There are 13 program headers, starting at offset 64 程序头: 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 0x0000000000000628 0x0000000000000628 R 0x1000 LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x0000000000000195 0x0000000000000195 R E 0x1000 LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x000000000000011c 0x000000000000011c R 0x1000 LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8 0x0000000000000258 0x0000000000000260 RW 0x1000 DYNAMIC 0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8 0x00000000000001f0 0x00000000000001f0 RW 0x8 NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000030 R 0x8 NOTE 0x0000000000000368 0x0000000000000368 0x0000000000000368 0x0000000000000044 0x0000000000000044 R 0x4 GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000030 R 0x8 GNU_EH_FRAME 0x0000000000002014 0x0000000000002014 0x0000000000002014 0x000000000000003c 0x000000000000003c R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8 0x0000000000000248 0x0000000000000248 R 0x1 Section to Segment mapping: 段节... 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 .plt.got .plt.sec .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .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
6 load segment 在 uELF64_load_segments
函数中我们重点完成将 segments
加载到内存.
只有 PT_LOAD
的 segments
我们才将它映射到内存当中.
期间我们要确定 ELF 文件的类型,这里从 ELF Header 就能进行分类.
类型
全称
中文含义
特点
PIE
Position Independent Executable
位置无关可执行文件
加载地址不固定,可被随机化
non-PIE
Normal Executable (Fixed Address)
固定地址可执行文件
加载到固定虚拟地址(例如 0x400000)
项目
non-PIE
PIE
ELF 类型
ET_EXEC
ET_DYN
编译参数
-no-pie
-fPIE -pie
加载地址
固定(如 0x400000)
随机(由内核 ASLR 决定)
main 函数地址
不变
每次都变
可被 ASLR 随机化
否
是
本质
固定地址程序
类似共享库的可执行文件
具体过程如下.
获取系统页大小(sysconf)
统计所有 PT_LOAD 段(可加载段)
分配段加载追踪结构(用于后续 munmap)
计算 PIE 情况下需要的地址空间范围
分别加载每个段:
设置内存保护权限
若加载失败则清理内存
下面的代码是计算 PIE 需要的布局情况,拿到最小和最大的映射地址,以此来计算应该给该程序预留多大的内存.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ... for (int i = 0 ; i < ehdr->e_phnum; i++) { uElf64_Phdr *ph = &elf_file->program_headers[i]; if (ph->p_type != UELF_PT_LOAD || ph->p_memsz == 0 ) { continue ; } uint64_t aligned_vaddr = uelf_align_down(ph->p_vaddr, (uint64_t )page_size); uint64_t segment_end = uelf_align_up(ph->p_vaddr + ph->p_memsz, (uint64_t )page_size); if (aligned_vaddr < min_vaddr) { min_vaddr = aligned_vaddr; } if (segment_end > max_vaddr) { max_vaddr = segment_end; } } ...
下面是 PIE 代码的关键.
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 ... if (is_pie && loadable_count > 0 ) { size_t total_size = (size_t )(max_vaddr - min_vaddr); if (total_size == 0 ) { total_size = (size_t )page_size; } void *reservation = mmap(NULL , total_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 ); if (reservation == MAP_FAILED) { uELF_ERROR("Failed to reserve address space for PIE: %s" , strerror(errno)); free (elf_file->loaded_segments); elf_file->loaded_segments = NULL ; free (elf_file->loaded_segment_sizes); elf_file->loaded_segment_sizes = NULL ; return -1 ; } base = (uintptr_t )reservation - min_vaddr; elf_file->loaded_segments[0 ] = reservation; elf_file->loaded_segment_sizes[0 ] = total_size; elf_file->loaded_segment_count = 1 ; } ...
之后我们看看将 segments 映射到内存的核心代码.
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 ... for (int i = 0 ; i < ehdr->e_phnum; i++) { uElf64_Phdr *ph = &elf_file->program_headers[i]; if (ph->p_type != UELF_PT_LOAD || ph->p_memsz == 0 ) { continue ; } uint64_t aligned_vaddr = uelf_align_down(ph->p_vaddr, (uint64_t )page_size); uint64_t segment_end = uelf_align_up(ph->p_vaddr + ph->p_memsz, (uint64_t )page_size); size_t map_size = (size_t )(segment_end - aligned_vaddr); int prot = uelf_prot_from_flags(ph->p_flags); if (is_pie) { uint8_t *segment_data = (uint8_t *)(base + ph->p_vaddr); ssize_t read_bytes = pread(elf_file->fd, segment_data, ph->p_filesz, ph->p_offset); if (read_bytes != (ssize_t )ph->p_filesz) { uELF_ERROR("Failed to read segment %d contents" , i); goto fail; } if (ph->p_memsz > ph->p_filesz) { size_t bss_size = (size_t )(ph->p_memsz - ph->p_filesz); memset (segment_data + ph->p_filesz, 0 , bss_size); } if (mprotect((void *)(base + aligned_vaddr), map_size, prot) < 0 ) { uELF_WARN("mprotect failed for segment %d: %s" , i, strerror(errno)); } uELF_INFO("Loaded segment %d at 0x%lx (%zu bytes)" , i, (unsigned long )(base + aligned_vaddr), map_size); continue ; } int map_prot = prot | PROT_WRITE; uintptr_t target_addr = base + aligned_vaddr; void *mapping_base = mmap((void *)target_addr, map_size, map_prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1 , 0 ); if (mapping_base == MAP_FAILED) { uELF_ERROR("mmap failed for segment %d: %s" , i, strerror(errno)); goto fail; } uint8_t *segment_data = (uint8_t *)(base + ph->p_vaddr); ssize_t read_bytes = pread(elf_file->fd, segment_data, ph->p_filesz, ph->p_offset); if (read_bytes != (ssize_t )ph->p_filesz) { uELF_ERROR("Failed to read segment %d contents" , i); munmap(mapping_base, map_size); goto fail; } if (ph->p_memsz > ph->p_filesz) { size_t bss_size = (size_t )(ph->p_memsz - ph->p_filesz); memset (segment_data + ph->p_filesz, 0 , bss_size); } if ((prot & PROT_WRITE) == 0 ) { if (mprotect(mapping_base, map_size, prot) < 0 ) { uELF_WARN("mprotect failed for segment %d: %s" , i, strerror(errno)); } } elf_file->loaded_segments[segment_index] = mapping_base; elf_file->loaded_segment_sizes[segment_index] = map_size; elf_file->loaded_segment_count++; segment_index++; uELF_INFO("Loaded segment %d at 0x%lx (%zu bytes)" , i, (unsigned long )(base + aligned_vaddr), map_size); } ...
7 relocations 重定位是编译器和链接器(或者运行时加载器)将符号引用转换为实际地址的过程.
重定位(顾名思义,重新定位)可以发生在三个时期.
阶段
参与者
重定位类型
说明
链接时重定位
ld
(静态链接器)
静态重定位
把所有目标文件的符号解析并生成一个固定地址的可执行文件。
加载时重定位
动态链接器(ld-linux.so
)
动态重定位
程序运行时,根据加载的共享库地址重新修正引用。
运行时重定位
用户自定义 loader / JIT / dlopen()
手动或延迟重定位
比如你自己写的 ELF 加载器、PIE、或者 dlfcn
动态调用。
重定位的条目结构如下.
1 2 3 4 5 typedef struct { uint64_t r_offset; uint64_t r_info; int64_t r_addend; } uElf64_Rela;
而类型为如下的 sections 被标记为重定位 section.
类型常量
含义
说明
SHT_REL
不带加数的重定位表
每个条目是 Elf32_Rel
/ Elf64_Rel
SHT_RELA
带加数(addend)的重定位表
每个条目是 Elf32_Rela
/ Elf64_Rela
在我们的代码中可能只会看到 Elf64_Rela
,这是因为.
“All relocations for the AMD64 architecture use the ELF64_Rela structure. Entries of type Elf64_Rel are not used.”
uELF64_apply_relocations
是实现重定位的主要函数.
其步骤如下.
检查是否有 section_headers
遍历所有节区
如果是 SHT_RELA
则读取重定位表
找符号表与字符串表
遍历每个 Elf64_Rela
解析 r_info
得到类型和符号索引
根据类型选择修正公式
写回 target
位置
当我们找到 relocation section 之后,我们先找到 relocs 表存放的位置.
一般 sh_link
表示当前节关联的另一个节的索引号.
对于每个重定位节会通过它的 sh_link
字段指向它所使用的符号表的节的索引(通常是 .dynsym 或 .symtab).
符号表节名
类型 (sh_type
)
典型对应的重定位节
用途
.symtab
SHT_SYMTAB
.rel.text
, .rela.text
静态链接时的符号表(目标文件 .o
/ 可执行文件 .exe
)
.dynsym
SHT_DYNSYM
.rel.dyn
, .rela.dyn
, .rel.plt
, .rela.plt
动态链接符号表(共享库 .so
/ 动态可执行文件)
接下来解读 uELF64_apply_relocations
的核心代码.
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 uElf64_Rela *relocs = malloc (sh->sh_size); ... if (pread(elf_file->fd, relocs, sh->sh_size, sh->sh_offset) != (ssize_t )sh->sh_size) { uELF_ERROR("Failed to read relocation section %d" , i); free (relocs); return -1 ; } if (sh->sh_link >= elf_file->elf_header.e_shnum) { uELF_ERROR("Relocation section %d references invalid symbol table" , i); free (relocs); return -1 ; } ... if (symtab_section == elf_file->symtab_section && elf_file->symtab) { symtab_data = elf_file->symtab; strtab_data = elf_file->strtab; if (elf_file->strtab_section) { strtab_size = elf_file->strtab_section->sh_size; } } else if (symtab_section == elf_file->dynsym_section && elf_file->dynsym) { symtab_data = elf_file->dynsym; strtab_data = elf_file->dynstr; if (elf_file->dynstr_section) { strtab_size = elf_file->dynstr_section->sh_size; } } else { uELF_WARN("Skipping relocation section %d: unsupported symbol table index %u" , i, sh->sh_link); free (relocs); continue ; } uELF64_arch_relocate(elf_file, relocs, count, symtab_section, symtab_data, strtab_data, strtab_size, sym_entsize);
uELF64_arch_relocate
是重定位进行数据纠正的重要一步,因为涉及架构相关,我们进一步处理.
在 uELF64_x86_64_relocate
.
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 static int uELF64_x86_64_relocate (uElf64_File *elf_file, uElf64_Rela *relocs, size_t count, uElf64_Shdr *symtab_section, char *symtab_data, char *strtab_data, size_t strtab_size, size_t sym_entsize) { for (size_t rel_idx = 0 ; rel_idx < count; rel_idx++) { uElf64_Rela *rela = &relocs[rel_idx]; uint32_t type = UELF64_R_TYPE(rela->r_info); uint32_t sym_index = UELF64_R_SYM(rela->r_info); uintptr_t target = elf_file->load_base + rela->r_offset; uint64_t value = 0 ; const uElf64_Sym *sym = NULL ; const char *name = NULL ; if (sym_index != 0 && symtab_data) { size_t symtab_size = symtab_section->sh_size; size_t offset = (size_t )sym_index * sym_entsize; if (offset + sym_entsize <= symtab_size) { sym = (const uElf64_Sym *)(symtab_data + offset); if (sym && sym->st_name && strtab_data && (size_t )sym->st_name < strtab_size) { name = strtab_data + sym->st_name; } } else { uELF_ERROR("Relocation references invalid symbol index %u" , sym_index); free (relocs); return -1 ; } } ... } }
下面的代码是重定位实际纠正生效的地方.
类型
名称
含义
计算公式
R_X86_64_NONE
无操作
忽略
—
R_X86_64_64
绝对重定位
写入符号绝对地址
S + A
R_X86_64_PC32
相对 PC 地址
相对于当前指令地址
S + A - P
R_X86_64_GLOB_DAT
全局变量重定位
修正 GOT 条目
S
R_X86_64_JUMP_SLOT
动态函数跳转表
修正 PLT/GOT
S
R_X86_64_RELATIVE
基址相对
自身基址 + 偏移
B + A
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 ... switch (type) { case UELF_R_X86_64_RELATIVE: *(uint64_t *)target = elf_file->load_base + rela->r_addend; break ; case UELF_R_X86_64_GLOB_DAT: case UELF_R_X86_64_JUMP_SLOT: if (!sym) { uELF_ERROR("Relocation requires symbol but none provided (index %u)" , sym_index); free (relocs); return -1 ; } if (uELF64_resolve_symbol_address(elf_file, sym, name, &value) < 0 ) { free (relocs); return -1 ; } *(uint64_t *)target = value; break ; case UELF_R_X86_64_64: if (!sym) { uELF_ERROR("Relocation requires symbol but none provided (index %u)" , sym_index); free (relocs); return -1 ; } if (uELF64_resolve_symbol_address(elf_file, sym, name, &value) < 0 ) { free (relocs); return -1 ; } *(uint64_t *)target = value + rela->r_addend; break ; case UELF_R_X86_64_NONE: break ; default : uELF_WARN("Unsupported relocation type %u at offset 0x%lx" , type, (unsigned long )rela->r_offset); break ; } ...
其中的 uELF64_resolve_symbol_address
函数负责对
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 static int uELF64_resolve_symbol_address (uElf64_File *elf_file, const uElf64_Sym *sym, const char *name, uint64_t *value) { if (!sym || !value) { return -1 ; } if (sym->st_shndx != 0 && sym->st_shndx < 0xff00 ) { *value = elf_file->load_base + sym->st_value; return 0 ; } if (!name || name[0 ] == '\0' ) { if (UELF64_ST_BIND(sym->st_info) == UELF_STB_WEAK) { *value = 0 ; return 0 ; } return -1 ; } dlerror(); void *addr = dlsym(RTLD_DEFAULT, name); const char *err = dlerror(); if (err != NULL || addr == NULL ) { if (UELF64_ST_BIND(sym->st_info) == UELF_STB_WEAK) { uELF_WARN("Leaving weak external symbol '%s' unresolved" , name); *value = 0 ; return 0 ; } uELF_ERROR("Failed to resolve external symbol '%s': %s" , name, err ? err : "unknown error" ); return -1 ; } *value = (uint64_t )(uintptr_t )addr; return 0 ; }
上面关于 st_shndx
进一步说明.
如果是 SHN_UNDEF
表示符号未在本文件定义
宏
数值
含义
影响
SHN_UNDEF
0
未定义符号 (在本文件中只被引用未被定义)
需要由链接器/动态链接器去别的目标里解析;若是弱符号可允许为 0
SHN_ABS
0xFFF1
绝对符号 (不随重定位变化)
st_value
即最终值,不能再按节进行重定位
SHN_COMMON
0xFFF2
公共块 (未分配节的未初始化数据)
链接时由链接器分配到 .bss
一类节;st_size
=大小,st_value
=对齐
SHN_XINDEX
0xFFFF
扩展节索引 标志
真正的节索引放在辅助节 SHT_SYMTAB_SHNDX
里
SHN_LORESERVE
0xFF00
保留区下界
通常用于 OS/Proc 特定
SHN_HIRESERVE
0xFFFF
保留区上界
——
另外这里对 GOT/PLT 表进行说明.
动态库的加载地址在运行时才确定,编译时无法知道这些函数的真实地址.
GOT(Global Offset Table)和 PLT(Procedure Linkage Table)是 ELF 动态链接中两张配合使用的运行时跳转表.
它们是为了让 ELF 在 运行时加载时 仍然能正确调用外部函数、访问全局变量而设计的.
GOT 表: 保存需要运行时修正的地址. 程序运行时不直接访问符号地址,而是通过 GOT 表间接访问.
例如访问一个外部的全局变量 extern int global;
,会直接经过 GOT 表.
编译器会生成.
1 2 mov rax, [rip + offset_to_GOT_entry] ; 通过GOT表间接取 global 的地址 mov eax, [rax]
当符号后期需要的时候被解析,这里才会变成实际地址.
PLT 表: 让外部函数调用延迟到运行时再解析,每个外部函数对应一个 PLT “stub”.
PLT 表是为了间接访问 GOT 表来获取地址.
假设有函数 puts()
.
编译时实际生成的地址为.
通过 PLT 我们间接地访问到 GOT 表,或者 GOT 表此时还没有实际地址,我们通过间接的逻辑来填充 GOT 表以此来实现延迟绑定.
我的理解是: GOT 存放“真实地址”,PLT 存放“跳转逻辑” .