MIT XV6 - 2.1 Lab: system calls - Using gdb

接上文 MIT XV6 - 1.6 Lab: Xv6 and Unix utilities - uptime

终于进入第二章了 Operating system organization

请在开始第二章配套实验前,务必阅读 教材 book-riscv-rev3.pdf 第二章全文,第四章的 4.3 4.4两小节,以及教材中提到的相关代码(务必要跟着书中的代码走,否则就是走马观花),这章呢主要是让你对Xv6是什么,以及Xv6是如何做的有个粗略的概念,比如进程啊、中断啊、堆栈啊、页表啊等等,后面的章节会分别对这些概念进行展开。

另外教材中推荐了自家的 Using the GNU Debugger,介绍了一些基本用法,我也额外推荐一个指南 Quick Guide to gdb: The GNU Debugger , 涵盖了更多的命令和用法,甚至教你如何在gdb界面中单步调试源码,同时显示寄存器和汇编等。

Using gdb

实验介绍如下,这个实验没要求,就是让你熟悉起来gdb (原文链接) :

In many cases, print statements will be sufficient to debug your kernel, but sometimes being able to single step through some assembly code or inspecting the variables on the stack is helpful.

To help you become familiar with gdb, run make qemu-gdb and then fire up gdb in another window (see the gdb bullet on the guidance page).

