PT_LOAD 的代码段(通常标记为 R-X,即可读可执行)中,包含了程序运行所需的核心指令和必要的只读数据。虽然它被称为“代码”段,但不仅仅只有 .text 节。

以下是代码段中主要包含的内容及其具体作用:

1. 核心指令节

这是代码段的主体,占据了绝大部分空间。

  • .text
    • 内容:编译器将源代码编译成的机器指令。
    • 作用:存放程序的主要业务逻辑。当你在调试器中单步调试时,大部分时间都在这里。这是 CPU 主要执行的区域。
    • 特点:通常只读、可执行,多个进程可以共享内存中的这一部分以节省资源。

2. 程序生命周期控制节

这些代码负责在 main() 函数执行前后进行环境的建立和清理。

  • .init / .init_array
    • 内容:初始化函数指针数组或初始化代码片段。
    • 作用:在 main() 函数执行之前执行。
      • 负责 C++ 全局对象的构造函数调用。
      • 负责 __attribute__((constructor)) 标记的函数执行。
      • 初始化静态变量等。
  • .fini / .fini_array
    • 内容:终止函数指针数组或终止代码片段。
    • 作用:在 main() 函数返回或调用 exit() 之后执行。
      • 负责 C++ 全局对象的析构函数调用。
      • 负责 __attribute__((destructor)) 标记的函数执行。

3. 动态链接机制节

这些内容是实现动态链接的关键,允许程序在运行时调用共享库(如 libc.so)中的函数。

  • .plt (Procedure Linkage Table)
    • 内容:一小段跳板代码。
    • 作用:当程序调用外部共享库的函数(如 printf)时,程序并不直接跳转到该函数地址(因为编译时不知道该地址),而是跳转到 .plt 中的对应条目。
      • .plt 代码负责触发动态链接器去查找目标函数的真实地址,并在第一次调用后修正地址,后续直接跳转。
  • .got.plt (Global Offset Table for PLT)
    • 内容:地址表。
    • 作用:配合 .plt 工作。初始时存放的是解析函数的入口,解析完成后会被动态链接器更新为外部函数的真实内存地址。
    • 注:虽然 .got 本身通常在数据段(RW),但 .got.plt 有时为了优化或布局原因会被放置在代码段附近,或者作为一个过渡区域。

4. 只读数据节

虽然这些是“数据”而非“代码”,但因为它们在运行期间不可修改,为了节省段的数量和内存页对齐,通常被归并入代码段。

  • .rodata (Read-Only Data)
    • 内容
      • 字符串常量(如 printf("Hello") 中的 "Hello")。
      • const 修饰的全局变量。
      • Switch-case 语句的跳转表。
    • 作用:防止程序意外修改常量值,提供数据给 .text 中的指令读取。
  • .interp
    • 内容:一个简单的 ASCII 字符串,例如 /lib64/ld-linux-x86-64.so.2
    • 作用:指定“程序解释器”(即动态链接器)的路径。
      • 内核在加载 ELF 文件时,会读取这个字符串,先加载指定的动态链接器,由链接器负责加载程序依赖的其他共享库。

5. 异常与调试支持节

用于支持高级语言特性(如 C++ 异常)和调试工具。

  • .eh_frame / .eh_frame_hdr
    • 内容:描述函数栈帧布局的元数据。
    • 作用
      • C++ 异常处理:当抛出异常时,运行时需要根据这些信息进行栈回滚,找到对应的 catch 块。
      • 栈保护:用于实现 Stack Unwinding 机制。

总结表

Section 名称 类型 关键作用
.text 代码 存放 main 及其他函数的机器指令。
.init 代码 main 执行前的环境初始化(如全局对象构造)。
.fini 代码 main 退出后的资源清理(如全局对象析构)。
.plt 代码 动态链接跳板,实现外部函数的延迟绑定。
.rodata 数据 存放字符串常量const 全局变量。
.interp 数据 告诉内核用哪个动态链接器来加载程序。
.eh_frame 数据 支持 C++ 异常处理和栈回滚。
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