Gerade war er noch da – nun will er ums Verrecken nicht mehr auftauchen. Die Jagd nach einem schwer reproduzierbaren Fehler hat schon manchen Software-Entwickler und Sysop auf die Palme gebracht. Das kostenlose Jockey schont in solchen Situationen die Nerven.
Ohne Hilfswerkzeuge erfordern Regressionstests gerade bei umfangreichen Programmen einen hohen Arbeitsaufwand. Der Entwickler muss jedes Testdatum notieren und nach der Fehlerbehebung wieder eingeben und jeden Mausklick exakt wiederholen. Das Jockey-Projekt versucht diese Vorgänge zumindest teilweise zu automatisieren, indem es die gesamte Ausführungsphase einer Anwendung inklusive sämtlicher Ein- und Ausgaben minutiös protokolliert. Zu einem späteren Zeitpunkt dienen die aufgezeichneten Daten dazu, den Programmablauf exakt zu reproduzieren. Einige Hinweise zum Start gibt der Kasten “Installation”.
|
Installation |
|---|
|
Bevor Jockey an den Start geht, sind einige Voraussetzungen zu erfüllen. Neben den Werkzeugen »autoconf«, »libtool«, dem Entwicklungspaket zur »zlib« und einer Ruby-Entwicklungsumgebung (bestehend aus den Paketen »ruby«, »ruby-devel« und je nach Distribution auch »ruby-lib«) ist noch die Boost-Bibliothek erforderlich. Hierbei handelt es sich um eine C++-Bibliothek, die mittlerweile auch den meisten Distributionen beiliegt. Anwender von Suse Linux sollten darauf achten, dass der GNU-C++-Compiler installiert ist. Standardmäßig packt ihn die Distribution aus dem Hause Novell nicht auf die Platte. Anschließend übersetzt man Jockey mit »./configure; make; make install«. Die Arbeitsweise der Testbibliothek erfordert es, die so genannte Address-Space Randomization abzuschalten. Der Linux-Kernel enthält diese Funktion, um Buffer-Overflow-Angriffe zu verhindern. Der Mechanismus sorgt dafür, dass jedes Programm und jede Bibliothek immer an eine andere Speicheradresse geladen wird. Das ist für Jockey natürlich Gift, da es die Eingaben so nicht mehr nachtragen kann. Bei Kernel 2.4 deaktiviert folgende Zeile die Address-Space Randomization: sysctl -w kernel.exec-shield-randomize=0 Für Kernel 2.6 übernimmt dies der Zweizeiler: sysctl -w kernel.randomize_va_space=0 sysctl -w vm.legacy_va_layout=1 Die hiermit vorgenommenen Einstellungen gelten nur bis zum nächsten Reboot. Sie dauerhaft zu aktivieren erfordert einen Eintrag in die Startskripte (»/etc/sysctl.conf«). Steht kein separater Rechner extra für Testzwecke zur Verfügung, ist hiervon jedoch aus den genannten Sicherheitsgründen abzuraten. |
Zugrunde liegt Jockey die Bibliothek »libjockey.so«. Sie wird dem zu untersuchenden Programm vor der eigentlichen Ausführung untergeschoben. Das ganze Jockey-System läuft somit vollständig im Userspace und braucht weder einen speziellen Kerneltreiber noch einen unterstützenden Daemon.
Um Jockey einzusetzen, schreibt der Entwickler zunächst ganz normal sein Programm. Das Beispiel in Listing 1 holt die aktuelle Uhrzeit und gibt sie aus. Kompiliert man es per
gcc -o zeitgeber zeitgeber.c
und startet es anschließend, gibt es bei jedem Aufruf einen anderen Wert aus, schließlich läuft die aktuelle Zeit ja weiter. Beim Testprozess hilft aber Jockey. Dies geschieht ganz einfach über das Startskript »jockey«:
jockey ./zeitgeber
Es sorgt dafür, dass die Bibliothek »libjockey.so« dynamisch gegen das Programm gelinkt wird. Die in ihr enthaltenen Funktionen fangen sämtliche Funktionsaufrufe von »zeitgeber« auf der Systemebene ab. Im Beispiel ist dies die Abfrage der aktuellen Zeit. Den Rückgabewert protokolliert Jockey in der versteckten Datei »./,,jockeylog-zeitgeber/log« und leitet ihn an die Anwendung weiter.
Um einen mit Jockey begleiteten Programmablauf exakt zu wiederholen, verwendet der Entwickler das »jockey«-Skript:
jockey --replay ./zeitgeber
In diesem Modus fängt Jockey erneut alle Systemaufrufe ab, liefert diesmal jedoch den Wert aus dem Protokoll an die auslösende Funktion zurück. Auf diese Weise rekonstruiert Jockey den gesamten Programmablauf. Folglich ist es egal, wann das Beispielprogramm mit Hilfe des Skripts startet – es erscheint jedesmal dieselbe Uhrzeit.
|
Listing 1: |
|---|
01 #include <time.h>
02 #include <stdio.h>
03 int main()
04 {
05 time_t diezeit;
06 diezeit=time(&diezeit);
07 printf("Die Zeit ist: %ldn", diezeit);
08 }
|
Kammerjäger
Die Reproduktion eines Programmablaufs ist insbesondere dann von Interesse, wenn ein augenscheinlich nicht reproduzierbarer Fehler vorliegt. In diesem Fall greift man einfach zu der Aufzeichnung und schickt sie durch einen Debugger. Da Jockey auf einer sehr systemnahen Ebene agiert, bekommt der Programmierer auch solche Fehler zu Gesicht, die er im normalen Betrieb nicht besonders beachten oder schlichtweg übersehen würde.
Der Aufruf über das Skript ist allerdings etwas hinderlich für den Einsatz in einem Debugger. Es macht jedoch nichts anderes, als eine Umgebungsvariable zu setzen und anschließend »libjockey.so« gegen das Programm zu linken:
LD_PRELOAD=libjockey.so JOCKEY=replay=0 ./zeitgeber
Steht »replay« auf »0«, zeichnet Jockey die Programmausführung auf, bei einer »1« speist die Bibliothek die Daten aus dem Protokoll ein. Die in der Variablen »LD_PRELOAD« geführten Bibliotheken lädt der Linker noch vor der eigentlichen Ausführung von »zeitgeber«. Mit
gcc zeitgeber.c -ljockey
lässt sich die Jockey-Bibliothek selbstverständlich auch zur Compile-Zeit direkt einbinden.

