mercoledì 10 novembre 2021

04.D - VMCS: VM-execution control

La VM-execution control contiene tutti quei campi in grado di controllare e governare l'esecuzione in non-root operation. In particolare, attraverso tali campi l'hypervisor può indicare quali sono le istruzioni eseguite e gli eventi generati dal guest che provocano una VM exit, e che quindi devono cedergli il controllo al fine di controllare l'esito finale di tali operazioni. L'immagine seguente fornisce una rappresentazione visiva della VM-execution control. Gli elementi con sfondo bianco rappresentano bit che appartengono al campo di bit arancione sull'estrema sinistra.




Verranno tralasciati (cioè impostati a zero) tutti quei campi\bit ritenuti marginali o non essenziali per un corso di base sulla virtualizzazione delle CPU. Allo stesso modo, verranno momentaneamente inizializzati a zero alcuni campi\bit particolarmente importanti nelle fasi successive del corso. Nelle seguenti sezioni verranno esaminati brevemente molti campi di interesse che compongono la VM-execution control. Per alcuni di questi campi verrà fornita una spiegazione più approfondita nel corso delle successive lezioni.




Pin-Based

Questo è un campo a 32 bit che si può considerare come un campo di bit. Permette di controllare gli eventi asincroni (interrupt) durante la non-root operation. In particolare, alcuni bit sono definiti come collegati ad un particolare interrupt. Se uno di questi bit è impostato ad 1 ed il relativo interrupt viene generato in non-root operation allora questo fatto causerà una VM exit. L'immagine seguente mostra i bit per cui è fornita una definizione.




