Virtualbox源码分析19 Trap Monitor

TRPM - The Trap Monitor (陷阱监视器)负责管理和向虚拟机内发送中断。

19.1 初始化API等

TRPMCPU

每个VCPU都一个的结构体TRPMCPU,保存了发送给当前VCPU的中断信息,当TRPMCPU里有active的中断信息的时候,进入GuestOS之前需要发送这个中断信息到GuestOS里

typedef struct TRPMCPU
{
		//中断LVT
    uint32_t                uActiveVector;
    //中断类型,硬件/软件/陷阱
    TRPMEVENT               enmActiveType;
    //中断错误码
    uint32_t                uActiveErrorCode;
    //发生中断的指令长度(BP,OF使用)
    uint8_t                 cbInstr;
    //标记DB异常是否是INT1指令触发的
    bool                    fIcebp;
    //部分中断CR2里保存发送中断的内存地址
    RTGCUINTPTR             uActiveCR2;
} TRPMCPU;

TRPMR3Init

初始化函数

VMMR3DECL(int) TRPMR3Init(PVM pVM)
{
  //初始化每个VCPU,没有active的中断
  for (VMCPUID i = 0; i < pVM->cCpus; i++)
  {
    PVMCPU pVCpu = pVM->apCpusR3[i];
    pVCpu->trpm.s.uActiveVector = ~0U;
  }
  //注册SSM callback
  rc = SSMR3RegisterInternal(pVM, "trpm", 1, TRPM_SAVED_STATE_VERSION, sizeof(TRPM),
                               NULL, NULL, NULL,
                               NULL, trpmR3Save, NULL,
                               NULL, trpmR3Load, NULL);
  //注册调试callback
  rc = DBGFR3InfoRegisterInternalEx(pVM, "trpmevent", "Dumps TRPM pending event.", trpmR3InfoEvent,
                                      DBGFINFO_FLAGS_ALL_EMTS);
}

TRPMR3Reset

重置函数,每个VCPU的active 中断设置成0(没有active中断)

VMMR3DECL(void) TRPMR3Reset(PVM pVM)
{
    for (VMCPUID i = 0; i < pVM->cCpus; i++)
        TRPMR3ResetCpu(pVM->apCpusR3[i]);
    TRPMR3Relocate(pVM, 0);
}
VMMR3DECL(void) TRPMR3ResetCpu(PVMCPU pVCpu)
{
    pVCpu->trpm.s.uActiveVector = ~0U;
}

19.2 发送中断

向GuestOS发送一个中断

VMMR3DECL(int) TRPMR3InjectEvent(PVM pVM, PVMCPU pVCpu, TRPMEVENT enmEvent, bool *pfInjected)
{
	uint8_t u8Interrupt = 0;
  //从APIC里获取最高优先级的pending的中断
  int rc = PDMGetInterrupt(pVCpu, &u8Interrupt);
  if (!VM_IS_NEM_ENABLED(pVM))
  {
    //设置中断到TRPM中,下次进入GuestOS之前执行中断
    rc = TRPMAssertTrap(pVCpu, u8Interrupt, TRPM_HARDWARE_INT);
  }
  else
  {
    //hyper-V(NEM)模式,直接模拟执行处理中断
    VBOXSTRICTRC rcStrict = IEMInjectTrap(pVCpu, u8Interrupt, enmEvent, 0, 0, 0);
    if (rcStrict != VINF_SUCCESS)
      return VBOXSTRICTRC_TODO(rcStrict);
  }
}

19.3 处理中断:

VMX和raw-mode模式向GuestOS注入中断的方法不同

HM模式:

通过写入VMCS发送中断到GuestOS,VMX帮我们完成中断注入功能。

static VBOXSTRICTRC hmR0VmxPreRunGuest(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient, bool fStepping)
{
  ...
  //如果TRPM里有active的中断,把这个中断转成VMX认识的中断event
  if (TRPMHasTrap(pVCpu))
  {
    hmR0VmxTrpmTrapToPendingEvent(pVCpu);
  }
  
  uint32_t fIntrState;
  rcStrict = hmR0VmxEvaluatePendingEvent(pVCpu, pVmxTransient, &fIntrState);
  //通过VMX注入中断
  hmR0VmxInjectPendingEvent(pVCpu, pVmxTransient, fIntrState, fStepping);
}

hmR0VmxTrpmTrapToPendingEvent

