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

发表回复