qemu tcg translation block机制
Base是git://git.qemu.org/qemu.git v2.6.0
入口是qemu_init_vcpu,在tcg_enabled下进入qemu_tcg_init_vcpu函数,在qemu_thread_create(cpu->thread, thread_name, qemu_tcg_cpu_thread_fn,
cpu, QEMU_THREAD_JOINABLE)中看到执行函数是qemu_tcg_cpu_thread_fn,下面的函数负责控制在machine完全初始化完成前进行等待
while (first_cpu->stopped) { qemu_cond_wait(first_cpu->halt_cond, &qemu_global_mutex); /* process any pending work */ CPU_FOREACH(cpu) { qemu_wait_io_event_common(cpu); } }
然后进入循环开始执行tcg_exec_all函数。
在cpu_can_run(cpu)之后执行tcg_cpu_exec,去除use_icount后,就只有cpu_exec,
#ifdef TARGET_I386 X86CPU *x86_cpu = X86_CPU(cpu); CPUArchState *env = &x86_cpu->env; #endif
需要注意看一下CPUArchState,即CPUX86State结构,
typedef struct CPUX86State { /* standard registers */ target_ulong regs[CPU_NB_REGS]; target_ulong eip; target_ulong eflags; /* eflags register. During CPU emulation, CC flags and DF are set to zero because they are stored elsewhere */ /* emulator internal eflags handling */ target_ulong cc_dst; target_ulong cc_src; target_ulong cc_src2; uint32_t cc_op; int32_t df; /* D flag : 1 if D = 0, -1 if D = 1 */ uint32_t hflags; /* TB flags, see HF_xxx constants. These flags are known at translation time. */ uint32_t hflags2; /* various other flags, see HF2_xxx constants. */ /* segments */ SegmentCache segs[6]; /* selector values */ SegmentCache ldt; SegmentCache tr; SegmentCache gdt; /* only base and limit are used */ SegmentCache idt; /* only base and limit are used */ target_ulong cr[5]; /* NOTE: cr1 is unused */ int32_t a20_mask; BNDReg bnd_regs[4]; BNDCSReg bndcs_regs; uint64_t msr_bndcfgs; uint64_t efer; }
更多内容自己拉出来看吧,qemu在KVM下,env内的数据只是作为vcpu sched退到qemu userspace之后保存的数据,当再次运行时则将内容加载到vmcs或真正的物理CPU上,而tcg对待env,每一个env都是tcg vcpu虚拟的寄存器,直接读取或写入。
然后执行cc->cpu_exec_enter(cpu)即x86_cpu_exec_enter,基本就是env->eflags的初始化,在下面的内容才是真正执行的地方:
for(;;) { if (sigsetjmp(cpu->jmp_env, 0) == 0) {
本质就是循环处理不同的tb,sigsetjmp的用法自己google吧,需要注意这一点。代码的真正执行是根据env->eip执行的,eip的初始化在x86_cpu_reset函数中,前面有SegmentCache的初始化,直接处理,
typedef struct SegmentCache { uint32_t selector; target_ulong base; uint32_t limit; uint32_t flags; } SegmentCache;
一开始开发选择的模拟器是bochs,然后换成了qemu,bochs处理SegmentCache是只需要拿到selector,然后从内存中读取对应的shadow值,而qemu则不对memory 中的shadow寄存器做处理,直接从物理寄存器读取内容。
回过来看env->eip,这个值就是读取代码的链条,修改它的有:
static void x86_cpu_set_pc(CPUState *cs, vaddr value) { X86CPU *cpu = X86_CPU(cs); cpu->env.eip = value; } static void x86_cpu_synchronize_from_tb(CPUState *cs, TranslationBlock *tb) { X86CPU *cpu = X86_CPU(cs); cpu->env.eip = tb->pc - tb->cs_base; }
先记住,回头看sigsetjmp。
后面一开始的部分都是中断和异常,不仅仅是guest产生的,还是qemu自身模拟的。
这些不关注,走到下一个for循环中
for(;;) { tb_lock(); tb = tb_find_fast(cpu); cpu->current_tb = tb; next_tb = cpu_tb_exec(cpu, tb); }
上面的代码才是核心内容,tb_find_fast负责找到下一个需要执行的tb,cpu_tb_exec负责执行这个translation block。
在tb_find_fast中,tb本身是有二级缓存的,hash索引是使用代码执行的pc指针,
static inline unsigned int tb_jmp_cache_hash_func(target_ulong pc) { target_ulong tmp; tmp = pc ^ (pc >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS)); return (((tmp >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS)) & TB_JMP_PAGE_MASK) | (tmp & TB_JMP_ADDR_MASK)); }
读取缓存就需要当前tb对应的pc值,
static inline void cpu_get_tb_cpu_state(CPUX86State *env, target_ulong *pc, target_ulong *cs_base, int *flags) { *cs_base = env->segs[R_CS].base; //pc是由段基址算出来的,当前linux已经默认段基址为0 *pc = *cs_base + env->eip; *flags = env->hflags | (env->eflags & (IOPL_MASK | TF_MASK | RF_MASK | VM_MASK | AC_MASK)); }
因为本身tb_jmp_cache是有hash冲突的
//判断tb的准确性 if (unlikely(!tb || tb->pc != pc || tb->cs_base != cs_base || tb->flags != flags)) { //如果没有找到缓存,则进行代码的初步翻译 tb = tb_find_slow(cpu, pc, cs_base, flags); }
继续看tb_find_slow,又是一级缓存,tb_find_physical,缓存放在tcg_ctx全局变量中,以pc对应的hva作为索引。
真正的代码翻译在tb_gen_code函数中,它完成了tb的代码翻译过程,新tb加入了缓存中,cpu->tb_jmp_cache[tb_jmp_cache_hash_func(pc)] = tb。
TranslationBlock *tb_gen_code(CPUState *cpu, target_ulong pc, target_ulong cs_base, int flags, int cflags) { phys_pc = get_page_addr_code(env, pc); //tb分配并初始化 tb = tb_alloc(pc); gen_code_buf = tcg_ctx.code_gen_ptr; tb->tc_ptr = gen_code_buf; tb->cs_base = cs_base; tb->flags = flags; tb->cflags = cflags; //启动tcg上下文 tcg_func_start(&tcg_ctx); //翻译中间代码 gen_intermediate_code(env, tb); }
gen_intermediate_code翻译中间代码,在初始化dc结构时很明显是从env读取寄存器内容的
dc->cpuid_features = env->features[FEAT_1_EDX]; dc->cpuid_ext_features = env->features[FEAT_1_ECX]; dc->cpuid_ext2_features = env->features[FEAT_8000_0001_EDX]; dc->cpuid_ext3_features = env->features[FEAT_8000_0001_ECX]; dc->cpuid_7_0_ebx_features = env->features[FEAT_7_0_EBX]; dc->cpuid_xsave_features = env->features[FEAT_XSAVE];
直接看到gen_tb_start(tb)下的for循环,
关于tcg调试的部分
DEBUG_DISAS标志下有几个print内容,
#define CPU_LOG_TB_OUT_ASM (1 << 0) #define CPU_LOG_TB_IN_ASM (1 << 1) #define CPU_LOG_TB_OP (1 << 2) #define CPU_LOG_TB_OP_OPT (1 << 3) #define CPU_LOG_INT (1 << 4) #define CPU_LOG_EXEC (1 << 5) #define CPU_LOG_PCALL (1 << 6) #define CPU_LOG_TB_CPU (1 << 8) #define CPU_LOG_RESET (1 << 9) #define LOG_UNIMP (1 << 10) #define LOG_GUEST_ERROR (1 << 11) #define CPU_LOG_MMU (1 << 12) #define CPU_LOG_TB_NOCHAIN (1 << 13) #define CPU_LOG_PAGE (1 << 14) #define LOG_TRACE (1 << 15)
可以根据上面值的内容在代码不同位置打桩处理。
qemu tcg translation block机制来自于OenHan
链接为:https://oenhan.com/qemu-tcg-translation-block