在 QEMU 中,我应该如何在 TB 链表中找到下一个 TranslationBlock

In QEMU, how am I supposed to find the next TranslationBlock in a TB linked-list

我试图了解在调用函数 cpu_loop_exec_tb() 后执行了哪些来宾指令。

更具体地说,我试图了解 jmp_list_headjmp_list_nextjmp_dest 之间的关系。根据documentation of these fields in the codejmp_list_next[0]jmp_list_next[1]的指针的LSB应该被设置,这将指示执行了哪个分支。但情况并非总是如此。

我也很困惑知道应该使用 jmp_destjmp_list_next 中的哪一个来获取下一个 TranslationBlock 指针值(两者都包含指向实例化 TB 的有效指针)。有时,jmp_destjmp_list_next 都有值,而其他时候 jmp_list_next 是 NULL 但有两个 jmp_dest.

例如:

cpu_loop_exec_tb()
last_tb    address: 
tb_exit:   0

EXEC:----------------
IN: 
0x00007de4:  88 f8                    movb     %bh, %al
0x00007de6:  88 fc                    movb     %bh, %ah
0x00007de8:  e8 e1 ff                 callw    0x7dcc

level:     0
tb         address: 0x00000253aca11cc0, LSB=0
tb.pc      address: 0x0000000000007de4, LSB=0
tb.cflags: 0xff020000.
jmp_target_arg: 0x0000000000000094, 0x0000000000000000.
incoming jumps:
tb->jmp_list_head: 
outgoing jumps:
tb->jmp_list_next[0]: 0x00000253aca11300, LSB=0
tb->jmp_list_next[1]: 
tb->jmp_list_next[0]->pc: 0x0000000000007dd6, LSB=0
tb->jmp_list_next[1]->pc: 
tb->jmp_dest[0]: 0x00000253aca11740, LSB=0
tb->jmp_dest[1]: 
tb->jmp_dest[0]->pc: 0x0000000000007dcc, LSB=0
tb->jmp_dest[1]->pc: 
STATUS: Following child TB 0x00000253aca11740: only jmp_dest[0] available.
Following child TB: : 0x00000253aca11740, LSB=0

EXEC:----------------
IN: 
0x00007dcc:  72 02                    jb       0x7dd0

level:     1
tb         address: 0x00000253aca11740, LSB=0
tb.pc      address: 0x0000000000007dcc, LSB=0
tb.cflags: 0xff020000.
jmp_target_arg: 0x0000000000000048, 0x0000000000000060.
incoming jumps:
tb->jmp_list_head: 0x00000253aca12300, LSB=0
outgoing jumps:
tb->jmp_list_next[0]: 
tb->jmp_list_next[1]: 
tb->jmp_list_next[0]->pc: 
tb->jmp_list_next[1]->pc: 
tb->jmp_dest[0]: 0x00000253aca118c0, LSB=0
tb->jmp_dest[1]: 0x00000253aca11e80, LSB=0
tb->jmp_dest[0]->pc: 0x0000000000007dce, LSB=0
tb->jmp_dest[1]->pc: 0x0000000000007dd0, LSB=0
tb->jmp_dest[0]->jmp_list_head: 0x00000253aca18c00, LSB=0
tb->jmp_dest[1]->jmp_list_head: 0x00000253aca13740, LSB=0
WARNING: Don't know which jmp_dest[] to choose from.


cpu_loop_exec_tb()
last_tb    address: 
tb_exit:   0

EXEC:----------------
IN: 
0x00007de4:  88 f8                    movb     %bh, %al
0x00007de6:  88 fc                    movb     %bh, %ah
0x00007de8:  e8 e1 ff                 callw    0x7dcc

在上面的日志中,tb_find() 返回的 TB 是 0x00000253aca11cc0。链表中的下一个 TB 很明显,因为 jmp_list_next[0] 程序计数器不是 0x7dcc 而 jmp_dest[0] 程序计数器是 0x7dcc。

当查看 TB 地址 0x00000253aca11740 时,我不明白如何 select 哪个 TB 是下一个,因为两个 jmp_dest 都已设置。

