Posts 聊聊可执行文件
Post
Cancel

聊聊可执行文件

1. 从操作系统角度看看可执行文件的加载

1.1进程建立过程

1. 创建一个独立的虚拟地址空间

  • 操作系统通过 execve() 系统调用为新进程创建一个干净的虚拟地址空间。
  • 清理旧地址空间中的内容(如代码段、数据段、堆栈等)。
  • 分配基本的地址空间结构,包括 用户态内存区域 和 内核态内存区域。
  • 创建虚拟地址与物理地址的映射(可做)

2. 读取可执行文件头, 建立 虚拟空间与可执行文件的映射关系

  • 检查是否为有效的 ELF 文件(ELF magic number 等)。
  • 根据 ELF 文件头解析 Program Header 表,获取各段的信息:

    • .text(代码段):映射为只读且可执行。
    • .data(已初始化数据段):映射为可读写。
    • .bss(未初始化数据段):分配物理内存。
    • .rodata(只读数据段):映射为只读。
  • 用系统调用(如 mmap)将文件的各个段映射到进程的虚拟地址空间。

3. 将CPU指令寄存器设置为可执行文件文件头

  • ELF 文件头中指定了程序的入口地址(Entry Point)。
  • 设置好初始堆栈内容(环境变量、命令行参数等)。
  • 切换到用户态,开始执行从入口地址开始的代码(通常是 _start 函数)。

1.2上述的几个概念描述

1. 节(Section)和段(Segment)的区别

属性节 (Section)段 (Segment)
面向对象编译器、链接器操作系统
层次文件层次文件到内存的映射层次
数量通常较多通常较少
冗余信息包含调试和符号信息,较为冗余仅保留运行时加载必要信息
是否映射到内存只有部分节被映射定义了所有需要映射的内容

为什么设计了 节 还要设计 段?

  • 节:服务于编译器和链接器,用于组织代码和数据,包含所有的程序信息,包括调试符号表、元数据等。
  • 段:服务于操作系统和运行时,告诉操作系统如何加载程序到内存,包括哪些内容需要加载、加载到哪里,以及加载后的权限。

2. 为什么有了节表(Section Header 表,SH 表) 还需要 程序头表(Program Header 表,PH 表)

PH 表的作用:从文件到内存的桥梁

ELF 文件中有两个主要表:

  • 节表(Section Header 表,SH 表):描述文件中各个节的细节,如 .text(代码)、.data(初始化数据)、.bss(未初始化数据)等。主要用于编译、链接和调试。
  • 程序头表(Program Header 表,PH 表):描述段(Segment)的信息,指导操作系统如何将 ELF 文件加载到内存中运行。

区别在于:

  • SH 表是文件视角,面向编译器和链接器。
  • PH 表是运行时视角,面向操作系统和动态链接器。
没有 PH 表,操作系统不知道如何加载 ELF 文件

如果没有 PH 表,操作系统根本无法确定:

  • 文件中的哪些部分需要加载。
  • 加载后它们该映射到内存中的什么地方。

3. 节(Section)和段(Segment)映射合并规则

  • 节(Section) 是编译器生成的逻辑单元,用于表示文件中的数据和指令内容。 主要存于节表。
  • 段(Segment) 是操作系统加载可执行文件时使用的内存单元。 主要存于Program Header 表。
目标文件的节信息
节名属性大小描述
.text可执行 + 只读1000存储程序指令
.data可写 + 初始化200存储已初始化的全局变量
.bss可写 + 未初始化300存储未初始化的全局变量
.rodata只读150存储只读的全局数据(常量)
.debug调试信息(不加载内存)500存储调试符号表
合并后生成的段
段名包含节属性大小描述
代码段.text可执行 + 只读1000加载到内存运行指令
数据段.data + .rodata可写 + 初始化350加载到内存运行全局变量
BSS 段.bss可写 + 未初始化300加载到内存,文件中不占空间
调试段.debug调试信息(不加载内存)500仅供调试使用
代码举例
1
2
3
4
5
# 查看节头表(Section Header Table, SHT)  查节
readelf -S <binary_file>

# 查看程序头表(Program Header Table, PHT) 查段
readelf -l <binary_file>

4. 段与VMA

