x86-qemu-mips做出的修改
tb_find
在翻译TB时采用直接从x86翻译到mips(即X86toMips的方法,x86=>IR1=>IR2=>MIPS),而不经过TCG,
- 保留QEMU的
tb_alloc, - 将QEMU的
gen_intermediate_code和tcg_gen_code替换为target_x86_to_mips_host,
在README里的Compile and Run基础上添加debug支持
# make clean, make distclean
./configure --enable-debug --enable-debug-info --enable-x86tomips --disable-werror --target-list="i386-linux-user"
make
系统调用修改思路
QEMU调用helper的方法
QEMU里有一套通用的在native环境(guest)下调用QEMU(host)函数的机制——tcg_gen_callN。native环境(guest)和QEMU(host)运行环境的诸多不同,最显著的不同是32/64和ABI,由这个函数tcg_gen_callN解决。
QEMU利用tcg_gen_callN调用helper_raise_interrupt函数,来实现跳出cpu_exec,然后到cpu_loop里通过中断处理代码(linux-user/i386/cpu_loop.c: 98~241根据中断号的switch case代码)完成系统调用,helper_raise_interrupt完成的任务如下,
- 保存中断处理代码需要的变量(跟踪helper代码,便可得下面这些变量)
- 中断号
intno, error_code=0,- 是中断还是异常
is_int=1,🤔有必要调研一下在QEMU里(不是X86里)的中断和异常分别是什么意思, - 中断指令的下一条指令地址
exception_next_eip, - CPU可以执行IO
can_do_io=1,
- 中断号
siglongjmp,
x86-qemu-mips与QEMU的不同
native和BT环境切换
QEMU对寄存器的操作全是和CPUState数据结构在打交道,而没有映射寄存器,所以QEMU无需过多考虑native和BT环境切换保存寄存器的事。
x86-qemu-mips将x86的体系结构寄存器和QEMU的一些常用变量(DONE:整理牛根画的寄存器映射图的内容,见寄存器映射总结)映射到了mips的体系结构寄存器中,在native切到BT环境时,就要考虑保存这些映射的寄存器,以便给QEMU的运行腾出空间;在BT切到native时,需要恢复这些寄存器映射。
在native里调用BT环境里的函数
QEMU通过tcg_gen_callN实现。能够解决native和BT环境的32/64和ABI不同的问题。
x86-qemu-mips无。
x86-qemu-mips调用helper的方法
前言:目标是尽可能多的沿用QEMU处理系统调用的方法。所以学习QEMU调用helper函数来保存中断处理代码需要的变量,然后用siglongjmp跳出cpu_exec循环,去中断处理代码里执行。
x86-qemu-mips缺少在native里调用函数的能力,所以有必要实现一个类似QEMU的tcg_gen_callN的功能。
调用BT环境(MIPS)的函数func(arg1, arg2, ...)需要两步,
-
按照MIPS的ABI,在相应的MIPS寄存器和栈里准备好参数arg1, arg2, ...
这一步很麻烦,即我们需要实现一个类似QEMU的
tcg_gen_callN的功能,需要考虑:- MIPS有o32, o64, n64共3种ABI,我们都得考虑实现;
- arg1, arg2, ...需要的寄存器和x86映射的寄存器可能会冲突(例如v0),所以有必要保存那些会冲突的寄存器映射;
- arg1, arg2, ...需要的寄存器和x86-qemu-mips的临时寄存器可能会冲突(例如a0~a3),x86-qemu-mips临时寄存器的算法听说写的还很不完善,那就需要将临时寄存器的代码搞清楚且修补存在问题的地方;
- 最后把arg1, arg2, ...放到MIPS ABI规定的地方,这一步十分简单;
-
jalr func,这一步很简单,append_ir2_opnd1(mips_jalr, func)即可。
综上所述,我选择不考虑参数,即将helper_raise_interrupt需要的参数都去掉形成一个新的函数helper_raise_int。直接在native环境保存这些需要传进去的参数。由此,有了现阶段对系统调用的实现,translate_int。helper_raise_interrupt需要3个参数:
CPUX86State *env,这是个全局变量,- 中断号
int intno,直接在translate_int里存入env即可, - 中断指令的长度
int next_eip_addend,总的来说是需要计算下一条指令的地址,直接在translate_int里存入env即可,
所以QEMU的helper_raise_interrupt里还没实现的加入到helper_raise_int的函数里即可:
- 参数设置,
error_code=0,- 是中断还是异常
is_int=1, - CPU可以执行IO
can_do_io=1,
- siglongjmp。
来源于牛根发在slack聊天信息(2019.12.6),表格使用table generator生成,
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | zero ZERO | at EDX | v0 EAX | v1 ECX |
| 4 | a0 TMP | a1 TMP | a2 TMP | a3 TMP |
| 8 | a4 TMP | a5 TMP | a6 TMP | a7 TMP |
| 12 | t0 TMP | t1 TMP | t2 GuestBase | t3 ForFutureUse |
| 16 | s0 n1 | s1 SS | s2 ENV | s3 EBX |
| 20 | s4 ESP | s5 EBP | s6 ESI | s7 EDI |
| 24 | t8 DBT | t9 DBT | k0 | k1 |
| 28 | gp TOP | sp SP | fp EFLAGS | ra MDA? |
图例说明:
- mips-reg:表示在上下问切换时需要保存和恢复的MIPS寄存器;这部分是QEMU的上下文,需要维护;
- TMP:表示临时寄存器,在翻译的时候可以通过
ra_alloc_itemp()获取; - x86-reg:表示对 x86 寄存器进行的寄存器映射;在上下文切换的时候从
ENV载入、写入ENV中,在翻译时需要访问 x86 这些寄存器时,可以直接访问相应的 MIPS 寄存器; - ENV:表示 ENV 的地址;目前是
lsenv->cpu_state,即CPUX86State; - DBT内部使用:二进制翻译器内部使用,在不同情况下有不同的用途,使用时需要注意;
- 浮点使用;
注:由于在context_switch_bt_to_native时,需要用到从 QEMU 传进来的参数(存放在a0和 a1中)因此在构建这部分代码时不能使用ra_alloc_itemp(),否则会污染掉a0和a1,
load_ireg_from_imm64有可能使用ra_alloc_itemp(),因此不推荐在generate_context_switch_bt_to_native中调用;load_ireg_from_imm32不会使用ra_alloc_itemp(),因此可以调用。
编写上下文切换的时候可以注意一下,不过似乎只是 native_to_bt 的时候没有这些问题。
因为今天张老师修好了涉及unaligned访问的相关代码,包括fpu load/store等问题,可以跑gcc编译的二进制文件了,所以测试了一下系统调用消息队列的事。
消息队列系统调用
(同QEMU的笔记)参考How do I use mqueue in a c program on a Linux based system?
注:编译时记得加上-lrt,表示链接时需要rt这个库。
| client | x86-qemu-mips client | |
|---|---|---|
| server | ✔️ | ❌ |
| x86-qemu-mips server | ✔️ | ❌ |