一.qemu简介
qemu是使用动态二进制翻译的cpu模拟器,它支持两种运行模式:全系统模拟和用户态模拟。在全系统模拟下,qemu可以模拟处理器和各种外设,可以运行操作系统。用户态可以运行为另外一种cpu编译的进程,前提是两者运行的os要一致。qemu使用了动态二进制翻译将target instruction翻译成host instruction,完成这个工作的是tcg模块。为了移植性和通用性方面的考虑,qemu定义了mirco-op,本文也称为中间代码,首先qemu会将targetinstruction翻译成mirco-op,然后tcg将mirco-op翻译成host instruction。
Qemu代码翻译流程:target instruction ->micro-op->host instruction
2.qemu代码执行流程:
1. 这部分主要是创建了一个为执行tcg翻译和执行的线程,它的函数是qemu_tcg_cpu_thread_fn,这个函数会调用tcg_exec_all,最后cpu_exec.
main
cpu_init
qemu_init_vcpu
qemu_tcg_init_vcpu
qemu_tcg_cpu_thread_fn
2.执行主函数(cpu_exec)
主要是处理中断异常, 找到代码翻译块,然后执行.
- for(;;) {
- process interruptrequest;
-
- tb_find_fast();
-
- tcg_qemu_tb_exec(tc_ptr);
- }
qemu会将翻译好到代码块暂存起来,因此首先会去查看该pc对应的代码是否已经翻译,如果已经存在直接返回,否则就进入tb_find_slow,进行翻译。
- 139 static inline TranslationBlock *tb_find_fast(CPUArchState *env)
- 140 {
- 141 TranslationBlock *tb;
- 142 target_ulong cs_base, pc;
- 143 int flags;
- 144
- 145
-
-
- 148 cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags);
- 149 tb = env->tb_jmp_cache[tb_jmp_cache_hash_func(pc)];
- 150 if (unlikely(!tb || tb->pc != pc || tb->cs_base != cs_base ||
- 151 tb->flags != flags)) {
- 152 tb = tb_find_slow(env, pc, cs_base, flags);
- 153 }
- 154 return tb;
- 155 }
进入tb_find_slow后会调用tb_gen_code(exec.c),首先分配TranslationBlock描述符,如果TB块满了,则会刷新所有TB块,在这里会看到code cache的起始地址是code_gen_buffer,然后再进行分配,分配之后就要填充信息,主要有所翻译的指令虚拟地址pc,该tb所对应的翻译成的代码块起始地址code_gen_ptr,客户机系统状态flags。保存完基本信息之后,就会调用cpu_gen_code(translate-all.c),这个函数完成代码翻译工作。qemu将翻译好的代码存在一个缓冲区里面。cpu_gen_code这个函数完成之后,需要更新code_gen_ptr,这样就可以翻译接下来的指令。
- 1029 TranslationBlock *tb_gen_code(CPUArchState *env,
- 1030 target_ulong pc, target_ulong cs_base,
- 1031 int flags, int cflags)
- 1032 {
- 1033 TranslationBlock *tb;
- 1034 uint8_t *tc_ptr;
- 1035 tb_page_addr_t phys_pc, phys_page2;
- 1036 target_ulong virt_page2;
- 1037 int code_gen_size;
- 1038
- 1039 phys_pc = get_page_addr_code(env, pc);
- 1040 tb = tb_alloc(pc);
- 1041 if (!tb) {
- 1042
- 1043 tb_flush(env);
- 1044
- 1045 tb = tb_alloc(pc);
- 1046
- 1047 tb_invalidated_flag = 1;
- 1048 }
- 1049 tc_ptr = code_gen_ptr;
- 1050 tb->tc_ptr = tc_ptr;
- 1051 tb->cs_base = cs_base;
- 1052 tb->flags = flags;
- 1053 tb->cflags = cflags;
- 1054 cpu_gen_code(env, tb, &code_gen_size);
- 1055 code_gen_ptr = (void *)(((uintptr_t)code_gen_ptr + code_gen_size +
- 1056 CODE_GEN_ALIGN - 1) & ~(CODE_GEN_ALIGN - 1));
- 1057
- 1058
- 1059 virt_page2 = (pc + tb->size - 1) & TARGET_PAGE_MASK;
- 1060 phys_page2 = -1;
- 1061 if ((pc & TARGET_PAGE_MASK) != virt_page2) {
- 1062 phys_page2 = get_page_addr_code(env, virt_page2);
- 1063 }
- 1064 tb_link_page(tb, phys_pc, phys_page2);
- 1065 return tb;
- 1066 }
在cpu_gen_code里面首先是将targetinstruction翻译成micro-op,然后将mirco-op翻译成host机器码。
- 54 int cpu_gen_code(CPUArchState *env, TranslationBlock *tb, int *gen_code_size_ptr)
- 55 {
- 56 TCGContext *s = &tcg_ctx;
- 57 uint8_t *gen_code_buf;
- 58 int gen_code_size;
- 59 #ifdef CONFIG_PROFILER
- 60 int64_t ti;
- 61 #endif
- 62
- 63 #ifdef CONFIG_PROFILER
- 64 s->tb_count1++;
-
- 66 ti = profile_getclock();
- 67 #endif
- 68 tcg_func_start(s);
- 69
- 70 gen_intermediate_code(env, tb);
- 71
- 72
- 73 gen_code_buf = tb->tc_ptr;
- 74 tb->tb_next_offset[0] = 0xffff;
- 75 tb->tb_next_offset[1] = 0xffff;
- 76 s->tb_next_offset = tb->tb_next_offset;
- 77 #ifdef USE_DIRECT_JUMP
- 78 s->tb_jmp_offset = tb->tb_jmp_offset;
- 79 s->tb_next = NULL;
- 80 #else
- 81 s->tb_jmp_offset = NULL;
- 82 s->tb_next = tb->tb_next;
- 83 #endif
- 84
- 85 #ifdef CONFIG_PROFILER
- 86 s->tb_count++;
- 87 s->interm_time += profile_getclock() - ti;
- 88 s->code_time -= profile_getclock();
- 89 #endif
- 90 gen_code_size = tcg_gen_code(s, gen_code_buf);
- 91 *gen_code_size_ptr = gen_code_size;
- 92 #ifdef CONFIG_PROFILER
- 93 s->code_time += profile_getclock();
- 94 s->code_in_len += tb->size;
- 95 s->code_out_len += gen_code_size;
- 96 #endif
- 97
- 98 #ifdef DEBUG_DISAS
- 99 if (qemu_loglevel_mask(CPU_LOG_TB_OUT_ASM)) {
- 100 qemu_log("OUT: [size=%d]\n", *gen_code_size_ptr);
- 101 log_disas(tb->tc_ptr, *gen_code_size_ptr);
- 102 qemu_log("\n");
- 103 qemu_log_flush();
- 104 }
- 105 #endif
- 106 return 0;
- 107 }
qemu将target翻译成中间码时,将操作码和操作数分开存储,分别存在gen_opc_buf和gen_opparam_buf中。翻译的过程就是不断向gen_opc_buf和gen_opparam_buf中填充操作码和操作数。
首先初始化tcg上下文,然后调用gen_intermediate_code生成中间代码,然后调用tcg_gen_code产生host机器代码,一旦产生机器代码,就可以在host机器上直接执行。
初始化tcg上下文时,会将全局gen_opc_buf赋值给gen_opc_ptr,gen_opparam_buf赋值给gen_opparam_ptr。
gen_intermediate_code将客户机的指令转换成中间代码形式,这个过程在这里称为译码过程。然后会调用体系结构相关的gen_intermediate_code_internal,在进行翻译。首先需要初始化翻译上下文dc,需要的变量有所翻译的tb,pc,以及跳转标志位。然后调用gen_icount_start貌似对时钟计数,接着进入了一个循环,这个循环对一个指令块进行翻译,如果设置singlestep,则每个指令块只有一条指令。翻译的核心是disas_uc64_insn,在这个函数将指令转换成用tcg表示的中间代码。
接下来就是tcg_gen_code(tcg/tcg.c)。tcg_gen_code将会把tcg表示的中间代码转换最终的host机器代码,并把在gen_code_buf所指向的缓冲区中。gen_code_buf其实就是该tb的成员tc_ptr所指向的缓冲区。
- 2175 int tcg_gen_code(TCGContext *s, uint8_t *gen_code_buf)
- 2176 {
- 2177 #ifdef CONFIG_PROFILER
- 2178 {
- 2179 int n;
- 2180 n = (gen_opc_ptr - gen_opc_buf);
- 2181 s->op_count += n;
- 2182 if (n > s->op_count_max)
- 2183 s->op_count_max = n;
- 2184
- 2185 s->temp_count += s->nb_temps;
- 2186 if (s->nb_temps > s->temp_count_max)
- 2187 s->temp_count_max = s->nb_temps;
- 2188 }
- 2189 #endif
- 2190
- 2191 tcg_gen_code_common(s, gen_code_buf, -1);
- 2192
- 2193
- 2194 flush_icache_range((tcg_target_ulong)gen_code_buf,
- 2195 (tcg_target_ulong)s->code_ptr);
- 2196
- 2197 return s->code_ptr - gen_code_buf;
- 2198 }
tcg_gen_code的工作是将中间码翻译成host机器码,它的主要函数是tcg_gen_code_common.
- 2045 static inline int tcg_gen_code_common(TCGContext *s, uint8_t *gen_code_buf,
- 2046 long search_pc)
- 2047 {
- 2048 TCGOpcode opc;
- 2049 int op_index;
- 2050 const TCGOpDef *def;
- 2051 unsigned int dead_args;
- 2052 const TCGArg *args;
- 2053
- 2054 #ifdef DEBUG_DISAS
- 2055 if (unlikely(qemu_loglevel_mask(CPU_LOG_TB_OP))) {
- 2056 qemu_log("OP:\n");
- 2057 tcg_dump_ops(s);
- 2058 qemu_log("\n");
- 2059 }
- 2060 #endif
- 2061
- 2062 #ifdef USE_TCG_OPTIMIZATIONS
- 2063 gen_opparam_ptr =
- 2064 tcg_optimize(s, gen_opc_ptr, gen_opparam_buf, tcg_op_defs);
- 2065 #endif
- 2066
- 2067 #ifdef CONFIG_PROFILER
- 2068 s->la_time -= profile_getclock();
- 2069 #endif
- 2070 tcg_liveness_analysis(s);
- 2071 #ifdef CONFIG_PROFILER
- 2072 s->la_time += profile_getclock();
- 2073 #endif
- 2074
- 2075 #ifdef DEBUG_DISAS
- 2076 if (unlikely(qemu_loglevel_mask(CPU_LOG_TB_OP_OPT))) {
- 2077 qemu_log("OP after liveness analysis:\n");
- 2078 tcg_dump_ops(s);
- 2079 qemu_log("\n");
- 2080 }
- 2081 #endif
- 2082
- 2083 tcg_reg_alloc_start(s);
- 2084
- 2085 s->code_buf = gen_code_buf;
- 2086 s->code_ptr = gen_code_buf;
- 2087
- 2088 args = gen_opparam_buf;
- 2089 op_index = 0;
- 2090
- 2091 for(;;) {
- 2092 opc = gen_opc_buf[op_index];
- 2093 #ifdef CONFIG_PROFILER
- 2094 tcg_table_op_count[opc]++;
- 2095 #endif
- 2096 def = &tcg_op_defs[opc];
- 2097 #if 0
- 2098 printf("%s: %d %d %d\n", def->name,
- 2099 def->nb_oargs, def->nb_iargs, def->nb_cargs);
- 2100
- 2101 #endif
- 2102 switch(opc) {
- 2103 case INDEX_op_mov_i32:
- 2104 #if TCG_TARGET_REG_BITS == 64
- 2105 case INDEX_op_mov_i64:
- 2106 #endif
- 2107 dead_args = s->op_dead_args[op_index];
- 2108 tcg_reg_alloc_mov(s, def, args, dead_args);
- 2109 break;
- 2110 case INDEX_op_movi_i32:
- 2111 #if TCG_TARGET_REG_BITS == 64
- 2112 case INDEX_op_movi_i64:
- 2113 #endif
- 2114 tcg_reg_alloc_movi(s, args);
- 2115 break;
- 2116 case INDEX_op_debug_insn_start:
- 2117
- 2118 break;
- 2119 case INDEX_op_nop:
- 2120 case INDEX_op_nop1:
- 2121 case INDEX_op_nop2:
- 2122 case INDEX_op_nop3:
- 2123 break;
- 2124 case INDEX_op_nopn:
- 2125 args += args[0];
- 2126 goto next;
- 2127 case INDEX_op_discard:
- 2128 {
- 2129 TCGTemp *ts;
- 2130 ts = &s->temps[args[0]];
- 2131
- 2132 if (!ts->fixed_reg) {
- 2133 if (ts->val_type == TEMP_VAL_REG)
- 2134 s->reg_to_temp[ts->reg] = -1;
- 2135 ts->val_type = TEMP_VAL_DEAD;
- 2136 }
- 2137 }
- 2138 break;
- 2139 case INDEX_op_set_label:
- 2140 tcg_reg_alloc_bb_end(s, s->reserved_regs);
- 2141 tcg_out_label(s, args[0], s->code_ptr);
- 2142 break;
- 2143 case INDEX_op_call:
- 2144 dead_args = s->op_dead_args[op_index];
- 2145 args += tcg_reg_alloc_call(s, def, opc, args, dead_args);
- 2146 goto next;
- 2147 case INDEX_op_end:
- 2148 goto the_end;
- 2149 default:
- 2150
- 2151 if (def->flags & TCG_OPF_NOT_PRESENT) {
- 2152 tcg_abort();
- 2153 }
- 2154
-
-
- 2157 dead_args = s->op_dead_args[op_index];
- 2158 tcg_reg_alloc_op(s, def, opc, args, dead_args);
- 2159 break;
- 2160 }
- 2161 args += def->nb_args;
- 2162 next:
- 2163 if (search_pc >= 0 && search_pc < s->code_ptr - gen_code_buf) {
- 2164 return op_index;
- 2165 }
- 2166 op_index++;
- 2167 #ifndef NDEBUG
- 2168 check_regs(s);
- 2169 #endif
- 2170 }
- 2171 the_end:
- 2172 return -1;
- 2173 }
从代码可以看到,
s->code_buf = gen_code_buf;
s->code_ptr = gen_code_buf;
上下文s翻译确实是从gen_code_buf缓冲区开始的,并且code_ptr指向缓冲区的头部。大部分执行default分支,tcg_reg_alloc_op主要是分析该指令的输入、输出约束,根据这些约束分配寄存器等,然后调用tcg_out_op将该中间码翻译成host机器码。
到这里,出现了好几个buffer的指针,比较乱,容易混淆,所以整理一下:
code_gen_buffer:code cache的起始地址,可以说是生成的所有最终的host代码,都是放在这个buffer里面的;
code_gen_ptr:code cache中的当前指针,通常指向当前正在翻译的host代码填充的位置;
tb->tb_ptr: 这个是TB所对应的host代码的起始位置;
gen_opc_buf:这个是全局的中间代码的操作码的buffer;
gen_opparam_buf:这个是全局的中间代码的参数的buffer;
gen_opc_ptr:当前的中间代码的操作码所填充的位置;
gen_opparam_ptr:当前中间代码的参数所填充的位置;
gen_code_buf:其实就是当前tb块所对应的host代码的起始位置;
s->code_buf:当前正在翻译的tb的host代码的起始位置;
s->code_ptr:当前正在翻译的tb的host代码的当前指针;
tcg_gen_code_common中,opc指向gen_opc_buf,args指向gen_opparam_buf,然后就从中间代码中取出操作码和参数,进行解析。我们以mov指令tcg_gen_mov_i64(t_op2_64, cpu_R[REG_S2]);为例,操作码是INDEX_op_mov_i64,参数就是t_op2_64,和cpu_R[REG_S2],执行到tcg_gen_code_common,就会跳转到代码:
2104 #if TCG_TARGET_REG_BITS == 64
2105 case INDEX_op_mov_i64:
2106 #endif
2107 dead_args = s->op_dead_args[op_index];
2108 tcg_reg_alloc_mov(s, def, args, dead_args);
2109 break;
接着就调用tcg_reg_alloc_mov,这个函数会检查源寄存器和目标寄存器的类型,然后就会调用tcg_out_mov(tcg/i386/tcg-target.c),接着调用tcg_out_modrm(tcg/i386/tcg-target.c),将x86的机器代码输入到s->code_ptr中。
3.翻译代码块的执行
代码块翻译好之后,主函数调用tcg_qemu_tb_exec,该函数会进入tcg的入口函数。
以host是arm为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec传进来的第一的参数(env)给TCG_AREG0,然后跳转至tb_ptr,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。
以host是arm为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec传进来的第一的参数(env)给TCG_AREG0,然后跳转至tb_ptr,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。
- 100 #define tcg_qemu_tb_exec(env, tb_ptr) \
- 101 ((long __attribute__ ((longcall)) \
- 102 (*)(void *, void *))code_gen_prologue)(env, tb_ptr)
code_gen_prologue是tcg的入口函数,在tcg初始化的时候会生成相应的代码.
以host是arm为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec传进来的第一的参数(env)给TCG_AREG0,然后跳转至tb_ptr,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。
- 1881 static void tcg_target_qemu_prologue(TCGContext *s)
- 1882 {
- 1883
-
-
- 1886
- 1887
- 1888 tcg_out32(s, (COND_AL << 28) | 0x092d5ff0);
- 1889
- 1890 tcg_out_mov(s, TCG_TYPE_PTR, TCG_AREG0, tcg_target_call_iarg_regs[0]);
- 1891
- 1892 tcg_out_bx(s, COND_AL, tcg_target_call_iarg_regs[1]);
- 1893 tb_ret_addr = s->code_ptr;
- 1894
- 1895
- 1896 tcg_out32(s, (COND_AL << 28) | 0x08bd9ff0);
- 1897 }
参考文献
1 http://blog.csdn.net/alloc_young/article/details/7719823