13-应对并发 Bugs (动态程序分析:应对死锁、死局和死线)
14-操作系统上的进程 (forkexecveexit)
14-操作系统上的进程 (fork/execve/exit)
立即复制状态机
包括
所有
信息的完整拷贝
- 每一个字节的内存
- 打开的文件 (共享)
- ……
- 复制失败返回 -1
- errno 会返回错误原因 (man fork)
如何区分两个状态机?
- 新创建进程返回 0
- 执行 fork 的进程返回子进程的进程号
系统调用的返回值放在rax中
1 | f(){ |
printf缓冲区在每一个进程的内存里,fork会一起复制
遇到了粘贴时乱码问题
execve把当前的进程重置成一个可执行文件描述状态机的初始状态
第三个参数是环境变量
fork默认直接把环境变量会继承
strace的使用
./demo当前目录下的程序
15-(入侵) 进程的地址空间
15-(入侵) 进程的地址空间
状态机里有什么
registers
memory
gdb就可以暂停查看
1 | fa1e0ff3 |
1 | f3 0f 1e fa |
在内存中显示以小端为首
地址空间是否可读写
在proc/pid/maps里都给出了,连续的一段一段的,每一段都给出了权限
1 | 564af047d000-564af0494000 r--p 00000000 08:20 31005 /usr/bin/zsh |
更可读
1 | pmap [pid] |
创建不同的数组,分配的内存是不一样的
在栈上,在堆上,
不进入内核的系统调用gettimeofday(2)
从操作系统读数据
只有syscall指令可以改变地址空间(增删改)
看看man 5 proc手册
mmap可以分配内存,控制权限
python执行时遇到ModuleNotFoundError: No module named 'hexdump'0
原因是sudo执行时的环境变量不一样
wsl显示全为0
gdb调试,可以把要调试的程序的内存空间放进自己的内存空间
变速齿轮
hook,劫持相关代码
17-C 标准库设计与实现 (_start; offsetof; printf; environ; mallocfree)
16-系统调用和 UNIX Shell (pipe; xv6 shell)
16-系统调用和 UNIX Shell (pipe; xv6 shell)
虚拟化,syscall
操作系统对象:文件和设备,
指针只能指向程序的内存空间
指向操作系统对象的指针(就是文件描述符),在linux中everything is a file
访问对象用指针open, close,read/write(解引用),lseek,dup等
复习指针
windows中文件描述符时handle,句柄
管道,IPC
写口,读口
管道是进程之间的同步机制
通信不仅可以用来传送数据,还可以用来同步
匿名管道
1 | pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe. |
fork复制时,管道也被复制了
指针也完成了浅拷贝
shell连接I/O设备和人
<(command)把命令变成文件
ctrl + z切换后台
jobs查看后台,fg %1切换后台到前台
shell可以调用syscall
20-动态链接和加载
20-动态链接和加载
libc.o静态链接
容易浪费空间
libc.so动态链接
生成位置无关代码,使用中间的table存放函数地址
借助编译器完成
用多个线程链接库,验证只有一个副本
是链接的同一份
地址空间是怎么分配的(虚拟内存)
动态链接(查表)

编译时,函数调用 = 查表(把函数调用替换成查表)
编译时,动态链接库调用 = 查表
1 | call *TABLE[printf@symtab] |
链接时,收集所有符号,“生成” 符号信息和相关代码:
1 |
|

1 | LOAD("libc.dl") |
gdb过程dlbox main.s
加载符号表,递归调用dlopen,调用libc.dl,导出符号,
putchar,exit填到全局的符号表,
解析第二个符号,libhello.dl
….
动态解析hello,hello不在main.dl里,是?
调用dlsym检查符号表,找到hello把地址填入符号表
执行DSYM(exit)
1 |
找到空位把符号填入符号表
前面的存放地址和函数名的表项,就是 GOT (Global Offset Table)
因为call 的偏移量是64位,跳不到远处
所以使用plt,作为中转,先跳到plt中,plt中存放GOT对应函数的地址
再次跳转到对应函数

数据的链接,plt怎么解决数据链接的问题
get_x会查表
get_y直接得到地址(hidden)
编译器默认extern变量来自另外一个共享库单元(保守)
gpt对objdump反汇编的分析
总结
- 变量
x:- 默认可见性(
visibility("default"))。 - 使用
mov指令,通过符号表获取地址。 - 可被其他模块或共享库访问。
- 默认可见性(
- 变量
y:- 隐藏可见性(
visibility("hidden"))。 - 使用
lea指令,直接计算地址,无需符号表查找。 - 仅在当前模块内部可见,无法被外部访问。
- 隐藏可见性(
- 性能影响:
- 隐藏符号(
y)链接效率更高,因为不需要符号表查找。 - 默认可见性符号(
x)灵活性更强,但动态链接时可能会引入额外开销。
- 隐藏符号(
21-系统调用、中断和上下文切换
22-进程的实现 (虚拟地址空间;UNIX 和 xv6)
22-进程的实现 (虚拟地址空间;UNIX 和 xv6)
1024叉树
32bit,10bit+10bit+12bit,12bit放访问信息
一个4KB页面,一个放一个4B的指针,总共2^1024
绝大部分节点都是空的,下一层也类似(局部性原理)
64bit,4KB的页面,一项8B,总共有512项,不是很整齐
映射不需要先载入,只需要一个数据结构记录访问权限
到用指针访问时,发生缺页中断,如果合法访问,则改变f,映射一页
指针单点访问地址空间,一段时间后就能记录哪里能访问,哪里不能访问,把不能访问的释放掉,放到磁盘上
swap机制,闲置页面放到磁盘上,释放内存
可以使所有进程使用的内存总和比物理内存大
fork系统调用
fork() 在进行状态机复制时,虽然理论上需要做出完整的状态复制,但本着 “复制的数据可能立即被浪费掉” 的观察,操作系统希望只在 “不得不复制” 时才复制。
现代操作系统会维护页面的共享情况,并在 fork 后将父子进程的地址空间都标记为 read-only,而当双方中的任何一个进程写入发生 page fault 时,才复制一页。对于连续的 fork(),则会产生多个进程共享页面的情况。
服务器的容错,可以用fork以很小的代价做快照
19-可执行文件:静态链接和加载
19-可执行文件:静态链接和加载
execve(加载)把当前的进程重置成指定可执行文件的初始状态
可执行文件:一个状态机初始状态的数据结构
里面规定了加载该可执行文件后地址空间里该有什么数据(寄存器,代码段等)
状态:内存和寄存器
elf为了性能丧失了阅读友好性
magic number是什么
Magic number一般是指硬写到代码里的整数常量,数值是编程者自己指定的,其他人不知道数值有什么具体意义,表示不明觉厉,就称作magic number。
a.out
设计一个可读的可执行文件,需要什么(代码,符号,重定位)
FLE 加载器:只做一件事
- 将一段字节序列复制到地址空间中
- 赋予可读、可写、可执行权限
- 然后跳转到 _start 执行
ELF 并没有多做多少
- 将多段字节序列复制到地址空间中
- 分别赋予可读/可写/可执行权限
- 然后跳转到指定的 entry (默认为 _start) 执行
#!/bin/bash
1 | #!A B C |
argc[0] = A
argv[1] = B C
argv[2]该 程序的名称