0.问题发现
在完成lab1课下代码后,我尝试进行make
run,发现不断输出init.c: mips_init() is called
,如下图
我认为是代码出现了问题,果断求助助教。在助教的帮助下,我逐渐理解了这个问题。
1.分析代码
通过阅读代码,我们知道,输出这个字符串是start.S调用init.c中函数mips_init得到,由于不方便展示源码,这里只指出我是通过j指令完成了跳转,强调这一点是有必要的,如果使用jal进行跳转就不会出现这种状况,jal指令会改变$ra寄存器的值,与后边的论述矛盾。
通过对我们make的内核mos进行gdb调试,发现在程序执行完mips_init()之后总是跳回到start.S的第一行代码,如下图。
通过上学期的计组知识,我们知道,从被调用者跳回到调用者是通过指令
jr $ra
实现的,这一点我们可以在gdb中展示汇编代码得到验证(gdb模式下直接输入disassemble),如下图。
这样问题就转化为
$ra
的值是什么?通过指令i registers
可以查看各个寄存器的值。我们得到$ra
寄存器的值如下图
同时我们可以发现,start.S中第一条代码的地址即为此地址。
但是在我们的代码中并没有存在对
$ra
赋值的操作,也就是说问题转化为$ra
初值是如何得到的?
2.bootloader
通过以上的分析,我们知道在内核相关代码中,我们并没有对$ra
的值有写操作,此时将目光放在启动内核之前的硬件初始化阶段,即bootloader
。直接make dbg
进入调试,我们会发现输出如下
这里的0xbfc00000即为bootloader的地址,gdb并不支持显示调试信息,此时我们可以使用汇编级调试,通过运行
1 | set disassemble-nextline-on |
进入汇编调试模式,之后ni
单步调试,我们发现在bootloader中进行了对寄存器的赋初值操作,这里只展示我们关心的对于$ra
寄存器的赋值操作,如下图
以上两条指令即为对
$ra
寄存器的赋值操作,更加值得注意的是下面两条汇编代码
我们发现在完成硬件相关初始化之后,跳转到了内核入口,在实践中印证了指导书中的内容
现在我们已经知道了
$ra
寄存器是在bootloader中进行赋初值,但是,bootloader是如何知道内核入口(kernel_entry)在哪里呢,即bootloader是如何知道要给$ra
赋这个地址的?这与我们的实验环境QEMU有关。
3.QEMU
我们知道,实验中我们使用了QEMU自带的bootloader,实际上,qemu也处理了一部分软件流程,对bootloader进行了简化,这在init.c的注释部分给出了提示。
直接去盒一下github上的源码!
在源码中我们找到了对
$ra
赋值操作,进一步的,查找kernel_entry
是如何得来的?进一步查阅源码,可以发现
同时源码中给出了关于load_kernel()函数的定义,这里不再关心,我们可以得到结论:对
$ra
寄存器的赋值是在bootloader阶段由QEMU辅助完成的!
4. 总结
此篇讨论帖讨论了关于$ra
寄存器初值的问题以及bootloader和QEMU的一些思考,如有错误敬请指正,这里特别感谢助教的指引和帮助!
最后修改于:
最后回复于: