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 | ✔️ | ❌ |