本文主要整理与学习了 ELF 文件的基本格式, 使用 readelf
, gdb
, objdump
等工具实际分析了 ELF 部分节区的字段细节, 但受于篇幅限制, 未对与 ELF 关联甚深的程序动静态链接、加载过程进行深入分析, 因此本文仅作为 ELF 基本结构的一个基本介绍与入门.
ELF 简介
可执行与可链接格式 ELF (Extensible Linking Format) 是一种用于可执行文件、目标代码、共享库和内核转储的通用标准文件格式, 主要在类Unix系统中使用. ELF 格式具有灵活性、可扩展性和跨平台性, 能在许多不同的硬件平台上被许多不同的操作系统所采用.
ELF 的主要使用场景如下:
-
可执行文件; 当运行 Linux Shell 上的各种命令时(
ls
,grep
) 时, 都是执行的 ELF 格式的文件. 其包含程序的机器代码和必要的元数据, 定义入口点、程序头表等执行所需信息, 加载器(loader)能直接将其映射到内存并执行. -
动态库/共享库(
.so
文件); 共享库允许多个程序共享同一份代码,节省内存. 其支持动态链接,程序运行时才加载所需库- 动态库与共享库本质是两个名称的一个东西, 例如在 Windows 下的
.dll
动态链接库 Dynamic Link Library, 在 Linux/Unix 下的.so
共享对象(Shared Object)
- 动态库与共享库本质是两个名称的一个东西, 例如在 Windows 下的
-
目标文件(
.o
文件); 目标文件是那些编译但未链接的中间文件, 其包含编译后的机器代码, 但不能直接执行, 其保存了符号表、重定位信息等待链接器处理, 链接器最终将多个.o文件合并为可执行文件或共享库; 目标文件允许分离编译,提高构建大型项目的效率 -
静态库文件(
.a
文件); 静态库本质上是多个目标文件(.o
)的归档集合, 在链接时相关代码会被直接复制到最终的可执行文件中, 因此部署更加简单, 不依赖外部库 -
内核模块(
.ko
文件); 内核模块作为 ELF 文件包含了特殊的段和符号信息, 如.modinfo
节包含模块元数据(作者、描述、版本等),.module_layout
存储内核版本和布局信息,.symtab
和.strtab
包含大量内核特定符号等 -
内核转储文件(coredump); 系统崩溃或程序异常终止时生成的调试信息, 记录程序崩溃时的内存状态、寄存器值等信息
ELF 文件基本结构
ELF 格式的文件可能既会参与程序链接又会参与程序执行, 根据使用过程的不同, 其被设计为同时拥有两种并行的视图:
-
链接视图(Linking View): 主要用于链接器处理, 关注节(sections)
-
执行视图(Execution View): 主要用于加载器处理, 关注段(segments)

总的来说, ELF 可以分为四大部分: ELF Header, ELF Program Header Table (或称Program Headers、程序头)、ELF Section Header Table (或称Section Headers、节头表)以及若干ELF Sections.
- 尽管图中是按照 ELF 头, 程序头部表、节区、节区头部表的顺序排列的, 但实际上除了 ELF 头部表以外, 其它部分都没有严格的的顺序.

