以写指令为例
e6 70 66 9c 66 9d是一条写指令,以它为例来讲解一下IO指令模拟流程。
vmx_vmexit_handler→handle_mmio
指令由虚拟机下发,由xen收到进行处理。
asmlinkage void vmx_vmexit_handler(struct cpu_user_regs *regs) { switch ( exit_reason ) { case EXIT_REASON_IO_INSTRUCTION: if ( !handle_mmio() ) vmx_inject_hw_exception(TRAP_gp_fault, 0); break; |
hvm_emulate_prepare取vcpu的上下文信息,为指令模拟做准备;
handle_mmio→hvm_emulate_one
hvm_emulate_one来进行指令模拟的动作。
int handle_mmio(void) { struct hvm_emulate_ctxt ctxt; struct vcpu *curr = current; int rc;
hvm_emulate_prepare(&ctxt, guest_cpu_user_regs());
rc = hvm_emulate_one(&ctxt); |
hvm_emulate_one→x86_emulate
x86_emulate中完成具体分析模拟指令的动作。
1)分析指令前缀
2)分析操作码长度
3)分析获得源操作数
4)分析获得目的操作数
5)进行具体的指令操作
int x86_emulate( struct x86_emulate_ctxt *ctxt, const struct x86_emulate_ops *ops) { 1)/* Prefix bytes. */ for ( ; ; ) {
2)/* Opcode byte(s). */ d = opcode_table[b];
3)/* Decode and fetch the source operand: register, memory or immediate. */ switch ( d & SrcMask ) {
4)/* Decode and fetch the destination operand: register or memory. */ …… |
我们的指令是e6 70 66 9c 66 9d
其中有效部分是e6 70,详情可查看intel手册
在switch(b)中可以查到这样的case,对应的指令就是out %al,imm8
于是会调用io写操作
case 0xe4: /* in imm8,%al */ case 0xe5: /* in imm8,%eax */ case 0xe6: /* out %al,imm8 */ …… if ( b & 2 ) { /* out */ fail_if(ops->write_io == NULL); rc = ops->write_io(port, op_bytes, _regs.eax, ctxt); } …… } |
write_io是一个函数指针,指向hvmemul_write_io,
hvmemul_write_io→hvmemul_do_pio→hvmemul_do_io
下面介绍一个重要状态标志curr->arch.hvm_vcpu.io_state,它负责记录指令模拟所处的阶段。
模拟指令第一次进入hvmemul_do_io时,其状态正常都应该是HVMIO_none,表示还未开始IO指令模拟。
另外一个重要的状态标志p->state,它负责记录所处的io请求及响应的阶段。
其初始状态为STATE_IOREQ_NONE,表示还没有发送IO请求。
static int hvmemul_do_io( int is_mmio, paddr_t addr, unsigned long *reps, int size, paddr_t ram_gpa, int dir, int df, void *p_data) { switch ( curr->arch.hvm_vcpu.io_state ) { case HVMIO_none: break; |
IO写指令此时对应的p_data为NULL,会将io_state置为HVMIO_dispatched。
curr->arch.hvm_vcpu.io_state = (p_data == NULL) ? HVMIO_dispatched : HVMIO_awaiting_completion; |
首先要看该指令xen能不能处理,由hvm_portio_intercept负责,e6 70是由qemu来处理的指令,因此hvm_portio_intercept返回X86EMUL_UNHANDLEABLE,表示无法处理。
if ( is_mmio ) { } else { rc = hvm_portio_intercept(p); } |
随后,该请求通过hvm_send_assist_req交给qemu来处理。
switch ( rc ) { case X86EMUL_UNHANDLEABLE: rc = X86EMUL_RETRY; if ( !hvm_send_assist_req(curr) ) curr->arch.hvm_vcpu.io_state = HVMIO_none; } |
hvm_send_assist_req将p->state置为STATE_IOREQ_READY,发送IO请求准备好啦,然后通知事件通道。
bool_t hvm_send_assist_req(struct vcpu *v) { p->state = STATE_IOREQ_READY; notify_via_xen_event_channel(v->domain, v->arch.hvm_vcpu.xen_port); |
cpu_handle_ioreq是qemu中绑定事件通道的一个回调函数,来处理io请求。cpu_get_ioreq用来获取io请求。
cpu_handle_ioreq→cpu_get_ioreq→__cpu_get_ioreq
static void cpu_handle_ioreq(void *opaque) { extern int shutdown_requested; CPUState *env = opaque; ioreq_t *req = cpu_get_ioreq(); |
在__cpu_get_ioreq中将req->state置为STATE_IOREQ_INPROCESS,此处req->state等同于p->state。
static ioreq_t *__cpu_get_ioreq(int vcpu) { req->state = STATE_IOREQ_INPROCESS; return req; |
__handle_ioreq用于具体对io请求进行处理动作。cpu_handle_ioreq→__handle_ioreq
static void cpu_handle_ioreq(void *opaque) { extern int shutdown_requested; CPUState *env = opaque; ioreq_t *req = cpu_get_ioreq();
__handle_buffered_iopage(env); if (req) { __handle_ioreq(env, req); |
最后,qemu处理io的动作完成,将req->state置为 STATE_IORESP_READY,表示io处理完毕,响应准备好啦。随后,通知事件通道。
static void cpu_handle_ioreq(void *opaque) { req->state =STATE_IORESP_READY; xc_evtchn_notify(xce_handle, ioreq_local_port[send_vcpu]); } } |
vcpu调度时schedule会触发hvm_do_resume,
schedule→context_switch→schedule_tail→vmx_do_resume→hvm_do_resume
hvm_do_resume中会通过while循环反复检查p→state状态,若为STATE_IOREQ_READY或STATE_IOREQ_INPROCESS则触发等待wait_on_xen_event_channel,然后,再次进入while判断,直至状态为STATE_IORESP_READY时,触发hvm_io_assist。
void hvm_do_resume(struct vcpu *v)
{
ioreq_t *p;
while ( p->state != STATE_IOREQ_NONE )
{
switch ( p->state )
{
case STATE_IORESP_READY: /* IORESP_READY -> NONE */
hvm_io_assist();
break;
}
}
hvm_io_assist中将p→state置为STATE_IOREQ_NONE,将io_state置为HVMIO_none,写IO到此是走完了全程。
void hvm_io_assist(void) { struct vcpu *curr = current; ioreq_t *p = get_ioreq(curr); enum hvm_io_state io_state;
rmb(); /* see IORESP_READY /then/ read contents of ioreq */
p->state = STATE_IOREQ_NONE;
io_state = curr->arch.hvm_vcpu.io_state; curr->arch.hvm_vcpu.io_state =HVMIO_none; |
非string读指令
此类指令在进入hvmemul_do_io后,因为其p_date不为NULL,所以会将io_state置为HVMIO_awaiting_completion。
curr->arch.hvm_vcpu.io_state = (p_data == NULL) ? HVMIO_dispatched :HVMIO_awaiting_completion; |
在hvm_emulate_one返回后,会将io_state置为HVMIO_handle_mmio_awaiting_completion。
int handle_mmio(void) { struct hvm_emulate_ctxt ctxt; struct vcpu *curr = current; int rc;
hvm_emulate_prepare(&ctxt, guest_cpu_user_regs());
rc = hvm_emulate_one(&ctxt);
if ( curr->arch.hvm_vcpu.io_state == HVMIO_awaiting_completion ) curr->arch.hvm_vcpu.io_state =HVMIO_handle_mmio_awaiting_completion; |
于是,在进入hvm_io_assist后,将io_state置为HVMIO_completed,并二次进入handle_mmio。
if ( (io_state == HVMIO_awaiting_completion) ||
(io_state ==HVMIO_handle_mmio_awaiting_completion) )
{
curr->arch.hvm_vcpu.io_state =HVMIO_completed;
curr->arch.hvm_vcpu.io_data = p->data;
if ( io_state == HVMIO_handle_mmio_awaiting_completion )
(void)handle_mmio();
}
于是二次进入hvmemul_do_io,将io_state置为HVMIO_none。读指令返回X86EMUL_OKAY,模拟成功。
switch ( curr->arch.hvm_vcpu.io_state )
{
case HVMIO_none:
break;
case HVMIO_completed:
curr->arch.hvm_vcpu.io_state =HVMIO_none;
if ( p_data == NULL )
return X86EMUL_UNHANDLEABLE;
goto finish_access;
关于eip
x86_emulate中做指令模拟的具体动作,若模拟成功返回X86EMUL_OKAY,若需要重试模拟同一条指令返回X86EMUL_RETRY,若遇到无法处理的状况返回X86EMUL_UNHANDLEABLE,若遇到异常返回X86EMUL_EXCEPTION。
若返回X86EMUL_OKAY,则表示可以执行下一条指令,会走回写流程,将改变后的eip值写回寄存器,否则直接返回,则寄存器中eip值不变,下次进入还执行当前指令。
int
x86_emulate(
struct x86_emulate_ctxt *ctxt,
const struct x86_emulate_ops *ops)
{
writeback:
/* Commit shadow register state. */
_regs.eflags &= ~EFLG_RF;
*ctxt->regs = _regs;
done:
return rc;
2013年5月22日上传