Handbuch zur dynamischen Ablaufverfolgung in Solaris

Die Subroutinen copyin() und copyinstr()

Wie DTrace mit Prozessen interagiert, unterscheidet sich geringfügig von den meisten herkömmlichen Debuggern oder Beobachtungstools. Viele dieser Hilfsmittel werden scheinbar innerhalb des Bereichs des Prozesses ausgeführt und lassen eine direkte Dereferenzierung von Zeigern auf Programmvariablen durch Benutzer zu. Anstatt scheinbar innerhalb oder als Bestandteil des Prozesses selbst, werden DTrace-Prüfpunkte im Solaris-Kernel ausgeführt. Für den Zugriff auf Prozessdaten müssen Prüfpunkte die Prozessdaten mithilfe der Subroutinen copyin() oder copyinstr() in den Adressraum des Kernels kopieren.

Betrachten wir beispielsweise den folgenden write(2)-Systemaufruf:

ssize_t write(int fd, const void *buf, size_t nbytes);

Das folgende D-Programm demonstriert einen falschen Versuch, den Inhalt einer dem write(2)-Systemaufruf übergebenen Zeichenkette auszugeben:

syscall::write:entry
{
	printf("%s", stringof(arg1)); /* incorrect use of arg1 */
}

Wenn Sie versuchen, dieses Skript auszuführen, erzeugt DTrace ähnliche Fehlermeldungen wie in diesem Beispiel:


dtrace: error on enabled probe ID 1 (ID 37: syscall::write:entry): \
    invalid address (0x10038a000) in action #1

Bei der Variable arg1, die den Wert des Parameters Puffer enthält, handelt es sich um eine Adresse im Speicher des Prozesses, der den Systemaufruf durchführt. Die Zeichenkette an dieser Adresse lesen wir mit der Subroutine copyinstr() und zeichnen das Ergebnis mit der Aktion printf() auf:

syscall::write:entry
{
	printf("%s", copyinstr(arg1)); /* correct use of arg1 */

Die Ausgabe dieses Skripts zeigt alle Zeichenketten, die an den write(2)-Systemaufruf übergeben werden. Gelegentlich kann jedoch eine unbrauchbare Ausgabe wie in diesem Beispiel erzeugt werden:


  0     37                      write:entry mada���

Die Subroutine copyinstr() wirkt auf ein Eingabeargument, die Benutzeradresse einer auf Null endenden ASCII-Zeichenkette. Dem write(2)-Systemaufruf übergebene Puffer beziehen sich jedoch möglicherweise nicht auf ASCII-Zeichenketten, sondern auf Binärdaten. Damit nur so viel von der Zeichenkette ausgegeben wird, wie der Aufrufer beabsichtigt hat, verwenden Sie die Subroutine copyin(), die als zweites Argument eine Größe annimmt:

syscall::write:entry
{
	printf("%s", stringof(copyin(arg1, arg2)));
}

Beachten Sie, dass DTrace den Operator stringof benötigt, um die mit copyin() abgerufenen Benutzerdaten in eine Zeichenkette umzuwandeln. Wenn Sie&;copyinstr verwenden, ist stringof() nicht erforderlich, da diese Funktion stets den Typ string zurückgibt.

Vermeiden von Fehlern

Die Subroutinen copyin() und copyinstr() können nicht aus noch „unberührten“ Benutzeradressen lesen. Das heißt, dass selbst eine gültige Adresse einen Fehler verursachen kann, wenn die Speicherseite, die diese Adresse enthält, noch nicht durch einen Seitenfehler infolge eines Zugriffs eingelagert wurde. Betrachten wir das folgende Beispiel:


# dtrace -n syscall::open:entry'{ trace(copyinstr(arg0)); }'
dtrace: description 'syscall::open:entry' matched 1 probe
CPU     ID                    FUNCTION:NAME
dtrace: error on enabled probe ID 2 (ID 50: syscall::open:entry): invalid address
(0x9af1b) in action #1 at DIF offset 52

Die Anwendung in der obigen Ausgabe funktioniert ordnungsgemäß und die Adresse in arg0 ist gültig, bezieht sich aber auf eine Seite, auf die noch nicht durch den entsprechenden Prozess zugegriffen wurde. Um dieses Problem zu umgehen, warten Sie mit der Aufzeichnung, bis Kernel oder Anwendung die Daten verwenden. Sie könnten etwa wie im nächsten Beispiel mit der Anwendung von copyinstr() warten, bis der Systemaufruf zurückkehrt:


# dtrace -n syscall::open:entry'{ self->file = arg0; }' \
-n syscall::open:return'{ trace(copyinstr(self->file)); self->file = 0; }'
dtrace: description 'syscall::open:entry' matched 1 probe
CPU     ID                    FUNCTION:NAME
  2     51                      open:return   /dev/null