开源开发工具技术(OSDT)博客

OSDT = HelloGCC + HelloLLVM

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

2. Block Chaining

我們再回到 cpu_exec (cpu-exec.c) 的內層迴圈。這邊主要看 tb_add_jump。

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); }   // 這邊要注意到,QEMU 利用 TranslatonBlock 指針後兩位必為零的結果 // 做了一些手腳。QEMU 將其末兩位編碼成 0、1 或 2 來指引 block chaing // 的方向。這種技巧在 QEMU 用得非常嫻熟。 next_tb = tcg_qemu_tb_exec(tc_ptr);

我們再來看是怎麼 patch code cache 中分支指令的目標地址。依據是否採用 direct jump,tb_set_jmp_target (exec-all.h) 有不同做法。採用 direct jump 的話,tb_set_jmp_target 會根據 host 呼叫不同的 tb_set_jmp_target1。tb_set_jmp_target1 會用到 TB 的 tb_jmp_offset。如果不採用 direct jump 做 block chaining,tb_set_jmp_target 會直接修改 TB 的 tb_next。

static inline void tb_set_jmp_target(TranslationBlock *tb, int n, unsigned long addr) { unsigned long offset;   // n 可以為 0 或 1,代表分支跳轉的分向 taken 或 not taken。 offset = tb->tb_jmp_offset[n]; // tb 要 patch 的位址相對於 tb->tc_ptr 的偏移量。 tb_set_jmp_target1((unsigned long)(tb->tc_ptr + offset), addr); }

#elif defined(__i386__) || defined(__x86_64__) static inline void tb_set_jmp_target1(unsigned long jmp_addr, unsigned long addr) { /* patch the branch destination */ // jmp 的參數為 jmp 下一條指令與目標地址的偏移量。 *(uint32_t *)jmp_addr = addr - (jmp_addr + 4); /* no need to flush icache explicitly */ }

你會有這個疑問嗎? 在分支跳轉位址被 patch 到分支跳轉指令之前,它是要跳去哪裡? :-)

QEMU – block chaining 可以幫助你比較清楚了解 block chaining 如何運作。