【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

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

相关推荐
charlie1145141912 天前
Cinux · 第一次跳进 Ring 3:用户态与特权隔离
开发语言·c++·操作系统·开源项目
程序猿乐锅2 天前
【操作系统 | 第一章】从资源管理到虚拟机
操作系统
wunaiqiezixin3 天前
MIT 6.S081 lazy 前置篇(lab5):缺页异常、惰性分配、写时复制、页面换出
linux·unix·os·xv6·mit6.s081
CoovallyAIHub4 天前
一个集装箱从船到火车的两个小时,AI 搭档 Coco在中间做了什么
操作系统·agent·资讯
驱动探索者4 天前
RISC-V 指令集深度解析:从 ISA 到 Zephyr
网络·计算机·操作系统·os
码农小韩5 天前
Linux操作系统(六)——软件包管理
linux·操作系统·linux驱动·嵌入式软件开发·linux应用
纵有疾風起5 天前
I/O 软件层次结构:从用户态到硬件中断的完整旅程
操作系统·408·中断·lut·驱动程序·设备独立性
TechEdu2026065 天前
[操作系统]操作系统文件系统与输入输出:架构、行为与调优
操作系统·计算机科学·os
charlie1145141915 天前
Cinux是如何管理进程的 —— 上下文与调度
开发语言·c++·操作系统·开源项目
The Chosen One9856 天前
【操作系统】操作系统引导、虚拟机、进程通信、信号
linux·运维·服务器·操作系统·虚拟机·信号