IO指令模拟流程

以写指令为例

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日上传

猜你喜欢

转载自blog.csdn.net/tdaajames/article/details/107517931