跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C

Linux ELF 格式与可执行程序加载全解析:从磁盘文件到运行进程

综述由AI生成Linux ELF 格式是二进制文件的通用标准,涵盖可重定位、可执行、共享库等类型。文章解析了 ELF 核心结构,包括文件头、程序头表和节头表,区分了链接视角的 Section 与加载视角的 Segment。通过编译链接生成可执行文件的过程,展示了符号解析与重定位机制。操作系统加载阶段利用虚拟地址空间映射段,实现内存隔离与权限管理。理解 ELF 结构与加载流程有助于排查程序崩溃及内存异常问题,掌握 Linux 程序运行的底层逻辑。

数字游民发布于 2026/3/24更新于 2026/5/34 浏览
Linux ELF 格式与可执行程序加载全解析:从磁盘文件到运行进程

前言

在 Linux 世界里,我们每天都在和各种可执行程序打交道:ls、gcc、自己编译的二进制文件……这些文件并非杂乱的机器码堆砌,而是遵循一套标准格式——ELF(Executable and Linkable Format,可执行与可链接格式)。它是 Linux 二进制文件的'身份证',更是操作系统加载、运行程序的核心依据。

本文将带你吃透ELF 文件结构,并一步步拆解可执行程序从触发执行到正式运行的完整加载流程,既有底层原理,也有实操验证,帮你彻底理解 Linux 程序的'诞生与启动'。


一、ELF 文件:Linux 二进制的标准载体

ELF 并非只代表可执行程序,它是一套通用的二进制格式标准,覆盖了 Linux 编译、链接、运行全生命周期的文件类型。相比老旧的 a.out 格式,ELF 具备跨架构、可扩展、双视角解析(链接/加载)的优势,成为 Unix-like 系统的主流二进制格式。

实战示例:生成并查看目标文件

// hello.c
#include<stdio.h>
void run(); // 声明外部函数
int main()
{
    printf("hello world!\n");
    run();
    return 0;
}

// code.c
#include<stdio.h>
void run()
{
    printf("running...\n");
}

编译生成目标文件:

# 编译源码生成目标文件(-c:只编译不链接)
gcc -c hello.c code.c
# 查看生成的目标文件
ls -l *.o
# 验证文件类型(确认是 ELF 格式)
file hello.o

文章配图

  • relocatable:表示该 ELF 文件是'可重定位文件'(目标文件类型);
  • :表示文件保留了符号表等调试信息。
not stripped

文章配图

1.1 ELF 文件的四大类型

我们日常接触的 ELF 文件主要分为四类,各司其职:

  • 可重定位文件(.o):编译阶段生成,包含独立机器码和重定位信息,无法直接运行,需经链接器合并为可执行文件/共享库;
  • 可执行文件(ET_EXEC):最终运行的程序,包含完整的代码、数据和加载指引,内核可直接加载执行;
  • 共享目标文件(.so,ET_DYN):动态链接库,运行时被加载到内存,多个进程可共享复用,节省内存;
  • 核心转储文件(core):程序崩溃时生成的内存镜像,用于调试定位崩溃原因。

文章配图

1.2 ELF 文件的双重视角:Section 与 Segment

ELF 文件设计最精妙的点,在于同时支持链接视角和加载视角,通过两套结构实现分工协作:

  • Section(节区,链接视角):供编译器、链接器使用,按功能拆分代码、数据、符号表、重定位信息等,比如.text(代码)、.data(初始化数据)、.bss(未初始化数据)、.symtab(符号表);
  • Segment(段,加载视角):供操作系统加载器使用,将多个相关 Section 打包为一个段,统一映射到内存,注重内存权限和加载地址,比如代码段、数据段。

✅️ 为什么要合并节为段? 减少内存碎片 (减少空间浪费):例如.text(4097 字节)和.init(512 字节),分开加载需 3 个 4KB 内存页,合并后仅需 2 个; 统一权限管理:相同属性的节合并后,操作系统可一次性设置权限(如所有只读节合并为一个只读段)。

实战查看段信息:

# 查看 a.out 的程序头表(段信息)
readelf -l a.out

输出关键信息解读(主要是 LOAD 加载这个部分):

Program Headers:
Type     Offset           VirtAddr       PhysAddr       FileSiz      MemSiz       Flags Align
LOAD     0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000744 0x0000000000000744 R E 200000
LOAD     0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x0000000000000218 0x0000000000000220 RW 200000
  • LOAD:表示该段需要加载到内存;
  • R E:只读可执行(对应.text、.rodata 等节);
  • RW:可读可写(对应.data、.bss 等节);
  • VirtAddr:段加载到内存后的虚拟地址