VMA 是操作系统为进程维护的虚拟内存区域,通常由内核内存管理子系统(如 Linux 的 mm_struct)管理。 每个进程有多个 VMA,每个 VMA 表示一个连续的虚拟地址区间,通常与段或文件映射相关。

1
2
3
4
5
6
程序段 (Segments):          虚拟内存区域 (VMAs):
  [代码段] .text  ------->   VMA1: [0x00400000 - 0x0040FFFF] R-X
  [数据段] .data  ------->   VMA2: [0x00600000 - 0x00600FFF] RW-
  [BSS 段] .bss   ------->   VMA3: [0x00601000 - 0x00601FFF] RW-
  [堆段]          ------->   VMA4: [0x00700000 - 0x0080FFFF] RW-
  [栈段]          ------->   VMA5: [0xFFFFE000 - 0xFFFFFFFF] RW-
  • 段是程序逻辑上的划分,VMA 是操作系统对内存管理的实现。
  • 每个段通常会对应一个或多个 VMA,但 VMA 可能比段更细粒度,也更灵活。(例如,一个 .text 段的结尾部分包含了跳转表(需要写权限),系统会为其创建一个新的 VMA。)

    1
    2
    
    VMA1: [0x00400000 - 0x0040FFFF] R-X  (普通代码)
    VMA2: [0x00410000 - 0x00410FFF] RW-  (跳转表)
    
  • 多个段也可以合并成一个VMA(节省内存), 如将 .text .init 合并成为一个 Segment, 装载的时候可以看为一个整体 VMA.
  • 两者在权限、大小和位置上是一致的,但 VMA 是段映射到操作系统内存模型的结果。

2. 进程的虚拟空间分布

2.1 再聊 EFL 文件链接视图与执行视图

1
2
3
4
5
6
7
8
9
10
11
// 编译 静态链接 gcc -static SectionMapping.c -o SectionMapping.elf

#include <stdlib.h>
#include <unistd.h>

int main() {
        while(1) {
                sleep(1000);
        }
        return 0;
}

1. 链接视图 Section Header

节表 和 efl 中的对应节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
xm@xm:~/xx/complier$ readelf -S SectionMapping.elf 
There are 32 section headers, starting at offset 0xd4618:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.propert NOTE             0000000000400270  00000270
       0000000000000020  0000000000000000   A       0     0     8
  [ 2] .note.gnu.build-i NOTE             0000000000400290  00000290
       0000000000000024  0000000000000000   A       0     0     4
  [ 3] .note.ABI-tag     NOTE             00000000004002b4  000002b4
       0000000000000020  0000000000000000   A       0     0     4
  [ 4] .rela.plt         RELA             00000000004002d8  000002d8
       0000000000000240  0000000000000018  AI       0    20     8
  [ 5] .init             PROGBITS         0000000000401000  00001000
       000000000000001b  0000000000000000  AX       0     0     4
  [ 6] .plt              PROGBITS         0000000000401020  00001020
       0000000000000180  0000000000000000  AX       0     0     16
  [ 7] .text             PROGBITS         00000000004011a0  000011a0
       0000000000091b50  0000000000000000  AX       0     0     16
  [ 8] __libc_freeres_fn PROGBITS         0000000000492cf0  00092cf0
       0000000000001ca0  0000000000000000  AX       0     0     16
  [ 9] .fini             PROGBITS         0000000000494990  00094990
       000000000000000d  0000000000000000  AX       0     0     4
  [10] .rodata           PROGBITS         0000000000495000  00095000
       000000000001bfec  0000000000000000   A       0     0     32
  [11] .stapsdt.base     PROGBITS         00000000004b0fec  000b0fec
       0000000000000001  0000000000000000   A       0     0     1
  [12] .eh_frame         PROGBITS         00000000004b0ff0  000b0ff0
       000000000000a63c  0000000000000000   A       0     0     8
  [13] .gcc_except_table PROGBITS         00000000004bb62c  000bb62c
       00000000000000b1  0000000000000000   A       0     0     1
  [14] .tdata            PROGBITS         00000000004bd0c0  000bc0c0
       0000000000000020  0000000000000000 WAT       0     0     8
  [15] .tbss             NOBITS           00000000004bd0e0  000bc0e0
       0000000000000040  0000000000000000 WAT       0     0     8
  [16] .init_array       INIT_ARRAY       00000000004bd0e0  000bc0e0
       0000000000000010  0000000000000008  WA       0     0     8
  [17] .fini_array       FINI_ARRAY       00000000004bd0f0  000bc0f0
       0000000000000010  0000000000000008  WA       0     0     8
  [18] .data.rel.ro      PROGBITS         00000000004bd100  000bc100
       0000000000002df4  0000000000000000  WA       0     0     32
  [19] .got              PROGBITS         00000000004bfef8  000beef8
       00000000000000f0  0000000000000000  WA       0     0     8
  [20] .got.plt          PROGBITS         00000000004c0000  000bf000
       00000000000000d8  0000000000000008  WA       0     0     8
  [21] .data             PROGBITS         00000000004c00e0  000bf0e0
       0000000000001a50  0000000000000000  WA       0     0     32
  [22] __libc_subfreeres PROGBITS         00000000004c1b30  000c0b30
       0000000000000048  0000000000000000  WA       0     0     8
  [23] __libc_IO_vtables PROGBITS         00000000004c1b80  000c0b80
       00000000000006a8  0000000000000000  WA       0     0     32
  [24] __libc_atexit     PROGBITS         00000000004c2228  000c1228
       0000000000000008  0000000000000000  WA       0     0     8
  [25] .bss              NOBITS           00000000004c2240  000c1230
       0000000000001718  0000000000000000  WA       0     0     32
  [26] __libc_freeres_pt NOBITS           00000000004c3958  000c1230
       0000000000000028  0000000000000000  WA       0     0     8
  [27] .comment          PROGBITS         0000000000000000  000c1230
       000000000000002b  0000000000000001  MS       0     0     1
  [28] .note.stapsdt     NOTE             0000000000000000  000c125c
       00000000000013e8  0000000000000000           0     0     4
  [29] .symtab           SYMTAB           0000000000000000  000c2648
       000000000000afb0  0000000000000018          30   735     8
  [30] .strtab           STRTAB           0000000000000000  000cd5f8
       0000000000006ec5  0000000000000000           0     0     1
  [31] .shstrtab         STRTAB           0000000000000000  000d44bd
       0000000000000157  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

