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.
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.
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.
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:00Einleitung 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:00Zusammenfassung des bisher gehörten:
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