Suchen

Einführung in die Linux Tracing Infrastruktur

Autor / Redakteur: Jan Altenberg / Sebastian Gerstl

Linux bietet mit seiner Tracing-Infrastruktur ein sehr mächtiges Werkzeug zur Analyse beliebiger Vorgänge innerhalb des Betriebssystems. Dieser Beitrag verschafft einen groben Überblick über die im Betriebssystem verfügbaren Tracing-Hilfsmittel und deren Bedienung.

Firmen zum Thema

Einzelne Vorgänge innerhalb eines Embedded-Systems nachzuvollziehen oder zu analysieren kann sich als Herausforderung gestalten. Linux bietet nativ bereits eine Tracing-Infrakstruktur und hält eine Reihe von Tracing-Tools bereit, mit der sich Ereignisse gezielt steuern oder überprüfen lassen.
Einzelne Vorgänge innerhalb eines Embedded-Systems nachzuvollziehen oder zu analysieren kann sich als Herausforderung gestalten. Linux bietet nativ bereits eine Tracing-Infrakstruktur und hält eine Reihe von Tracing-Tools bereit, mit der sich Ereignisse gezielt steuern oder überprüfen lassen.
(Bild: gemeinfrei / Pixabay )

Egal ob Analyse von Nebenläufigkeiten, die Untersuchung von Latenzen oder die Behebung von Serialisierungsproblemen: Die Fehlersuche innerhalb des Betriebssystems stellt immer wieder eine ganz besondere Herausforderung dar! Glücklich kann sich hier schätzen, wer als Betriebssystem Linux einsetzt! Denn neben der einfachen Portierbarkeit und der enormen Anzahl an unterstützter Hardware hat sich Linux nicht zuletzt durch die sehr umfangreichen Debugging Möglichkeiten durchgesetzt. Insbesondere mit seiner Tracing Infrastruktur bietet Linux ein sehr mächtiges Werkzeug, um jegliche Art von Abläufen innerhalb des Betriebssystems zu analysieren, zu verstehen und auch zu visualisieren.

Grundlagen

Ein ganz herausragender Aspekt ist die Tatsache, dass für die Verwendung der Linux Tracing Infrastruktur keine besonderen Tools notwendig sind. Es gibt zwar eine große Auswahl von Werkzeugen, die den Anwender unterstützen, diese dienen aber lediglich der leichteren Handhabung und der besseren Visualisierung der Analyseergebnisse.

Bildergalerie
Bildergalerie mit 6 Bildern

Doch befassen wir uns zunächst damit, welche Möglichkeiten zur Fehlersuche die Tracing Infrastruktur überhaupt bietet. Ganz einfach gesagt, können verschiedenste Ereignisse innerhalb des Betriebssystems aufgezeichnet werden. Die Aufzeichnung erfolgt in einen Ringbuffer (siehe Bild 1).

Die Events können von ganz unterschiedlichen Quellen generiert werden. So gibt es sogenannte Tracepoints bzw. Traceevents, die über das ganze Betriebssystem verteilt sind. Diese sind in Subsysteme kategorisiert, wie zum Beispiel Interrupts oder Scheduling. Weiterhin besteht mit den kprobes und uprobes die Möglichkeit, eigene Events in das Betriebssystem und auch in beliebige Applikationen zu integrieren. Mit den Tracern stellt Linux bereits bestimmte Heuristiken zur Verfügung, um konkrete Problemstellungen, wie die Analyse von Codepfaden, die Untersuchung des Scheduling Verhaltens oder von Latenzen zu untersuchen. Dies sind nur einige wenige Beispiele der vielen Eventquellen innerhalb des Betriebssystems, die beliebig und unabhängig voneinander konfiguriert werden können.

Bild 2 veranschaulicht die Konfiguration der verschiedenen Eventquellen. So können ganze Gruppen von Ereignissen, einzelne Ereignisse oder sogar selbst definierte Ereignisse getrennt voneinander aktiviert werden. Die Auswahl der einzelnen Events ist nicht exklusiv, d.h. es können beliebig viele Events miteinander kombiniert und aufgezeichnet werden. Die Aufzeichnung erfolgt mit derselben Zeitbasis in denselben Ringbuffer.

