首页 > 解决方案 > 在 mmio 期间检索 QEMU VM 的堆栈跟踪

问题描述

我正在模拟 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 %lx\n", 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);
}

标签: qemukvm

解决方案


不幸的是,QEMU 并没有提供任何真正可以为您做到这一点的 API,您可以从 QEMU C 代码中调用它。有几个问题:

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

  2. QEMU 没有任何代码知道如何进行访客堆栈回溯。这是一件相对复杂的事情要正确完成(当然,您会在调试器中找到它的代码)。

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

也就是说,这里有一些你可以尝试的建议:

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

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

  3. 使用 QEMU 的 -d 和 -D 选项来记录特定于设备的跟踪事件和一般客户 CPU 执行/控制流信息的组合是我发现的另一个技巧,它有助于了解客户对设备所做的事情。


推荐阅读