2. 装载&运行视图 Program Header

Segment 表, 一个段就是一个VMA, 只用关注LOAD 段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
xm@xm:~/xx/complier$ readelf -l SectionMapping.elf 

Elf file type is EXEC (Executable file)
Entry point 0x401b90
There are 10 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000518 0x0000000000000518  R      0x1000
  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000
                 0x000000000009399d 0x000000000009399d  R E    0x1000
  LOAD           0x0000000000095000 0x0000000000495000 0x0000000000495000
                 0x00000000000266dd 0x00000000000266dd  R      0x1000
  LOAD           0x00000000000bc0c0 0x00000000004bd0c0 0x00000000004bd0c0
                 0x0000000000005170 0x00000000000068c0  RW     0x1000
  NOTE           0x0000000000000270 0x0000000000400270 0x0000000000400270
                 0x0000000000000020 0x0000000000000020  R      0x8
  NOTE           0x0000000000000290 0x0000000000400290 0x0000000000400290
                 0x0000000000000044 0x0000000000000044  R      0x4
  TLS            0x00000000000bc0c0 0x00000000004bd0c0 0x00000000004bd0c0
                 0x0000000000000020 0x0000000000000060  R      0x8
  GNU_PROPERTY   0x0000000000000270 0x0000000000400270 0x0000000000400270
                 0x0000000000000020 0x0000000000000020  R      0x8
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x00000000000bc0c0 0x00000000004bd0c0 0x00000000004bd0c0
                 0x0000000000002f40 0x0000000000002f40  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.property .note.gnu.build-id .note.ABI-tag .rela.plt 
   01     .init .plt .text __libc_freeres_fn .fini 
   02     .rodata .stapsdt.base .eh_frame .gcc_except_table 
   03     .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data __libc_subfreeres __libc_IO_vtables __libc_atexit .bss __libc_freeres_ptrs 
   04     .note.gnu.property 
   05     .note.gnu.build-id .note.ABI-tag 
   06     .tdata .tbss 
   07     .note.gnu.property 
   08     
   09     .tdata .init_array .fini_array .data.rel.ro .got 

总的来说, SectionSegment 就是从 链接执行 视图来看ELF文件。

