操作系统学习笔记:ELF 文件与常用 ELF 节详解
一、基础前置认知
1. 什么是 ELF 文件
Windows 系统的可执行程序是 .exe 格式,Linux 系统下可执行程序、动态库、编译中间目标文件,统一遵循ELF 格式。
ELF 是磁盘上的静态文件,程序未运行时存储在硬盘中。文件内部被划分成多个section(节),用于分类存放程序代码、常量、全局变量元数据与初始数据。
执行程序时,操作系统不会直接拷贝 ELF 数据,而是通过页映射的方式,将 ELF 的多个 section,按权限合并映射为进程虚拟内存中的 segment(内存段),供运行中的进程使用。
2. section 和 segment 的概念区分
section(节):ELF 文件内部的划分单元,服务编译器、链接器,用来区分代码、常量、全局变量。.text、.rodata、.data、.bss 都是 section。操作系统加载程序时不读取 section 信息。
segment(段):由链接器生成,写在 program header(程序头),服务操作系统加载器。它把若干 section 打包在一起,规定映射到虚拟内存后的地址、内存读写执行权限。内核加载程序只读取 program header,按照 segment 的规则设置虚拟内存。
简单类比:ELF 文件像一个箱子。section 是箱子内部一个个贴了标签的小抽屉;segment 是写在箱子外面的搬运说明,告诉操作系统搬运时怎么摆放、设置权限。搬运工(内核加载器)只看搬运说明,不会看抽屉上的标签。
多对一是常态:一个 segment 可以包含多个 section;一个 section 极少情况可以拆分到多个 segment。
section 描述信息保存在 section header table;segment 描述信息保存在 program header table。
section 属于静态概念(文件躺在硬盘上);segment 用来描述加载之后虚拟内存的区域。
磁盘文件权限(chmod 设置的 rwx),和 segment 定义的进程内存权限,二者完全独立,不要混淆。
3. 默认打包分组(gcc 默认链接脚本)
segment1(r-x 可读可执行):只打包
.textsegment2(r-- 只读):只打包
.rodatasegment3(rw- 可读可写):打包
.data + .bss
二、常用 ELF 节完整详解(命名 + 存储 + 权限 + 特性)
这四类 ELF 节仅存储编译期确定的指令、全局数据、静态数据,不包含函数局部变量、运行期动态内存。所有命名均对应英文原意,无需死记硬背。
1. .text
命名由来:源自老式计算机术语 text,特指程序机器指令,非普通文字。 存储内容:程序编译生成的全部 CPU 机器指令,包含所有自定义函数、程序业务逻辑代码。 磁盘状态:真实占用 ELF 文件磁盘空间,完整存储指令数据。 内存权限(标准):可读、可执行、不可写(r-x)。禁止运行时修改,防止程序逻辑被篡改。 核心特性:多进程运行同一个程序时,该内存页可共享,不会重复拷贝物理内存,节省资源;进程修改代码会触发写时复制。
2. .rodata
命名由来:read-only data,直译只读数据。 存储内容:程序中所有只读常量,典型为字符串常量、全局 const 修饰的常量。 磁盘状态:真实占用 ELF 文件磁盘空间,完整存储常量数据。 内存权限(标准、安全核心):可读、不可写、不可执行(r--)。 关键说明:现代 Linux 默认开启 NX 数据执行保护,严格隔离指令与数据。数据区域绝对禁止执行,是防溢出攻击的基础保障。 示例:char *str = "hello world"; 中的字符串常量存储在该节。
3. .data
命名由来:data,特指已初始化的可读写数据。 存储内容:定义时赋予初始值的全局变量、静态局部变量。 磁盘状态:真实占用 ELF 文件磁盘空间,文件中保存变量的初始值数据。 内存权限:可读、可写、不可执行(rw-),程序运行中可修改变量值。 示例:int g_num = 10;、static int s_num = 20;。
4. .bss
命名由来:Block Started by Symbol,历史汇编遗留术语,无直观字面含义,仅记特性即可。 存储内容:未初始化的全局变量、未初始化的静态变量。 磁盘状态:仅在 ELF 头部记录所需内存大小,不占用文件数据区磁盘空间,可执行文件体积更小。 内存特性:程序加载时,操作系统自动在虚拟地址空间开辟对应内存,并全额清零。这是 C/C++ 未初始化全局、静态变量默认值为 0 的根本原因。 内存权限:可读、可写、不可执行(rw-)。 示例:int g_empty;、static int s_empty;。
三、程序从磁盘到内存的标准加载流程
程序加载无一次性拷贝,全程基于虚拟内存页映射、缺页加载,是 Linux 核心机制:
磁盘静态状态:ELF 文件保存
.text、.rodata、.data的真实数据;.bss仅记录内存尺寸,无数据。启动程序:操作系统为新进程创建独立的虚拟地址空间。
建立页映射:操作系统按 segment 分组规则,建立虚拟地址与磁盘文件的映射关系,不立刻加载数据到物理内存。
缺页加载:程序首次执行代码、读取常量时触发缺页异常,内核才将对应磁盘数据载入物理内存。
初始化 BSS:操作系统开辟 BSS 虚拟内存并清零,完成程序内存初始化。
四、堆、栈与 ELF 节的本质区别
堆、栈不存在于 ELF 文件中,不属于编译期静态内存,是进程运行期动态开辟的内存:
栈(Stack):进程创建时由内核自动开辟,存放函数局部变量、栈帧、返回地址,随函数调用动态创建、销毁。
堆(Heap):程序运行后通过 malloc /mmap 动态申请,由用户手动管理生命周期。
五、核心信息汇总表
表格