CRIU介绍
CRIU(Checkpoint/Restore In Userspace)运行在linux操作系统上的一个软件工具,其功能是在用户空间实现Checkpoint/Restore功能。使用这个工具,你可以冻结一个正在运行的程序,并且checkpoint它到一系列的文件,然后你就可以使用这些文件在任何主机重新恢复这个程序到被冻结的那个点。
Checkpoint/Restore介绍
checkpoint程序严重依赖**/proc**文件系统,它从/proc收集的信息包括:
- 文件描述信息(通过**/proc/pid/fdinfo**)
- 管道参数信息
- 内存表(通过**/proc/pid/map_files/**)
监控进程做的checkpoint由如下步骤组成:
1、收集并且冻结被监控程序的进程树
监控程序使用被监控程序的主进程pid遍历**/proc/%pid/task/路径收集线程tid,并且递归遍历/proc/tid/children**,然后通过ptrace函数的PTRACE_SEIZE命令冻结被监控程序。
2、收集被监控程序的资源并保存
在这个阶段,CRIU读被监控程序的所有可获取的资源信息并写到文件里。这些资源的获取通过如下步骤:
- 通过 /proc/pid/map_files 连接读取所有maps文件。
- 通过 /proc/$pid/fd获取文件描述号。
- 通过ptrace接口和解析**/proc/$pid/stat**块完成一个进程的核心参数(寄存器和friends)的获取。
然后CRIU通过ptrace接口注入parasite code。这个过程由两步完成:首先注入mmap系统调用到任务被冻结那一刻的CS:IP位置,然后ptrace允许我们运行这个被注入的系统调用,这样我们就在被监控进程里申请到了足够的内存用于parasite code块。接下来把parasite code拷贝到这个新申请到的内存地址,并把CS:IP指向到parasite code的位置。
从注入上下文,CRIU可以获取更多的信息,比如认证、内存容量等。
3、Cleanup
所有信息(比如内存页,它只能从被监控程序内部地址空间写出)被获取后,我们使用ptrace系列参数去掉被注入代码并恢复被监控程序的地址空间。
Parasite code
Parasite code是为了运行在其他进程地址空间的一个二进制代码块。它唯一的目的就是可以在被监控程序的内部地址空间执行CRIU服务例程。
parasite的使用
All architecture independent code calling for parasite service routines is sitting in parasite-syscall.c file. When we need to run parasite code inside some dumpee task we:
- 利用**ptrace(PTRACE_SEIZE,…)**把dumpee task置于seized状态,由此dumpee停止。
- 利用ptrace实现往dumpee进程的地址空间 注入并执行mmap系统调用,因此我们要分配一块共享内存用于CRIU和dumpee之间的注入栈和参数交换。
- 从 /proc/PID是dumpee的进程标识符。
上述过程都在函数parasite_infect_seized()完成,代码注入后,CRIU就能调用parasite服务例程。
parasite有两种模式:Trap模式、Daemon模式。在trap模式,parasite简单地执行一个命令并且产生cpu trap指令(由CRIU拦截)。在daemon模式(也叫implies),parasite打开一个socket并监听命令,一旦收到命令,就会及时处理,daemon并通过socket包把结果返回。然后daemon继续监听命令。所有当前已知的命令并定成PARASITE_CMD_…**枚举类型(在parasite.h文件内)
parasite内部结构
内部parasite大概是如下块:
bootstrap |
---|
daemon |
arguments |
sigframe |
stack |
bootstrap代码由汇编语言编写,和具体的架构有关(x86、arm、arm64…)
parasite boostrap位于parasite-head.S文件,实现daemon模块的入口点。
parasite daemon代码位于pie/parasite.c文件。它的入口点是parasite_daemon().daemon启动以后,socket将会创立连接,并等待调用者的命令。
由于整个parasite 内存是一块共享内存区,所以criu和dumpee通过arguments区域进行读/写操作。
Compel
compel是CRIU的一个模块,可以实现在其他进程上下文里执行任意代码的功能。被执行的代码叫parasite code . parasite code一旦编译和打包成带有compel标识,就可以被运行在其他进程的上下文。注意code被运行在没有glibc的环境下,所以它不能调用类似stdio/stdlib等库。
Building PIE code blobs for criu
Parasite code在被监控进程里执行,因此它需要被PIE编译并有自己的栈。restorer code也是一样。
- Parasite code有自己的引导程序,位于一个纯汇编文件里(parasite_head.S)
- Restore code 的引导程序在restorer,c里面实现。
对这两种情况生成的头文件由函数偏移和二进制数据的数组组成,如下:
#define parasite_blob_offset____export_parasite_args 0x000000000000002c
#define parasite_blob_offset____export_parasite_cmd 0x0000000000000028
#define parasite_blob_offset____export_parasite_head_start 0x0000000000000000
#define parasite_blob_offset____export_parasite_stack 0x0000000000006034
static char parasite_blob[] = {
0x48, 0x8d, 0x25, 0x2d, 0x60, 0x00, 0x00, 0x48,
0x83, 0xec, 0x10, 0x48, 0x83, 0xe4, 0xf0, 0x6a,
0x00, 0x48, 0x89, 0xe5, 0x8b, 0x3d, 0x0e, 0x00,
0x00, 0x00, 0x48, 0x8d, 0x35, 0x0b, 0x00, 0x00,
...
};
这些头部信息被包含在criu的编译文件里,并在checkpoint/restore时使用到。
这些文件的产生由如下几步完成:
1、所有目标文件被链接到“built-in.o”
2、使用链接脚本移动代码和数据到具体的layout,比如sections段
3、使用objcopy移动sections到一个二进制文件
4、使用hexdump产生c风格的数组并放到-blob.h头文件里。
编译过程大概如下:
LINK pie/parasite.built-in.o
GEN pie/parasite.built-in.bin.o
GEN pie/parasite.built-in.bin
GEN pie/parasite-blob.h
名词解释
dumper: 监控进程
dumpee: 被监控进程
Bootstrap code : 引导程序
相关地址
CS、IP和PC寄存器 https://www.cnblogs.com/zhuge2018/p/8466288.html
ptrace介绍 https://blog.csdn.net/edonlii/article/details/8717029
mmap介绍 https://www.cnblogs.com/huxiao-tee/p/4660352.html
vdso介绍 https://blog.csdn.net/wang_xya/article/details/43985241
kcmp介绍 https://chunqiulfq.com/open_creat_and_openat_function.html
中断、异常、trap 的区别 https://blog.csdn.net/zat111/article/details/36420903
来源:https://blog.csdn.net/weixin_38669561/article/details/98183545