[hellogcc] [投稿] QEMU Internal - Tiny Code Generator (TCG) 2/2

  • From: 陳韋任 <chenwj@xxxxxxxxxxxxxx>
  • To: hellogcc@xxxxxxxxxxxxx
  • Date: Sat, 1 Oct 2011 16:09:14 +0800

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

1.2 TCG Flow

  介紹完一些資料結構之後,我開始介紹 cpu_exec 的流程。底下複習一下
process mode 的流程:

  cpu_loop (linux-user/main.c) -> cpu_x86_exec/cpu_exec (cpu-exec.c)

cpu_exec 有兩層 for 迴圈。我們先看內層:

  next_tb = 0; /* force lookup of first TB */
  for(;;) {
 
      tb = tb_find_fast();

      tc_ptr = tb->tc_ptr;

      next_tb = tcg_qemu_tb_exec(tc_ptr);
  }

tb_find_fast 會先試圖查看目前 pc (guest virtual address) 以否已有翻譯過
的 host binary 存放在 code cache。

  // pc = eip + cs_base
  cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags);

  // CPUState 中的 tb_jmp_cache 即是做此用途。
  tb = env->tb_jmp_cache[tb_jmp_cache_hash_func(pc)];

  // 檢查該 tb 是否合法。這是因為不同的 eip + cs_base 可能會得到相同的 pc。
  if (unlikely(!tb || tb->pc != pc || tb->cs_base != cs_base ||
               tb->flags != flags)) {
        tb = tb_find_slow(pc, cs_base, flags);
  }

  return tb; // code cache 已有翻譯過的 host binary,返回 TranslationBlock。

tb_find_slow 以 pc 對映的物理位址 (guest physcal address) 查找 TB。如果成功,
則將該 TB 寫入 env->tb_jmp_cache; 若否,則進行翻譯。 

  phys_pc = get_page_addr_code(env, pc);
  phys_page1 = phys_pc & TARGET_PAGE_MASK;

  // 除了 env->tb_jmp_cache 這個以 guest virtual address 為索引的緩存之外,
  // QEMU 還維護了一個 tb_phys_hash,這個是以 guest physical address 為索引。
  h = tb_phys_hash_func(phys_pc);
  ptb1 = &tb_phys_hash[h];
  for (;;) {

    not_found:

    found:

    // TranslationBlock 中的 phys_hash_next 用在這裡。
    // 如果 phys_pc 索引到同一個 tb_phys_hash 欄位,用 phys_hash_next 串接起來。
    ptb1 = &tb->phys_hash_next;
  }

  not_found:
    tb = tb_gen_code(env, pc, cs_base, flags, 0);

  found:
    env->tb_jmp_cache[tb_jmp_cache_hash_func(pc)] = tb;
    return tb;

這裡小結一下 cpu_exec (cpu-exec.c) -> tb_find_fast (cpu-exec.c)
               -> tb_find_slow (cpu-exec.c)

QEMU 先以 guest virtual address (GVA) 查找是否已有翻譯過的 TB,再以 guest physical
address (GPA) 查找是否已有翻譯過的 TB。

如果沒有翻譯過的 TB,開始進行 guest binary -> TCG IR -> host binary 的翻譯。
大致流程如下:

  tb_gen_code (exec.c) -> cpu_gen_code (translate-all.c)
    -> gen_intermediate_code (target-i386/translate.c)
      -> tcg_gen_code (tcg/tcg.c) -> tcg_gen_code_common (tcg/tcg.c)

  - tb_gen_code 配置內存給 TB (TranslationBlock),再交由 cpu_gen_code。

  // 注意! 這裡會將 GVA 轉成 GPA。phys_pc 將交給之後的 tb_link_page 使用。
  phys_pc = get_page_addr_code(env, pc);
  tb = tb_alloc(pc);
  if (!tb) {
    // 清空 code cache
  }

  // 初始 tb
 
  // 開始 guest binary -> TCG IR -> host binary 的翻譯。
  cpu_gen_code(env, tb, &code_gen_size);

  // 將 tb 加入 tb_phys_hash 和二級頁表 l1_map。
  // phys_pc 和 phys_page2 分別代表 tb (guest pc) 對映的 GPA 和所屬的第二個
  // 頁面 (如果 tb 代表的 guest binary 跨頁面的話)。
  tb_link_page(tb, phys_pc, phys_page2);
  return tb;

