giovedì 11 novembre 2021

04.E - VMCS: VM-entry e VM-exit control (ed information)

Le ultime tre aree che compongono la VMCS verranno esaminate in questa lezione e completeranno il discorso sull'inizializzazione dei campi che si trovano in tale struttura. La seguente immagine mostra solo due di queste tre aree ma quella mancante (VM-Entry Control) non è certamente meno importante.






VM-Entry Control

Il compito della VM-entry control è quello di indicare i comportamenti ed il contesto esecutivo durante le VM entry. Il primo campo di quest'area è un campo a 32 bit omonimo (chiamato cioè VM-entry control, come l'area che lo contiene). Si tratta di un campo di bit e la seguente immagine mostra le definizioni di quei bit per cui ne è stata fornita una.




Al momento il bit più importante è il nono (IA-32e mode guest), che indica se dopo una VM entry il processore sarà in IA-32e mode: ciò si verifica se CR0.PG = 1 e IA32_EFER.LME = 1. Tale condizione è quasi sempre soddisfatta per due motivi:

- Il processore, una volta entrato in long mode, difficilmente ne esce con una contemporanea disabilitazione della possibilità di rientrarci (anche momentanea). Quindi, quasi sempre si avrà IA32_EFER.LME = 1 (il bit LME dell'MSR IA32_EFER; LME sta per long mode enable)

- Per quanto visto nella sezione "Limitazione della VMX operation" in [4], il paging, salvo rare eccezioni, è sempre abilitato. Quindi si avrà quasi sempre CR0.PG = 1. 

Per il resto, spesso vengono impostati ad 1 anche i bit 2 (per compatibilità con i vecchi processori) e 17 (per efficienza e risparmio di risorse). I bit per cui non è fornita una definizione sono riservati e necessitano di una impostazione specifica a 0 o ad 1. Per conoscere quale dei due valori bisogna impostare per ognuno dei bit riservati si può leggere l'MSR IA32_VMX_ENTRY_CTLS oppure IA32_VMX_TRUE_ENTRY_CTLS (consultare l'MSR IA32_VMX_BASIC per sapere quale tra questi due MSR è necessario leggere).

Il campo successivo a quello appena visto offre la possibilità di indicare quali MSR leggere ad ogni VM entry. Al momento non è fondamentale approfondire tale argomento.

Gli ultimi tre campi del VM-entry control offrono la possibilità di iniettare eventi tramite la IDT (interrupt descriptor table) dopo una VM entry (esattamente dopo che lo stato del guest è stato caricato in quello del processore e gli eventuali MSR, indicati dal precedente campo, letti). Il primo di questi tre campi è un campo di bit denominato VM-Entry Interruption-Information che si occupa di indicare quale evento iniettare.




Gli altri due campi, invece, sono campi a 32 bit e forniscono informazioni aggiuntive.




Maggiori dettagli verranno forniti al momento opportuno.




VM-Exit Control

Il compito della VM-exit control è quello di indicare i comportamenti ed il contesto esecutivo durante le VM exit. Il primo campo di quest'area è un campo a 32 bit omonimo (chiamato cioè VM-exit control, come l'area che lo contiene). Si tratta di un campo di bit e la seguente immagine mostra le definizioni di quei bit per cui ne è stata fornita una.




Al momento il bit più importante è il nono (Host address-space size), che indica se dopo una VM exit il processore sarà in IA-32e mode. Tale condizione è quasi sempre soddisfatta per gli stessi motivi visti nella sezione precedente. Per il resto, spesso vengono impostati ad 1 anche i bit 2 (per compatibilità con i vecchi processori), 15 (per disporre di maggiori info) e 24 (per efficienza e risparmio di risorse).
I bit per cui non è fornita una definizione sono riservati e necessitano di una impostazione specifica a 0 o ad 1. Per conoscere quale dei due valori bisogna impostare per ognuno dei bit riservati si può leggere l'MSR IA32_VMX_EXIT_CTLS oppure IA32_VMX_TRUE_EXIT_CTLS (consultare l'MSR IA32_VMX_BASIC per sapere quale tra questi due MSR è necessario leggere).

Il campo successivo a quello appena visto offre la possibilità di indicare quali MSR leggere e scrivere ad ogni VM exit. Al momento non è fondamentale approfondire tale argomento.




VM-Exit Information