static void hmR0VmxTrpmTrapToPendingEvent(PVMCPUCC pVCpu)
{
	//从TRPM 里获取中断的所有信息
  int rc = TRPMQueryTrapAll(pVCpu, &uVector, &enmTrpmEvent, &uErrCode, &GCPtrFaultAddress, &cbInstr, &fIcebp);
  uint32_t u32IntInfo;
  u32IntInfo  = uVector | VMX_IDT_VECTORING_INFO_VALID;
  //中断类型从TRPMtype转成VmxEventType
  u32IntInfo |= HMTrpmEventTypeToVmxEventType(uVector, enmTrpmEvent, fIcebp);
	//中断已经被获取了,TRPM重置成初始值
  rc = TRPMResetTrap(pVCpu);
  //告诉HM,有中断到来,并保存相关信息
  hmR0VmxSetPendingEvent(pVCpu, u32IntInfo, cbInstr, uErrCode, GCPtrFaultAddress);
}
VMMDECL(int) TRPMQueryTrapAll(PVMCPU pVCpu, uint8_t *pu8TrapNo, TRPMEVENT *pEnmType, uint32_t *puErrorCode, PRTGCUINTPTR puCR2,
                              uint8_t *pcbInstr, bool *pfIcebp)
{
  if (pu8TrapNo)
    *pu8TrapNo      = (uint8_t)pVCpu->trpm.s.uActiveVector;
  if (pEnmType)
    *pEnmType       = pVCpu->trpm.s.enmActiveType;
  if (puErrorCode)
    *puErrorCode    = pVCpu->trpm.s.uActiveErrorCode;
  if (puCR2)
    *puCR2          = pVCpu->trpm.s.uActiveCR2;
  if (pcbInstr)
    *pcbInstr       = pVCpu->trpm.s.cbInstr;
  if (pfIcebp)
    *pfIcebp        = pVCpu->trpm.s.fIcebp;
}
DECLINLINE(void) hmR0VmxSetPendingEvent(PVMCPUCC pVCpu, uint32_t u32IntInfo, uint32_t cbInstr, uint32_t u32ErrCode,
                                        RTGCUINTPTR GCPtrFaultAddress)
{
  	//设置有中断到来
    pVCpu->hm.s.Event.fPending          = true;
    //中断的信息等
    pVCpu->hm.s.Event.u64IntInfo        = u32IntInfo;
    pVCpu->hm.s.Event.u32ErrCode        = u32ErrCode;
    pVCpu->hm.s.Event.cbInstr           = cbInstr;
    pVCpu->hm.s.Event.GCPtrFaultAddress = GCPtrFaultAddress;
}
 

hmR0VmxEvaluatePendingEvent

