ELF加载内存

当一个 ELF(Executable and Linkable Format)文件加载到内存后,它的各个段会根据文件中的描述被映射到内存的不同区域。ELF 文件被广泛用于 Unix/Linux 系统中的可执行文件、共享库和目标文件。典型的 ELF 文件包含多个段(sections),这些段被加载到内存中用于不同的目的,例如代码、数据、堆栈等。

ELF 文件的段(Section)与内存布局#

ELF 文件在磁盘上有多个段,这些段在被加载到内存后,操作系统将它们映射到不同的虚拟内存区域。不同的段有不同的功能,它们通常包括代码段、数据段和其他需要加载的内容。

ELF 文件的主要段和内存中的分布#

在内存中,ELF 文件通常会被分为以下几个关键段:

  1. .text 段(代码段)

    • 功能:该段存放可执行的程序代码,即编译后的指令。
    • 权限:一般标记为只读和可执行r-x)。
    • 内存位置:通常位于程序映射的最低虚拟地址附近,且通常是页面对齐的(比如 4KB 对齐)。
    • 映射到内存后:该段加载到内存后,系统将其标记为可执行的内存区域,而不会允许写入操作。

    内存布局示意图:

    0x08048000: (低地址)
        ↓
        .text 段(代码段)
        存储程序的可执行代码
        [r-x] (只读 + 可执行)
        ↑
    
  2. .rodata 段(只读数据段)

    • 功能:该段存储程序的只读数据,如字符串常量、全局常量等。
    • 权限:标记为只读r--),防止运行时修改。
    • 内存位置:通常紧随 .text 段之后加载到内存中。

    内存布局示意图:

    0x08049000: 
        ↓
        .rodata 段(只读数据段)
        存储只读数据(例如字符串常量)
        [r--] (只读)
        ↑
    
  3. .data 段(数据段)

    • 功能:该段存储初始化的全局变量和静态变量。
    • 权限:标记为可读和可写rw-),因为这些变量在程序运行过程中可能会被修改。
    • 内存位置:通常位于 .rodata 段之后。

    内存布局示意图:

    0x0804A000: 
        ↓
        .data 段(数据段)
        存储初始化的全局变量
        [rw-] (可读 + 可写)
        ↑
    
  4. .bss 段(未初始化数据段)

    • 功能:该段存储未初始化的全局变量和静态变量。在 ELF 文件中,.bss 段并不占据磁盘空间(只在内存中分配),其大小在 ELF 文件头中描述。
    • 权限:标记为可读和可写rw-),因为这些变量可能会被修改。
    • 内存位置:通常位于 .data 段之后。

    内存布局示意图:

    0x0804B000: 
        ↓
        .bss 段(未初始化数据段)
        存储未初始化的全局变量
        [rw-] (可读 + 可写)
        ↑
    
  5. 堆(Heap)

    • 功能:堆是动态分配内存的区域,用于程序在运行时通过 malloc 等动态分配函数申请的内存。
    • 权限:标记为可读和可写rw-)。
    • 内存位置:堆区域位于 .bss 段之后,通常会随着程序的运行动态增长,操作系统通过系统调用(如 brksbrk)来调整堆的大小。

    内存布局示意图:

    0x0804C000:
        ↓
        堆
        动态内存分配区域
        [rw-] (可读 + 可写)
        ↑
    
  6. 栈(Stack)

    • 功能:栈是用于函数调用时存储局部变量、返回地址等信息的区域。
    • 权限:标记为可读和可写rw-),并具有自动增长的特点(通常栈向下增长)。
    • 内存位置:栈通常位于虚拟地址空间的高地址处,并向低地址方向增长。

    内存布局示意图:

    高地址:
        ↓
        栈(Stack)
        [rw-] (可读 + 可写)
        ↑
    

ELF 文件的段加载和布局示意图#

内存中 ELF 文件加载后的典型布局示例如下:

内存高地址
+----------------------------+ 
|         栈段 (Stack)        |  ← 栈从高地址向低地址增长
+----------------------------+
|            ...              |
+----------------------------+
|         堆段 (Heap)         |  ← 堆从低地址向高地址增长
+----------------------------+
|         .bss 段             |  ← 未初始化数据
+----------------------------+
|         .data 段            |  ← 已初始化的全局变量
+----------------------------+
|        .rodata 段           |  ← 只读数据
+----------------------------+
|         .text 段            |  ← 可执行代码
+----------------------------+
内存低地址
  • :位于高地址,向低地址方向增长。栈用于存储函数调用的局部变量和返回地址等信息。
  • :位于 .bss 段之后,随着动态内存分配不断增长。堆用于存储运行时动态分配的内存。
  • .bss 段:存储未初始化的全局变量和静态变量。
  • .data 段:存储已初始化的全局变量和静态变量。
  • .rodata 段:存储只读数据(如常量字符串、常量数组等)。
  • .text 段:存储程序的可执行代码。

各个段的权限说明#

  • .text 段:只读且可执行,存放程序的指令。
  • .rodata 段:只读,存放只读数据。
  • .data 段:可读可写,存放已初始化的全局变量和静态变量。
  • .bss 段:可读可写,存放未初始化的全局变量和静态变量。
  • :可读可写,用于动态内存分配。
  • :可读可写,用于函数调用时存储局部变量、返回地址等。

段加载的过程#

  1. 解析 ELF 文件头:加载器首先读取 ELF 文件头,了解文件的格式、段的数量和位置等信息。

  2. 加载各个段:根据程序头表(Program Header Table)中描述的段信息,加载器将各个段(如 .text.data.bss)从磁盘加载到内存,并设置相应的访问权限。

  3. 内核映射内存区域:操作系统的加载器为 ELF 文件中的各个段分配虚拟地址空间,并通过页表将虚拟地址映射到实际的物理内存。还会为堆和栈分配内存。

  4. 权限设置:根据段的类型和需求,加载器为每个段设置合适的权限。例如,.text 段被标记为可执行但不可写,.data 段可读可写等。

总结#

ELF 文件加载到内存后,各个段的分布和权限如下:

  • .text 段:存放可执行代码,通常位于内存低地址,具有只读和可执行权限。
  • .rodata 段:存放只读数据,具有只读权限。
  • .data 段:存放初始化的全局变量,具有读写权限。
  • .bss 段:存放未初始化的全局变量,
posted @   xiazichengxi  阅读(157)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
历史上的今天:
2023-09-18 230. 二叉搜索树中第K小的元素
2023-09-18 543. 二叉树的直径
点击右上角即可分享
微信分享提示
主题色彩