参考:
Lab: Traps
- 关于寄存器s0和堆栈
https://pdos.csail.mit.edu/6.828/2020/lec/l-riscv-slides.pdf
RISC-V assembly
Q: 哪些寄存器包含函数的参数?例如,哪个寄存器在main对printf的调用中保存了传参13 ?
A: a2保存13(通过gdb调试可看出寄存器a2的值)
Q:在main的程序集代码中,函数f的调用在哪里?调用g在哪里?(提示:编译器可能内联函数。)
A:查看asm文件,发现没有对f,g的调用,g被内联f函数,f函数被内联到main函数
Q:printf函数的地址在哪里?
A:34: 600080e7 jalr 1536(ra)
# 630 <printf>可知0x630是printf函数入口
Q:main中,jalr跳转到printf之后,ra的值是多少?
A:ra的值是0x38, 对应的0x34的下一条指令
当printf执行完成后,会让pc=寄存器ra的值,然后程序会执行main函数中的0x38这行的代码
Q: xv6是小端的,如果要实现一样的输出:HE110 World,大端模式下该如何实现?
小端:低地址在前
0x00000064用字符串打出来,发现解释的方向是64 00 00 00 即r\0\0\0 而不是 \0\0\0r这就是小端
1. 所以如果大端需要输出r ,int c = 0x64000000; 即可输出r.
2. 而对于int的数据,则无论大小端用%d输出的值都一样
Q: printf("x=%d y=%d", 3); 输出结果是什么?
y输出的是寄存器a2的值
Backtrace
首先
static inline uint64 r_fp() { uint64 x; asm volatile("mv %0, s0" : "=r" (x) ); return x; } // 返回的是寄存器s0的值寄存器s0的值代表Frame pointer
- 栈指针fp:代表当前函数的入口地址
- fp - 8: 代表返回地址,代表当前栈帧执行结束后,下一行该执行的代码地址
- fp - 16代表调用当前栈帧的栈帧(比如函数A调用函数B,那么B的fp -16就代表A),所以可通过prev.Frame的地址打印,即可得到调用当前函数的Frame pointer, 在通过addr2line工具即可得到代码执行的情况。
- stack frame: 函数的调用栈
- xv6为每一个stack分配4K的页,于是可通fp = r_fp(); 得到当前栈帧,PGROUNDDOWN(fp) and PGROUNDUP(fp) 来得到当前stack的函数调用界限。
- 根据fp得到Return Address , 即fp - 8 ;
- 比如调用顺序是:
- bttest函数的sleep(1);系统调用
- trap.c的usertrap()函数
- syscall.c的syscall()函数
- sysproc.c的sys_sleep()函数
- 调用backtrace()函数: 可通过fp--->得到return adrees,即可向上递归,得到调用栈
void backtrace(void) { printf("backtrace:\n"); uint64 fp = r_fp(); uint64 *frame = (uint64*) fp; uint64 up = PGROUNDUP(fp); uint64 down = PGROUNDDOWN(fp); // xv6为每个堆栈分配一个页。大小为4K, // 通过PGROUNDUP和PGROUNDDOWN可计算出这个页的边界 while (fp < up && fp > down) { printf("%p\n", frame[-1]); // 返回地址 fp = frame[-2]; // 上一个fp frame = (uint64* )fp; } }
结果如下:
a123@ubuntu:~/Public/repo/xv6-labs-2020$ make qemu qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 xv6 kernel is booting hart 2 starting hart 1 starting init: starting sh $ bttest backtrace: 0x0000000080002d56 0x0000000080002bb8 0x00000000800028a2 $ QEMU: Terminated a123@ubuntu:~/Public/repo/xv6-labs-2020$ addr2line -e kernel/kernel 0x0000000080002d56 /home/a123/Public/repo/xv6-labs-2020/kernel/sysproc.c:76 0x0000000080002bb8 /home/a123/Public/repo/xv6-labs-2020/kernel/syscall.c:140 0x00000000800028a2 /home/a123/Public/repo/xv6-labs-2020/kernel/trap.c:76 ^C a123@ubuntu:~/Public/repo/xv6-labs-2020$
test0: invoke handler
实现参考:Mit6.S081-实验4-Traps_解析Ta的博客-CSDN博客
目标:调用signalarm(n, f)函数,实现间隔n秒,执行一次f函数
- 首先实现sigalarm和sigalreturn的系统调用,便于alarmtest.c执行系统调用
- 在sigalarm函数:
- 得到当前进程
- 在struct proc中添加函数指针handler, 间隔时间,消耗时间。
- 在当前进程的表示struct proc中去记下回调函数handler和alarm间隔时间
- 在中断产生:会进入usertrap()函数,而其中which_dev == 2表示是滴答中断,于是在此中断检查是否时间到了,到了就执行定时回调函数
test1/test2(): resume interrupted code
MIT 6.S081 Lab4: traps_rocketeerLi的博客-CSDN博客_mit 6.s081 traps
目标:在执行signal_alarm函数前后,希望可以保持trapframe不变。
- 在调用signal_alarm(n,f);设定的f函数前,先保存trapframe
- 在usertraps中调用完成设定的函数之后,将trapframe设置为保存的trapframe