static VBOXSTRICTRC hmR0VmxEvaluatePendingEvent(PVMCPUCC pVCpu, PCVMXTRANSIENT pVmxTransient, uint32_t *pfIntrState)
{
	*pfIntrState = hmR0VmxGetGuestIntrStateAndUpdateFFs(pVCpu);
  //判断一个中断是否需要注入到GuestOS里
  if (   !pVCpu->hm.s.Event.fPending
        && !VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INHIBIT_INTERRUPTS))
  {
    //之前没有中断在处理,而且中断没有被禁止,正常处理中断
    //但需要从APIC里获取一个优先级最高的pending中断
    //高优先级处理NMI中断
    PCCPUMCTX pCtx = &pVCpu->cpum.GstCtx;
    if (VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_NMI))
    {
      //如果没有block nmi 中断
      if (!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_BLOCK_NMIS))
      {
        //设置注入nmi中断
        hmR0VmxSetPendingXcptNmi(pVCpu);
        //中断已注入,清除掉有nmi中断到标记
        VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_NMI);
        return VINF_SUCCESS;
      }
      //block了nmi中断,让GuestOS触发一个NMI的vmexit ?
      else if (!fIsNestedGuest)
        hmR0VmxSetNmiWindowExitVmcs(pVCpu, pVmcsInfo);
    }
    //外部中断
    if (    VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC)
            && !pVCpu->hm.s.fSingleInstruction)
    {
      if (pCtx->eflags.u32 & X86_EFL_IF)
      {
        //获取优先级最高的pending中断
        uint8_t u8Interrupt;
        rc = PDMGetInterrupt(pVCpu, &u8Interrupt);
        if (RT_SUCCESS(rc))
        {
          //设置外部中断
          hmR0VmxSetPendingExtInt(pVCpu, u8Interrupt);
        }
        else if (rc == VERR_APIC_INTR_MASKED_BY_TPR)
        {
          //没有找到中断优先级比当前TPR高的中断(都被屏蔽了)。设置TPR threshold
          if (   !fIsNestedGuest
              && (pVmcsInfo->u32ProcCtls & VMX_PROC_CTLS_USE_TPR_SHADOW))
            hmR0VmxApicSetTprThreshold(pVmcsInfo, u8Interrupt >> 4);
        }
        return VINF_SUCCESS;
      }
      else if (!fIsNestedGuest)
        //中断被屏蔽了,设置让GuestOS尽快触发外部中断VMExit
        hmR0VmxSetIntWindowExitVmcs(pVCpu, pVmcsInfo);
    }
  }
  else if (!fIsNestedGuest)
  {
    //正在处理中断或者中断被屏蔽了
    //如果新的中断是NMI,设置让GuestOS尽快触发NMI vmexit
    if (VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_NMI))
      hmR0VmxSetNmiWindowExitVmcs(pVCpu, pVmcsInfo);
    //外部中断,设置让GuestOS尽快触发外部中断VMExit
    else if (   VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC)
             && !pVCpu->hm.s.fSingleInstruction)
      hmR0VmxSetIntWindowExitVmcs(pVCpu, pVmcsInfo);
  }
}
static VBOXSTRICTRC hmR0VmxInjectPendingEvent(PVMCPUCC pVCpu, PCVMXTRANSIENT pVmxTransient, uint32_t fIntrState, bool fStepping)
{
  if (pVCpu->hm.s.Event.fPending)
  {
    //异常信息写入VMCS里,hmR0VmxInjectEventVmcs代码解析在(VMM虚拟化实现源码分析3 HMVMXR0.cpp)一篇里介绍过,这里不再介绍
    uint32_t const uIntType = VMX_ENTRY_INT_INFO_TYPE(pVCpu->hm.s.Event.u64IntInfo);
    rcStrict = hmR0VmxInjectEventVmcs(pVCpu, pVmxTransient, &pVCpu->hm.s.Event, fStepping, &fIntrState);
  }
  //如果设置了单步中断,需要注入一个VMX_VMCS_GUEST_PENDING_DEBUG_XCPTS
  if (   (fIntrState & (VMX_VMCS_GUEST_INT_STATE_BLOCK_STI | VMX_VMCS_GUEST_INT_STATE_BLOCK_MOVSS))
        && !pVmxTransient->fIsNestedGuest)
  {
    //异常执行多条指令
    if (!pVCpu->hm.s.fSingleInstruction)
    {
      uint8_t const fTrapFlag = !!(pVCpu->cpum.GstCtx.eflags.u32 & X86_EFL_TF);
      int rc = VMXWriteVmcsNw(VMX_VMCS_GUEST_PENDING_DEBUG_XCPTS, fTrapFlag << VMX_BF_VMCS_PENDING_DBG_XCPT_BS_SHIFT);
    }
    else 
    {
      //本来就是单条指令执行,无视,设置DRx寄存器被改写
      ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_GUEST_DR_MASK);
		}
	}
  //写入VMCS的VMX_VMCS32_GUEST_INT_STATE,中断类型
  int rc = VMXWriteVmcs32(VMX_VMCS32_GUEST_INT_STATE, fIntrState);
}


VmxExitToRing3的时候,如果发现有需要处理的中断,会把VMX中断转换成TRPM中断,因为下一次执行的之前才能确定是HM模式还是RAW-mode模式,两种模式处理中断的方法不一样。

hmR0VmxPendingEventToTrpmTrap

static void hmR0VmxPendingEventToTrpmTrap(PVMCPUCC pVCpu)
{
	uint32_t const  u32IntInfo  = pVCpu->hm.s.Event.u64IntInfo;
  uint32_t const  uVector     = VMX_IDT_VECTORING_INFO_VECTOR(u32IntInfo);
  TRPMEVENT const enmTrapType = HMVmxEventTypeToTrpmEventType(u32IntInfo);
  
  //初始化TRPM里的变量,并赋值enmTrapType和uVector
  int rc = TRPMAssertTrap(pVCpu, uVector, enmTrapType);
  //如果设置了errorcode有效位,设置errorcode到TRPM里
  if (VMX_IDT_VECTORING_INFO_IS_ERROR_CODE_VALID(u32IntInfo))
    TRPMSetErrorCode(pVCpu, pVCpu->hm.s.Event.u32ErrCode);
  //如果是PF,设置GCPtrFaultAddress(CR2)到TRPM里
  if (VMX_IDT_VECTORING_INFO_IS_XCPT_PF(u32IntInfo))
    TRPMSetFaultAddress(pVCpu, pVCpu->hm.s.Event.GCPtrFaultAddress);
  //int指令触发的中断,设置指令长度
  else if (VMX_IDT_VECTORING_INFO_TYPE(u32IntInfo) == VMX_IDT_VECTORING_INFO_TYPE_SW_INT)
    TRPMSetInstrLength(pVCpu, pVCpu->hm.s.Event.cbInstr);
	//int 3(BP)和 INT0(OF)指令,设置fIcebp项
  if (VMX_IDT_VECTORING_INFO_TYPE(u32IntInfo) == VMX_IDT_VECTORING_INFO_TYPE_PRIV_SW_XCPT)
    TRPMSetTrapDueToIcebp(pVCpu);
}

