Das Tracing-Werkzeug Ktap hilft Admins und Kernelentwicklern bei Tuning und Fehlersuche. Diese Kern-Technik zeigt die ersten Schritte mit dem Interpreter im Kernel.
Linux bietet schon seit Kernel 2.6.9 vom Oktober 2004 Kernel-Tracing, also die Möglichkeit, Informationen über Kernel-interne Abläufe nachzuverfolgen. Bereits 2005 stellte die “Kern-Technik” die damals aktuellen Kernel-Interfaces Kprobe und Jprobe vor [1]. Da diese in der Handhabung jedoch kompliziert sind, haben sie nur eine sehr kleine Anwenderbasis für sich gewonnen.
Mit Systemtap und Dtrace ist in den letzten Jahren ein Werkzeugkasten entstanden, mit dem sich das Tracing-Interface programmgesteuert nutzen lässt. Damit können Admins und Entwickler komplexe Fehlersuchen realisieren oder Performance-Bottlenecks aufspüren.
Allerdings stehen diese Werkzeugkästen nicht allen offen. Das hängt zum einen mit lizenzrechtlichen Problemen, zum anderen mit den technischen Lösungen als solchen zusammen. Die Dtrace-Lizenz beispielsweise verhindert eine Aufnahme des Codes in den Standard-Linux-Kernel, auch erlauben die Komplexität und die Fixierung der Lösung auf eine einzelne Plattform keinen Einsatz in den kleineren eingebetteten Systemen. Außerdem kann Dtrace das System “einfrieren” lassen – bei produktiven Systemen lässt der Systemadministrator also lieber die Finger davon.
Mit entsprechender Euphorie haben im Mai 2013 Admins und Kernelentwickler den neuen Ansatz Ktap [2] aufgenommen. Als schlanke Lösung erlaubt Ktap das Tracing auf Embedded-Systemen genauso wie auf Servern. Eine eingebaute Skriptsprache zur Definition der zu messenden Abläufe (Tracing), die in Bytecode übersetzt auf einer virtuellen Maschine (VM) im Kernel läuft, bietet nicht nur Flexibilität, sondern auch weitgehende Unabhängigkeit von der zugrunde liegenden Hardware.
Ob auf einer x86-Plattform oder einem System mit ARM-Prozessor (Tablet, Smartphone): Der Code läuft gleichermaßen und vor allem auch in gemischten, verteilten Umgebungen.
Vorbild Minecraft
Beim Skripting hat sich der Ktap-Entwickler Jovi Zhangwei für Lua entschieden, eine Sprache, die speziell zur einfachen Funktionserweiterung von Programmen geschrieben wurde. Als eingebettete Skriptsprache findet Lua sich beispielsweise im Spiel Minecraft, im Netzwerkanalysator Wireshark, in Nmap und im Mediaplayer VLC. Der Interpreter beziehungsweise die zur Abarbeitung notwendige virtuelle Maschine umfasst nur wenige Hundert KByte, der Bytecode selbst besteht aus gerade mal 38 Befehlen. Die Sprache ist ein Mischmasch aus C und Pascal und nicht typisiert. Der Typ einer Variablen, ob Integer oder String, ergibt sich aus dem Kontext.
Ende 2013 hat Linus Torvalds auf Vorschlag von Greg Kroah-Hartman den Ktap-Kerneltreiber in sein Standard-Linux übernommen. Allerdings hagelte es stante pede kräftig Kritik, die Torvalds zwang den Commit wieder rückgängig zu machen. Die Kernelentwickler kritisierten allerdings nicht Ktap selbst, sondern die Geschwindigkeit, mit der ein so umfangreiches und auch sicherheitskritisches Subsystem ohne ausgiebige kritische Kontrolle übernommen werden sollte. Jetzt ist die Aufnahme des Subsystems für einen der nächsten Kernel, vielleicht 3.15, vorgesehen. Dennoch lässt sich Ktap bereits heute sinnvoll einsetzen.
Interpreter im Kernel
Ktap besteht aus zwei Teilen: zum einen aus der zumeist als Modul realisierten Kernelerweiterung »ktapvm.ko« und zum anderen aus der Applikation »ktap« . Bei der Kernelerweiterung handelt es sich um den Interpreter, die VM und die Anbindung an die Kernelfunktionen, die so genannte Runtime-Library. Die Ktap-Applikation lädt Lua-Programme, übersetzt sie in Bytecode und übergibt diesen dem Kernel. Sie initiiert die Abarbeitung, holt die Ergebnisse ab und stellt sie auf dem Bildschirm dar (Abbildung 1).
Die Installation von Ktap gestaltet sich unter Ubuntu 12.04 LTS erfreulich problemlos. Der mitgelieferte Betriebssystemkern ist bereits mit den benötigten Optionen »CONFIG_EVENT_TRACING« , »CONFIG_PERF_EVENTS« , »CONFIG_DEBUG_FS« und »CONFIG_FTRACE_SYSCALLS« kompiliert. Damit braucht man nur noch den Ktap-Quellcode per Git auf den Rechner zu holen und per »make« zu übersetzen. Es empfiehlt sich, zuvor noch die Pakete »elfutils« und »libelf-dev« zu installieren. Mit Hilfe dieser Werkzeuge beziehungsweise Bibliotheken verarbeitet Ktap ELF-Dateien (Executable and Linkable Format) sowie die in Programmdateien hinterlegten Meta-Informationen und löst Adressen auf. Damit ist es Ktap auch möglich, Applikationen zu überwachen. Listing 1 zeigt die Kommandos zum Installieren im Detail [3].
Listing 1
Ktap installieren
01 $ sudo apt-get install elfutils libelf-dev 02 $ git clone http://github.com/ktap/ktap.git 03 $ cd ktap 04 $ make 05 $ sudo make load
Nach dem erfolgreichen »make load« aus dem Quellcode-Verzeichnis heraus ist Ktap einsatzbereit. Optional kann der Admin noch »sudo make install« sowie danach »depmod« aufrufen. Damit installiert er Kernelmodul und Applikation für den systemweiten Zugriff. Allerdings wird das Kernelmodul beim nächsten Update des Linux-Kernels nicht automatisch neu generiert. Für alle Freunde des Editors Vim installiert »make install« gleich Syntaxdateien mit, die die Schlüsselwörter im Editor farblich hervorheben.
Einzeiler
Ein erster Test mit Ktap ist schnell durchgeführt. Dazu gibt der Anwender im Ktap-Quellcodeverzeichnis (hier hat »make« den Applikationsteil abgelegt) auf der Kommandozeile den Einzeiler
sudo ./ktap -e 'printf("Hello World\n")'
ein. Verständlicherweise benötigt Ktap bei der Ausführung Rootrechte, sodass man es mit vorangestelltem »sudo« aufruft. Auf die Eingabe des Passworts reagiert die Anwendung mit dem bekannten Erstlingswerk “Hello World”.
Die Option »-e« erlaubt es, ein Kommando direkt anzugeben, ohne sie erwartet Ktap eine Skriptdatei. Innerhalb der Hochkommata folgen die auszuführenden Befehle in der Programmiersprache Lua. Im obigen Fall ist das nur ein »printf()« . Das in der Programmiersprache C übliche Semikolon kann, muss aber nicht folgen. Eine »main()« -Funktion wie in C-Programmen gibt es nicht.
Die so genannte Runtime-Library erweitert Lua unter anderem um Funktionen zum Kernel-Tracing. Wesentlich ist »trace« , gefolgt von der Angabe eines Funktionsbereichs und – durch Doppelpunkt getrennt – eines Funktionsnamens, durchaus auch in Form eines regulären Ausdrucks. Außerdem folgt noch eine durch geschweifte Klammern eingefasste Codesequenz. Immer wenn die hinter »trace« definierte Kernelfunktion ausgeführt wird, arbeitet der Lua-Interpreter die Codesequenz ab (Abbildung 2).
Prozesse beobachten
Listing 2 zeigt einen Ktap-Befehl samt Ergebnis, der die gerade aktiven Rechenprozesse ausgibt, und zwar mit deren PID, Namen und der CPU, auf der der Job läuft. Die in der Befehlszeile verwendeten Funktionsaufrufe »cpu()« , »pid()« und »execname()« sind fest in Ktap eingebaut. Weitere Standardfunktionen listet Tabelle 1 auf.
Tabelle 1
Funktionen der Ktap-Runtime-Library [3]
|
Funktionsname |
Bedeutung |
|---|---|
|
print() |
Schnelle, unformatierte Ausgabe von Daten |
|
printf(fmt, […]) |
Formatierte Ausgabe von Daten |
|
pairs(t) |
Funktion, um über alle Schlüssel-Wert-Paare einer Tabelle zu iterieren |
|
len(s) len(t) |
Falls das Argument ein String ist, wird die Länge des Strings zurückgegeben; ist das Argument eine Tabelle, ist das Ergebnis die Anzahl der Tabellenelemente |
|
in_interrupt() |
Falls sich das System gerade im Interrupt-Kontext befindet, gibt die Funktion »true« zurück |
|
exit() |
Beendet Ktap |
|
pid() |
Gibt die PID des gerade aktiven Jobs zurück |
|
tid() |
Gibt die Thread-ID (TID) des aktuell ausgeführten Jobs zurück |
|
uid() |
Liefert die UID des aktuell ausgeführten Jobs |
|
execname() |
Gibt den Namen des aktuell ausgeführten Jobs zurück |
|
cpu() |
Gibt die ID der CPU an, die für die Abarbeitung zuständig ist |
|
arch() |
Gibt in Kurzform die Architektur der Maschine zurück (»x86« , »arm« , …) |
|
kernel_v() |
Liefert die Kernelversion |
|
user_string(addr) |
Liefert den zu einer Adresse aus dem Userspace gehörenden String |
|
histogram(t) |
Stellt die als Parameter übergebene Tabelle als Histogramm dar |
|
curr_task_info(offset, fetch_bytes) |
Liefert einzelne 4 oder 8 Byte breite Werte aus dem Task-Control-Block des gerade aktiven Jobs zurück |
|
print_backtrace() |
Gibt den Stack-Trace des aktuell ausgeführten Jobs aus |
Listing 2
Aktive Rechenprozesse ausgeben
01 # ktap -e 'trace sched:sched_process_exec
{ print("cpu pid name: ", cpu(), pid(), execname()); }'
02 Tracing... Hit Ctrl-C to end.
03 cpu pid name: 2 20990 sh
04 cpu pid name: 1 20991 ps
05 cpu pid name: 3 20992 grep
06 cpu pid name: 3 20993 sh
07 cpu pid name: 2 20994 sh
08 cpu pid name: 3 20995 w
09 cpu pid name: 1 20996 grep
10 cpu pid name: 2 20998 grep
Auf das Kommando »./ktap -le« gibt Ktap die Namen der Funktionen aus, die man mit dem Befehl »trace« verwenden und überwachen kann. Bei Kernel 3.5.0 sind das immerhin 1254. Es gibt noch viele weitere Routinen, auf die Ktap über das Ftrace-Subsystem zugreift. Der Befehl »ktap -lf Programm« listet Funktionen auf, die man zusätzlich in der Anwendung »Programm« tracen kann.
Wie sich damit ein in C geschriebenes “Hello World”-Programm überwachen lässt, zeigt Abbildung 3. Dort ist ebenfalls zu sehen, wie unter der Kontrolle von Ktap die mit »– Programm« angehängte zu tracende Applikation startet. Bereits laufende Programme lassen sich übrigens unter Angabe ihrer Prozess-ID mit »-p PID« überwachen.
Systemcalls
Ein weiteres Beispiel zeigt Listing 3. Hier listet Ktap fortlaufend jene Programme auf, die den Systemcall »open()« aufrufen. Dabei gibt es neben dem Programmnamen auch den Name der Dateien aus, auf die die Anwendungen zugreifen.
Listing 3
Ktap überwacht das Öffnen von Dateien
01 sudo ./ktap -e 'trace syscalls:sys_enter_open
{ print( execname(), user_string(arg2)) }'
02
03 Tracing... Hit Ctrl-C to end.
04 ktap /sys/kernel/debug/ktap/trace_pipe_6976
05 sh /etc/ld.so.cache
06 sh /lib/x86_64-linux-gnu/libc.so.6
07 sh /tmp/numLogin.txt
08 sh /etc/ld.so.cache
09 sh /lib/x86_64-linux-gnu/libc.so.6
10 w /etc/ld.so.cache
11 w /lib/libproc-3.2.8.so
12 w /lib/x86_64-linux-gnu/libc.so.6
13 grep /etc/ld.so.cache
14 grep /lib/x86_64-linux-gnu/libdl.so.2
15 grep /lib/x86_64-linux-gnu/libc.so.6
16 w /proc/version
17 w
18 w /proc/1/stat
19 [...]
Besonders interessant ist die Fähigkeit der Programmiersprache Lua, mit Tabellen in Kombination mit Histogrammen umzugehen. Tabellen dürfen in ihren Zeilen beliebig viele Elemente enthalten, die sogar noch unterschiedlichen Datentyps sind. Sie lassen sich in Lua wie Felder über Indizes, aber auch in Form so genannter assoziativer Felder ansprechen. Ein solches Feld speichert einen Schlüssel-Wert-Verbund (Key-Value). Der Index des Feldes ist ein Name (String). Damit lässt sich etwa zählen, wie oft einzelne Systemcalls aufgerufen werden.
Das Kommando »histogram()« stellt das Ergebnis übersichtlich dar. So zeigt Listing 4, wie Ktap die Häufigkeit von Systemcalls zählt, die an ihrem Namensvorsatz »sys_enter_« zu erkennen sind. Der Name des Systemcalls (»argname« ) dient als Index (Schlüssel) für die Tabelle »s« . Mit jedem Auftreten zählt der zugehörige Tabelleneintrag hoch. Sobald der Anwender Ktap per [Strg]+[C] abbricht, ruft es die Funktion »trace_end()« auf (siehe Abbildung 2). Diese gibt die Tabelle »s« als Histogramm aus.
Listing 4
Histogramm der am meisten verwendeten Systemcalls
01 # ktap -e 's={} trace syscalls:sys_enter_* { s[argname]+=1 } trace_end { histogram(s) }'
02 Tracing... Hit Ctrl-C to end.
03 ^C
04 value ------------- Distribution ------------- count
05 sys_enter_read |@@@@@@@@@@ 13677
06 sys_enter_close |@@@@@ 7034
07 sys_enter_open |@@@@@ 6922
08 sys_enter_recvfrom |@@@ 4160
09 sys_enter_newstat |@@ 3279
10 sys_enter_poll |@ 2642
11 sys_enter_setitimer |@ 2499
12 sys_enter_writev |@ 2484
13 sys_enter_ioctl |@ 2301
14 sys_enter_select | 1271
15 sys_enter_mmap | 1208
16 sys_enter_rt_sigaction | 874
17 sys_enter_newfstat | 666
18 sys_enter_alarm | 660
19 sys_enter_mprotect | 559
20 sys_enter_fcntl | 510
21 sys_enter_access | 372
22 sys_enter_munmap | 287
23 sys_enter_lseek | 260
24 [...]
Ausblick
Auf seiner Homepage [4] nennt der Kernelentwickler Brendan D. Gregg weitere Beispiele, die belegen, welches Potenzial der Interpreter im Kernel hat. Der Overhead hält sich dabei nach Angaben der Entwickler mit knapp 10 Prozent in Grenzen. Sie arbeiten derzeit daran, ihn weiter zu reduzieren.
Allerdings sollte niemand vergessen, dass ein Interpreter im Kernel sicherheitstechnisch betrachtet ein Desaster darstellt, oder anders formuliert: ein Freudenfest für jeden ambitionierten Hacker. Ein Systemadministrator sollte das Ktap-Modul also nur vorübergehend bei Bedarf laden. Dann allerdings kann er selbst komplexe Fragestellungen tracen, zumindest dann, wenn er ausreichend Wissen über den Kernel, Lua und die Ktap-Runtime-Library besitzt.
Weitere Informationen über die Programmiersprache Lua und die Ktap-Library vermittelt die nächste Folge der Kern-Technik. Daneben beschreibt sie das Foreign Function Interface (FFI) von Ktap. Mit ihm lassen sich direkt aus dem Lua-Skript Funktionen des Linux-Kernels aufrufen, beispielsweise »printk()« .
Als Zugabe demonstriert die nächste Folge außerdem, wie man mit Ktap ein einfaches Tetris spielen kann (siehe Abbildung 4). Alles im Betriebssystemkern, versteht sich. (mhu)
73
Infos
- Quade, Kunst, “Kern-Technik, Folge 22 – Debugging mit Kernel-Probes”: Linux-Magazin 08/05, S. 82
- Ktap: http://www.ktap.org
- Ktap-Tutorial: http://www.ktap.org/doc/tutorial.html
- Brendan D. Gregg, “Ktap Examples”: http://www.brendangregg.com/ktap.html