2.2 聊聊堆 和 栈

程序的内存映射区域,以及 ELF 文件中段和 VMA 的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xm@xm:~/xx/complier$ ./SectionMapping.elf &
[1] 35142
xm@xm:~/xx/complier$ cat /proc/35142/maps
00400000-00401000 r--p 00000000 08:05 6946957                            /home/xm/xx/complier/SectionMapping.elf
00401000-00495000 r-xp 00001000 08:05 6946957                            /home/xm/xx/complier/SectionMapping.elf
00495000-004bc000 r--p 00095000 08:05 6946957                            /home/xm/xx/complier/SectionMapping.elf
004bd000-004c0000 r--p 000bc000 08:05 6946957                            /home/xm/xx/complier/SectionMapping.elf
004c0000-004c3000 rw-p 000bf000 08:05 6946957                            /home/xm/xx/complier/SectionMapping.elf
004c3000-004c4000 rw-p 00000000 00:00 0 
050aa000-050cd000 rw-p 00000000 00:00 0                                  [heap]
7ffedc961000-7ffedc982000 rw-p 00000000 00:00 0                          [stack]
7ffedc99d000-7ffedc9a1000 r--p 00000000 00:00 0                          [vvar]
7ffedc9a1000-7ffedc9a3000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

1. ELF 文件映射

1
2
3
4
5
00400000-00401000 r--p 00000000 08:05 6946957 /home/xm/xx/complier/SectionMapping.elf
00401000-00495000 r-xp 00001000 08:05 6946957 /home/xm/xx/complier/SectionMapping.elf
00495000-004bc000 r--p 00095000 08:05 6946957 /home/xm/xx/complier/SectionMapping.elf
004bd000-004c0000 r--p 000bc000 08:05 6946957 /home/xm/xx/complier/SectionMapping.elf
004c0000-004c3000 rw-p 000bf000 08:05 6946957 /home/xm/xx/complier/SectionMapping.elf
  • 每一行代表一个 VMA(虚拟内存区域),通常映射到 ELF 文件中的一个或多个段。
  • 权限标志:
    • r–p: 只读,不可执行。
    • r-xp: 只读 + 可执行。
    • rw-p: 可读 + 可写,不可执行。

对应ELF文件段

  • .text 段(代码段):通常映射到 r-xp 区域,对应程序的指令。
  • .rodata 段(只读数据段):映射到 r–p 区域,对应只读的全局数据。
  • .data 段(已初始化数据段):映射到 rw-p 区域,对应可写的全局变量。
  • .bss 段(未初始化数据段):没有实际文件内容,仅分配内存(rw-p 部分的 0 填充区域)。

2. 堆

1
  050aa000-050cd000 rw-p 00000000 00:00 0 [heap]

3. 栈

1
  7ffedc961000-7ffedc982000 rw-p 00000000 00:00 0 [stack]

4. vvar 和 vdso

  • vvar(Virtual Variable):虚拟变量区域,提供高效访问某些内核数据。
  • vdso(Virtual Dynamically-linked Shared Object):用于加速系统调用(如 gettimeofday),通过用户空间直接访问。

3. 32 位中, 4G的虚拟空间, 为什么不可以都给进程使用 ? 要保留1G 内核空间

32 位系统通常采用 3GB 用户空间 + 1GB 内核空间 的分配策略:

  • 3GB 用户空间:为用户进程分配虚拟地址。
  • 1GB 内核空间:为内核专用,所有进程共享这一部分空间。

内核区的虚拟地址在实际访问时,会映射到物理内存中的某些区域,具体如何映射取决于内核的内存管理机制和硬件支持。

内核虚拟地址与物理内存的映射

  • 内核的虚拟地址空间(通常是 0xC0000000 到 0xFFFFFFFF,约 1GB 空间)主要映射的是物理内存的高地址部分。
  • 这个映射是固定的,由内核在启动时通过页表设置,直接将内核虚拟地址空间映射到物理内存。

32 位系统下,假设物理内存为 4GB:

  • 物理地址 0x00000000 到 0xBFFFFFFF 映射到用户空间(3GB)。
  • 物理地址 0xC0000000 到 0xFFFFFFFF 映射到内核空间(1GB)。

内核空间的虚拟地址会直接对应到 物理地址的高地址部分。

This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags