MIT6.S081:System calls(2) trace

In this assignment you will add a system call tracing feature that may help you when debugging later labs. You'll create a new trace system call that will control tracing. It should take one argument, an integer "mask", whose bits specify which system calls to trace. For example, to trace the fork system call, a program calls trace(1 << SYS_fork), where SYS_fork is a syscall number from kernel/syscall.h. You have to modify the xv6 kernel to print out a line when each system call is about to return, if the system call's number is set in the mask. The line should contain the process id, the name of the system call and the return value; you don't need to print the system call arguments. The trace system call should enable tracing for the process that calls it and any children that it subsequently forks, but should not affect other processes.

要求在xv6内核中添加一个新的系统调用trace,通过传递一个整数"mask"作为参数来控制需要追踪哪些系统调用。

这个mask的每个位代表一个系统调用号码,例如,如果想要追踪fork系统调用,可以通过调用trace(1 << SYS_fork)来实现,其中SYS_fork是内核/syscall.h中的系统调用号码。

在trace系统调用中,需要打印出每个系统调用返回前的相关信息,包括进程ID、系统调用名称和返回值。不需要打印系统调用的参数。这个trace系统调用应该只对调用它的进程及其后续创建的子进程生效,不影响其他进程。

提示

  • Add $U/_trace to UPROGS in Makefile

  • Run make qemu and you will see that the compiler cannot compile user/trace.c, because the user-space stubs for the system call don't exist yet: add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h. The Makefile invokes the perl script user/usys.pl, which produces user/usys.S, the actual system call stubs, which use the RISC-V ecall instruction to transition to the kernel. Once you fix the compilation issues, run trace 32 grep hello README; it will fail because you haven't implemented the system call in the kernel yet.

  • Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.

  • Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.

  • Modify the syscall() function in kernel/syscall.c to print the trace output. You will need to add an array of syscall names to index into.

  • If a test case passes when you run it inside qemu directly but you get a timeout when running the tests using make grade, try testing your implementation on Athena. Some of tests in this lab can be a bit too computationally intensive for your local machine (especially if you use WSL).

 实现目标(例):

$ trace 32 grep hello README
70: syscall read -> 1023
70: syscall read -> 961
70: syscall read -> 321
70: syscall read -> 0

  32= 1< trace 32 grep hello README 可以打印grep hello README该指令调用的SYS_read的信息。

$ trace 2147483647 grep hello README 
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 961
4: syscall read -> 321
4: syscall read -> 0
4: syscall close -> 0

 MIT6.S081:System calls(2) trace_第1张图片

2147483647是0111 1111 1111 1111 1111 1111 1111 1111,trace 2147483647 grep hello README 可以查看grep hello README中0~31的所有系统调用。

补充:

strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通 过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。trace功能和strace类似。

根据提示完成实验:

user/user.h: 用户态程序调用跳板函数(也就是系统调用在用户态的声明)
user/usys.S: 跳板函数使用 CPU 提供的 ecall 指令,调用到内核态
kernel/syscall.c :到达内核态统一系统调用处理函数 syscall(),所有系统调用都会跳到这里来处理。
kernel/syscall.c syscall(): 根据跳板传进来的系统调用编号,查询 syscalls[ ] 表,找到对应的内核函数并调用。

 做实验前需要实现user/trace.c

#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 failed\n", argv[0]);
                          exit(1);
            }
            if (trace(atoi(argv[1])) < 0) {
                          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);
            exit(0);
}

(1) Add $U/_trace to UPROGS in Makefile

vim Makefile

在UPROGS下新添一行$U/_trace

MIT6.S081:System calls(2) trace_第2张图片

(2)add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h.

MIT6.S081:System calls(2) trace_第3张图片

MIT6.S081:System calls(2) trace_第4张图片

MIT6.S081:System calls(2) trace_第5张图片

(3) Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.

 kernel/proc.h

MIT6.S081:System calls(2) trace_第6张图片

  • 为了要让系统在调用trace后,当产生系统调用时,输出相关信息,需要在进程pcb中增添一个成员syscall_trace用来存放mask。

kernel/sysproc.c

MIT6.S081:System calls(2) trace_第7张图片

uint64 sys_trace(void)
{
  int mask;
  argint(0,&mask);
  myproc()->syscall_trace = mask;
  return 0;
}
  • 并且由于内核与用户进程的页表不同,寄存器也不互通,所以参数无法直接通过 C 语言参数的形式传过来,而是需要使用 argaddr、argint、argstr 等系列函数,从进程的 trapframe 中读取用户进程寄存器中的参数。

 kernel/proc.h

(4)Modify fork()  to copy the trace mask from the parent to the child process.

MIT6.S081:System calls(2) trace_第8张图片

  • fork时,应实验要求,也要对子进程进行追踪,所以需要让子进程继承mask

(5)Modify the syscall() function in kernel/syscall.c to print the trace output. You will need to add an array of syscall names to index into.

MIT6.S081:System calls(2) trace_第9张图片

MIT6.S081:System calls(2) trace_第10张图片

  • 加入一个字符数组,通过系统调用号来找到名字

MIT6.S081:System calls(2) trace_第11张图片

 if (p->syscall_trace & (1 << num)) {
    // print trace info
      printf("%d: syscall %s -> %d\n",p->pid, syscalls_name[num], p->trapframe->a0);
    }    
// Prototypes for the functions that handle system calls.
...
extern uint64 sys_close(void);
extern uint64 sys_trace(void);

// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
static uint64 (*syscalls[])(void) = {
...
[SYS_close]   sys_close,
[SYS_trace]   sys_trace,
};

// 添加识别名
char* syscalls_name[23] = {"", "fork", "exit", "wait", "pipe", "read", "kill", "exec",
                      "fstat", "chdir", "dup", "getpid", "sbrk", "sleep", "uptime",
                      "open", "write", "mknod", "unlink", "link", "mkdir", "close", "trace"};

void
syscall(void)
{
  ...
  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    // Use num to lookup the system call function for num, call it,
    // and store its return value in p->trapframe->a0
    p->trapframe->a0 = syscalls[num]();
    if (p->syscall_trace & (1 << num)) {
    // print trace info
      printf("%d: syscall %s -> %d\n",p->pid, syscalls_name[num], p->trapframe->a0);
    }    
}

测试:

MIT6.S081:System calls(2) trace_第12张图片

补充:xv6从用户模式切换到内核模式进行系统调用的全过程 。

待续。。

你可能感兴趣的:(linux,前端,javascript)