本实验的主要代码位于:
backtrace:
kernel/printf.c
kernel/riscv.h
alarm:kernel/
sysproc.c:sys_sigalarm & sys_sigreturn
proc.h:proc
proc.c:allocproc
trap.c:usertrap
a0-a7 作为参数寄存器;a2
可以看到在 main 中并没有显示地调用 f ,而是直接把结果加载到了 a1:
在 f 中,函数 g 直接被内联:
根据上文,调用 printf 时跳转到 ra 偏离 1586 的位置。如果使用gdb可以更好地体现这点
跳转到 printf 后,ra应该等于 38
57616 的十六进制刚好是 E110,ASCII码中,r为0x72,l为0x6c,d为0x64
而xv6为小端序,也就是说从地址01,E110排布为:10 E1;0x00646c72从地址03,排布为:72 6c 64 00,所以打印出来为 rld\0
如果xv6为大端序,为了获得相同的输出,i需要更改为:72 6c 64 00。
而 57616 需要吗?gpt给的回答是需要,我觉得应该不用吧,作为一个数字类型怎么存的就怎么取,这些过程应该都是自动的,而不像字符串那么分散的形式
一个随机值,动态参数中未初始化的变量
获取 fp:
请注意,xv6是小端序,而fp指向栈帧顶部,即ra位于fp-8,prev fp位于fp-16
比如:栈帧1-8,则fp指向0
首先调用 r_fp 获取当前fp的值x0,输出一行地址
获取x0-16处的值x1,输出一行地址
获取x1-16…..
直到栈顶,那么如何判断栈顶
?xv6按页分配内存,在我们的例子中,stack只占一页,我们只需要判断fp指向的地址是否在同一页——通过使用宏:PGROUNDDOWN
对页大小向下取整
顶部的fp可能也只是一个垃圾值,总之,和当前页号不一样就是到达了顶部
注意用%p,注意输出的是返回地址
于是我们可以愉快的把backtrace加入到panic中了
据说完成只需要几行代码,但是难度是hard,让我试试
CPU 每次时钟 tick 都会触发 timer interrupt
调用位于地址0处的函数
:
1 | (*(void(*)())fn)() |
内核和用户地址空间是不同的,虽然这在本实验中也没什么影响。特别注意,不要在内核空间调用用户的函数
另外,执行 handler 时,会把用户原来的寄存器覆盖完,需要保存原来的trapframe
,并确保在sigreturn时恢复了它们,以及 a0
记得初始化 proc 中新的字段
防止重入
,执行 alarm_handler 时不能再次进入~
修改proc,添加字段记录总tick、当前tick、handler、timer_trapframe、timer_can_enter,在allocproc中初始化。修改 usertrap,当trap类型为定时器中断时,检查是否设置timer_handler,是则++tick。当tick等于总tick,重置tick,保存trapframe,设置timer_can_enter为0,设置返回指令为:handler中的第一句指令
在 sigreturn 中,需要恢复trapframe,enable timer handler,然后返回一个a0–这是一个系统调用