Fünf Ursachen für unliebsame Firmware-Fehler Rezepte fürs Bugfixing

Wie man im Vorfeld die schlimmsten Software-Fehler verhindert.
Wie man im Vorfeld die schlimmsten Software-Fehler verhindert.

Am besten ist es natürlich, wenn Fehler gar nicht erst entstehen. Doch die Wirklichkeit sieht anders aus. Aber wenn man ­potenzielle Fehlerquellen kennt, lässt sich schon im Vorfeld das Auftreten der wichtigsten Software-Fehler verhindern. Hier sind fünf Fehlerszenarien ­beschrieben – und was man dagegen tun kann.

Firmware-Fehler – jeder Entwickler kennt sie. Sie lauern in den Systemen und können katastrophale Folgewirkungen haben – besonders in sicherheitskritischen Systemen wie etwa Automobilen und Medizintechnik. Sie aufzudecken und zu beseitigen kann immense Schwierigkeiten bereiten. Erschwerend kommt hinzu, dass diese Fehler derart subtile Schäden am Code oder den Daten hervorrufen, dass das betreffende System zunächst noch problemlos oder weitestgehend normal zu arbeiten scheint, bevor sich die Fehlfunktion manifestiert.

Aus diesem Grund ist es wichtig, die Ursprünge gängiger Fehler zu kennen und zu verstehen, denn mit diesem Wissen ist bereits der erste Schritt zu ihrer Eliminierung getan. Der vorliegende Beitrag widmet sich einigen häufigen Grundursachen schwierig reproduzierbarer Firmware Bugs und beschreibt erprobte Abhilfemaßnahmen.

Stack-Überläufe

Jeder Programmierer weiß, dass ein Stack-Überlauf ein sehr gravierender Vorfall ist. Das Ausmaß des daraus entstehenden Schadens und der zeitliche Ablauf der daraus resultierenden Fehlfunktion hängen jedoch ganz allein davon ab, welche Daten oder Befehle betroffen sind und wie diese verwendet werden. Leider kommen Stack-Überläufe in Embedded-Systemen weit häufiger vor als in Desktop-Computern. Das hat mehrere Gründe:

  • Embedded-Systeme besitzen in der Regel weniger RAM.
  • Wegen des fehlenden Festplattenspeichers gibt es meist keinen virtuellen Speicher, der als Rückfallebene dienen könnte.
  • Auf RTOS Tasks basierende Firmware Designs nutzen mehrere Stacks (einen pro Task), die alle passend dimensioniert sein müssen, um für die jeweils im ungünstigsten Fall zu erwartende Stack-Tiefe gerüstet zu sein.
  • Interrupt Handler versuchen möglicherweise, dieselben Stacks zu benutzen.

Auch mit noch so gründlichen Tests lässt sich nicht gewährleisten, dass ein bestimmter Stack genügend groß ist. Man kann ein System zwar unter verschiedenen Auslastungsbedingungen testen, aber der Testzeitraum ist in jedem Fall begrenzt. Unter Anwendung algorithmischer Restriktionen (z.B. Ausschluss von Rekursionen) lässt sich mit einer Top-Down-Analyse des Programmablaufs durchaus nachweisen, dass es niemals zu einem Stack-Überlauf kommt. Allerdings muss dieser Nachweis nach jeder am Code vorgenommenen Änderung erneut geführt werden.

Abhilfe: Legen Sie nach dem Hochfahren ein wahrscheinlich nicht zufällig vorkommendes Datenmuster in allen Stacks ab. Der Autor verwendet gern das Muster 23 3D 3D 23 (hex), das in einem ASCII-Dump wie ein Zaun aussieht (#==#). Zur Laufzeit prüft man mit einer Überwachungs-Task periodisch, dass keines dieser Muster oberhalb einer vorgegebenen „Hochwassermarke“ geändert wurde. Zeichnen Sie etwaige Fehler (also welcher Stack betroffen ist und wie weit die Hochwassermarke überschritten wurde) in einem nichtflüchtigen Speicher auf und implementieren Sie entsprechende Schutzvorkehrungen für die Anwender (z. B. ein kontrolliertes Abschalten oder einen Reset).