Linux 底层深入:目标文件、ELF 格式与程序加载全解析

Linux 底层深入:目标文件、ELF 格式与程序加载全解析
在这里插入图片描述

🔥草莓熊Lotso:个人主页
❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:

在这里插入图片描述

文章目录


前言:

在 Linux 开发中,我们每天都在和可执行程序打交道,但你是否好奇:gcc编译后生成的.o文件是什么?可执行程序为什么能直接运行?动态库又是如何被加载到内存中的?这些问题的答案,都藏在目标文件、ELF 格式和程序加载的底层逻辑里。本文从目标文件的本质入手,深入剖析 ELF 文件的结构、section 与 segment 的关系,最终讲透程序加载与进程虚拟地址空间的映射逻辑,帮你打通从源代码到运行程序的全链路认知。

一. 目标文件:编译后的 “半成品”

1.1 目标文件的本质

当我们用gcc -c编译 C/C++ 源代码时,编译器会将源码翻译成 CPU 能识别的机器码,但不会进行最终链接 —— 这个中间产物就是目标文件(后缀.o)。

  • 核心作用:作为程序的 “半成品”,存储单个模块的代码和数据,等待链接器组合成可执行程序;
  • 关键特性:修改单个源码文件后,只需重新编译对应的目标文件,无需全量编译,提升开发效率;
  • 文件格式:Linux 下目标文件遵循 ELF 格式(Executable and Linkable Format),是二进制文件的标准化封装。

1.2 目标文件的生成与验证

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

// hello.c#include<stdio.h>voidrun();// 声明外部函数intmain(){printf("hello world!\n");run();return0;}// code.c#include<stdio.h>voidrun(){printf("running...\n");}

编译生成目标文件

# 编译源码生成目标文件(-c:只编译不链接) gcc -c hello.c code.c # 查看生成的目标文件ls-l *.o # 验证文件类型(确认是ELF格式)file hello.o 
在这里插入图片描述
  • relocatable:表示该 ELF 文件是 “可重定位文件”(目标文件类型);

not stripped:表示文件保留了符号表等调试信息。

在这里插入图片描述

1.3 目标文件的核心问题:未解析的外部符号

目标文件是独立编译的,因此会存在 “不知道外部函数 / 变量地址” 的问题。例如:

  • hello.o中的printf(来自 C 标准库)和run(来自code.o),编译时无法确定其内存地址,编译器会暂时将跳转地址设为 0;
  • 这些未确定的地址,需要在链接阶段由链接器修正 —— 这就是 “重定位” 的核心目的。这个我们后面还会详细讲述的

二. ELF 文件:Linux 下的 “万能二进制格式”

ELF 是 Linux 系统中可执行程序、目标文件、动态库、核心转储文件(core dump)的统一格式标准。理解 ELF,就掌握了 Linux 二进制文件的 “通用语言”。

2.1 ELF 文件的四大类型

ELF 文件类型后缀用途示例
可重定位文件.o目标文件,用于链接生成可执行程序 / 动态库hello.o、code.o
可执行文件无(或 .out)可直接运行的程序/bin/ls、a.out
共享目标文件.so动态库,运行时加载/lib64/libc.so.6
核心转储文件.core进程崩溃时的内存快照,用于调试core.12345

2.2 ELF 文件的核心结构

无论哪种 ELF 文件,都由四大核心部分组成,从文件开头到末尾依次排列:

在这里插入图片描述

2.2.1 ELF 头:文件的 “身份证”

  • 核心作用:描述 ELF 文件的全局属性,定位其他部分的位置;
  • 关键信息
    • 魔数(Magic):7f 45 4c 46(ELF 文件的标识);
    • 文件类型(Type):可重定位 / 可执行 / 动态库等;
    • 目标架构(Machine):x86-64、ARM 等;
    • 入口点地址(Entry):可执行程序运行时的起始地址(目标文件为 0);
    • 程序头表偏移(e_phoff)和节头表偏移(e_shoff):定位程序头表和节头表的位置。
  • 实战查看 ELF 头
