Trace-Visualisierung beim Debugging von RTOS-Firmware

Autor / Redakteur: Dr. Johan Kraft * / Sebastian Gerstl

Echtzeit-Betriebssysteme sind in Embedded Systemen längst fest etabliert. Um RTOS-basierte Systeme vernünftig zu debuggen, bedarf es aber besserer Einblicke in ihre Echtzeitverarbeitung.

Firmen zum Thema

Bild 1: Pathfinder mit dem Rover Sojourner während der Vorbereitungsphase. Bei der Marsmission der NASA kam es zu einem Problem mit dem RTOS.
Bild 1: Pathfinder mit dem Rover Sojourner während der Vorbereitungsphase. Bei der Marsmission der NASA kam es zu einem Problem mit dem RTOS.
(Bild: NASA)

Vor einigen Jahrzehnten gab es in der Embedded-Industrie eine Verlagerung des Schwerpunkts von der Assembler- zur C-Programmierung. Schnellere Prozessoren und bessere Compiler ermöglichten diese Anhebung des Abstraktionsgrads, die die Produktivität und Qualität der Entwicklung verbesserte.

Zurzeit befinden wir uns inmitten einer neuen bedeutenden Umstellung in der Firmwareentwicklung-Technologie: die zunehmende Verwendung von Echtzeit-Betriebssystemen (Real-Time Operating Systems – RTOS) verkörpert die dritte Generation der Entwicklung von Embedded-Software. Ein RTOS ist ein schnelles und deterministisches Betriebssystem für den Einsatz in Embedded- und IoT-Anwendungen. Seine Hauptaufgabe ist das Multithreading, um die Aufteilung der Softwarefunktionalität in mehrere ‚parallel‘ laufende Teilprogramme, so genannte ‚Tasks‘ zu ermöglichen.

Bildergalerie

Der Trend zum Echtzeit-Betriebssystem

RTOS sind kein Hype mehr, wie sie es noch vor 10 bis 15 Jahren waren, sondern befinden sich in allen möglichen Arten von Embedded-Anwendungen im Einsatz. Im Embedded Market Survey, der wohl am besten etablierten und am meisten vertrauten Studie der Branche, ist dieser Trend klar erkennbar. Auf die Frage nach der ‚größten technologischen Herausforderung‘ wird am häufigsten das RTOS genannt. Der Anteil dieser Antwort wuchs von 12 % im Jahr 2013 auf 17 % 2014 und 26 % im Jahr 2015. Der historisch sehr fragmentierte Markt scheint sich jetzt in einer Konsolidierungsphase zu befinden, in der sich die Entwickler zunehmend den führenden Akteuren zuwenden.

Herausforderungen bei der Anwendung eines RTOS

Was macht nun aber ein RTOS so besonders, dass man es als die dritte Generation beim Firmwaredesign bezeichnen kann? Ein RTOS übernimmt die Kontrolle über die Programmausführung und bringt mit den Tasks einen neuen Abstraktionsgrad ein. Der Kontrollfluss eines Programms lässt sich nun nicht mehr aus dem Quellcode ersehen, denn das RTOS entscheidet darüber, welche Task jeweils ausgeführt wird.

Während ein RTOS die Komplexität des Quellcodes einer Applikation verringern kann, reduziert es die der Applikation selbst innenwohnende Komplexität nicht. Eine Ansammlung scheinbar einfacher RTOS-Tasks kann, wenn sie gemeinsam als System ausgeführt wird, zu einem überraschend komplexen Laufzeitverhalten führen. Der Entwickler muss festlegen, wie die Tasks miteinander interagieren und die Daten mit den Dienstfunktionen des RTOS teilen sollen. Dem Entwickler obliegt außerdem die Entscheidung über wichtige RTOS-Parameter wie die Task-Prioritäten (also die relative Dringlichkeit der Tasks), die durchaus nicht selbstverständlich sein müssen. Selbst wenn Sie Ihren gesamten Code nach den bewährten Verfahrensweisen des RTOS-basierten Designs geschrieben haben, können andere Teile des Systems, ob es sich nun um Komponenten aus dem eigenen Unternehmen oder von Drittanbietern handelt, in derselben Umgebung laufen, aber sich nicht an dieselben Prinzipien halten.

Das grundlegende Problem besteht darin, dass es sich bei den RTOS-Tasks um keine isolierten Entitäten handelt. Zwischen den Tasks gibt es mindestens eine Art von Abhängigkeit, nämlich die von ihnen geteilte Prozessorzeit. Beim präemptiven Scheduling mit feststehender Priorität können praktisch jederzeit Tasks mit höherer Priorität erscheinen, die die Verarbeitung der Tasks geringerer Priorität verzögern.

