欢迎光临散文网 会员登陆 & 注册

QEMU 用户模式(User Mode)流程及源码分析 (一)

2022-02-17 12:02 作者:范文捷  | 我要投稿

用户模式加载流程

用户模式(User Mode)下的QEMU可以看作是其它架构指令代码的即时编译(JIT)执行器,客端(guest)代码通过系统调用(syscall)的方式使用主端(host)的内核,并和HOST端使用相同的资源。和系统模式(System Mode)相比,该方式运行效率更高。目前用户模式支持Linux和BSD系统。

注:以下文档中用{GUEST_ARCH}表示GUEST端的CPU架构名称字符串,比如GUEST端为x86_64架构时,qemu-{GUEST_ARCH}表示qemu-x86_64。

以Linux User Mode为例,加载部分的代码在linux-user文件夹内。编译完成后,会生成qemu-{GUEST_ARCH}的可执行文件。

打开linux-user目录下的main.c文件,main函数为应用入口。会进行一系列初始化操作。比如配置GUEST端应用的ROOT目录(可通过命令行参数配置-L guest_root)。并初始化全局变量、系统调用和寄存器存储。

该函数内有特别关注3个函数:cpu_create,loader_exec,cpu_loop

cpu_create

cpu_create 是创建CPUState对象的操作, 该对象用于描述CPU架构的相关数据, 包括寄存器数据、CPU特性等。该对象的成员变量env_ptr表示GUEST端CPU架构相关数据,其类型为CPU{GUEST_ARCH}State的指针。在结构体定义中可看到env_ptr类型是CPUArchState,其定义是:

typedef CPU{GUEST_ARCH}Stat

e CPUArchState

CPU{GUEST_ARCH}State是一个结构体,主要存储GUEST端的寄存器数据。GUEST端寄存器对应为HOST端的内存,换句话说,读写寄存器的操作会被翻译为特定内存区域的读写。

在执行翻译后的代码时, 会将CPUState对象的成员变量env_ptr(类型CPU{GUEST_ARCH}State)存入栈帧寄存器,然后对GUEST端的寄存器操作就可翻译为针对栈帧寄存器间接寻址操作。

例如GUEST端为x86_64, HOST端为ARM64, 此时x86_64的代码

翻译到ARM64位时为

Guest端为x86_64时, env_ptr的类型是CPUX86State指针, 打开target/i386/cpu.h文件,可以看到R_ECX的值为1,该值也是x86_64平台上RCX(ECX)寄存器的指令ID。 查看CPUX86State结构体,regs字段即为存储寄存器值的位置。 RCX寄存器存储在env_ptr->regs[1], 该位置相对CPUX86State的偏移是(size_t)&(env_ptr->regs[1]) - (size_t)env_ptr = 8字节。在ARM64平台上,env_ptr存储于栈帧寄存器x29内,因此针对rcx寄存器的操作可转化为针对[x29, #8]的间接寻址操作。 上述x0寄存器在实际翻译过程中可为任意空闲可变寄存器。

loader_exec

loader_exe 是加载可执行文件的的函数, 其实现位于linux-user/linuxload.c文件内, 可加载ELF格式和FLAT格式的可执行文件。

以加载ELF格式为例, load_elf_binary实现位于linux-user/elfload.c文件内, 在首次加载可执行文件时,需要使用QEMU的加载器。

该可执行文件加载动态库,则是使用/lib/ld.**.so的GUEST端代码可执行库。 因此需要确保/lib/ld.**.so之类的文件在GUEST端运行的根目录内。

GUEST端运行的根目录可通过命令行参数-L guest_root指定。该目录内存储GUEST端运行的环境。比如/lib/ld.**.so所在位置就是guest_root/lib/ld.**.so

需要特别说明的是,在linux环境下,GUEST端所需的各种动态库(GUEST架构)都存放于guest_root/lib/{GUEST_ARCH}-linux-gnu/

cpu_loop

cpu_loop函数负责动态翻译执行的过程,为QEMU的核心部分, 其函数原型为

参数即为CPUState的env_ptr, 其过程分析将在下章讲解。

QEMU 用户模式(User Mode)流程及源码分析 (一)的评论 (共 条)

分享到微博请遵守国家法律