[hellogcc] [投稿] QEMU Internal - Block Chaining 3/3

  • From: 陳韋任 <chenwj@xxxxxxxxxxxxxx>
  • To: hellogcc@xxxxxxxxxxxxx
  • Date: Sat, 1 Oct 2011 19:13:17 +0800

           Copyright (c) 2011 陳韋任 (Chen Wei-Ren)

2. Block Chaining

  由 guest binary -> TCG IR 的過程中,gen_goto_tb 會做 block chaining 的準備。
我們先來看何時會呼叫到 gen_goto_tb。以 i386 為例,遇到 guest binary 中的條件
分支和直接跳轉都會呼叫 gen_goto_tb (target-i386/translate.c)。這邊以條件分支
當例子:

static inline void gen_jcc(DisasContext *s, int b,
                           target_ulong val, target_ulong next_eip)
{
    int l1, l2, cc_op;

    cc_op = s->cc_op;
    gen_update_cc_op(s);
    if (s->jmp_opt) { // use direct block chaining for direct jumps
        l1 = gen_new_label();
        gen_jcc1(s, cc_op, b, l1);

        gen_goto_tb(s, 0, next_eip); // 我猜是 taken
  
        gen_set_label(l1);
        gen_goto_tb(s, 1, val); // 我猜是 not taken
        s->is_jmp = DISAS_TB_JUMP;
    } else {

      /* 忽略不提 */

    }
}

  - gen_goto_tb。強烈建議閱讀 Porting QEMU to Plan 9: QEMU Internals and Port Strategy
    2.2.3 和 2.2.4 節,也別忘了 http://qemu.sourcearchive.com 。

// tb_num 代表目前 tb block linking 分支情況。eip 代表跳轉目標。
static inline void gen_goto_tb(DisasContext *s, int tb_num, target_ulong eip)
{
    TranslationBlock *tb;
    target_ulong pc;
 
    // s->pc 代表翻譯至目前 guest binary 的所在位址。tb->pc 表示 guest binary 的起始位址。
    // 注意! 這裡 s->cs_base + eip 代表跳轉位址; s->pc 代表目前翻譯到的 guest pc。
    pc = s->cs_base + eip; // 計算跳轉目標的 pc
    tb = s->tb; // 目前 tb
    // http://lists.nongnu.org/archive/html/qemu-devel/2011-08/msg02249.html
    // http://lists.gnu.org/archive/html/qemu-devel/2011-09/msg03065.html
    // 滿足底下兩個條件之一,則可以做 direct block linking
    // 第一,跳轉目標和目前 tb 起始的 pc 同屬一個 guest page。
    // 第二,跳轉目標和目前翻譯到的 pc 同屬一個 guest page。
    if ((pc & TARGET_PAGE_MASK) == (tb->pc & TARGET_PAGE_MASK) ||
        (pc & TARGET_PAGE_MASK) == ((s->pc - 1) & TARGET_PAGE_MASK))  {
        // 如果 guest jump 指令和其跳轉位址同屬一個 guest page,則做 direct block linking。

        tcg_gen_goto_tb(tb_num); // 生成準備做 block linking 的 TCG IR。詳情請見之後描述。

        // 更新 env 的 eip,使其指向此 tb 之後欲執行指令的位址。
        // tb_find_fast 會用 eip 查找該 TB 是否已被翻譯過。
        gen_jmp_im(eip);

        // 最終回到 QEMU tcg_qemu_tb_exec,賦值給 next_tb。
        // 注意! tb_num 會被 next_tb & 3 取出,由此可以得知 block chaining 的方向。
        tcg_gen_exit_tb((tcg_target_long)tb + tb_num);
    } else {
        /* jump to another page: currently not optimized */
        gen_jmp_im(eip);
        gen_eob(s);
    }
}

  o tcg_gen_goto_tb 生成 TCG IR。

static inline void tcg_gen_goto_tb(int idx)
{
    tcg_gen_op1i(INDEX_op_goto_tb, idx);
}
  o tcg_out_op (tcg/i386/tcg-target.c) 將 TCG IR 翻成 host binary。
    注意! 這邊利用 patch jmp 跳轉位址達成 block linking。

static inline void tcg_out_op(TCGContext *s, TCGOpcode opc,
                              const TCGArg *args, const int *const_args)
{
    case INDEX_op_goto_tb:
        if (s->tb_jmp_offset) {
            /* direct jump method */
            tcg_out8(s, OPC_JMP_long); /* jmp im */
            // 紀錄將來要 patch 的地方。
            s->tb_jmp_offset[args[0]] = s->code_ptr - s->code_buf;
            // jmp 的參數為 jmp 下一個指令與目標的偏移量。
            // 如果還沒做 block chaining,則 jmp 0 代表 fall through。
            tcg_out32(s, 0);
        } else {

            /* 在此忽略 */

        }
        // 留待將來 "reset" direct jump 之用。
        s->tb_next_offset[args[0]] = s->code_ptr - s->code_buf;
        break;
}

  回答上一篇最後留下的問題。在還未 patch code cache 中的分支跳轉指令的跳轉位址,
它會 fall through,還記得 jmp 0 嗎? 我這邊在列出 gen_goto_tb 的部分內容:

        tcg_gen_goto_tb(tb_num);

        // Fall through

        // 更新 env 的 eip,使其指向此 tb 之後欲執行指令的位址。
        // tb_find_fast 會用 eip 查找該 TB 是否已被翻譯過。
        gen_jmp_im(eip);

        // 最終回到 QEMU tcg_qemu_tb_exec,賦值給 next_tb。
        // 注意! tb_num 會被 next_tb & 3 取出,由此可以得知 block chaining 的方向。
        tcg_gen_exit_tb((tcg_target_long)tb + tb_num);

目前執行的 tb 會賦值給 next_tb (末兩位編碼 block chaining 的方向)。等待下一次迴圈,
tb_find_fast 回傳 next_tb 的下一個 tb。

  if (next_tb != 0 && tb->page_addr[1] == -1) {
      // 這邊利用 TranlationBlock 指針的最低有效位後兩位指引 block chaining 的方向。
      // next_tb -> tb
      tb_add_jump((TranslationBlock *)(next_tb & ~3), next_tb & 3, tb);
  }

That's it! That's how direct block chaining is done in QEMU, I think... :-)
 

-- 
Wei-Ren Chen (陳韋任)
Computer Systems Lab, Institute of Information Science,
Academia Sinica, Taiwan (R.O.C.)
Tel:886-2-2788-3799 #1667

Other related posts:

  • » [hellogcc] [投稿] QEMU Internal - Block Chaining 3/3 - 陳韋任