Ich versuche herauszufinden, ob es möglich ist, eine Linux-VM auszuführen, deren RAM nur von einer einzigen physischen Seite unterstützt wird.
Um dies zu simulieren, habe ich den Handler für verschachtelte Seitenfehler in KVM so geändert, dass das vorhandene Bit aus allen NPT-Einträgen (Nested Page Table) entfernt wird, mit Ausnahme desjenigen, der dem aktuell verarbeiteten Seitenfehler entspricht.
Beim Versuch, einen Linux-Gast zu starten, habe ich festgestellt, dass Assembly-Anweisungen, die Speicheroperanden verwenden, wie z
add [rbp+0x820DDA], ebp
führen zu einer Seitenfehlerschleife, bis ich das aktuelle Bit für die Seite, die die Anweisung enthält, sowie für die Seite, auf die im Operanden verwiesen wird (in diesem Beispiel [rbp+0x820DDA]
), wiederherstelle .
Ich frage mich, warum das so ist. Sollte die CPU nicht nacheinander auf die Speicherseiten zugreifen, dh zuerst die Anweisung lesen und dann auf den Speicheroperanden zugreifen? Oder erfordert x86, dass sowohl die Anweisungsseite als auch alle Operandenseiten gleichzeitig zugänglich sind?
Ich teste auf AMD Zen 1.
Antworten:
Ja, sie benötigen den Maschinencode und alle Speicheroperanden.
Ja, das passiert logischerweise, aber eine Seitenfehlerausnahme unterbricht diesen zweistufigen Prozess und verwirft jeden Fortschritt. Die CPU kann sich nicht merken, welche Anweisung sich in der Mitte eines Seitenfehlers befand.
Wenn ein Seitenfehler-Handler nach der Behandlung eines gültigen Seitenfehlers zurückkehrt, ist RIP = die Adresse des Fehlerbefehls, sodass die CPU erneut versucht, ihn von Grund auf neu auszuführen .
Es wäre für das Betriebssystem legal, den Maschinencode des fehlerhaften Befehls zu ändern und zu erwarten, dass es nach
iret
dem Seitenfehler-Handler (oder einem anderen Ausnahme- oder Interrupt-Handler) einen anderen Befehl ausführt . AFAIK ist es architektonisch erforderlich, dass die CPU den Code-Abruf von CS: RIP wiederholt, falls Sie sprechen. (Angenommen, es kehrt sogar zum fehlerhaften CS: RIP zurück, anstatt einen anderen Prozess zu planen, während auf einen Festplattenfehler auf der Festplatte gewartet wird, oder einen SIGSEGV an einen Signalhandler bei einem ungültigen Seitenfehler zu senden.)Es ist wahrscheinlich auch architektonisch für den Ein- / Ausstieg von Hypervisoren erforderlich. Und selbst wenn dies auf dem Papier nicht ausdrücklich verboten ist, funktionieren CPUs nicht so.
@torek kommentiert, dass einige (CISC) Mikroprozessoren Anweisungen teilweise dekodieren und den Mikroregister-Status bei einem Seitenfehler ausgeben , aber x86 ist nicht so.
Einige Anweisungen sind unterbrechbar und können teilweise Fortschritte machen, z. B.
rep movs
(memcpy in a can) und andere Zeichenfolgenanweisungen, oder Lasten / Streuspeicher sammeln. Der einzige Mechanismus besteht jedoch darin, Architekturregister wie RCX / RSI / RDI für String-Ops oder die Ziel- und Maskenregister für Gather zu aktualisieren (z. B. manuell für AVX2vpgatherdd
). Wenn der Opcode / Decod nicht beibehalten wird, führt dies zu einem versteckten internen Register und einem Neustart nach dem Iret von einem Seitenfehler-Handler. Dies sind Anweisungen, die mehrere separate Datenzugriffe ausführen.Denken Sie auch daran, dass x86 (wie die meisten ISAs) garantiert, dass Anweisungen atomar geschrieben sind. Interrupts / Ausnahmen: Sie treten entweder vollständig oder gar nicht vor einem Interrupt auf. Unterbrechen einer Montageanweisung während des Betriebs . So
add [mem], reg
wäre beispielsweise erforderlich, die Last zu verwerfen, wenn das Speicherteil fehlerhaft ist, auch ohnelock
Präfix.Die ungünstigste Anzahl von Gastbenutzerseiten, die vorhanden sind, um Fortschritte zu erzielen, beträgt möglicherweise 6 (plus separate Gastbaum-Seitentabellen-Teilbäume für jede Seite):
movsq
odermovsw
2-Byte-Befehl, der sich über eine Seitengrenze erstreckt, sodass beide Seiten zum Dekodieren benötigt werden.[rsi]
auch ein seitensplit[rdi]
auch eine SeitenaufteilungWenn eine dieser 6 Seiten fehlerhaft ist, sind wir wieder auf dem ersten Platz.
rep movsd
ist auch eine 2-Byte-Anweisung, und Fortschritte in einem Schritt zu machen, hätte die gleiche Anforderung. Ähnliche Fälle wiepush [mem]
oderpop [mem]
könnten mit einem falsch ausgerichteten Stapel konstruiert werden.Einer der Gründe (oder Nebeneffekte) dafür, Sammellasten / Streuspeicher "unterbrechbar" zu machen (Aktualisieren des Maskenvektors mit ihrem Fortschritt), besteht darin, zu vermeiden, dass dieser minimale Platzbedarf erhöht wird, um einen einzelnen Befehl auszuführen. Auch zur Verbesserung der Effizienz bei der Behandlung mehrerer Fehler während einer Erfassung oder Streuung.
@Brandon weist in Kommentaren darauf hin, dass ein Gast seine Seitentabellen im Speicher benötigt und die Seitenaufteilungen für den Benutzerbereich auch 1-GB-Aufteilungen sein können, sodass sich die beiden Seiten in unterschiedlichen Unterbäumen der PML4 der obersten Ebene befinden. HW Page Walk muss alle diese Seiten mit Gastseitentabellen berühren, um Fortschritte zu erzielen. Es ist unwahrscheinlich, dass eine solche pathologische Situation zufällig eintritt.
Der TLB (und die Page-Walker-Interna) dürfen einige der Seitentabellendaten zwischenspeichern und müssen den Page-Walk nicht von Grund auf neu starten, es sei denn, das Betriebssystem hat
invlpg
ein neues CR3-Seitenverzeichnis der obersten Ebene erstellt oder festgelegt. Beides ist nicht erforderlich, wenn eine Seite von nicht vorhanden in vorhanden geändert wird. x86 auf Papier garantiert, dass es nicht benötigt wird (daher ist "negatives Caching" nicht vorhandener PTEs nicht zulässig, zumindest für Software nicht sichtbar). Daher kann es sein, dass die CPU nicht VMexit wird, selbst wenn einige der physischen Seiten mit Gast-Seitentabellen nicht vorhanden sind.PMU-Leistungsindikatoren können so aktiviert und konfiguriert werden, dass der Befehl auch ein Perf-Ereignis zum Schreiben in einen PEBS-Puffer für diesen Befehl erfordert . Wenn die Maske eines Zählers so konfiguriert ist, dass nur Anweisungen für den Benutzerbereich und nicht der Kernel gezählt werden, kann es durchaus sein, dass bei jeder Rückkehr in den Benutzerbereich immer wieder versucht wird, den Zähler zu überlaufen und ein Sample im Puffer zu speichern, was zu einem Seitenfehler führt.
quelle
push dword [foo
" (oder sogar nurcall [foo]
) sein, bei dem alles über die "Seitenverzeichniszeiger-Tabellengrenze" falsch ausgerichtet ist (Hinzufügen von bis zu 6 Seiten, 6 Seitentabellen, 6 Seitenverzeichnissen, 6 PDPTs und einer PML4). Die Funktion "Präzises ereignisbasiertes Sampling mit PEBS-Puffer" der CPU ist aktiviert und so konfiguriert, dasspush
Leistungsüberwachungsdaten zum PEBS-Puffer hinzugefügt werden. Für eine konservative "Mindestseite, die vom Host bereitgestellt wird, damit der Gast in pathologischen Fällen Fortschritte erzielen kann", würde ich mindestens 16 Seiten wünschen.EIP
. Es gibt also eine logische Folgefrage. Wie viele Seiten werden mindestens benötigt, wenn ein intelligentes Anweisungs-Patch-Schema vorausgesetzt wird? Kopieren Sie beispielsweise den nicht ausgerichteten Wert in einen ausgerichteten Arbeitspuffer, emulieren Sie den Befehl und IRET in den nächsten Befehl.iret
Anweisungen des Betriebssystems muss sich ebenfalls im Speicher befinden. Dies ist eine Ein-Byte-Anweisung, also eine zusätzliche Seite. Die Interrupt-Adresse des Seitenfehler-Handlers muss sich ebenfalls im Speicher befinden, dies kann jedoch dieselbe Seite wie oben sein.