第12讲 进程的地址空间
堆区,栈只是对平坦的内存地址空间进行的抽象。对于汇编来说,能看见的只有地址,寄存器。
比如定义一个指针,可以指向任何地方,但是你不能访问。就算你可以访问,也不一定能写它。
pmap
report memory of a process
是通过访问procfs(/proc/)实现的
(gdb)info inferiors
查看进程号
1
2
3
4
5
6
7
8
9
10
11
|
(gdb) info inferiors
Num Description Executable
* 1 process 25732 /home/charon/jyy/12/a.out
(gdb) !pmap 25732
25732: /home/charon/jyy/12/a.out
0000000000400000 4K r---- a.out
0000000000401000 4K r-x-- a.out
00007ffff7ffa000 16K r---- [ anon ]
00007ffff7ffe000 4K r-x-- [ anon ]
00007ffffffde000 132K rw--- [ stack ]
total 160K
|
操作系统在用execve创建进程时,就会把参数,环境变量放到stack里
pmap究竟执行了什么系统调用,可以通过strace pmap pid
来探究一下
openat打开了一个文件 /proc/25732/maps
1
2
3
4
5
6
|
(gdb) !cat /proc/25732/maps
00400000-00401000 r--p 00000000 08:10 1591 /home/charon/jyy/12/a.out
00401000-00402000 r-xp 00001000 08:10 1591 /home/charon/jyy/12/a.out
7ffff7ffa000-7ffff7ffe000 r--p 00000000 00:00 0 [vvar]
7ffff7ffe000-7ffff7fff000 r-xp 00000000 00:00 0 [vdso]
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
|
推荐宝藏手册man 5 proc
[vdso] The virtual dynamically linked shared object. See vdso(7).
我们可以对照 readelf中的Program Header 来验证
1
2
3
4
5
6
7
|
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000b0 0x00000000000000b0 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x000000000000004a 0x000000000000004a R E 0x1000
|
对于动态链接来说,地址空间又复杂了一些。
这时.so动态链接库也被放入内存中,但是出现了啥都没有的地址空间映像,这是什么?
是不是bss?
这时jyy做了一个实验.
1
2
3
|
char big[1<<30]={0};
int main(){
}
|
看看这么大的数组究竟在哪?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
(gdb) info inferiors
Num Description Executable
* 1 process 25796 /home/charon/jyy/12/a.out
(gdb) !pmap 25796
25796: /home/charon/jyy/12/a.out
0000555555554000 4K r---- a.out
0000555555555000 4K r-x-- a.out
0000555555556000 4K r---- a.out
0000555555557000 8K rw--- a.out
0000555555559000 1048576K rw--- [ anon ]
00007ffff7fca000 16K r---- [ anon ]
00007ffff7fce000 4K r-x-- [ anon ]
00007ffff7fcf000 4K r---- ld-2.31.so
00007ffff7fd0000 140K r-x-- ld-2.31.so
00007ffff7ff3000 32K r---- ld-2.31.so
00007ffff7ffc000 8K rw--- ld-2.31.so
00007ffff7ffe000 4K rw--- [ anon ]
00007ffffffde000 132K rw--- [ stack ]
total 1048936K
|
确实在堆区heap
启动之后甚至吃了我1g的内存
1
2
3
|
$htop
PID VIRT Command
25805 1024M /home/charon/jyy/12/a.out
|
vdso
既想执行系统调用,又不想通过syscall进入内核执行
time 跳转到0x7ffff7fce730
在内存中的vdso段,
再把rip减掉-0x469,进入vvar。
而且这个内存gdb无法访问。
查看rax的值,发现就是unix时间秒数,说明这个0x7fffff7fca0a0中存的是操作系统的秒数
1
|
┌─────────────────────────────────────────────────────────────────────────────────────────────┐│ >0x7ffff7fce730 <time> test %rdi,%rdi ││ 0x7ffff7fce733 <time+3> mov -0x469a(%rip),%rax # 0x7ffff7fca0a0 ││ 0x7ffff7fce73a <time+10> je 0x7ffff7fce73f <time+15> ││ 0x7ffff7fce73c <time+12> mov %rax,(%rdi)
|
vvar是什么?由操作系统维护的一个秒数。
与其费劲心力一定要通过陷入内核的方式来读取这些数据,不如在内核与用户态之间建立一段共享内存区域,由内核定期“推送”最新的值到该共享内存区域,然后由用户态程序在调用这些glibc库函数的时候,库函数并不真正执行系统调用。
那么能不能让其他系统调用也 trap 进入内核?
疯狂的事情也许真的是能实现的 (这算是魔法吗?)
FlexSC: Flexible system call scheduling with exception-less system calls (OSDI'10).
https://www.usenix.org/conference/osdi10/flexsc-flexible-system-call-scheduling-exception-less-system-calls
使用共享内存和内核通信!
map是动态变化的。
动态链接库在开始阶段并没有加载到地址空间。
mmap
map or unmap files or devices into memory
在状态上增改一段可访问的内存。
1
2
3
4
5
6
7
8
|
// 映射
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
// 修改映射权限
int mprotect(void *addr, size_t length, int prot);
|
把文件映射到进程的地址空间
这个mmap特别有用,再此基础上实现加载器就非常容易。
elf文件会告诉你把什么段加载到哪里Program Headers
mmap会把内存标记为已分配,等到缺页的时候再说。
所以mmap申请大量内存空间几乎是瞬间完成的。
地址空间的隔离
每个ptr只能访问本进程的内存
剩下的40分钟都是jyy介绍上一个时代的游戏
红警、金山游侠、
如何制造外挂?
动态修改一个运行中的程序(热更新)