[Podcast] Kernel Panic - Board on Fire ~~> #001: Speculative Execution und ein Execute Never Bit

In der ersten Folge des Kernel Panic Podcasts geht es um ein Problem mit dem sich mein Kollege Ahmad Fatoum beschäftigt hat. Wie so oft beginnt die Geschichte mit einer Aufgabe, die eigentlich™ in fünf Minuten erledigt sein sollte, driftet aber rasant ab in eine Fehlersuche in den Untiefen der Systemprogrammierung. Es geht um Caches, Adressbereiche, Spekulative Ausführung von Code, um Funktionierbits und auch um Nicht-Funktionier-Bits.

Über den Kernel Panic - Board on Fire Podcast

In wahrscheinlich jedem Berufsfeld gibt es Schauergeschichten, die man sich abends am Lagerfeuer mit einer Taschenlampe am Kinn erzählen kann. So auch in der Welt der Software. In diesem Podcast geben wir in unregelmäßigen Abständen Entwicklerinnen und Entwicklern die Möglichkeit ihre Schauergeschichten zu erzählen. Es geht um monatelange Fehlersuchen, deren Ergebnis nur eine Hand voll Zeilen falscher Code sind, um subtil fehlerhafte Hardware, die zu sporadisch auftretenden Geistern im System führt, um bröckelnde Software, deren Quellcode schon vor Jahren verloren gegangen ist, und manchmal auch um ganz was anderes.

Shownotes

Wer nicht die ganze Folge hören möchte kann sich an den folgenden fünf Eskalationsstufen mit ungefähren Zeitangaben orientieren:

Eskalationsstufe 0 - "Licht einschalten Crashed den Bootloader"

00:00
Vorstellung des Podcasts und der Personen
05:30
Die ursprüngliche Problemstellung. Es gibt ein eingebettetes Gerät mit einem Display dran und darauf soll, um BenutzerInnen das Gefühl zu geben, dass nach dem Einschalten etwas passiert, frühestmöglichst die Display-Hintergrundbeleuchtung eingeschaltet werden.
06:00

Versucht man die Beleuchtung um Bootloader einzuschalten crashed dieser, und zwar in einem ganz anderen Bereich des Codes.

Mehr zum Thema Barebox:

Mehr dazu, was schon passiert bevor überhaupt der Bootloader startet:

08:30

Erste Vermutungen:

  • Probleme im NAND-Code (der Speicher auf dem die Daten und Programme des Systems abgelegt werden).
  • Probleme durch Änderungen an der Hardware (z.B. mehr Arbeitsspeicher)

Eskalationsstufe 1 - "printf lässt das Problem verschwinden"

10:00

Neuer Lösungsansatz: Fehlersuche mit printf.

Neues Problem: mit printf verschwinden die Fehler.

Warum können Probleme durch Einfügen von printfs verschwinden?

  • Timing der ausgeführten Funktionen ändert sich
  • Codestücke verschieben sich im Speicher

Timing sollte eigentlich keine große Rolle spielen, da im Bootloader noch alles ohne Nebenläufigkeit eins nach dem anderen ausgeführt wird.

13:30

Vermutung: DMA

Durch falsch eingestelltes DMA (Direct Memory Access) könnten auch im Bootloader Dinge im Hintergrund passieren. Im Bootloader wird DMA genutzt, um Daten aus dem NAND in den Hauptspeicher zu lesen.

15:30

Vermutung: Schlecht angebundener RAM

Beim neuen Hardwaredesign könnte sich z.B. das Layout der RAM-Leitungen verschlechtert haben.

17:00

Brainstorming mit den Kollegen. Sind es vielleicht die Caches?

Das DMA im verwendeten Prozessor ist nicht Cache coherent. Das heißt man muss zu den richtigen Zeiten den Inhalt der Caches wegwerfen, sonst erhält man falsche Daten.

18:00

Ist es vielleicht Spekulation?

Um nicht immer darauf warten zu Müssen, dass die richtigen Daten vom Cache in den RAM geladen wurden führt der Prozessor zukünftige Operationen schon einmal "spekulativ" aus und lädt dabei Daten in die Caches. Falls falsch spekuliert wird, werden diese Daten eigentlich einfach verworfen.

Oder vielleicht nicht?

20:00

Warum können auch lesende spekulative Speicherzugriffe ein Problem sein?

Nicht alles was im Adressraum des Prozessors liegt ist auch Speicher, sondern es gibt dort auch eine Vielzahl an Peripherieeinheiten.

21:30

Lösungsansatz: Nicht-Speicher auch spekulative nicht ausführen.

Um zu verhindern, dass ein Adressbereich (spekulativ) ausgeführt wird kann er als nicht ausführbar markiert werden. Bei Programmen, die unter einem Betriebssystem laufen macht man das z.B. für alle Daten, die nicht Teil des Programmcodes sind, z.B. aus dem Internet geladene Dinge. Im Bootloader ist man da etwas laxer, auch weil Sachen in den Speicher zu laden und auszuführen zur Kernfunktionalität gehört.

Die entsprechende Markierung der Adressbereiche lieferte auch keine Abhilfe.

Eskalationsstufe 2 - "Der JTAG-Debugger lässt das Problem verschwinden"

24:00
Fehlersuche mit Hardwaredebugger / JTAG-Adapter. Führt man das Programm im Debugger Befehl für Befehl aus tritt der Fehler, wie auch schon mit den printfs, nicht mehr auf.
28:00
Fragestellung: Unterscheiden sich die Daten in den Caches von den Daten im Arbeitsspeicher?

Eskalationsstufe 3 - "Ändern von nie ausgeführtem Code lässt das Problem verschwinden"

30:00
Da printfs, der Debugger und neu Kompilieren das Verhalten des Programms ändern patcht Ahmad stattdessen den generierten Maschinencode.
39:00
Durch Tauschen von zwei Instruktionen in einer nie ausgeführten Funktion verschwindet das Problem.
43:00
Fragestellung: Crasht das System auch wenn der entsprechende Code unter Linux als Programm ausgeführt wird?

Eskalationsstufe 4 - "Handbuch lesen"

45:00

Letzter Akt der Verzweiflung: lesen des Arm Architecture Reference Manual (Seite 9198) liefert den entscheidenden Hinweis.

When using the Short-descriptor translation table format, the XN attribute is not checked for domains marked as Manager. Therefore, the system must not include read-sensitive memory in domains marked as Manager, because the XN field does not prevent speculative fetches from a Manager domain.

Im Modus "Manager" ignoriert der Prozessor die gesetzten Restriktionen für Speicherbereiche. Der Bootloader läuft im Manager-Modus.

49:00

Die Codeänderung damit der Bootloader in der Client Domain und nicht in der Manager Domain läuft löst das Problem.

Außerdem: ein kleiner Ausblick darauf welche Software es sonst falsch /richtig gemacht hat.

53:00
Abschließende Worte