# 查看目标文件hello.o的ELF头 readelf -h hello.o # 查看可执行程序a.out的ELF头 gcc hello.o code.o -o a.out readelf -h a.out 
在这里插入图片描述

2.2.2 节(Section):文件的 “功能模块”

节是 ELF 文件的基本组成单位,按功能划分,核心节如下:
好的,已根据您提供的内容整理为表格。

节名称类型用途
.text代码节存储可执行机器指令(程序核心逻辑)
.data数据节存储已初始化的全局变量和局部静态变量
.bss未初始化数据节为未初始化的全局变量 / 静态变量预留空间(不占文件空间,加载时分配内存)
.symtab符号表存储函数名、变量名与地址的映射关系
.reloc重定位表记录需要链接时修正的地址(如外部函数调用)
.rodata只读数据节存储字符串常量等只读数据(如 printf 的字符串)
  • 实战查看节信息
# 查看a.out的所有节 readelf -S a.out 

2.2.3 节头表(Section Header Table):节的 “索引目录”

  • 核心作用:存储每个节的描述信息,包括节的名称、类型、大小、在文件中的偏移量等;
  • 链接器(如ld:通过节头表找到各个节,进行合并、重定位等操作。
在这里插入图片描述

2.2.4 程序头表(Program Header Table):加载的 “指南”

  • 核心作用:仅存在于可执行文件和动态库中,告诉操作系统如何将文件加载到内存,就像一个操作指南一样。

关键信息:描述 “段(Segment)” 的信息 —— 段是节的 “合并分组”(将属性相同的节合并,如只读可执行的.text 和.rodata 合并为一个段)。

在这里插入图片描述
在这里插入图片描述

2.3 关键概念:Section(节)与 Segment(段)的关系

很多初学者会混淆节和段,核心区别在于 “视角不同”:

  • 节(Section):链接视角(用于编译链接)—— 粒度细,按功能划分(如代码、数据、符号表),方便链接器处理;
  • 段(Segment):执行视角(用于加载运行)—— 粒度粗,按内存属性划分(如只读可执行、可读可写),方便操作系统加载和权限管理。
✅️ 为什么要合并节为段?减少内存碎片(减少空间浪费):例如.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我这里就不改了
在这里插入图片描述
‼️小补充:对于 程序头表节头表 又有什么用呢,其实 ELF 文件提供 2 个不同的视图/视角来让我们理解这两个部分:链接视图 (Linking view) - 对应节头表 Section header table文件结构的粒度更细,将文件按功能模块的差异进行划分,静态链接分析的时候一般关注的是链接视图,能够理解 ELF 文件中包含的各个部分的信息。为了空间布局上的效率,将来在链接目标文件时,链接器会把很多节(section)合并,规整成可执行的段(segment)、可读写的段、只读段等。合并了后,空间利用率就高了,否则,很小的很小的一段,未来物理内存页浪费太大(物理内存页分配一般都是整数倍一块给你,比如
4k),所以,链接器趁着链接就把小块们都合并了。执行视图 (execution view) - 对应程序头表 Program header table告诉操作系统,如何加载可执行文件,完成进程内存的初始化。一个可执行程序的格式中,一定有 program header table。

说白了就是:一个在链接时作用,一个在运行加载时作用。

在这里插入图片描述

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

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

3.1 阶段 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进行合并,和上述过程一样,所以链接其实就是将编译之后的所有目标文件连同用到的一些静态库运行时库组合,拼装成一个独立的可执行文件。其中就包括我们之前提到的地址修正,当所有模块组合在一起之后,链接器会根据我们的.o文件或者静态库中的重定位表找到那些需要被重定位的函数全局变量,从而修正它们的地址。这其实就是静态链接的过程。
在这里插入图片描述
在这里插入图片描述


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

3.2 阶段 2:加载运行(可以暂时先不看,继续往下理解)

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

  • 创建进程:操作系统调用fork创建新进程,分配进程控制块(task_struct)和虚拟地址空间;
  • 解析程序头表:读取 ELF 的程序头表,识别需要加载的段(LOAD 类型);
  • 内存映射:通过mmap系统调用,将 ELF 文件中的段映射到进程虚拟地址空间的对应区域(如只读可执行段映射到 0x400000 开始的地址);
  • 初始化内存
    • 为.bss 节分配内存并清零;
    • 将.data 节的数据从文件复制到内存;
  • 设置程序入口:将 CPU 的程序计数器(PC)指向 ELF 头中的入口点地址(Entry),程序开始执行。
  • 注意:建议大家先往下看,这里我们可以暂时先不去理解,主要还是需要理解下面哪些图里面的一些逻辑过程。

四. 进程虚拟地址空间:ELF 的 “运行舞台”

4.1 虚拟地址的核心作用

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

  • 隔离进程:每个进程有独立的虚拟地址空间,互不干扰;
  • 简化编程:程序编译时使用 “平坦地址空间”(从 0 开始的连续地址)(加载到内存之前在磁盘上我们系统叫它逻辑地址,加载到内存之后我们习惯叫虚拟地址(线性地址)),无需关心物理内存布局;
  • 高效利用内存:通过页表映射物理内存,支持内存共享(如动态库)和交换(Swap)。
  • 大家可以仔细看下下面的图示解析,有点多但都很重要
在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

4.2 核心总结和补充

  • 统一格式:ELF 统一了目标文件、可执行程序、动态库等二进制文件的格式,让编译器、链接器、操作系统能无缝协作;
  • 分离视角:通过节(链接视角)和段(执行视角)的分离,兼顾了编译链接的灵活性和加载运行的高效性;
  • 虚拟地址抽象:ELF 编译时采用虚拟地址布局,屏蔽了物理内存的细节,让程序开发和加载更简单;
  • 模块化支持:目标文件的设计支持模块化开发,单个模块修改后无需全量编译,提升开发效率。
  • EIP -> mmu -> 通过CR3拿到页表物理地址 -> 查表 -> 去内存中找到物理地址
  • 页表初始化,数据从哪里来?ELF解决了虚拟地址(逻辑地址,Linux下两个都行),加载使具有物理地址
  • EIP怎么找到下一条地址,当前地址 + 当前指令长度 = 下一条指令地址
  • 虚拟地址空间 -> mm_struct <- 数据从哪里来?(ELF->section->segment)
  • 进程虚拟地址空间,不仅仅是需要OS支持,也需要CPU本身在硬件上支持,比如CR3 + MMU + 页表,也需要编译器在编译上支持,比如统一编址。

补充:如下图,结合最后的关键要点一起看

在这里插入图片描述


上图中关键要点


结尾:

🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 

结语:

✨把这些内容吃透超牛的!放松下吧✨ʕ˘ᴥ˘ʔづきらど

Read more

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 作者:高瑞冬 本文目录 * AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 * 一、MCP协议简介 * 二、创建MCP工具集 * 1. 获取MCP服务地址 * 2. 在FastGPT中创建MCP工具集 * 三、测试MCP工具 * 四、AI模型调用MCP工具 * 1. 调用单个工具 * 2. 调用整个工具集 * 五、私有化部署支持 * 1. 环境准备 * 2. 修改docker-compose.yml文件 * 3. 修改FastGPT配置 * 4. 重启服务 * 六、使用MCP-Proxy集成多个MCP服务 * 1. MCP-Proxy简介 * 2. 安装MCP-Proxy * 3. 配置MCP-Proxy * 4. 将MCP-Proxy与FastGPT集成 * 5. 高级配置

By Ne0inhk
【大模型实战篇】基于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