Die umfangreichen Konfigurationsmöglichkeiten sind ein sehr wichtiger Aspekt, denn so kann die Menge an Events, die zur Untersuchung eines Fehlerbildes notwendig sind, drastisch eingeschränkt werden. Dies reduziert die Datenmenge und somit den Overhead und die benötigte Rechenleistung. Außerdem erleichtert ein geringeres Volumen aufgezeichneter Daten die Analyse. Wichtig ist zu wissen: Das bloße Vorhandensein eines Events verursacht keinen bzw. nur kaum messbaren Overhead. Erst beim Aktivieren eines Events, wird der zugehörig Code dynamisch in das Betriebssystem gepatcht.

Erste Schritte

Wie zuvor bereits beschrieben, wird für die Konfiguration und die Verwendung der Tracing Infrastruktur kein besonderes Werkzeug benötigt. Wie in der Linuxwelt üblich, erfolgt die Bedienung über ein virtuelles Filesystem, in diesem Falle über das sogenannte DebugFS. Auf den meisten gängigen Linux Distributionen ist DebugFS automatisch eingebunden, und zwar unter: /sys/kernel/debug

Sofern DebugFS nicht automatisch gemounted ist, kann dies über folgendes Kommando erfolgen:

$ mount -t nodev debugfs /sys/kernel/debug

Sowohl für das Einbinden von DebugFS, als auch für die spätere Bedienung der Tracing Infrastruktur, sind Root Privilegien erforderlich. Der für das Tracing relevante Bereich, befindet sich nach dem Mounten des virtuellen Filesystem unter /sys/kernel/debug/tracing.

Die Bedienung der Tracing Infrastruktur lässt sich am einfachsten an einem Beispiel erläutern. Nehmen wir an, wir möchten alle Ereignisse aufzeichnen, die mit dem Scheduling zu tun haben. Hierfür müssen zunächst alle relevanten Events aktiviert werden (die zugehörige Eventgruppe heißt „sched“):

$ echo 1 > events/sched/enable

Mit dem obenstehenden Kommando wird die Aufzeichnung aller Events dieser Gruppe aktiviert. Von nun an werden die Ereignisse in Binärform in den Ringbuffer geschrieben. Der Inhalt des Ringbuffers kann jederzeit (auch während einer Aufzeichnung) über die Datei mit dem Namen „trace“ ausgegeben werden, z.B. mit dem Kommando less:

$ less trace

Beim Zugriff auf diese Datei wird der Ringbuffer Inhalt nicht nur gelesen, sondern auch direkt in ein menschenlesbares Format umgewandelt. Bild 3 zeigt das Ausgabeformat. Jede Zeile steht für ein aufgezeichnetes Event. Die erste Spalte zeigt den Prozess, der beim Auftreten des Ereignisses aktiv war. Die zweite Spalte zeigt die CPU auf der das Event auftrat. Danach folgen einige Detailinformationen, wie zum Beispiel ob Interrupts deaktiviert waren. Jedes Ereignis wird mit einem Mikrosekunden genauen Timestamp versehen. Neben dem Namen des Events werden zusätzlich einige Randinformationen aufgezeichnet, die weitere Informationen geben.

Das Lesen einer Aufzeichnung über die Datei „trace“ hat keinerlei Einfluss auf den Inhalt des Ringbuffers. Es besteht allerdings auch die Möglichkeit, Ereignisse konsumierend zu lesen, d.h. Events können beim Auslesen zeitgleich aus dem Ringbuffer entfernt werden. Dies wird bei längeren Aufzeichnungen benötigt, um ein Überlaufen und somit ein Überschreiben von Events zu verhindern. Das konsumierende Lesen erfolgt über die Datei „trace_pipe“, das Ausgabeformat ist genau dasselbe, wie obenstehend beschrieben. Mit dem Kommando:

echo 0 > events/sched/enable

können die zuvor aktivierten Events wieder deaktiviert werden. Übrigens können die bisher aufgezeichneten Ereignisse jederzeit mit: echo > trace gelöscht werden.

Das Recording in den Ringbuffer ist per default aktiviert, d.h. mit dem Einschalten eines Events wird dieses auch sofort aufgezeichnet, wenn es gertiggert wird. Wer die Aufzeichnung von Events in den Ringbuffer stoppen möchte, kann dies über die Datei tracing_on tun:

$ echo 0 > tracing on

deaktiviert das Aufzeichnen. Mit dem Schreiben einer „1“ wird das Recording wieder gestartet.

Multicore-Systeme