话不多说开始~

  • 首先在项目目录下,一个终端中输入

    make qemu-gdb
    *** Now run 'gdb' in another window.
    qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -S -gdb tcp::25501
    
  • 然后另起一个终端输入

    gdb
    GNU gdb (GDB) 16.2
    Copyright (C) 2024 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    Type "show copying" and "show warranty" for details.
    This GDB was configured as "--host=aarch64-apple-darwin24.2.0 --target=x86_64-apple-darwin20".
    Type "show configuration" for configuration details.
    For bug reporting instructions, please see:
    <https://www.gnu.org/software/gdb/bugs/>.
    Find the GDB manual and other documentation resources online at:
      <http://www.gnu.org/software/gdb/documentation/>.
    
    For help, type "help".
    Type "apropos word" to search for commands related to "word".
    The target architecture is set to "riscv:rv64".
    warning: No executable has been specified and target does not support
    determining executable automatically.  Try using the "file" command.
    0x0000000000001000 in ?? ()
    (gdb)   
    

    macos 安装 gdb 参考 gdb — Homebrew Formulae

    感兴趣可以看一下 Makefile: 312 和 项目中的 .gdbinit文件,问一下AI看看是如何把gdb挂载到qemu运行的xv6系统中的。

  • syscall 设置断点,在 gdb 窗口中输入

    b syscall
    Breakpoint 1 at 0x8000208e: file kernel/syscall.c, line 133.
    
  • 恢复运行~,在 gdb 窗口中输入

    c
    Continuing.
    [Switching to Thread 1.3]
    
    Thread 3 hit Breakpoint 1, syscall () at kernel/syscall.c:133
    133     {
    (gdb)
    

    可以看到,断点已经触发了,源码位于 kernel/syscall.c:133

  • 输入 layout src,查看对应的源码文件

    layout src
    

    界面变得花里胡哨了起来
    在这里插入图片描述

  • 输入 backtrace 查看调用栈

    backtrace
    #0  syscall () at kernel/syscall.c:133
    #1  0x0000000080001dca in usertrap () at kernel/trap.c:67
    #2  0x0505050505050505 in ?? ()
    
  • 然后不停的输入 n,直到 struct proc *p = myproc(); 这行代码执行完毕,就两次

  • 输入 p/x *p,以16进制打印指针 p 所指向的对象 struct proc 的内容

    p/x *p
    $1 = {lock = {locked = 0x0, name = 0x800081b8, cpu = 0x0}, state = 0x4, chan = 0x0, killed = 0x0, 
    xstate = 0x0, pid = 0x1, parent = 0x0, kstack = 0x3fffffd000, sz = 0x1000, pagetable = 0x87f73000, 
    trapframe = 0x87f74000, context = {ra = 0x80001514, sp = 0x3fffffde90, s0 = 0x3fffffdec0, 
      s1 = 0x80008d50, s2 = 0x80008920, s3 = 0x1, s4 = 0x3, s5 = 0x800199f0, s6 = 0x1, s7 = 0x4, 
      s8 = 0xffffffffffffffff, s9 = 0x0, s10 = 0x0, s11 = 0x0}, ofile = {0x0 <repeats 16 times>}, 
    cwd = 0x80016e60, name = {0x69, 0x6e, 0x69, 0x74, 0x63, 0x6f, 0x64, 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 
      0x0, 0x0, 0x0}}
    
  • 查看 p->trapframe->a7 的值,并与 user/initcode.S 对照

    p/o p->trapframe->a7
    $3 = 07
    
    # Initial process that execs /init.
    # This code runs in user space.
    
    #include "syscall.h"
    
    # exec(init, argv)
    .globl start
    start:
           la a0, init
           la a1, argv
           li a7, SYS_exec
           ecall
    
    # for(;;) exit();
    exit:
           li a7, SYS_exit
           ecall
           jal exit
    
    # char init[] = "/init\0";
    init:
    .string "/init\0"
    
    # char *argv[] = { init, 0 };
    .p2align 2
    argv:
    .long init
    .long 0
    

    可以看到啊,寄存器 a7 的值对应 SYS_exec,查看 kernel/syscall.h: 8,刚好是 7

    // System call numbers
    #define SYS_fork    1
    #define SYS_exit    2
    #define SYS_wait    3
    #define SYS_pipe    4
    #define SYS_read    5
    #define SYS_kill    6
    #define SYS_exec    7
    #define SYS_fstat   8
    #define SYS_chdir   9
    #define SYS_dup    10
    #define SYS_getpid 11
    #define SYS_sbrk   12
    #define SYS_sleep  13
    #define SYS_uptime 14
    #define SYS_open   15
    #define SYS_write  16
    #define SYS_mknod  17
    #define SYS_unlink 18
    #define SYS_link   19
    #define SYS_mkdir  20
    #define SYS_close  21
    
    
  • 查看寄存器 sstatus 的值,它的说明在 RISC-V privileged instructions

    p /x $sstatus
    $2 = 0x200000022
    
  • 干点坏事,教材说了,在以后的实验中呢,不可避免的会发生内核级的异常,怎么办呢,我们先尝试人为的做点坏事,修改 kernel/syscall.c : 137

    void syscall(void)
    {
      int num;
      struct proc *p = myproc();
    
      // num = p->trapframe->a7;
      num = * (int *) 0;
      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]();
      } else {
        printf("%d %s: unknown sys call %d\n",
                p->pid, p->name, num);
        p->trapframe->a0 = -1;
      }
    }
    

    然后重新运行~ 会发现系统启动后就直接抛出异常了( sepc 值和教材不一样,因为他们更新了代码,教材还没更新)

    xv6 kernel is booting
    
    hart 2 starting
    hart 1 starting
    scause 0x000000000000000d
    sepc=0x00000000800020a2 stval=0x0000000000000000
    panic: kerneltrap
    

    这种情况怎么办呢,可以对异常的 sepc 直接下断点

    b *0x00000000800020a2
    Breakpoint 2 at 0x800020a2: file kernel/syscall.c, line 138.
    

    然后打开汇编窗口继续运行

    layout asm
    c
    Continuing.
    
    Thread 1 hit Breakpoint 2, syscall () at kernel/syscall.c:138
    

    断点触发,这一条指令的汇编代码

    0x800020a2 <syscall+20> lw      a3,0(zero) # 0x0 
    

    这是一个典型的内存访问问题,然后呢你这时候大概率想知道当前运行的进程是什么,就可以输入

    p p->name
    $3 = "initcode\000\000\000\000\000\000\000"
    

好了结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值