Handbuch zur dynamischen Ablaufverfolgung in Solaris

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);}'