Bevor wir nun einen genaueren Blick auf die Tracing Infrastruktur werfen, sollten wir uns zunächst noch einmal näher mit dem Ringbuffer beschäftigen. Der Einfachheit halber wurde bisher nur von einem einzigen Ringbuffer gesprochen. Auf Mehrkernsystemen ist allerdings für jeden einzelnen Core ein separater Ringbuffer vorhanden, in den die zugehörigen Events aufgezeichnet werden. Das Auslesen über die im vorangegangenen Kapitel besprochene Datei „trace“ liest die Ereignisse aller CPUs aus und aggregiert diese auf einer Zeitschiene. Wer einen gezielten Blick auf die Vorgänge eines bestimmten CPU Cores werfen möchte, der kann dies über das Unterverzeichnis „per_cpu/“ tun. Dort wird für jeden Core ein Verzeichnis angelegt, welches die Dateien „trace“, „trace_pipe“ und „trace_pipe_raw“ beinhaltet. Der Zugriff auf diese Dateien erfolgt exakt so, wie es zuvor beschrieben wurde, nur eben mit dem Unterschied, dass sich das Auslesen der Ereignisse nur auf einen bestimmten CPU Core bezieht. Wer neben dem bereits aufbereiteten Format auch an den Binärdaten einer Aufzeichnung interessiert ist, der kann diese per „trace_pipe_raw“ auslesen.

In vielen Szenarien kann es zur leichteren Analyse nützlich sein, das Recording von verschiedenen Events voneinander zu trennen. Hierfür gibt es sogenannte Tracing Instanzen. Mit jeder Instanz, die der Nutzer anlegt, wird pro Core ein weiterer Ringbuffer angelegt (das Anlegen einer Instanz erfolgt im Unterverzeichnis „instances/“, siehe Bild 4).

Im jeweiligen Verzeichnis einer Instanz findet sich wieder dieselbe Ordner und Datenstruktur, mit der Events für diese Tracing Instanz aktiviert, konfiguriert und ausgelesen werden können. Wird eine Instanz nicht mehr benötigt, so kann diese durch das Löschen des zugehörigen Ordners unterhalb von instances/ erfolgen:

rmdir inst0

Weitere Konfigurationsmöglichkeiten

Gehen wir noch einmal zurück zum Beispiel mit den Scheduling Events. Im ersten Schritt haben wir alle Events der Gruppe „sched“ aufgezeichnet. Selbstverständlich können auch nur einzelne Ereignisse und diese auch nur unter ganz bestimmten Bedingungen aktiviert werden. Angenommen wir interessieren uns nur für das Event mit dem Namen sched_wakeup (dieses wird generiert, wenn ein bestimmter Prozess aufgeweckt, also als lauffähig markiert wird):

$ echo 1 > events/sched/sched_wakeup/enable

Nun haben wir also lediglich das sched_wakeup Event aktiviert. Wer die Datenmenge noch weiter einschränken möchte, dem stehen zusätzlich noch die sogenannten Filter zur Verfügung. Beim Auslesen des Ringbuffers haben wir gesehen, dass mit jedem Ereignis auch zusätzliche Informationen geschrieben werden. Welche Daten mit einem Event genau aufgezeichnet werden, kann man sehr einfach prüfen:

$ cat events/sched/sched_wakeup/format

Mit dem obenstehenden Kommando bekommt man eine genau Darstellung der Datenstruktur für das sched_wakeup Event.

Bildergalerie
Bildergalerie mit 6 Bildern

Bild 5 zeigt die entsprechende Ausgabe. Auf alle Elemente der dargestellten Datenstruktur kann gefiltert werden, d.h. ein Ereignis wird nur dann aufgezeichnet, wenn der Inhalt eines der Elemente einer bestimmten Bedingung entspricht. Sind wir zum Beispiel nur daran interessiert, wenn der Prozess „sh“ aufgeweckt wird, so kann dies wie folgt erreicht werden (der Name des Prozesses wird im Feld „comm“ abgelegt):

echo comm == “sh“ > events/sched/sched_wakeup/filter

Von nun an werden alle sched_wakeup Events aufgezeichnet, aber nur unter der Bedingung, dass das Datenfeld comm den String „sh“ enthält.

Die bisher vorgestellten Funktionen sind selbstverständlich nur ein Bruchteil dessen, was die Tracing Infrastruktur zum Aufzeichnen von Traceevents bietet. Eine sehr umfangreiche Dokumentation befindet sich in den Sourcen des Linux Kernels unter: Documentation/trace/events.rst

