最近偷时间看了一点QEMU的代码,是和RISVCV指令相关的部分,但是没看完,很多地方也没看明白,不过先记录下来,以后备查吧.
主要涉及QEMU模拟汇编指令的线程.
1. 调试部分的代码
启动的过程中,会先检测入参是否设置了g选项,如果设置了就要打开调试功能,等待来自GDB发起的链接请求,主要是通过socket来建立的链接.依据解析收到的GDB命令字符串来判断是什么样的命令,同时调用特定的debug函数,函数我已经列出如下了.
2. 解析下载的ELF文件
QEMU会将入参导入进来的ELF文件进行解析,以此判断是哪类处理器的指令.同时由ELF的文件格式,解析出其代码段,数据段等的信息,为后来解析汇编指令奠定基础.它采用的是将ELF文件整个映射到当前进程空间的方式,这样对ELF文件的访问,就变成了对地址空间的访问,变的比较的方便.
3. 主体函数运行的入口与出口
QEMU可以模拟多个CPU的运行,因此在真正模拟运行当前ELF的代码时,它需要判断是否有其他的模拟cpu在运行,如果有,那么它就需要停下来等待其他cpu处理完当前的任务后,再继续进行.QEMU并不是一次把一个ELF文件中的代码都执行完了才释放当前的执行权的. 每完成一个TB,QEMU就会调用cpu_exec_end()函数释放一次,以保证其他CPU线程能够及时得到执行.
其他模拟的CPU有时也会发送消息给当前CPU的线程,那么在完成一个TB后,当前线程需要检查是否有挂起的信号需要处理,处理完后发送响应的消息给其他线程.
4. 汇编指令的解析与翻译
tb_find函数是这里的关键,它负责从ELF文件中将汇编指令一个一个解析出来,同时将它转换成一种中间类的指令.其他类型的汇编指令在这里也会被转成这种中间类的指令,这保证了代码的模块化,便于维护. 在解析转换的过程中,涉及到寄存器生命周期的分析,通过分析可以减少产生的中间指令的数量,提高后续指令代码时的效率. 对生命周期的分析代码我没能看懂,只能放到以后了.
TCG解析的几个原则:
(1)一条汇编指令会被解析出一条或多条微指令。
(2)一个TB(translation block)包含多条微指令,且以汇编指令的跳转指令为截至,表示一个TB。
(3)解析出来的每条微指令是插入在tcg_ctx->ops的链表中的;链表中的每项表示一个微指令操作,包含入参,出参以及该微指令的操作码;它的入参出参实例都是从tcg_ctx->temps[]数组中获取的。
下表列出了一些例子,TCG模块将RISCV指令转译成什么样的微指令:
(1)对于addi指令,TCG是如何将该指令转译成微指令的,注意这里所说的微寄存器是笔者自己命名的,为了便于讲述转译的过程. 实际上是TCGTemp{}实例.
(2)对于load指令的转译处理过程
(3)对于add指令的转译处理
(4)对于slli左移指令的转译处理
(5)对于跳转指令jal的处理;对于跳转指令以及下面的返回指令,笔者这里多说几句,这两条指令的转译后,此时is_jmp标志会被置位,这意味着一个TB的结束.后面开始执行这些转译好的微指令了.
(6)对于返回指令ret的处理.根据RISCV官网的手册,ret指令会编译成jalr来处理,rs1寄存器默认是ra寄存器,读者可以仔细看图中的注释.
(7)对于乘指令的转译处理
一些说明:
对机器码(汇编指令)的转译,它的入口函数都是在qemu的编译过程中通过python脚本自动生成的.关于这部分内容,笔者学习后,后续会增加新的篇章来进行讲解.这对于增加qemu支持新的指令来将非常的关键.