Qemu 插件函数——如何访问客户内存和寄存器
Qemu plugin functions - how to access guest memory and registers
背景
Qemu version 4.2.0, released Dec '19, included a new functionality for something called TCG Plugins. They have a few examples in the tests/plugins directory, and the API is more or less defined in qemu-plugin.h.
此文件定义了两个枚举类型,qemu_plugin_cb_flags
和 qemu_plugin_mem_rw
,它们被传递到注册回调的函数中。这些枚举似乎表明回调是否将读取或写入 CPU 寄存器或内存。但是,所有示例插件都使用 QEMU_PLUGIN_CB_NO_REGS
,并且只有 2 个插件使用内存访问枚举。 hotpages.c
和 mem.c
使用 QEMU_PLUGIN_MEM_RW
作为注册内存回调的默认值 (qemu_plugin_register_vcpu_mem_cb
)。 mem.c
加载插件时有一个参数来选择它是读还是写,但是,它在回调函数中似乎没有任何区别。
问题
我的问题是,如何从插件回调函数访问来宾内存和寄存器? API 似乎表明这是可能的,因为回调注册要求您说明是否要访问它们,以及它是 RW 还是只读。
有没有使用API这部分的例子?我意识到这是 Qemu 功能的一个非常新的部分。
代码
当你在一条指令上注册回调时,比如insn.c,你可以获得指令的虚拟地址。
uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);
我是运行一个裸机ARM程序,这个虚拟地址好像和ELF文件中的指令地址有关
在内存回调函数中,你可以调用qemu_plugin_get_hwaddr
获取内存访问的硬件地址,但我不确定那个结构到底代表什么。
相关
这个答案已有 7 年历史,建议使用 GDB 接口。我的问题与使用 TCG 插件功能特别相关。
我刚遇到完全相同的问题。似乎 qemu 团队真的试图阻止我们使用 CPU 或插件中的内存内容。我尝试通过修改 Makefile 来包含我需要的 headers,但是那些 headers 不应该包含在插件等外部代码中。我无法编译它。
如您所说,有些标志表明这是可能的。我的猜测是该功能尚未完全实现,也许很快就会实现。
与此同时,在我们等待合适的方法来执行此操作时,以下是我破解它的方法:
正在注册
在我的例子中,CPU 是一个 ARM。我会先展示代码再解释。
void *qemu_get_cpu(int index);
static uint32_t get_cpu_register(unsigned int cpu_index, unsigned int reg) {
uint8_t* cpu = qemu_get_cpu(cpu_index);
return *(uint32_t*)(cpu + 33488 + 5424 + reg * 4);
}
我首先声明 qemu_get_cpu
函数,因为我们不能包含它的 header。那个函数returns一个CPUState*
。由于我的 CPU 是一个 ARM,所以我知道指针实际上是一个 ARMCPU*
。由于在 qemu 中实现了继承,因此从 CPUState*
到 ARMCPU*
的转换是 no-op,因此无事可做。
然后,查看 target/arm/cpu.h
,我们可以看到结构:
struct ARMCPU {
/*< private >*/
CPUState parent_obj;
/*< public >*/
CPUNegativeOffsetState neg;
CPUARMState env;
// ...
我用this compiler trick得到CPUState和CPUNegativeOffsetState的大小,在我的例子中分别是33488和5424。这为我们提供了 CPUARMState
的偏移量,其开始如下:
typedef struct CPUARMState {
/* Regs for current mode. */
uint32_t regs[16];
// ...
所以寄存器只是在开头,这就是为什么我使用 reg * 4
。
现在我们可以读取我们的寄存器,下一步是...
从内存读取
这个比较简单,我是从 qemu 本身的 gdbstub.c
得到的:
void cpu_physical_memory_rw(uint64_t addr, uint8_t *buf,
uint64_t len, int is_write);
// and in my function:
char name[9] = {0};
cpu_physical_memory_rw(name_addr, name, 8, 0);
我们只需声明我们需要的方法并调用它。看起来这个方法永远不会失败,从未映射的内存中读取什么都不做。
我刚刚发现,在用户模式下,您可以取消引用 vaddr
以从内存中获取值。
char *val = (char*) vaddr;
此外,我在cpu.c
中找到了以下方法,当gdbstub不可用时也可以使用。
int cpu_memory_rw_debug(void *cpu, uint64_t addr,
void *ptr, uint64_t len, bool is_write);
尽管如此,这是一种解决方法,我希望 TCG 插件具有更多功能。
背景
Qemu version 4.2.0, released Dec '19, included a new functionality for something called TCG Plugins. They have a few examples in the tests/plugins directory, and the API is more or less defined in qemu-plugin.h.
此文件定义了两个枚举类型,qemu_plugin_cb_flags
和 qemu_plugin_mem_rw
,它们被传递到注册回调的函数中。这些枚举似乎表明回调是否将读取或写入 CPU 寄存器或内存。但是,所有示例插件都使用 QEMU_PLUGIN_CB_NO_REGS
,并且只有 2 个插件使用内存访问枚举。 hotpages.c
和 mem.c
使用 QEMU_PLUGIN_MEM_RW
作为注册内存回调的默认值 (qemu_plugin_register_vcpu_mem_cb
)。 mem.c
加载插件时有一个参数来选择它是读还是写,但是,它在回调函数中似乎没有任何区别。
问题
我的问题是,如何从插件回调函数访问来宾内存和寄存器? API 似乎表明这是可能的,因为回调注册要求您说明是否要访问它们,以及它是 RW 还是只读。
有没有使用API这部分的例子?我意识到这是 Qemu 功能的一个非常新的部分。
代码
当你在一条指令上注册回调时,比如insn.c,你可以获得指令的虚拟地址。
uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);
我是运行一个裸机ARM程序,这个虚拟地址好像和ELF文件中的指令地址有关
在内存回调函数中,你可以调用qemu_plugin_get_hwaddr
获取内存访问的硬件地址,但我不确定那个结构到底代表什么。
相关
这个答案已有 7 年历史,建议使用 GDB 接口。我的问题与使用 TCG 插件功能特别相关。
我刚遇到完全相同的问题。似乎 qemu 团队真的试图阻止我们使用 CPU 或插件中的内存内容。我尝试通过修改 Makefile 来包含我需要的 headers,但是那些 headers 不应该包含在插件等外部代码中。我无法编译它。
如您所说,有些标志表明这是可能的。我的猜测是该功能尚未完全实现,也许很快就会实现。
与此同时,在我们等待合适的方法来执行此操作时,以下是我破解它的方法:
正在注册
在我的例子中,CPU 是一个 ARM。我会先展示代码再解释。
void *qemu_get_cpu(int index);
static uint32_t get_cpu_register(unsigned int cpu_index, unsigned int reg) {
uint8_t* cpu = qemu_get_cpu(cpu_index);
return *(uint32_t*)(cpu + 33488 + 5424 + reg * 4);
}
我首先声明 qemu_get_cpu
函数,因为我们不能包含它的 header。那个函数returns一个CPUState*
。由于我的 CPU 是一个 ARM,所以我知道指针实际上是一个 ARMCPU*
。由于在 qemu 中实现了继承,因此从 CPUState*
到 ARMCPU*
的转换是 no-op,因此无事可做。
然后,查看 target/arm/cpu.h
,我们可以看到结构:
struct ARMCPU {
/*< private >*/
CPUState parent_obj;
/*< public >*/
CPUNegativeOffsetState neg;
CPUARMState env;
// ...
我用this compiler trick得到CPUState和CPUNegativeOffsetState的大小,在我的例子中分别是33488和5424。这为我们提供了 CPUARMState
的偏移量,其开始如下:
typedef struct CPUARMState {
/* Regs for current mode. */
uint32_t regs[16];
// ...
所以寄存器只是在开头,这就是为什么我使用 reg * 4
。
现在我们可以读取我们的寄存器,下一步是...
从内存读取
这个比较简单,我是从 qemu 本身的 gdbstub.c
得到的:
void cpu_physical_memory_rw(uint64_t addr, uint8_t *buf,
uint64_t len, int is_write);
// and in my function:
char name[9] = {0};
cpu_physical_memory_rw(name_addr, name, 8, 0);
我们只需声明我们需要的方法并调用它。看起来这个方法永远不会失败,从未映射的内存中读取什么都不做。
我刚刚发现,在用户模式下,您可以取消引用 vaddr
以从内存中获取值。
char *val = (char*) vaddr;
此外,我在cpu.c
中找到了以下方法,当gdbstub不可用时也可以使用。
int cpu_memory_rw_debug(void *cpu, uint64_t addr,
void *ptr, uint64_t len, bool is_write);
尽管如此,这是一种解决方法,我希望 TCG 插件具有更多功能。