qemu在每一个guest指令的中间码实现之间都要插入一条insn_start的中间码:
1 | translator_loop |
插入insn_start的行为靠guest相关的一个回调函数完成,riscv上是riscv_tr_insn_start,
不通构架下基本都是插入一条insn_start指令,只不过insn_start指令带的参数个数有可能
是不一样的。riscv上带来pc和0两个参数。
qemu在后端翻译的时候解析insn_start并把guest pc等信息保存在TCGContext的gen_insn_end_off
和gen_insn_data表项里,其中前者保存的是每条guest指令对应的host指令的地址,后者保
存的是每条guest指令对应的guest pc以及其它信息。
1 | tcg_gen_code |
qemu在翻译完一个tb后,根据如上保存的信息,在生成代码的尾部生成对应的信息:
1 | tb_gen_code |
如上的准备是为了guest产生异常的时候可以精确的找到guest pc,还是用riscv举例,像ecall
这种主动产生的异常,在模拟触发异常时同步更新下guest pc就好:
1 | trans_ecall |
注意qemu在模拟的时候为了性能不会每条guest指令都更新cpu_env的guest cpu,只是在需要
的时候才更新。
但是除了ecall指令触发的U_ECALL这种异常,CPU上其它的异常都是被动产生的,比如下面的
图上,guest insn0/insn1就可能触发各种被动的异常,比如guest insn1是存储指令,那么
在执行它对应的host指令时就可能跑到模拟触发缺页异常,qemu会跳出当前的翻译执行大循环,
跳到异常模拟的地方,异常模拟的地方会把异常上下文报告给软件,其中就包括guest insn1
的pc。被动异常可能发生在各种guest指令上,所以qemu就记录了如上guest pc和host pc的
对照表,被动异常发生时通过host pc反查到guest pc并更新到guest cpu_env上。
1 | pc_0: guest insn0 IR0 host insn0 |
如下是相关的代码逻辑:
1 | cpu_loop_exit_restore(CPUState *cpu, uintptr_t pc) |
注意,load/store操纵的guest va不要和load/store指令的地址搞混,load/store指令本身
的地址还是要靠如上的方式获得。
qemu里host和target的概念比较绕,这里再次明确下,qemu/tcg/README其实有澄清。对于
qemu这个大的范围target指的就是被模拟的CPU构架,host就是qemu进程运行的CPU,但是
TCG里,target表示生成代码的CPU构架,就是qemu里的host。