[Podcast] Kernel Panic - Board on Fire ~~> #003: Ein Rennen um Nanosekunden - Nebenläufigkeit mit Hardwareeinheiten

In dieser Folge erzählt Michael Grzeschik die Geschichte eines USB-Controllers, der dann und wann einfach aufhört zu funktionieren und davon wie er dem Fehler mit Tracing, einem Disassembler und viel Codelektüre auf die Schliche gekommen ist. Wir stellen einmal mehr fest, dass Probleme mit Nebenläufigkeit schwierig zu debuggen sind und nicht nur Software-Software Interaktion betreffen, sondern manchmal auch Software-Hardware.

Ü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 Zeitmarken orientieren:

00:00
Vorstellung und einleitende warme Worte. Michael beschäftigt sich viel mit der Device-Seite von USB unter Linux. In letzter Zeit insbesondere mit dem Fall, dass ein Linux Gerät sich verhält wie eine USB-Webcam und Video an einen anderen Rechner überträgt. Das gibt ihm sowohl Berührungspunkte mit Kernelthemen, als auch mit Grafikthemen.
07:00

Einleitung der Lagerfeuergeschichte. Beim Bug-Quartett wäre sie mit drei Zeilen angepasstem Code bei mehreren Wochen Suchzeit eine gute Karte.

Ausgangspunkt ist ein Linux-Computer, der sich einem anderen Computer gegenüber als USB-Gerät ausgeben sollte. An diesem sollte er dann ein serielles Kommunikationsinterface bereitstellen, über das man z.B. eine Linux Commandline benutzen kann. Nach einigen Stunden Laufzeit blieb allerdings die gesamte Kommunikation stecken und die USB-Einheit im Gerät ließ sich nicht mehr ansprechen.

11:00
Weil das Problem so schlecht reproduzierbar ist war Trial and Error kein guter Ansatz. Die Alternative ist erstmal scharfes Hinsehen im Treibercode, um das Verhalten mit dem Datenblatt zu vergleichen. Besonderes Augenmerk hat er dabei auf die Datenstrukturen gelegt, die zwischen dem Treiber und der Hardware ausgetauscht werden.
13:00

Zusammenfassung des bisher gehörten:

  • Das Gerät um das es geht spielt die Rolle eines USB-Device, ein anderes Gerät (z.B. ein Desktoprechner) den USB Host.
  • Die Übertragungskette ist also Userspace-Software -> Kernel-Treiber -> USB-Einheit -> Kabel -> USB-Einheit -> Kernel-Treiber -> Userspace-Software.
  • Der Fehler liegt zwischen Kernel-Treiber und USB-Einheit auf Geräteseite. In den Treiber kann man hinein schauen, in die USB-Einheit nicht.
16:00
Weiter mit der Bestandsaufnahme. Es handelt sich nicht um einen der typischen USB-Fehler, wie einen leer gelaufenen Datenpuffer, die werden nämlich schon behandelt. Es bleibt wirklich die ganze USB-Einheit hängen.
21:00
Peripherieeinheiten wie USB oder Netzwerkcontroller werden von Chipherstellern oft eingekauft und in ihr eigenes Design integriert. So finden sich die selben Einheiten oft in ganz unterschiedlichen Chips. Bei USB waren in der Zeit besonders der dwc2 und der Chipidea Controller relevant.
30:00
Ansatz um die fehlerhafte Stelle einzugrenzen: Einbau von Tracing in den Treiber, um Daten aufnehmen zu können im Moment in dem der Fehler tatsächlich passiert. Das Tracing ergibt, dass die Hardware nach Einhängen neuer Daten stehen bleibt.
39:00

Der selbe Treiber und die Peripherieeinheit werden auch auf anderen Systemen genutzt, warum ist es da nicht aufgefallen? Die CPU in der problematischen Hardware kann Daten nicht unaligned schreiben/ lesen. D.h. wenn z.B. ein 32 Bit Wert aus dem Speicher in ein Register gelesen werden soll geht das nur dann in einer einzigen Prozessor-Instruktion, wenn die Speicheradresse (in Bytes) durch vier teilbar ist (also durch 32 Bit). Ansonsten muss es durch vier einzelne byteweise Reads passieren. Auf anderen Platformen gibt es mitunter Befehle um auch unaligned in einer Instruktion lesen/schreiben zu können.

Im disassemblierten Code konnte man sehen, dass die Adresse der nächsten Datenstruktur als vier einzelne Bytes geschrieben wurde und nicht als ein 32 Bit Wert.

43:00
In der Zeit in dem der neue Pointer Byte für Byte geschrieben wird kann die Hardware vorbei kommen und einen Wert auslesen, der nur teilweise die richtigen Daten enthält. Weil das Zeitfenster nur ein paar Nanosekunden lang ist, tritt der Fehler so selten auf.
50:00
Warum hat der Compiler überhaupt angenommen, dass das Datenfeld nicht auf 32 Bit aligned war? Weil die Datenstruktur extra als packed markiert war. Mit der zusätzlichen Markierung aligned(4) wird das Feld wieder in einer Instruktion geschrieben.
58:00
Mit dem Markieren als aligned(4) ist der Patch auch quasi schon fertig. Der Vollständigkeit halber fügt er auch noch ein wmb() (Write-Memory-Barrier) ein, um sicherzustellen, dass der Compiler das Schreiben der Daten in die Datenstruktur nicht hinter das Einhängen der Datenstruktur verschiebt.
60:00
Zusammenfassung und Abschluss

Weiterführende Links

[Podcast] Kernel Panic - Board on Fire ~~> #002: Wachhunde und (nicht so) ideale Bauelemente

In der zweiten Folge des Kernel Panic Podcasts geht es um ein Problem mit dem ich mich ausnahmsweise mal selbst beschäftigt habe. Damit ich mich dazu nicht selbst ausfragen muss springt netterweise Chris als Gastinterviewer ein. Problem dieses mal ist ein Gerät, das dann und wann einfach neu startet, und wieder einmal könnte die Ursache sowohl in der Software als auch in der Hardware zu finden sein. Es folgt eine Suche durch die verschiedenen möglichen Ursachen für Neustarts von Geräten, die uns an Watchdogs, Übertemperatur, den Eigenheiten von PoE, WAD pins und Dioden vorbei zur Lösung führt. Und wieder einmal steht die Größe der Änderung in keiner Relation zur vorherigen Suche.


[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.