编译和链接这两个步骤,在Windows下被IDE封装的很完美,我们一般是使用一键编译并运行,但是当链接出错的话我们就束手无措了。在Linux下有gcc/g++编译器,可以直接展示出编译链接的过程。
在软件开发中,编译是将程序的源代码(通常是人类可读的高级语言,如 C/C++)翻译成 CPU 能够直接执行的机器代码(二进制代码)。通过这一步骤,源文件被转换为目标文件,为后续的链接奠定基础。
以一个简单的例子为例:假设我们有一个源文件 hello.c
,其内容如下:
// hello.c
#include <stdio.h>
int main() {
printf("hello world!\n");
return 0;
}
使用 gcc
编译器,我们可以通过以下命令编译该源文件:
$ gcc -c hello.c
编译完成后,生成一个扩展名为 .o
的文件(例如 hello.o
),被称为目标文件(Object File)。我们可以通过以下命令查看生成的文件:
$ ls
hello.c hello.o
file
命令可以检查目标文件的类型,例如:$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
通过编译过程,我们可以生成目标文件,并了解 ELF 格式作为二进制文件封装的重要作用。
如果我们修改了一个源文件,那么我们只需要单独编译这一个文啊进,而不是重新编译整个工会测过,将目标文件编译后重新链接即可。
为了全面理解编译和链接的细节,我们需要深入了解 ELF(Executable and Linkable Format)文件格式。ELF 是一种通用的二进制文件格式,在 Linux 系统中广泛用于目标文件、可执行文件、共享库以及内核转储等。以下是 ELF 文件的四种主要类型及其特点:
.o
文件(目标文件)。gcc -c
命令生成,尚未进行最终的地址解析和链接。a.out
或其他二进制可执行文件)。.so
文件(动态库)。一个 ELF 文件由以下四个主要部分组成:
.bss
(未初始化的全局变量和静态变量)、.rodata
(只读数据,如字符串字面量)等,具体取决于文件类型和编译选项。ELF(Executable and Linkable Format)文件是 Linux 系统中编译和链接的核心格式。为了生成可执行文件,涉及以下两个主要步骤,并补充相关的知识点:
.o
文件gcc
或 g++
)将 C/C++ 源代码(.c
或 .cpp
文件)翻译成目标文件(.o
文件)。编译器会执行以下几个阶段: #include
、宏定义和条件编译指令,生成预处理文件(.i
文件)。.s
文件),生成特定架构的机器指令。.o
文件),格式为 ELF 可重定位文件。gcc -c
编译源文件,例如:$ gcc -c source1.c -o source1.o
$ gcc -c source2.c -o source2.o
.text
Section)、数据(.data
和 .bss
Section)、符号表(.symtab
)和重定位信息(.rela
),但地址尚未最终确定(符号引用未解析)。gcc -Wall
可启用警告选项,gcc -g
可生成调试信息(.debug
Section),便于调试。.o
文件的 Section 进行合并ld
)将多个目标文件(.o
文件)的各个 Section 合并,并可能与库文件(如静态库 .a
或动态库 .so
)结合,生成最终的可执行文件(.out
或指定名称)。.text
(代码)、.data
(初始化数据)、.bss
(未初始化数据)、.rodata
(只读数据)等 Section。.symtab
)和重定位表(.rela
),解决未定义符号(如函数或变量的引用),确保所有地址引用正确。.dynsym
)和全局偏移表/过程链接表(.got.plt
),为运行时加载动态库做准备。$ gcc source1.o source2.o -o program
或直接从源文件生成:
$ gcc source1.c source2.c -o program
.a
)内容直接嵌入可执行文件;动态链接则引用动态库(.so
),仅记录加载信息,运行时由动态链接器(如 /lib64/ld-linux-x86-64.so.2
)加载。.debug
Section,便于使用 gdb
调试。ld
的 linker script),可自定义内存布局和 Section 合并规则。当生成的 ELF 可执行文件加载到内存中时,操作系统会根据其结构完成对ELF中不同的Section的合并,形成segment。
.text
、.data
、.rodata
)是链接视图的逻辑单元,描述文件内容的组织方式。.text
和 .rodata
)、可读写段(如包含 .data
和 .bss
)或可执行段。.text
部分为 4097 字节,.init
部分为 512 字节,未合并时需 3 个页面(4096 × 3 = 12288 字节);合并后可能仅需 2 个页面(4096 × 2 = 8192 字节)。**程序头表(Program header table)**
确定,程序头表记录了每个 Segment 的起始地址、长度、权限和文件偏移等信息。.dynamic
和 .got.plt
Section,用于运行时解析共享库符号。readelf -S
命令。例如:$ readelf -S a.out
输出显示可执行文件中的各个 Section,如 .text
(代码)、.data
(初始化数据)、.rodata
(只读数据)、.bss
(未初始化数据)等,及其属性(地址、偏移、大小、权限等)。
readelf -l
命令。例如:$ readelf -l a.out
输出显示程序头表中的 Segment 信息,包括类型(如 LOAD
、DYNAMIC
)、虚拟地址、文件偏移、文件大小、内存大小、权限(R/W/E)和对齐方式。
- `LOAD` 段:需要加载到内存的代码和数据段,可能包含 `.text`、`.data` 等 Section。
- `DYNAMIC` 段:用于动态链接,包含动态库加载信息。
- `GNU_STACK` 段:指定栈的权限(通常可读写)。
$ readelf -S a.out
There are 30 section headers, starting at offset 0x1a50:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000 # 空 Section,用于占位
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238 # 程序解释器路径(动态链接器)
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254 # ABI 版本信息
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 # 构建 ID,唯一标识可执行文件
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 # GNU 哈希表,加速符号查找
0000000000000024 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002c0 000002c0 # 动态符号表
00000000000000f0 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000004003b0 000003b0 # 动态字符串表(符号名称)
000000000000008b 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000040043c 0000043c # 符号版本信息
0000000000000014 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400450 00000450 # 符号版本需求信息
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400470 00000470 # 动态重定位表
0000000000000030 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000004004a0 000004a0 # PLT(过程链接表)重定位表
00000000000000c0 0000000000000018 AI 5 23 8
[11] .init PROGBITS 0000000000400560 00000560 # 程序初始化代码
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400580 00000580 # 过程链接表(PLT)
0000000000000090 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400610 00000610 # 程序代码段
00000000000001e2 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 00000000004007f4 000007f4 # 程序终止代码
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000400800 00000800 # 只读数据段
0000000000000024 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 0000000000400824 00000824 # 异常处理框架头
0000000000000034 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400858 00000858 # 异常处理框架数据
00000000000000f4 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600de0 00000de0 # 初始化函数指针数组
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600de8 00000de8 # 终止函数指针数组
0000000000000008 0000000000000008 WA 0 0 8
[20] .jcr PROGBITS 0000000000600df0 00000df0 # Java 类注册信息
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600df8 00000df8 # 动态链接信息
0000000000000200 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600ff8 00000ff8 # 全局偏移表(GOT)
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000601000 00001000 # PLT 相关的 GOT
0000000000000058 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000601058 00001058 # 数据段
0000000000000004 0000000000000000 WA 0 0 1
[25] .bss NOBITS 0000000000601060 0000105c # 未初始化数据段
0000000000000010 0000000000000000 WA 0 0 16
[26] .comment PROGBITS 0000000000000000 0000105c # 编译器注释信息
000000000000002d 0000000000000001 MS 0 0 1
[27] .symtab SYMTAB 0000000000000000 00001090 # 符号表
0000000000000678 0000000000000018 28 46 8
[28] .strtab STRTAB 0000000000000000 00001708 # 字符串表(符号名称)
000000000000023f 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 00001947 # Section 名称字符串表
0000000000000108 0000000000000000 0 0 1
.text
:
.data
:
.bss
(better save space):
data
,而是在bss
中记录有多少个变量,因为所有的全局变量都是未知的,没有初始化,过于臃肿。bss
读取,初始化为0
。这就是为什么为初始化的变量会自动初始化为0
的原因。$ readelf -l a.out
Elf file type is EXEC (Executable file)
Entry point 0x4003e0 # 程序入口地址
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 # 程序头表信息
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238 # 程序解释器路径
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] # 动态链接器路径
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 # 可加载段(代码段)
0x0000000000000744 0x0000000000000744 R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 # 可加载段(数据段)
0x0000000000000218 0x0000000000000220 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28 # 动态链接信息
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254 # 注释信息(ABI、构建 ID)
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000005a0 0x00000000004005a0 0x00000000004005a0 # 异常处理框架信息
0x000000000000004c 0x000000000000004c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 # 栈权限(RW,不可执行)
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 # 重定位只读段
0x00000000000001f0 0x00000000000001f0 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp # 程序解释器路径
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame # 代码段
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss # 数据段
04 .dynamic # 动态链接信息
05 .note.ABI-tag .note.gnu.build-id # 注释信息
06 .eh_frame_hdr # 异常处理框架信息
07
08 .init_array .fini_array .jcr .dynamic .got # 初始化相关段
.text
为可执行只读,.data
为可读写),由操作系统通过内存保护机制(如 MMU)实现,提高程序安全性。ld-linux.so
)会解析 .dynamic
和 .got.plt
Section,加载共享库并绑定符号。.text
:保存机器指令,是程序执行的核心,权限通常为可执行只读。.data
:保存已初始化的全局变量和局部静态变量,权限为可读写。.rodata
:保存只读数据(如字符串字面量),只能存在于只读段(通常与 .text
合并)。.bss
:为未初始化的全局变量和局部静态变量预留空间,实际数据在运行时初始化,权限为可读写。.symtab
:符号表,记录函数名、变量名与代码或数据的对应关系,用于链接阶段解析符号引用。.rela
:重定位表,记录需要调整地址的符号引用位置,链接器根据此表修正地址。.debug
:调试信息,包含源代码行号、变量名和类型等,供调试工具(如 gdb
)使用。.got.plt
:全局偏移表和过程链接表,用于动态链接,保存共享库函数的间接引用地址,运行时由动态链接器修改。readelf -S
命令查看目标文件(如 hello.o
)或可执行文件(如 a.out
)的节头表。LOAD
、DYNAMIC
、GNU_STACK
),描述每个 Segment 的虚拟地址、文件偏移、大小、权限和对齐方式。**Program Header Table**
LOAD
:需要加载到内存的代码和数据段,包含 .text
、.data
、.rodata
等 Section。DYNAMIC
:动态链接信息,包含共享库依赖和符号解析数据。GNU_STACK
:栈段的权限设置(通常可读写)。GNU_RELRO
:只读重定位段,保护动态链接后的数据免受修改。readelf -l
命令查看可执行文件的程序头表。 /lib64/ld-linux-x86-64.so.2
)解析 .dynamic
和 .got.plt
,加载共享库并绑定符号,确保程序运行时能访问外部函数。总结:
二者说白了就是,一个在链接时用,一个在运行时用。 执行命令查看的内容在
3.2.2
中已展示。
.symtab
符号表的基本概念.symtab
是 ELF 文件中的一个重要 Section(节),称为符号表(Symbol Table)。它是存储程序中符号(函数名、变量名等)及其相关信息的表格,用于描述源码中的标识符(如函数、变量)与目标文件或可执行文件中代码和数据的对应关系。.symtab
通常位于目标文件(.o
)或可执行文件(.out
)中,属于链接视图(Linking View)的部分,存储在节头表(Section Header Table)中描述的 Section 中。main
、变量 label
),确保程序的正确连接和地址分配。gdb
)提供符号信息,映射源码中的标识符到内存地址,便于定位和分析。gcc -s
)去除 .symtab
,减小文件大小,但会失去调试能力。类似于数组,将每个符号分隔,独立存储。
.symtab
与源码的对应关系.symtab
是源码中函数名、变量名和代码对应关系的“桥梁”,具体来说:
int main(void)
)和变量(如 char label[] = "helloworld";
)。这些名称是人类可读的标识符。.symtab
中,并关联到目标文件中对应的代码(.text
Section)或数据(.data
、.bss
或 .rodata
Section)。.symtab
记录每个符号的名称、类型、地址(或偏移量)、大小和所属 Section。例如: main
可能记录在 .text
Section,符号表条目显示其类型为 FUNC
(函数),地址为某个虚拟地址。label
可能记录在 .data
或 .rodata
Section,符号表条目显示其类型为 OBJECT
(对象/变量),地址为数据段的偏移量。printf
),但未在当前文件定义,.symtab
会标记这些符号为 UND
(未定义),等待链接器从其他目标文件或库(如 libc
)中解析和绑定。.symtab
的结构与内容符号表(.symtab
)由多个符号表条目(Symbol Table Entries)组成,每个条目包含以下字段(可以通过 nm
或 readelf -s
查看):
main
、label
、printf
)。NOTYPE
:未指定类型(通常为未定义符号)。OBJECT
:变量或数据对象(如 label
)。FUNC
:函数(如 main
)。SECTION
:Section 本身。FILE
:源文件名称。LOCAL
:本地符号,仅在当前文件可见。GLOBAL
:全局符号,可被其他文件引用。WEAK
:弱符号,如果未定义则可被忽略。.text
、.data
、.bss
或 UND
表示未定义)。示例:符号表条目
假设有一个简单的 C 源码:
#include <stdio.h>
char label[] = "helloworld";
int main(void) {
printf("Hello, world!\n");
return 0;
}
编译生成目标文件 example.o
:
$ gcc -c example.c -o example.o
$ nm example.o
输出可能如下(简化):
0000000000000000 T main # 地址 0x0,类型 FUNC,绑定 GLOBAL,位于 .text
0000000000000000 D label # 地址 0x0,类型 OBJECT,绑定 GLOBAL,位于 .data
U printf # 未定义,类型 NOTYPE,绑定 GLOBAL,位于 UND(需链接 libc)
main
:函数,存储在 .text
Section,地址为 0(可重定位文件中的相对地址,链接后确定)。label
:变量,存储在 .data
Section,地址为 0(链接后确定)。printf
:未定义符号,标记为 U
,需从标准库 libc
中解析。链接生成可执行文件 example
:
$ gcc example.o -o example
$ nm example
输出中 main
和 label
的地址变为具体值(如 0x401000
),printf
的地址也从 libc
绑定。
.symtab
的生成与使用gcc
)在编译源代码时,解析源码中的函数和变量,生成目标文件(.o
)。.symtab
Section,记录符号的名称、类型和临时地址(相对于 Section 的偏移)。UND
,等待链接器处理。ld
)读取 .symtab
,解析未定义符号(如 printf
),从库文件(如 libc.a
或 libc.so
)或其他目标文件中查找定义,分配最终地址。gdb
)使用 .symtab
将源码中的函数名和变量名映射到内存地址,方便设置断点、查看变量值。nm
和 readelf -s
可查看符号表,分析程序结构和依赖。.symtab
您可以使用以下命令查看符号表:
nm
命令:$ nm hello.o # 查看目标文件的符号表
$ nm a.out # 查看可执行文件的符号表
- 输出显示符号名称、地址、类型和 Section(如 `T` 为 `.text`,`D` 为 `.data`,`U` 为未定义)。
readelf -s
命令:$ readelf -s hello.o # 详细查看目标文件的符号表
- 输出包括符号的名称、值、大小、类型、绑定和 Section 索引,提供更详细的信息。
示例输出(如 readelf -s hello.o
):
Symbol table '.symtab' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 17 OBJECT GLOBAL DEFAULT 3 label
5: 0000000000000000 0 FUNC GLOBAL DEFAULT 1 main
6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
label
:OBJECT
类型,GLOBAL
绑定,位于 Section 3(可能为 .data
或 .rodata
),大小为 17 字节(字符串 "helloworld\0"
的长度加终止符)。main
:FUNC
类型,GLOBAL
绑定,位于 Section 1(.text
),大小为 0(实际大小由链接后确定)。printf
:NOTYPE
类型,GLOBAL
绑定,位于 UND
(未定义),需链接器从 libc
解析。strip
命令去除 .symtab
和调试信息:$ strip hello.o # 去除符号表和调试信息
但这会使调试变得困难。
.dynsym
(动态符号表),用于动态链接,记录与共享库相关的符号(如 libc
中的函数)。.symtab
和 .dynsym
的区别在于:.symtab
包含所有符号(包括本地和全局),而 .dynsym
只包含与动态链接相关的全局符号。.symtab
可能占较大空间,尤其在包含大量函数和变量的程序中。通过优化代码或使用 gcc -fvisibility=hidden
减少导出符号,可以减小符号表大小。.symtab
.symtab
是源码中函数名、变量名和代码对应关系的“映射表”,记录程序的符号及其在目标文件或可执行文件中的位置和属性。int main(void)
对应 .symtab
中的 main
条目,指向 .text
Section 的代码。char label[] = "helloworld";
对应 .symtab
中的 label
条目,指向 .data
或 .rodata
Section 的数据。printf
)标记为未定义(UND
),链接时从标准库(如 libc
)解析。nm
、readelf -s
查看符号表,结合源码和目标文件理解符号的定义和引用。ELF 头(ELF Header)位于文件开头,描述文件的基本信息,并定位程序头表和节头表。
查看目标文件(**.o**
** 文件)**
$ readelf -h hello.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64 # 64 位 ELF 文件
Data: 2's complement, little endian # 小端字节序
Version: 1 (current) # ELF 格式版本
OS/ABI: UNIX - System V # 目标操作系统
ABI Version: 0 # ABI 版本
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: 728 (bytes into file) # 节头表偏移
Flags: 0x0 # 特定标志(无特殊标志)
Size of this header: 64 (bytes) # ELF 头大小
Size of program headers: 0 (bytes) # 程序头表项大小(目标文件无程序头表)
Number of program headers: 0 # 程序头表项数量
Size of section headers: 64 (bytes) # 节头表项大小
Number of section headers: 13 # 节头表项数量
Section header string table index: 12 # 节名字符串表的索引
查看可执行文件
$ gcc *.o -o a.out
$ readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 # ELF文件的标识符
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file) # 文件类型为共享对象(可执行文件)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1060 # 程序入口地址
Start of program headers: 64 (bytes into file) # 程序头表偏移
Start of section headers: 14768 (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: 31
Section header string table index: 30
ELF 头的作用:
Magic
(魔数 7f 45 4c 46
),每个二进制文件都有,随机,系统可以通过magic
标识文件为 ELF 格式,防止误解析。 exe
会直接运行。Type
字段区分文件类型:REL
(可重定位)、EXEC
(可执行)、DYN
(共享对象)。Start of program headers
和 Start of section headers
)用于定位文件的其他部分,确保解析器正确读取数据。Entry point address
)指定程序启动时的起始指令地址(通常指向用于存储代码的 .text
Section 的起始位置)。ELF 文件的整体结构:像一本书
想象 ELF 文件是一本书,每个部分都有自己的“页码”(偏移量)和作用:
e_phoff
和 e_shoff
指定)。e_phoff
字段指定。.text
)、数据段(.data
)等。e_phoff
决定。例如,如果 e_phoff = 64
,意味着程序头表从文件第 64 字节开始。e_phnum
指定。.text
:存储程序的机器代码。.data
:存储初始化过的全局变量。.bss
:存储未初始化的全局变量(不占文件空间,只记录大小)。.symtab
:存储符号表(函数名、变量名等)。.text
节可能从偏移量 1024 开始,.data
节从 2048 开始。e_shoff
字段指定。.text
节从偏移量 1024 开始,大小 512 字节,可执行。e_shoff
决定。例如,e_shoff = 5000
意味着节头表从文件第 5000 字节开始。e_shnum
指定。.text
)从 10 页开始,讲故事;第 2 章(.data
)从 20 页开始,放插图。” 它的“页码”(偏移量)由 ELF 头指明。ELF 文件中的每个区域通过偏移量紧密关联,以下是它们的位置和依赖关系:
e_phoff
)和节头表(e_shoff
)的偏移量。e_phoff
指定,描述段的偏移量和内存映射。e_shoff
指定,通常在末尾,记录所有节的偏移量和属性。文件偏移量 (字节):
0 64 128 1024 2048 5000
|----------|-----------|-----------|----------|----------|----------|
ELF 头 程序头表 (其他内容) .text 节 .data 节 节头表
.text
和 .rodata
合成一个只读段)。段的偏移量记录在程序头表中。readelf
或 objdump
)定位 ELF 文件的每一部分。