看着我不完全理解的other places in the code,我期待评估两个jmp_dest,看看他们的jmp_list_head,看看两者中哪一个设置了LSB到 1. 在上面的日志中,tb->jmp_dest[0]->jmp_list_headtb->jmp_dest[1]->jmp_list_head 都没有设置它们的 LSB,这似乎表明这个 TB 是叶子,但显然不是。明确地说,我有时会看到 tb->jmp_dest[0]->jmp_list_headtb->jmp_dest[1]->jmp_list_head 的 LSB 设置为 1 的实例。

我知道列表中缺少我无法打印的 TB,因为下一个执行的 PC 是 0x7de4(它不是 0x7dd0 或 0x7dce)。

我正在执行的来宾源代码是存储在 MBR 中的 x86 Space Invaders 游戏。

注意:这是我第一次在 Whosebug 上发帖。

注意:我也看了this question,但是好像没有解决我的问题。

TB linking 有点复杂。大多数情况下,您应该能够忽略它。特别是,对于大多数易于阅读的日志,您应该使用“-d nochain”,这将完全禁用 TB linking。如果你不使用 'nochain' 那么你可以在某种程度上从 'exec' 日志记录中找出已执行的 TB,方法是查看它何时表示它是“链接 TB”,但这要痛苦得多。

另请注意,像这样的 TB 链接并不是 cpu_loop_exec_tb() 可能执行多个 TB 的唯一原因——以及这种将 TB 静态链接在一起的“goto_tb”机制,还有“goto_ptr”机制,它执行动态“如果可能的话查找指向下一个 TB 的指针”链接。

如果您还没有阅读 the QEMU developer docs on TCG block chaining,您应该阅读。

回答您的“使用了哪个 jmp_dest?”问题,这取决于结核病内部发生了什么。 TCG TB 最多可以有 2 个出口; 2 exit case 的经典用法是条件分支。生成的代码看起来像“测试条件;如果条件失败,则退出 0;如果条件通过,则退出 1”。 (顺便说一句,没有要求按该顺序使用 0 和 1。)您不能回答“我们走哪个 TB 出口?”只需查看 TB 数据结构,因为答案取决于运行时,并且每次执行 TB 时都可能不同。您可以在示例日志输出中看到这一点:对于 TB 0x00000253aca11740,jmp_dest[0] 指向访客 PC 0x7dce 的 TB,这是“条件失败,执行失败”的情况,并且 jmp_dest [1] 指向来宾 PC 0x7dd0 的 TB,这是“条件通过,采取分支”的情况。

你对jmp_list_head值中LSB的含义也有误解。 LSB 用作跳入此 TB 的 linked 列表的一部分:它告诉我们列表中的下一个元素是在 jmp_list_next[0] 还是 jmp_list_next[1]。您可以使用以下内容遍历 linked 传入跳转列表:

uintptr_t ptr_and_n = this_tb->jmp_list_head;
for (;;) {
    int n = ptr_and_n & 1;
    TranslationBlock *tb = (TranslationBlock *)(ptr_and_n & ~1);
    if (!tb) {
        break; /* end of linked list */
    }
    printf("next TB in incoming list: %p (it gets to us via its exit %d)\n",
           tb, n);
    ptr_and_n = tb->jmp_list_next[n];
}

(在 QEMU 中,这就是 TB_FOR_EACH_JMP 宏的作用;我在这里写了一个普通的等价物,希望更容易理解。)

最后,请注意,TB 之间的这些 link 可以动态创建和断开——它们是一种优化,如果没有预先创建的 link 从一个 TB 到接下来,QEMU 将回到主循环以找到下一个 TB,如果可能的话希望添加 link。有时现有的 links 会被破坏(例如,如果被 linked 的 TB 无效)。当模拟 SMP 来宾时,其中一些可能会在您的线程遍历数据结构时并行发生,这就是为什么有 jmp_lock 以及为什么必须以原子方式对字段进行某些更改的原因。