Linux Tracing Infrastruktur: Tiefere Funktionen und gängige Anwendungsszenarien

Autor / Redakteur: Jan Altenberg / Sebastian Gerstl

In den ersten beiden Teilen dieser Artikelserie haben wir uns mit den Grundlagen der Linux Tracing Infrastruktur und verschiedenen Werkzeugen zu deren Nutzung beschäftigt. Wir möchten uns in der Fortsetzung nun mit einigen tiefergreifenden Funktionen und gängigen Anwendungsszenarien auseinandersetzen. Dies umfasst die Nachverfolgung von Funktionsaufrufen und Abläufen im Betriebssystem, sowie das Generieren von Events aus einer Applikation heraus.

Firmen zum Thema

Die im Linux Kernel enthaltenen Funktionen wie Tracemarker und Uprobes ermöglichen eine viel tiefgängigere Analyse von Funktionsaufrufen und Abläufen im Betriebssystem.
Die im Linux Kernel enthaltenen Funktionen wie Tracemarker und Uprobes ermöglichen eine viel tiefgängigere Analyse von Funktionsaufrufen und Abläufen im Betriebssystem.
(Bild: Clipdealer)

Tracer

Die Analyse von bestimmten Fehlerszenarien erfordert meist dasselbe oder zumindest ein ähnliches Vorgehen und häufig auch eine bestimmte Strategie. Auch hierfür stellt uns die Linux Tracing Infrastruktur einige sehr mächtige Werkzeuge zur Seite: Die sogenannten Tracer.

Neben der reinen Aufzeichnung von Events bieten die Tracer zusätzlich bereits eine bestimmte Heuristik oder sind einem bestimmten Zweck gewidmet. Dies kann zum Beispiel die Untersuchung von Interrupt- oder Schedulinglatenzen sein, oder das Nachverfolgen von Funktionsaufrufen. Im Gegensatz zum reinen Eventtracing kann das Übersetzen des Linux Kerns mit bestimmten Tracer Funktionalitäten bereits einen Einfluss auf das zeitliche Verhalten des Systems haben. Deswegen sind auf einem Produktivsystem üblicherweise nicht alle, sondern nur bestimmte Tracer verfügbar.

Welche Funktionen genau zur Verfügung stehen, lässt sich ganz einfach mit folgendem Kommando herausfinden:

$ cat available_tracersfunction_graph function nop

In diesem Beispiel stehen der function und der function_graph Tracer zur Verfügung; auf beide werden wir im Folgenden näher eingehen. Beginnen wir zunächst mit dem sogenannten function Tracer: Dieser ist dazu gedacht, Codepfade zu untersuchen und zu verfolgen. Hierzu kann praktisch jeder beliebige Funktionsaufruf in ein Event verwandelt und aufgezeichnet werden. Die Aktivierung eines Tracers erfolgt über die Datei „current_tracer“, über diese kann auch geprüft werden, ob aktuell ein Tracer aktiv ist:

$ cat current_tracernop

Der Wert “nop” bedeutet, dass aktuell keiner der verfügbaren Tracer aktiviert ist. Um nun beispielsweise function tracing einzuschalten, muss folgendes Kommando ausgeführt werden:

$ echo function > current_tracer

Von nun an wird jeder einzelne Funktionsaufruf innerhalb des Betriebssystems als Event aufgezeichnet. Beim Auslesen des Ringbuffers über die Datei „trace“, sieht ein entsprechender Eintrag dann ungefähr wie folgt aus:

sh-79 [000] dn.. 358.947865: wakeup_preempt_entity <-pick_next_task_fair

Aufgezeichnet wurde in diesem Fall ein Aufruf von „wakeup_preempt_entity()“aus der Funktion „pick_next_task_fair()“. Ohne zusätzliche Konfigurationsschritte werden beim function tracing alle Funktionsaufrufe innerhalb des Betriebssystemkerns aufgezeichnet. Man kann sich leicht vorstellen, mit welcher Fülle an Events man es hier zu tun bekommt.

Bild 1: Function Tracing
Bild 1: Function Tracing
(Bild: Jan Altenberg / Continental)

Bild 1 zeigt einen Ausschnitt aus einer entsprechenden Aufzeichnung. Die Analyse eines solchen Traces kann aufgrund der puren Datenmenge sehr schwierig sein. Darüber hinaus hat das Erzeugen einer großen Anzahl an Events einen nicht unwesentlichen Einfluss auf das Zeitverhalten des Systems. Aus diesem Grund stehen (so wie beim Event Tracing auch) diverse Möglichkeiten zur Konfiguration der Aufzeichnung und zum Filtern von Ereignissen zur Verfügung. So kann das Recording von Funktionen auf den Zeitraum der Ausführung einer bestimmten Applikation beschränkt werden. Dies erfolgt über die Datei „set_ftrace_pid“:

echo 79 > set_ftrace_pid

Obenstehendes Kommando führt dazu, dass die Aufzeichnung von Funktionsaufrufen fortan nur noch erfolgt, während der Prozess mit der PID 79 aktiv ist. Dies kann insbesondere bei der Analyse von Latenzproblemen in einem bestimmten Programm sehr hilfreich sein.

Selbstverständlich lässt sich die Aufzeichnung auch auf die Aufrufe bestimmter Funktionen begrenzen. Hierfür steht die Datei „set_ftrace_filter“ zur Verfügung. Ein Filter kann wahlweise explizit über den Funktionsnamen oder auch über Wildcards angelegt werden. Im Folgenden einige Beispiele:

# Aufzeichnung aller Aufrufe von add_wait_queue$ echo add_wait_queue > set_ftrace_filter# Erweitern des bestehenden Filters um alle Aufrufe, die mit irq_ beginnen$ echo 'irq_*' >> set_ftrace_filter# Anzeigen der gesetzten Filer:$ cat set_ftrace_filterirq_enterirq_exitirq_sysfs_addirq_to_desc[…]

Während „set_ftrace_filter” angibt, welche Funktionsaufrufe aufgezeichnet werden sollen, so kann über die Datei „set_ftrace_notrace“ umgekehrt angegeben werden, welche Aufrufe von der Aufzeichnung ausgeschlossen sind. Mit diesen Filtermethoden lässt sich die Datenmenge und der entsprechende zeitliche Einfluss auf das System wesentlich reduzieren. Mit dem function Tracer bietet die Linux Tracing Infrastruktur bereits eine sehr mächtige Möglichkeit, beliebige Codepfade aufzuzeichnen und nachzuverfolgen. In vielen Fällen kann aber neben den reinen Funktionsaufrufen noch eine weitere Information von Interesse sein, nämlich die Aufrufhierarchie. Und auch hierfür gibt es ein passendes Werkzeug: Den function_graph Tracer.

Aktiviert wird der function_graph Tracer analog zum vorhergehenden Beispiel:

$ echo function_graph > current_tracer

Hier sehen wir auch eine Einschränkung bei der Verwendung der Tracer. Es kann zu einem Zeitpunkt nur ein bestimmter Tracer aktiv sein. Vergleichen wir nun im Folgenden die Ergebnisse mit denen des function Tracers.

Bild 2: Function Graph Tracer
Bild 2: Function Graph Tracer
(Bild: Jan Altenberg / Continental)

Bild 2 zeigt eine Aufzeichnung mit dem function_graph Tracer. Es ist sehr einfach zu erkennen, dass neben den Aufrufen der einzelnen Funktionen auch die Aufrufhierarchie gezeigt wird. Weiterhin ist über die Annotierung in der 2. Spalte die jeweilige Ausführungsdauer ersichtlich. Selbstverständlich kann auch hier wieder auf bestimmte Aufrufe gefiltert werden. Zusätzlich besteht noch die Möglichkeit eine sogenannte „graph function“ zu setzen, d.h. die Aufzeichnung wird automatisch gestartet, wenn eine bestimmte Funktion aufgerufen wird. Greifen wir das Beispiel aus Abbildung 2 auf und setzen die „graph function“ auf „arch_cpu_idle_enter()“.

