摘 要
本实验围绕一个简单的 C 程序 hello.c
,系统分析了其从源代码到可执行程序、再到运行结束的完整生命周期。通过分阶段操作与工具分析,深入探讨了预处理、编译、汇编、链接、加载、执行、进程调度、内存映射、系统调用、动态链接、异常处理等关键过程。实验过程中使用了 GCC、readelf、objdump、gdb等常用工具。
关键词:编译系统;ELF 文件;进程管理;虚拟内存;系统调用
目 录
第1章 概述................................................................................... - 4 -
1.1 Hello简介............................................................................ - 4 -
1.2 环境与工具........................................................................... - 4 -
1.3 中间结果............................................................................... - 4 -
1.4 本章小结............................................................................... - 4 -
第2章 预处理............................................................................... - 5 -
2.1 预处理的概念与作用........................................................... - 5 -
2.2在Ubuntu下预处理的命令................................................ - 5 -
2.3 Hello的预处理结果解析.................................................... - 5 -
2.4 本章小结............................................................................... - 5 -
第3章 编译................................................................................... - 6 -
3.1 编译的概念与作用............................................................... - 6 -
3.2 在Ubuntu下编译的命令.................................................... - 6 -
3.3 Hello的编译结果解析........................................................ - 6 -
3.4 本章小结............................................................................... - 6 -
第4章 汇编................................................................................... - 7 -
4.1 汇编的概念与作用............................................................... - 7 -
4.2 在Ubuntu下汇编的命令.................................................... - 7 -
4.3 可重定位目标elf格式........................................................ - 7 -
4.4 Hello.o的结果解析............................................................. - 7 -
4.5 本章小结............................................................................... - 7 -
第5章 链接................................................................................... - 8 -
5.1 链接的概念与作用............................................................... - 8 -
5.2 在Ubuntu下链接的命令.................................................... - 8 -
5.3 可执行目标文件hello的格式........................................... - 8 -
5.4 hello的虚拟地址空间......................................................... - 8 -
5.5 链接的重定位过程分析....................................................... - 8 -
5.6 hello的执行流程................................................................. - 8 -
5.7 Hello的动态链接分析........................................................ - 8 -
5.8 本章小结............................................................................... - 9 -
第6章 hello进程管理.......................................................... - 10 -
6.1 进程的概念与作用............................................................. - 10 -
6.2 简述壳Shell-bash的作用与处理流程........................... - 10 -
6.3 Hello的fork进程创建过程............................................ - 10 -
6.4 Hello的execve过程........................................................ - 10 -
6.5 Hello的进程执行.............................................................. - 10 -
6.6 hello的异常与信号处理................................................... - 10 -
6.7本章小结.............................................................................. - 10 -
第7章 hello的存储管理...................................................... - 11 -
7.1 hello的存储器地址空间................................................... - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理................... - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理............. - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换.................... - 11 -
7.5 三级Cache支持下的物理内存访问................................ - 11 -
7.6 hello进程fork时的内存映射......................................... - 11 -
7.7 hello进程execve时的内存映射..................................... - 11 -
7.8 缺页故障与缺页中断处理................................................. - 11 -
7.9动态存储分配管理.............................................................. - 11 -
7.10本章小结............................................................................ - 12 -
第8章 hello的IO管理....................................................... - 13 -
8.1 Linux的IO设备管理方法................................................. - 13 -
8.2 简述Unix IO接口及其函数.............................................. - 13 -
8.3 printf的实现分析.............................................................. - 13 -
8.4 getchar的实现分析.......................................................... - 13 -
8.5本章小结.............................................................................. - 13 -
结论............................................................................................... - 14 -
附件............................................................................................... - 15 -
参考文献....................................................................................... - 16 -
P2P是指从源程序到运行进程的全过程(Program to Process),象征着hello.c从C源程序文本经过预处理,编译,汇编,链接,最后生成可执行文件的过程。
O2O则是从0到0的循环,反映了一个hello程序在内存中的声明周期。在内存中,hello程序从shell调用execve启动hello开始,shell在虚拟内存中为程序分配空间,并将其映射到物理内存。随后,hello从入口开始加载并执行,进入main函数,执行目标代码。hello执行完毕后,shell收到子进程信号并回收hello进程,回收hello占用的资源,内核也清楚hello使用的数据结构,使内存回到刚开始的状态,形成完整的程序周期。
硬件环境:
软件环境:
本章主要介绍了hello.c的P2P,O2O流程,说明了本次实验用到的软硬件平台和中间文件
预处理(Preprocessing)是 C 语言编译过程的第一阶段,其主要任务是对源代码中以 #
开头的 预处理指令 进行处理,生成一个中间文件(通常以 .i
为后缀),为后续编译阶段准备好纯粹的C语言代码。
#define
定义的宏名称替换为相应的值或表达式,例如:#ifdef
, #ifndef
, #if
, #else
, #endif
等条件语句,根据宏是否被定义选择性地保留/丢弃代码块,用于跨平台或调试控制。//
或 /* */
)在预处理阶段被彻底删除,不会出现在 .i
文件中。\
进行行拼接,变为一行完整的指令。--
经对比,hello.i被扩展为了大约3100行的C代码。其中大部分为插入的头文件内容,并去除了全部的注释
本章介绍预处理的概念和作用,展示了Ubuntu下的预处理命令与输出结果分析
编译(Compilation)是将预处理后的 C 语言代码(.i
文件)翻译成对应的汇编语言代码(.s
文件)的过程。这个阶段由编译器(如 GCC)完成,是整个程序构建过程中的核心步骤之一。
编译的输入和输出:
编译阶段的主要作用:
得到hello.s如下:
.file "hello.c"
.text
.section .rodata
.align 8
.LC0:
.string "\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \346\211\213\346\234\272\345\217\267 \347\247\222\346\225\260\357\274\201"
.LC1:
.string "Hello %s %s %s\n"
.text
.globl main
.type main, @function
main:
.LFB5:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $5, -20(%rbp)
je .L2
leaq .LC0(%rip), %rdi
call puts@PLT
movl $1, %edi
call exit@PLT
.L2:
movl $0, -4(%rbp)
jmp .L3
.L4:
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rcx
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
movq -32(%rbp), %rax
addq $32, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, %edi
call sleep@PLT
addl $1, -4(%rbp)
.L3:
cmpl $9, -4(%rbp)
jle .L4
call getchar@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE5:
.size main, .-main
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
字符串常量:section .rodata表明为只读段,包含.LC0和.LC1两个字符串常量
局部变量:数据存在栈中,包括循环使用的i,argc,argv
mov指令实现,比如movl $0, -4(%rbp)为将循环计数器置0
在本程序中主要使用sub,add,lea,比如对于循环计数器自增采用:
addl $1, -4(%rbp)
在本程序中主要比较大小关系,使用cmp指令,如比较argc是否为5使用:
cmpl $5, -20(%rbp)
hello中只有一个数组为参数列表int *argv,读取参数列表使用基址寄存器%rbp与偏移量间接寻址:
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rcx
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
movq -32(%rbp), %rax
addq $32, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, %edi
call sleep@PLT
addl $1, -4(%rbp)
根据上文的关系逻辑比较给出的条件码与条件控制指令(xe,xne,xge,xle等)决定程序执行顺序,例如逻辑:if(argc != 5){…}有:
cmpl $5, -20(%rbp)
je .L2
.L2:
movl $0, -4(%rbp)
jmp .L3
.L3:
cmpl $9, -4(%rbp)
jle .L4
call getchar@PLT
movl $0, %eax
leave
.cfi _def_cfa 7, 8
ret
.cfi_ endproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
类型转换在汇编中处类似于atoi函数均为隐式转换,atoi为接受字符串并返回整型值
本章介绍了编译大致过程,解析了C语言编译产生汇编语言与源代码的关系
汇编(Assembly)是指将编译生成的汇编语言代码(.s 文件),转换成机器能够直接执行的机器码(即二进制指令),并生成可重定位目标文件(.o 文件,Object File)的过程。这个阶段由汇编器(Assembler)完成,是程序从文本代码向可执行形式过渡的关键步骤之一。
readelf -h hello.o:
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 1192 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 64 (字节)
节头数量: 13
字符串表索引节头: 12
readelf -S hello.o:
There are 13 section headers, starting at offset 0x4a8:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000099 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000368
00000000000000c0 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 000000d9
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000d9
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000e0
0000000000000040 0000000000000000 A 0 0 8
[ 6] .comment PROGBITS 0000000000000000 00000120
000000000000002a 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000014a
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000150
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000428
0000000000000018 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000188
0000000000000198 0000000000000018 11 9 8
[11] .strtab STRTAB 0000000000000000 00000320
0000000000000048 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000440
0000000000000061 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)
上述为节表头,记录了ELF文件各节位置,大小,偏移等信息
readelf -s hello.o:
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 153 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND atoi
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sleep
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND getchar
上述为符号表,存放着源文件定义和引用的函数,全局变量,静态局部变量的信息,帮助链接器正确解析符号引用
readelf -r hello.o:
重定位节 '.rela.text' at offset 0x368 contains 8 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000018 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
00000000001d 000b00000004 R_X86_64_PLT32 0000000000000000 puts - 4
000000000027 000c00000004 R_X86_64_PLT32 0000000000000000 exit - 4
00000000005b 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 2c
000000000065 000d00000004 R_X86_64_PLT32 0000000000000000 printf - 4
000000000078 000e00000004 R_X86_64_PLT32 0000000000000000 atoi - 4
00000000007f 000f00000004 R_X86_64_PLT32 0000000000000000 sleep - 4
00000000008e 001000000004 R_X86_64_PLT32 0000000000000000 getchar - 4
重定位节 '.rela.eh_frame' at offset 0x428 contains 1 entry:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
上述为重定位节.rela.text&.rela.eh_frame,记录了可重定位文件中所有重定位条目的信息,包括重定位类型偏移量等信息。这些信息用于在链接过程中计算出变量符号正确的地址,包括相对与直接寻址。
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o :
0000000000000000
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 20 sub $0x20,%rsp
8: 89 7d ec mov %edi,-0x14(%rbp)
b: 48 89 75 e0 mov %rsi,-0x20(%rbp)
f: 83 7d ec 05 cmpl $0x5,-0x14(%rbp)
13: 74 16 je 2b
15: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1c
18: R_X86_64_PC32 .rodata-0x4
1c: e8 00 00 00 00 callq 21
1d: R_X86_64_PLT32 puts-0x4
21: bf 01 00 00 00 mov $0x1,%edi
26: e8 00 00 00 00 callq 2b
27: R_X86_64_PLT32 exit-0x4
2b: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
32: eb 53 jmp 87
34: 48 8b 45 e0 mov -0x20(%rbp),%rax
38: 48 83 c0 18 add $0x18,%rax
3c: 48 8b 08 mov (%rax),%rcx
3f: 48 8b 45 e0 mov -0x20(%rbp),%rax
43: 48 83 c0 10 add $0x10,%rax
47: 48 8b 10 mov (%rax),%rdx
4a: 48 8b 45 e0 mov -0x20(%rbp),%rax
4e: 48 83 c0 08 add $0x8,%rax
52: 48 8b 00 mov (%rax),%rax
55: 48 89 c6 mov %rax,%rsi
58: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 5f
5b: R_X86_64_PC32 .rodata+0x2c
5f: b8 00 00 00 00 mov $0x0,%eax
64: e8 00 00 00 00 callq 69
65: R_X86_64_PLT32 printf-0x4
69: 48 8b 45 e0 mov -0x20(%rbp),%rax
6d: 48 83 c0 20 add $0x20,%rax
71: 48 8b 00 mov (%rax),%rax
74: 48 89 c7 mov %rax,%rdi
77: e8 00 00 00 00 callq 7c
78: R_X86_64_PLT32 atoi-0x4
7c: 89 c7 mov %eax,%edi
7e: e8 00 00 00 00 callq 83
7f: R_X86_64_PLT32 sleep-0x4
83: 83 45 fc 01 addl $0x1,-0x4(%rbp)
87: 83 7d fc 09 cmpl $0x9,-0x4(%rbp)
8b: 7e a7 jle 34
8d: e8 00 00 00 00 callq 92
8e: R_X86_64_PLT32 getchar-0x4
92: b8 00 00 00 00 mov $0x0,%eax
97: c9 leaveq
98: c3 retq
相较于gcc产生的.s文件,每条指令有一个十六进制的表示形式,即机器语言编码。且反汇编中所有直接数与间接数均被改为hex格式,且跳转与函数调用由标签改为了函数入口加偏移量的形式。
本节介绍了汇编的概念与作用,具体分析了hello.o的elf结构和反汇编的改变
链接(Linking)是将多个目标文件(.o 文件)以及相关库文件(如标准 C 库 libc.a 或 libc.so)合并成一个完整可执行文件(如 hello)的过程。这个过程通常由链接器(Linker)负责完成。
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x400550
程序头起点: 64 (bytes into file)
Start of section headers: 5936 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 8
节头大小: 64 (字节)
节头数量: 25
字符串表索引节头: 24
There are 25 section headers, starting at offset 0x1730:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400200 00000200
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c
0000000000000020 0000000000000000 A 0 0 4
[ 3] .hash HASH 0000000000400240 00000240
0000000000000038 0000000000000004 A 5 0 8
[ 4] .gnu.hash GNU_HASH 0000000000400278 00000278
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000400298 00000298
00000000000000d8 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400370 00000370
000000000000005c 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 00000000004003cc 000003cc
0000000000000012 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000004003e0 000003e0
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400400 00000400
0000000000000030 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400430 00000430
0000000000000090 0000000000000018 AI 5 19 8
[11] .init PROGBITS 00000000004004c0 000004c0
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004004e0 000004e0
0000000000000070 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400550 00000550
0000000000000142 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000400694 00000694
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 00000000004006a0 000006a0
0000000000000048 0000000000000000 A 0 0 8
[16] .eh_frame PROGBITS 00000000004006e8 000006e8
00000000000000fc 0000000000000000 A 0 0 8
[17] .dynamic DYNAMIC 0000000000600e50 00000e50
00000000000001a0 0000000000000010 WA 6 0 8
[18] .got PROGBITS 0000000000600ff0 00000ff0
0000000000000010 0000000000000008 WA 0 0 8
[19] .got.plt PROGBITS 0000000000601000 00001000
0000000000000048 0000000000000008 WA 0 0 8
[20] .data PROGBITS 0000000000601048 00001048
0000000000000004 0000000000000000 WA 0 0 1
[21] .comment PROGBITS 0000000000000000 0000104c
0000000000000029 0000000000000001 MS 0 0 1
[22] .symtab SYMTAB 0000000000000000 00001078
0000000000000498 0000000000000018 23 28 8
[23] .strtab STRTAB 0000000000000000 00001510
0000000000000158 0000000000000000 0 0 1
[24] .shstrtab STRTAB 0000000000000000 00001668
00000000000000c5 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)
重定位节 '.rela.dyn' at offset 0x400 contains 2 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
000000600ff0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000600ff8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
重定位节 '.rela.plt' at offset 0x430 contains 6 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000601028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
000000601030 000600000007 R_X86_64_JUMP_SLO 0000000000000000 atoi@GLIBC_2.2.5 + 0
000000601038 000700000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
000000601040 000800000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0
Symbol table '.dynsym' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@GLIBC_2.2.5 (2)
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoi@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@GLIBC_2.2.5 (2)
符号表:
Symbol table '.symtab' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400200 0 SECTION LOCAL DEFAULT 1
2: 000000000040021c 0 SECTION LOCAL DEFAULT 2
3: 0000000000400240 0 SECTION LOCAL DEFAULT 3
4: 0000000000400278 0 SECTION LOCAL DEFAULT 4
5: 0000000000400298 0 SECTION LOCAL DEFAULT 5
6: 0000000000400370 0 SECTION LOCAL DEFAULT 6
7: 00000000004003cc 0 SECTION LOCAL DEFAULT 7
8: 00000000004003e0 0 SECTION LOCAL DEFAULT 8
9: 0000000000400400 0 SECTION LOCAL DEFAULT 9
10: 0000000000400430 0 SECTION LOCAL DEFAULT 10
11: 00000000004004c0 0 SECTION LOCAL DEFAULT 11
12: 00000000004004e0 0 SECTION LOCAL DEFAULT 12
13: 0000000000400550 0 SECTION LOCAL DEFAULT 13
14: 0000000000400694 0 SECTION LOCAL DEFAULT 14
15: 00000000004006a0 0 SECTION LOCAL DEFAULT 15
16: 00000000004006e8 0 SECTION LOCAL DEFAULT 16
17: 0000000000600e50 0 SECTION LOCAL DEFAULT 17
18: 0000000000600ff0 0 SECTION LOCAL DEFAULT 18
19: 0000000000601000 0 SECTION LOCAL DEFAULT 19
20: 0000000000601048 0 SECTION LOCAL DEFAULT 20
21: 0000000000000000 0 SECTION LOCAL DEFAULT 21
22: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
23: 0000000000000000 0 FILE LOCAL DEFAULT ABS
24: 0000000000600e50 0 NOTYPE LOCAL DEFAULT 17 __init_array_end
25: 0000000000600e50 0 OBJECT LOCAL DEFAULT 17 _DYNAMIC
26: 0000000000600e50 0 NOTYPE LOCAL DEFAULT 17 __init_array_start
27: 0000000000601000 0 OBJECT LOCAL DEFAULT 19 _GLOBAL_OFFSET_TABLE_
28: 0000000000400690 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
29: 0000000000601048 0 NOTYPE WEAK DEFAULT 20 data_start
30: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
31: 000000000060104c 0 NOTYPE GLOBAL DEFAULT 20 _edata
32: 0000000000400694 0 FUNC GLOBAL DEFAULT 14 _fini
33: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
34: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
35: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 20 __data_start
36: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@@GLIBC_2.2.5
37: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
38: 00000000004006a0 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
39: 0000000000400620 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
40: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 20 _end
41: 0000000000400580 2 FUNC GLOBAL HIDDEN 13 _dl_relocate_static_pie
42: 0000000000400550 43 FUNC GLOBAL DEFAULT 13 _start
43: 000000000060104c 0 NOTYPE GLOBAL DEFAULT 20 __bss_start
44: 0000000000400582 153 FUNC GLOBAL DEFAULT 13 main
45: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoi@@GLIBC_2.2.5
46: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@@GLIBC_2.2.5
47: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@@GLIBC_2.2.5
48: 00000000004004c0 0 FUNC GLOBAL DEFAULT 11 _init
程序头:
Elf 文件类型为 EXEC (可执行文件)
Entry point 0x400550
There are 8 program headers, starting at offset 64
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001c0 0x00000000000001c0 R 0x8
INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000007e4 0x00000000000007e4 R E 0x200000
LOAD 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
0x00000000000001fc 0x00000000000001fc RW 0x200000
DYNAMIC 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
0x00000000000001a0 0x00000000000001a0 RW 0x8
NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c
0x0000000000000020 0x0000000000000020 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
0x00000000000001b0 0x00000000000001b0 R 0x1
Section to Segment mapping:
段节...
00
01 .interp
02 .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame
03 .dynamic .got .got.plt .data
04 .dynamic
05 .note.ABI-tag
06
07 .dynamic .got
-
objdump -d -r hello:
hello: 文件格式 elf64-x86-64
Disassembly of section .init:
00000000004004c0 <_init>:
4004c0: 48 83 ec 08 sub $0x8,%rsp
4004c4: 48 8b 05 2d 0b 20 00 mov 0x200b2d(%rip),%rax # 600ff8 <__gmon_start__>
4004cb: 48 85 c0 test %rax,%rax
4004ce: 74 02 je 4004d2 <_init+0x12>
4004d0: ff d0 callq *%rax
4004d2: 48 83 c4 08 add $0x8,%rsp
4004d6: c3 retq
Disassembly of section .plt:
00000000004004e0 <.plt>:
4004e0: ff 35 22 0b 20 00 pushq 0x200b22(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4004e6: ff 25 24 0b 20 00 jmpq *0x200b24(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4004ec: 0f 1f 40 00 nopl 0x0(%rax)
00000000004004f0
4004f0: ff 25 22 0b 20 00 jmpq *0x200b22(%rip) # 601018
4004f6: 68 00 00 00 00 pushq $0x0
4004fb: e9 e0 ff ff ff jmpq 4004e0 <.plt>
0000000000400500
400500: ff 25 1a 0b 20 00 jmpq *0x200b1a(%rip) # 601020
400506: 68 01 00 00 00 pushq $0x1
40050b: e9 d0 ff ff ff jmpq 4004e0 <.plt>
0000000000400510
400510: ff 25 12 0b 20 00 jmpq *0x200b12(%rip) # 601028
400516: 68 02 00 00 00 pushq $0x2
40051b: e9 c0 ff ff ff jmpq 4004e0 <.plt>
0000000000400520
400520: ff 25 0a 0b 20 00 jmpq *0x200b0a(%rip) # 601030
400526: 68 03 00 00 00 pushq $0x3
40052b: e9 b0 ff ff ff jmpq 4004e0 <.plt>
0000000000400530
400530: ff 25 02 0b 20 00 jmpq *0x200b02(%rip) # 601038
400536: 68 04 00 00 00 pushq $0x4
40053b: e9 a0 ff ff ff jmpq 4004e0 <.plt>
0000000000400540
400540: ff 25 fa 0a 20 00 jmpq *0x200afa(%rip) # 601040
400546: 68 05 00 00 00 pushq $0x5
40054b: e9 90 ff ff ff jmpq 4004e0 <.plt>
Disassembly of section .text:
0000000000400550 <_start>:
400550: 31 ed xor %ebp,%ebp
400552: 49 89 d1 mov %rdx,%r9
400555: 5e pop %rsi
400556: 48 89 e2 mov %rsp,%rdx
400559: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
40055d: 50 push %rax
40055e: 54 push %rsp
40055f: 49 c7 c0 90 06 40 00 mov $0x400690,%r8
400566: 48 c7 c1 20 06 40 00 mov $0x400620,%rcx
40056d: 48 c7 c7 82 05 40 00 mov $0x400582,%rdi
400574: ff 15 76 0a 20 00 callq *0x200a76(%rip) # 600ff0 <__libc_start_main@GLIBC_2.2.5>
40057a: f4 hlt
40057b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
0000000000400580 <_dl_relocate_static_pie>:
400580: f3 c3 repz retq
0000000000400582
400582: 55 push %rbp
400583: 48 89 e5 mov %rsp,%rbp
400586: 48 83 ec 20 sub $0x20,%rsp
40058a: 89 7d ec mov %edi,-0x14(%rbp)
40058d: 48 89 75 e0 mov %rsi,-0x20(%rbp)
400591: 83 7d ec 05 cmpl $0x5,-0x14(%rbp)
400595: 74 16 je 4005ad
400597: 48 8d 3d 0a 01 00 00 lea 0x10a(%rip),%rdi # 4006a8 <_IO_stdin_used+0x8>
40059e: e8 4d ff ff ff callq 4004f0
4005a3: bf 01 00 00 00 mov $0x1,%edi
4005a8: e8 83 ff ff ff callq 400530
4005ad: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
4005b4: eb 53 jmp 400609
4005b6: 48 8b 45 e0 mov -0x20(%rbp),%rax
4005ba: 48 83 c0 18 add $0x18,%rax
4005be: 48 8b 08 mov (%rax),%rcx
4005c1: 48 8b 45 e0 mov -0x20(%rbp),%rax
4005c5: 48 83 c0 10 add $0x10,%rax
4005c9: 48 8b 10 mov (%rax),%rdx
4005cc: 48 8b 45 e0 mov -0x20(%rbp),%rax
4005d0: 48 83 c0 08 add $0x8,%rax
4005d4: 48 8b 00 mov (%rax),%rax
4005d7: 48 89 c6 mov %rax,%rsi
4005da: 48 8d 3d f7 00 00 00 lea 0xf7(%rip),%rdi # 4006d8 <_IO_stdin_used+0x38>
4005e1: b8 00 00 00 00 mov $0x0,%eax
4005e6: e8 15 ff ff ff callq 400500
4005eb: 48 8b 45 e0 mov -0x20(%rbp),%rax
4005ef: 48 83 c0 20 add $0x20,%rax
4005f3: 48 8b 00 mov (%rax),%rax
4005f6: 48 89 c7 mov %rax,%rdi
4005f9: e8 22 ff ff ff callq 400520
4005fe: 89 c7 mov %eax,%edi
400600: e8 3b ff ff ff callq 400540
400605: 83 45 fc 01 addl $0x1,-0x4(%rbp)
400609: 83 7d fc 09 cmpl $0x9,-0x4(%rbp)
40060d: 7e a7 jle 4005b6
40060f: e8 fc fe ff ff callq 400510
400614: b8 00 00 00 00 mov $0x0,%eax
400619: c9 leaveq
40061a: c3 retq
40061b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
0000000000400620 <__libc_csu_init>:
400620: 41 57 push %r15
400622: 41 56 push %r14
400624: 49 89 d7 mov %rdx,%r15
400627: 41 55 push %r13
400629: 41 54 push %r12
40062b: 4c 8d 25 1e 08 20 00 lea 0x20081e(%rip),%r12 # 600e50 <_DYNAMIC>
400632: 55 push %rbp
400633: 48 8d 2d 16 08 20 00 lea 0x200816(%rip),%rbp # 600e50 <_DYNAMIC>
40063a: 53 push %rbx
40063b: 41 89 fd mov %edi,%r13d
40063e: 49 89 f6 mov %rsi,%r14
400641: 4c 29 e5 sub %r12,%rbp
400644: 48 83 ec 08 sub $0x8,%rsp
400648: 48 c1 fd 03 sar $0x3,%rbp
40064c: e8 6f fe ff ff callq 4004c0 <_init>
400651: 48 85 ed test %rbp,%rbp
400654: 74 20 je 400676 <__libc_csu_init+0x56>
400656: 31 db xor %ebx,%ebx
400658: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
40065f: 00
400660: 4c 89 fa mov %r15,%rdx
400663: 4c 89 f6 mov %r14,%rsi
400666: 44 89 ef mov %r13d,%edi
400669: 41 ff 14 dc callq *(%r12,%rbx,8)
40066d: 48 83 c3 01 add $0x1,%rbx
400671: 48 39 dd cmp %rbx,%rbp
400674: 75 ea jne 400660 <__libc_csu_init+0x40>
400676: 48 83 c4 08 add $0x8,%rsp
40067a: 5b pop %rbx
40067b: 5d pop %rbp
40067c: 41 5c pop %r12
40067e: 41 5d pop %r13
400680: 41 5e pop %r14
400682: 41 5f pop %r15
400684: c3 retq
400685: 90 nop
400686: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40068d: 00 00 00
0000000000400690 <__libc_csu_fini>:
400690: f3 c3 repz retq
Disassembly of section .fini:
0000000000400694 <_fini>:
400694: 48 83 ec 08 sub $0x8,%rsp
400698: 48 83 c4 08 add $0x8,%rsp
40069c: c3 retq
发现:
可以看到libc中存在_start,过程为(按调用顺序 ):
0x400550 _start,
(Global offset table) _libc_start_main@GLIBC_2.2.5,
0x400528 main,
0x400500 printf@plt,
0x400530 exit@plt,
0x400540 sleep@plt,
0x400510 _getchar,
0x400530 exit
使用 gdb
工具,对 hello
程序中的 printf
函数调用过程进行了跟踪,验证了其动态链接机制。通过如下步骤观察到:
printf@plt
实际跳转到 .got.plt
表中的地址;.got.plt
中为跳转到 plt[1]
的入口;printf
后,动态链接器解析并将实际地址填入 GOT;printf
的调用则直接跳转,不再经过动态解析。5.8 本章小结
本章介绍了连接的基本概念和功能,展示了如何链接hello.o与其他文件生成hello,并展示了其虚拟地址空间
在操作系统中,进程(Process)是资源分配和调度的基本单位。一个程序在运行时就成为一个进程,它拥有独立的地址空间、打开的文件描述符、栈、堆等资源。
在本实验中,hello
程序在执行时由 Linux 内核创建对应的进程,在运行过程中通过系统调用(如 fork()
、execve()
、sleep()
、getchar()
)与操作系统进行交互,体现了一个进程从创建到终止的完整生命周期。
Linux 下的 bash 是最常用的命令行 Shell,主要负责:
bash 调用 fork()
创建一个子进程,子进程复制父进程的上下文,但拥有独立的进程ID和地址空间。
在子进程中,bash
会调用 execve()
系统调用,加载 ELF 格式的 hello
可执行文件。此时:
hello
的代码被加载到新的地址空间;_start
开始执行;argc/argv
被传递给 main()
函数。在运行过程中,hello
程序进入用户态,由操作系统调度执行:
printf()
→ 调用 write()
系统调用,输出文字;sleep()
→ 使进程进入阻塞状态,让出 CPU;getchar()
→ 通过 read()
系统调用监听标准输入;hello
;系统在用户态和内核态之间切换,执行系统调用、信号处理等。
在运行过程中,hello
程序可能接收来自键盘或系统的各种信号,如:
按键 |
触发信号 |
说明 |
|
|
中断进程(终止) |
|
|
挂起进程(转后台) |
|
其他信号 |
如 |
|
中断 |
- |
|
陷阱 |
调用Syscall |
|
故障 |
决定是否返回源程序位置 |
|
终止 |
发生硬件错误 |
以下为不同操作:
本章概述了进程的基本概念以及shell的核心功能。分析了hello进程的创建,回收,并模拟了运行中的异常。
在现代操作系统与 CPU 架构中,一个进程在运行时涉及到多层次的地址转换机制。以 hello 程序为例,其执行过程中涉及以下四种地址:
1. 逻辑地址(Logical Address)
定义:由 C 语言程序或汇编代码中直接使用的地址(如变量的偏移、指令跳转目标),一般以段寄存器 + 偏移构成;
例子:main: 函数中的局部变量 i,其访问使用的是 [rbp - 4] 这类偏移值;
hello 中体现:
汇编代码中的地址如 .L3: 是逻辑地址标签;CPU 执行时先从逻辑地址转换为线性地址。
2. 线性地址(Linear Address)
定义:逻辑地址经过段表转换后的地址(在 x86-64 中段机制被“扁平化”,段基址通常为 0,所以逻辑地址 ≈ 线性地址);
例子:hello 被加载到 0x400000 虚拟地址处,main 的入口地址就是线性地址
hello 中体现:
使用 readelf -l hello 显示的 VirtAddr 就是程序的线性地址;
所有编译时定位的符号(如 printf@plt)都有确定的线性地址。
3. 虚拟地址(Virtual Address)
定义:从进程的视角看到的地址空间。通常情况下,线性地址 = 虚拟地址;
例子:在 GDB 中执行 info proc mappings 或查看 /proc/
hello 中体现:
程序被加载到如 0x400000(.text 段),0x601000(.data 段);
用户使用 malloc() 时返回的堆地址也是虚拟地址。
4. 物理地址(Physical Address)
定义:实际访问主存时的内存地址,仅由操作系统和 MMU(内存管理单元)知道;
例子:程序中的虚拟地址如 0x601000,可能被映射到物理内存中地址 0x1234ABCD;
hello 中体现
看不到物理地址,操作系统通过四级页表将虚拟地址转换为物理地址;
若页面不在内存,还可能触发缺页异常(Page Fault)。
Intel x86-64 使用段式管理 + 页式管理结合的方式进行地址转换:
操作系统采用分页机制实现虚拟地址 → 物理地址映射:
为了加速地址转换,CPU 使用TLB(Translation Lookaside Buffer)缓存最近访问的页表项。流程如下:
hello 的执行涉及大量 TLB 操作,特别是在进行 sleep()
或 getchar()
阻塞时会发生上下文切换,可能导致 TLB 刷新。
现代 CPU 中,访问物理地址时会通过多级 Cache(缓存)加速:
级别 |
容量 |
作用 |
L1 |
极小 |
指令/数据分离 |
L2 |
中等 |
核心专用 |
L3 |
较大 |
多核共享 |
每一级缓存都是下一级缓存的缓存,cpu从L1开始向下顺序查找
hello 中大量使用 printf(),涉及访问 .rodata 常量区,以及函数调用栈,Cache 命中率对性能影响明显。
执行 fork()
时,Linux 并不会立刻复制父进程的所有内存,而是采用写时复制(COW, Copy-on-Write)机制:
在 fork()
创建子进程后,execve()
被调用加载 hello 程序,此时:
.text
、.data
、.bss
段;libc.so
);argc/argv
;_start
,准备执行。缺页中断(Page Fault)是指访问虚拟地址时,若页表项无效或不在物理内存中,CPU 将触发中断:
SIGSEGV
信号,终止进程。hello 中的 getchar()
调用 read()
,等待用户输入,此时可能进入休眠状态,一旦用户输入,唤醒并访问输入缓冲区 → 若该页尚未加载 → 触发缺页中断 → 映射页 → 继续执行。
程序中 printf()
实际调用了 malloc()
分配内部缓冲区。C 库使用堆区管理内存,分配器采用以下机制:
brk()
或 mmap()
扩展堆;本章分析了 hello 程序在执行过程中涉及的存储管理机制,包括段式管理、页表映射、TLB、Cache 层级结构以及 execve 加载过程和 page fault 异常处理。通过本章学习,我系统理解了虚拟地址空间的布局与动态演化过程来观测和调试进程的内存状态。
通过本课程,从一个极其简单的 C 程序出发,系统性地学习并实践了程序从源代码到最终运行所经历的完整过程,包括:预处理、编译、汇编、链接、加载、执行与退出。
在此过程中,不仅掌握了 GCC 编译工具链的使用方法,还熟练应用了诸如 readelf、objdump、gdb、strace、ps、pmap 等 Linux 下的重要调试和分析工具,深入理解了 ELF 文件结构、进程地址空间、系统调用、动态链接机制、虚拟内存与页表映射、信号传递与异常处理等计算机系统底层机制。
尤其重要的是,本课程从“用户视角”转向“系统视角”,以操作系统和硬件运行机制的角度重新审视程序的执行过程。程序不再只是“运行了就完事”,而是一个动态、复杂、结构化的过程,背后蕴含着大量隐含的系统调用、数据结构和资源调度。
这份课程不仅让我巩固了《计算机系统》课程中的理论知识,也让我体会到系统思维的重要性。只有理解了底层机制,未来我在软件开发、系统设计或性能优化等方面才能更得心应手。
[1] Randal E. Bryant, David R. O'Hallaron. Computer Systems: A Programmer's Perspective (3rd Edition). Pearson, 2015.(中文版:《深入理解计算机系统》)
[2] man7.org. ELF - Executable and Linkable Format [EB/OL]. https://man7.org/linux/man-pages/man5/elf.5.html