Abbildung 1: Während das Beispielprogramm im Normalbetrieb jedes Mal unterschiedliche Zeiten ausgibt, reproduziert es mit Jockey immer den gleichen Wert. Die Programmoption »–force« unterdrückt bei der Aufzeichnung alle nicht fatalen Fehlermeldungen.
Von Gabeln und Fäden
Startet das zu beobachtende Programm über »fork« oder »exec« einen neuen Prozess, betrachtet Jockey im Folgenden nur den Vaterprozess weiter. Ein Kind protokolliert Jockey nur dann mit, wenn es aus einer anderen Binärdatei besteht, also beim Aufruf eines anderen Binary per »exec()«.
Der Programmierer kann dem Jockey-Skript auch explizit mit auf den Weg geben, welche Programme es von einer Protokollierung ausschließen und welche es einbeziehen soll. Im folgenden Beispiel soll »fuddel« die drei Programme »huddel«, »hanni« und »nanni« aufrufen. Diese Zeile legt dafür das Verhalten von Jockey fest:
jockey --program=huddel,fuddel --excludedprogram=hanni,nanni fuddel
Jockey protokolliert nun nur die Aktivitäten von »huddel« und »fuddel«.
Bei den Threads sieht es etwas anders aus. Um auch nach dem Posix-Standard gesponnene Programmfäden berücksichtigen zu können, liefert Jockey mit »fakethread« einen Ersatz für die korrespondierende Thread-Bibliothek »libpthread«. Alle Aufrufe dorthin fängt »fakethread« mit Hilfe der Jockey-Bibliothek ab und emuliert anschließend einfach das erwartete nebenläufige Verhalten. Hierdurch schnellt die Prozessorauslastung natürlich in die Höhe. Bei der Laufzeit können die Unterschiede bis zu 80 Prozent betragen.
Um Fakethread zu aktivieren, muss die Bibliothek zusätzlich zur Libjockey gegen das Programm gelinkt sein. Die Ersatzbibliothek mit dem Namen »libpthread.so« versteckt sich im Jockey-Unterverzeichnis »fakethread«. Um Verwirrungen vorzubeugen, verwendet man am besten das »jockey«-Skript. Es erkennt selbstständig, ob das aufgerufene Programm Posix-Threads verwendet, und zieht gegebenenfalls seine eigene Bibliothek hinzu. Alternativ bietet es sich an, mit Hilfe von
LD_LIBRARY_PATH=/usr/lib/fakethread LD_PRELOAD=libjockey.so ./meinprg --jockey=replay=0
die Standard-Pthread-Bibliothek auszutauschen.
Bitte recht freundlich
Jockey ist nicht nur in der Lage, den Programmablauf aufzuzeichnen, es fertigt auf Wunsch auch in vorgegebenen Zeitintervallen einen Schnappschuss des aktuellen Programmzustands an. Diese Funktion ist insbesondere bei sehr zeitintensiven oder lange ablaufenden Anwendungen nützlich. Man kann so später einfach zu einem Aufzeichnungspunkt springen und dann mit anderen Eingaben die Ausführung fortsetzen. Der Tester muss sich also nicht erneut bis zu dieser Stelle im Programmablauf vorkämpfen. Der Befehl
jockey --firstcheckpointtime=10 -I5 ./meinprg
startet die Aufzeichnung zehn Sekunden nach Programmstart, wobei Jockey alle weiteren fünf Sekunden (Parameter »-I«) einen Schnappschuss anfertigt. Das geschieht immer, bevor der erste Systemaufruf nach Ablauf des Intervalls zurückkehrt. Sollte die Anwendung also längere Zeit keine von Jockey beobachteten Systemaufrufe absetzen, funktioniert der Checkpoint-Mechanismus nicht wie erwartet.
Alle aufgezeichneten Daten landen in einem Verzeichnis der Form »./,,jockeylog-programmname« und heißen »c-UTC«. UTC ist jene Systemzeit, zu der der Schnappschuss entstand. Um zu einem der Checkpoints zurückzukehren, genügt zum Beispiel:
jockey --restore=./jockeylog-meinprg/c-1133245475 ./meinprg
Interna
Das Jockey-System besteht aus dem so genannten Interposer und benutzerfreundlichem Drumherum, dem Jockey-Proper-Teil. Der erste Teil ist nichts anderes als eine Bibliothek, die alle Systemaufrufe mit einigen Prozessorbefehlen nicht deterministischer Auswirkung (derzeit nur »rdtsc«) mitschneidet. Damit die Bibliothek die Kontrolle noch vor der eigentlichen Anwendung übernehmen kann, greift Jockey auf die in jeder Bibliothek vorhandenen Zusatzinformationen zurück.
Neben den übersetzten Fassungen der eigentlichen Funktionen packt der Compiler noch weitere Daten in die erzeugten Binärdateien. Sie unterstützen später den Linker. Jockey benutzt für Initialisierungsprozesse die ».init_array«-Sektion. Der Linker »ld« startet die dort platzierten Anweisungen direkt nach dem Laden der Bibliothek. Einen Überblick über den Aufbau gibt Abbildung 2.