Andere Arten geteilter Ressourcen (z. B. globale Daten oder Hardwareperipherie) führen ebenfalls zu Abhängigkeiten zwischen den Tasks und können unvorhersehbare Verzögerungen bewirken, wenn die Synchronisation nicht korrekt geplant ist – unabhängig von den Task-Prioritäten.

Ein Beispiel für diese Art von Problemen findet man in der Pathfinder-Mission der NASA, in deren Verlauf ein Rover auf dem Mars gelandet wurde. Während dieser Mission kam es im Raumfahrzeug zu kompletten System-Resets, die zu Datenverlusten führten. Nach einigem Ärger ermittelte die NASA als Ursache ein klassisches RTOS-Problem, das als ‚Prioritätsinversion‘ bezeichnet wird.

Zu einer Prioritätsinversion kann es kommen, wenn eine Task hoher Priorität (Task H in der folgenden Abbildung) auf eine geteilte Ressource (z. B. eine Kommunikationsschnittstelle) zugreifen will, die gerade von einer Task mit geringerer Priorität (Task L) belegt wird. Task H würde normalerweise für kurze Zeit blockiert, bis Task L die geteilte Ressource freigibt. Eine Prioritätsinversion tritt dann auf, wenn an dieser Stelle eine Task mittlerer Priorität (Task M) Task L unterbricht und die hochpriore Task dadurch weiter verzögert. Bei der Pathfinder-Mission der NASA führte dieses Verhalten zu System-Resets, zum Verlust von Daten und beinahe auch zu einem Fehlschlag der gesamten Mission.

Task-Abhängigkeiten wie das Scheduling und geteilte Ressourcen werden durch das Timing, also durch Verarbeitungszeiten und das Eingangs-Timing, beeinflusst. Dies macht es nahezu unmöglich, rein auf Grund des Quellcodes Aussagen über das Echtzeitverhalten eines RTOS-basierten Systems zu machen. Unter dem Einfluss vieler Faktoren können die Tasks langsamer als vorgesehen laufen, zufällige, unerwartete Verzögerungen aufweisen oder auch niemals ausgeführt werden. Selbst dann, wenn das System scheinbar so arbeitet wie im Labor vorgesehen, kann es zahllose andere Verarbeitungs-Szenarien mit mehr oder weniger bedeutenden Timing-Unterschieden geben, von denen einige zu Problemen führen. Im schlimmsten Fall besteht das System zwar die Tests, stürzt aber im Praxiseinsatz bei Ihren Kunden immer wieder ab.

Das Debugging RTOS-basierter Systeme

Der Wunsch, das Debugging auf derselben Abstraktionsebene durchzuführen wie die Entwicklung, ist völlig normal, jedoch haben sich die Debugging-Tools auf den RTOS-Trend noch nicht entscheidend weiterentwickelt. Einige Debugger wurden zwar mit so genannten ‚RTOS Awareness‘-Features aufgewertet, die Ihnen beim Debugging das Inspizieren des Status von RTOS-Objekten wie etwa Tasks und Semaphoren ermöglichen. Hierbei handelt es sich jedoch nur um Nachbesserungen an den Quellcode-Debuggern der zweiten Generation, deren Fokus strikt auf dem Quellcode und dem Debugging nach dem Run/Halt/Single-Step-Prinzip liegt. Ein RTOS-basiertes System mit einem traditionellen Source-Level Debugger zu debuggen, ist vergleichbar mit dem Versuch, einen auf der Assembler-Ebene arbeitenden Debugger bei der C-Programmierung einzusetzen.

Bildergalerie

Um das Laufzeitverhalten eines RTOS-basierten Systems umfassend zu verstehen, müssen Sie das Echtzeitverhalten auf der RTOS-Ebene beobachten können, wofür ein auf RTOS ausgerichtetes Tracing-Tool benötigt wird. Dieses operiert als Ergänzung zu traditionellen Debugging-Tools und stellt eine Zeitachse auf der RTOS-Ebene bereit. Ein traditioneller Debugger ähnelt sehr einem Mikroskop, mit dem sich die detaillierte Verarbeitung innerhalb einer Task untersuchen lässt. Das Tracing dagegen ist mehr so etwas wie ein Zeitlupenvideo der Echtzeitverarbeitung.