Gli altri bit 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_PINBASED_CTLS oppure IA32_VMX_TRUE_PINBASED_CTLS (consultare l'MSR IA32_VMX_BASIC per sapere quale tra questi due MSR è necessario leggere).




Primary Processor-Based 

Questo è un campo a 32 bit che si può considerare come un campo di bit. Permette abilitare opzioni aggiuntive e di controllare alcuni eventi sincroni (in genere l'esecuzione di alcune istruzioni) durante la non-root operation. In particolare, se uno di questi bit è impostato ad 1, l'esecuzione dell'istruzione collegata causerà una VM exit. L'immagine seguente mostra i bit per cui è fornita una definizione.




Gli altri bit 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_PROCBASED_CTLS oppure IA32_VMX_TRUE_PROCBASED_CTLS (consultare l'MSR IA32_VMX_BASIC per sapere quale tra questi due MSR è necessario leggere).
Se il bit 31 è impostato ad 1 viene attivato un secondo campo per il controllo degli eventi sincroni (argomento della prossima sezione). Il bit 17 permette di attivarne un terzo ma questo non sarà argomento di discussione in questo corso.




Secondary Processor-Based

Questo è un campo a 32 bit che si può considerare come un campo di bit. Permette di abilitare opzioni aggiuntive e di controllare alcuni eventi sincroni (in genere la generazione di eccezioni a causa dell'esecuzione di una particolare istruzione) durante la non-root operation. Se il bit 31 del primary processor-based è zero allora tutti i bit del secondary processor-based saranno considerati come impostati a zero. L'immagine seguente mostra i bit per cui è fornita una definizione.




Gli altri bit 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_PROCBASED_CTLS2.




Exception Bitmap

Campo a 32 bit in cui ogni bit identifica un'eccezione diversa. Se durante la non-root operation avviene una eccezione ed il relativo bit in questo campo è impostato ad 1 allora si avrà una VM exit. Altrimenti tale eccezione viene consegnata al guest normalmente attraverso la IDT (interrupt descriptor table) usando il descrittore corrispondente al valore numerico abbinato all'eccezione (in genere tale valore è conosciuto con il termine vettore).




I/O Bitmap Addresses

Si tratta di due campi a 64 bit che contengono gli indirizzi di due pagine (A e B) di 4 KB ciascuna e dove ogni bit corrisponde ad una porta di I/O. Se il bit "Use I/O bitmaps" del primary processor-based è impostato ad 1 allora una VM exit si verifica ogni volta che si esegue una istruzione di I/O su una porta il cui corrispondente bit (nella pagina A o B) è impostato ad 1. Se il bit "Use I/O bitmaps" è 0 l'istruzione è eseguita normalmente.




Guest/Host Masks e Read Shadows (CR0 e CR4)

La dimensione di questi campi dipende dall'architettura. Un read shadow è un valore che indica quali dovrebbero essere i valori dei bit in CR0 o CR4 e che il guest dovrebbe vedere. Una guest/host mask è invece una maschera di bit che permette di controllare ogni bit di tali registri. Se un bit della maschera è 1 allora il bit corrispondente nei registri CR0 o CR4 appartiene all'host. Questo significa che ogni tentativo, da parte del guest, di scrivere tale bit con valore diverso dal bit corrispondente nel read shadow causa una VM exit. Ogni lettura invece ritorna il bit corrispondente nel read shadow.
Se, invece, un bit della maschera è 0 vuol dire che tale bit appartiene al guest e ogni lettura e scrittura coinvolge direttamente i registri CR0 o CR4 (non c'è alcun filtro da parte del read shadow).


(fonte https://revers.engineering/)




MSR-Bitmap Address

Si tratta dell'indirizzo di una pagina di 4 KB. Tale pagina conterrà 4 bitmap (area di memoria in cui ogni bit è definito ed ha un significato) di 1 KB ciascuna e dove ogni bit corrisponde ad un MSR. Tali bitmap sono usate solo se il bit "Use MSR bitmaps" del primary processor-based è impostato ad 1. Di seguito sono riportate le definizioni delle 4 bitmap ed il loro significato.

Read bitmap for low MSR: bitmap degli MSR che hanno valori da 0x00000000 ad 0x00001FFF (tale range copre esattamente 8192 valori; 1 KB = 1024 byte = 8192 bit). Eseguire una lettura di un MSR (tramite RDMSR) il cui corrispondente bit nella bitmap è impostato ad 1 causa una VM exit.

Read bitmap for high MSR: bitmap degli MSR che hanno valori da 0xC0000000 ad 0xC0001FFF. Eseguire una lettura di un MSR (tramite RDMSR) il cui corrispondente bit nella bitmap è impostato ad 1 causa una VM exit. Si trova esattamente a 1024 bit dall'indirizzo fornito nel campo MSR-Bitmap Address della VM-execution control.

Write bitmap for low MSR: bitmap degli MSR che hanno valori da 0x00000000 ad 0x00001FFF. Eseguire una scrittura di un MSR (tramite WRMSR) il cui corrispondente bit nella bitmap è impostato ad 1 causa una VM exit. Si trova esattamente a 2048 bit dall'indirizzo fornito nel campo MSR-Bitmap Address della VM-execution control.

Write bitmap for high MSR: bitmap degli MSR che hanno valori da 0xC0000000 ad 0xC0001FFF. Eseguire una scrittura di un MSR (tramite WRMSR) il cui corrispondente bit nella bitmap è impostato ad 1 causa una VM exit.Si trova esattamente a 3072 bit dall'indirizzo fornito nel campo MSR-Bitmap Address della VM-execution control.

Se vengono usate tali bitmap (bit "Use MSR bitmaps" è 1), nella lettura o nella scrittura di un MSR, una VM exit è causata anche se il valore del registro RCX (i cui 32 bit bassi contengono il valore numerico dell'MSR da leggere o scrivere tramite RDMSR o WRMSR) è fuori dall'intervallo numerico di valori MSR definiti come validi per le 4 bitmap. Se il bit "Use MSR bitmaps" è 0 si avrà una VM exit ad ogni esecuzione di RDMSR o WRMSR.




Extended-Page-Table Pointer (EPTP)

Se non indicato altrimenti, ogni volta che il guest accede ad un indirizzo virtuale questo viene tradotto in indirizzo fisico in RAM nel modo consueto. 




In un ambiente virtualizzato questo non sempre è desiderabile perché vuol dire che lo spazio degli indirizzi fisici dell'hypervisor è lo stesso di quello del guest (in altre parole, il guest può "vedere" la stessa memoria dell'hypervisor). Per tale motivo è stato introdotto un sistema per virtualizzare gli accessi alla memoria da parte del guest in modo da dividere gli spazi di memoria fisici. Tale sistema prende il nome di Extended Page Table (EPT) e prevede che ogni indirizzo fisico del guest (ottenuto con la consueta traduzione vista in precedenza) sia tradotto una seconda volta per ottenere un indirizzo fisico dell'host. In questo modo l'hypervisor può riservare uno spazio dedicato e mappare lo spazio degli indirizzi fisici del guest ad esso, preservando ed isolando così il proprio spazio degli indirizzi fisici.
Quando un indirizzo fisico del guest è tradotto per ottenere un indirizzo fisico dell'host il risultato viene messo in cache così da non ripetere ogni volta la traduzione.  L'uso dell'EPT prevede che il bit "Enable EPT" del secondary processor-based sia impostato ad 1.
Un Extended-Page-Table pointer (EPTP) contiene l'indirizzo fisico di una ulteriore PML4, i cui elementi puntano ad una ulteriore page-directory-pointer table, i cui elementi puntano ad una ulteriore page-directory table, i cui elementi puntano ad una ulteriore page table, i cui elementi puntano a pagine fisiche nel sistema host (e che compongono lo spazio riservato dall'hypervisor a tale scopo). Alla EPT verrà dedicata un'intera lezione e ci sarà modo di fornire maggiori dettagli e chiarire tutti gli aspetti critici. La seguente immagine mostra la definizione dell'EPTP.




Dato che gli indirizzi fisici dei page frame hanno gli ultimi 12 bit nulli si può sfruttare questo fatto per inserire nell'EPTP anche informazioni aggiuntive. $N$ è il numero di bit (dei 64 disponibili) usati effettivamente dalla CPU per indirizzare la memoria fisica. Solitamente $N=48$ e dato che i bit $[12, 47]$ sono i 36 bit più significativi di un indirizzo fisico (considerando gli ultimi 12 azzerati) si può considerare tale campo di bit come il PFN del page frame che contiene la PML4 aggiuntiva.




Virtual-Processor Identifier (VPID)

Questo campo a 16 bit contiene l'identificatore del processore virtuale da associare alle informazioni che verranno salvate nella cache.
All'inizio, nei i primi processori in grado di fornire supporto per la virtualizzazione, ad ogni transizione in VMX operation (da root a non-root operation e viceversa) era necessario scaricare la cache in modo che il processore non utilizzasse indirizzi conservati nella cache che facevano riferimento allo spazio degli indirizzi usato da una modalità operativa differente da quella corrente. Con l'introduzione del virtual-processor identifier (VPID) per il processore è possibile taggare le informazioni nella cache per distinguere tra le due modalità. Il valore che assume tale identificatore è definito come nullo nei seguenti casi:

- Fuori dalla VMX operation
- In root operation
- In non-root operation se il bit "Enable VPID" del secondary processor-based è impostato ad 0.

Altrimenti, se il bit "Enable VPID" del secondary processor-based è impostato ad 1, VPID assumerà in non-root operation (cioè ad ogni VM entry) il valore conservato nel campo Virtual-Processor Identifier del VM-execution control.




Codice

Il seguente codice mostra l'inizializzazione di alcuni campi nella VM-execution control.

// Pin-Based VM-Execution Control
typedef union {
    struct {
        UINT32 ExternalInterruptExiting : 1;
        UINT32 Reserved_1 : 2;
        UINT32 NMIExiting : 1;
        UINT32 Reserved_4 : 1;
        UINT32 VirtualNMI : 1;
        UINT32 ActivateVMXPreemptionTimer : 1;
        UINT32 ProcessPostedInterrupts : 1;
    } Bits;
    UINT32 Uint32;
} PINBASED_CTLS, * PPINBASED_CTLS;
 
 
 
// Primary Processor-Based VM-Execution Control
typedef union {
    struct {
        UINT32 Reserved_0 : 2;
        UINT32 InterrupWindowsExiting : 1;
        UINT32 UseTSCOffseting : 1;
        UINT32 Reserved_4 : 3;
        UINT32 HLTExiting : 1;
        UINT32 Reserved_8 : 1;
        UINT32 INVLPGExiting : 1;
        UINT32 MWAITExiting : 1;
        UINT32 RDPMCExiting : 1;
        UINT32 RDTSCExiting : 1;
        UINT32 Reserved_13 : 2;
        UINT32 CR3LoadExiting : 1;
        UINT32 CR3StoreExiting : 1;
        UINT32 ActivateTertiaryControls : 1;
        UINT32 Reserved_18 : 1;
        UINT32 CR8LoadExiting : 1;
        UINT32 CR8StoreExiting : 1;
        UINT32 UseTPRShadow : 1;
        UINT32 NMIWindowExiting : 1;
        UINT32 MOVDRExiting : 1;
        UINT32 UnconditionalIoExiting : 1;
        UINT32 UseIoBitmaps : 1;
        UINT32 Reserved_26 : 1;
        UINT32 MonitorTrapFlag : 1;
        UINT32 UseMSRBitmaps : 1;
        UINT32 MONITORExiting : 1;
        UINT32 PAUSEExiting : 1;
        UINT32 ActivateSecondaryControls : 1;
    } Bits;
    UINT32 Uint32;
} PRIMARY_PROCBASED_CTLS, * PPRIMARY_PROCBASED_CTLS;
 
 
// Secondary Processor-Based VM-Execution Control
typedef union {
    struct {
        UINT32 VirtualizeAPICAccesses : 1;
        UINT32 EnableEPT : 1;
        UINT32 DescriptorTableExiting : 1;
        UINT32 EnableRDTSCP : 1;
        UINT32 VirtualizeX2APICMode : 1;
        UINT32 EnableVPID : 1;
        UINT32 WBINVDExiting : 1;
        UINT32 UnrestrictedGuest : 1;
        UINT32 APICRegisterVirtualization : 1;
        UINT32 VirtualInterruptDelivery : 1;
        UINT32 PAUSELoopExiting : 1;
        UINT32 RDRANDExiting : 1;
        UINT32 EnableINVPCID : 1;
        UINT32 EnableVMFunctions : 1;
        UINT32 VMCSShadowing : 1;
        UINT32 EnableENCLSExiting : 1;
        UINT32 RDSEEDExiting : 1;
        UINT32 EnablePML : 1;
        UINT32 EPTViolationVE : 1;
        UINT32 ConcealVMXFromPT : 1;
        UINT32 EnableXSAVESAndXRSTORS : 1;
        UINT32 Reserved_21 : 1;
        UINT32 ModeBasedExecuteControlForEPT : 1;
        UINT32 SubPageWritePermissionForEPT : 1;
        UINT32 IntelPTUsesGuestPA : 1;
        UINT32 UseTSCScaling : 1;
        UINT32 EnableUserWaitAndPause : 1;
        UINT32 Reserved_27 : 1;
        UINT32 EnableENCLVExiting : 1;
    } Bits;
    UINT32 Uint32;
} SECONDARY_PROCBASED_CTLS, * PSECONDARY_PROCBASED_CTLS;

// Campi della VM-execution control
PINBASED_CTLS pinbased_controls = { 0 };
PRIMARY_PROCBASED_CTLS primary_controls = { 0 };
SECONDARY_PROCBASED_CTLS secondary_controls = { 0 };


// Imposta\Attiva bit specifici nei campi della VM-execution control.
SetPinCtls(&pinbased_controls);
SetPrimaryCtls(&primary_controls);
SetSecondaryCtls(&secondary_controls);


// Legge MSR IA32_VMX_BASIC
IA32_VMX_BASIC_MSR vmx_basic = { 0 };
vmx_basic.Uint64 = __readmsr(MSR_IA32_VMX_BASIC);


// Imposta campi di VM-execution control
__vmx_vmwrite(CONTROL_PIN_BASED_VM_EXECUTION_CONTROLSAdjustControls(pinbased_controls.Uint32, 
		vmx_basic.Bits.VmxTrueControls ? MSR_IA32_VMX_TRUE_PINBASED_CTLS : MSR_IA32_VMX_PINBASED_CTLS));
 
__vmx_vmwrite(CONTROL_PRIMARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS,
	AdjustControls(primary_controls.Uint32, 
		vmx_basic.Bits.VmxTrueControls ? MSR_IA32_VMX_TRUE_PROCBASED_CTLS : MSR_IA32_VMX_PROCBASED_CTLS));
 
__vmx_vmwrite(CONTROL_SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLSAdjustControls(secondary_controls.Uint32, MSR_IA32_VMX_PROCBASED_CTLS2));

Dato che alcuni bit devono essere impostati a valori fissati negli MSR corrispondenti, è conveniente avere un'unica funzione che "aggiusta" i bit di tutti i campi.

UINT32 AdjustControls(UINT32 ctlUINT32 msr)
{
    MSR msr_value = { 0 };
    msr_value.Uint64 = __readmsr(msr);
    ctl &= msr_value.High;
    ctl |= msr_value.Low;
    return ctl;
}
I metodi SetXXXCtls impostano\attivano i bit nei campi del VM-execution control e dipendono dal contesto applicativo. Ad esempio, se si vuole attivare il secondary processor-based control è necessario impostare ad 1 il relativo bit nel primary processor-based control.

void SetPrimaryCtls(PPRIMARY_PROCBASED_CTLS primary_controls)
{

    // ...

 
    /**
    * This control determines whether the secondary processor-based VM-execution controls are
    * used. If this control is 0, the logical processor operates as if all the secondary 
    * processor-based VM-execution controls were also 0.
    */
    primary_controls->Bits.ActivateSecondaryControls = TRUE;
 
    // ...

}

Come detto, i bit da attivare dipendono dal contesto applicativo, nello specifico da cosa il nostro hypervisor intende monitorare. Per tale motivo è praticamente inutile mostrare il codice dei metodi SetXXXCtls in questo momento.




Riferimenti:

Nessun commento:

Posta un commento