Handbuch zur dynamischen Ablaufverfolgung in Solaris

Kapitel 10 Aktionen und Subroutinen

Mit D-Funktionsaufrufen wie trace() und printf() lassen sich zwei verschiedene Arten von DTrace-Diensten aufrufen: Aktionen, die Daten verfolgen oder den DTrace-externen Status ändern, und Subroutinen, die nur den internen DTrace-Status beeinflussen. In diesem Kapitel werden die Aktionen und Subroutinen definiert und ihre Syntax und Semantik beschrieben.

Aktionen

Aktionen machen die Interaktion zwischen Ihren DTrace-Programmen und dem System außerhalb von DTrace möglich. Die üblichsten Aktionen bestehen in der Aufzeichnung von Daten in einem DTrace-Puffer. Darüber hinaus stehen Aktionen wie das Anhalten des aktuellen Prozesses, das Setzen eines bestimmten Signals auf den laufenden Prozess oder das Beenden der gesamten Ablaufverfolgung zur Verfügung. Einige dieser Aktionen sind insofern als destruktiv zu bezeichnen, als sie das System ändern, wenn auch auf genau definierte Weise. Diese Aktionen können nur eingesetzt werden, wenn destruktive Aktionen ausdrücklich zugelassen wurden. Die Aufzeichnungsaktionen zeichnen die Daten standardmäßig im Hauptpuffer auf. Ausführliche Informationen zu Richtlinien für Hauptpuffer und Puffer finden Sie in Kapitel 11Puffer und Pufferung.

Standardaktion

Eine Klausel kann eine beliebige Anzahl von Aktionen und Variablenmanipulationen enthalten. Wird eine Klausel leer gelassen, so erfolgt die Standardaktion. Die Standardaktion besteht in der Aufzeichnung der ID des aktivierten Prüfpunkts (EPID) im Hauptpuffer. Die EPID bezeichnet eine bestimmte Aktivierung eines bestimmten Prüfpunkts mit einem bestimmten Prädikat und den dazugehörigen Aktionen. Aus der EPID können DTrace-Verbraucher den Prüfpunkt ableiten, der eine Aktion eingeleitet hat. Immer wenn Daten verfolgt werden, müssen diese durch die EPID ergänzt werden, damit sie überhaupt einen Sinn für den Verbraucher ergeben. Deshalb besteht die Standardaktion darin, ausschließlich die EPID zu verfolgen.

Die Nutzung der Standardaktion ermöglicht·eine einfache Verwendung von dtrace(1M). Beispielsweise aktiviert der folgende Befehl alle Prüfpunkte im Timeshare-Scheduling-Modul TS mit der Standardaktion:


# dtrace -m TS

Dieser Befehl kann eine Ausgabe wie in folgendem Beispiel erzeugen:


# dtrace -m TS
dtrace: description 'TS' matched 80 probes
CPU     ID                    FUNCTION:NAME
  0  12077                 ts_trapret:entry
  0  12078                ts_trapret:return
  0  12069                   ts_sleep:entry
  0  12070                  ts_sleep:return
  0  12033                  ts_setrun:entry
  0  12034                 ts_setrun:return
  0  12081                  ts_wakeup:entry
  0  12082                 ts_wakeup:return
  0  12069                   ts_sleep:entry
  0  12070                  ts_sleep:return
  0  12033                  ts_setrun:entry
  0  12034                 ts_setrun:return
  0  12069                   ts_sleep:entry
  0  12070                  ts_sleep:return
  0  12033                  ts_setrun:entry
  0  12034                 ts_setrun:return
  0  12069                   ts_sleep:entry
  0  12070                  ts_sleep:return
  0  12023                  ts_update:entry
  0  12079             ts_update_list:entry
  0  12080            ts_update_list:return
  0  12079             ts_update_list:entry
...

Daten aufzeichnende Aktionen

Die Daten aufzeichnenden Aktionen stellen die zentralen Aktionen von DTrace dar. Sie alle zeichnen standardmäßig Daten im Hauptpuffer auf, ermöglichen aber auch das Aufzeichnen von Daten in spekulativen Puffern. Ausführliche Informationen zum Hauptpuffer finden Sie in Kapitel 11Puffer und Pufferung. Ausführliche Informationen zu spekulativen Puffern finden Sie in Kapitel 13Spekulative Ablaufverfolgung. Die Beschreibungen in diesem Abschnitt beziehen sich lediglich auf den Zielpuffer, wobei angegeben wird, ob die Daten im Hauptpuffer oder, wenn auf die Aktion die Funktion speculate() folgt, in einem spekulativen Puffer aufgezeichnet wird.

trace()

void trace(Ausdruck)

Die grundlegende Aktion ist trace(), die einen D-Ausdruck als Argument übernimmt und das Ergebnis im Zielpuffer aufzeichnet. Die folgenden Anweisungen sind Beispiele für trace()-Aktionen:

trace(execname);
trace(curlwpsinfo->pr_pri);
trace(timestamp / 1000);
trace(`lbolt);
trace("somehow managed to get here");

tracemem()

void tracemem(Adresse, size_t Anzahl-Byte)

Die Aktion tracemem() übernimmt als erstes Argument einen D-Ausdruck, Adresse, und als zweites Argument eine Konstante, Anzahl-Byte. tracemem() kopiert den Speicherinhalt aus der mit Adresse angegebenen Adresse über die mit Anzahl_Byte angegebene Länge in den Zielpuffer.

printf()

void printf(string Format, ...) 

Wie trace() verfolgt auch die Aktion printf() D-Ausdrücke. printf() ermöglicht jedoch eine gezielte Formatierung im printf(3C)-Stil. Wie bei printf(3C) bestehen die Parameter aus einer Format-Zeichenkette gefolgt von einer variablen Anzahl von Argumenten. Die Argumente werden standardmäßig im Zielpuffer aufgezeichnet. Anschließend werden die Argumente für die Ausgabe durch dtrace(1M) gemäß der angegebenen Format-Zeichenkette formatiert. So ließen sich etwa die ersten zwei Beispiele für trace() unter trace() in einer einzigen printf()-Aktion kombinieren:

printf("execname is %s; priority is %d", execname, curlwpsinfo->pr_pri);

Weitere Informationen zu printf() finden Sie in Kapitel 12Formatierung der Ausgabe.

printa()

void printa(Aggregation)
void printa(string Format, Aggregation)

Die Aktion printa() dient zum Anzeigen und Formatieren von Aggregaten. Weitere Informationen zu Aggregationen finden Sie in Kapitel 9Aggregate. Wenn kein Format angegeben wird, verfolgt printa() lediglich eine Direktive, eine Anweisung für den DTrace-Verbraucher, die besagt, dass das angegebene Aggregat verarbeitet und im Standardformat angezeigt werden soll. Wenn ein Format angegeben wurde, wird das Aggregat gemäß der Angabe formatiert. Kapitel 12Formatierung der Ausgabe enthält eine ausführlichere Beschreibung der printa()-Formatzeichenkette.

printa() zeichnet nur eine Direktive auf, die besagt, dass das Aggregat vom DTrace-Verbraucher verarbeitet werden soll. Das Aggregat im Kernel wird von der Aktion nicht verarbeitet. Deshalb hängt die Dauer zwischen der Ablaufverfolgung der printa()-Direktive und der tatsächlichen Verarbeitung der Direktive von den die Pufferverarbeitung beeinflussenden Faktoren ab. Bei diesen Faktoren handelt es sich um die Aggregationsfrequenz, die Pufferungsregel und, wenn letztere auf switching gesetzt ist, die Frequenz der Pufferumschaltung. Ausführliche Beschreibungen dieser Faktoren finden Sie in Kapitel 9Aggregate und·Kapitel 11Puffer und Pufferung.

stack()

void stack(int Anzahl_Frames)
void stack(void)

Die Aktion stack() zeichnet ein Kernel-Stackprotokoll im Zielpuffer auf. Dabei ist die Tiefe des Kernel-Stacks durch Anzahl_Frames vorgegeben. Wenn Anzahl_Frames nicht angegeben ist, werden so viele Stack-Frames aufgezeichnet, wie mit der Option stackframes angegeben wurden. Beispiel:


# dtrace -n uiomove:entry'{stack()}'
  CPU     ID                    FUNCTION:NAME
    0   9153                    uiomove:entry
                genunix`fop_write+0x1b
                namefs`nm_write+0x1d
                genunix`fop_write+0x1b
                genunix`write+0x1f7

    0   9153                    uiomove:entry
                genunix`fop_read+0x1b
                genunix`read+0x1d4

    0   9153                    uiomove:entry
                genunix`strread+0x394
                specfs`spec_read+0x65
                genunix`fop_read+0x1b
                genunix`read+0x1d4
   ...

Die Aktion stack() unterscheidet sich insofern leicht von anderen Aktionen, als sie auch als Schlüssel für Aggregate eingesetzt werden kann:


# dtrace -n kmem_alloc:entry'{@[stack()] = count()}'
dtrace: description 'kmem_alloc:entry' matched 1 probe
^C

                rpcmod`endpnt_get+0x47c
                rpcmod`clnt_clts_kcallit_addr+0x26f
                rpcmod`clnt_clts_kcallit+0x22
                nfs`rfscall+0x350
                nfs`rfs2call+0x60
                nfs`nfs_getattr_otw+0x9e
                nfs`nfsgetattr+0x26
                nfs`nfs_getattr+0xb8
                genunix`fop_getattr+0x18
                genunix`cstat64+0x30
                genunix`cstatat64+0x4a
                genunix`lstat64+0x1c
                  1

                genunix`vfs_rlock_wait+0xc
                genunix`lookuppnvp+0x19d
                genunix`lookuppnat+0xe7
                genunix`lookupnameat+0x87
                genunix`lookupname+0x19
                genunix`chdir+0x18
                  1

                rpcmod`endpnt_get+0x6b1
                rpcmod`clnt_clts_kcallit_addr+0x26f
                rpcmod`clnt_clts_kcallit+0x22
                nfs`rfscall+0x350
                nfs`rfs2call+0x60
                nfs`nfs_getattr_otw+0x9e
                nfs`nfsgetattr+0x26
                nfs`nfs_getattr+0xb8
                genunix`fop_getattr+0x18
                genunix`cstat64+0x30
                genunix`cstatat64+0x4a
                genunix`lstat64+0x1c
                  1
    ...

ustack()

void ustack(int Anzahl_Frames, int strsize)
void ustack(int Anzahl_Frames)
void ustack(void)

Die Aktion ustack() zeichnet ein Benutzer-Stackprotokoll im Zielpuffer auf. Dabei ist die Tiefe des Benutzer-Stacks durch Anzahl_Frames vorgegeben. Wenn Anzahl_Frames nicht angegeben ist, werden so viele Stack-Frames aufgezeichnet, wie mit der Option ustackframes angegeben wurden. Während ustack() in der Lage ist, die Adresse der aufrufenden Frames zum Zeitpunkt der Prüfpunktauslösung zu ermitteln, werden die Stack-Frames erst in Symbole übersetzt, wenn die Aktion ustack() auf Benutzerebene vom DTrace-Verbraucher verarbeitet wurde. Wenn Größe_Zeichenkette angegeben und nicht Null ist, reserviert ustack() den angegebenen Speicherplatz für die Zeichenkette und nutzt ihn dazu, die Adresse direkt aus dem Kernel in ein Symbol zu übersetzen. Diese direkte Benutzersymbol-Übersetzung ist derzeit nur für Java Virtual Machines der Version 1.5 und höher verfügbar. Die Adress/Symbol-Übersetzung in Java kennzeichnet Benutzer-Stacks, die Java-Frames enthalten, mit der Java-Klasse und dem Methodennamen. Wenn diese Frames nicht übersetzt werden können, erscheinen diese nur als hexadezimale Adressen.

Im nächsten Beispiel wird ein Stack ohne Speicherplatz für die Zeichenkette und folglich ohne Java-Adress/Symbol-Übersetzung verfolgt:


# dtrace -n syscall::write:entry'/pid == $target/{ustack(50, 0);
    exit(0)}' -c "java -version"
dtrace: description 'syscall::write:entry' matched 1 probe
java version "1.5.0-beta3"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta3-b58)
Java HotSpot(TM) Client VM (build 1.5.0-beta3-b58, mixed mode)
dtrace: pid 5312 has exited
CPU     ID                    FUNCTION:NAME
  0     35                      write:entry
              libc.so.1`_write+0x15
              libjvm.so`__1cDhpiFwrite6FipkvI_I_+0xa8
              libjvm.so`JVM_Write+0x2f
              d0c5c946
              libjava.so`Java_java_io_FileOutputStream_writeBytes+0x2c
              cb007fcd
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb000152
              libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_
                          pnMmethodHandle_pnRJavaCallArguments_
                          pnGThread__v_+0x187
              libjvm.so`__1cCosUos_exception_wrapper6FpFpnJJavaValue_
                          pnMmethodHandle_pnRJavaCallArguments_
                          pnGThread__v2468_v_+0x14
              libjvm.so`__1cJJavaCallsEcall6FpnJJavaValue_nMmethodHandle_
                          pnRJavaCallArguments_pnGThread __v_+0x28
              libjvm.so`__1cRjni_invoke_static6FpnHJNIEnv__pnJJavaValue_
                          pnI_jobject_nLJNICallType_pnK_jmethodID_pnSJNI_
                          ArgumentPusher_pnGThread__v_+0x180
              libjvm.so`jni_CallStaticVoidMethod+0x10f
              java`main+0x53d

Beachten Sie, dass die C- und C++-Stack-Frames aus der Java Virtual Machine symbolisch anhand von „verstümmelten“ C++-Symbolnamen und die Java-Stack-Frames nur als hexadezimale Adressen dargestellt werden. Das nächste Beispiel zeigt einen ustack()-Aufruf mit einer Zeichenkettengröße ungleich Null:


# dtrace -n syscall::write:entry'/pid == $target/{ustack(50, 500); exit(0)}'
      -c "java -version"
dtrace: description 'syscall::write:entry' matched 1 probe
java version "1.5.0-beta3"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta3-b58)
Java HotSpot(TM) Client VM (build 1.5.0-beta3-b58, mixed mode)
dtrace: pid 5308 has exited
CPU     ID                    FUNCTION:NAME
  0     35                      write:entry
              libc.so.1`_write+0x15
              libjvm.so`__1cDhpiFwrite6FipkvI_I_+0xa8
              libjvm.so`JVM_Write+0x2f
              d0c5c946
              libjava.so`Java_java_io_FileOutputStream_writeBytes+0x2c
              java/io/FileOutputStream.writeBytes
              java/io/FileOutputStream.write
              java/io/BufferedOutputStream.flushBuffer
              java/io/BufferedOutputStream.flush
              java/io/PrintStream.write
              sun/nio/cs/StreamEncoder$CharsetSE.writeBytes
              sun/nio/cs/StreamEncoder$CharsetSE.implFlushBuffer
              sun/nio/cs/StreamEncoder.flushBuffer
              java/io/OutputStreamWriter.flushBuffer
              java/io/PrintStream.write
              java/io/PrintStream.print
              java/io/PrintStream.println
              sun/misc/Version.print
              sun/misc/Version.print
              StubRoutines (1)
              libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_
                          pnMmethodHandle_pnRJavaCallArguments_pnGThread
                          __v_+0x187
              libjvm.so`__1cCosUos_exception_wrapper6FpFpnJJavaValue_
                          pnMmethodHandle_pnRJavaCallArguments_pnGThread
                          __v2468_v_+0x14
              libjvm.so`__1cJJavaCallsEcall6FpnJJavaValue_nMmethodHandle
                          _pnRJavaCallArguments_pnGThread__v_+0x28
              libjvm.so`__1cRjni_invoke_static6FpnHJNIEnv__pnJJavaValue_pnI
                          _jobject_nLJNICallType_pnK_jmethodID_pnSJNI
                          _ArgumentPusher_pnGThread__v_+0x180
              libjvm.so`jni_CallStaticVoidMethod+0x10f
              java`main+0x53d
              8051b9a

Die obige Beispielausgabe veranschaulicht die symbolischen Stack-Frame-Informationen zu Java-Stack-Frames. Dass weiterhin hexadezimale Frames in der Ausgabe enthalten sind, ist darauf zurückzuführen, dass einige Funktionen statisch sind und keine Einträge in der Anwendungssymboltabelle für sie vorliegen. Diese Frames können nicht übersetzt werden.

Die ustack()-Symbolübersetzung für Java-fremde Frames erfolgt nach der Aufzeichnung der Stackdaten. Folglich wird der entsprechende Benutzerprozess möglicherweise beendet, noch bevor die Symbolübersetzung durchgeführt werden kann. Dadurch wird die Stack-Frame-Übersetzung unmöglich. Wenn der Benutzerprozess vor der Symbolübersetzung beendet wird, gibt dtrace eine Warnmeldung wie im nächsten Beispiel, gefolgt von den hexadezimalen Stack-Frames aus:


  dtrace: failed to grab process 100941: no such process
                c7b834d4
                c7bca85d
                c7bca1a4
                c7bd4374
                c7bc2628
                8047efc

Verfahren zur Linderung dieses Problems werden in Kapitel 33Ablaufverfolgung von Benutzerprozessen beschrieben.

Da schließlich die DTrace-Befehle zur nachträglichen Fehleranalyse (Post-Mortem-Debugging) die Frame-Übersetzung nicht durchführen können, ergibt die Verwendung von ustack() mit der ring-Pufferregel stets unverarbeitete ustack()-Daten.

Das folgende D-Programm zeigt ein Beispiel für eine ustack()-Aktion ohne Angabe von Größe_Zeichenkette:

syscall::brk:entry
/execname == $$1/
{
	@[ustack(40)] = count();
}

Um dieses Beispiel auf den Webbrowser Netscape, .netscape.bin, in Solaris-Standardinstallationen anzuwenden, geben Sie folgenden Befehl ein:


# dtrace -s brk.d .netscape.bin
dtrace: description 'syscall::brk:entry' matched 1 probe
^C
                libc.so.1`_brk_unlocked+0xc
                88143f6
                88146cd
                .netscape.bin`unlocked_malloc+0x3e
                .netscape.bin`unlocked_calloc+0x22
                .netscape.bin`calloc+0x26
                .netscape.bin`_IMGCB_NewPixmap+0x149
                .netscape.bin`il_size+0x2f7
                .netscape.bin`il_jpeg_write+0xde
                8440c19
                .netscape.bin`il_first_write+0x16b
                8394670
                83928e5
                .netscape.bin`NET_ProcessHTTP+0xa6
                .netscape.bin`NET_ProcessNet+0x49a
                827b323
                libXt.so.4`XtAppProcessEvent+0x38f
                .netscape.bin`fe_EventLoop+0x190
                .netscape.bin`main+0x1875
                   1

                libc.so.1`_brk_unlocked+0xc
                libc.so.1`sbrk+0x29
                88143df
                88146cd
                .netscape.bin`unlocked_malloc+0x3e
                .netscape.bin`unlocked_calloc+0x22
                .netscape.bin`calloc+0x26
                .netscape.bin`_IMGCB_NewPixmap+0x149
                .netscape.bin`il_size+0x2f7
                .netscape.bin`il_jpeg_write+0xde
                8440c19
                .netscape.bin`il_first_write+0x16b
                8394670
                83928e5
                .netscape.bin`NET_ProcessHTTP+0xa6
                .netscape.bin`NET_ProcessNet+0x49a
                827b323
                libXt.so.4`XtAppProcessEvent+0x38f
                .netscape.bin`fe_EventLoop+0x190
                .netscape.bin`main+0x1875
                  1
    ...