Es gibt zwei Arten des Tracings, die jeweils geringfügig unterschiedlichen Zwecken dienen. Das hardwarebasierte Tracing wird vom Prozessor selbst generiert. Es liefert ein detailliertes Verarbeitungs-Trace auf der Quellcode- oder Assembler-Ebene, allerdings mit wenig oder gar keiner RTOS-Orientierung. Es wird hauptsächlich für die Überdeckungsanalyse und das Debugging besonders kniffliger Probleme verwendet, für die ein maschinennahes Instruction-Tracing benötigt wird.

Beim softwarebasierten Tracing werden dagegen kleine Tracecode-Abschnitte in die Zielsoftware eingefügt, um wichtige Ereignisse im RTOS sowie optional auch im Applikations-Code aufzuzeichnen. Meist muss man den RTOS-Trace-Code aber nicht selbst einfügen, denn viele RTOS-Kernels enthalten an strategischen Punkten bereits Trace-Makros. Das softwarebasierte Tracing kann deshalb auf jedem Prozessor genutzt werden, beliebige Arten von Software-Ereignissen speichern und jegliche relevanten Daten einschließen.

Nachteilig ist der Verarbeitungsaufwand des Tracing-Codes, allerdings verbraucht das RTOS-Tracing auf einer modernen 32-Bit-MCU nur ein paar Prozent der Prozessorzeit, da nur sehr wenige Daten aufgezeichnet werden müssen und die erfassten Ereignisse nicht sehr häufig auftreten. Hierdurch ist ein kontinuierliches Streamen der Daten über gängige Debug-Schnittstellen oder auch über Interfaces wie USB oder TCP/IP möglich. Die geringe Datenrate lässt ebenfalls das Tracing in einen RAM-Puffer zu. Schon wenige Kilobyte reichen aus, um ein brauchbares Trace der jüngsten Ereignisse zu bekommen. Somit ist ein Tracing auch außerhalb des Labors möglich – zum Beispiel im Zuge von Feldtests oder an schon im Einsatz befindlichen Systemen.

Für das Begreifen der Traces ist die Visualisierung entscheidend. Viele Embedded-Systeme legen ein mehr oder weniger zyklisches Verhalten an den Tag, sodass es sich beim Großteil der Trace-Daten um irrelevante Wiederholungen der ‚normalen‘ Verhaltensweisen handelt. Interessant sind dagegen meist die Anomalien, die jedoch unter Umständen schwierig zu finden sind, wenn man nicht genau weiß, wonach man eigentlich sucht. Das menschliche Gehirn leistet allerdings Außerordentliches, wenn es um das Erkennen visueller Muster und Anomalien geht. Voraussetzung hierfür ist jedoch die korrekte Visualisierung der Daten.

Es gibt viele Tools, die ein RTOS-Trace als klassisches Gantt-Diagramm darstellen. Diese Visualisierung eignet sich aber nicht, um weitere Ereignisse (z. B. API-Aufrufe oder User-Logging) in derselben Ansicht anzuzeigen. Ein vertikales Execution Trace dagegen kann derartige Ereignisse mit Beschriftungsfeldern anzeigen, die auf das grafische Execution Trace verweisen. Die alleinige Anzeige eines Execution Trace ist außerdem eine sehr eingeschränkte Perspektive, da aus einem RTOS-Trace deutlich mehr Informationen extrahiert werden können. Zum Beispiel lassen sich die Interaktionen von Tasks und ISRs als Abhängigkeitsgraph visualisieren, und ebenso können Trace-Ansichten angezeigt werden, die sich auf weitere relevante RTOS-Objekte wie Semaphoren, Warteschlangen und Mutexe konzentrieren.

Trace-Visualisierung mit dem Tracealyzer

Mit dem Tracealyzer können Sie die Geschwindigkeit Ihrer Entwicklung und die Qualität Ihrer Software entscheidend steigern, denn Sie erhalten bessere Möglichkeiten für das Debugging, die Optimierung und die Vermeidung potenzieller Probleme schon in einer frühen Phase.

Das Visualisierungs-Tool bietet mehr als 25 interaktive Ansichten, die zahlreiche Aspekte des Echtzeitverhaltens offenbaren. Sie stützen sich dabei auf RTOS-Ereignisse, Speicherzuweisungs-Ereignisse und kundenspezifische User-Ereignisse im Applikations-Code. Die Spanne der Visualisierungen reicht von detaillierten grafischen Traces und Test-Logs bis zu individuellen Daten-Plots, Task-Statistiken und Abhängigkeitsgraphen. Sämtliche Ansichten sind zudem miteinander verknüpft, sodass Sie sich von Anomalien in den abstrakten Darstellungen zu den einzelnen Ereignissen vorarbeiten können. Auch ein Wechsel zwischen verschiedenen Perspektiven ist problemlos möglich, ohne die Fokussierung auf das jeweilige Problem aufzugeben.

