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;
}