在进行Linux设备驱动的逆向工程时,经常需要一个能够模拟各种操作系统环境的工具。虽然DOSBox-X能够很好地模拟DOS环境,但为了更全面地研究设备驱动的行为,需要一个能够在完整Linux环境下运行的工具。QEMU就是这样一个工具,它不仅维护活跃,而且能够运行从MS-DOS到最新的64位操作系统的各种系统。然而,QEMU的最新版本只能在Linux或至少MinGW上构建,而且使用GDB进行调试并不是首选方式。尽管有些开发者不喜欢Visual Studio,但更倾向于使用图形界面进行调试,如果可能的话。
经过一番努力,找到了一个由一位中国开发者(从包含的readme.txt文件中的中文可以判断)为Visual Studio2008移植的QEMU v0.10.2版本,这个版本在2009年之后就不再维护了。尽管如此,决定尝试让这个版本的QEMU在Windows上运行。
由于Visual Studio2008在Windows 11上不再正常工作,选择在VirtualBox中运行的Windows XP上安装它。安装完成后,安装了DirectX November 2007 SDK(文件名:dxsdk_november2007.exe),并配置Visual Studio以解析相关的头文件和库文件。这可以通过“工具”->“选项”->“项目和解决方案”->“VC++目录”来完成。
接下来,修复了一些硬编码的包含路径以及一个缺失的头文件(opcode-cris.h)。虽然这个文件在WinQEMU的Github仓库中可用,但多年来其内容已经发生了变化。花了很长时间搜索Github的提交历史,找到了一个与WinQEMU兼容的老版本。有了这个,WinQEMU成功地在Visual Studio 2008上编译了。
下一个挑战是找到与这个版本的QEMU兼容的正确ROM BIOS和VGA BIOS。这需要在SeaBIOS的存档中搜索。再次,花了很长时间才找到正确的BIOS,没有它,模拟的计算机将无法启动,只会显示一个黑屏。
接下来,需要设置命令行参数,告诉WinQEMU模拟机器的配置。这可以通过设置“调试”->“命令参数”从WinQEMUTest的属性页来完成:
-net none -cpu coreduo -m 480 -M pc -vga std -hda d:\Images\WINME.img
上述设置模拟了一个Core Duo PC,480MB的RAM,VGA显示,使用WINME.IMG作为硬盘驱动器。
WinQEMU然后会从config-host.h中指定的CONFIG_QEMU_SHAREDIR目录加载ROM BIOS和VGA BIOS:
C++
#define CONFIG_QEMU_SHAREDIR "d:\\Images"
#define HOST_I386 1
#define HOST_LONG_BITS 32
#define CONFIG_WIN32 1
//#define CONFIG_GDBSTUB 1
#define CONFIG_STATIC 1
#define CONFIG_SLIRP 1
#define CONFIG_ADLIB 1
#define QEMU_VERSION "0.10.0"
#define CONFIG_UNAME_RELEASE ""
正确设置后,Visual Studio 2008终于能够调试WinQEMU启动Windows ME了。需要注意的是,应该禁用VirtualBox的鼠标集成,否则鼠标光标会随机移动。这个问题不是VirtualBox或QEMU独有的,而是模拟器内部模拟器场景的常见问题。
在成功设置VS2008项目后,还设法在Visual Studio2012下编译了该项目。在经历了VS2012升级向导后,除了需要在项目属性的VC++目录页面添加DirectX SDK文件外,不需要额外的步骤。在VS2008中,这些设置是从“工具”->“选项”全局设置的。
WinQEMU现在可以很好地启动Windows XP 32位系统:
Ubuntu 10也可以很好地工作:
如果为模拟机器配置了更多的RAM,可以启动更新的32位操作系统。WinQEMU是一个32位应用程序,理论上最多支持4GB的RAM。在Visual Studio 2012下,为了安全起见,应该将模拟RAM配置为3GB或更少。
在过程中,发现WinQEMU总是反汇编一堆指令,然后一次性执行它们,不管操作系统会做什么样的缓存。在cpu-exec.c大约第640行,会发现以下代码:
C++
/*
execute the generated code */
#if defined(__sparc__) && !defined(HOST_SOLARIS)
#undef env
env = cpu_single_env;
#define env cpu_single_env
#endif
#ifdef _MSC_VER
pGenCodeBuffer = &(code_gen_prologue [
0
]);
if
((env-
>
eip ==
0x9016ee30
) || (env-
>
eip ==
0x9117a000
))
{
pGenCodeBuffer = &(code_gen_prologue [
0
]);
}
__asm
{
mov
eax
, tc_ptr
;
mov
nEbpBackup,
ebp
;
mov
ebp
, env
;
call
pGenCodeBuffer
;
mov
ebp
, nEbpBackup
;
mov
next_tb,
eax
;
}
#else
next_tb = tcg_qemu_tb_exec(tc_ptr);
#endif
在这里,生成的代码存储在pGenCodeBuffer中,它实际上是一个指向函数的指针数组,这些函数将执行相关的CPU指令(例如MOV、CMP、JMP等)。使用内联汇编指令执行这些函数,特别是call pGenCodeBuffer这行代码,会迷惑调试器。如果运行OUT DX, AL并追踪QEMU实际上是如何处理这个指令的,会发现堆栈跟踪从op_helper.c中的helper_outb(uint32_t port, uint32_t data)开始,以vl.c中的ioport_write(int index, uint32_t address, uint32_t data)结束,没有记录什么导致了helper_outb调用。然后必须执行QEMU源代码的文本搜索,以意识到这个函数来自translate.c中的gen_helper_outb调用,gen_helper_out_func(文件translate.c第747行),然后可以追溯回函数disas_insn(),也在translate.c中,大部分代码生成是在这里完成的。出于某种原因,代码生成缓冲区只有在使用Visual C++编译时才会执行(参见#ifdef _MSC_VER预处理器)。
必须说,QEMU的结构比DOSBox或其他模拟器要复杂得多。DOSBox通常一次反汇编并执行一个指令。但话又说回来,QEMU是一个更高级的模拟器……
WinQEMU不能在更新版本的Visual Studio上编译。试过了,只是在各种C头文件中遇到了无数的错误消息,没有时间修复这个问题。Visual Studio 2012在Windows 11上运行良好,并且可以与更新版本的Visual Studio同时安装。因此,WinQEMU VS2012设置对来说已经足够好了。