readelf
是一个强大的命令行工具, 用于分析 ELF 文件的内容. 通过 readelf
, 你可以查看可执行文件、共享库、目标文件的内部结构细节.
readelf 的基本语法是: readelf [options] elffile
, 常用选项为:
-
-h
; 显示 ELF header 中的信息 -
-S
; 列出文件的所有节, 显示 Section headers 的相关信息(Name, Type, Address 以及 Offset 等) -
-l
; 列出文件的程序头表以及所有段 -
-s
; 显示文件的符号表信息, 包括函数名、全局变量等及其地址 -
-r
; 显示重定位信息, 涉及动态链接过程 -
-d
; 显示动态段的内容, 包括共享库依赖、符号表位置等. -
-n
; 显示文件中的注释段内容 -
-a
; 显示文件的所有信息 -
-x <number or name>
; 以 16 进制查看特定节的内容
更多的信息可以通过man
查看 readelf
指令的说明.
ELF Header
ELF 头部的 ELF Header 给出了整个文件的组织情况, 使用 readelf -h
可以看到对应的内容. 使用 g++
不加任何额外选项的编译一段简单的 C代码, 生成的可执行文件的头信息如下:
1$ readelf -h a.out
2ELF Header:
3 Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
4 Class: ELF64
5 Data: 2's complement, little endian
6 Version: 1 (current)
7 OS/ABI: UNIX - System V
8 ABI Version: 0
9 Type: DYN (Position-Independent Executable file)
10 Machine: Advanced Micro Devices X86-64
11 Version: 0x1
12 Entry point address: 0x2210
13 Start of program headers: 64 (bytes into file)
14 Start of section headers: 55040 (bytes into file)
15 Flags: 0x0
16 Size of this header: 64 (bytes)
17 Size of program headers: 56 (bytes)
18 Number of program headers: 13
19 Size of section headers: 64 (bytes)
20 Number of section headers: 32
21 Section header string table index: 31
其中比较重要的字段:
- 魔数
Magic
; 作为 ELF 文件的签名, 总是以固定的字节序列开始: 0x7F后跟ASCII码"ELF"(45 4c 46). 操作系统通过检查这个魔数来确认文件是ELF格式. 后面的字节包含了其他元信息如位宽、字节序等.
每种可执行文件的格式的开头几个字节都是很特殊的, 特别是开头4个字节, 通常被称为魔数(Magic Number). 通过对魔数的判断可以确定文件的格式和类型.
ELF的可执行文件格式的头4个字节为0x7F、e、l、f; Java的可执行文件格式的头4个字节为c、a、f、e; 如果被执行的是Shell脚本或perl、python等解释型语言的脚本, 那么它的第一行往往是#!/bin/sh
或#!/usr/bin/perl
或#!/usr/bin/python
, 此时前两个字节#
和!
就构成了魔数, 系统一旦判断到这两个字节, 就对后面的字符串进行解析, 以确定具体的解释程序路径(该特性称为
Shebang).
-
文件类型
Type
: ELF 文件主要有四种类型: 可重定位文件ET_REL
、可执行文件ET_EXEC
, 共享目标文件ET_DYN
、核心转储文件ET_CORE
. 但是可以看到, 这里我们使用 g++ 生成的是一个DYN
类型的文件, 括号里进一步说明这是一个 PIE 程序, 这不是一个传统的EXEC
类型的可执行文件, 这是由于安全考虑的演进结果.-
问题背景: 传统上, 可执行文件是
EXEC
类型, 它们有固定的加载地址. 但随着安全威胁的增加, 特别是缓冲区溢出和返回导向编程(ROP)攻击, 操作系统引入了地址空间布局随机化(ASLR)作为防御机制. 但 ASLR 最初只能随机化共享库和堆栈的位置, 而传统 EXEC 类型的可执行文件主体依然加载在固定地址, 成为攻击者的目标. 为解决这个问题, PIE(位置无关可执行文件)被开发出来, 允许整个程序(包括代码段)被随机加载. -
工作原理: 当 g++ 生成 PIE 可执行文件时,其使用相对寻址而非绝对寻址, 编译所有代码为位置无关代码(PIC), 并将可执行文件标记为 DYN 类型
-
程序加载: 操作系统加载器看到 DYN 类型的可执行文件时, 会将其当作共享库一样处理: 随机选择一个基地址, 然后相应调整所有内部引用. 这种行为很像动态库, 因此这些文件被标记为 DYN 类型, 尽管它们实际上是可执行文件.
-
使用
g++ -no-pie
选项进行编译程序, 即可编译一个传统的EXEC
类型的可执行文件.
-
-
兼容性相关字段, 包括
Machine
目标架构,Class
类别,Data
数据编码等 -
该ELF 文件结构相关字段, 包括程序头表、节头表的偏移量、长度与数量等
程序头表
如果程序头部表(Program Header Table)存在的话, 它会告诉系统如何创建进程. 用于生成进程的目标文件必须具有程序头部表, 但是重定位文件不需要这个表.
一个可执行文件(PIE)的程序头表示例如下:
1$ readelf -l a.out
2
3Elf file type is DYN (Position-Independent Executable file)
4Entry point 0x21f0
5There are 13 program headers, starting at offset 64
6
7Program Headers:
8 Type Offset VirtAddr PhysAddr
9 FileSiz MemSiz Flags Align
10 PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
11 0x00000000000002d8 0x00000000000002d8 R 0x8
12 INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318
13 0x000000000000001c 0x000000000000001c R 0x1
14 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
15 LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
16 0x0000000000001278 0x0000000000001278 R 0x1000
17 LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
18 0x0000000000003579 0x0000000000003579 R E 0x1000
19 LOAD 0x0000000000006000 0x0000000000006000 0x0000000000006000
20 0x0000000000001780 0x0000000000001780 R 0x1000
21 LOAD 0x0000000000007da0 0x0000000000008da0 0x0000000000008da0
22 0x0000000000000358 0x00000000007a15f0 RW 0x1000
23 DYNAMIC 0x0000000000007db0 0x0000000000008db0 0x0000000000008db0
24 0x0000000000000210 0x0000000000000210 RW 0x8
25 NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338
26 0x0000000000000040 0x0000000000000040 R 0x8
27 NOTE 0x0000000000000378 0x0000000000000378 0x0000000000000378
28 0x0000000000000044 0x0000000000000044 R 0x4
29 GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338
30 0x0000000000000040 0x0000000000000040 R 0x8
31 GNU_EH_FRAME 0x0000000000006068 0x0000000000006068 0x0000000000006068
32 0x0000000000000454 0x0000000000000454 R 0x4
33 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
34 0x0000000000000000 0x0000000000000000 RW 0x10
35 GNU_RELRO 0x0000000000007da0 0x0000000000008da0 0x0000000000008da0
36 0x0000000000000260 0x0000000000000260 R 0x1
37
38 Section to Segment mapping:
39 Segment Sections...
40 00
41 01 .interp
42 02 .interp .note.gnu.property .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
43 03 .init .plt .text .fini
44 04 .rodata .eh_frame_hdr .eh_frame .gcc_except_table
45 05 .init_array .fini_array .dynamic .got .got.plt .data .bss
46 06 .dynamic
47 07 .note.gnu.property
48 08 .note.ABI-tag .note.gnu.build-id
49 09 .note.gnu.property
50 10 .eh_frame_hdr
51 11
52 12 .init_array .fini_array .dynamic .got
每个程序头对应的字段含义如下:
字段 | 说明 |
---|---|
p_type | 该字段为段的类型,或者表明了该结构的相关信息 |
p_offset | 该字段给出了从文件开始到该段开头的第一个字节的偏移 |
p_vaddr | 该字段给出了该段第一个字节在内存中的虚拟地址 |
p_paddr | 该字段仅用于物理地址寻址相关的系统中 |
p_filesz | 该字段给出了文件镜像中该段的大小,可能为0 |
p_memsz | 该字段给出了内存镜像中该段的大小,可能为0 |
p_flags | 该字段给出了与段相关的标记 |
p_align | 该字段给出了段在文件以及内存中的对齐方式 |
段类型
段的类型 p_type
如下表所示.
需要注意, 程序头部表中的段类型又可以分为基本段和扩展段两大类:
-
基本段 是ELF规范最初定义的标准段类型, 在所有符合ELF标准的系统中都被支持和识别. 这些段类型构成了ELF可执行文件的基础结构, 对于程序的加载和执行是必不可少的. 基本段类型通常具有较小的类型值(0-7), 并在原始ELF规范中明确定义.
-
扩展段 是在ELF基本规范之外, 由特定操作系统、编译器或工具链引入的额外段类型. 这些段类型通常用于实现特定于平台的功能、安全特性或性能优化.
段类型 | 值 | 描述 | 用途 |
---|---|---|---|
PT_NULL | 0 | 未使用段 | 表示一个未使用的程序头部表项,系统会忽略该段 |
PT_LOAD | 1 | 可加载段 | 定义需要从文件映射到内存中的段,包含代码(.text)和数据(.data)等 |
PT_DYNAMIC | 2 | 动态链接信息段 | 包含动态链接器所需的信息,如共享库依赖、符号表位置等 |
PT_INTERP | 3 | 解释器段 | 包含程序解释器路径的字符串,通常指向动态链接器,如"/lib/ld-linux.so.2" |
PT_NOTE | 4 | 附加信息段 | 存储辅助信息,如版本、供应商信息、ABI 兼容性说明等 |
PT_SHLIB | 5 | 保留段 | 在当前 ELF 规范中未定义用途,保留供将来使用 |
PT_PHDR | 6 | 程序头部表段 | 包含程序头部表自身在内存中的位置和大小信息 |
PT_TLS | 7 | 线程局部存储段 | 包含线程局部变量的初始值和模板,用于线程专有数据 |
PT_GNU_EH_FRAME | 0x6474e550 | 异常处理框架段 | GNU 特有段类型,包含用于栈展开的异常处理信息 |
PT_GNU_STACK | 0x6474e551 | 栈权限段 | GNU 扩展,用于标记程序栈是否具有可执行权限 |
PT_GNU_RELRO | 0x6474e552 | 只读重定位段 | GNU 扩展,标记在初始化后应设为只读的段,增强安全性 |
PT_LOPROC | 0x70000000 | 处理器特定段下限 | 为处理器特定语义保留的段类型范围的下界 |
PT_HIPROC | 0x7fffffff | 处理器特定段上限 | 为处理器特定语义保留的段类型范围的上界 |
段地址
ELF 文件中程序段的 p_vaddr
表示该段在虚拟内存中的预期加载地址, 但这个地址通常不是最终的实际加载地址, 而是需要与基地址(Base Address)结合计算.
在讨论一个可执行程序实际的虚拟地址之前, 需要首先明确其是否使用了 PIC 位置无关代码技术:
-
对于那些使用绝对地址的代码, 程序在编译和链接时, 就假定它将在特定的虚拟地址空间中运行, 加载器也会尝试将程序段放置在它们指定的虚拟地址上.
-
但对于可能被加载到任何位置的共享库或是 PIC/PIE 程序而言, 其完全避免了绝对地址引用, 而是通过相对寻址的方式来访问自己的代码和数据, 使用全局偏移表(GOT)和过程链接表(PLT)处理动态符号, 段和段之间维持着相对位置关系即可.
因此, 程序段在内存中的实际地址计算公式为:
1实际加载地址 = 基地址 + (p_vaddr - 最小可加载段的p_vaddr)
- 基地址由动态加载器 (如 ld.so) 在加载时决定, 还会受到 ASLR、页对齐等机制的影响.
段标志位
当系统为可加载的段创建内存镜像时, 它会按照 p_flags
将段设置为对应的权限:
值 | 名称 | 含义 |
---|---|---|
0x1 | PF_X | 可执行段(Executable segment) |
0x2 | PF_W | 可写段(Writeable segment) |
0x4 | PF_R | 可读段(Readable segment) |
节区头表
节区头部表(Section Header Table)包含了描述文件节区的信息, 每个节区在表中都有一个表项, 会给出节区名称、节区大小等信息.
1$ readelf -S a.out
2There are 31 section headers, starting at offset 0xc558:
3
4Section Headers:
5 [Nr] Name Type Address Offset
6 Size EntSize Flags Link Info Align
7 [ 0] NULL 0000000000000000 00000000
8 0000000000000000 0000000000000000 0 0 0
9 [ 1] .interp PROGBITS 0000000000000318 00000318
10 000000000000001c 0000000000000000 A 0 0 1
11 [ 2] .note.gnu.pr[...] NOTE 0000000000000338 00000338
12 0000000000000040 0000000000000000 A 0 0 8
13 [ 3] .note.ABI-tag NOTE 0000000000000378 00000378
14 0000000000000020 0000000000000000 A 0 0 4
15 [ 4] .note.gnu.bu[...] NOTE 0000000000000398 00000398
16 0000000000000024 0000000000000000 A 0 0 4
17 [ 5] .gnu.hash GNU_HASH 00000000000003c0 000003c0
18 0000000000000024 0000000000000000 A 6 0 8
19 [ 6] .dynsym DYNSYM 00000000000003e8 000003e8
20 0000000000000360 0000000000000018 A 7 1 8
21 [ 7] .dynstr STRTAB 0000000000000748 00000748
22 000000000000067a 0000000000000000 A 0 0 1
23 [ 8] .gnu.version VERSYM 0000000000000dc2 00000dc2
24 0000000000000048 0000000000000002 A 6 0 2
25 [ 9] .gnu.version_r VERNEED 0000000000000e10 00000e10
26 00000000000000f0 0000000000000000 A 7 4 8
27 [10] .rela.dyn RELA 0000000000000f00 00000f00
28 00000000000000d8 0000000000000018 A 6 0 8
29 [11] .rela.plt RELA 0000000000000fd8 00000fd8
30 00000000000002a0 0000000000000018 AI 6 24 8
31 [12] .init PROGBITS 0000000000002000 00002000
32 000000000000001b 0000000000000000 AX 0 0 4
33 [13] .plt PROGBITS 0000000000002020 00002020
34 00000000000001d0 0000000000000010 AX 0 0 16
35 [14] .text PROGBITS 00000000000021f0 000021f0
36 000000000000337c 0000000000000000 AX 0 0 16
37 [15] .fini PROGBITS 000000000000556c 0000556c
38 000000000000000d 0000000000000000 AX 0 0 4
39 [16] .rodata PROGBITS 0000000000006000 00006000
40 0000000000000067 0000000000000000 A 0 0 8
41 [17] .eh_frame_hdr PROGBITS 0000000000006068 00006068
42 0000000000000454 0000000000000000 A 0 0 4
43 [18] .eh_frame PROGBITS 00000000000064c0 000064c0
44 00000000000011f8 0000000000000000 A 0 0 8
45 [19] .gcc_except_table PROGBITS 00000000000076b8 000076b8
46 00000000000000c8 0000000000000000 A 0 0 1
47 [20] .init_array INIT_ARRAY 0000000000008da0 00007da0
48 0000000000000008 0000000000000008 WA 0 0 8
49 [21] .fini_array FINI_ARRAY 0000000000008da8 00007da8
50 0000000000000008 0000000000000008 WA 0 0 8
51 [22] .dynamic DYNAMIC 0000000000008db0 00007db0
52 0000000000000210 0000000000000010 WA 7 0 8
53 [23] .got PROGBITS 0000000000008fc0 00007fc0
54 0000000000000028 0000000000000008 WA 0 0 8
55 [24] .got.plt PROGBITS 0000000000008fe8 00007fe8
56 00000000000000f8 0000000000000008 WA 0 0 8
57 [25] .data PROGBITS 00000000000090e0 000080e0
58 0000000000000018 0000000000000000 WA 0 0 8
59 [26] .bss NOBITS 0000000000009100 000080f8
60 00000000007a1290 0000000000000000 WA 0 0 32
61 [27] .comment PROGBITS 0000000000000000 000080f8
62 0000000000000036 0000000000000001 MS 0 0 1
63 [28] .symtab SYMTAB 0000000000000000 00008130
64 0000000000001578 0000000000000018 29 10 8
65 [29] .strtab STRTAB 0000000000000000 000096a8
66 0000000000002d84 0000000000000000 0 0 1
67 [30] .shstrtab STRTAB 0000000000000000 0000c42c
68 0000000000000128 0000000000000000 0 0 1
69Key to Flags:
70 W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
71 L (link order), O (extra OS processing required), G (group), T (TLS),
72 C (compressed), x (unknown), o (OS specific), E (exclude),
73 D (mbind), l (large), p (processor specific)
每个节区头对应的字段含义如下:
成员 | 说明 |
---|---|
sh_name | 节名称, 本质是一个数值, 作为节区头字符串表节区(Section Header String Table Section)的索引, 在该表中是一个以 NULL 结尾的字符串 |
sh_type | 节区类型 |
sh_flags | 每一bit代表不同的标志, 描述节是否可写, 可执行, 需要分配内存等属性 |
sh_addr | 如果节区将出现在进程的内存映像中, 此成员给出节区的第一个字节应该在进程镜像中的位置. 否则, 此字段为 0 |
sh_offset | 给出节区的第一个字节与文件开始处之间的偏移. SHT_NOBITS 类型的节区不占用文件的空间, 因此其 sh_offset 成员给出的是概念性的偏移 |
sh_size | 此成员给出节区的字节大小. 除非节区的类型是 SHT_NOBITS, 否则该节占用文件中的 sh_size 字节. 类型为 SHT_NOBITS 的节区长度可能非零, 不过却不占用文件中的空间 |
sh_link | 此成员给出节区头部表索引链接, 其具体的解释依赖于节区类型 |
sh_info | 此成员给出附加信息, 其解释依赖于节区类型. |
sh_addralign | 某些节区的地址需要对齐. 目前它仅允许为 0 以及 2 的正整数幂数, 0 和 1 表示没有对齐约束. |
sh_entsize | 某些节区中存在具有固定大小的表项的表, 如符号表. 对于这类节区, 该成员给出每个表项的字节大小. 反之, 此成员取值为 0. |
节区头部表中存在一些特殊的节区, 如:
-
SHN_UNDEF
(0), 表示未定义的节区 -
SHN_LORESERVE
(0xFF00), 表示保留区的下界,SHN_HIRESERVE
(0xFFFF), 表示保留区的上界- 系统保留在
SHN_LORESERVE
到SHN_HIRESERVE
之间(包含边界)的索引值,这些值不在节头表中引用, 节头表不包含保留索引项
- 系统保留在
节区类型
名称 | 取值 | 说明 |
---|---|---|
SHT_NULL | 0 | 该类型节区是非活动的, 这种类型的节头中的其它成员取值无意义. |
SHT_PROGBITS | 1 | 该类型节区包含程序定义的信息, 它的格式和含义都由程序来决定. |
SHT_SYMTAB | 2 | 该类型节区包含一个符号表(SYMbol TABle). 目前目标文件对每种类型的节区都只能包含一个, 不过这个限制将来可能发生变化. 一般, SHT_SYMTAB 节区提供用于链接编辑(指 ld 而言)的符号, 尽管也可用来实现动态链接. |
SHT_STRTAB | 3 | 该类型节区包含字符串表(STRing TABle). |
SHT_RELA | 4 | 该类型节区包含显式指定位数的重定位项(RELocation entry with Addends), 例如, 32 位目标文件中的 Elf32_Rela 类型. 此外, 目标文件可能拥有多个重定位节区. |
SHT_HASH | 5 | 该类型节区包含符号哈希表(HASH table). |
SHT_DYNAMIC | 6 | 该类型节区包含动态链接的信息(DYNAMIC linking). |
SHT_NOTE | 7 | 该类型节区包含以某种方式标记文件的信息(NOTE). |
SHT_NOBITS | 8 | 该类型节区不占用文件的空间, 其它方面和SHT_PROGBITS相似. 尽管该类型节区不包含任何字节, 其对应的节头成员sh_offset 中还是会包含概念性的文件偏移. |
SHT_REL | 9 | 该类型节区包含重定位表项(RELocation entry without Addends), 不过并没有指定位数. 例如, 32位目标文件中的 Elf32_rel 类型. 目标文件中可以拥有多个重定位节区. |
SHT_SHLIB | 10 | 该类型此节区被保留, 不过其语义尚未被定义. |
SHT_DYNSYM | 11 | 作为一个完整的符号表, 它可能包含很多对动态链接而言不必要的符号. 因此, 目标文件也可以包含一个 SHT_DYNSYM 节区, 其中保存动态链接符号的一个最小集合, 以节省空间. |
SHT_LOPROC | 0X70000000 | 此值指定保留给处理器专用语义的下界(LOw PROCessor-specific semantics). |
SHT_HIPROC | 0X7FFFFFFF | 此值指定保留给处理器专用语义的上界(HIgh PROCessor-specific semantics). |
SHT_LOUSER | 0X80000000 | 此值指定保留给应用程序的索引下界. |
SHT_HIUSER | 0X8FFFFFFF | 此值指定保留给应用程序的索引上界. |
节区标志位
节头中 sh_flags
字段的每一个比特位都可以给出其相应的标记信息, 其定义了对应的节区的内容是否可以被修改、被执行等信息.
名称 | 值 | 说明 |
---|---|---|
SHF_WRITE | 0x1 | 这种节包含了进程运行过程中可以被写的数据. |
SHF_ALLOC | 0x2 | 这种节在进程运行时占用内存. 对于不占用目标文件的内存镜像空间的某些控制节, 该属性处于关闭状态(off). |
SHF_EXECINSTR | 0x4 | 这种节包含可执行的机器指令(EXECutable INSTRuction). |
SHF_MASKPROC | 0xf0000000 | 所有在这个掩码中的比特位用于特定处理器语义. |
link 与 info 字段
当节区类型的不同的时候,sh_link
和 sh_info
也会具有不同的含义:
sh_type | sh_link | sh_info |
---|---|---|
SHT_DYNAMIC | 节区中使用的字符串表的节头索引 | 0 |
SHT_HASH | 此哈希表所使用的符号表的节头索引 | 0 |
SHT_REL/SHT_RELA | 与符号表相关的的节头索引 | 重定位应用到的节的节头索引 |
SHT_SYMTAB/SHT_DYNSYM | 操作系统特定信息, Linux 中的 ELF 文件中该项指向符号表中符号所对应的字符串节区在 Section Header Table 中的偏移. | 操作系统特定信息 |
other | SHN_UNDEF | 0 |
节区
节区部分包含在链接视图中要使用的大部分信息: 指令、数据、符号表、重定位信息等等.
下表罗列了一些重要的节区:
名称 | 类型 | 属性 | 含义 |
---|---|---|---|
.bss | SHT_NOBITS | SHF_ALLOC + SHF_WRITE | 包含将出现在程序的内存映像中的为初始化数据。根据定义,当程序开始执行,系统将把这些数据初始化为 0。此节区不占用文件空间。 |
.comment | SHT_PROGBITS | (无) | 包含版本控制信息。 |
.data | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE | 这些节区包含初始化了的数据,将出现在程序的内存映像中。 |
.data1 | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE | 这些节区包含初始化了的数据,将出现在程序的内存映像中。 |
.debug | SHT_PROGBITS | (无) | 此节区包含用于符号调试的信息。 |
.dynamic | SHT_DYNAMIC | SHF_ALLOC 位,是否 SHF_WRITE 位被设置取决于处理器。 | 此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。 |
.dynstr | SHT_STRTAB | SHF_ALLOC | 此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称。 |
.dynsym | SHT_DYNSYM | SHF_ALLOC | 此节区包含了动态链接符号表。 |
.fini | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码。 |
.got | SHT_PROGBITS | 此节区包含全局偏移表。 | |
.hash | SHT_HASH | SHF_ALLOC | 此节区包含了一个符号哈希表。 |
.init | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主入口之前(通常指 C 语言的 main 函数)执行这些代码。 |
.interp | SHT_PROGBITS | 此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 SHF_ALLOC 位,否则该位为 0。 | |
.line | SHT_PROGBITS | (无) | 此节区包含符号调试的行号信息,其中描述了源程序与机器指令之间的对应关系。其内容是未定义的。 |
.note | SHT_NOTE | (无) | 此节区中包含注释信息,有独立的格式。 |
.plt | SHT_PROGBITS | 此节区包含过程链接表(procedure linkage table)。 | |
.rel[name] | SHT_REL | 这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text。 | |
.rela[name] | SHT_RELA | 这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。 | |
.rodata | SHT_PROGBITS | SHF_ALLOC | 这些节区包含只读数据,这些数据通常参与进程映像的不可写段。 |
.rodata1 | SHT_PROGBITS | SHF_ALLOC | 这些节区包含只读数据,这些数据通常参与进程映像的不可写段。 |
.shstrtab | SHT_STRTAB | 此节区包含节区名称。 | |
.strtab | SHT_STRTAB | 此节区包含字符串,通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表,节区的属性将包含 SHF_ALLOC 位,否则该位为 0。 | |
.symtab | SHT_SYMTAB | 此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含SHF_ALLOC 位,否则该位置为 0。 | |
.text | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含程序的可执行指令。 |
段
段(Segments)是ELF文件在执行视图(Execution View)中的基本单位, 与链接视图中的节(Sections)不同, 段是程序加载到内存时实际使用的单位.
链接视图的节(Sections)提供了非常细粒度的程序组织方式, 这对编译器和链接器很有用. 但对操作系统加载器来说, 处理大量节会增加复杂性并降低效率. 段提供了一种更高层次的抽象, 将具有相似属性(如内存权限)的多个节组合在一起, 简化了加载过程.
一个简单的 ELF 可执行文件的内存布局如下:
1虚拟地址空间
2+------------------+ 高地址
3| 栈区域 |
4| ↓ |
5| |
6| ↑ |
7| 堆区域 |
8+------------------+
9| 未映射区域 |
10+------------------+
11| |
12| 数据段(rw-) | ← 包含.data, .bss等节
13| |
14+------------------+
15| |
16| 代码段(r-x) | ← 包含.text, .rodata等节
17| |
18+------------------+ 低地址
节区类型
字符串表节区
对于字符串表类型 SHT_STRTAB
来说, 其包含了以 NULL(0) 结尾的字符串序列, 对字符串的引用通常以字符串在表中的下标来给出, 例如节区头表的 sh_name
便是使用这种方式来给出自身的名字, 节区头表关联的是 .shstrtab
节(Section Header String Table).

