最近偷时间看了一点QEMU的代码,是和RISVCV指令相关的部分,但是没看完,很多地方也没看明白,不过先记录下来,以后备查吧.

主要涉及QEMU模拟汇编指令的线程.

1. 调试部分的代码

启动的过程中,会先检测入参是否设置了g选项,如果设置了就要打开调试功能,等待来自GDB发起的链接请求,主要是通过socket来建立的链接.依据解析收到的GDB命令字符串来判断是什么样的命令,同时调用特定的debug函数,函数我已经列出如下了.




qemu 调试引导代码 qemu指令_寄存器


2. 解析下载的ELF文件

QEMU会将入参导入进来的ELF文件进行解析,以此判断是哪类处理器的指令.同时由ELF的文件格式,解析出其代码段,数据段等的信息,为后来解析汇编指令奠定基础.它采用的是将ELF文件整个映射到当前进程空间的方式,这样对ELF文件的访问,就变成了对地址空间的访问,变的比较的方便.


qemu 调试引导代码 qemu指令_at指令解析代码_02


3. 主体函数运行的入口与出口

QEMU可以模拟多个CPU的运行,因此在真正模拟运行当前ELF的代码时,它需要判断是否有其他的模拟cpu在运行,如果有,那么它就需要停下来等待其他cpu处理完当前的任务后,再继续进行.QEMU并不是一次把一个ELF文件中的代码都执行完了才释放当前的执行权的. 每完成一个TB,QEMU就会调用cpu_exec_end()函数释放一次,以保证其他CPU线程能够及时得到执行.


qemu 调试引导代码 qemu指令_微指令_03


其他模拟的CPU有时也会发送消息给当前CPU的线程,那么在完成一个TB后,当前线程需要检查是否有挂起的信号需要处理,处理完后发送响应的消息给其他线程.


qemu 调试引导代码 qemu指令_qemu 调试引导代码_04


4. 汇编指令的解析与翻译

tb_find函数是这里的关键,它负责从ELF文件中将汇编指令一个一个解析出来,同时将它转换成一种中间类的指令.其他类型的汇编指令在这里也会被转成这种中间类的指令,这保证了代码的模块化,便于维护. 在解析转换的过程中,涉及到寄存器生命周期的分析,通过分析可以减少产生的中间指令的数量,提高后续指令代码时的效率. 对生命周期的分析代码我没能看懂,只能放到以后了.


qemu 调试引导代码 qemu指令_寄存器_05


TCG解析的几个原则:

(1)一条汇编指令会被解析出一条或多条微指令。

(2)一个TB(translation block)包含多条微指令,且以汇编指令的跳转指令为截至,表示一个TB。

(3)解析出来的每条微指令是插入在tcg_ctx->ops的链表中的;链表中的每项表示一个微指令操作,包含入参,出参以及该微指令的操作码;它的入参出参实例都是从tcg_ctx->temps[]数组中获取的。

下表列出了一些例子,TCG模块将RISCV指令转译成什么样的微指令:


qemu 调试引导代码 qemu指令_寄存器_06


(1)对于addi指令,TCG是如何将该指令转译成微指令的,注意这里所说的微寄存器是笔者自己命名的,为了便于讲述转译的过程. 实际上是TCGTemp{}实例.


qemu 调试引导代码 qemu指令_at指令解析代码_07


(2)对于load指令的转译处理过程


qemu 调试引导代码 qemu指令_汇编指令_08


(3)对于add指令的转译处理


qemu 调试引导代码 qemu指令_寄存器_09


(4)对于slli左移指令的转译处理


qemu 调试引导代码 qemu指令_微指令_10


(5)对于跳转指令jal的处理;对于跳转指令以及下面的返回指令,笔者这里多说几句,这两条指令的转译后,此时is_jmp标志会被置位,这意味着一个TB的结束.后面开始执行这些转译好的微指令了.


qemu 调试引导代码 qemu指令_汇编指令_11


(6)对于返回指令ret的处理.根据RISCV官网的手册,ret指令会编译成jalr来处理,rs1寄存器默认是ra寄存器,读者可以仔细看图中的注释.


qemu 调试引导代码 qemu指令_汇编指令_12


(7)对于乘指令的转译处理


qemu 调试引导代码 qemu指令_寄存器_13


一些说明:

对机器码(汇编指令)的转译,它的入口函数都是在qemu的编译过程中通过python脚本自动生成的.关于这部分内容,笔者学习后,后续会增加新的篇章来进行讲解.这对于增加qemu支持新的指令来将非常的关键.