RAW-mode:

调用IEMInjectTrap构造异常栈,跳转到中断处理函数入口开始执行GuestOS代码。

VMM_INT_DECL(VBOXSTRICTRC) IEMInjectTrap(PVMCPUCC pVCpu, uint8_t u8TrapNo, TRPMEVENT enmType, uint16_t uErrCode, RTGCPTR uCr2,uint8_t cbInstr)
{
  //初始化IEM decoder
  iemInitDecoder(pVCpu, false);
  //根据TRAM的信息设置IEM的信息
  uint32_t fFlags;
  switch (enmType)
  {
    case TRPM_HARDWARE_INT:
      fFlags = IEM_XCPT_FLAGS_T_EXT_INT;
      //外部中断cr2和错误码都是0 (不生效)
      uErrCode = uCr2 = 0;
      break;
    case TRPM_SOFTWARE_INT:
      //软件中断 (into,int x,bound指令触发的中断)
      fFlags = IEM_XCPT_FLAGS_T_SOFT_INT;
      uErrCode = uCr2 = 0;
      break;
    case TRPM_TRAP:
      //陷阱中断
      fFlags = IEM_XCPT_FLAGS_T_CPU_XCPT;
      //PF,需要用到输入的CR2寄存器
      if (u8TrapNo == X86_XCPT_PF)
        fFlags |= IEM_XCPT_FLAGS_CR2;
      switch (u8TrapNo)
      {
        //这些中断需要用到输入的错误码
        case X86_XCPT_DF:
        case X86_XCPT_TS:
        case X86_XCPT_NP:
        case X86_XCPT_SS:
        case X86_XCPT_PF:
        case X86_XCPT_AC:
          fFlags |= IEM_XCPT_FLAGS_ERR;
          break;
      }
      break;
  }
  //iemRaiseXcptOrInt 构造异常栈,跳转到中断处理函数入口, 在IEM一篇里已经介绍过。
  VBOXSTRICTRC rcStrict = iemRaiseXcptOrInt(pVCpu, cbInstr, u8TrapNo, fFlags, uErrCode, uCr2);
}

连续代码模拟执行

IEM会连续模拟执行多条代码,当模拟到某条指令的时候发生异常,尝试模拟执行这个中断(调用IEMInjectTrap),如果模拟执行InjectTrap成功(返回VINF_SUCCESS),则会继续执行下面的指令,不会退出模拟执行循环。

IEMInjectTrpmEvent (emR3HmHandleRC里调用)

VMMDECL(VBOXSTRICTRC) IEMInjectTrpmEvent(PVMCPUCC pVCpu)
{
    uint8_t     u8TrapNo;
    TRPMEVENT   enmType;
    uint32_t    uErrCode;
    RTGCUINTPTR uCr2;
    uint8_t     cbInstr;
  	//获取当前CPU的中断信息
    int rc = TRPMQueryTrapAll(pVCpu, &u8TrapNo, &enmType, &uErrCode, &uCr2, &cbInstr, NULL /* fIcebp */);
    if (RT_FAILURE(rc))
        return rc;
   	//raw-mode模拟执行注入异常到GuestOS里
    VBOXSTRICTRC rcStrict = IEMInjectTrap(pVCpu, u8TrapNo, enmType, uErrCode, uCr2, cbInstr);
    //如果执行成功,或者又有新的中断生成,都需要reset TRPM
    if (   rcStrict == VINF_SUCCESS
        || rcStrict == VINF_IEM_RAISED_XCPT)
        TRPMResetTrap(pVCpu);
    return rcStrict;
}
发布了26 篇原创文章 · 获赞 10 · 访问量 1233

猜你喜欢

转载自blog.csdn.net/qq_29684547/article/details/104219007