La VM-Exit Information è l'unica area della VMCS per cui non è richiesta esplicita inizializzazione in quanto è memoria di output per il processore e di input per il nostro hypervisor. In particolare, contiene informazioni utili su cosa abbia causato l'ultima VM exit: causa (esecuzione di una istruzione, lancio di un'eccezione, interrupt), guest RIP al momento della VM exit, lunghezza dell'istruzione che ha causato la VM exit (se è quello il motivo che l'ha causata), ecc. Al momento sarebbe abbastanza tedioso approfondire ulteriormente in quanto tale area non contiene campi che bisogna impostare ma solo leggere. Per tale motivo risulta sicuramente più efficace entrare nei dettagli solo al momento opportuno.




Codice

Il seguente codice mostra un esempio di inizializzazione per i campi contenuti sia nella VM-entry che nella VM-exit control.

// VM-Entry Control
typedef union {
    struct {
        UINT32 Reserved_0 : 2;
        UINT32 LoadDebugControls : 1;
        UINT32 Reserved_3 : 6;
        UINT32 IA32eModeGuest : 1;
        UINT32 EntryToSMM : 1;
        UINT32 DeactivateDualMonitorTreatment : 1;
        UINT32 Reserved_12 : 1;
        UINT32 LoadIA32PerfGlobalCtrl : 1;
        UINT32 LoadIA32PAT : 1;
        UINT32 LoadIA32EFER : 1;
        UINT32 LoadIA32BNDCFGS : 1;
        UINT32 ConcealVMXFromPT : 1;
        UINT32 LoadIA32RtitCtl : 1;
        UINT32 Reserved_19 : 1;
        UINT32 LoadCETState : 1;
        UINT32 Reserved_21 : 1;
        UINT32 LoadPKRS : 1;
    } Bits;
    UINT32 Uint32;
} VM_ENTRY_CONTROL, * PVM_ENTRY_CONTROL;
 
 
// VM-Exit Control
typedef union {
    struct {
        UINT32 Reserved_0 : 2;
        UINT32 SaveDebugControls : 1;
        UINT32 Reserved_3 : 6;
        UINT32 HostAddressSpaceSize : 1;
        UINT32 Reserved_10 : 2;
        UINT32 LoadIA32PerfGlobalCtrl : 1;
        UINT32 Reserved_13 : 2;
        UINT32 AcknowledgeIntOnExit : 1;
        UINT32 Reserved_16 : 2;
        UINT32 SaveIA32PAT : 1;
        UINT32 LoadIA32PAT : 1;
        UINT32 SaveIA32EFER : 1;
        UINT32 LoadIA32EFER : 1;
        UINT32 SaveVMXPreemptionTimerValue : 1;
        UINT32 ClearIA32BNDCFGS : 1;
        UINT32 ConcealVMXFromPT : 1;
        UINT32 ClearIA32RtitCtl : 1;
        UINT32 Reserved_26 : 2;
        UINT32 LoadCETState : 1;
        UINT32 LoatPKRS : 1;
    } Bits;
    UINT32 Uint32;
} VM_EXIT_CONTROL, * PVM_EXIT_CONTROL;

// VM-Entry e VM-Exit Control
VM_ENTRY_CONTROL entry_controls = { 0 };
VM_EXIT_CONTROL exit_controls   = { 0 };
 
 
// Imposta bit specifici.
SetVmEntryCtls(&entry_controls);
SetVmExitCtls(&exit_controls);
 
 
// Legge MSR IA32_VMX_BASIC
IA32_VMX_BASIC_MSR vmx_basic = { 0 };
vmx_basic.Uint64 = __readmsr(MSR_IA32_VMX_BASIC);


// Inizializza VM-exit e VM-entry control in VMCS
__vmx_vmwrite(CONTROL_VM_ENTRY_CONTROLS,
	AdjustControls(entry_controls.Uint32,
		vmx_basic.Bits.VmxTrueControls ? MSR_IA32_VMX_TRUE_ENTRY_CTLS : MSR_IA32_VMX_ENTRY_CTLS));

__vmx_vmwrite(CONTROL_VM_EXIT_CONTROLS,
	AdjustControls(exit_controls.Uint32,
		vmx_basic.Bits.VmxTrueControls ? MSR_IA32_VMX_TRUE_EXIT_CTLS : MSR_IA32_VMX_EXIT_CTLS));
 

Il metodo AdjustControls è lo stesso visto nella lezione precedente. I metodi SetVmEntryCtls e SetVmExitCtls impostano i bit nei campi omonomi della VM-entry e della VM-exit control. La loro implementazione dipende dal contesto applicativo ma alcuni bit è probabile che vengano attivati a prescindere dal tipo di applicazione, come mostra il seguente estratto di codice.

void SetVmEntryCtls(PVM_ENTRY_CONTROL entry_control)
{
	/**
	* This control determines whether DR7 and the IA32_DEBUGCTL MSR are loaded on VM entry.
	* The first processors to support the virtual-machine extensions supported only the 1-setting of
	* this control.
	*/
	entry_control->Bits.LoadDebugControls = TRUE;
 
	/**
	* On processors that support Intel 64 architecture, this control determines whether the logical
	* processor is in IA-32e mode after VM entry. Its value is loaded into IA32_EFER.LMA as part of
	* VM entry.
	* This control must be 0 on processors that do not support Intel 64 architecture.
	*/
	entry_control->Bits.IA32eModeGuest = TRUE;
 
	/**
	* If this control is 1, Intel Processor Trace does not produce a paging information packet (PIP) on
	* a VM entry or a VMCS packet on a VM entry that returns from SMM (see Chapter 35).
	*/
	entry_control->Bits.ConcealVMXFromPT = TRUE;


	// ...

}

void SetVmExitCtls(PVM_EXIT_CONTROL exit_control)
{
	/**
	* This control determines whether DR7 and the IA32_DEBUGCTL MSR are saved on VM exit.
	* The first processors to support the virtual-machine extensions supported only the 1-
	* setting of this control.
	*/
	exit_control->Bits.SaveDebugControls = TRUE;
 
	/**
	* On processors that support Intel 64 architecture, this control determines whether a logical
	* processor is in 64-bit mode after the next VM exit. Its value is loaded into CS.L,
	* IA32_EFER.LME, and IA32_EFER.LMA on every VM exit.
	* This control must be 0 on processors that do not support Intel 64 architecture.
	*/
	exit_control->Bits.HostAddressSpaceSize = TRUE;
 
	/**
	* This control affects VM exits due to external interrupts:
	* • If such a VM exit occurs and this control is 1, the logical processor acknowledges the
	*   interrupt controller, acquiring the interrupt’s vector. The vector is stored in the VM-exit
	*   interruption-information field, which is marked valid.
	* • If such a VM exit occurs and this control is 0, the interrupt is not acknowledged and the
	*   VM-exit interruption-information field is marked invalid.
	*/
	exit_control->Bits.AcknowledgeIntOnExit = TRUE;

 
	// ...

}




Riferimenti:

[5] Intel Software Developer's Manual Vol. 3C

Nessun commento:

Posta un commento