Handbuch zur dynamischen Ablaufverfolgung in Solaris

Prädikate

Einer der Hauptunterschiede zwischen D und anderen Programmiersprachen wie C, C++ und Java besteht im Verzicht auf Kontrollstrukturen wie beispielsweise if-Anweisungen und Schleifen. Klauseln von D-Programmen werden als einfache, geradlinige Anweisungslisten geschrieben, die eine optionale, festgelegte Menge an Daten verfolgen. D bietet die Möglichkeit der bedingten Datenverfolgung und der Einflussnahme auf den Kontrollfluss anhand von logischen Ausdrücken namens Prädikaten, die Programmklauseln vorangesetzt werden können. Ein Prädikatausdruck wird bei der Prüfpunktauslösung ausgewertet, bevor die Anweisungen in der entsprechenden Klausel ausgeführt werden. Wenn die Prädikatauswertung „wahr“ ergibt, was durch einen beliebigen Wert ungleich Null dargestellt wird, erfolgt die Ausführung der Anweisungsliste. Ergibt die Prädikatauswertung bei einem Wert von Null „falsch“, wird keine der Anweisungen ausgeführt und die Auslösung des Prüfpunkts ignoriert.

Schreiben Sie für das nächste Beispiel den folgenden Quellcode und speichern Sie ihn in einer Datei namens countdown.d:

dtrace:::BEGIN
{
	i = 10;
}

profile:::tick-1sec
/i > 0/
{
	trace(i--);
}

profile:::tick-1sec
/i == 0/
{
	trace("blastoff!");
	exit(0);
}

Dieses D-Programm implementiert unter Verwendung von Prädikaten einen Timer für einen 10-sekündigen Countdown. Wenn countdown.d ausgeführt wird, zählt das Programm von 10 abwärts, gibt anschließend eine Meldung aus und wird beendet:

# dtrace -s countdown.d
dtrace: script 'countdown.d' matched 3 probes
CPU     ID                    FUNCTION:NAME
	0  25499                       :tick-1sec        10
	0  25499                       :tick-1sec         9
	0  25499                       :tick-1sec         8
	0  25499                       :tick-1sec         7
	0  25499                       :tick-1sec         6
	0  25499                       :tick-1sec         5
	0  25499                       :tick-1sec         4
	0  25499                       :tick-1sec         3
	0  25499                       :tick-1sec         2
	0  25499                       :tick-1sec         1
	0  25499                       :tick-1sec   blastoff!
# 

In diesem Beispiel wird mit dem Prüfpunkt BEGIN zum Starten des Countdowns ein Integer i auf 10 initialisiert. Wie im vorigen Beispiel wird anschließend mit dem Prüfpunkt tick-1sec ein einmal pro Sekunde ausgelöster Timer implementiert. Beachten Sie, dass die Beschreibung des Prüfpunkts tick-1sec in countdown.d in zwei verschiedenen Klauseln und mit zwei verschiedenen Prädikaten und Aktionslisten verwendet wird. Das Prädikat ist ein zwischen Schrägstrichen / / stehender logischer Ausdruck, der dem Prüfpunktnamen folgt und der geschweiften Klammer { } um die Klausel-Anweisungsliste vorangeht.

Das erste Prädikat testet, ob i größer als Null ist, was darauf hinweist, dass der Timer noch läuft:

profile:::tick-1sec
/i > 0/
{
	trace(i--);
}

Der relationale Operator > bedeutet größer als und gibt als „falsch“ den ganzzahligen Wert Null und als „wahr“ den Wert 1 zurück. In D werden alle aus C bekannten relationalen Operatoren unterstützt. Eine vollständige Liste finden Sie in Kapitel 2Typen, Operatoren und Ausdrücke. Wenn i noch nicht Null ist, wird i durch das Skript überwacht und dann mithilfe des Operators -- verringert.

Das zweite Prädikat verwendet den Operator ==, um „wahr“ zurückzugeben, wenn i genau gleich Null ist. Dies weist darauf hin, dass der Countdown abgeschlossen ist:

profile:::tick-1sec
/i == 0/
{
	trace("blastoff!");
	exit(0);
}

Ähnlich wie in unserem ersten Beispiel hello.d kommt in countdown.d eine Folge von Zeichen in Anführungsstrichen zum Einsatz. Diese Stringkonstante stellt die zum Abschluss des Countdowns auszugebende Meldung dar. Anschließend wird die Funktion exit() verwendet, um dtrace zu beenden und zur Shell-Eingabeaufforderung zurückzukehren.

Wenn Sie nun auf die Struktur von countdown.d zurückblicken, erkennen Sie, wie durch die Erstellung von zwei Klauseln mit derselben Prüfpunktbeschreibung, aber unterschiedlichen Prädikaten und Aktionen effektiv ein logischer Fluss erzeugt wurde:

i = 10
einmal pro Sekunde,
wenn i größer als Null,
dann trace(i--);
anderenfalls, wenn i gleich Null,
dann trace("blastoff!");
; exit(0);

Wenn Sie komplexe Programme mit Prädikaten schreiben möchten, bietet es sich an, den Algorithmus zunächst auf diese Weise darzustellen. Setzen Sie dann jeden Bestandteil des bedingten Konstrukts in eine separate Klausel mit Prädikat um.

