实验一
实现一个用户级别的程序,功能为,指定系统调用后,跟踪程序的系统调用情况
分析实验
实验目标为实现一个程序去跟踪指定程序的系统调用。因此目标有两个
- 实现一个程序
- 跟踪目标程序的系统调用
实现1,就需要在用户这边实现一个trace的相关程序,接收监控的syscall参数,以及启动被监控程序。注意部分代码已被实现user/trace.c
实现2,需要在被监控的程序中当调用到被监控的syscall时,记录或者输出其调用情况
实验目标一
第一步
先看已提供代码
分为三步:
- 容错处理
- 调用系统调用trace,对argv[1]代表参数进行监控
- 调用exec,执行被监控程序
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int
main(int argc, char *argv[])
{int i;char *nargv[MAXARG];//
/if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){ //容错处理fprintf(2, "Usage: %s mask command\n", argv[0]);exit(1);}if (trace(atoi(argv[1])) < 0) { //调用系统调用trace,对argv[1]代表参数进行监控fprintf(2, "%s: trace failed\n", argv[0]);exit(1);}for(i = 2; i < argc && i < MAXARG; i++){nargv[i-2] = argv[i];}exec(nargv[0], nargv); //调用exec,执行被监控程序exit(0);
}
trace还未实现,先看第三步exec执行被监控程序的特点:
exec系统调用使用从文件系统中存储的文件所加载的新内存映像替换调用进程的内存。(百度百科:根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件)该文件必须有特殊的格式,它指定文件的哪部分存放指令,哪部分是数据,以及哪一条指令用于启动等等。xv6使用ELF格式(将会在第三章详细讨论)。当exec执行成功,它不向调用进程返回数据,而是使加载自文件的指令在ELF header中声明的程序入口处开始执行。
也就是说exec所执行的可执行文件在调用进程之中,所以我们监控时只需要监控自身进程即可。
第二步
由于trace该用户进程还没有声明进入编译,因此我们需要Makefile中声明$U/_trace\
,使其被编译(注意系统调用未完善,编译会失败)
实验目标二
在实验目标一的基础上,可以发现被监控的程序与原监控程序在同一个进程之中,所以该trace系统调用要做的就是将跟踪的syscall写入该进程之中,将其保存。在后续触发syscall时,判断是否需要为监控的syscall,是则输出其调用记录。因此分为如下几步:
- 使用trace系统调用,记录监控的syscall
- 检查执行的syscall
第一步
修改kernel/proc.h
文件,在其中声明跟踪syscall,这里使用trace_mask做标记。
修改kernel/proc.c
文件,增加trace函数,其中记录跟踪的syscall
注意系统调用中fork会重新开辟进程空间,因此fork出来的进程是需要继承父进程的trace_mask的
第二步
检查系统调用是否为被监控syscall,在kernel/syscall.c
中加入监控代码即可。syscall_names
是我手动定义的syscall name。用于输出
代码执行过程
主体代码在上面过程中已经全部实现。后续就是把上面代码连通。
参考xv6的系统调用进入kernel逻辑:
想要调用内核函数的应用程序(例如xv6中的read系统调用)必须过渡到内核。CPU提供一个特殊的指令,将CPU从用户模式切换到管理模式,并在内核指定的入口点进入内核(RISC-V为此提供ecall指令)。一旦CPU切换到管理模式,内核就可以验证系统调用的参数,决定是否允许应用程序执行请求的操作,然后拒绝它或执行它。由内核控制转换到管理模式的入口点是很重要的;如果应用程序可以决定内核入口点, 那么恶意应用程序可以在跳过参数验证的地方进入内核。(原书2.2节)
因此需要在user/usys.pl
里面声明trace,使其调用trace时进程能顺利进入内核态。
注意13行,那里会查询SYS_trace的编号并执行对应函数,因此我们需要定义该调用编号kernel/syscall.h
声明该编号对应的函数kernel/syscall.c
至于sys_trace函数的实现,直接让其调用目标二的trace函数即可kernel/sysproc.c
最后在user/user.h
中增加对trace函数的引用