Le istruzioni x86 richiedono che la loro codifica e tutti i loro argomenti siano presenti in memoria contemporaneamente?


64

Sto cercando di capire se è possibile eseguire una VM Linux la cui RAM è supportata solo da una singola pagina fisica.

Per simulare questo, ho modificato il gestore degli errori di pagina nidificata in KVM per rimuovere il bit presente da tutte le voci della tabella di pagine nidificate (NPT), tranne quella corrispondente all'errore di pagina attualmente elaborato.

Durante il tentativo di avviare un guest Linux, ho osservato che le istruzioni di assemblaggio che utilizzano operandi di memoria, come

add [rbp+0x820DDA], ebp

portare a un loop di errori di pagina fino a quando non ripristino il bit corrente per la pagina contenente l'istruzione e per la pagina a cui fa riferimento l'operando (in questo esempio [rbp+0x820DDA]).

Mi chiedo perché sia ​​così. La CPU non dovrebbe accedere alle pagine di memoria in sequenza, ovvero prima leggere le istruzioni e quindi accedere all'operando di memoria? Oppure x86 richiede che la pagina delle istruzioni e tutte le pagine degli operandi siano accessibili contemporaneamente?

Sto testando su AMD Zen 1.


2
Perché vorresti farlo?
SS Anne,

11
Appena per interesse tecnico :)
savvybug

14
Miglioramento per l'idea del progetto esilarante.
pipe

10
Ciò è folle a livello di "avvio di Linux su un emulatore 486 in esecuzione in JavaScript nel browser". Lo adoro.
Chrylis

3
Heh, a quanto pare ho portato questa domanda alla stessa logica conclusione che stavi già pensando, sul minimo set di lavoro per progressi diretti garantiti. Avevo già risposto che prima hai aggiunto quel nuovo primo paragrafo alla domanda. : PI ha aggiunto alcuni collegamenti e maggiori dettagli in alcuni punti (ad esempio il camminatore di pagine è autorizzato a memorizzare nella cache alcune voci della directory di pagine guest internamente) poiché questa domanda sta ottenendo molta più attenzione di quanto mi aspettassi grazie in qualche modo al passaggio a HNQ.
Peter Cordes,

Risposte:


56

Sì, richiedono il codice macchina e tutti gli operandi di memoria.

La CPU non dovrebbe accedere alle pagine di memoria in sequenza, ovvero prima leggere le istruzioni e quindi accedere all'operando di memoria?

Sì, è logicamente ciò che accade, ma un'eccezione di errore di pagina interrompe il processo in 2 passaggi e ignora qualsiasi progresso. La CPU non ha alcun modo di ricordare quale istruzione fosse nel mezzo di un errore di pagina.

Quando un gestore di errori di pagina ritorna dopo aver gestito un errore di pagina valido, RIP = l'indirizzo dell'istruzione di errore, quindi la CPU riprova a eseguirlo da zero .

