1. ELF 文件结构概览
ELF(Executable and Linkable Format)是一种常见的文件格式,用于可执行文件、目标文件、共享库和核心转储文件。其整体结构如下:
ELF 文件的主要组成部分
- ELF Header
- 描述文件的全局信息。
- 提供 Program Header Table 和 Section Header Table 的位置。
- 文件结构的入口,位于文件的起始部分。
- Program Header Table
- 描述运行时段(Segment)的信息。
- 仅对可执行文件和共享库有意义,目标文件通常不包含。
- 负责运行时内存布局的描述。
- Sections
- 包含代码、数据、符号表等逻辑存储单元。
- 对链接和调试非常重要。
- 常见节包括
.text(代码段)、.data(数据段)、.bss(未初始化数据段)等。
- Section Header Table
- 描述文件中各节(Section)的元信息。
- 包含每个节的名称、类型、大小、偏移等。
- 用于链接和调试。
ELF 文件的具体结构布局
文件从头到尾的逻辑顺序如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| +---------------------+
| ELF Header | <-- 文件头,描述文件全局信息
+---------------------+
| Program Header Table| <-- 可选,描述段的运行时布局
+---------------------+
| .text | <-- 代码段,存放可执行指令
+---------------------+
| .rodata | <-- 只读数据段
+---------------------+
| .data | <-- 已初始化数据段
+---------------------+
| .bss (逻辑存在) | <-- 未初始化数据段,文件中不占空间
+---------------------+
| .symtab | <-- 符号表,用于调试和链接
+---------------------+
| .strtab | <-- 字符串表
+---------------------+
| .rel.text | <-- 重定位表
+---------------------+
| Section Header Table| <-- 描述所有节的信息
+---------------------+
|
命令详解
- 查询 elf header
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| xm@xm:~/xx/complier$ readelf -h SimpleSection.o
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: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 1184 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
|
- elf.h 详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // vim /usr/include/elf.h
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
|
字段对应表
| ELF Header 字段 | Elf64_Ehdr 字段 | SimpleSection.o 输出值 | 解释 |
|---|
| Magic | e_ident | 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 | ELF 文件的魔数(7f 45 4c 46 为 “ELF”),标识 ELF 格式。 |
| Class | e_ident | ELF64 | 文件的架构,表示 64 位 ELF 文件。 |
| Data | e_ident | 2's complement, little endian | 字节序,小端字节序。 |
| Version | e_version | 1 (current) | ELF 文件的版本号。 |
| OS/ABI | e_ident | UNIX - System V | 操作系统 ABI,表示 UNIX System V。 |
| ABI Version | e_ident | 0 | ABI 的版本号。 |
| Type | e_type | REL (Relocatable file) | 文件类型,REL 表示可重定位文件。 |
| Machine | e_machine | Advanced Micro Devices X86-64 | 目标架构,表示 x86-64 架构。 |
| Version | e_version | 0x1 | 文件的版本号。 |
| Entry point address | e_entry | 0x0 | 程序入口地址。对于目标文件,入口地址为 0。 |
| Start of program headers | e_phoff | 0 (bytes into file) | 程序头表的偏移量。目标文件不包含程序头表,因此为 0。 |
| Start of section headers | e_shoff | 1184 (bytes into file) | 节头表的偏移量,从文件的 1184 字节处开始。 |
| Flags | e_flags | 0x0 | 处理器特定的标志,通常为 0。 |
| Size of this header | e_ehsize | 64 (bytes) | ELF 文件头的大小。对于 64 位 ELF 文件,大小为 64 字节。 |
| Size of program headers | e_phentsize | 0 (bytes) | 每个程序头表项的大小。目标文件不包含程序头表,因此为 0。 |
| Number of program headers | e_phnum | 0 | 程序头表项的数量。目标文件不包含程序头表项,因此为 0。 |
| Size of section headers | e_shentsize | 64 (bytes) | 每个节头表项的大小。通常为 64 字节。 |
| Number of section headers | e_shnum | 14 | 节头表项的数量。该文件包含 14 个节。 |
| Section header string table index | e_shstrndx | 13 | 节头表字符串表的索引,指向节名称的字符串表。 |
解释
Magic:Magic 字段是用于识别 ELF 文件的标识符。在文件的开头有一个固定的字节序列 7f 45 4c 46,表示文件为 ELF 格式,后续的字段提供更多的文件信息。
Class:表示 ELF 文件的架构,ELF64 表示这是一个 64 位的 ELF 文件。
Data:指示文件中的数据使用的字节序,2's complement, little endian 表示采用小端字节序。
Type:表示文件的类型,REL 表示这是一个可重定位文件(通常是目标文件 .o)。
Machine:表示目标机器架构,Advanced Micro Devices X86-64 表示该 ELF 文件是为 x86-64 架构编译的。
Version:ELF 文件的版本号,通常为 0x1。
Entry point address:表示程序的入口地址,目标文件通常没有入口点,所以此处为 0x0。
Program Header Table 和 Section Header Table 的偏移:Start of program headers 为 0,表示没有程序头表(因为这是一个目标文件);Start of section headers 为 1184 字节,表示节头表从文件的 1184 字节开始。
Size of headers:Size of this header 为 64 字节,表示 ELF 文件头的大小;Size of section headers 为 64 字节,表示每个节头表项的大小。
Number of section headers:Number of section headers 为 14,表示文件中包含 14 个节头表项。
Section header string table index:Section header string table index 为 13,表示节头表的字符串表在第 13 个节头表项中。
段(Segment)与节(Section)
1
| elf 中, 段与节是两个完全截然不同的概念。 后续有遇到再描述。
|
节(Section)是文件中的概念, 段(Segment)是运行时的概念。
翻译也有问题, 先留个坑吧
命令查看
1
2
| objdump -h SimpleSection.o // 只能查看主要段
readelf -S SimpleSection.o // 查看所有段
|
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
| xm@xm:~/xx/complier$ readelf -S SimpleSection.o
There are 14 section headers, starting at offset 0x4a0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000005f 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000380
0000000000000078 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000a0
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000a8
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000a8
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000ac
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000d8
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 000000d8
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 000000f8
0000000000000058 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 000003f8
0000000000000030 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000150
00000000000001b0 0000000000000018 12 12 8
[12] .strtab STRTAB 0000000000000000 00000300
000000000000007a 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000428
0000000000000074 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)
|
对应头文件 vim /usr/include/elf.h
1
2
3
4
5
6
7
8
9
10
11
12
13
| typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
|
表格整理
readelf -S
| Nr | Name | Type | Address | Offset | Size | EntSize | Flags | Link | Info | Align | 描述 |
|---|
| 0 | | NULL | 0x00000000 | 0x000000 | 0x000000 | 0x000000 | | 0 | 0 | 0 | 空节,占位用,表示索引 0 的节无实际内容。 |
| 1 | .text | PROGBITS | 0x00000000 | 0x000040 | 0x00005F | 0x000000 | AX | 0 | 0 | 1 | 包含可执行代码的段,存储程序的机器指令。 |
| 2 | .rela.text | RELA | 0x00000000 | 0x000380 | 0x000078 | 0x000018 | I | 11 | 1 | 8 | .text 段的重定位表,存储需要修正的符号地址及相关信息。 |
| 3 | .data | PROGBITS | 0x00000000 | 0x0000A0 | 0x000008 | 0x000000 | WA | 0 | 0 | 4 | 存储已初始化的全局变量和静态变量值,加载后可修改。 |
| 4 | .bss | NOBITS | 0x00000000 | 0x0000A8 | 0x000004 | 0x000000 | WA | 0 | 0 | 4 | 未初始化数据段,仅分配内存但不占用文件空间。 |
| 5 | .rodata | PROGBITS | 0x00000000 | 0x0000A8 | 0x000004 | 0x000000 | A | 0 | 0 | 1 | 只读数据段,存储不可修改的数据,如字符串字面量和常量。 |
| 6 | .comment | PROGBITS | 0x00000000 | 0x0000AC | 0x00002C | 0x000001 | MS | 0 | 0 | 1 | 编译器注释信息段,通常包含编译器版本号等信息。 |
| 7 | .note.GNU-stack | PROGBITS | 0x00000000 | 0x0000D8 | 0x000000 | 0x000000 | | 0 | 0 | 1 | 标识栈的权限属性的段,通常不含内容。 |
| 8 | .note.gnu.propert | NOTE | 0x00000000 | 0x0000D8 | 0x000020 | 0x000000 | A | 0 | 0 | 8 | 包含与二进制文件相关的属性信息,例如安全性标志或平台特定的元数据。 |
| 9 | .eh_frame | PROGBITS | 0x00000000 | 0x0000F8 | 0x000058 | 0x000000 | A | 0 | 0 | 8 | 异常处理框架的段,用于支持堆栈展开和 C++ 异常处理。 |
| 10 | .rela.eh_frame | RELA | 0x00000000 | 0x0003F8 | 0x000030 | 0x000018 | I | 11 | 9 | 8 | .eh_frame 的重定位表,用于修正异常处理相关的符号引用。 |
| 11 | .symtab | SYMTAB | 0x00000000 | 0x000150 | 0x0001B0 | 0x000018 | | 12 | 12 | 8 | 符号表,列出了 ELF 文件中的所有符号及其属性。 |
| 12 | .strtab | STRTAB | 0x00000000 | 0x000300 | 0x00007A | 0x000000 | | 0 | 0 | 1 | 字符串表,存储符号表中的符号名及其他字符串。 |
| 13 | .shstrtab | STRTAB | 0x00000000 | 0x000428 | 0x000074 | 0x000000 | | 0 | 0 | 1 | 节名称字符串表,存储 ELF 文件中所有节名称。 |
elf.h 简述
Elf32_Shdr 字段 | readelf -S 表头 | 描述 |
|---|
sh_name | Name | 节名称的索引,指向 .shstrtab 字符串表中对应节名的位置。 |
sh_type | Type | 节类型,描述节的内容和用途,如 PROGBITS、SYMTAB、RELA 等。 |
sh_flags | Flags | 节标志,描述节的属性,如是否可写 (W)、是否可执行 (X)、是否需要分配内存 (A) 等。 |
sh_addr | Address | 节在内存中的加载地址,如果未分配(如 NOBITS 段),则为 0x0。 |
sh_offset | Offset | 节在文件中的偏移量,表示从文件开头到节内容的起始位置的字节数。 |
sh_size | Size | 节的大小(以字节为单位),如果节为空则为 0。 |
sh_entsize | EntSize | 如果节包含固定大小的条目(如符号表),表示每个条目的大小;否则为 0。 |
sh_link | Link | 节的关联节索引,具体用途取决于节的类型(如符号表关联到字符串表)。 |
sh_info | Info | 额外信息字段,含义取决于节的类型(如 .symtab 的此字段表示局部符号的数目)。 |
sh_addralign | Align | 节的对齐要求,表示节在内存或文件中起始地址必须满足的对齐字节数(如 4、8 等)。 |
| 字段名 | 经典值 | 说明 |
|---|
sh_name | 索引值 | 表示节名称在 .shstrtab 中的偏移值,例如: |
| | 0x00 | 无名称的节(如第 0 节)。 |
| | 0x01 | 对应节名为 .text。 |
| | 0x0A | 对应节名为 .data。 |
sh_type | 0x0 (NULL) | 无意义的节,例如第 0 节。 |
| | 0x1 (PROGBITS) | 包含程序代码或数据的节。 |
| | 0x2 (SYMTAB) | 符号表,包含 ELF 文件的符号信息。 |
| | 0x3 (STRTAB) | 字符串表,存储符号名称等字符串。 |
| | 0x8 (NOBITS) | 未初始化数据段(如 .bss)。 |
sh_flags | 0x1 (SHF_WRITE) | 节内容可写。 |
| | 0x2 (SHF_ALLOC) | 节内容需要加载到内存中。 |
| | 0x4 (SHF_EXECINSTR) | 节内容包含可执行指令。 |
| | 0x30 (SHF_MERGE + SHF_STRINGS) | 节内容可以合并且包含字符串(如 .comment)。 |
sh_addr | 地址值 | 节加载到内存的地址,例如: |
| | 0x00000000 | 非加载段,通常用于调试信息或注释。 |
| | 0x08048000 | 可执行文件的起始代码段地址。 |
sh_offset | 偏移值 | 节在文件中的偏移量,例如: |
| | 0x40 | 表示从文件开始处偏移 0x40 字节是此节的起始位置。 |
sh_size | 字节数 | 节的大小,例如: |
| | 0x100 | 节的大小为 256 字节。 |
| | 0x0 | 节无内容,例如 .note.GNU-stack。 |
sh_entsize | 0x0 | 节中条目大小未知或无固定大小。 |
| | 0x10 | 节中每个条目大小为 16 字节,例如符号表中的条目。 |
sh_link | 节索引值 | 依赖节的索引,例如: |
| | 0x0B | .symtab 的字符串表在 .strtab(索引 0x0B)。 |
| | 0x00 | 无关联的节。 |
sh_info | 数值 | 节的额外信息,例如: |
| | 0x0A | .symtab 中局部符号的数量。 |
| | 0x00 | 不需要额外信息。 |
sh_addralign | 对齐值 | 节在内存中对齐的字节数,例如: |
| | 0x1 | 字节对齐,无特别对齐要求。 |
| | 0x8 | 8 字节对齐,常用于 .data、.bss。 |
| | 0x10 | 16 字节对齐,常用于 SIMD 指令相关的数据段。 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // SimpleSection.c 文件示例
int printf(const char* format, ...);
int global_init_var = 84;
int global_uint_var;
void func1(int i) {
printf("%d\n", i);
}
int main(void) {
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}
|
1
2
3
| objdump -s -d SimpleSection.o
// -s 显示目标文件中所有段的内容(以十六进制和 ASCII 格式显示)。
// -d 反汇编文件中的可执行代码部分,显示对应的汇编指令。
|
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
| xm@xm:~/xx/complier$ objdump -s -d SimpleSection.o
SimpleSection.o: file format elf64-x86-64
Contents of section .text:
0000 f30f1efa 554889e5 4883ec10 897dfc8b ....UH..H....}..
0010 45fc89c6 488d3d00 000000b8 00000000 E...H.=.........
0020 e8000000 0090c9c3 f30f1efa 554889e5 ............UH..
0030 4883ec10 c745f801 0000008b 15000000 H....E..........
0040 008b0500 00000001 c28b45f8 01c28b45 ..........E....E
0050 fc01d089 c7e80000 00008b45 f8c9c3 ...........E...
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 25640a00 %d..
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520392e .GCC: (Ubuntu 9.
0010 342e302d 31756275 6e747531 7e32302e 4.0-1ubuntu1~20.
0020 30342e32 2920392e 342e3000 04.2) 9.4.0.
Contents of section .note.gnu.property:
0000 04000000 10000000 05000000 474e5500 ............GNU.
0010 020000c0 04000000 03000000 00000000 ................
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 28000000 00450e10 8602430d ....(....E....C.
0030 065f0c07 08000000 1c000000 3c000000 ._..........<...
0040 00000000 37000000 00450e10 8602430d ....7....E....C.
0050 066e0c07 08000000 .n......
Disassembly of section .text:
0000000000000000 <func1>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 83 ec 10 sub $0x10,%rsp
c: 89 7d fc mov %edi,-0x4(%rbp)
f: 8b 45 fc mov -0x4(%rbp),%eax
12: 89 c6 mov %eax,%esi
14: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1b <func1+0x1b>
1b: b8 00 00 00 00 mov $0x0,%eax
20: e8 00 00 00 00 callq 25 <func1+0x25>
25: 90 nop
26: c9 leaveq
27: c3 retq
0000000000000028 <main>:
28: f3 0f 1e fa endbr64
2c: 55 push %rbp
2d: 48 89 e5 mov %rsp,%rbp
30: 48 83 ec 10 sub $0x10,%rsp
34: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
3b: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 41 <main+0x19>
41: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 47 <main+0x1f>
47: 01 c2 add %eax,%edx
49: 8b 45 f8 mov -0x8(%rbp),%eax
4c: 01 c2 add %eax,%edx
4e: 8b 45 fc mov -0x4(%rbp),%eax
51: 01 d0 add %edx,%eax
53: 89 c7 mov %eax,%edi
55: e8 00 00 00 00 callq 5a <main+0x32>
5a: 8b 45 f8 mov -0x8(%rbp),%eax
5d: c9 leaveq
5e: c3 retq
|
.text 代码段
1
2
3
| [ 1] .text PROGBITS 0000000000000000 00000040
000000000000005f 0000000000000000 AX 0 0 1
|
| 字段 | 值 | 说明 |
|---|
| 段索引号 | [ 1 ] | ELF 文件段表的第一个条目 |
| 段名 | .text | 存储程序的可执行指令 |
| 类型 | PROGBITS | 段包含程序的实际代码或数据 |
| 虚拟地址 | 0000000000000000 | 段在内存中的起始虚拟地址(目标文件为 0) |
| 文件偏移 | 00000040 | 段在 ELF 文件中的起始位置,偏移 64 字节 |
| 段大小 | 000000000000005f | 段大小为 95 字节 |
| 对齐要求 | 1 | 起始地址可以是任意字节边界 |
| 标志 | AX | A: 可加载到内存;X: 可执行 |
代码段没有什么好细聊的, 可以看看上面针对 .txt 的反汇编
.data 数据段
1
2
| [ 3] .data PROGBITS 0000000000000000 000000a0
0000000000000008 0000000000000000 WA 0 0 4
|
- 数据段段存储已初始化的全局变量和静态变量
如何查看 .data 数据段的值?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| xm@xm:~/xx/complier$ objdump -s -j .data SimpleSection.o
SimpleSection.o: file format elf64-x86-64
Contents of section .data:
0000 54000000 55000000 T...U...
------
54000000 转换为十进制是 84,可能对应变量 global_init_var 的初始化值。
55000000 转换为十进制是 85,可能对应 static_var 或其他静态变量的初始化值。
---
int global_init_var = 84; // 这个变量初始化为 84
static int static_var = 85; // 这个变量初始化为 85
|
- 它的内容会在程序运行时加载到内存,并在程序结束时释放。
- 修改后的值存储在进程的 内存空间 中,具体是在程序的 数据段。
- 代码段(.text)和只读数据段(.rodata):多个进程共享一份内存,节省内存资源。
- 数据段(.data)和 BSS 段:每个进程有独立的一份数据,它们存储各自的全局变量和静态变量。
- 堆和栈:每个进程都有独立的堆和栈,用于存储局部变量、动态分配内存等。
- COW(copy on write 机制):当进程需要修改共享部分内存时,操作系统才会为该进程分配新的内存空间。
- 每个进程都有自己独立的 .data 段,在程序执行期间,静态变量的值存储在该进程的 .data 区域中。如果静态变量在运行时被修改,新的值就会写入到该进程的数据段中。
rodata 只读数据段
1
2
| [ 5] .rodata PROGBITS 0000000000000000 000000a8
0000000000000004 0000000000000000 A 0 0 1
|
1
2
3
4
5
6
| xm@xm:~/xx/complier$ objdump -s -j .rodata SimpleSection.o
SimpleSection.o: file format elf64-x86-64
Contents of section .rodata:
0000 25640a00 %d..
|
代码中没有显式定义 const 数据,但编译器可能将常量字符串(如格式化字符串 %d\n)自动放置在 .rodata 段中,因为它是只读的。编译器会将所有不需要修改的字符串或数据(如常量字符串)放入只读数据段 .rodata。
.bss 段
用于存储 未初始化的全局变量 和 静态变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // bss 段只有4字节, 只有一个变量
[ 4] .bss NOBITS 0000000000000000 000000a8
0000000000000004 0000000000000000 WA 0 0 4
xm@xm:~/xx/complier$ nm -S SimpleSection.o
0000000000000000 0000000000000028 T func1
0000000000000000 0000000000000004 D global_init_var
U _GLOBAL_OFFSET_TABLE_
0000000000000004 0000000000000004 C global_uint_var
0000000000000028 0000000000000037 T main
U printf
0000000000000004 0000000000000004 d static_var.1920
0000000000000000 0000000000000004 b static_var2.1921 // static_var2 被放入bss 段
|
问题说明:
- 按道理来说, static_var2 和 global_uninit_var 都应该放在 .bss 段, 现在却是有static_var2 被放入。
- 与编译器实现有关:有些会将
全局的未初始化变量 存放到 .bss 段, 有些则不会, 仅仅只预留一个 未定义的全局变量符号, 后续 common 块章节展开。 global_uint_var 被置为 common, 因为强弱符号的关系, 现在不能确定该弱符号的大小(4 or 8 字节) - 若有一个变量,static int static_global_var; // 未初始化,放入.bss
static int a = 1; static int b = 0; 像 static int a = 1; 这样的变量被显式初始化为非零值,因此会被放入 .data 段。像 static int b = 0; 或 static int c;(未初始化,默认值为零)的变量,会被放入 .bss 段。
重定位表
1
2
3
4
5
6
7
| readelf -S SimpleSection.o
[ 2] .rela.text RELA 0000000000000000 00000380
0000000000000078 0000000000000018 I 11 1 8
[10] .rela.eh_frame RELA 0000000000000000 000003f8
0000000000000030 0000000000000018 I 11 9 8
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| xm@xm:~/xx/complier$ readelf -r SimpleSection.o
Relocation section '.rela.text' at offset 0x380 contains 5 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000017 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
000000000021 001000000004 R_X86_64_PLT32 0000000000000000 printf - 4
00000000003d 000300000002 R_X86_64_PC32 0000000000000000 .data + 0
000000000043 000400000002 R_X86_64_PC32 0000000000000000 .bss - 4
000000000056 000e00000004 R_X86_64_PLT32 0000000000000000 func1 - 4
Relocation section '.rela.eh_frame' at offset 0x3f8 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + 28
|
- rela.text 中对
printf & func1 的调用重定位。 - .data 段中暂时没有绝对地址引用, 所以没有重定位表。 若后续有, 则也会存在。
字符串表 strtab & shstrtab
1
2
3
4
5
| [12] .strtab STRTAB 0000000000000000 00000300
000000000000007a 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000428
0000000000000074 0000000000000000 0 0 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
| xm@xm:~/xx/complier$ readelf -x .strtab SimpleSection.o // 字符串表
Hex dump of section '.strtab':
0x00000000 0053696d 706c6553 65637469 6f6e2e63 .SimpleSection.c
0x00000010 00737461 7469635f 7661722e 31393230 .static_var.1920
0x00000020 00737461 7469635f 76617232 2e313932 .static_var2.192
0x00000030 3100676c 6f62616c 5f696e69 745f7661 1.global_init_va
0x00000040 7200676c 6f62616c 5f75696e 745f7661 r.global_uint_va
0x00000050 72006675 6e633100 5f474c4f 42414c5f r.func1._GLOBAL_
0x00000060 4f464653 45545f54 41424c45 5f007072 OFFSET_TABLE_.pr
0x00000070 696e7466 006d6169 6e00 intf.main.
xm@xm:~/xx/complier$ readelf -x .shstrtab SimpleSection.o // 段表字符串表
Hex dump of section '.shstrtab':
0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
0x00000010 002e7368 73747274 6162002e 72656c61 ..shstrtab..rela
0x00000020 2e746578 74002e64 61746100 2e627373 .text..data..bss
0x00000030 002e726f 64617461 002e636f 6d6d656e ..rodata..commen
0x00000040 74002e6e 6f74652e 474e552d 73746163 t..note.GNU-stac
0x00000050 6b002e6e 6f74652e 676e752e 70726f70 k..note.gnu.prop
0x00000060 65727479 002e7265 6c612e65 685f6672 erty..rela.eh_fr
0x00000070 616d6500
|
| 表名 | strtab | shstrtab |
|---|
| 全名 | 字符串表(String Table) | 节头字符串表(Section Header String Table) |
| 作用 | 存储符号名、字符串等(如函数名、变量名)。用于符号表(.symtab)等节的符号名称。 | 存储节(section)的名称,供节头表(.shstrtab)使用。 |
| 内容 | 包含 ELF 文件中的符号名(如函数名、全局变量名)。 | 包含 ELF 文件中所有节的名称(如 .text、.data、.bss 等)。 |
| 引用 | 被符号表(如 .symtab)中的条目引用,指定符号的名称。 | 被节头表中的每个节引用,指定每个节的名称。 |
| 存储格式 | 每个符号名称后跟一个 null 字符(\0)。 | 每个节名称后跟一个 null 字符(\0)。 |
| 位置 | 位于 .strtab 节中。 | 位于 .shstrtab 节中。 |
| 使用场景 | 用于符号表中符号的名字引用,链接器、调试器等工具通过它查找符号名称。 | 用于识别 ELF 文件中的不同节,链接器和调试器通过它识别和处理不同的节。 |
| 示例 | .text、main、printf 等符号名。 | .text、.data、.bss 等节名。 |
.symtab 符号表
具体见下一大章节。
4. 聊聊符号表
基础结构
1
2
3
4
5
6
7
8
9
10
11
| // vim /usr/include/elf.h
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
|
符号表 和 字符串表
| 特性 | symtab 符号表 | strtab 字符串表 |
|---|
| 内容 | 存储符号的描述信息(类型、地址、大小、绑定等) | 存储符号的名称(字符串) |
| 目的 | 管理符号的信息,供链接器、调试器等使用 | 存储符号名称的实际字符串,符号表通过索引引用它们 |
| 格式 | 每个条目包含符号的元数据(结构体形式) | 字符串以 null 结尾的字符序列,连续存储 |
| 是否包含符号名 | 不直接包含符号名称,而是通过索引引用 strtab 中的字符串 | 直接包含符号名称作为字符串 |
| 索引关系 | 符号表中的每个条目都包含一个指向 strtab 中字符串的索引 | 无索引,它只是存储字符串的地方 |
查看字符表内容
1
2
3
4
5
6
7
8
9
| xm@xm:~/xx/complier$ nm SimpleSection.o
0000000000000000 T func1
0000000000000000 D global_init_var
U _GLOBAL_OFFSET_TABLE_
0000000000000004 C global_uint_var
0000000000000028 T main
U printf
0000000000000004 d static_var.1920
0000000000000000 b static_var2.1921
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| xm@xm:~/xx/complier$ readelf -s SimpleSection.o
Symbol table '.symtab' contains 18 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS SimpleSection.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.1920
7: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.1921
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 9
11: 0000000000000000 0 SECTION LOCAL DEFAULT 6
12: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
13: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uint_var
14: 0000000000000000 40 FUNC GLOBAL DEFAULT 1 func1
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
17: 0000000000000028 55 FUNC GLOBAL DEFAULT 1 main
|
字段说明:
- Num:符号在符号表中的编号。
- Value:符号的值,通常是地址或其他数据。例如,变量的地址或函数的起始地址。
- Size:符号的大小,以字节为单位。函数的大小表示其代码的长度,变量的大小表示其内存占用。
- Type:符号的类型:
NOTYPE:没有类型(通常为文件符号或无类型符号)。FILE:表示源文件符号。SECTION:表示一个节(节号)。OBJECT:表示符号是一个对象,通常是变量。FUNC:表示符号是一个函数。
- Bind:符号的绑定类型:
LOCAL:符号是本地的,仅在当前文件中可见。GLOBAL:符号是全局的,可以被其他文件访问。COM:符号是公共的(通常用于未初始化的全局变量)。UND:符号未定义,通常是外部符号。
- Vis:符号的可见性,通常是
DEFAULT,表示符号在程序中的默认可见性。 - Ndx:符号所属的节(段)。
ABS 表示符号是一个绝对值,UND 表示符号未定义。 数字表示段号, 具体可在readelf -S SimpleSection.o 中找到对应段。 3 -> .data 段。 - Name:符号的名称,如函数名、变量名等。
readelf -s SimpleSection.o 符号表解析
| Num | Value | Size | Type | Bind | Vis | Ndx | Name | 说明 |
|---|
| 0 | 0000000000000000 | 0 | NOTYPE | LOCAL | DEFAULT | UND | (none) | 无类型符号,通常是文件头或不定义的符号。 |
| 1 | 0000000000000000 | 0 | FILE | LOCAL | DEFAULT | ABS | SimpleSection.c | 文件符号,表示源文件 SimpleSection.c。 |
| 2 | 0000000000000000 | 0 | SECTION | LOCAL | DEFAULT | 1 | (none) | ELF 文件的第 1 节。 |
| 3 | 0000000000000000 | 0 | SECTION | LOCAL | DEFAULT | 3 | (none) | ELF 文件的第 3 节。 |
| 4 | 0000000000000000 | 0 | SECTION | LOCAL | DEFAULT | 4 | (none) | ELF 文件的第 4 节。 |
| 5 | 0000000000000000 | 0 | SECTION | LOCAL | DEFAULT | 5 | (none) | ELF 文件的第 5 节。 |
| 6 | 0000000000000004 | 4 | OBJECT | LOCAL | DEFAULT | 3 | static_var.1920 | 局部符号 static_var.1920,位于第 3 节,大小 4 字节。 |
| 7 | 0000000000000000 | 4 | OBJECT | LOCAL | DEFAULT | 4 | static_var2.1921 | 局部符号 static_var2.1921,位于第 4 节,大小 4 字节。 |
| 8 | 0000000000000000 | 0 | SECTION | LOCAL | DEFAULT | 7 | (none) | ELF 文件的第 7 节。 |
| 9 | 0000000000000000 | 0 | SECTION | LOCAL | DEFAULT | 8 | (none) | ELF 文件的第 8 节。 |
| 10 | 0000000000000000 | 0 | SECTION | LOCAL | DEFAULT | 9 | (none) | ELF 文件的第 9 节。 |
| 11 | 0000000000000000 | 0 | SECTION | LOCAL | DEFAULT | 6 | (none) | ELF 文件的第 6 节。 |
| 12 | 0000000000000000 | 4 | OBJECT | GLOBAL | DEFAULT | 3 | global_init_var | 全局变量 global_init_var,位于第 3 节,大小 4 字节。 |
| 13 | 0000000000000004 | 4 | OBJECT | GLOBAL | COM | 3 | global_uint_var | 公共符号(未初始化)global_uint_var,大小 4 字节。 |
| 14 | 0000000000000000 | 40 | FUNC | GLOBAL | DEFAULT | 1 | func1 | 全局函数 func1,位于第 1 节,大小 40 字节。 |
| 15 | 0000000000000000 | 0 | NOTYPE | GLOBAL | DEFAULT | UND | _GLOBAL_OFFSET_TABLE_ | 未定义符号 _GLOBAL_OFFSET_TABLE_,通常是由链接器生成的。 |
| 16 | 0000000000000000 | 0 | NOTYPE | GLOBAL | DEFAULT | UND | printf | 外部符号 printf,来自外部库(如 libc)。 |
| 17 | 0000000000000028 | 55 | FUNC | GLOBAL | DEFAULT | 1 | main | 全局函数 main,位于第 1 节,大小 55 字节。 |
总结:
从符号表中,我们可以看到目标文件 SimpleSection.o 包含了多个符号,包括:
- 局部符号:如
static_var.1920 和 static_var2.1921,这些是局部静态变量。 - 全局符号:如
global_init_var、global_uint_var、func1 和 main,它们在其他文件中可能被引用。 - 未定义符号:如
_GLOBAL_OFFSET_TABLE_ 和 printf,这些符号会在链接阶段解析。
对应关系说明
Elf64_Sym 字段 | readelf -s 输出字段 | 说明 |
|---|
| st_name | Name | 符号的名称(字符串表中的索引) |
| st_value | Value | 符号的值或地址 |
| st_size | Size | 符号的大小 |
| st_info | Type, Bind | 符号的类型和绑定信息(Type 和 Bind) |
| st_other | — | 符号的其他信息,通常用于符号的可见性(Vis) |
| st_shndx | Ndx | 符号所在的节的索引 |
具体例子
以符号 main 为例:
Elf64_Sym 字段 | readelf -s 输出字段 | 示例值 |
|---|
| st_name | Name | main |
| st_value | Value | 0x28 |
| st_size | Size | 55 |
| st_info | Type, Bind | FUNC / GLOBAL |
| st_other | — | — |
| st_shndx | Ndx | 1 |