文章配图

简单来说:链接看 Section,加载看 Segment。

1.3 ELF 核心结构:从头部到加载指引

一个标准的 ELF 文件,由三部分核心结构组成,层层递进指引程序加载:

(1)ELF Header(文件头)

位于文件最开头,是 ELF 的'总目录',固定长度(32 位 52 字节、64 位 64 字节),内核加载时首先读取这里验证文件合法性。核心字段包括:

  • 魔数(Magic):前 4 字节固定为**0x7f 45 4c 46**(对应 ASCII 的 DEL+ELF),是 ELF 文件的唯一标识,内核以此判断是否为合法 ELF;
  • 文件类型/架构:标明是可执行文件、动态库,以及适配的 CPU 架构(x86_64、ARM 等);
  • 程序入口地址(e_entry):程序加载后第一条指令的虚拟地址;
  • 程序头表/节头表偏移:指向 Segment 和 Section 的位置,是加载、链接的入口。

实操查看:执行 readelf -h 可执行文件 即可查看 ELF 文件头详情。

(2)Program Header Table(程序头表)

仅对可执行文件、动态库有效,是操作系统加载程序的**'装载地图'**。它是一个数组,每个元素描述一个 Segment 的信息:

  • 段类型(PT_LOAD:需加载到内存的段;PT_INTERP:动态链接器路径);
  • 文件偏移、虚拟内存地址、内存大小、文件大小;
  • 内存权限(可读 R、可写 W、可执行 X)。

实操查看:执行 readelf -l 可执行文件 查看程序头表(段信息)。

(3)Section Header Table(节头表)

供链接器和调试器使用,描述每个 Section 的名称、类型、偏移、大小等,调试、反汇编时依赖该表。

实操查看:执行 readelf -S 可执行文件 查看节头表信息。

文章配图


二、ELF 的生命周期:从源码到运行

ELF 文件的完整生命周期分为 '编译链接' 和 '加载运行' 两个阶段,每个阶段都有明确的核心操作:我们这里主要讲讲编译链接就可以了,运行可以继续往下看看虚拟地址空间先。

2.1 编译链接(生成可执行 ELF,研究静态链接)

无论是自己的 .o , 还是静态库中的 .o , 本质都是把.o 文件进行连接的过程,所以:研究静态链接,本质就是研究 .o 是如何链接的,我们这里就不打包成静态库来研究了。

核心目标:将多个目标文件(.o)和库文件合并,修正未解析的符号地址,生成可执行 ELF。

关键步骤:

  • 编译:gcc -c 将源码(hello.c、code.c)翻译成目标文件(hello.o、code.o),每个目标文件包含独立的.text、.data 等节;
  • 合并节:通过链接,链接器将所有目标文件的同名节合并(如所有.text 节合并为一个大的.text 节,.data 节同理);
  • 符号解析与重定位:链接器通过符号表(.symtab)找到未解析的符号(如 hello.o 中的 run 函数),修正其地址(指向 code.o 中 run 函数的实际位置)
  • 生成程序头表:根据合并后的节的属性,划分段(如只读可执行段、可读可写段),写入程序头表。

实战验证重定位效果:

# 反汇编目标文件 hello.o,查看未重定位的 call 指令
objdump -d hello.o | grep callq
# 反汇编可执行程序 a.out,查看重定位后的 call 指令
objdump -d a.out | grep callq

输出对比:

  • 目标文件 hello.o 中,call 指令地址为e8 00 00 00 00(地址未修正);
  • 可执行程序 a.out 中,call 指令地址为e8 dc fe ff ff(地址已修正为实际函数地址)。

文章配图

文章配图

文章配图

所以,链接过程中会涉及到对.o 中外部符号进行地址重定位。

2.2 加载运行

核心目标:操作系统根据 ELF 的程序头表,将文件加载到内存,创建进程并执行。

关键步骤:

  • 创建进程:操作系统调用 fork 创建新进程,分配进程控制块(task_struct)和虚拟地址空间;
  • 解析程序头表:读取 ELF 的程序头表,识别需要加载的段(LOAD 类型);
  • 内存映射:通过 mmap 系统调用,将 ELF 文件中的段映射到进程虚拟地址空间的对应区域(如只读可执行段映射到 0x400000 开始的地址);
  • 初始化内存:
    • 为.bss 节分配内存并清零;
    • 将.data 节的数据从文件复制到内存;
  • 设置程序入口:将 CPU 的程序计数器(PC)指向 ELF 头中的入口点地址(Entry),程序开始执行。

建议结合下文虚拟地址空间部分理解。


三、进程虚拟地址空间

3.1 虚拟地址的核心作用

现代操作系统都采用'虚拟地址机制',程序加载时使用的是虚拟地址,而非物理内存地址:

  • 隔离进程:每个进程有独立的虚拟地址空间,互不干扰;
  • 简化编程:程序编译时使用'平坦地址空间'(从 0 开始的连续地址)(加载到内存之前在磁盘上我们系统叫它逻辑地址,加载到内存之后我们习惯叫虚拟地址 (线性地址)),无需关心物理内存布局;
  • 高效利用内存:通过页表映射物理内存,支持内存共享(如动态库)和交换(Swap)。

大家可以仔细看下下面的图示解析,有点多但都很重要

文章配图

文章配图

四、加载流程核心知识点总结

  • 内核只负责加载,不负责动态链接:动态链接由用户态 ld-linux.so 完成,内核仅做内存映射和权限管理;
  • 虚拟地址而非物理地址:加载时使用虚拟地址,通过页表映射到物理内存,实现内存隔离和共享;
  • 按需加载(懒加载):内核并非一次性将整个 ELF 加载到内存,而是通过缺页异常,按需加载代码和数据,节省内存;
  • 内存权限隔离:代码段不可写、数据段不可执行,防范内存篡改、栈溢出等安全风险。

结语

ELF 格式是 Linux 二进制程序的基石,而加载流程则是连接磁盘文件与运行进程的桥梁。理解 ELF 结构,能帮我们更好地排查程序崩溃、库依赖、内存异常等问题;吃透加载流程,才能真正掌握 Linux 程序运行的底层逻辑。

目录

  1. 前言
  2. 一、ELF 文件:Linux 二进制的标准载体
  3. 编译源码生成目标文件(-c:只编译不链接)
  4. 查看生成的目标文件
  5. 验证文件类型(确认是 ELF 格式)
  6. 1.1 ELF 文件的四大类型
  7. 1.2 ELF 文件的双重视角:Section 与 Segment
  8. 查看 a.out 的程序头表(段信息)
  9. 1.3 ELF 核心结构:从头部到加载指引
  10. (1)ELF Header(文件头)
  11. (2)Program Header Table(程序头表)
  12. (3)Section Header Table(节头表)
  13. 二、ELF 的生命周期:从源码到运行
  14. 2.1 编译链接(生成可执行 ELF,研究静态链接)
  15. 反汇编目标文件 hello.o,查看未重定位的 call 指令
  16. 反汇编可执行程序 a.out,查看重定位后的 call 指令
  17. 2.2 加载运行
  18. 三、进程虚拟地址空间
  19. 3.1 虚拟地址的核心作用
  20. 四、加载流程核心知识点总结
  21. 结语
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • llama.cpp 启动效率优化指南:加载延迟与系统调优
  • Neo4j Windows 安装与配置教程
  • AI-Goofish-Monitor:闲鱼智能监控机器人指南
  • 基于 Java 在高德地图面查询检索中使用 WGS84 坐标的方法
  • GitHub Copilot 学生认证申请及使用指南
  • Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南
  • MacOS 安装 OpenClaw 并接入飞书机器人
  • 基于大模型 API 与 RAG 知识库构建智能客服机器人实战
  • MySQL 数据类型核心指南:选型、实战与避坑
  • Ubuntu 22.04 下 libwebkit2gtk-4.1-0 安装配置指南
  • 自定义 DialogAlert 对话框并实现复用
  • C++ 模板编程基础:泛型编程入门与实践
  • Windows 系统安装 Linux 子系统(WSL)完整指南
  • 基于 AI 大模型与 Playwright 的 Web UI 自动化测试实践
  • Visual Studio Code 集成 Cursor AI 实战指南
  • 腾讯 WorkBuddy 一键部署与 AI 办公智能体使用指南
  • OpenClaw 爆发推动低代码 AI 从工具赋能到生态重构
  • OpenClaw AI 助手跨设备迁移指南
  • DeepSeek 隐藏玩法与提示词技巧指南
  • Python 处理 Markdown 文件:生成、转换与解析实战

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online