Kprobes

Die bereits vorhanden Trace Events des Linux Kernels bieten schon sehr mächtige und umfangreiche Analysemöglichkeiten. Doch es kommt noch besser: Mit den Kprobes kann der Nutzer an jeder beliebigen Stelle des Betriebssystems eigene Events hinzufügen. Dies erfolgt dynamisch zur Laufzeit, es sind also keinerlei Codeanpassungen notwendig!

Das Einfügen eines Kprobes ist denkbar einfach. Wer ein eigenes Event erstellen möchte, muss der Tracing Infrastruktur lediglich mitteilen, wie das Event heißen soll und wo es eingefügt werden soll. Auch hierfür kommt wieder das DebugFS Interface zum Einsatz. Möchten wir ein Event mit dem Namen „my_probe“ beim Einsprung in die Funktion generic_handle_irq generieren, so müssen wir folgendes Kommando ausführen:

echo “p:my_probe generic_hande_irq” > kprobe_evens

Bild 6 veranschaulicht die Syntax nochmals.

Sobald ein Kprobe angelegt wurde, verhält es sich wie jedes andere Event im Betriebssystem, d.h. alle Schritte, die bisher zur Aktivierung eines Events gezeigt wurden, sind auch für Kprobes gültig. Bezogen auf das hier beschriebene Beispiel würde das neu erstellte Event also wie folgt aktiviert werden:

echo 1 > events/kprobes/my_probe/enable

Auch die Kprobes bieten einen enormen Funktionsumfang, so kann die Eventdefinition auch festlegen, welche zusätzlichen Daten bei einem Kprobe mit aufgezeichnet werden und auch das Einfügen eines Kprobes ist so gut wie an jeder Stelle möglich. Eine umfangreiche Dokumentation der Kprobes findet sich ebenfalls in den Quellen des Linux Kernels, nämlich unter:

Documentation/trace/kprobetrace.rst.

Fazit

Linux bietet mit seiner Tracing Infrastruktur ein sehr mächtiges Werkzeug zur Analyse beliebiger Vorgänge innerhalb des Betriebssystems. Neben einer Vielzahl frei konfigurierbarer Ereignisse, die über das ganz Betriebssystem verteilt sind, können auch zur Laufzeit beliebige Events hinzugefügt werden. Die Aufzeichnung verschiedener Ereignisse kann beliebig miteinander kombiniert werden und es kann sehr feingranular auf bestimmte Vorgänge gefiltert werden. Zur Bedienung der Tracing Infrastruktur sind keine speziellen Tools notwendig: Konfiguration, Aufzeichnung und Auswertung können komplett über ein virtuelles Filesystem erfolgen!

Die hier vorgestellten Funktionen sind selbstverständlich nur ein Bruchteil dessen, was mit der Tracing Infrastruktur möglich ist, und sollen nur einen ersten Einblick in die Möglichkeiten und die Bedienung bieten. In den nächsten Artikeln dieser Reihe beschäftigen wir uns damit, wie die sogenannten Tracer verwendet werden, wie Events aus einer Applikation heraus generiert werden können, welche Tools existieren und wie eine Trace-Auswertung grafisch erfolgen kann.

* Jan Altenberg beschäftigt sich seit mehr als 15 Jahren beruflich mit Linux. Er betreute u.a. verschiedene Arbeiten für das EU-Projekt OCEAN, welches sich zum Ziel setzte, eine offene Steuerungsplattform auf Basis von Realtime Linux zu schaffen. Weiterhin trug er bei einem namhaften Maschinenhersteller die volle konzeptionelle Verantwortung für die Einführung von Linux in der neuen Steuerungsgeneration. Von 2007-2019 arbeitete er für die Linutronix GmbH und leitete dort zuletzt den technischen Vertrieb. Jan Altenberg ist regelmäßiger Sprecher auf verschiedenen Fachkonferenzen und wurde 2014, 2018 und 2019 von den Besuchern des ESE Kongress mit dem Speaker Award Publikumspreis ausgezeichnet. Seit April 2019 arbeitet er als System Architekt und Experte für Open-Source Technologien für die Continental Automotive GmbH.

(ID:46754113)

Über den Autor

 Jan Altenberg

Jan Altenberg

System Architect ADAS / Open-Source Technology Expert, Continental Automotive GmbH