在 mmio 期间检索 QEMU 虚拟机的堆栈跟踪



我正在模拟QEMU中的一些硬件,这些硬件对应于linux guest内核中的一些驱动程序。

现在,我可以使用memory_region_init_io来设置 mmio 区域,以便每当内核驱动程序读取/写入 mmio 地址时,我都会收到回调。

如何在回调中获取触发 mmio 访问的内核的堆栈跟踪?我想知道内核驱动程序中的哪一行触发了哪个 mmio 访问。

我知道mmiotrace可能是一种选择,但该跟踪发生在来宾内核中。无论如何,我可以用 qemu-kvm 实现这一目标。

static uint64_t mmio_read(void *opaque, hwaddr addr,
unsigned size) {
/* Here, I want to get the stacktrace inside VM 
* that caused this mmio read */
printf("mmio_read: %lx[%u] returns %lxn", addr, size, ret);
return 0;
}
static void stream_dma_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size) {
/* Here, I want to get the stacktrace inside VM 
* that caused this mmio write */
printf("mmio_write: %lx[%u]=%lx n", addr, size, val);
}
static const MemoryRegionOps mmio_ops {
.read = mmio_read,
.write = mmio_write,
}
void init_region(uintptr_t addr, size_t size) {
MemoryRegion *subregion = malloc(sizeof(MemoryRegion));
memory_region_init_io(subregion, OBJECT(opaque), 
&mmio_ops, NULL, "mmio-region", size);
memory_region_add_subregion_overlap(get_system_memory(),
addr, subregion, 100);
}

不幸的是,QEMU 没有提供任何真正可以为您执行此操作的 API,您可以从 QEMU C 代码中调用。有几个问题:

  1. QEMU 不会持续更新每条指令的所有 CPU 状态,特别是它不会更新 PC 值,直到绝对必要,因为一直写"在 CPU 状态结构中的 PC 字段加 4"是昂贵的。因此,当前的PC无法真正方便地从设备MMIO读/写功能访问。

  2. QEMU 没有任何知道如何执行来宾堆栈回溯的代码。这是一件相对复杂的事情(当然,您会在调试器中找到它的代码(。

我想如果我为此目的设计一些东西,我可能会尝试为设备提供一种方法来触发来宾停止,以便附加到 QEMU gdbstub 的目标架构 gdb 可以检查寄存器并进行回溯。然后,如果要"打印回溯并继续来宾执行",则可以编写调试器的脚本。

也就是说,您可以尝试以下一些建议:

  1. 如果幸运的话,那么在 QEMU gdbstub 上为设备寄存器的地址设置目标架构 gdb 的观察点,可以让您在访客进行设备访问时在 gdb 中获得控制权,以便您可以进行回溯。我给这个大约 50% 的工作机会,因为我不确定大面积观察点支持会有多强大;此外,您还需要在内核将设备映射到的虚拟地址上设置观察点,这可能很难确定。

  2. 我在编写设备模型方面的经验是,通常只需查看设备驱动程序的源代码即可明显看出它在对设备进行 MMIO 访问时正在执行的操作。您知道写入了哪个寄存器以及使用什么值,这通常足以缩小驱动程序的哪个位进行访问的范围。当然,这确实取决于硬件和驱动程序的复杂性。

  3. 使用 QEMU 的 -d 和 -D 选项来记录特定于设备的跟踪事件和常规来宾 CPU 执行/控制流信息的组合是我发现的另一个技巧,有助于确定来宾对设备执行的操作。

最新更新