Abbildung 2: Der Aufbau von Jockey mit den Hauptkomponenten, dem Syscall-Dispatcher und der Jockey-Bibliothek.
Als erste Aktion knöpft sich der Interposer den zu behandelnden Prozess vor und sucht ihn nach allen auftauchenden Systemaufrufen ab. Diese biegt er dann auf sein eigenes Ersatzangebot um, indem er die entsprechenden Assembler-Befehle ersetzt. Auf diese Weise kann er ganz bequem Daten mitschneiden oder die vorhandenen Werte aus dem Protokoll ins Programm drücken.
Eine solche Arbeitsweise dürfte bei einigen Entwicklern zumindest eine Augenbraue skeptisch nach oben schnellen lassen. Schließlich sind diese Manipulationen auf der Binärebene nicht gerade ohne Risiko. Sogar die Dokumentation warnt davor, dass dies auch mal schief gehen kann. Nämlich dann, wenn Jockey in der Binärdatei auf den Assembler-Befehl »int $0x80« ohne vorausgehenden »mov«-Befehl trifft. In diesem Fall löscht die Testbibliothek einfach den »int«- sowie alle nachfolgenden Befehle und hofft danach auf das Beste.
Auch folgt aus dieser Arbeitsweise, dass die Ausführung des Programms unter Jockey nicht mit einer Ausführung ohne diese Bibliothek identisch ist. Man kommt also um weitere Tests ohne die Jockey-Bibliothek nicht herum. Sind alle Ersetzungen vorgenommen, wertet Jockey die relevanten Umgebungsvariablen aus und übergibt schließlich die Kontrolle an den gestarteten Prozess.
Problemkinder
Aufgrund seiner Arbeitsweise unterliegt Jockey einigen Einschränkungen. Außerhalb seiner Zuständigkeit liegen jene Anwendungen, die dynamische Code-Erzeugung verwenden, zum Beispiel Java-Programme oder einige Skriptsprachen. Da sich bei ihnen der Programmcode erst zur Laufzeit ergibt oder während dieser sogar ändert (so genannte selbstmodifizierende Programme), kann Jockey keine genaue Reproduktion des Laufs garantieren. Gleiches gilt für Anwendungen, die auf die Ausgaben von externen Programmen angewiesen sind. Das bezieht auch die Kommunikation über gemeinsam genutzte Speicherbereiche (Shared Memory) mit ein.
Bei Anwendungen, die über ein Netzwerk agieren, ist zu beachten, dass Jockey beim Wiederherstellen der Protokolldaten diese direkt zurückliefert. Die normalerweise an der Kommunikation teilnehmenden Partner bleiben draußen und bekommen nichts von der Ausführung mit. Aus diesem Grund eignet sich Jockey nicht für das Testen von verteilten Anwendungen.
Fazit
Jockey unterstützt Programmentwickler und Tester gleich in mehreren Lebenslagen: Zum einen können sie anhand der Protokolle den genauen Ablauf des getesteten Programms nachvollziehen. Hierbei hilft das mitgelieferte Werkzeug »jockeylog«, das als Parameter das Verzeichnis mit den Protokollen benötigt (Abbildung 3).

Abbildung 3: Diese Aufrufe hat Jockey beim Beispielprogramm protokolliert, hier zum Beispiel die Aufrufdaten von vier Syscalls.
Zum anderen findet Jockey bei Abstürzen im Zusammenspiel mit einem Debugger schnell den Schuldigen. Dank der Checkpoints gelangt der Entwickler im Eiltempo immer wieder an eine kritische Stelle. Interessant sind auch die Möglichkeiten zur Weiterverwendung, die sich aus der Arbeitsweise und dem modularen Aufbau ergeben. Beispielsweise ließe sich um den Interposer ein eigener Tracer bauen oder sogar ein abgeschottetes Dateisystem implementieren.
Als komplettes Werkzeug für die Durchführung von Regressionstests taugt Jockey jedoch nicht. Schon eine kleine Änderung im Programm macht die gesamte Aufzeichnung unbrauchbar. Professionelle Werkzeuge erlauben es in diesem Fall, wenigstens die bereits eingegebenen Testdaten weiter zu verwenden. (ofr)
|
Infos |
|---|
|
[1] Jockey: [http://home.gna.org/jockey] |






