qemu翻译和执行流程分析

一.qemu简介

         qemu是使用动态二进制翻译的cpu模拟器,它支持两种运行模式:全系统模拟和用户态模拟。在全系统模拟下,qemu可以模拟处理器和各种外设,可以运行操作系统。用户态可以运行为另外一种cpu编译的进程,前提是两者运行的os要一致。qemu使用了动态二进制翻译将target instruction翻译成host instruction,完成这个工作的是tcg模块。为了移植性和通用性方面的考虑,qemu定义了mirco-op,本文也称为中间代码,首先qemu会将targetinstruction翻译成mirco-op,然后tcgmirco-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)

                主要是处理中断异常, 找到代码翻译块,然后执行.

  1. for(;;) {  
  2. process interruptrequest;  
  3.   
  4. tb_find_fast();  
  5.   
  6. tcg_qemu_tb_exec(tc_ptr);  
  7. }  

        qemu会将翻译好到代码块暂存起来,因此首先会去查看该pc对应的代码是否已经翻译,如果已经存在直接返回,否则就进入tb_find_slow,进行翻译。

  1. 139 static inline TranslationBlock *tb_find_fast(CPUArchState *env)  
  2. 140 {  
  3. 141     TranslationBlock *tb;  
  4. 142     target_ulong cs_base, pc;  
  5. 143     int flags;  
  6. 144   
  7. 145     /* we record a subset of the CPU state. It will 
  8. 146        always be the same before a given translated block 
  9. 147        is executed. */  
  10. 148     cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags);  
  11. 149     tb = env->tb_jmp_cache[tb_jmp_cache_hash_func(pc)];  
  12. 150     if (unlikely(!tb || tb->pc != pc || tb->cs_base != cs_base ||  
  13. 151                  tb->flags != flags)) {  
  14. 152         tb = tb_find_slow(env, pc, cs_base, flags);  
  15. 153     }  
  16. 154     return tb;  
  17. 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,这样就可以翻译接下来的指令。

  1. 1029 TranslationBlock *tb_gen_code(CPUArchState *env,  
  2. 1030                               target_ulong pc, target_ulong cs_base,  
  3. 1031                               int flags, int cflags)  
  4. 1032 {            
  5. 1033     TranslationBlock *tb;  
  6. 1034     uint8_t *tc_ptr;  
  7. 1035     tb_page_addr_t phys_pc, phys_page2;  
  8. 1036     target_ulong virt_page2;  
  9. 1037     int code_gen_size;  
  10. 1038      
  11. 1039     phys_pc = get_page_addr_code(env, pc);  
  12. 1040     tb = tb_alloc(pc);  
  13. 1041     if (!tb) {  
  14. 1042         /* flush must be done */  
  15. 1043         tb_flush(env);  
  16. 1044         /* cannot fail at this point */  
  1. 1045         tb = tb_alloc(pc);  
  2. 1046         /* Don't forget to invalidate previous TB info.  */  
  3. 1047         tb_invalidated_flag = 1;  
  4. 1048     }  
  5. 1049     tc_ptr = code_gen_ptr;  
  6. 1050     tb->tc_ptr = tc_ptr;  
  7. 1051     tb->cs_base = cs_base;  
  8. 1052     tb->flags = flags;  
  9. 1053     tb->cflags = cflags;  
  10. 1054     cpu_gen_code(env, tb, &code_gen_size);  
  11. 1055     code_gen_ptr = (void *)(((uintptr_t)code_gen_ptr + code_gen_size +  
  12. 1056                              CODE_GEN_ALIGN - 1) & ~(CODE_GEN_ALIGN - 1));  
  13. 1057   
  14. 1058     /* check next page if needed */  
  15. 1059     virt_page2 = (pc + tb->size - 1) & TARGET_PAGE_MASK;  
  16. 1060     phys_page2 = -1;  
  17. 1061     if ((pc & TARGET_PAGE_MASK) != virt_page2) {  
  18. 1062         phys_page2 = get_page_addr_code(env, virt_page2);  
  19. 1063     }  
  20. 1064     tb_link_page(tb, phys_pc, phys_page2);  
  21. 1065     return tb;  
  22. 1066 }  

      在cpu_gen_code里面首先是将targetinstruction翻译成micro-op,然后将mirco-op翻译成host机器码。

  1. 54 int cpu_gen_code(CPUArchState *env, TranslationBlock *tb, int *gen_code_size_ptr)  
  2.  55 {  
  3.  56     TCGContext *s = &tcg_ctx;  
  4.  57     uint8_t *gen_code_buf;  
  5.  58     int gen_code_size;  
  6.  59 #ifdef CONFIG_PROFILER  
  7.  60     int64_t ti;  
  8.  61 #endif  
  9.  62   
  10.  63 #ifdef CONFIG_PROFILER  
  11.  64     s->tb_count1++; /* includes aborted translations because of 
  12.  65                        exceptions */  
  13.  66     ti = profile_getclock();  
  14.  67 #endif  
  15.  68     tcg_func_start(s);  
  16.  69   
  17.  70     gen_intermediate_code(env, tb);  
  18.  71   
  19.  72     /* generate machine code */  
  20.  73     gen_code_buf = tb->tc_ptr;  
  21.  74     tb->tb_next_offset[0] = 0xffff;  
  22.  75     tb->tb_next_offset[1] = 0xffff;  
  23.  76     s->tb_next_offset = tb->tb_next_offset;  
  24.  77 #ifdef USE_DIRECT_JUMP  
  25.  78     s->tb_jmp_offset = tb->tb_jmp_offset;  
  26.  79     s->tb_next = NULL;  
  27.  80 #else  
  28.  81     s->tb_jmp_offset = NULL;  
  29.  82     s->tb_next = tb->tb_next;  
  30.  83 #endif  
  31.  84   
  32.  85 #ifdef CONFIG_PROFILER  
  33.  86     s->tb_count++;  
  34.  87     s->interm_time += profile_getclock() - ti;  
  35.  88     s->code_time -= profile_getclock();  
  36.  89 #endif  
  37.  90     gen_code_size = tcg_gen_code(s, gen_code_buf);  
  38.  91     *gen_code_size_ptr = gen_code_size;  
  39.  92 #ifdef CONFIG_PROFILER  
  40.  93     s->code_time += profile_getclock();  
  41.  94     s->code_in_len += tb->size;  
  42.  95     s->code_out_len += gen_code_size;  
  43.  96 #endif  
  44.  97   
  45.  98 #ifdef DEBUG_DISAS  
  46.  99     if (qemu_loglevel_mask(CPU_LOG_TB_OUT_ASM)) {  
  47. 100         qemu_log("OUT: [size=%d]\n", *gen_code_size_ptr);  
  48. 101         log_disas(tb->tc_ptr, *gen_code_size_ptr);  
  49. 102         qemu_log("\n");  
  50. 103         qemu_log_flush();  
  51. 104     }  
  52. 105 #endif  
  53. 106     return 0;  
  54. 107 }  

    qemutarget翻译成中间码时,将操作码和操作数分开存储,分别存在gen_opc_bufgen_opparam_buf中。翻译的过程就是不断向gen_opc_bufgen_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所指向的缓冲区。

  1. 2175 int tcg_gen_code(TCGContext *s, uint8_t *gen_code_buf)  
  2. 2176 {  
  3. 2177 #ifdef CONFIG_PROFILER  
  4. 2178     {  
  5. 2179         int n;  
  6. 2180         n = (gen_opc_ptr - gen_opc_buf);  
  7. 2181         s->op_count += n;  
  8. 2182         if (n > s->op_count_max)  
  9. 2183             s->op_count_max = n;  
  10. 2184   
  11. 2185         s->temp_count += s->nb_temps;  
  12. 2186         if (s->nb_temps > s->temp_count_max)  
  13. 2187             s->temp_count_max = s->nb_temps;  
  14. 2188     }  
  15. 2189 #endif  
  16. 2190   
  17. 2191     tcg_gen_code_common(s, gen_code_buf, -1);  
  18. 2192   
  19. 2193     /* flush instruction cache */  
  20. 2194     flush_icache_range((tcg_target_ulong)gen_code_buf,  
  21. 2195                        (tcg_target_ulong)s->code_ptr);  
  22. 2196   
  23. 2197     return s->code_ptr -  gen_code_buf;  
  24. 2198 }  

      tcg_gen_code的工作是将中间码翻译成host机器码,它的主要函数是tcg_gen_code_common.

  1. 2045 static inline int tcg_gen_code_common(TCGContext *s, uint8_t *gen_code_buf,  
  2. 2046                                       long search_pc)  
  3. 2047 {  
  4. 2048     TCGOpcode opc;  
  5. 2049     int op_index;  
  6. 2050     const TCGOpDef *def;  
  7. 2051     unsigned int dead_args;  
  8. 2052     const TCGArg *args;  
  9. 2053   
  10. 2054 #ifdef DEBUG_DISAS  
  11. 2055     if (unlikely(qemu_loglevel_mask(CPU_LOG_TB_OP))) {  
  12. 2056         qemu_log("OP:\n");  
  13. 2057         tcg_dump_ops(s);  
  14. 2058         qemu_log("\n");  
  15. 2059     }  
  16. 2060 #endif  
  17. 2061   
  18. 2062 #ifdef USE_TCG_OPTIMIZATIONS  
  19. 2063     gen_opparam_ptr =  
  20. 2064         tcg_optimize(s, gen_opc_ptr, gen_opparam_buf, tcg_op_defs);  
  21. 2065 #endif  
  22. 2066   
  23. 2067 #ifdef CONFIG_PROFILER  
  24. 2068     s->la_time -= profile_getclock();  
  25. 2069 #endif  
  26. 2070     tcg_liveness_analysis(s);  
  27. 2071 #ifdef CONFIG_PROFILER  
  28. 2072     s->la_time += profile_getclock();  
  29. 2073 #endif  
  30. 2074   
  31. 2075 #ifdef DEBUG_DISAS  
  32. 2076     if (unlikely(qemu_loglevel_mask(CPU_LOG_TB_OP_OPT))) {  
  33. 2077         qemu_log("OP after liveness analysis:\n");  
  34. 2078         tcg_dump_ops(s);  
  35. 2079         qemu_log("\n");  
  36. 2080     }  
  37. 2081 #endif  
  38. 2082   
  39. 2083     tcg_reg_alloc_start(s);  
  40. 2084   
  41. 2085     s->code_buf = gen_code_buf;  
  42. 2086     s->code_ptr = gen_code_buf;  
  43. 2087   
  44. 2088     args = gen_opparam_buf;  
  45.  2089     op_index = 0;  
  46. 2090   
  47. 2091     for(;;) {  
  48. 2092         opc = gen_opc_buf[op_index];  
  49. 2093 #ifdef CONFIG_PROFILER  
  50. 2094         tcg_table_op_count[opc]++;  
  51. 2095 #endif  
  52. 2096         def = &tcg_op_defs[opc];  
  53. 2097 #if 0  
  54. 2098         printf("%s: %d %d %d\n", def->name,  
  55. 2099                def->nb_oargs, def->nb_iargs, def->nb_cargs);  
  56. 2100         //        dump_regs(s);  
  57. 2101 #endif  
  58. 2102         switch(opc) {  
  59. 2103         case INDEX_op_mov_i32:  
  60. 2104 #if TCG_TARGET_REG_BITS == 64  
  61. 2105         case INDEX_op_mov_i64:  
  62. 2106 #endif  
  63. 2107             dead_args = s->op_dead_args[op_index];  
  64. 2108             tcg_reg_alloc_mov(s, def, args, dead_args);  
  65. 2109             break;  
  66. 2110         case INDEX_op_movi_i32:  
  67. 2111 #if TCG_TARGET_REG_BITS == 64  
  68. 2112         case INDEX_op_movi_i64:  
  69. 2113 #endif  
  70. 2114             tcg_reg_alloc_movi(s, args);  
  71. 2115             break;  
  72. 2116         case INDEX_op_debug_insn_start:  
  73. 2117             /* debug instruction */  
  74. 2118             break;  
  75. 2119         case INDEX_op_nop:  
  76. 2120         case INDEX_op_nop1:  
  77. 2121         case INDEX_op_nop2:  
  78. 2122         case INDEX_op_nop3:  
  79. 2123             break;  
  80. 2124         case INDEX_op_nopn:  
  81. 2125             args += args[0];  
  82. 2126             goto next;  
  83. 2127         case INDEX_op_discard:  
  84. 2128             {  
  85.  2129                 TCGTemp *ts;  
  86. 2130                 ts = &s->temps[args[0]];  
  87. 2131                 /* mark the temporary as dead */  
  88. 2132                 if (!ts->fixed_reg) {  
  89. 2133                     if (ts->val_type == TEMP_VAL_REG)  
  90. 2134                         s->reg_to_temp[ts->reg] = -1;  
  91. 2135                     ts->val_type = TEMP_VAL_DEAD;  
  92. 2136                 }  
  93. 2137             }  
  94. 2138             break;  
  95. 2139         case INDEX_op_set_label:  
  96. 2140             tcg_reg_alloc_bb_end(s, s->reserved_regs);  
  97. 2141             tcg_out_label(s, args[0], s->code_ptr);  
  98. 2142             break;  
  99. 2143         case INDEX_op_call:  
  100. 2144             dead_args = s->op_dead_args[op_index];  
  101. 2145             args += tcg_reg_alloc_call(s, def, opc, args, dead_args);  
  102. 2146             goto next;  
  103. 2147         case INDEX_op_end:  
  104. 2148             goto the_end;  
  105. 2149         default:  
  106. 2150             /* Sanity check that we've not introduced any unhandled opcodes. */  
  107. 2151             if (def->flags & TCG_OPF_NOT_PRESENT) {  
  108. 2152                 tcg_abort();  
  109. 2153             }  
  110. 2154             /* Note: in order to speed up the code, it would be much 
  111. 2155                faster to have specialized register allocator functions for 
  112. 2156                some common argument patterns */  
  113. 2157             dead_args = s->op_dead_args[op_index];  
  114. 2158             tcg_reg_alloc_op(s, def, opc, args, dead_args);  
  115. 2159             break;  
  116. 2160         }  
  117. 2161         args += def->nb_args;  
  118. 2162     next:  
  119. 2163         if (search_pc >= 0 && search_pc < s->code_ptr - gen_code_buf) {  
  120. 2164             return op_index;  
  121. 2165         }  
  122. 2166         op_index++;  
  123. 2167 #ifndef NDEBUG  
  124. 2168         check_regs(s);  
  125. 2169 #endif  
  126. 2170     }  
  127.  2171  the_end:  
  128. 2172     return -1;  
  129. 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的入口函数。

hostarm为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec传进来的第一的参数(env)TCG_AREG0,然后跳转至tb_ptr,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。

以hostarm为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec传进来的第一的参数(env)TCG_AREG0,然后跳转至tb_ptr,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。
  1. 100 #define tcg_qemu_tb_exec(env, tb_ptr) \  
  2. 101     ((long __attribute__ ((longcall)) \  
  3. 102       (*)(void *, void *))code_gen_prologue)(env, tb_ptr)  

      code_gen_prologuetcg的入口函数,在tcg初始化的时候会生成相应的代码.

         以hostarm为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec传进来的第一的参数(env)TCG_AREG0,然后跳转至tb_ptr,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。

  1. 1881 static void tcg_target_qemu_prologue(TCGContext *s)  
  2. 1882 {  
  3. 1883     /* Calling convention requires us to save r4-r11 and lr; 
  4. 1884      * save also r12 to maintain stack 8-alignment. 
  5. 1885      */  
  6. 1886       
  7. 1887     /* stmdb sp!, { r4 - r12, lr } */  
  8. 1888     tcg_out32(s, (COND_AL << 28) | 0x092d5ff0);  
  9. 1889   
  10. 1890     tcg_out_mov(s, TCG_TYPE_PTR, TCG_AREG0, tcg_target_call_iarg_regs[0]);  
  11. 1891   
  12. 1892     tcg_out_bx(s, COND_AL, tcg_target_call_iarg_regs[1]);  
  13. 1893     tb_ret_addr = s->code_ptr;  
  14. 1894       
  15. 1895     /* ldmia sp!, { r4 - r12, pc } */  
  16. 1896     tcg_out32(s, (COND_AL << 28) | 0x08bd9ff0);  
  17. 1897 }     


参考文献

1 http://blog.csdn.net/alloc_young/article/details/7719823


  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值