关于$ra寄存器的赋初值问题的一些探索

杜启嵘
🔖︎ 3 订阅
👍︎ 20 点赞

0.问题发现

​ 在完成lab1课下代码后,我尝试进行make run,发现不断输出init.c: mips_init() is called,如下图 image.png ​ 我认为是代码出现了问题,果断求助助教。在助教的帮助下,我逐渐理解了这个问题。

1.分析代码

​ 通过阅读代码,我们知道,输出这个字符串是start.S调用init.c中函数mips_init得到,由于不方便展示源码,这里只指出我是通过j指令完成了跳转,强调这一点是有必要的,如果使用jal进行跳转就不会出现这种状况,jal指令会改变$ra寄存器的值,与后边的论述矛盾。 通过对我们make的内核mos进行gdb调试,发现在程序执行完mips_init()之后总是跳回到start.S的第一行代码,如下图。 52fd8e355dba9fe1c58332de8bdb6fd.png ​ 通过上学期的计组知识,我们知道,从被调用者跳回到调用者是通过指令jr $ra实现的,这一点我们可以在gdb中展示汇编代码得到验证(gdb模式下直接输入disassemble),如下图。 2d60f5d8497ef92340b385af439163f.png ​ 这样问题就转化为$ra的值是什么?通过指令i registers可以查看各个寄存器的值。我们得到$ra寄存器的值如下图 981d818d3da49ea7a2a3e0e1a925e4f.png ​ 同时我们可以发现,start.S中第一条代码的地址即为此地址。 385fa0c26be69d5e844ce3c4c662318.png ​ 但是在我们的代码中并没有存在对$ra赋值的操作,也就是说问题转化为$ra初值是如何得到的?

2.bootloader

​ 通过以上的分析,我们知道在内核相关代码中,我们并没有对$ra的值有写操作,此时将目光放在启动内核之前的硬件初始化阶段,即bootloader。直接make dbg进入调试,我们会发现输出如下 e7a3a409b5977724741fc94ee3d2d08.png ​ 这里的0xbfc00000即为bootloader的地址,gdb并不支持显示调试信息,此时我们可以使用汇编级调试,通过运行

1
set disassemble-nextline-on

​ 进入汇编调试模式,之后ni单步调试,我们发现在bootloader中进行了对寄存器的赋初值操作,这里只展示我们关心的对于$ra寄存器的赋值操作,如下图 b2ac38bbfc263bb6210eae09c74cb33.png ​ 以上两条指令即为对$ra寄存器的赋值操作,更加值得注意的是下面两条汇编代码 ac0db2ef1f430b125c260dc7cfc89b4.png ​ 我们发现在完成硬件相关初始化之后,跳转到了内核入口,在实践中印证了指导书中的内容 image.png ​ 现在我们已经知道了$ra寄存器是在bootloader中进行赋初值,但是,bootloader是如何知道内核入口(kernel_entry)在哪里呢,即bootloader是如何知道要给$ra赋这个地址的?这与我们的实验环境QEMU有关。

3.QEMU

​ 我们知道,实验中我们使用了QEMU自带的bootloader,实际上,qemu也处理了一部分软件流程,对bootloader进行了简化,这在init.c的注释部分给出了提示。 8e10c1cbc775fdf42f1c9a1b4d733fd.png ​ 直接去盒一下github上的源码! 6e4d8d8a263a601cf9f2f02eca49203.png ​ 在源码中我们找到了对$ra赋值操作,进一步的,查找kernel_entry是如何得来的?进一步查阅源码,可以发现 3c4bffe45971963326277f98a06882a.png ​ 同时源码中给出了关于load_kernel()函数的定义,这里不再关心,我们可以得到结论:对$ra寄存器的赋值是在bootloader阶段由QEMU辅助完成的!

4. 总结

​ 此篇讨论帖讨论了关于$ra寄存器初值的问题以及bootloader和QEMU的一些思考,如有错误敬请指正,这里特别感谢助教的指引和帮助!

0%