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

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

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

《Git深度解析》:版本管理实战全解

🌟心向往之行必能至


🎥Cx330🌸的简介:


目录

前言:

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

1.1 ELF文件的四大类型

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

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

(1)ELF Header(文件头)

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

(3)Section Header Table(节头表)

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

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

2.2 加载运行(可以暂时先不看,继续往下理解)

三. 进程虚拟地址空间

3.1 虚拟地址的核心作用

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

结语:


前言:

在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段加载到内存后的虚拟地址
  • 下图中的A应该是R我这里就不改了

简单来说:链接看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程序运行的底层逻辑。

下一篇我们将深入动态链接PLT/GOT机制静态链接与动态链接的优劣对比,继续拆解Linux程序运行的底层细节,敬请关注。

🔥如果你在调试ELF文件、排查加载报错时遇到问题,欢迎留言交流,我会逐一解答。

Read more

【大模型实战篇】基于Claude MCP协议的智能体落地示例

【大模型实战篇】基于Claude MCP协议的智能体落地示例

1. 背景         之前我们在《MCP(Model Context Protocol) 大模型智能体第一个开源标准协议》一文中,介绍了MCP的概念,虽然了解了其概念、架构、解决的问题,但还缺少具体的示例,来帮助进一步理解整套MCP框架如何落地。         今天我们基于claude的官方例子--获取天气预报【1】,来理解MCP落地的整条链路。 2. MCP示例         该案例是构建一个简单的MCP天气预报服务器,并将其连接到主机,即Claude for Desktop。从基本设置开始,然后逐步发展到更复杂的使用场景。         大模型虽然能力非常强,但其弊端就是内容是过时的,这里的过时不是说内容很旧,只是表达内容具有非实时性。比如没有获取天气预报和严重天气警报的能力。因此我们将使用MCP来解决这一问题。         构建一个服务器,该服务器提供两个工具:获取警报(get-alerts)和获取预报(get-forecast)。然后,将该服务器连接到MCP主机(在本例中为Claude for Desktop)。         首先我们配置下环

By Ne0inhk
基于腾讯云HAI + DeepSeek快速设计自己的个人网页

基于腾讯云HAI + DeepSeek快速设计自己的个人网页

前言:通过结合腾讯云HAI 强大的云端运算能力与DeepSeek先进的 AI技术,本文介绍高效、便捷且低成本的设计一个自己的个人网页。你将了解到如何轻松绕过常见的技术阻碍,在腾讯云HAI平台上快速部署DeepSeek模型,仅需简单几步,就能获取一个包含个人简介、技能特长、项目经历及联系方式等核心板块的响应式网页。 目录 一、DeepSeek模型部署在腾讯云HAI 二、设计个人网页 一、DeepSeek模型部署在腾讯云HAI 把 DeepSeek 模型部署于腾讯云 HAI,用户便能避开官网访问限制,直接依托腾讯云 HAI 的超强算力运行 DeepSeek-R1 等模型。这一举措不仅降低了技术门槛,还缩短了部署时间,削减了成本。尤为关键的是,凭借 HAI 平台灵活且可扩展的特性,用户能够依据自身特定需求定制专属解决方案,进而更出色地适配特定业务场景,满足各类技术要求 。 点击访问腾讯云HAI控制台地址: 算力管理 - 高性能应用服务 - 控制台 腾讯云高性能应用服务HAI已支持DeepSeek-R1模型预装环境和CPU算力,只需简单的几步就能调用DeepSeek - R1

By Ne0inhk
AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

云边有个稻草人-ZEEKLOG博客 目录 引言 一、什么是DeepSeek? 1.1 DeepSeek平台概述 1.2 DeepSeek的核心功能与技术 二、蓝耘通义万相2.1概述 2.1 蓝耘科技简介 2.2 蓝耘通义万相2.1的功能与优势 1. 全链条智能化解决方案 2. 强大的数据处理能力 3. 高效的模型训练与优化 4. 自动化推理与部署 5. 行业专用解决方案 三、蓝耘通义万相2.1与DeepSeek的对比分析 3.1 核心区别 3.2 结合使用的优势 四、蓝耘注册流程 五、DeepSeek与蓝耘通义万相2.1的集成应用 5.1 集成应用场景 1. 智能医疗诊断

By Ne0inhk
如何通过 3 个简单步骤在 Windows 上本地运行 DeepSeek

如何通过 3 个简单步骤在 Windows 上本地运行 DeepSeek

它是免费的——社区驱动的人工智能💪。         当 OpenAI 第一次推出定制 GPT 时,我就明白会有越来越多的人为人工智能做出贡献,并且迟早它会完全由社区驱动。         但从来没有想过它会如此接近😂让我们看看如何在 Windows 机器上完全免费使用第一个开源推理模型!  步骤 0:安装 Docker 桌面         我确信很多人已经安装了它,所以可以跳过,但如果没有 — — 这很简单,只需访问Docker 的官方网站,下载并运行安装 👍         如果您需要一些特定的设置,例如使用 WSL,那么有很多指导视频,请查看!我将继续下一步。 步骤 1:安装 CUDA 以获得 GPU 支持         如果您想使用 Nvidia 显卡运行 LLM,则必须安装 CUDA 驱动程序。(嗯……是的,它们需要大量的计算能力)         打开CUDA 下载页面,

By Ne0inhk