文章目录
-
- 一、先回顾编译和链接
- 二、.o 文件是什么
- 三、ELF 是什么
- 四、ELF 的几种常见类型
-
- 1. 可重定位文件 Relocatable File
- 2. 可执行文件 Executable File
- 3. 共享目标文件 Shared Object File
- 4. Core dump
- 五、ELF 文件整体由哪些部分组成
-
- 1. ELF Header
- 2. Section Header Table
- 3. Program Header Table
- 4. Sections
- 六、常见 section 解释
-
- 1. .text
- 2. .data
- 3. .bss
- 4. .symtab
- 5. .got 和 .plt
- 七、链接视图和执行视图
-
- 1. 链接视图
- 2. 执行视图
- 八、为什么 section 要合并成 segment
- 九、动静态库与 ELF 的关系
-
- 1. 静态库
- 2. 动态库
- 3. 可执行文件
- 十、常用观察命令
-
- 1. file
- 2. readelf -h
- 3. readelf -S
- 4. readelf -l
- 5. size
- 十一、一个推荐实验
- 十二、总结
前几篇我们已经会制作静态库和动态库,也解决了动态库运行期找不到的问题。但如果只停留在命令层面,很容易把 gcc、ar、ldd、readelf 当成一堆零散工具。
从这篇开始,我们往底层走一步:看看 .o、.a、.so、可执行文件背后到底是什么。你会发现,它们都绕不开一个核心格式:ELF。 
一、先回顾编译和链接
一个 C 程序从源码变成可执行程序,大致经过这些阶段:
预处理 -> 编译 -> 汇编 -> 链接
平时我们一条命令:
gcc main.c -o main
看起来一步完成,实际上 gcc 帮我们串起了多个阶段。
如果拆开看,可以先生成目标文件:
gcc -c main.c -o main.o
这里的 -c 表示只编译,不链接。
此时 main.o 还不是可执行程序。它只是一个目标文件,里面有机器指令、数据、符号表、重定位信息等。
多个 .o 最后还要链接:
gcc main.o code.o -o main
链接阶段会把多个目标文件和用到的库组合成最终可执行程序。
二、.o 文件是什么
.o 是目标文件。它已经不是 C 源码,而是二进制文件。
可以用 file 查看:
file main.o
可能输出:
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
这里最重要的信息是:
ELF
relocatable
这说明 .o 文件是 ELF 格式,而且属于可重定位文件。
可重定位意味着它还不能直接运行,需要和其他目标文件或库继续链接,才能形成可执行文件或共享库。
三、ELF 是什么
ELF 全称是 Executable and Linkable Format,即“可执行与可链接格式”。
Linux 下很多二进制文件都采用 ELF 格式,例如:
.o 目标文件
可执行程序
.so 动态库
core dump 文件
这也是为什么我们研究动静态库时必须理解 ELF。
因为:
.o 是 ELF;
.so 是 ELF;
可执行程序也是 ELF;
链接和加载都围绕 ELF 中的信息展开。
四、ELF 的几种常见类型
1. 可重定位文件 Relocatable File
也就是 .o 文件。
它包含代码和数据,但里面的很多地址还没有最终确定。链接器会根据这些信息继续生成可执行文件或动态库。
查看方式:
file main.o
readelf -h main.o
你会看到类型类似:
Type: REL (Relocatable file)
2. 可执行文件 Executable File
例如我们生成的 main:
gcc main.o code.o -o main
file main
可能输出:
ELF 64-bit LSB executable
它可以被操作系统加载运行。
3. 共享目标文件 Shared Object File
也就是动态库 .so:
file libmyc.so
可能输出:
ELF 64-bit LSB shared object
.so 可以被动态链接器加载到进程地址空间中。
4. Core dump
进程崩溃时产生的 core 文件,也属于 ELF 的一种,用于保存进程崩溃时的上下文,方便调试。
五、ELF 文件整体由哪些部分组成
一个 ELF 文件可以从四个关键部分理解:
ELF Header
Program Header Table
Section Header Table
Sections

