> > translation block 執行完後,它的跳躍目標確定且該跳躍目標也已經 > > 在 code cache 裡,那我們就把這兩個 translation block 串接起來。 > > 這個就叫做 block chaining/linking。 > > > > 这个在qemu是能控制的吗? block chaining/linking 可以打开或者关闭吗? 是可以控制的。:-) QEMU 在生成 TCG IR 的時候,有考慮到 block chaining。所以在之後,一但 確定 tb1 下一個 block,tb2,在 code cache 的位址,QEMU 就會 patch 在 code cache 中的 tb1 其 jmp 指令的 address,這樣 tb1 一執行完就會直接跳 到 tb2 執行。 struct TranslationBlock 裡的欄位 jmp_first 和 jmp_next 就是用來記錄整個 在 code cache 中 block chaining 的情況,這是用來以後做 block unchaining 之用,也就是你所說的關閉。 我之後再來講 block unchaining。:-) > > // 一般是這樣呼叫: tcg_gen_exit_tb((tcg_target_long)tb + tb_num); > > // 注意! 請留意其參數: (tcg_target_long)tb + tb_num。 > > static inline void tcg_gen_exit_tb(tcg_target_long val) > > { > > // 將 INDEX_op_exit_tb 寫入 gen_opc_buf; val 寫入 gen_opparam_buf。 > > tcg_gen_op1i(INDEX_op_exit_tb, val); > > } > > > > 不管是否支持block chaining/linking,这里应该都是这样吧。 不太能這麼說。你看一下 cpu_exec 內層迴圈。 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); tcg_gen_exit_tb 的參數 val,就是 tcg_qemu_tb_exec 的返回值 next_tb。這 val 是給 block chaining 用的,看一下 tb_add_jump。 如果不做 block chaining,那直接返回 QEMU 就好,應該不需要返回什麼參數。 只需在返回之前,更新 env->eip。這樣從 code cache 回到 QEMU 之後,tb_find_fast 就會看 env->eip 是否已被翻譯過。 > > case INDEX_op_exit_tb: > > // QEMU 把跳至 code cache 執行當作是函式呼叫,EAX 存放返回值。 > > // 將 val 寫進 EAX,val 是 (tcg_target_long)tb + tb_num。 > > tcg_out_movi(s, TCG_TYPE_I32, TCG_REG_EAX, args[0]); > > > > 这里相当于生成了这样一条指令 mov eax, #(tcg_target_long)tb + tb_num ? 沒錯。 > > // e9 是 jmp 指令,後面的 operand 為相對偏移量,將會加上 eip。 > > // 底下兩條的總和效果是跳回 code_gen_prologue 中 prologue 以後的位置。 > > tcg_out8(s, 0xe9); /* jmp tb_ret_addr */ > > 这里可以多介绍一下jmp指令,可能跟好理解。jmp指令一共五个字节,第一个字节是opcode, > 应该就是 e9,这里生成一个字节的opcode > > > // tb_ret_addr 在 tcg_target_qemu_prologue 初始成指向 code_gen_prologue > > // 中 prologue 以後的位置。 > > // 生成 host binary 的同時,s->code_ptr 會移向下一個 code buffer 的位址。 > > // 所以要減去 4。 > > tcg_out32(s, tb_ret_addr - s->code_ptr - 4); > > 然后生成跳转地址偏移 tb_ret_addr - s->code_ptr - 4,所以就是这样一条指令 > > jmp (tb_ret_addr - s->code_ptr - 4) > > 这个 - 4 我不是和明白,是因为上边那条 mov eax, #((tcg_target_long)tb + > tb_num),所以 - 4 吗? 說實在話,我對 x86 ISA 不很熟,慚愧。我想是生成 host binary 的同時,s->code_ptr會 移向下一個 code buffer 的位址,所以要減去 4。 > 这里想到于生成了这样的两个指令 > > > mov eax, #(tcg_target_long)tb + tb_num > jmp (tb_ret_addr - s->code_ptr - 4) > > jmp过去以后,eax的值有什么用吗? QEMU 將跳至 code cache 執行視為一個函式呼叫。code cache 是 function body。那 function prologue/epilogue 在哪? 在 code_gen_prologue 裡面, 你可以回去參考一下 QEMU Internal - Tiny Code Generator (TCG) 1/2。 code cache -> code_gen_prologue -> QEMU (tcg_qemu_tb_exec)。我想那邊 就是把 eax 上的內容當作返回值。 > > break; > > } > > > > o tcg_out_movi 將 arg 移至 ret 代表的暫存器。 > > > > 这个有什么用吗? 這你得和上面一起看。:-) 這代表從 code cache 返回 QEMU 的時候,以哪一個 暫存器存放返回值。 > > static inline void tcg_out_movi(TCGContext *s, TCGType type, > > int ret, int32_t arg) > > { > > if (arg == 0) { > > /* xor r0,r0 */ > > tcg_out_modrm(s, 0x01 | (ARITH_XOR << 3), ret, ret); > > } else { > > // move arg 至 ret > > tcg_out8(s, 0xb8 + ret); // 0xb8 為 move,ret 代表目地暫存器。0xb8 + ret 合成一個 > > opcode。 > > 这里是不是 0xb8 | ret 更好理解一些? 我們來看一下 tcg_out8 的 prototype。 static inline void tcg_out8(TCGContext *s, uint8_t v); 應該用 0xb8 + ret。當然以 binary 理解的話,確實是 0xb8 | ret。 > > tcg_out32(s, arg); > > } > > } > > > > -- > Yao Qi <qiyaoltc AT gmail DOT com> > > If two people love each other, there can be no happy end to it. > -- Ernest Hemingway -- Wei-Ren Chen (陳韋任) Computer Systems Lab, Institute of Information Science, Academia Sinica, Taiwan (R.O.C.) Tel:886-2-2788-3799 #1667