欢迎光临
我们一直在努力

Re:Linux 系统篇(三十):库的制作与原理Chapter1:初识 ELF 格式 —— Linux 二进制文件的底层组织逻辑

在这里插入图片描述

观众老爷们大家好 这里是邪修KING的独家频道

本文属于系列Linux系统篇 ——动静态库

一起学Linux的小伙伴可订阅专栏: Linux系统篇

前面我们讲了编译、链接、静态库与动态库,一直在和 .o、可执行程序、.so 打交道。但很少有人往下深挖一层:这些二进制文件内部到底是什么结构?代码、数据、符号是怎么组织的?操作系统凭什么能把它加载到内存运行? 答案就是 ELF(Executable and Linkable Format),它是 Linux 世界里二进制文件的统一组织规范。 本篇我们从整体视角切入,讲清楚 ELF 是什么、里面装了什么、Section 与 Segment 的核心区别,以及它在整个程序生命周期里的角色。


一、一句话认识 ELF

ELF 是 Linux 系统中二进制文件的标准组织格式。 它不是某一个具体的程序,而是一套约定:规定了程序里的代码、数据、符号、加载信息应该按照什么结构、什么顺序存放在文件里。

我们日常接触的三类二进制文件,本质上都遵循 ELF 格式:

  • 目标文件 .o
  • 可执行程序 a.out
  • 动态库 .so

尽管用途不同、阶段不同,但它们的内部结构都符合 ELF 规范,都包含统一的头部、段表、程序头表等结构。

ELF 文件整体结构
┌─────────────────────┐
│ ELF Header 头部 │ → 标识文件类型、版本、架构等基础信息
├─────────────────────┤
│ Section 各个段 │ → 代码、数据、符号等具体内容分区存放
├─────────────────────┤
│ Section Table 段表 │ → 记录所有 Section 的位置、大小、属性
├─────────────────────┤
│ Program Header Table│ → 程序头表,描述运行时如何加载
└─────────────────────┘


二、ELF 里核心装了什么?5 个经典 Section

ELF 内部按功能分成不同的 Section(段),每个段存放一类内容。最核心、最常见的有 5 个。

2.1 .text:代码段

存放程序的机器指令,也就是我们写的代码编译后生成的二进制指令。 特点:只读、可执行。 比如 int main() { printf("hello"); },main 函数编译后的机器码,就存在 .text 段里。

2.2 .data:数据段

存放已经初始化的全局变量、静态变量。 特点:可读写。 比如 int global = 10;,这个初始值为 10 的全局变量,就存在 .data 段。

2.3 .rodata:只读数据段

存放只读数据,比如字符串常量、const 修饰的常量。 特点:只读,不可写。 比如 printf("hello world"); 里的字符串 "hello world",就存放在 .rodata 段。

2.4 .bss:未初始化数据段

存放未初始化的全局变量、静态变量。 特点:不占用实际文件空间,程序加载时由内核清零。 比如 int g_val; 这种没给初始值的全局变量,就归属 .bss 段。它在文件里只是占位,不存真实数据,运行时自动初始化为 0。

2.5 .symtab:符号表

存放程序里所有的符号信息:函数名、变量名、对应的地址、类型等。 比如 main、global、printf 这些符号的信息,都记录在符号表里。 作用:给链接器做符号解析、重定位用,也给调试工具提供信息。

💡 一个简单的对应示例

int global = 10; // .data 段
const char *str = "hi"; // "hi" 在 .rodata,str 指针在 .data

int main() // .text 段
{
static int s; // .bss 段
printf(str);
return 0;
}
// main、global、s、printf 这些名字 → .symtab 符号表


三、最容易混淆:Section 与 Segment 不是一回事

这是 ELF 最核心的辨析点,90% 的初学者都会搞混。 两者是完全不同视角下的概念:Section 是文件视角,Segment 是运行 / 加载视角。

3.1 Section:文件组织视角

  • 关注的问题:文件里面具体存了什么?
  • 代表:.text、.data、.rodata、.symtab、.debug 等
  • 服务对象:链接器、调试器、分析工具
  • 特点:分类细、数量多,按功能划分

3.2 Segment:内存加载视角

  • 关注的问题:程序运行的时候,怎么装进内存?
  • 代表:可执行段、可读段、可写段
  • 服务对象:操作系统加载器
  • 特点:把属性相同的多个 Section 合并到一起,作为一个整体映射到内存

3.3 两者的关系

多个属性相同的 Section,会被组织到同一个 Segment 里。 比如:

  • .text、.rodata 都是只读的,会被合并到一个「只读可执行 Segment」
  • .data、.bss 都是可读写的,会被合并到一个「可读写 Segment」

一句话总结: Section 是给链接器看的,用来做文件内的功能划分;Segment 是给操作系统看的,用来做内存映射加载。


四、Program Header Table 是干什么的?

Program Header Table(程序头表),就是专门用来描述 Segment 的表。 可以把它理解成「操作系统加载说明书」:告诉操作系统,这个程序运行时应该怎么映射到内存。

里面记录了每个 Segment 的:

  • 在文件中的偏移位置
  • 加载到内存的虚拟地址
  • 大小
  • 权限(读 / 写 / 执行)

操作系统加载程序的时候,就是照着程序头表,把一个个 Segment 按要求映射到内存的对应位置,程序就能跑起来了。

整体关系梳理

ELF 文件
├── Section 们 + Section Table → 文件内部怎么组织,给链接器用
└── Program Header Table → 运行时怎么加载,给操作系统用


五、ELF 最终要解决什么?

在 ELF 之前,不同系统、不同工具的二进制格式五花八门,编译、链接、加载各玩各的,兼容性极差。 ELF 的出现,提供了一套统一的二进制标准,贯穿了程序的整个生命周期:

  • 编译阶段:.c 编译成 .o 目标文件,.o 就是 ELF 格式
  • 链接阶段:链接器把多个 .o 和库链接成可执行程序,还是 ELF 格式
  • 加载阶段:操作系统读取 ELF 的程序头表,把 Segment 映射到内存
  • 运行阶段:CPU 从入口地址开始执行代码
  • 💡 ELF 就像整个程序世界的「通用集装箱」 不管里面装的是什么货物(代码 / 数据 / 符号),不管是运输(链接)还是卸货(加载),都用统一的规格,所有工具都能识别、都能处理。 它是连接「编译、链接、加载、运行」全流程的统一二进制载体。


    全文总结・一张核心图

    ELF
    │
    ┌──────────┴──────────┐
    ↓ ↓
    Section Segment
    ↓ ↓
    文件组织 内存加载
    │ │
    └──────────┬──────────┘
    ↓
    贯穿编译 / 链接 / 加载 / 运行

    核心结论

  • ELF 是 Linux 二进制文件的标准格式,.o、可执行程序、.so 都遵循 ELF 规范。
  • ELF 内部按功能划分 Section,常见的有 .text 代码、.data 初始化数据、.rodata 只读数据、.bss 未初始化数据、.symtab 符号表。
  • Section 是文件视角,服务链接;Segment 是加载视角,服务操作系统,相同属性的 Section 合并成一个 Segment。
  • Program Header Table 是加载说明书,描述 Segment 如何映射到内存。
  • ELF 是贯穿编译、链接、加载、运行全流程的统一二进制载体。
  • 下篇预告: 整体框架搭完,下一篇我们深入细节:用 readelf、objdump 工具实地解析 ELF 文件,看每个段里到底存了什么内容。 在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » Re:Linux 系统篇(三十):库的制作与原理Chapter1:初识 ELF 格式 —— Linux 二进制文件的底层组织逻辑
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址