kvm_emualte_instruction(struct kvm_vcpu *vcpu, int emulation_type)
根据当前vcpu的状态和指定的emulation_type来进行当前vcpu所执行的指令进行模拟。
Emulation type & Emulation result
1 | enum emulation_result { |
x86_emulate_instruction
kvm_emulate_instruction()函数实际是对x86_emulate_instruction()函数的封装:1
2
3
4int kvm_emulate_instruction(struct kvm_vcpu *vcpu, int emulate_type)
{
return x86_emulate_instruction(vcpu, 0, emulation_type, NULL, 0);
}
1 | int x86_emulate_instruction(struct kvm_vcpu *vcpu, |
该函数有5个参数:
- vcpu:需要进行指令模拟的vcpu,
- cr2: 当前vcpu的cr2;
- emulation_type: 模拟的类型;
- insn: 需要模拟的指令的指针;
- insn_len:需要模拟的指令的长度;
该函数在kvm中有三个地方会被调用:
- in arch/x86/kvm/mmu.c-> kvm_mmu_page_fault()->x86_emulate_instruction(vcpu, cr2, emulation_type, insn, insn_len);
- in arch/x86/kvm/x86.c-> kvm_emulate_instruction()->x86_emulate_instruction(vcpu, 0, emulation_type, NULL, 0);
- in arch/x86/kvm/x86.c-> kvm_emulate_instruction_from_buffer()->x86_emulate_instruction(vcpu, 0, 0, insn, insn_len);
我们重点关注case 2, jikvm_emulate_instruction()
kvm_emulate_instruction
其调用x86_emulate_instruction()传入的cr2为0,insn为NULL,insn_len为0,即只有vcpu,emulation_type有效。
x86_emulate_instruction
1 | int x86_emulate_instruction(struct kvm_vcpu *vcpu, |
- 首先判断是否需要decode instruction.即如果没有置上EMULTYPE_NO_DECODE,那么就需要指令译码。
- init_emulate_ctxt(vcpu): 初始化vcpu->arch.tmulate_ctxt;
- 初始化ctxt的一些变量
- 获取当前vcpu的CS段描述符的DB位(bit22) 和L位(bit21)
- ctxt->eflag
- ctxt->tf;
- ctxt->rip;
- ctxt->mode;可选的值有
- X86EMUL_MODE_REAL
- X86EMUL_MODE_VM86
- X86EMUL_MODE_PROT64
- X86EMUL_MODE_PROT32
- X86EMUL_MODE_PROT16
- init_decode_cache(ctxt);
- memset(&ctxt->rip_relative, 0 , (void )&ctxt->modrm - (void )&ctxt->rip_relative);
- ctxt->io_read.pos = 0;
- ctxt->io_read.end = 0;
- ctxt->mem_read.end = 0;
- vcpu->arch.emulate_regs_need_sync_from_vcpu = false;
- 初始化ctxt的一些变量
- 如果当前模拟没有设置EMULTYPE_SKIP并且当前指令设置了breakpoint,那么直接返回,因为还没有set_complete_userspace_io,所以还会再进入到当前指令。
- 继续初始化ctxt的一些域:
- ctxt->interruptibility = 0;
- ctxt->have_exception = false;
- ctxt->exception.vector = -l;
- ctxt->perm_ok = false;
- ctxt->ud = emulation_type & EMULTYPE_TRAP_UD;
- x86_decode_insn(ctxt, insn, insn_len)进行指令decode;
在此处会有tracepoint来trace kvm_emulate_insn(failed=0),并且将vcpu->stat.insn_emulation加1;
如果返回值为EMULATION_OK,说明指令译码过程没有出问题。
- init_emulate_ctxt(vcpu): 初始化vcpu->arch.tmulate_ctxt;
- 如果emulation_type置上了EMULTYPE_VMWARE,但是该指令不是vmware_backdoor_opcode,那么return EMULATE_FAIL;
- 如果置上了EMULTYPE_SKIP,那么更新vcpu_rip和EFLAGS_RF;
x86_decode_insn
1 | int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len) |
初始状态:
- op_prefix = false;
- has_seg_override = false;
- ctxt->memop.type = OP_NONE;
- ctxt->memopp = NULL;
- ctxt->_eip = ctxt->eip;
- ctxt->fetch.ptr = ctxt->fetch.data;
- ctxt->fetch.end = ctxt->fetch.data + insn_len; 如果传入的insn为NULL,insn_len为0,那么ctxt->fetch.ptr == ctxt->fetch.end;
- ctxt->opcode.len = 1;
如果传入的insn != NULL, 那么将insn地址开始的insn_len长度的指令memcpy到ctxt->fetch.data;
如果传入的insn == NULL, 那么调用__do_insn_fetch_bytes(ctxt,1)。该函数会设置ctxt->fetch.end;根据ctxt->mode来设置def_op_bytes和def_ad_bytes的长度,最终会赋值到
- ctxt->op_bytes = def_op_bytes; 操作数长度
- ctxt->ad_bytes = def_ad_bytes; 地址长度
- for()循环进行指令前缀处理,ctxt->b = insn_fetch(u8, ctxt), 取出一个字节,根据不同的值进行不同的处理,比如有前缀,段超越,address-size override等等。
- 设置op_prefix,更新ctxt->op_bytes; 0x66: operand-size override;
- 更新ctxt->ad_bytes; 0x67: address-size override;
- 设置has_seg_override, ctxt->seg_override; 0x26,0x2e,0x36,0x3e,0x64,0x65: Segment override;
- 设置ctxt->rex_prefix; 0x40…0x4f: /REX/
- 设置ctxt->lock_prefix; 0xf0: lock;
- 设置ctxt->rep_prefix; 0xf2, 0xf3: REP
上一步处理完指令前缀后,ctxt->b指向前缀后的指令的第一个字节,
然后根据ctxt->b的值,来检查是几字节指令,将指令译码出入opcode,并设置ctxt->opcode_len= 1/2/3;retry_instruction(),很多情况不执行,具体还不清楚这一步是在什么场景需要。
restart开始指令执行的模拟:
- ctxt->exception.address = cr2;
- x86_emulate_insn(ctxt)进行指令执行模拟