代码:https://github.com/JasenChao/xv6-labs.git

使用GDB调试

安装risc-v的GDB

先安装依赖:

sudo apt-get install libncurses5-dev python2 python2-dev texinfo libreadline-dev

再下载源码,可以从清华镜像源下载:

wget https://mirrors.tuna.tsinghua.edu.cn/gnu/gdb/gdb-13.2.tar.xz

解压缩并编译安装:

tar xvf gdb-13.2.tar.xz
cd gdb-13.2/
mkdir build
cd build
../configure --prefix=/usr/local --target=riscv64-unknown-elf --enable-tui=yes
make -j$(nproc)
sudo make install

使用GDB

先用qemu运行xv6,在xv6目录下执行:

make qemu-gdb

此时应该打印一个端口号,例如tcp::26000,记住这个端口号,另起一个终端,运行gdb:

riscv64-unknown-elf-gdb

在GDB中连接这个端口,由于本次实验想在每次syscall中断时断点,所以file kernel/kernel

target remote localhost:26000
file kernel/kernel
b syscall

c让程序从断点处继续执行,输出如下:

(gdb) b syscall
Breakpoint 1 at 0x80002142: file kernel/syscall.c, line 243.
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, syscall () at kernel/syscall.c:243
243     {
(gdb) layout src
(gdb) backtrace

layout 命令将窗口一分为二,显示src,也可以layout asm等。

GDB有一些常用指令:

1.break/b (*地址)/函数名 #设置断点,指令地址可以查看asm文件
#除此之外通常可以使用b *$stvec,在trampoline处设置断点

2.continue/c #运行到断点处然后停下

3.stepi/si #运行单个汇编指令,进入函数

4.n #运行一行c语言代码,不进入函数

5.step/s #运行一行C语言代码,进入函数

6.info/i reg/r #查看32个同一寄存器和PC寄存器的值

7.info/i break/b #查看断点信息

8.info/i frame #打印当前栈帧

9.print/p (/x) 变量/$寄存器 #打印C语言变量值或寄存器的值,/x是将值为打印16进制的值

10.examining/x 地址 #检测内存单元并打印
                #可以使用/x十六进制 /i指令 /c字符格式化打印值
                #而且可以使用/4c打印4个字节单元并输出为字符
11.list 地址  #打印函数的源代码在指定的位置。

12.layout asm/src/split #分别打开汇编代码窗口、源码窗口、split打开汇编和源码窗口
                        #ctrl x + a 可以关闭layout窗口

添加系统调用-trace

题目要求基于已经给出的trace.c实现系统调用跟踪。根据提示按步骤进行。

  • $U/_trace\添加到MakefileUPROGS
  • user/user.h中添加系统调用int trace(int)
  • user/usys.pl中添加stub entry("trace")
  • kernel/syscall.h中添加系统调用编号#define SYS_trace 22
  • kernel/sysproc.c中添加函数sys_trace(),代码如下:
      uint64
      sys_trace(void)
      {
          int n;
          argint(0, &n);
          myproc()->trace_mask = n;
          return 0;
      }
    
  • kernel/syscall.c中添加命令名称数组,便于显示系统调用名,修改syscall函数,代码如下:
      static char syscall_name[23][16] = {"fork", "exit", "wait", "pipe", "read", "kill", "exec", "fstat", "chdir", "dup", "getpid", "sbrk", "sleep", "uptime", "open", "write", "mknod", "unlink", "link", "mkdir", "close", "trace", "sysinfo"};
    
      void
      syscall(void)
      {
      int num;
      struct proc *p = myproc();
    
      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->trace_mask > 0 && (p->trace_mask & (1 << num)))
          {
          printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num - 1], p->trapframe->a0);
          }
      } else {
          printf("%d %s: unknown sys call %d\n",
                  p->pid, p->name, num);
          p->trapframe->a0 = -1;
      }
      }
    
  • kernel/syscall.c中还需要增加系统调用的函数指针:
      extern uint64 sys_trace(void);
    
      static uint64 (*syscalls[])(void) = {
      [SYS_fork]    sys_fork,
      [SYS_exit]    sys_exit,
      [SYS_wait]    sys_wait,
      [SYS_pipe]    sys_pipe,
      [SYS_read]    sys_read,
      [SYS_kill]    sys_kill,
      [SYS_exec]    sys_exec,
      [SYS_fstat]   sys_fstat,
      [SYS_chdir]   sys_chdir,
      [SYS_dup]     sys_dup,
      [SYS_getpid]  sys_getpid,
      [SYS_sbrk]    sys_sbrk,
      [SYS_sleep]   sys_sleep,
      [SYS_uptime]  sys_uptime,
      [SYS_open]    sys_open,
      [SYS_write]   sys_write,
      [SYS_mknod]   sys_mknod,
      [SYS_unlink]  sys_unlink,
      [SYS_link]    sys_link,
      [SYS_mkdir]   sys_mkdir,
      [SYS_close]   sys_close,
      [SYS_trace]   sys_trace,
      };
    
  • 修改kernel/proc.h中的struct proc结构体,增加int trace_mask
  • 修改kernel/proc.c中的fork函数,使得子进程可以继承父进程的trace_mask,增加的代码如下:
      acquire(&np->lock);
      np->trace_mask = p->trace_mask;
      release(&np->lock);
    