Tracealyzer ist ein eigenständiges Tool, das parallel zu einem vorhandenen Debugger verwendet werden kann. Es erfordert keinerlei besondere Hardware und ist auf praktisch jedem Prozessor lauffähig, sofern ein unterstütztes RTOS eingesetzt wird. Kontinuierliches Streaming ermöglicht das Tracing Ihres Systems über lange Zeitspannen hinweg, während das Tracing an einen RAM-Puffer das Sammeln von Traces ohne Debugger-Verbindung im Rahmen von Feldtests oder an bereits im Einsatz befindlichen Systemen zulässt.

Einige Kunden aktivieren Tracealyzer im Produktions-Code und erfassen Traces aus der Ferne. Dies gibt den Entwicklern detaillierte Problemdiagnosen aus dem Praxisbetrieb in die Hand, die sonst beinahe unmöglich zu reproduzieren wären.

Die Haupt-Traceansicht (Main Trace View) visualisiert alle Daten entlang einer vertikalen Zeitachse und fokussiert sich auf die Ausführung von Tasks, sowie auf Interrupthandler und Ereignisse wie etwa RTOS-API-Aufrufe und Ihre eigenen anwenderspezifischen Ereignisse. Jeder Task und jedem Interrupthandler wird abhängig von der Priorität eine eigene Farbe zugewiesen, die übereinstimmend in allen Ansichten benutzt wird. Mit einem Doppelklick auf eine Task, eine ISR oder ein Ereignis öffnet sich eine weitere Ansicht, die relevante Ereignisse zeigt.

Mit User Events ist ein explizites Logging gemeint, das ähnlich wie der klassische ‚printf‘-Aufruf in den Applikations-Code eingefügt wird. Allerdings geht die Verarbeitung deutlich schneller und dauert auf den meisten 32-Bit-MCUs nur wenige Mikrosekunden. User Events werden in der Hauptansicht mit gelben Beschriftungsfeldern angezeigt und lassen sich nutzen, um für das Debugging oder die Performance-Analyse weitere Details über den Applikations-Kontext beizusteuern. Wie in den meisten anderen Ansichten auch, ist jeder Listeneintrag mit dem zugehörigen Ereignis in der Haupt-Traceansicht verknüpft. Ein Doppelklick fokussiert die Hauptansicht somit auf das entsprechende Ereignis.

Auch die dynamische Speichernutzung lässt sich analysieren. Auch wenn die dynamische Speicherzuweisung bei Embedded-Systemen allgemein als schlechtes Verfahren gilt, ist sie gelegentlich notwendig. Tracealyzer ermöglicht Ihnen das Analysieren der Zuweisungen und wartet sogar mit einem Filter auf, mit dem sich Speicherlecks einfacher finden lassen.

Tracealyzer wird für führende RTOS-Produkte und auch für Linux-Systeme angeboten. Es bietet direkte Unterstützung für die meisten 32-Bit-Prozessoren (einschließlich ARM) und lässt sich einfach auf andere Architekturen portieren.

Der Trend zum RTOS in der Embedded-Branche ist unübersehbar – und dies aus gutem Grund. RTOS lassen sich als dritte Generation der Firmware-Entwicklung betrachten, da das Multithreading einen höheren Abstraktionsgrad und weniger Kontrolle über die Verarbeitung bietet. Dies birgt jedoch erhebliche Fallstricke, die nach besserer Debugging-Unterstützung auf der RTOS-Ebene verlangen.

Vereinfachen lässt sich das Debugging RTOS-basierter Systeme mithilfe besserer Einblicke in ihre Echtzeitverarbeitung. Hierzu bedarf es des Tracings auf der RTOS-Ebene, wobei die Visualisierung entscheidend für das Begreifen der Daten ist. Percepio Tracealyzer ist die führende Lösung für diesen Zweck, denn es vermittelt bessere Einblicke und sorgt für höhere Qualität und eine schnellere Entwicklung.

* Dr. Johan Kraft ist CEO und Gründer von Percepio AB.

Artikelfiles und Artikellinks

(ID:44950748)