最后更新2021/12/26
这几天研究qemu linux-user有关的问题,此地立个桩,把我认为值得记录的研究结果记录在此。qemu整体的编译不细说了,只针对我需要的ppc64相关关键记录,很大众、特别是没遇到问题的过程就不记录了,也许以后整理全本说明的时候补充一下。另外由于我的最终目标是搞aix-user,与aix-user相关的内容也许会在这里有关联,但全部的内容应在另一个专题中,此处都是linux-user的东西。需要继续分析的标记为黑体,随时更新。
前置
通过configure定义目标,我用的是:
./configure --target-list="ppc64-softmmu ppc64-linux-user" --disable-curses --disable-kvm --disable-sdl
除了disable-curses是为了填wsl上libtinfow.so.6 missing的老坑,别的随意。
然后make,如果一切正常,会搞出两个可执行文件,一个用于虚拟ppc64 machine,另一个可作为ppc64 linux elf程序的加载器:
ppc64-softmmu/qemu-system-ppc64
ppc64-linux-user/qemu-ppc64
本系列只研究linux-user。
顺序结构
主代码在linux-user/main.c
从main()开始,都是各种初始化,涉及到的时候会更新本文,不需要仔细研究,则先承认,需要的时候再分析。我喜欢直达关键点,其它的东西遇到再说。虽然这种方法不太适合一层层抽丝剥茧,特别是没有任何概念的时候,一下子跳得太远,思维会乱,也不适合学习。但对我现在的研究来说,比较节省时间,而且如果走太多岔路,最后都不知道转到哪里去了,把主线荒废了。以走迷宫比喻,一种方法是从入口遍历每个岔路,这种适合对迷宫有透彻全面的了解;而我喜欢先到出口,反向走逆推,先通过再说,走迷宫不是我的目的,通过迷宫才是。所以,我们就直接翻到main.c的最后几行:
cpu_loop(env);
cpu_loop里面是个死循环
到了cpu_loop,则所有初始化等准备工作都已经完成,包括待加载执行文件格式审核、加载到内存等等,马上需要做的只是解码执行了。
在很多文件中都有cpu_loop调用,真正被使用的是对应目标cpu架构下的cpu_loop,我这里用的是linux-user/ppc/cpu_loop.c
cpu_loop()看起来也很简单,进来就是个for死循环,在里面完成这么几个操作:
for (;;) {
cpu_exec_start(cs);
trapnr = cpu_exec(cs);
cpu_exec_end(cs);
process_queued_cpu_work(cs);
...
switch (trapnr) {
case POWERPC_EXCP_NONE:
...
}
process_pending_signals(env);
}
这里有个背景知识,qemu执行目标代码是以jump block为单位整体处理之后再执行,每个jump block(程序中名称直译是翻译块,我觉得不形象贴切,所以自己起了个名字,也许以后会改更好的名字)是一组将要被连续执行的代码,直到需要跳转到其它地址或者发生意外中断等等情况发生,才结束一块,也许块太大也是一个分割条件,待确认。
那么,这个程序结构就很清晰了,代码从cs指定位置开始做准备,(解码、生成tcg代码等等,在cpu_exec里完成)解码顺序执行,直到本块执行完或者发生意外(此意外包含所有意外还是只包含guest代码意外待确认,目前认为包含所有意外),返回后,进行块后处理,此块将被保留,下次再跑到这个地址,就无需解码,可以直接执行,就更快了。这个过程类似java的JIT解释执行过程。
这里有个潜在的情况,就是代码块的连续性,会存在代码块解码时是连续的,运行时会提前中断跳出,或者虽然是跳转,本应两个代码块,但前后两个代码块其实可以合并到一起。这些问题qemu都有考虑,后续深入分析的时候能解析具体相应处理过程逻辑。
代码块预处理(cpu_exec_start),主操作(cpu_exec),后处理(cpu_exec_end),再加上一个排队中的cpu任务处理(process_queued_cpu_work)都完成后就是意外处理,一个switch,case一堆的意外类型。这里也有我要关注的syscall问题。
case POWERPC_EXCP_SYSCALL_USER:
/* system call in user-mode emulation */
/* WARNING:
* PPC ABI uses overflow flag in cr0 to signal an error
* in syscalls.
*/
env->crf[0] &= ~0x1;
env->nip += 4;
ret = do_syscall(env, env->gpr[0], env->gpr[3], env->gpr[4],
env->gpr[5], env