Software-Tracing bei Embedded-Linux Mit LTTng dem Fehler auf der Spur

Das Debugging von Embedded-Software kann zu einer echten Herausforderung werden. Mithilfe von Tracing lassen sich Probleme bei der Programmausführung häufig schnell und zuverlässig lösen. Für Multi-Threaded-Softwaresysteme unter Embedded-Linux eignet sich die Open-Source-Lösung »LTTng«.

Tracing zeichnet das Verhalten der Software zur Laufzeit auf, sodass sich die Trace-Daten nachträglich analysieren lassen. Meist nutzt man das Tracing im Zuge der Entwicklung, es kann aber auch in der Produktion zum Einsatz kommen. Dann läuft es ständig im Hintergrund und erfasst Fehler, die erst nach der Auslieferung (Deployment) auftreten. Besonders effektiv ist dieses »Production Tracing« beim Detektieren von Fehlern, die nur sporadisch auftreten, was ihre Reproduktion mithilfe eines Debuggers schwierig macht. Es geht hier beispielsweise um Fälle, in denen ein System unerwartet langsam reagiert, falsche oder suboptimale Resultate liefert, sich aufhängt oder komplett abstürzt.

Tracing kann entweder in der Hardware – also im Prozessor – oder in der Software erfolgen. Das hardwarebasierte Tracing erzeugt eine detaillierte Historie der verarbeiteten Befehle, ohne das analysierte System zu stören. Nachteilig an dieser Technik ist, dass ein Prozessor, eine Leiterplatte und ein Debugger mit expliziter Trace-Unterstützung nötig sind. Deshalb muss die Entwicklung die Unterstützung für das Hardware-Tracing schon sehr früh berücksichtigen, wenn sie die Hardwareplattform für das Projekt auswählt. Mit den meisten Hardware-Trace-Lösungen lassen sich keine Daten aufzeichnen, sondern es lässt sich nur der Kontrollfluss (also der ausgeführte Code) registrieren.

Dagegen liegt der Fokus des softwarebasierten Tracings auf ausgewählten Ereignissen wie Betriebssystemaufrufen, Interruptroutinen und Aktualisierungen wichtiger Variablen. Dies erfordert keinerlei besondere Hardware, und Entwickler können es ähnlich wie einen Flugschreiber in einem Flugzeug sogar in bereits ausgelieferten Produkten einrichten. Beim Software-Tracing lassen sich außerdem relevante Daten zusammen mit den Ereignissen (zum Beispiel Parametern von Systemaufrufen) abspeichern.

Nachteilig am Software-Tracing ist, dass der Systemprozessor und das RAM zum Abspeichern der Ereignisse in Anspruch genommen werden. Meist dauert das Speichern eines Ereignisses aber nur ein paar Mikrosekunden, sodass ein mit Software-Tracing ausgerüstetes System mit etwa 99% seiner regulären Geschwindigkeit läuft. Die vom Tracing beanspruchte Prozessorzeit lässt sich überdies durch die besseren Möglichkeiten zum Optimieren der Software kompensieren. Ein weiteres Problem beim softwarebasierten Tracing ist der »Probe-Effekt«, also die theoretischen Rückwirkungen auf das Verhalten der Software durch den Einfluss des Tracings auf das Timing. Allerdings sind die Timing-Effekte gering, und gängige Änderungen an der Software können ähnliche Auswirkungen haben. Außerdem lässt sich dieser Effekt komplett vermeiden, indem man die Aufzeichnung immer, also auch im Produktions-Code, aktiv lässt. Die Trace-Aufzeichnung wird dadurch zum Bestandteil des integrierten, geprüften Systems. Vorteil dieses Konzepts: Traces können vom Fehlerbehandlungscode automatisch abgespeichert werden, um Post-Mortem-Analysen entscheidend zu vereinfachen.

Besonders wichtig ist das Tracing, wenn ein System über ein integriertes Betriebssystem verfügt. Denn beim Multithreading, einem zentralen Merkmal von Betriebssystemen, wechselt das System schnell zwischen verschiedenen Verarbeitungskontexten. Besonders praktikabel ist das Multithreading für Embedded-Software, in der mehrere periodische Aktivitäten mit unterschiedlichen Geschwindigkeiten laufen müssen. Beispiele hierfür sind Steuerungssysteme oder jene Fälle, in denen bei bestimmten Ereignissen zeitkritische Funktionen zu aktivieren sind, die andere, weniger dringende Abläufe unterbrechen (Präemption). Allerdings macht das Multithreading die Software auch komplexer und lässt dem Entwickler weniger Kontrolle über das Laufzeitverhalten, da das Betriebssystem die Verarbeitung unterbrechen kann.

Tracing mit »LTTng«

LTTng (Linux Trace Toolkit - next generation) ist eine führende Lösung für das softwarebasierte Tracing in Linux. Nicht nur die meisten Linux-Distributionen unterstützen dieses Open-Source-Tool, sondern auch »Yocto«, ein verbreitetes Build-System für Embedded-Linux. LTTng ist überaus effizient, im praktischen Einsatz erprobt und bietet Unterstützung für Linux-Kernels ab Version 2.6.32. Kernels ab v2.6.38 unterstützt es ohne jegliche Modifikation am Kernel.

LTTng basiert auf sogenannten Tracepoints, das sind Funktionsaufruf-Platzhalter, die im Normalzustand inaktiv sind. Ein inaktiver Tracepoint erfordert nur wenige Taktzyklen und wirkt sich damit nur minimal auf die Performance aus. Wird LTTng aktiviert, verbindet es die Tracepoints zu einer internen LTTng-Funktion, die das betreffende Ereignis in einem RAM-Puffer abspeichert. Die im RAM-Puffer befindlichen Tracedaten lassen sich entweder fortlaufend auf einen Plattenspeicher übertragen oder über eine Netzwerkverbindung an ein anderes System auslagern. Threads im User-Space führen diese Übertragungen aus. Eine weitere Möglichkeit ist, die Tracedaten in einem zyklischen RAM-Puffer zu speichern. Hier werden die jeweils ältesten Daten von den neuen überschrieben, sobald der Puffer voll ist. In dieser Betriebsart wird bei Bedarf eine Momentaufnahme mit den neuesten Ereignissen abgespeichert.