Sarebbe legale per il sistema operativo modificare il codice macchina dell'istruzione di errore e aspettarsi che esegua un'istruzione diversa dopo iretdal gestore degli errori di pagina (o qualsiasi altra eccezione o gestore di interrupt). Quindi AFAIK è richiesto dal punto di vista architettonico che la CPU recuperi il recupero del codice da CS: RIP nel caso di cui stai parlando. (Supponendo che ritorni anche al CS in errore: RIP invece di pianificare un altro processo durante l'attesa del disco in caso di errore della pagina effettiva o la consegna di un SIGSEGV a un gestore di segnali in un errore di pagina non valido.)

Probabilmente è anche richiesto dal punto di vista architettonico per l'entrata / uscita dell'hypervisor. E anche se non è esplicitamente vietato sulla carta, non è come funzionano le CPU.

@torek osserva che alcuni microprocessori (CISC) decodificano parzialmente le istruzioni e scaricano lo stato di microregistrazione in un errore di pagina , ma x86 non è così.


Alcune istruzioni sono interrompibili e possono compiere progressi parziali, come rep movs(memcpy in a can) e altre istruzioni di stringa, oppure raccogliere carichi / negozi sparsi. Ma l'unico meccanismo è l'aggiornamento dei registri di architettura come RCX / RSI / RDI per operazioni su stringa o i registri di destinazione e maschera per le raccolte (ad es. Manuale per AVX2vpgatherdd ). La mancata conservazione del codice operativo / decodifica comporta alcuni registri interni nascosti e il riavvio dopo iret da un gestore degli errori di pagina. Queste sono istruzioni che eseguono più accessi separati ai dati.

Inoltre, tieni presente che x86 (come la maggior parte degli ISA) garantisce che le istruzioni siano atomiche. interruzioni / eccezioni: o si verificano completamente, o non accadono affatto, prima di un interruzione. Interruzione di un'istruzione di assemblaggio mentre è in funzione . Quindi, ad esempio, add [mem], regsarebbe necessario eliminare il carico se la parte del negozio era difettosa, anche senza un lockprefisso.


Il numero peggiore di pagine di spazio utente guest presenti per avanzare potrebbe essere 6 (più sottotitoli di tabella delle pagine del guest guest separati per ognuna):

  • movsqo movswistruzione a 2 byte che attraversa un limite di pagina, quindi entrambe le pagine sono necessarie per la decodifica.
  • L'operando di origine qword è [rsi]anche suddiviso in pagine
  • L'operando di destinazione qword è [rdi]anche suddiviso in pagine

Se una di queste 6 pagine si guasta, torniamo al punto di partenza.

rep movsdè anche un'istruzione a 2 byte e fare progressi su un passo di esso avrebbe lo stesso requisito. Casi simili come push [mem]o pop [mem]potrebbero essere costruiti con una pila disallineata.

Uno dei motivi (o benefici collaterali) per / di rendere "interrompibili" i carichi di raccolta / dispersione dei negozi (aggiornando il vettore maschera con i loro progressi) è di evitare di aumentare questo footprint minimo per eseguire una singola istruzione. Anche per migliorare l'efficienza della gestione di più guasti durante una raccolta o dispersione.


@Brandon sottolinea nei commenti che un guest avrà bisogno delle sue tabelle di pagine in memoria e che le suddivisioni di pagine dello spazio utente possono anche essere suddivisioni di 1GiB in modo che i due lati si trovino in sotto-alberi diversi del livello superiore PML4. La camminata della pagina HW dovrà toccare tutte queste pagine della tabella delle pagine degli ospiti per fare progressi. È improbabile che una situazione così patologica si verifichi per caso.

I TLB (e gli interni di page-walker) sono autorizzati a memorizzare nella cache alcuni dei dati della tabella delle pagine e non sono tenuti a riavviare la pagina da zero a meno che il sistema operativo non abbia invlpgimpostato o impostato una nuova directory di pagina di livello superiore CR3. Nessuno di questi è necessario quando si cambia una pagina da non presente a presente; x86 su carta garantisce che non è necessario (quindi la "memorizzazione nella cache negativa" di PTE non presenti non è consentita, almeno non visibile al software). Pertanto, la CPU potrebbe non VMexit anche se alcune pagine della tabella delle pagine fisiche del guest non sono effettivamente presenti.

I contatori delle prestazioni PMU possono essere abilitati e configurati in modo tale che l'istruzione richieda anche un evento perf per una scrittura in un buffer PEBS per tale istruzione. Con una maschera del contatore configurata per contare solo le istruzioni dello spazio utente, non il kernel, potrebbe essere che continui a provare a traboccare il contatore e memorizzare un campione nel buffer ogni volta che ritorni nello spazio utente, producendo un errore di pagina.


15
Il caso peggiore per una singola istruzione potrebbe essere qualcosa come " push dword [foo" (o anche solo call [foo]) con tutto disallineato attraverso il "limite della tabella del puntatore della directory della pagina" (aggiunta di un massimo di 6 pagine, tabelle di 6 pagine, directory di 6 pagine, 6 PDPT e un PML4); con la funzione "campionamento preciso basato su eventi con buffer PEBS" della CPU abilitata e configurata in modo che i pushdati di monitoraggio delle prestazioni vengano aggiunti al buffer PEBS. Per un conservatore "pagine minime fornite dall'ospite in modo che l'ospite possa fare progressi in casi patologici" vorrei almeno 16 pagine.
Brendan,

4
Si noti che questo genere di cose è sempre stato comune nelle architetture CISC-y. Alcuni microprocessori decodificano parzialmente le istruzioni e scaricano lo stato di microregistrazione in un errore di pagina, ma altri non richiedono e / o richiedono che gli operandi di indirizzo per le istruzioni "loop-y" (DBRA su m68k, MOVC3 / MOVC5 su Vax, ecc.) Siano in registri simili nell'esempio REP MOVS.
Torek,

1
@Brendan: qualcuno ha contato il caso peggiore su un'istruzione VAX come circa 50 pagine. Dimentico i dettagli, ma ovviamente inseriresti l'istruzione stessa su un limite di pagina, usi qualcosa come la ricerca della tabella di traduzione con la tabella che si estende su un limite di pagina, usa (rX) [rY] con gli indiretti ai confini di pagina, e presto. Le istruzioni più pelose hanno richiesto fino a 6 operandi (caricandoli in r0-r5) e tutti e sei potrebbero essere doppi indiretti, credo.
Torek,

3
Il sistema operativo potrebbe modificare le istruzioni, ma può anche cambiare EIP. Quindi c'è una logica domanda di follow-up. Qual è il numero minimo di pagine necessarie, presupponendo uno schema di patch istruzioni intelligente? Ad esempio, copiare il valore non allineato su un buffer scratch allineato, emulare l'istruzione e IRET sull'istruzione successiva.
MSalters il

1
Anche la pagina contenente le iretistruzioni del sistema operativo deve essere in memoria. Questa è un'istruzione a un byte, quindi una pagina in più. Anche l'indirizzo di interruzione del gestore degli errori di pagina deve essere in memoria, ma può essere la stessa pagina di cui sopra.
Stig Hemmer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.