jstack()

void jstack(int Anzahl_Frames, int Größe)
void jstack(int Anzahl_Frames)
void jstack(void)

jstack() ist ein Aliasname für ustack(). Dabei gilt als Anzahl der Stack-Frames der mit der Option jstackframes angegebene Wert und als Speicherplatz für die Zeichenkette der mit der Option jstackstrsize angegebene Wert. jstacksize nimmt standardmäßig einen Wert ungleich Null an. Das bedeutet, dass die Verwendung von jstack() einen Stack mit erfolgter Java-Frame-Übersetzung ergibt.

Destruktive Aktionen

Einige DTrace-Aktionen können als destruktiv bezeichnet werden, da sie den Zustand des Systems auf irgendeine, allerdings genau definierte Weise ändern. Destruktive Aktionen können erst nach expliziter Zulassung verwendet werden. Sie können destruktive Aktionen mit dtrace(1M) und der Option -w zulassen. Bei dem Versuch, destruktive Aktionen in dtrace(1M) zu aktivieren, ohne sie ausdrücklich zuzulassen, schlägt dtrace mit einer Meldung wie in folgendem Beispiel fehl:


dtrace: failed to enable 'syscall': destructive actions not allowed

Prozessdestruktive Aktionen

Einige destruktive Aktionen sind nur in Bezug auf einen bestimmten Prozess destruktiv. Diese Aktionen stehen Benutzern mit den Zugriffsrechten dtrace_proc oder dtrace_user zur Verfügung. Ausführliche Informationen zu DTrace-Sicherheitszugriffsrechten finden Sie in Kapitel 35Sicherheit.

