Ich habe eine Situation, in der ein Teil des Adressraums empfindlich ist, weil Sie gelesen haben, dass Sie abstürzen, da niemand da ist, der auf diese Adresse reagiert.
%Vor%Der bx wurde nicht vom Compiler als Befehl erstellt, sondern ist das Ergebnis einer 32-Bit-Konstanten, die nicht als Direktbefehl in einen einzelnen Befehl passte, so dass eine relative PC-Last eingerichtet wird. Dies ist im Grunde der Literalpool. Und es hat zufällig Bits, die einem bx ähneln.
Kann leicht ein Testprogramm schreiben, um das Problem zu erzeugen.
%Vor%Was zu geschehen scheint, ist, dass der Prozessor auf Daten wartet, die von dem Pop (ldm) zurückkommen, bewegt sich in diesem Fall auf den nächsten Befehl bx r0 und startet einen Prefetch an der Adresse in r0. Welche hängt den ARM.
Als Menschen sehen wir den Pop als einen unbedingten Zweig, aber der Prozessor geht nicht durch die Pipe.
Prefetching und Verzweigungsvorhersage sind nichts Neues (wir haben den Zweig Prädiktor ausgeschaltet in diesem Fall), Jahrzehnte alt und nicht auf ARM beschränkt, sondern die Anzahl der Befehlssätze, die den PC als GPR und Befehle haben, die in gewisser Weise behandeln es als nicht speziell sind wenige.
Ich suche nach einer gcc-Befehlszeilenoption, um dies zu verhindern. Ich kann mir nicht vorstellen, dass wir die Ersten sind, die das sehen.
Ich kann das natürlich tun
%Vor%verhindert das Problem
Beachten Sie, dass gcc nicht nur auf den Daumenmodus beschränkt ist, sondern auch für einen solchen Code mit dem Literalpool nach dem Pop Armcode erzeugen kann.
%Vor%Ich hoffe, jemand kennt eine generische oder armspezifische Option, um eine armv4t-artige Rückkehr (Pop {r4, lr}; bx lr im Arm-Modus zum Beispiel) ohne das Gepäck zu machen oder einen Zweig direkt nach einem Pop-PC zu setzen Um das Problem zu lösen, wird die Pipe nicht mit b als unbedingter Verzweigung verwechselt.
BEARBEITEN
%Vor%bewirkt auch einen Prefetch. was nicht unter -march = armv4t fallen wird. gcc erzeugt absichtlich ldrls pc, []; b irgendwo für Switch-Anweisungen und das ist in Ordnung. Das Backend wurde nicht untersucht, um zu sehen, ob andere ldr pc-, [] -Anweisungen erzeugt wurden.
Ссылка hat die Option -mpure-code
, die keine Konstanten in Code-Abschnitte schreibt . "Diese Option ist nur verfügbar, wenn mit der MOVT-Anweisung ein Nicht-Bildcode für M-Profil-Ziele generiert wird." also lädt es wahrscheinlich Konstanten mit einem Paar mov-direkt-Anweisungen statt aus einem Konstanten-Pool.
Dies löst Ihr Problem jedoch nicht vollständig, da die spekulative Ausführung regulärer Anweisungen (nach einer bedingten Verzweigung innerhalb einer Funktion) mit gefälschten Registerinhalten immer noch den Zugriff auf nicht vorhersagbare Adressen auslösen könnte. Oder nur die erste Anweisung einer anderen Funktion könnte eine Last sein, also ist es auch nicht immer sicher, in eine andere Funktion zu fallen.
Ich kann versuchen, etwas Licht in die Frage zu bringen, warum das so dunkel genug ist, dass Compiler es nicht schon vermeiden.
Normalerweise ist die spekulative Ausführung von Anweisungen, dass der Fehler kein Problem ist. Die CPU übernimmt den Fehler erst, wenn sie nicht spekulativ wird. Falsche (oder nicht vorhandene) Verzweigungsvorhersage kann die CPU veranlassen, etwas langsam zu machen, bevor der richtige Pfad gefunden wird, aber es sollte niemals ein Korrektheitsproblem geben.
Normalerweise sind spekulative Lasten aus dem Speicher in den meisten CPU-Designs zulässig. Aber Speicherbereiche mit MMIO-Registern müssen offensichtlich davor geschützt werden. In x86 können Speicherbereiche zum Beispiel WB (normal, Write-Back cachefähig, spekulative Lasten zulässig) oder UC (Uncacheable, keine spekulativen Lasten) sein. Ganz zu schweigen von write-combining write-through ...
Sie brauchen wahrscheinlich etwas Ähnliches, um Ihr Korrektheitsproblem zu lösen, um zu verhindern, dass spekulative Ausführung etwas tut, das tatsächlich explodiert. Dies beinhaltet den spekulativen Abruf von Anweisungen, der durch eine spekulative bx r0
ausgelöst wird. (Entschuldigung, ich kenne ARM nicht, also kann ich nicht vorschlagen, wie wie man das macht.
Aber das ist der Grund, warum es für die meisten Systeme nur ein geringes Leistungsproblem ist, obwohl sie MMIO-Register haben, die nicht spekulativ gelesen werden können.)
Ich denke, es ist sehr ungewöhnlich, dass die CPU spekulative Lasten von Adressen ausführt, die das System zum Absturz bringen, anstatt nur eine Ausnahme auszulösen, wenn sie nicht spekulativ werden.
>In diesem Fall haben wir den Zweig-Prädiktor ausgeschaltet
Dies könnte der Grund dafür sein, dass Sie immer statt außerhalb eines unbedingten Zweiges spekulative Ausführung sehen (das pop
), anstatt nur sehr selten.
Netter Detektiv arbeitet mit einem bx
, um zurückzukehren, und zeigt an, dass Ihre CPU diese Art von unbedingten Verzweigungen beim Dekodieren erkennt, aber das pc
-Bit in einem pop
nicht überprüft. : /
Im Allgemeinen muss die Verzweigungsvorhersage vor dem Decodieren erfolgen, um Abrufblasen zu vermeiden. Gegeben die Adresse eines Abrufblocks, die nächste Blockholadresse voraussagen. Vorhersagen werden auch auf der Anweisungsebene statt auf der Abrufblockebene generiert, um von späteren Stufen des Kerns verwendet zu werden (da es mehrere Verzweigungsinstruktionen in einem Block geben kann und Sie wissen müssen, welche verwendet wird).
Das ist die generische Theorie. Die Verzweigungsvorhersage ist nicht 100%, Sie können also nicht darauf zählen, um Ihr Korrektheitsproblem zu lösen.
x86 CPUs können Leistungsprobleme haben, wenn die Standardvorhersage für eine indirekte jmp [mem]
oder jmp reg
die nächste Anweisung ist. Wenn die spekulative Ausführung etwas startet, das langsam abbricht (wie div
auf einigen CPUs) oder einen langsamen spekulativen Speicherzugriff oder einen TLB-Fehltreffer auslöst, kann es die Ausführung des korrekten Pfades verzögern, sobald er bestimmt ist.
Es wird also (von Optimierungshandbüchern) empfohlen, ud2
(illegaler Befehl) oder int3
(Debug-Trap) oder ähnliches nach einem jmp reg
zu setzen. Oder besser gesagt, setzen Sie eines der Sprungtabellen-Ziele dort, damit "Fall-Through" eine richtige Vorhersage ist. (Wenn das BTB keine Vorhersage hat, ist die nächste Anweisung die einzig vernünftige Sache, die es tun kann.)
x86 vermischt Code normalerweise nicht mit Daten, daher ist dies eher ein Problem für Architekturen, in denen Literalpools üblich sind. (Aber Lasten von Scheinadressen können immer noch spekulativ nach indirekten Verzweigungen oder falsch vorhergesagten normalen Verzweigungen auftreten.
z.B. if(address_good) { call table[address](); }
könnte leicht spekulativen Code-Fetch von einer schlechten Adresse falsch vorhersagen und auslösen. Wenn jedoch der tatsächliche Bereich der physikalischen Adresse als nicht speicherbar markiert wird, wird die Ladeanforderung im Speichercontroller angehalten, bis bekannt ist, dass sie nicht spekulativ ist.
Eine Rückkehranweisung ist eine Art indirekter Verzweigung, aber es ist weniger wahrscheinlich, dass eine Vorhersage einer nächsten Anweisung nützlich ist. Also vielleicht bx lr
blockiert, weil spekulative Fall-Through weniger wahrscheinlich nützlich sein wird?
pop {pc}
(aka LDMIA
vom Stapelzeiger) wird entweder nicht als Verzweigung in der Dekodierstufe erkannt (wenn das pc
-Bit nicht speziell überprüft wird) oder als generische indirekte Verzweigung behandelt.Es gibt sicherlich andere Anwendungsfälle für ld
in pc
als nichtrückkehrende Verzweigung, so dass das Erkennen der Quellregistercodierung sowie das pc
-Bit erforderlich ist, um sie als wahrscheinliche Rückgabe zu erkennen.
Vielleicht gibt es einen speziellen (intern versteckten) Rücksprungadressen-Prädiktor-Stack, der dazu beiträgt, dass bx lr
jedes Mal richtig vorhergesagt wird, wenn er mit bl
gepaart wird? x86 tut dies, um call
/ ret
Anweisungen vorherzusagen.
Haben Sie getestet, ob pop {r4, pc}
effizienter ist als pop {r4, lr}
/ bx lr
? Wenn bx lr
speziell in mehr behandelt wird, als nur die spekulative Ausführung von Garbage zu vermeiden, ist es möglicherweise besser, gcc dazu zu veranlassen, anstatt dass es seinen Literal-Pool mit einer b
Anweisung oder etwas führt.