Mittwoch, 20. August 2008

Dtrace Workshop - Teil 4: Userspace tracen mit dem PID-Provider

Nachdem wir uns in den letzten drei Teilen eigentlich nur mit Kerneltracing befasst haben, wird es Zeit sich dem Userspace zu widmen. Hierfür liefert dtrace den PID-Provider mit dem wir jede Funktion innerhalb eines Prozesses genauso unter die Lupe nehmen können wie wir es auch schon beim Kernel gelernt haben. Diesen Provider werde ich jetzt anhand eines einfachen C-Programms vorstellen.

#include <stdio.h>

void a(int i)
{
printf("a\n");
return;
}

void b(int i)
{
printf("b\n");
a(3);
}

void c(int i)
{
printf("c\n");
b(2);
}

int main()
{
c(1);
return 0;
}

Dieses Programm werden wir komplett ohne Debugsymbole übersetzen und userspace nennen. Wir wollen jetzt mit dem PID-Provider sehen welche Funktionen innerhalb des Programms ausgeführt werden. Dazu schreiben wir ein einfaches D Skript.

#!/usr/sbin/dtrace -s

#pragma D option flowindent

pid$target:userspace::return,
pid$target:userspace::entry
{
}

Die Variable $target bezieht sich auf das Programm das wir für dtrace als Ziel angeben. Dieses können wir mir der -c Option für ein spezifisches Kommando, oder mit -p für eine bestimmte PID angeben. Normal setzt sich der PID-Provider aus dem Wort pid und einer PID zusammen (z.B. pid2342), mit der Zielvariablen ist unser Skript allerdings sehr viel dynamischer. Als Modul haben wir hier userspace angegeben. Das ist der Name unseres Programms. Wir werden also nur Funktionen tracen die wir innerhalb unseres Programms definiert haben. Mit dtrace -l -p pidPID kann man sich übrigens alle tracebaren Funktionen eines bestimmten Prozesses ausgeben lassen (aber vorsicht, das ist meistens echt viel ;) ).

raichoo@itzkoatl:~/Projects# ./userspacetrace.d -c ./userspace
dtrace: script './userspacetrace.d' matched 13 probes
c
b
a
CPU FUNCTION
0 -> _start
0 -> __fsr
0 <- __fsr
0 -> main
0 -> c
0 -> b
0 -> a
0 <- a
0 <- b
0 <- c
0 <- main
dtrace: pid 697 has exited

Jetzt würden wir aber gerne noch unsere printfs mittracen, dazu erweitern wir unser Skript einfach um die Tupel pid$target:libc.so.1:printf:entry und pid$target:libc.so.1:printf:return um die in libc.so.1 definierte printf Funktion zu tracen (das geht auch für jede andere gelinkte lib). Wir erhalten nun folgende Ausgabe:

dtrace: script './userspacetrace.d' matched 15 probes
c
b
a
CPU FUNCTION
0 -> _start
0 -> __fsr
0 <- __fsr
0 -> main
0 -> c
0 -> printf
0 <- printf
0 -> b
0 -> printf
0 <- printf
0 -> a
0 -> printf
0 <- printf
0 <- a
0 <- b
0 <- c
0 <- main
dtrace: pid 714 has exited

Somit können wir jetzt jede Userspace Funktion tracen. Zum Abschluss will ich noch zeigen wie wir hier auch Probeargumente anwenden können:

#!/usr/sbin/dtrace -s

#pragma D option flowindent

pid$target:userspace::entry
{
printf("Funktionsargument %d",arg0);
}


pid$target:userspace::return,
pid$target:libc.so.1:printf:return
{
}

pid$target:libc.so.1:printf:entry
{
printf("Funktionsargument %s",copyinstr(arg0));
}

Kommentare:

El hat gesagt…

Hi, teste Dein Tutorial gerade auf meinem Mac aus und bekomme leider beim ausführen von ./userspacetrace.d -c ./userspace die Fehlermeldung 'dtrace: failed to compile script ./userspacetrace.d: line 5: failed to create return probe for 'start': Unknown provider name'.

Irgendeine Idee, woran es liegen könnte?

xRaich[o]²x hat gesagt…

Hi,

Ich habe die Beispiele unter OpenSolaris getestet. Zu damaliger Zeit habe ich keinen Mac besessen. Ich habe das ganze jetzt mal unter Snow Leopard getestet. Das Problem scheint zu sein, daß das Programm einfach durchläuft bevor dtrace überhaupt die Probes aktiviert hat. Unter OpenSolaris ging das, aber da dieses Beispiel jetzt nicht umbedingt ein Paradebeispiel ist kannst du vor dem Aufruf der Funktion c einfach mal ein sleep(2) einfügen. Das ist zugegebenermaßen ein etwas armseeliger Fix. Aber mir fällt gerade auf die Schnelle nichts besseres ein ;). Ich werde mir das noch mal genauer ansehen und gegebenenfalss drüber bloggen. Aber derzeit ist mein Zeitplan etwas voll.

Gruß
raichoo