1. ELF Header
ELF Header 位于文件开头,描述整个 ELF 文件的基本信息,比如:
- 文件类型;
- 目标机器架构;
- 程序入口地址;
- Program Header Table 的位置;
- Section Header Table 的位置。
查看命令:
readelf -h main
你会看到类似字段:
Magic
Class
Data
Type
Machine
Entry point address
Start of program headers
Start of section headers
ELF Header 的作用可以理解为:
告诉工具和操作系统:这个 ELF 文件是什么,以及其他重要结构在哪里。
2. Section Header Table
Section Header Table 描述 ELF 中有哪些 section。
section 是链接视角下的组织单位,比如:
.text
.data
.bss
.symtab
.strtab
.rela.text
.got
.plt
查看命令:
readelf -S main.o
链接器在链接 .o 文件时,非常关心 section。比如多个 .o 的 .text 代码节会被合并,多个 .data 数据节也会被合并。
3. Program Header Table
Program Header Table 描述 ELF 如何被加载到内存。
查看命令:
readelf -l main
你会看到 LOAD、INTERP 等段信息。
操作系统加载可执行程序时,更关心 Program Header Table,因为它告诉系统:
哪些内容要加载到内存;
加载到什么虚拟地址;
内存区域权限是什么;
哪些段可读、可写、可执行。
4. Sections
Sections 是 ELF 中真正存放代码、数据、符号等内容的区域。
比如:
.text 存放机器指令
.data 存放已初始化全局变量和静态变量
.bss 描述未初始化全局变量和静态变量
.symtab 存放符号表
.got 全局偏移表
.plt 过程链接表
六、常见 section 解释
1. .text
.text 保存程序的机器指令,也就是函数代码。
例如 main、add、my_strlen 最终都会对应 .text 中的一段指令。
2. .data
.data 保存已初始化的全局变量或静态变量。
例如:
int g_val = 10;
这种变量通常会进入 .data。
3. .bss
.bss 对应未初始化或初始化为 0 的全局变量、静态变量。
例如:
int g_count;
static int s_flag;
.bss 很特别:它通常不在文件中保存大量实际的 0 数据,而是记录大小。程序加载时再在内存中清零。
这能节省磁盘空间。
4. .symtab
.symtab 是符号表,记录函数名、变量名和相关地址信息。
后面讲符号解析和重定位时会重点用到它。
5. .got 和 .plt
.got 是全局偏移表,.plt 是过程链接表。
它们和动态链接密切相关,后面会用两篇文章专门讲。
现在先记住:
GOT/PLT 是动态库函数调用的重要基础设施。
七、链接视图和执行视图
ELF 可以从两个视角理解。
1. 链接视图
链接视图关注 section。
链接器处理目标文件时,会关心:
.text 在哪里
.data 在哪里
符号表在哪里
重定位表在哪里
所以链接视图主要对应:
Section Header Table
2. 执行视图
执行视图关注 segment。
操作系统加载程序时,不会一个 section 一个 section 地零散加载,而是按 segment 加载。
执行视图主要对应:
Program Header Table
可以简单总结:
链接器看 section;
操作系统加载器看 segment。
八、为什么 section 要合并成 segment
一个 ELF 文件里可能有很多 section。如果加载时每个 section 都单独映射,会浪费内存页,也不利于权限管理。
现代系统以页为单位管理内存,常见页大小是 4KB。很多 section 很小,如果每个都单独占页,会造成大量碎片。
所以在生成可执行文件时,链接器会把属性相同或相近的 section 合并到 segment 中。
例如:
可读 + 可执行的 section -> 合并到代码相关 LOAD segment
可读 + 可写的 section -> 合并到数据相关 LOAD segment
只读数据 -> 合并到只读 segment
这样操作系统加载时就可以按 segment 建立内存区域,并设置权限:
代码段:可读、可执行
数据段:可读、可写
栈:可读、可写
这就是 readelf -l 中 LOAD 段的重要意义。
九、动静态库与 ELF 的关系
现在回头看前面几篇的内容:
1. 静态库
静态库 .a 是多个 .o 的归档包,而 .o 是 ELF 可重定位文件。
所以静态库本质上是在组织一批 ELF 目标文件。
2. 动态库
动态库 .so 本身就是 ELF 共享目标文件。
它可以被链接器引用,也可以在运行时被动态链接器加载。
3. 可执行文件
最终程序也是 ELF。它的 Program Header Table 会告诉操作系统如何加载它。
所以整个链条可以写成:
.c -> .o(ELF REL) -> .a 或 .so -> 可执行文件(ELF EXEC/DYN)

十、常用观察命令
1. file
查看文件大类:
file main.o
file libmyc.so
file main
2. readelf -h
查看 ELF Header:
readelf -h main
3. readelf -S
查看 section:
readelf -S main.o
readelf -S main
4. readelf -l
查看 segment:
readelf -l main
5. size
查看各段大致大小:
size main
输出中常见:
text data bss
这能帮助你观察程序中代码、已初始化数据、未初始化数据分别占多少空间。
十一、一个推荐实验
准备两个文件。
hello.c:
#include <stdio.h>
extern void run(void);
int main()
{
printf("hello\\n");
run();
return 0;
}
code.c:
#include <stdio.h>
void run(void)
{
printf("run\\n");
}
编译成 .o:
gcc -c hello.c
gcc -c code.c
观察:
file hello.o
readelf -h hello.o
readelf -S hello.o
链接:
gcc hello.o code.o -o main
观察可执行文件:
file main
readelf -h main
readelf -l main
readelf -S main
你会看到:
.o 是可重定位 ELF;
main 是可执行 ELF;
main 有 Program Header Table;
section 会映射到 segment。
十二、总结
ELF 是理解 Linux 编译、链接、库和加载的关键。
.o、.so、可执行程序都和 ELF 有关:
.o:ELF 可重定位文件
.so:ELF 共享目标文件
可执行程序:ELF 可执行文件或 PIE 形式共享对象
链接器主要关注 section,操作系统加载程序主要关注 segment。理解这一点,后面讲符号表、重定位、动态库映射、GOT/PLT 时就不会迷路。
下一篇,我们继续深入链接器最核心的工作:符号解析与重定位。也就是链接器到底如何把一个文件里的函数调用,接到另一个文件里的函数实现上。
网硕互联帮助中心![基于SpringBoot的校园社团活动与经费管理系统[源码免费+文档免费]-网硕互联帮助中心](https://www.wsisp.com/helps/wp-content/uploads/2026/08/20260829151713-6a92f7f9e7c60-220x150.png)




评论前必须登录!
注册