$ echo arch_cpu_idle_enter > set_graph_function

Bild 3: function_graph und set_graph_function
Bild 3: function_graph und set_graph_function
(Bild: Jan Altenberg / Continental)

Bild 3 veranschaulicht die Funktion von „set_graph_function“. Die Aufzeichnung wird nun automatisch mit arch_cpu_idle_enter() gestartet und auf die Ausführung dieser Funktion begrenzt. Analog zu den Filtern können auch beliebig viele „graph functions“ gesetzt werden.

Die hier gezeigten Features sind nur ein Bruchteil dessen, was mit den Tracern möglich ist. Weitere Funktionalitäten des function und des function_graph Tracers, sowie eine Beschreibung weiterer Tracer befindet sich in der Dokumentation des Linux Kernels unter: Documentation/trace/ftrace.txt.

trace_marker

Abläufe im Betriebssystem müssen sehr häufig mit Abläufen in einer Applikation korreliert werden. Insbesondere bei der Analyse von Latenzproblemen ist das Zusammenspiel eines Programmes mit dem Betriebssystem von sehr großem Interesse. Für diesen Zweck ist es notwendig, Trace Events auch aus einer Applikation heraus erzeugen zu können. Die Linux Tracing Infrastruktur bietet hierfür verschiedene Möglichkeiten, die einfachste davon ist die Verwendung der sogenannten Tracemarker.

Bild 4: Verwendung eines Tracemarkers
Bild 4: Verwendung eines Tracemarkers
(Bild: Jan Altenberg / Continental)

Die Nutzung der Tracemarker gestaltet sich denkbar einfach. Bild 4 zeigt den grundsätzlichen Ablauf: Über die Datei „trace_marker“ kann ein beliebiger String in den Tracebuffer geschrieben werden. Diese Zeichenkette taucht bei der Analyse des Traces dann als einzelnes Event auf. Der Vorteil dieser Methode ist die äußerst einfache Anwendung, allerdings gibt es auch einen entscheidenden Nachteil. Der Overhead beim Schreiben eines Tracemarkers ist recht groß. Betrachten wir uns den Ablauf hierzu etwas genauer: Das Erzeugen des Events erfolgt durch das Schreiben auf eine virtuelle Datei. Hierzu muss diese geöffnet, geschrieben und wieder geschlossen werden. Das bedeutet, dass allein für das Schreiben eines einzigen Events drei Systemcalls erforderlich sind (open(), write() und close()).

Für einfache Analysen des Ablaufs einer Applikation oder des Zusammenspiels der Applikation mit einem Treiber ist dies durchaus akzeptabel, für zeitkritische Ereignisse oder für Events, die sehr häufig auftreten, ist diese Methode allerdings nicht geeignet. Daher gibt es noch eine weitere Möglichkeit, um Events aus einer Applikation heraus erzeugen zu können: Die sogenannten Uprobes.

SEMINAR-TIPP

Embedded-Linux Woche | 08. - 12. März 2021

Wie entwickelt man eigentlich gute Software? Software, die tolle Features hat und keine Bugs? Treiber, die das Letzte aus der Hardware herauskitzeln? GUIs mit hoher Usability? Die prämierten Referenten der Embedded-Linux-Woche geben Antworten auf diese Fragen in den verschiedenen Seminaren. Diese Embedded-Kurse sollten Sie nicht verpassen:

  • Linux Grundlagen
  • Embedded Linux
  • Systemprogrammierung
  • Embedded Linux Security

Egal ob Sie Anfänger, Fortgeschrittener oder Experte sind, Sie können sich je nach Level ganz einfach Ihren individuellen Kursplan zusammenstellen.

Jetzt informieren und anmelden!

Uprobes

