进程的地址空间
内容回顾
在操作系统的视角,进程就是运行的状态机,可以通过系统调用来复制、重置和销毁状态机。状态机有自己的地址空间,但是进程的地址空间从哪里来呢?
进程的初始状态
进程 execve
后的初始状态需要满足 ABI 中规定的 initial state (System V ABI),里面只规定了部分寄存器和栈(argv
和 envp
中的字符串保存在栈中)。
进程的地址空间管理
进程的地址空间: 配合 gdb 调试器,我们甚至可以 "随时查看程序的地址空间"。这一切代码都是人工智能生成的!因为 AI 知晓几乎所有的细节,它是帮助我们理解复杂细节的利器。
Memory Map 一系列系统调用可以改变进程的地址空间。
C
memory_mapping.h
1// 映射2void *mmap(void *addr, size_t length, int prot, int flags,3 int fd, off_t offset);4int munmap(void *addr, size_t length);56// 修改映射权限7int mprotect(void *addr, size_t length, int prot);
例如,调用 mmap 分配 1GiB 内存前后的进程地址空间如下图:
mmap前 - 初始状态
0xFFFFFFFF
FF601000
FF601000
[vsyscall]
系统调用页面
系统调用页面
0x7FFFFFFF
FFF00000
FFF00000
[stack]
用户栈
~140KB
用户栈
~140KB
0x7FFFF7FF
D000
D000
大量空白区域
可用于mmap分配
[vdso] 虚拟DSO
[vvar] 虚拟变量
可用于mmap分配
[vdso] 虚拟DSO
[vvar] 虚拟变量
0x4F2000
[heap]
堆区域
~152KB
堆区域
~152KB
0x4CB000
.data/.bss
全局变量
~12KB
全局变量
~12KB
0x4C8000
.rodata
只读数据
~164KB
只读数据
~164KB
0x49A000
.text
代码段
~612KB
代码段
~612KB
0x401000
ELF头
程序头表
4KB
程序头表
4KB
0x400000
程序入口
mmap后 - 分配1GiB
0xFFFFFFFF
FF601000
FF601000
[vsyscall]
系统调用页面
系统调用页面
0x7FFFFFFF
FFF00000
FFF00000
[stack]
用户栈
~140KB
用户栈
~140KB
0x7F000000
mmap区域
1 GiB
物理内存未使用
1 GiB
物理内存未使用
0x6F000000
剩余空白区域
[vdso] 虚拟DSO
[vvar] 虚拟变量
[vdso] 虚拟DSO
[vvar] 虚拟变量
0x4F2000
[heap]
堆区域
~152KB
堆区域
~152KB
0x4CB000
.data/.bss
全局变量
~12KB
全局变量
~12KB
0x4C8000
.rodata
只读数据
~164KB
只读数据
~164KB
0x49A000
.text
代码段
~612KB
代码段
~612KB
0x401000
ELF头
程序头表
4KB
程序头表
4KB
0x400000
程序入口
栈区域
mmap区域
堆区域
数据段
代码段
空白区域
mmap 前栈和堆之间有大量空白区域可供分配,mmap 在空白区域中分配了 1GiB 的虚拟地址空间。mmap 只是虚拟分配,实际物理内存在写入时才分配,当进程访问 mmap 区域时触发缺页异常,然后分配物理页面。
使用 mmap 系统调用可以申请大量内存,当 malloc 申请较大内存时,会调用一次 mmap 实现。或者将一个大文件直接搬到进程的地址空间中。
Python
mmap_example.py
1with open('/dev/sda', 'rb') as fp:2 mm = mmap.mmap(fp.fileno(),3 prot=mmap.PROT_READ, length=128 << 30)4 hexdump.hexdump(mm[:512])
入侵进程的地址空间
进程对应的状态机在”无情执行指令机器”上执行,状态机是一个封闭的世界,但是有些系统调用允许一个进程对其他进程的地址空间有访问权,可以任意改变另一个程序的行为。
gdb 可以任意观测和修改程序的状态。
但是并不是任意一个进程都可以访问和修改另一个进程的地址空间,如果进程 A 能访问和修改进程 B 的地址空间,需满足:
- A 是
root
用户 - A 和 B 是同用户且
ptrace_scope = 0
- A 和 B 是父子进程且
ptrace_scope <= 1
- A 具有
CAP_SYS_PTRACE
能力
其中,ptrace_scope
是 Linux 内核中 Yama
安全模块提供的一个安全特性,用来限制 ptrace
系统调用的使用,防止进程被恶意调试和内存访问。
金山游侠:通过操作系统赋予我们实现调试器的机制,我们可以窥探甚至修改任何进程的代码和数据。这个能力使我们可以绕过游戏为我们设置的 "人为障碍",取得更多的金钱、经验,或是锁定生命值等。
总结
Take-away Messages 操作系统通过虚拟内存为每个进程提供独立的地址空间,实现了进程间的隔离和保护。操作系统通过 mmap/munmap 实现地址空间的管理,并且还提供特定机制 (如 procfs、ptrace、process_vm_writev、共享内存等) 访问彼此的地址空间。