Makefile调用perl脚本user/usys.pl,该脚本生成user/usys.S,这是实际的系统调用,它使用RISC-V ecall指令转换到内核。

使用make GRADEFLAGS=trace grade测试代码是否通过。

添加系统调用-sysinfo

题目要求基于已经给出的sysinfotest.c实现系统调用跟踪。根据提示按步骤进行。

  • $U/_sysinfotest\添加到MakefileUPROGS
  • user/user.h中添加系统调用:
      struct sysinfo;
      int sysinfo(struct sysinfo*);
    
  • user/usys.pl中添加stub entry("sysinfo");
  • kernel/syscall.h中添加系统调用编号#define SYS_sysinfo 23
  • kernel/sysproc.c中添加函数sys_sysinfo(),代码如下,free_memory()proc_num()函数待实现:
      #include "sysinfo.h"
    
      uint64
      sys_sysinfo(void)
      {
          struct sysinfo info;
          uint64 addr;
    
          argaddr(0, &addr);
          struct proc* p = myproc();
          info.freemem = free_memory();
          info.nproc = proc_num();
          // 将内核态中的info复制到用户态
          if (copyout(p->pagetable, addr, (char*)&info, sizeof(info)) < 0)
              return -1;
          return 0;
      }
    
  • kernel/syscall.c中增加系统调用的函数指针:
      extern uint64 sys_sysinfo(void);
    
      static uint64 (*syscalls[])(void) = {
      [SYS_fork]    sys_fork,
      [SYS_exit]    sys_exit,
      [SYS_wait]    sys_wait,
      [SYS_pipe]    sys_pipe,
      [SYS_read]    sys_read,
      [SYS_kill]    sys_kill,
      [SYS_exec]    sys_exec,
      [SYS_fstat]   sys_fstat,
      [SYS_chdir]   sys_chdir,
      [SYS_dup]     sys_dup,
      [SYS_getpid]  sys_getpid,
      [SYS_sbrk]    sys_sbrk,
      [SYS_sleep]   sys_sleep,
      [SYS_uptime]  sys_uptime,
      [SYS_open]    sys_open,
      [SYS_write]   sys_write,
      [SYS_mknod]   sys_mknod,
      [SYS_unlink]  sys_unlink,
      [SYS_link]    sys_link,
      [SYS_mkdir]   sys_mkdir,
      [SYS_close]   sys_close,
      [SYS_trace]   sys_trace,
      [SYS_sysinfo] sys_sysinfo,
      };
    
  • kernel/proc.c中增加proc_num()函数,统计进程数,代码如下:
      int
      proc_num(void)
      {
          int i;
          int n = 0;
          for (i = 0; i < NPROC; i++)
          {
              if (proc[i].state != UNUSED) n++;
          }
          return n;
      }
    
  • kernel/kalloc.c中增加free_memory()函数,统计可用内存,代码如下:
      uint64 
      free_memory(void)
      {
          struct run* p = kmem.freelist;
          uint64 num = 0;
          while (p)
          {
              num ++;
              p = p->next;
          }
          return num * PGSIZE;
      }
    
  • kernel/defs.h中对应的区域增加函数声明,代码如下:
      uint64          free_memory(void);
      int             proc_num(void);
    

使用make GRADEFLAGS=sysinfo grade测试代码是否通过。

题目提示copyout()函数的使用可以参考filestat()函数,这个函数的功能是从内核复制到用户,传入参数依次为当前进程的页表、地址、需要复制内容的起始地址、需要复制的长度。

测试结果

使用make grade测试,结果如下:

== Test trace 32 grep ==
$ make qemu-gdb
trace 32 grep: OK (1.5s)
== Test trace all grep ==
$ make qemu-gdb
trace all grep: OK (1.0s)
== Test trace nothing ==
$ make qemu-gdb
trace nothing: OK (0.9s)
== Test trace children ==
$ make qemu-gdb
trace children: OK (8.3s)
== Test sysinfotest ==
$ make qemu-gdb
sysinfotest: OK (1.1s)