【MIT-OS6.S081作业-4-2】Lab4-traps-Backtrace

本文记录MIT-OS6.S081 Lab4 traps的Backtrace函数的实现过程

文章目录

  • [1. 作业要求](#1. 作业要求)
  • [2. 实现过程](#2. 实现过程)
    • [2.1 代码实现](#2.1 代码实现)

1. 作业要求

Backtrace (moderate)

For debugging it is often useful to have a backtrace: a list of the function calls on the stack above the point at which the error occurred.

Implement a backtrace() function in kernel/printf.c. Insert a call to this function in sys_sleep, and then run bttest, which calls sys_sleep. Your output should be as follows:

c 复制代码
backtrace:
0x0000000080002cda
0x0000000080002bb6
0x0000000080002898

After bttest exit qemu. In your terminal: the addresses may be slightly different but if you run addr2line -e kernel/kernel (or riscv64-unknown-elf-addr2line -e kernel/kernel) and cut-and-paste the above addresses as follows:

c 复制代码
$ addr2line -e kernel/kernel
0x0000000080002de2
0x0000000080002f4a
0x0000000080002bfc
Ctrl-D

You should see something like this:

c 复制代码
kernel/sysproc.c:74
kernel/syscall.c:224
kernel/trap.c:85

The compiler puts in each stack frame a frame pointer that holds the address of the caller's frame pointer. Your backtrace should use these frame pointers to walk up the stack and print the saved return address in each stack frame.
Some hints:

  • Add the prototype for backtrace to kernel/defs.h so that you can invoke backtrace in sys_sleep.
  • The GCC compiler stores the frame pointer of the currently executing function in the register s0. Add the following function to kernel/riscv.h:
c 复制代码
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}

and call this function in backtrace to read the current frame pointer. This function uses in-line assembly to read s0.

  • These lecture notes have a picture of the layout of stack frames. Note that the return address lives at a fixed offset (-8) from the frame pointer of a stackframe, and that the saved frame pointer lives at fixed offset (-16) from the frame pointer.
  • Xv6 allocates one page for each stack in the xv6 kernel at PAGE-aligned address. You can compute the top and bottom address of the stack page by using PGROUNDDOWN(fp) and PGROUNDUP(fp) (see kernel/riscv.h. These number are helpful for backtrace to terminate its loop.
    Once your backtrace is working, call it from panic in kernel/printf.c so that you see the kernel's backtrace when it panics.

2. 实现过程

2.1 代码实现

我们先看一下bttest

c 复制代码
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
  sleep(1);
  exit(0);
}

这里调用了sleep,题目说backtrace要被插入到sleep里。

Add the prototype for backtrace to kernel/defs.h so that you can invoke backtrace in sys_sleep.

c 复制代码
// printf.c
void            printf(char*, ...);
void            panic(char*) __attribute__((noreturn));
void            printfinit(void);
void            backtrace(void); // 添加在这里

The GCC compiler stores the frame pointer of the currently executing function in the register s0. Add the following function to kernel/riscv.h

s0寄存器存放当前执行函数的frame指针。我们需要在kernel/riscv.h添加函数:

c 复制代码
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}

call this function in backtrace to read the current frame pointer. This function uses in-line assembly to read s0.

backtrace会使用上面的r_fp函数读取s0寄存器。

These lecture notes have a picture of the layout of stack frames. Note that the return address lives at a fixed offset (-8) from the frame pointer of a stackframe, and that the saved frame pointer lives at fixed offset (-16) from the frame pointer.

从这句话可以看出栈的布局

Xv6 allocates one page for each stack in the xv6 kernel at PAGE-aligned address. You can compute the top and bottom address of the stack page by using PGROUNDDOWN(fp) and PGROUNDUP(fp) (see kernel/riscv.h. These number are helpful for backtrace to terminate its loop.

xv6会为栈分配一页,backtrace沿着上面的内存布局不断上溯,其实是一个从低地址往高地址回溯的过程,停止的条件就是当fp超过[PGROUNDDOWN(fp), PGROUNDUP(fp))范围。

于是在printf我们很自然地可以写出backtrace

c 复制代码
void            
backtrace()
{
  uint64 fp = r_fp();
  uint64 stackUp = PGROUNDUP(fp);
  uint64 stackBottom = PGROUNDDOWN(fp);
  while (fp >= stackBottom && fp < stackUp)
  {
    uint64 ra = *(uint64*)(fp - 8);
    printf("%p\n", ra);
    fp = *(uint64*)(fp - 16);
  }
}

我们测试一下:

c 复制代码
make qemu
bttest

然后我们再使用作业所说的测试命令:

cpp 复制代码
./grade-lab-traps backtrace

完活!理解了其实不难的。

相关推荐
_OP_CHEN1 天前
【Linux系统编程】(二十八)深入 ELF 文件原理:从目标文件到程序加载的完整揭秘
linux·操作系统·编译·c/c++·目标文件·elf文件
肆忆_1 天前
手搓 VM 复盘:从我的 C++ 并发 GC 到字节 PrimJS 的架构演进
操作系统
c++逐梦人1 天前
Linux基础IO
linux·操作系统·io
凉、介1 天前
VMware 三种网络模式(桥接 / NAT / Host-Only)原理与实验解析
c语言·网络·笔记·操作系统·嵌入式·vmware
_OP_CHEN2 天前
【Linux系统编程】(二十七)手撕动静态库原理与实战:从底层逻辑到代码落地
linux·操作系统·动态库·静态库·c/c++·库的原理与制作
fakerth3 天前
【OpenHarmony】输入法imf框架
操作系统·openharmony
Trouvaille ~3 天前
【Linux】UDP Socket编程实战(二):网络字典与回调设计
linux·运维·服务器·网络·c++·udp·操作系统
凉、介3 天前
静态路由探究
网络·笔记·操作系统·嵌入式
_OP_CHEN3 天前
【Linux系统编程】(二十六)一文吃透 Ext 系列文件系统软硬链接:原理、实战与底层逻辑揭秘
linux·操作系统·文件系统·c/c++·硬链接·软链接·ext2文件系统
fleaxin3 天前
统信服务器操作系统V20(1070)安装过程
docker·操作系统·nvidia·统信