我底下分別針對 cpu_gen_code 和 tb_link_page 稍微深入的介紹一下。

  - cpu_gen_code 負責 guest binary -> TCG IR -> host binary 的翻譯。

  // 初始 TCGContext 的 gen_opc_ptr 和 gen_opparam_ptr,使其分別指向
  // gen_opc_buf 和 gen_opparam_buf。gen_opc_buf 和 gen_opparam_buf
  // 分別存放 TCGOpcode 和 operand。
  tcg_func_start(s);

  // 呼叫 gen_intermediate_code_internal 產生 TCG IR
  gen_intermediate_code(env, tb);

  // TCG IR -> host binary  
  gen_code_size = tcg_gen_code(s, gen_code_buf);

  o gen_intermediate_code_internal (target-*/translate.c) 初始化並呼叫
    disas_insn 反組譯 guest binary 成 TCG IR。disas_insn 呼叫 tcg_gen_xxx
    (tcg/tcg-op.h) 產生 TCG IR。分別將 opcode 寫入 gen_opc_ptr 指向的緩衝
    區 (translate-all.c 裡的 gen_opc_buf); operand 寫入 gen_opparam_ptr
    指向的緩衝區 (translate-all.c 裡的 gen_opparam_buf)。

  o tcg_gen_code (tcg/tcg.c) 呼叫 tcg_gen_code_common (tcg/tcg.c) 將 TCG
    IR 轉成 host binary。

  tcg_reg_alloc_start(s);
 
  s->code_buf = gen_code_buf;
  // host binary 會寫入 TCGContext s 的 code_ptr 所指向的緩衝區。
  s->code_ptr = gen_code_buf;

至此,guest binary -> TCG IR -> host binary 算是完成了。剩下把 TranslationBlock
(TB) 納入 QEMU 的管理,這是 tb_link_page 做的事。

  - tb_link_page (exec.c) 把新的 TB 加進 tb_phys_hash 和 l1_map 二級頁表。
    tb_find_slow 會用 pc 對映的 GPA 的哈希值索引 tb_phys_hash。

  /* 把新的 TB 加進 tb_phys_hash */
  h = tb_phys_hash_func(phys_pc);
  ptb = &tb_phys_hash[h];
  tb->phys_hash_next = *ptb; // 如果兩個以上的 TB 其 phys_pc 的哈希值相同,則做 chaining。
  *ptb = tb; // 新加入的 TB 放至 chaining 的開頭。

  // 在 l1_map 中配置 PageDesc 給 TB,並設置 TB 的 page_addr 和 page_next。
  tb_alloc_page(tb, 0, phys_pc & TARGET_PAGE_MASK);
  if (phys_page2 != -1) // TB 對應的 guest binary 跨頁
      tb_alloc_page(tb, 1, phys_page2);
  else
      tb->page_addr[1] = -1;

  // 以下和 block chaining 有關,留待下次再講,這邊暫且不提。
  tb->jmp_first = (TranslationBlock *)((long)tb | 2);

  o tb_alloc_page (exec.c) 設置 TB 的 page_addr 和 page_next,並在 l1_map 中配置 PageDesc
    給 TB。

static inline void tb_alloc_page(TranslationBlock *tb,
                                 unsigned int n, tb_page_addr_t page_addr)
{
  // 代表 tb (guest binary) 所屬 guest page。
  tb->page_addr[n] = page_addr;
  // 在 l1_map 中配置一個 PageDesc,返回該 PageDesc。
  p = page_find_alloc(page_addr >> TARGET_PAGE_BITS, 1);
  // 將該頁面目前第一個 TB 串接到此 TB。將來有需要將某頁面所屬所有 TB 清空。
  tb->page_next[n] = p->first_tb;
  // n 為 1 代表 tb 對應的 guest binary 跨 page。
  p->first_tb = (TranslationBlock *)((long)tb | n);
  // PageDesc 會維護一個 bitmap,這是給 SMC 之用。這裡不提。
  invalidate_page_bitmap(p);
}

這裡先回顧一下,QEMU 查看當前 env->pc 是否已翻譯過。若否,則進行
翻譯。

    tb_find_fast (cpu-exec.c) -> tb_find_slow (cpu-exec.c) -> tb_gen_code 
(exec.c)

tb_gen_code 講到這裡,guest binary -> host binary 已翻譯完成,相關資料結構也已設置完畢。
返回 TB (TranslationBlock *) 給 tb_find_fast。

  tb = tb_find_fast();

  tc_ptr = tb->tc_ptr; // tc_ptr 指向 code cache (host binary)

  next_tb = tcg_qemu_tb_exec(tc_ptr);

很好,我們準備從 QEMU 跳入 code cache 開始執行了。:-) tcg_qemu_tb_exec 被定義在 tcg/tcg.h。

  #define tcg_qemu_tb_exec(tb_ptr) ((long REGPARM (*)(void 
*))code_gen_prologue)(tb_ptr)

(long REGPARM (*)(void *)) 將 code_gen_prologue 轉型成函式指針,void * 為該函式的參數,
返回值為 long。REGPARM 指示 GCC 此函式透過暫存器而非棧傳遞參數。至此,
(long REGPARM (*)(void *)) 將數組指針 code_gen_prologue 轉型成函式指針。tb_ptr 為該函式
指針的參數。綜合以上所述,code_gen_prologue 被視為一函式,其參數為 tb_ptr,返回當前 TB
(tc_ptr 代表的 TB,等講到 block chaining 會比較清楚)。code_gen_prologue 所做的事為一般
函式呼叫前的 prologue,之後將控制交由 tc_ptr 指向的 host binary 並開始執行。

-- 
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 - Tiny Code Generator (TCG) 2/2 - 陳韋任