Wir werden jetzt Prädikate mit einem neuen Provider, syscall, kombinieren und ein erstes wirkliches Ablaufverfolgungsprogramm in D schreiben. Mit dem Provider syscall lassen sich Prüfpunkte zu Beginn oder nach Abschluss eines jeden Solaris-Systemaufrufs aktivieren. Im nächsten Beispiel wird mit DTrace jeder read(2) bzw.·write(2)-Systemaufruf Ihrer Shell beobachtet. Öffnen Sie zunächst zwei Terminalfenster - eines für DTrace und das andere für den Shell-Prozess, den Sie überwachen möchten. Geben Sie in das zweite Fenster den folgenden Befehl zur Ermittlung der Prozess-ID dieser Shell ein:


# echo $$
12345

Schreiben Sie nun das nachfolgende D-Programm in das erste Terminalfenster und speichern Sie es unter dem Namen rw.d. Ersetzen Sie bei der Eingabe des Programms 12345 durch die Prozess-ID der Shell, die Sie als Antwort auf den Befehl echo erhalten haben.

syscall::read:entry,
syscall::write:entry
/pid == 12345/
{

}

Beachten Sie, dass der Rumpf der Prüfpunktklausel in rw.d leer bleibt, da das Programm nur zur Verfolgung von Benachrichtigungen über Prüfpunktauslösungen und nicht zum Aufzeichnen zusätzlicher Informationen vorgesehen ist. Wenn Sie rw.d fertig eingegeben haben, starten Sie das Experiment mit dtrace und wechseln zum zweiten Shell-Fenster. Geben Sie dort einige Befehle ein und drücken Sie nach jedem Befehl die Eingabetaste. Während Sie die Befehle eingeben, meldet dtrace im ersten Fenster die Prüfpunktauslösungen, etwa wie in diesem Beispiel:


# dtrace -s rw.d
dtrace: script 'rw.d' matched 2 probes
CPU     ID                    FUNCTION:NAME
	0     34                      write:entry
	0     32                       read:entry
	0     34                      write:entry
	0     32                       read:entry
	0     34                      write:entry
	0     32                       read:entry
	0     34                      write:entry
	0     32                       read:entry
...

Sie beobachten gerade, wie die Shell mit read(2) - und write(2) -Systemaufrufen Zeichen aus dem Terminalfenster liest und das Ergebnis zurückgibt! In diesem Beispiel sind viele der bisher betrachteten Konzepte enthalten - und auch einige neue. Zunächst wird in dem Skript eine einzige Prüfpunktklausel mit mehreren Prüfpunktbeschreibungen verwendet, um read(2) und write(2) auf gleiche Weise zu instrumentieren. Dabei werden die Beschreibungen wie folgt durch Kommas getrennt:

syscall::read:entry,
syscall::write:entry

Zur besseren Lesbarkeit erhält jede Prüfpunktbeschreibung eine eigene Zeile. Diese Anordnung ist nicht obligatorisch, sorgt aber für übersichtlichere Skripten. Als Nächstes definiert das Skript ein Prädikat, das nur die von Ihrem Shell-Prozess ausgeführten Systemaufrufe herausfiltert:

/pid == 12345/

In diesem Prädikat kommt die vordefinierte DTrace-Variable pid zum Einsatz, deren Auswertung stets die Prozess-ID des Threads ergibt, der den entsprechenden Prüfpunkt ausgelöst hat. DTrace stellt zahlreiche integrierte Variablendefinitionen für nützliche Informationen wie die Prozess-ID bereit. Sehen Sie hier eine Liste einiger DTrace-Variablen, die Sie in Ihren ersten D-Programmen verwenden können:

Variablenname 

Datentyp 

Bedeutung 

errno

int

Aktueller errno-Wert für Systemaufrufe

execname

string

Name der ausführbaren Datei des aktuellen Prozesses 

pid

pid_t

Prozess-ID des aktuellen Prozesses 

tid

id_t

Thread-ID des aktuellen Threads 

probeprov

string

Providerfeld der aktuellen Prüfpunktbeschreibung 

probemod

string

Modulfeld der aktuellen Prüfpunktbeschreibung 

probefunc

string

Funktionsfeld der aktuellen Prüfpunktbeschreibung 

probename

string

Namensfeld der aktuellen Prüfpunktbeschreibung 

Ändern Sie nun in Ihrem Instrumentationsprogramm versuchsweise die Prozess-ID und die instrumentierten Systemaufruf-Prüfpunkte ab, um das Programm auf andere auf dem System laufende Prozesse anzuwenden. Anschließend können Sie rw.d durch eine weitere kleine Änderung in eine sehr einfache Version eines Tracing-Tools für Systemaufrufe wietruss(1). umwandeln. Ein leeres Feld in einer Prüfpunktbeschreibung fungiert als Platzhalter, mit dem alle Prüfpunkte als Übereinstimmung interpretiert werden. Ändern Sie das Programm also auf den folgenden neuen Quellcode ab, sodass sämtliche von der Shell ausgeführten Systemaufrufe verfolgt werden:

syscall::: entry
/pid == 12345/
{

}

Geben Sie einige Befehle wie beispielsweise cd, ls oder date in die Shell ein und beobachten Sie die Ausgabe des DTrace-Programms.