Im ersten Teil dieser Artikelserie haben wir uns mit den Kprobes beschäftigt, mit denen sich dynamisch beliebige Events in das Betriebssystem einfügen lassen. Die Uprobes sind eine logische Weiterentwicklung dieser Technologie. Sie bieten die Möglichkeit Events zur Laufzeit in eine Applikation einzufügen. Die Aufzeichnung erfolgt analog zu allen anderen Events in die entsprechenden Ringbuffer. Wer zuvor bereits mit den Kprobes gearbeitet hat, dem wird die Verwendung der Uprobes sehr leichtfallen, denn die Konfiguration lehnt sich sehr stark an die der Kprobes an. Betrachten wir dies an einem einfachen Beispiel:

Bild 5: Beispielprogramm (hello)
Bild 5: Beispielprogramm (hello)
(Bild: Jan Altenberg / Continental)

Gehen wir davon aus wir möchten in der Beispielapplikation aus Abbildung 6 ein Event in Zeile 8 einfügen. Um ein entsprechendes Uprobe definieren zu können ist ein wenig Vorarbeit notwendig. Bei der Verwendung von Kprobes können wir einfach einen Symbolnamen spezifizieren. Da jede Applikation aber über einen eigenen virtuelle Adressraum und somit über eine andere Adressbasis verfügt, wird für das Erzeugen eines Uprobes das Adressoffest im Textsegment der jeweiligen Applikation benötigt. Dies kann zum Beispiel mit objdump ermittelt werden (das Executable wurde mit Debugsymbolen erzeugt):

$ objdump -F -S -D hello | less

Bild 6: Ausgabe von objdump
Bild 6: Ausgabe von objdump
(Bild: Jan Altenberg / Continental)

In Bild 6 sind die Ausgaben von objdump zu sehen. Daraus ergibt sich für die gewünschte Codezeile ein Offset von 0x378. Mit dieser Information kann nun bereits ein Uprobe generiert werden, hierfür steht die Datei uprobe_events zur Verfügung:

$ echo "p:my_uprobe /path_to_application/hello:0x378" > uprobe_events

Es ist leicht zu erkennen, dass die verwendete Syntax sehr dem Erzeugen eines Kprobes ähnelt. Anstelle des Symbolnamens wird hier nun das Executable (mit vollem Pfad) und mit einem Doppelpunkt getrennt das Offset angegeben. Das somit erzeugte Event befindet sich unter events/uprobes und lässt sich wie folgt aktivieren:

$ echo 1 > events/uprobes/my_uprobe/enable

Beim nächsten Aufruf der Applikation wird dieses Event entsprechend aufgezeichnet:

# Ausführen der Applikation$ /path_to_application/hello

Bild 7: Aufgezeichnete Uprobe Events
Bild 7: Aufgezeichnete Uprobe Events
(Bild: Jan Altenberg / Continental)

Bild 7 zeigt die aufgezeichneten Events. Im Vergleich zu den im vorangegangenen Kapitel vorgestellten Tracemarkern ist diese Methode mit wesentlich geringerem Overhead verbunden und somit auch für zeitkritische Ereignisse geeignet. Eine ausführliche Dokumentation zu den Uprobes und deren Möglichkeiten findet sich ebenfalls in der Dokumentation des Linux Kernels und zwar unter: Documentation/trace/uprobetracer.txt

Fazit

Wir haben uns in dieser 3-teiligen Artikelserie mit den in Linux verfügbaren Boardmitteln für Tracing beschäftigt. Mit der Tracing Infrastruktur bietet Linux von Hause aus alles, was zur Analyse von komplexen Fehlerbildern innerhalb des Betriebssystems oder im Zusammenhang mit dem Betriebssystem benötigt wird. Es besteht nicht nur die Möglichkeit einzelne Events aufzuzeichnen, sondern Ereignisse können auch dynamisch zur Laufzeit erzeugt und verwendet werden. Mit den Tracern bietet Linux darüber hinaus noch vorgefertigte Heuristiken, die bei der Analyse bestimmter Szenarien unterstützen. Und mit den Tracemarkern und den Uprobes können Vorgänge innerhalb des Betriebssystemkerns sehr einfach mit Abläufen in der Applikation korreliert werden.

* 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:47041908)

Über den Autor

 Jan Altenberg

Jan Altenberg

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