Debugging von RTOS-Software-Systemen Schluss mit dem Rätselraten

Aufdecken von Fehlern

Als die Embedded-Branche von Assembler- auf C-Programmierung umstellte, zog man bei den Debugging-Tools schnell nach und bot das Quellcode-Debugging an, womit die C-Code-Perspektive zur normalen Debugging-Ansicht wurde. Leider haben sich die Tools in der Regel nicht über jenes Level hinaus weiterentwickelt. Einige wurden mit Features ergänzt, mit denen Entwickler den Zustand von RTOS-Objekten wie Tasks oder Semaphoren untersuchen können. Das reicht jedoch nicht aus. Ein RTOS-Debugging-Tool muss vielmehr das Konzept »Zeit« verstehen, damit es Ereignisse korrelieren kann. Außerdem muss es den Entwicklern die Möglichkeit geben, das Echtzeit-Verhalten einer Anwendung zu beobachten. Das verlangt nach RTOS-Tracing, was bedeutet, dass die Software-Ereignisse im Laufzeit-Code, im RTOS-Kernel und optional auch im Anwendungs-Code für die host-seitige Analyse aufgezeichnet werden.

Eine gutes Visualisieren ist entscheidend zum Verstehen von RTOS-Traces. Viele Embedded-Systeme legen zudem ein zyklisches Verhalten an den Tag, sodass ein Trace größtenteils aus Wiederholungen des normalen Ablaufmusters besteht. Interessant sind meist die Anomalien, die jedoch in einem Rohdatenstrom möglicherweise schwer zu erkennen sind. Bei einer grafischen Darstellung dagegen treten die Anomalien klar hervor.

Ein Debugging-Tool, das die Ereignisse und Datenstrukturen eines RTOS versteht, kann aus einem Trace außerdem weit mehr Informationen extrahieren als lediglich den grundlegenden Verarbeitungsablauf. Zum Beispiel besteht die Möglichkeit zum Erstellen eines Abhängigkeitsdiagramms, aus dem die Interaktionen zwischen Tasks, Interrupt-Service-Routinen und RTOS-Objekten wie etwa Semaphoren und Message Queues hervorgehen.

Sehen heißt verstehen

Die primäre Aufgabe eines Software-Tracing-Tools besteht im Erfassen von Ereignissen im Zielsystem. Hier reicht die Skala von Scheduling- und RTOS-Aufrufen bis zu Timer-Ticks und anwendungsspezifischen Log-Nachrichten. Man braucht jedoch lediglich einen kurzen Blick auf ein typisches Event Log (Bild 1) werfen, um zu erkennen, dass es zwar sinnvoll sein mag. Ein solches Textdokument ist jedoch nicht auf die großen Datenmengen skalierbar, die beim Software-Tracing entstehen. Das gezeigte Beispiel bezieht sich auf eine Ausführungszeit von lediglich etwa 2 ms. Ein RTOS-Trace, das sich über mehrere Minuten erstreckt, kann dagegen Millionen von Ereignissen enthalten.

Das Auffinden eines Fehlers in einem umfangreichen Text-Log gleicht der sprichwörtlichen Suche nach der Stecknadel im Heuhaufen – und der Entwickler weiß dabei nicht einmal, wie die Nadel aussieht, nach der er eigentlich sucht. Um die nächste Ebene zu erreichen, also zu verstehen, welches Verhalten beabsichtigt ist und welches nicht, sind Entwickler auf geeignete Tools zum Visualisieren der Daten angewiesen.

Besser lassen sich große Mengen an RTOS-Trace-Daten mit einer grafischen Trace-Ansicht in der Art eines Gantt-Diagramms (Balkenplan) darstellen (Bild2). Die Trace-Daten erscheinen dabei entlang einer interaktiven Zeitleiste. Entwickler können somit in einer niedrigen Zoomstufe den Überblick über umfangreiche Trace-Daten gewinnen und abnormale Muster identifizieren, anschließend den Zoomfaktor erhöhen, um Einzelheiten sichtbar zu machen. Eine grafische Trace-Darstellung muss sich nicht auf das Ausführen der RTOS-Tasks beschränken, sondern kann auch Programmierschnittstellen-Aufrufe, Log-Nachrichten der Anwendung und andere Ereignisse einschließen, wie das folgende Beispiel zeigt.

Bugs aufdecken mit graphischen Mitteln

Eine Herausforderung, mit der die Entwickler von Embedded-Systemen häufig zu tun haben, ist die Tatsache, dass die Zielsysteme häufig in ihrer CPU-Leistung und ihrem Speicherplatz beschränkt sind. Hier kann ein CPU-Auslastungsdiagramm, wie es nachfolgend gezeigt ist, hilfreich sein. Es gibt an, wieviel Prozessorzeit die einzelnen Tasks und Interrupt-Serviceroutinen beanspruchen. Anhand dieser Informationen lässt sich schnell erkennen, an welchen Stellen die Auslastung über eine längere Zeitspanne nahezu 100 Prozent beträgt (was wahrscheinlich zu einem verzögerten Ausführen von Tasks führt). Außerdem lässt sich erkennen wieviel CPU-Zeit für neue Features verfügbar ist, ohne die Hardware aufzurüsten (Bild 3).

Um auf den Debugging-Aspekt des Tracings zurückzukommen, wird eine durchgegangene Task, die mehr CPU-Zeit als vorgesehen beansprucht, in einem CPU-Auslastungsdiagramm deutlich auszumachen sein. Gleiches gilt für Tasks, die das ineffiziente »Busy Waiting« nutzen. Es vergeudet nämlich CPU-Zeit, die sonst für andere Tasks verfügbar wäre, und ist ein häufig begangener Verstoß gegen die Regeln für gutes RTOS-basiertes Design.

Mit dem Verfolgen von API-Aufrufen erfasst man tatsächlich Abhängigkeiten zwischen den Tasks, und kann sie mit einem Tool in einem Abhängigkeitsdiagramm visualisieren (Bild 4). Eine solche visuelle Zusammenfassung des Anwendungs-Designs liefert die Gewissheit, dass der Anwendungs-Code wie vorgesehen funktioniert. Außerdem kann sie Bugs aufdecken, die mit falschen oder fehlenden Programmierschnittstellen-Aufrufen zusammenhängen und somit zu Stabilitätsproblemen führen können.

Ein gutes Tracing-Tool sollte Entwicklern außerdem die Möglichkeit zum Aufzeichnen individueller, anwendungsspezifischer Daten im Rahmen des Trace-Streams geben (Bild 5). Die betreffenden Ereignisse sind für beliebige Zwecke zu verwenden. Gängig ist jedoch das Aufzeichnen wichtiger Variablenwerte und Zustandsänderungen aus dem Anwendungs-Code.