stop()

void stop(void)

Die Aktion stop() erzwingt das Anhalten des Prozesses, der den aktivierten Prüfpunkt auslöst, sobald er den Kernel das nächste Mal verlässt. Der Prozess wird dabei wie durch eine proc(4)-Aktion angehalten. Mit dem Dienstprogramm prun(1) können Prozesse, die durch die Aktion stop() angehalten wurden, wieder fortgesetzt werden. Die Aktion stop() ermöglicht das Anhalten eines Prozesses an einem beliebigen DTrace-Prüfpunkt. Sie dient dazu, bestimmte Programmzustände zu erfassen, die mithilfe eines einfachen Haltepunkts nur schwer zu erreichen wären, und dann einen herkömmlichen Debugger wie beispielsweise mdb(1) an den Prozess anzuhängen. Außerdem können Sie mit dem Dienstprogramm gcore(1) den Zustand eines angehaltenen Prozesses für die nachträgliche Analyse in einer Speicherabbilddatei speichern.

raise()

void raise(int Signal)

Die Aktion raise() sendet dem aktuell laufenden Prozess das angegebene Signal. Diese Aktion ist mit dem Befehl kill(1) zum Senden eines Signals an einen Prozess vergleichbar. Mit der Aktion raise() können Sie gezielt an einem genauen Punkt in der Ausführung eines Prozesses ein Signal senden.

copyout()

void copyout(void *Puffer, uintptr_t addr, size_t Anzahl_Byte)

Die Aktion copyout() kopiert Anzahl_Byte aus dem mit Puffer angegebenen Puffer an die mit Adresse angegebene Adresse im Adressraum des Prozesses, zu dem der aktuelle Thread gehört. Sollte die Benutzerraumadresse nicht auf eine gültige, durch Seitenfehler eingelagerte Seite im aktuellen Adressraum zutreffen, wird ein Fehler generiert.

copyoutstr()

void copyoutstr(string Zeichenkette, uintptr_t addr, size_t maxlen)

Die Aktion copyoutstr() kopiert die mit Zeichenkette angegebene Zeichenkette an die mit Adresse angegebene Adresse im Adressraum des Prozesses, zu dem der aktuelle Thread gehört. Sollte die Benutzerraumadresse nicht auf eine gültige, durch Seitenfehler eingelagerte Seite im aktuellen Adressraum zutreffen, wird ein Fehler generiert. Die Länge der Zeichenkette ist auf den mit der Option strsize festgelegten Wert beschränkt. Ausführliche Informationen finden Sie in Kapitel 16Optionen und Tunables .

system()

void system(string Programm, ...) 

Die Aktion system() bewirkt die Ausführung des mit Programm angegebenen Programms, als wäre es der Shell als Eingabe übergeben worden. Die Zeichenkette Programm kann beliebige printf()/printa()-Formatumwandlungen enthalten. Es müssen Argumente angegeben werden, die mit den Formatumwandlungen übereinstimmen. Ausführliche Informationen zu zulässigen Formatumwandlungen finden Sie in Kapitel 12Formatierung der Ausgabe.

Im folgenden Beispiel wird der Befehl date(1) einmal pro Sekunde ausgeführt:


# dtrace -wqn tick-1sec'{system("date")}'
 Tue Jul 20 11:56:26 CDT 2004
 Tue Jul 20 11:56:27 CDT 2004
 Tue Jul 20 11:56:28 CDT 2004
 Tue Jul 20 11:56:29 CDT 2004
 Tue Jul 20 11:56:30 CDT 2004

Das nächste Beispiel zeigt eine etwas komplexere Verwendung der Aktion. Hier werden neben printf()-Konvertierungen in der Programm-Zeichenkette herkömmliche Filtertools wie Pipes eingesetzt:

#pragma D option destructive
#pragma D option quiet

proc:::signal-send
/args[2] == SIGINT/
{
	printf("SIGINT sent to %s by ", args[1]->pr_fname);
	system("getent passwd %d | cut -d: -f5", uid);
}

Die Ausführung des obigen Skripts erzeugt eine Ausgabe wie in folgendem Beispiel:


# ./whosend.d
SIGINT sent to MozillaFirebird- by Bryan Cantrill
SIGINT sent to run-mozilla.sh by Bryan Cantrill
^C
SIGINT sent to dtrace by Bryan Cantrill

Die Ausführung des angegebenen Befehls erfolgt nicht im Kontext des ausgelösten Prüfpunkts, sondern findet statt, wenn der Puffer, der die Angaben zur Aktion system() enthält, auf Benutzerebene verarbeitet wird. Wie und wann diese Verarbeitung erfolgt, hängt von der in Kapitel 11Puffer und Pufferung beschriebenen Pufferungsregel ab. Wenn die Standard-Pufferungsregel gilt, ist die Puffer-Verarbeitungsfrequenz durch die Option switchrate vorgegeben. Wenn Sie wie im nächsten Beispiel die switchrate ausdrücklich auf einen höheren als den Standardwert von 1 Sekunde einstellen, können Sie die der Aktion system() anhaftende Verzögerung beobachten:

#pragma D option quiet
#pragma D option destructive
#pragma D option switchrate=5sec

tick-1sec
/n++ < 5/
{
	printf("walltime  : %Y\n", walltimestamp);
	printf("date      : ");
	system("date");
	printf("\n");
}

tick-1sec
/n == 5/
{
	exit(0);
}

Die Ausführung des obigen Skripts erzeugt eine Ausgabe wie in folgendem Beispiel:


# dtrace -s ./time.d
 walltime  : 2004 Jul 20 13:26:30
date      : Tue Jul 20 13:26:35 CDT 2004

walltime  : 2004 Jul 20 13:26:31
date      : Tue Jul 20 13:26:35 CDT 2004

walltime  : 2004 Jul 20 13:26:32
date      : Tue Jul 20 13:26:35 CDT 2004

walltime  : 2004 Jul 20 13:26:33
date      : Tue Jul 20 13:26:35 CDT 2004

walltime  : 2004 Jul 20 13:26:34
date      : Tue Jul 20 13:26:35 CDT 2004

Beachten Sie, dass die walltime-Werte voneinander abweichen, die date-Werte jedoch identisch sind. Dieses Ergebnis spiegelt die Tatsache wider, dass der Befehl date(1) nicht bei der Aufzeichnung der Aktion system(), sondern erst zur Verarbeitung des Puffers ausgeführt wurde.

Kerneldestruktive Aktionen

Einige destruktive Aktionen wirken sich auf das gesamte System aus. Diese Aktionen müssen natürlich mit äußerster Vorsicht eingesetzt werden, da sie jeden Prozess auf dem System und jedes andere implizit oder explizit von den Netzwerkdiensten des betreffenden Systems abhängende System beeinflussen.

breakpoint()

void breakpoint(void)

Die Aktion breakpoint() setzt einen Kernel-Haltepunkt, der bewirkt, dass das System anhält und dem Kernel-Debugger die Steuerung übergibt. Der Kernel-Debugger gibt eine Zeichenkette aus, die den DTrace-Prüfpunkt bezeichnet, der die Aktion ausgelöst hat. Nehmen wir beispielsweise Folgendes vor:


# dtrace -w -n clock:entry'{breakpoint()}'
dtrace: allowing destructive actions
dtrace: description 'clock:entry' matched 1 probe

Das kann auf einem SPARC-System unter Solaris zur Ausgabe folgender Meldung auf der Konsole führen:


dtrace: breakpoint action at probe fbt:genunix:clock:entry (ecb 30002765700)
Type  'go' to resume
ok

Unter Solaris auf einem x86-System wird dies möglicherweise mit folgender Meldung auf der Konsole quittiert:


dtrace: breakpoint action at probe fbt:genunix:clock:entry (ecb d2b97060)
stopped at      int20+0xb:      ret
kmdb[0]:

Bei der Adresse im Anschluss an die Prüfpunktbeschreibung handelt es sich um die Adresse des aktivierenden Steuerblocks (ECB, Enabling Control Block) innerhalb von DTrace. Anhand dieser Adresse lassen sich weitere Informationen über die Prüfpunktaktivierung ermitteln, die die Haltepunktaktion eingeleitet hat.

Ein Fehler im Umgang mit der Aktion breakpoint() kann dazu führen, dass diese wesentlich häufiger als beabsichtigt aufgerufen wird. Dieses Verhalten kann es unter Umständen sogar unmöglich machen, den DTrace-Verbraucher zu beenden, der die Haltepunktaktionen auslöst. In diesem Fall sollten Sie die Kernel-Ganzzahlvariable dtrace_destructive_disallow auf 1 setzen. Diese Einstellung lässt keinerlei destruktive Aktionen auf dem Rechner zu. Greifen Sie auf diese Einstellung jedoch ausschließlich in der beschriebenen Situation zurück.

Das genaue Verfahren zum Festlegen von dtrace_destructive_disallow hängt dabei von dem jeweiligen Kernel-Debugger ab. Wenn Sie mit dem OpenBoot PROM auf einem SPARC-System arbeiten, verwenden Sie w!:


ok 1 dtrace_destructive_disallow w!
ok

Überprüfen Sie mit w?, ob die Variable gesetzt wurde:


ok dtrace_destructive_disallow w?
1
ok

Geben Sie dann go ein:


ok go

Für kmdb(1) auf einem x86- oder SPARC-System verwenden Sie den 4-Byte-write-Modifizierer (W) mit der /-Formatierung: x1 dcmd[


kmdb[0]: dtrace_destructive_disallow/W 1
dtrace_destructive_disallow:    0x0             =       0x1
kmdb[0]:

Fahren Sie fort mit :c:


kadb[0]: :c

Um anschließend wieder destruktive Aktionen zuzulassen, muss dtrace_destructive_disallow mithilfe von mdb(1) wieder auf 0 zurückgesetzt werden:


# echo "dtrace_destructive_disallow/W 0" | mdb -kw
dtrace_destructive_disallow:    0x1             =       0x0
#

panic()

void panic(void)

Wenn die Aktion panic() ausgelöst wird, verursacht sie einen Kernel-Absturz. Sie dient zum Erzwingen eines Systemspeicherabzugs zu einem gezielten Zeitpunkt. Diese Aktion eignet sich zur Untersuchung von Problemen in Kombination mit ring-Pufferung und Post-Mortem-Analyse. Weitere Informationen finden Sie in Kapitel 11Puffer und Pufferung und Kapitel 37Nachträgliche Ablaufverfolgung. Beim Einsatz dieser Aktion wird eine Absturzmeldung mit der Angabe des Prüfpunkts angezeigt, der den Absturz verursacht hat. Beispiel:


  panic[cpu0]/thread=30001830b80: dtrace: panic action at probe
  syscall::mmap:entry (ecb 300000acfc8)

  000002a10050b840 dtrace:dtrace_probe+518 (fffe, 0, 1830f88, 1830f88,
    30002fb8040, 300000acfc8)
    %l0-3: 0000000000000000 00000300030e4d80 0000030003418000 00000300018c0800
    %l4-7: 000002a10050b980 0000000000000500 0000000000000000 0000000000000502
  000002a10050ba30 genunix:dtrace_systrace_syscall32+44 (0, 2000, 5,
    80000002, 3, 1898400)
    %l0-3: 00000300030de730 0000000002200008 00000000000000e0 000000000184d928
    %l4-7: 00000300030de000 0000000000000730 0000000000000073 0000000000000010

  syncing file systems... 2 done
  dumping to /dev/dsk/c0t0d0s1, offset 214827008, content: kernel
  100% done: 11837 pages dumped, compression ratio 4.66, dump
  succeeded
  rebooting...

syslogd(1M) gibt auch beim Neustart eine Meldung aus.


  Jun 10 16:56:31 machine1 savecore: [ID 570001 auth.error] reboot after panic:
  dtrace: panic action at probe syscall::mmap:entry (ecb 300000acfc8)

Im Meldungspuffer des Speicherabzugs sind auch der Prüfpunkt und der ECB enthalten, die für die Aktion panic() verantwortlich sind.

chill()

void chill(int Nanosekunden)

Die Aktion chill() bewirkt, dass DTrace für die angegebene Dauer in Nanosekunden in den Wartezustand versetzt wird. chill() ist hauptsächlich zur Untersuchung von Problemen geeignet, die mit zeitlichen Abläufen in Zusammenhang stehen könnten. Beispielsweise lassen sich mit dieser Aktion Fenster für Gleichzeitigkeitsbedingungen öffnen oder regelmäßig stattfindende Ereignisse in einen gemeinsamen oder in unterschiedliche Rhythmen schalten. Da Interrupts innerhalb des DTrace-Prüfpunktkontexts deaktiviert sind, führt jede Verwendung von chill() zu Interrupt-Latenz, Scheduling-Latenz und Dispatch-Latenz. chill() verursacht deshalb unter Umständen systemische Effekte und sollte keinesfalls willkürlich eingesetzt werden. Da die Systemtätigkeit von der regelmäßigen Interrupt-Behandlung abhängt, führt DTrace die Aktion chill() keinesfalls länger als 500 Millisekunden pro einsekündigem Intervall auf derselben CPU aus. Wenn das maximale chill()-Intervall überschritten wird, meldet DTrace wie im nächsten Beispiel einen unzulässigen Vorgang:


# dtrace -w -n syscall::open:entry'{chill(500000001)}'
dtrace: allowing destructive actions
dtrace: description 'syscall::open:entry' matched 1 probe
dtrace: 57 errors
CPU     ID                    FUNCTION:NAME
dtrace: error on enabled probe ID 1 (ID 14: syscall::open:entry): \
  illegal operation in action #1

Dieser Grenzwert wird selbst dann berücksichtigt, wenn die Zeit auf mehrere chill()-Aufrufe oder mehrere DTrace-Verbraucher eines einzigen Prüfpunkts aufgeteilt ist. Derselbe Fehler würde beispielsweise auch durch den folgenden Befehl generiert werden:


# dtrace -w -n syscall::open:entry'{chill(250000000); chill(250000001);}'

Besondere Aktionen

In diesem Abschnitt werden Aktionen beschrieben, die weder Daten aufzeichnen noch destruktiv sind.

Spekulative Aktionen

Für die spekulative Ablaufverfolgung stehen die Aktionen speculate(), commit() und discard() zur Verfügung. Diese Aktionen werden in Kapitel 13Spekulative Ablaufverfolgung erläutert.

exit()

void exit(int Status)

Die Aktion exit() beendet die Ablaufverfolgung unverzüglich und teilt dem DTrace-Verbraucher mit, dass er die Aufzeichnung abbrechen, die erforderliche abschließende Verarbeitung durchführen und exit(3C) mit dem angegebenen Status aufrufen soll. Da exit() einen Status an die Benutzerebene zurückgibt, ist dies zwar eine Daten aufzeichnende Aktion, aber im Gegensatz zu anderen Daten aufzeichnenden Aktionen kann exit() nicht spekulativ verfolgt werden. Unabhängig von der Pufferungsregel bewirkt exit(), dass der DTrace-Verbraucher beendet wird. Da exit() eine Daten aufzeichnende Aktion ist, kann sie verworfen werden.

Wenn exit() aufgerufen wird, werden nur die auf anderen CPUs bereits laufenden DTrace-Aktionen abgeschlossen. Auf keiner CPU erfolgen jedoch neue Aktionen. Die einzige Ausnahme zu dieser Regel ist die Abarbeitung des Prüfpunkts END, der aufgerufen wird, nachdem der DTrace-Verbraucher die Aktion exit() verarbeitet und angegeben hat, dass die Ablaufverfolgung zu beenden ist.

Subroutinen

Subroutinen unterscheiden sich dadurch von Aktionen, dass sie im Allgemeinen nur den internen DTrace-Status beeinflussen. Es gibt also keine destruktiven Subroutinen, und Subroutinen schreiben niemals Datenablaufprotokolle in Puffer. Viele der Subroutinen besitzen ein Pendant unter den Schnittstellen aus Teil 9F und 3C. Weitere Informationen zu den entsprechenden Subroutinen finden Sie unter Intro(9F) und Intro(3).

alloca()

void *alloca(size_t Groesse)

alloca() reserviert im Scratch-Bereich die Menge der mit Groesse angegebenen Byte und gibt einen Zeiger auf den reservierten Speicherplatz zurück. Der zurückgegebene Zeiger hat immer eine 8-Byte-Ausrichtung. Die Gültigkeit des Scratch-Bereichs beschränkt sich auf die Lebensdauer einer Klausel. Nach Abschluss der Klausel wird die Zuweisung des mit alloca() reservierten Speicherplatzes wieder aufgehoben. Wenn nicht genügend Platz im Scratch-Bereich vorhanden ist, wird kein Speicherplatz reserviert und ein Fehler generiert.

basename()

string basename(char *Zeichenkette)

basename() ist ein D-Pendant zu basename(1). Diese Subroutine erzeugt eine Zeichenkette, die aus einer Kopie der angegebenen Zeichenkette ohne ein auf / endendes Präfix besteht. Der zurückgegebenen Zeichenkette wird Speicherplatz im Scratch-Bereich reserviert, sodass sie nur für die Dauer der Klausel gültig ist. Wenn nicht genügend Platz im Scratch-Bereich vorhanden ist, wird basename nicht ausgeführt und ein Fehler generiert.

bcopy()

void bcopy(void *src, void *dest, size_t Groesse)

bcopy() kopiert so viele Byte wie mit Groesse angegeben aus dem Speicherbereich, auf den Ausg zeigt, in den mit Ziel angegebenen Speicherbereich. Der Ausgangsspeicher muss vollständig außerhalb des Scratch-Bereichs und der Zielspeicher vollständig darin liegen. Werden diese Voraussetzungen nicht erfüllt, erfolgt keine Kopie und es wird ein Fehler generiert.

cleanpath()

string cleanpath(char *Zeichenkette)

cleanpath() erzeugt eine Zeichenkette, die aus einer um gewisse redundante Teile gekürzten Kopie des mit Zeichenkette angegebenen Pfads besteht. Insbesondere werden „/.„/./”-Elemente aus dem Pfad entfernt und „/../”-Elemente gekürzt. Bei der Kürzung der /../-Elemente im Pfad werden symbolische Links nicht berücksichtigt. Deshalb ist es denkbar, dass cleanpath() einen gültigen Pfad übernimmt und einen kürzeren, ungültigen zurückgibt.

Wenn die Zeichenkette beispielsweise „ /foo/../bar” ist und /foo ein symbolischer Link zu /net/foo/export, dann gibt cleanpath() die Zeichenkette „/bar” zurück, obwohl sich bar möglicherweise nur in /net/foo und nicht in / befindet. Diese Einschränkung ist darauf zurückzuführen, dass cleanpath() im Kontext eines ausgelösten Prüfpunkts aufgerufen wird, wo eine vollständige Auflösung symbolischer Links oder beliebiger Namen nicht möglich ist. Der zurückgegebenen Zeichenkette wird Speicherplatz im Scratch-Bereich reserviert, sodass sie nur für die Dauer der Klausel gültig ist. Wenn nicht genügend Platz im Scratch-Bereich vorhanden ist, wird cleanpath nicht ausgeführt und ein Fehler generiert.

copyin()

void *copyin(uintptr_t Adr, size_t Groesse)

copyin() kopiert die angegebene Größe in Byte von der angegebenen Benutzeradresse in einen Scratch-Puffer von DTrace und gibt die Adresse dieses Puffers zurück. Die Benutzeradresse wird als eine Adresse im Bereich des Prozesses interpretiert, zu dem der aktuelle Thread gehört. Der zurückgegebene Zeiger auf den Puffer hat immer eine 8-Byte-Ausrichtung. Die betreffende Adresse muss auf eine durch Seitenfehler eingelagerte Seite im aktuellen Prozess zutreffen. Ist dies nicht der Fall oder ist nur unzureichender Scratch-Platz verfügbar, wird NULL zurückgegeben und ein Fehler generiert. Informationen zu Verfahren, mit denen das Auftreten von copyin-Fehlern vermindert wird, finden Sie in Kapitel 33Ablaufverfolgung von Benutzerprozessen.

copyinstr()

string copyinstr(uintptr_t Adr)

copyinstr() kopiert eine auf Null endende C-Zeichenkette von der angegebenen Benutzeradresse in einen Scratch-Puffer von DTrace und gibt die Adresse dieses Puffers zurück. Die Benutzeradresse wird als eine Adresse im Bereich des Prozesses interpretiert, zu dem der aktuelle Thread gehört. Die Länge der Zeichenkette ist auf den mit der Option strsize festgelegten Wert beschränkt. Näheres hierzu siehe Kapitel 16Optionen und Tunables . Wie auch bei copyin muss die angegebene Adresse mit einer durch Seitenfehler eingelagerten Seite im aktuellen Prozess übereinstimmen. Ist dies nicht der Fall oder ist nur unzureichender Scratch-Platz verfügbar, wird NULL zurückgegeben und ein Fehler generiert. Informationen zu Verfahren, mit denen das Auftreten von Kapitel 33Ablaufverfolgung von Benutzerprozessen-Fehlern vermindert wird, finden Sie in Chapter 33, User Process Tracing.

copyinto()

void copyinto(uintptr_t Adr, size_t Groesse, void *Ziel)

copyinto() kopiert die angegebene Menge Byte von der angegebenen Benutzeradresse in den mit Ziel festgelegten Scratch-Puffer von DTrace. Die Benutzeradresse wird als eine Adresse im Bereich des Prozesses interpretiert, zu dem der aktuelle Thread gehört. Die betreffende Adresse muss auf eine durch Seitenfehler eingelagerte Seite im aktuellen Prozess zutreffen. Ist dies nicht der Fall oder liegt auch nur ein Teil des Zielspeichers außerhalb des Scratch-Bereichs, erfolgt keine Kopie und ein Fehler wird generiert. Informationen zu Verfahren, mit denen das Auftreten von Kapitel 33Ablaufverfolgung von Benutzerprozessen-Fehlern vermindert wird, finden Sie in Chapter 33, User Process Tracing.

dirname()

string dirname(char *Zeichenkette)

dirname() ist ein D-Pendant zu dirname(1). Diese Subroutine erzeugt eine Zeichenkette, die aus dem mit Zeichenkette angegebenen Pfadnamen ohne dem letzten Glied besteht. Der zurückgegebenen Zeichenkette wird Speicherplatz im Scratch-Bereich reserviert, sodass sie nur für die Dauer der Klausel gültig ist. Wenn nicht genügend Platz im Scratch-Bereich vorhanden ist, wird dirname nicht ausgeführt und ein Fehler generiert.

msgdsize()

size_t msgdsize(mblk_t *Meldungszeiger)

msgdsize() gibt die Anzahl der Byte in der Datenmeldung zurück, auf die Meldungszeiger zeigt. Ausführliche Informationen finden Sie im Abschnitt msgdsize(9F). msgdsize() berücksichtigt in der Zählung nur Datenblöcke des Typs M_DATA.

msgsize()

size_t msgsize(mblk_t *Meldungszeiger)

msgsize() gibt die Anzahl der Byte in der Meldung zurück, auf die Meldungszeiger zeigt. Anders als msgdsize() gibt msgsize() nicht nur die Anzahl der Datenbyte, sondern die Gesamtbyteanzahl der Meldung zurück.

mutex_owned()

int mutex_owned(kmutex_t *Mutex)

mutex_owned() ist eine Implementierung von mutex_owned(9F). mutex_owned() gibt einen Wert ungleich Null zurück, wenn der aufrufende Thread derzeit den angegebenen Kernel-Mutex belegt, und Null, wenn der angegebene adaptive Mutex derzeit frei ist.

mutex_owner()

kthread_t *mutex_owner(kmutex_t *Mutex)

mutex_owner() gibt den Thread-Zeiger des aktuellen Besitzers des angegebenen adaptiven Kernel-Mutex zurück. mutex_owner() gibt NULL zurück, wenn der angegebene adaptive Mutex derzeit frei ist oder es sich bei dem angegebenen Mutex um eine Warteschleife (Spinlock) handelt. Siehe hierzu mutex_owned(9F).

mutex_type_adaptive()

int mutex_type_adaptive(kmutex_t *Mutex)

mutex_type_adaptive() gibt einen Wert ungleich Null zurück, wenn es sich bei dem angegebenen Kernel-Mutex um den Typ MUTEX_ADAPTIVE handelt, anderenfalls gibt die Aktion Null zurück. Ein Mutex ist dann adaptiv, wenn er mindestens eine der folgenden Bedingungen erfüllt:

mutex_init(9F) enthält weitere Informationen zu Mutexen. Die meisten Mutexe im Solaris-Kernel sind adaptiv.

progenyof()

int progenyof(pid_t PID)

progenyof() gibt einen Wert ungleich Null zurück, wenn der aufrufende Prozess (der Prozess, zu dem der Thread gehört, der derzeit den übereinstimmenden Prüfpunkt auslöst) ein Nachkomme des mit der ID angegebenen Prozesses ist.

rand()

int rand(void)

rand() gibt eine pseudo-zufällige Ganzzahl zurück. Die zurückgegebene Zahl ist eine schwache, pseudo-zufällige Zahl, die in keiner kryptographischen Anwendung verwendet werden sollte.

rw_iswriter()

int rw_iswriter(krwlock_t *rwlock)

rw_iswriter() gibt einen Wert ungleich Null zurück, wenn die angegebene Leser/Schreiber-Sperre entweder von einem Schreiber belegt oder angefordert wird. Wird die Sperre nur von Lesern belegt und ist kein Schreiber blockiert oder ist die Sperre ganz frei, gibt rw_iswriter() Null zurück. Siehe hierzu rw_init(9F).

rw_write_held()

int rw_write_held(krwlock_t *rwlock)

rw_write_held() gibt einen Wert ungleich Null zurück, wenn die angegebene Leser/Schreiber-Sperre derzeit im Besitz eines Schreibers ist. Ist die Sperre entweder nur von Lesern belegt oder ganz frei, gibt rw_write_held() Null zurück. Siehe hierzu rw_init(9F).

speculation()

int speculation(void)

speculation() reserviert einen spekulativen Tracing-Puffer für die Verwendung durch speculate() und gibt eine ID für diesen Puffer zurück. Ausführliche Informationen finden Sie in Kapitel 13Spekulative Ablaufverfolgung.

strjoin()

string strjoin(char *Zeichenkette1, char *Zeichenkette2)

strjoin() erzeugt eine Zeichenkette, die aus einer Verkettung von Zeichenkette1 und Zeichenkette2 besteht. Der zurückgegebenen Zeichenkette wird Speicherplatz im Scratch-Bereich reserviert, sodass sie nur für die Dauer der Klausel gültig ist. Wenn nicht genügend Platz im Scratch-Bereich vorhanden ist, wird strjoin nicht ausgeführt und ein Fehler generiert.

strlen()

size_t strlen(string Zeichenkette)

strlen() gibt die Länge der angegebenen Zeichenkette in Byte ohne das abschließende Null-Byte zurück.