符号表节区
对于符号表类型 SHT_SYMTAB
, 其包含了用来定位、重定位程序中符号定义和引用的信息, 表项字段含义如下:
字段 | 说明 |
---|---|
st_name | 符号在字符串表中对应的索引. 如果该值非 0, 则它表示了给出符号名的字符串表索引, 否则符号表项没有名称. 注: 外部 C 符号在 C 语言和目标文件的符号表中具有相同的名称. |
st_value | 给出与符号相关联的数值, 具体取值依赖于上下文, 可能是一个正常的数值、一个地址等等. |
st_size | 给出对应符号所占用的大小. 如果符号没有大小或者大小未知, 则此成员为0. |
st_info | 给出符号的类型和绑定属性. 之后会给出若干取值和含义的绑定关系. |
st_other | 目前为0, 其含义没有被定义. |
st_shndx | 每个符号表项都以和其他节区间的关系给出定义, 此成员给出相关的节区头表索引 |
st_value
st_value
在不同的上下文下含义不同, 在文章
ELF文件基本结构 - CTF Wiki 中其说明:
当符号对应为一个变量时,
st_value
表明该变量在内存中的偏移.
获取该符号对应的
st_shndx
, 进而获取到相关的节区根据节区头元素可以获取节区的虚拟基地址和文件基地址
value-内存基虚拟地址=文件偏移-文件基地址
当符号对应为一个函数时,
st_value
表明该函数在文件中的偏移.
但这其实貌似是不完全对的. ELF 中不会区别对待变量或函数, st_value
的含义由以下的因素决定:
-
文件类型(可重定位文件、可执行文件或共享对象文件)
-
符号绑定类型(局部、全局、弱符号)
更加精确的结论:
-
在可重定位文件(.o文件), 对于所有已定义的符号(无论是变量还是函数),
st_value
都表示节区偏移量, 也就是相对于由st_shndx
标识的节区起始位置的偏移量. 这一点对于变量和函数本质上是相同的. -
在可执行文件或共享对象文件中: 对于所有已定义的符号,
st_value
都表示虚拟内存地址.
为了更好地理解 st_value
, 我们使用一段 C 代码来演示:
1#include <stdio.h>
2
3// 全局变量
4int global_var = 42;
5
6// 静态全局变量
7static int static_global = 100;
8
9// 函数声明
10void another_function(void);
11
12int main(void) {
13 int local_var = 10;
14 printf("全局变量: %d\n", global_var);
15 another_function();
16 return 0;
17}
18
19void another_function(void) {
20 printf("另一个函数被调用\n");
21}
这边我们主要关注三个符号: 全局变量 global_var
, 静态全局变量 static_global
以及一个函数 another_function
.
首先使用-c
选项编译出来 .o
目标文件, 并查看其符号表以及节区头表:
1$ gcc -c symbols.c -o symbols.o
2
3$ readelf -s symbols.o
4
5Symbol table '.symtab' contains 10 entries:
6 Num: Value Size Type Bind Vis Ndx Name
7 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
8 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS symbols.c
9 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
10 3: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_global
11 4: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .rodata
12 5: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var
13 6: 0000000000000000 55 FUNC GLOBAL DEFAULT 1 main
14 7: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15 8: 0000000000000037 22 FUNC GLOBAL DEFAULT 1 another_function
16 9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
17
18$ readelf -S symbols.o
19There are 14 section headers, starting at offset 0x3d8:
20
21Section Headers:
22 [Nr] Name Type Address Offset
23 Size EntSize Flags Link Info Align
24 [ 0] NULL 0000000000000000 00000000
25 0000000000000000 0000000000000000 0 0 0
26 [ 1] .text PROGBITS 0000000000000000 00000040
27 000000000000004d 0000000000000000 AX 0 0 1
28 [ 2] .rela.text RELA 0000000000000000 000002a0
29 0000000000000090 0000000000000018 I 11 1 8
30 [ 3] .data PROGBITS 0000000000000000 00000090
31 0000000000000008 0000000000000000 WA 0 0 4
32
33 ...
通过查看节区头表, 可以看到索引 Ndx
1, 3 分别对应.text
代码段以及.data
数据段, 此时st_value
表示该符号在对应的分区的偏移量. 使用 objdump
进行验证:
1#################
2## 查看数据段 ###
3#################
4$ objdump -s -j .data symbols.o
5
6symbols.o: file format elf64-x86-64
7
8Contents of section .data:
9 0000 2a000000 64000000 *...d...
10
11
12#################
13## 查看代码段 ###
14#################
15# 直接查看 .text 不够直观
16$ objdump -s -j .text symbols.o
17
18# 反汇编查看代码段
19$ objdump -d symbols.o
20
21symbols.o: file format elf64-x86-64
22
23
24Disassembly of section .text:
25
260000000000000000 <main>:
27 0: 55 push %rbp
28 1: 48 89 e5 mov %rsp,%rbp
29 4: 48 83 ec 10 sub $0x10,%rsp
30 8: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp)
31 f: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 15 <main+0x15>
32 15: 89 c6 mov %eax,%esi
33 17: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # 1e <main+0x1e>
34 1e: 48 89 c7 mov %rax,%rdi
35 21: b8 00 00 00 00 mov $0x0,%eax
36 26: e8 00 00 00 00 call 2b <main+0x2b>
37 2b: e8 00 00 00 00 call 30 <main+0x30>
38 30: b8 00 00 00 00 mov $0x0,%eax
39 35: c9 leave
40 36: c3 ret
41
420000000000000037 <another_function>:
43 37: 55 push %rbp
44 38: 48 89 e5 mov %rsp,%rbp
45 3b: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # 42 <another_function+0xb>
46 42: 48 89 c7 mov %rax,%rdi
47 45: e8 00 00 00 00 call 4a <another_function+0x13>
48 4a: 90 nop
49 4b: 5d pop %rbp
50 4c: c3 ret
可以看到:
-
在
.data
中, 以小端序存放着 42 和 100 两个全局变量的值 -
.text
为汇编后的代码, 使用objdump -d
查看反汇编后的代码, 可以看到在对应偏移量的位置为another_function
至此, 目标文件的分析完成, 无论是函数还是变量, st_value
都是对应节区的偏移量.
下面分析可执行文件中的情况, 我们需要验证: st_value
表示虚拟地址的位置.
1# 省略了部分输出
2
3$ readelf -s symbols
4
5...
6
7Symbol table '.symtab' contains 28 entries:
8 Num: Value Size Type Bind Vis Ndx Name
9 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
10 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS symbols.c
11 2: 0000000000004024 4 OBJECT LOCAL DEFAULT 24 static_global
12 3: 0000000000000000 0 FILE LOCAL DEFAULT ABS
13 12: 0000000000001198 0 FUNC GLOBAL HIDDEN 15 _fini
14 13: 0000000000001180 22 FUNC GLOBAL DEFAULT 14 another_function
15 14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5
16 15: 0000000000004020 4 OBJECT GLOBAL DEFAULT 24 global_var
17 16: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 24 __data_start
18...
19
20$ readelf -S symbols
21There are 30 section headers, starting at offset 0x3580:
22
23Section Headers:
24 [Nr] Name Type Address Offset
25 Size EntSize Flags Link Info Align
26 [14] .text PROGBITS 0000000000001050 00001050
27 0000000000000146 0000000000000000 AX 0 0 16
28 [24] .data PROGBITS 0000000000004010 00003010
29 0000000000000018 0000000000000000 WA 0 0 8
从 readelf
的结果可以看到, 相比于目标文件, 可执行文件的 st_value
的值已经变得大得多了, 尽管仍然通过 Ndx
指向数据段和代码段, 但此时该字段的含义已经变成了对应符号在运行时的虚拟地址的空间了. 我们使用 gdb 进行验证:
1$ gdb ./symbols
2GNU gdb (GDB) 15.2
3
4(gdb) info address another_function
5Symbol "another_function" is at 0x1180 in a file compiled without debugging.
6(gdb) info address global_var
7Symbol "global_var" is at 0x4020 in a file compiled without debugging.
8(gdb) info address static_global
9Symbol "static_global" is at 0x4024 in a file compiled without debugging.
gdb 的输出与符号表的输出是统一的, 以此验证通过!
st_info
st_info
中包含符号类型和绑定信息, 这里给出了控制它的值的方式具体信息如下:
1#define ELF32_ST_TYPE(i) ((i)&0xf) # 低 4 位
2#define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf)) # 高 4 位
符号类型如下:
名称 | 取值 | 说明 |
---|---|---|
STT_NOTYPE | 0 | 符号的类型没有定义. |
STT_OBJECT | 1 | 符号与某个数据对象相关, 比如一个变量、数组等等. |
STT_FUNC | 2 | 符号与某个函数或者其他可执行代码相关. |
STT_SECTION | 3 | 符号与某个节区相关. 这种类型的符号表项主要用于重定位, 通常具有 STB_LOCAL 绑定. |
STT_FILE | 4 | 一般情况下, 符号的名称给出了生成该目标文件相关的源文件的名称. 如果存在的话, 该符号具有 STB_LOCAL 绑定, 其节区索引是 SHN_ABS 且优先级比其他STB_LOCAL符号高. |
STT_LOPROC~STT_HIPROC | 13~15 | 保留用于特定处理器 |
- 共享目标文件中的函数符号有比较特殊, 当另一个目标文件从共享目标文件中引用一个函数时, 链接器自动为被引用符号创建过程链接表(PLT)项. 共享目标中除了
STT_FUNC
, 其它符号将不会通过过程链接表自动被引用.
这段话是在解释共享目标文件中的函数符号如何被特殊处理。让我来帮你理解这个概念:
共享目标文件(通常是.so文件,类似于Windows中的DLL)中的函数符号有特殊处理机制,主要涉及到"过程链接表"(Procedure Linkage Table,简称PLT)的自动创建。
当我们分解这段话时:
-
当另一个目标文件从共享目标文件中引用一个函数时,链接器会自动为这个被引用的函数符号创建过程链接表(PLT)项。
-
这种自动创建PLT项的机制只适用于标记为
STT_FUNC
类型的符号(即函数符号)。 -
共享目标文件中的其他类型符号(比如数据对象
STT_OBJECT
)不会通过过程链接表自动被引用。
为什么需要这种特殊处理?这与动态链接的工作原理有关:
当程序运行时,共享库中的函数可能需要被延迟加载或重定位。过程链接表(PLT)与全局偏移表(GOT)一起工作,允许程序在运行时正确地调用共享库中的函数。当程序调用共享库中的函数时,它实际上是跳转到PLT中的一个条目,该条目再跳转到实际函数的地址。
这种机制的好处是:
- 支持延迟绑定(lazy binding):函数地址只在第一次调用时解析
- 允许共享库在内存中的不同位置加载
- 使共享库能够被多个程序共享,同时每个程序可以有自己的函数地址引用
非函数符号(如全局变量)通常直接通过GOT访问,不需要PLT这一额外的间接层,因为数据访问模式与函数调用不同。
实际上,这是ELF(可执行与可链接格式)动态链接设计的一部分,专门针对函数调用进行了优化处理。
符号绑定的信息确定了符号的链接可见性以及其行为, 具体的取值如下:
名称 | 取值 | 说明 |
---|---|---|
STB_LOCAL | 0 | 表明该符号为局部符号, 在包含该符号定义的目标文件以外不可见. 相同名称的局部符号可以存在于多个文件中, 互不影响. |
STB_GLOBAL | 1 | 表明该符号为全局符号, 对所有将被组合在一起的目标文件都是可见的. 一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用. 我们称初始化非零变量的全局符号为强符号, 只能定义一次. |
STB_WEAK | 2 | 弱符号与全局符号类似, 不过它们的定义优先级比较低. |
STB_LOPROC ~STB_HIPROC | 13 | 这个范围的取值是保留给处理器专用语义的. |
- 当链接器在链接多个可重定位目标文件时, 不允许定义多个相同名字的
STB_GLOBAL
符号. 但如果存在一个已定义的全局符号, 同名弱符号的存在不会引起错误. 链接器会优先选择全局定义, 忽略弱符号定义.
st_shndx
通常的 st_shndx
定义了符号所在节在节区头部表中的下标, 下面给出了一些特殊的取值:
-
SHN_ABS
: 符号的取值具有绝对性, 不会因为重定位而发生变化. -
SHN_COMMON
: 符号标记了一个尚未分配的公共块. 符号的取值给出了对齐约束, 与节区的sh_addralign
成员类似. 就是说, 链接编辑器将在地址位于st_value
的倍数处为符号分配空间. 符号的大小给出了所需要的字节数. -
SHN_UNDEF
: 此索引值表示符号没有定义. 当链接编辑器将此目标文件与其他定义了该符号的目标文件进行组合时, 此文件中对该符号的引用将被链接到实际定义的位置.
符号名称的定位
对于一个符号表而言, 其所有的符号名称也是通过另一张字符串表来给出的. 与节区头表中的节区名称 sh_name
类似, 符号表表项的名称 st_name
也是一个数值索引, 其使用的字符串表根据符号表头的 sh_link
字段给出.
以上述的 C 代码为例, 为了简便, 编译出目标代码, 使用 readelf
查看对应的内容:
1# 查看所有的符号头表
2$ readelf -S symbols.o
3There are 14 section headers, starting at offset 0x408:
4
5Section Headers:
6 [Nr] Name Type Address Offset
7 Size EntSize Flags Link Info Align
8
9 ... 省略若干输出
10
11 [11] .symtab SYMTAB 0000000000000000 00000168
12 00000000000000f0 0000000000000018 12 5 8
13 [12] .strtab STRTAB 0000000000000000 00000258
14 0000000000000046 0000000000000000 0 0 1
15 [13] .shstrtab STRTAB 0000000000000000 00000360
16 0000000000000074 0000000000000000 0 0 1
17
18# 查看 .symtab 关联的 12 号节区 .strtab
19$readelf -x 12 symbols.o
20
21Hex dump of section '.strtab':
22 0x00000000 0073796d 626f6c73 2e630073 74617469 .symbols.c.stati
23 0x00000010 635f676c 6f62616c 00676c6f 62616c5f c_global.global_
24 0x00000020 76617200 6d61696e 00707269 6e746600 var.main.printf.
25 0x00000030 616e6f74 6865725f 66756e63 74696f6e another_function
26 0x00000040 00707574 7300 .puts.
通过符号表头的 Link
可以关联到 12 号节区表, 在 12 号节区内, 看到了所有符号的字符串名称.
但需要注意, 12 号节区中的内容是符号自身的字符串名字, 符号的值则由符号表内部的 Ndx
与 st_value
给出.
哈希表节区
哈希表节区是用于存储哈希值的特殊节区. 这些节区主要用于以下几个目的:
-
完整性验证:存储文件或特定节区的哈希值,用于验证文件是否被篡改
-
数字签名:支持代码签名机制,以验证软件的来源和完整性
-
安全启动:在嵌入式系统和安全启动过程中验证可执行文件
-
符号查找加速:某些哈希表用于加速符号查找过程
.hash
是传统的哈希表节区, 使用 GNU 工具链则会以 .gnu.hash
替代, 其比传统的 .hash
更加高效, 占用的内存空间更小, 哈希函数也更加现代, 相关的内容留待后续有机会再学习:
参考:
数据相关节区
-
.bss
; 未初始化的全局变量对应的节. 此节区不占用 ELF 文件空间, 但占用程序的内存映像中的空间. 当程序开始执行时, 系统将把这些数据初始化为 0.- bss 是 Block Started by Symbol 的简写, 说明该节区中单纯地说明了有哪些变量.
-
.data
; 这些节区包含初始化了的数据, 会在程序的内存映像中出现. -
.rodata
; 这些节区包含只读数据,这些数据通常参与进程映像的不可写段。
代码相关节区
-
.init
&.init_array
; 此节区包含可执行指令, 是进程初始化代码的一部分. 程序开始执行时, 系统会在开始调用主程序入口(通常指 C 语言的 main 函数)前执行这些代码. -
.text
;此节区包含程序的可执行指令 -
.fini
&.fini_array
; 此节区包含可执行的指令, 是进程终止代码的一部分. 程序正常退出时, 系统将执行这里的代码.
动态链接与重定位节区
ELF 文件中的动态链接相关节区主要包括:
-
.dynamic
; 核心动态链接信息节区, 包含了所有动态链接所需的信息结构. 它存储了各种动态链接条目, 如共享库路径、符号表位置等. -
.dynsym
; 动态符号表, 包含了程序中所有需要动态链接的符号信息. 与.symtab
不同,.dynsym
只包含动态链接必需的符号. -
.dynstr
; 动态字符串表, 存储了.dynsym
中符号的名称字符串. -
.plt
(Procedure Linkage Table)- 过程链接表, 用于支持延迟绑定(lazy binding)机制, 提高程序启动速度. -
.got
(Global Offset Table)- 全局偏移表, 存储外部符号的地址. PLT会使用GOT进行符号解析. -
.got.plt
; PLT专用的GOT表部分, 专门用于函数调用. -
.rela.dyn
/.rel.dyn
; 用于变量的重定位表. -
.rela.plt
/.rel.plt
; 用于函数的重定位表. -
.interp
; 存储动态链接器(如/lib64/ld-linux-x86-64.so.2
)的路径.
有关动态链接、PLT、GOT 相关的内容, 由于较为复杂, 参见后续博客. TODO
后记
在写知识类博客的时候, 总是难免地变成知识摘抄(从网络博客与 LLM 的知识库中), 或是技术说明书(罗列冗余无趣的字段详解).. 尽管断断续续从在网上写下第一个字到如今已经有 6 年左右了, 但始终无法很好地产出此类的知识文章; 相比之下, 各种技术工具的折腾记录反而更加容易些. 其原因本质上还是在于对于知识的理解不够到位, 却又希望输出好的文章: 对于初学者来说, 对新知识的学习必然是零碎的、片面的, 但对于博客创作者来说, 又希望对知识的介绍是系统的、引人入胜且条理清晰的, 我是不愿将笔记、草稿作为博客上传到互联网上的, 此类的内容不宜使用博客作为载体, 使用批注或 Obisidian 等管理工具或许更好, 但我又不愿将一份知识的学习拆分为两种; 尽管有言道学习最好的方式就是将它教给别人, 但当我真的学会后, 我是没有太大的动力去做科普、系统性的知识教程的, 这就导致了一些内容产出理念上的偏差..
不过总体来说, 我的博客还是个人学习的沉淀与输出, 他人倘若能有些许的受益就是其发表于互联网上的价值所在了. 毕竟, 目前内容农场大行其道, 各种图片过期、CSS 样式丑陋的博客也不在少数, 我自认本博客的审美不至于过于丑陋, 也并无任何的广告, 不滥用 LLM 生成内容, 一些地方也是会记录下我在学习、探索过程中的独到之处, 因此应当还是有一定价值的!
参考资料
参考阅读: