Manuel de suivi dynamique Solaris

Pointeurs vers structs

Faire référence à des structs à l'aide de pointeurs est courant en C et en D. Vous pouvez utiliser l'opérateur -> pour accéder aux membres d'une struct par l'intermédiaire d'un pointeur. Si une struct s intègre le membre m et qu'un pointeur sur cette struct s'appelle sp (sp étant une variable du type struct s *), vous pouvez utiliser l'opérateur * pour déréférencer le pointeur sp, afin d'accéder au membre :

struct s *sp;

(*sp).m

Vous pouvez également utiliser l'opérateur -> comme raccourci pour cette notation. Les deux exemples en D suivants ont la même signification si sp est utilisé comme pointeur vers une struct :

(*sp).m				sp->m

DTrace intègre plusieurs variables de type pointeurs vers structs, y compris curpsinfo et curlwpsinfo. Ces pointeurs renvoient respectivement aux structs psinfo et lwpsinfo et leur contenu offre un aperçu des informations relatives à l'état du processus courant et du processus léger (LWP) associés au thread ayant déclenché la sonde actuelle. Un processus Solaris léger est la représentation du noyau d'un thread utilisateur sur lequel les interfaces de thread Solaris et POSIX sont créées. Pour plus de facilité, DTrace exporte ces informations sous la même forme que les fichiers du système de fichiers /proc /proc/pid/psinfo et /proc/pid/lwps/lwpid/lwpsinfo. Les structures /proc sont utilisées par les outils d'observation et de débogage comme ps(1), pgrep(1) et truss(1). Elles sont définies dans le fichier d'en-tête du système <sys/procfs.h>, ainsi que sur la page de manuel proc(4). Voici quelques exemples d'expressions utilisant curpsinfo, leurs types et leurs significations :

curpsinfo->pr_pid

pid_t

ID de processus courant 

curpsinfo->pr_fname

char []

nom de fichier exécutable 

curpsinfo->pr_psargs

char []

arguments de ligne de commande initiaux 

Vous devez revoir la définition complète de la structure ultérieurement en examinant le fichier d'en-tête <sys/procfs.h> et les descriptions correspondantes dans proc(4). L'exemple suivant utilise le membre pr_psargs pour identifier le processus qui nous intéresse en adaptant les arguments de la ligne de commande.

Les structs sont fréquemment utilisées pour créer des structures de données complexes dans les programmes en langage C. Par conséquent, l'aptitude à décrire et référencer les structs à partir du langage D offre une puissante capacité d'observation du fonctionnement interne du noyau du système d'exploitation Solaris et de ses interfaces système. En plus de l'utilisation de la struct curpsinfo mentionnée ci-dessus, l'exemple suivant examine certaines structs du noyau en observant la relation entre le pilote ksyms(7D) et les demandes read(2). Le pilote utilise les deux structs usuelles uio(9S) et iovec(9S) pour répondre aux demandes de lecture à partir du fichier de périphérique caractère /dev/ksyms.

La struct uio, accessible via le nom struct uio ou l'alias de type uio_t, est décrite sur la page de manuel uio(9S) et permet de décrire une demande d'E/S impliquant la copie de données entre le noyau et un processus utilisateur. La struct uio contient en retour un tableau d'une ou plusieurs structures iovec(9S) qui décrivent chacune un élément de l'E/S demandée dans le cas où plusieurs éléments sont demandés à l'aide des appels système readv(2) ou writev(2). L'une des routines DDI (interface de pilote de périphérique) du noyau qui fonctionne sur struct uio est représentée par la fonction uiomove(9F), cette dernière constituant l'un des ensembles de fonctions que les pilotes du noyau utilisent pour répondre aux demandes read(2) du processus utilisateur et recopier les données sur les processus utilisateur.

Le pilote ksyms gère le fichier de périphérique caractère /dev/ksyms, ce dernier semblant être un fichier ELF contenant des informations sur la table de symboles du noyau. En fait, il s'agit d'une illusion créée par le pilote à l'aide de l'ensemble de modules actuellement chargés dans le noyau. Le pilote utilise la routine uiomove(9F) pour répondre aux demandes read(2). L'exemple suivant montre que les arguments et les appels de read(2) à partir de /dev/ksyms correspondent aux appels du pilote vers uiomove(9F) pour recopier les résultats dans l'espace d'adressage utilisateur à l'emplacement spécifié pour read(2).

Il est possible d'utiliser l'utilitaire strings(1) combiné à l'option -a pour forcer un ensemble de lecture à partir de /dev/ksyms. Essayez d'exécuter strings -a /dev/ksyms dans votre shell et voyez la sortie obtenue. Dans un éditeur, tapez la première clause de l'exemple de script et enregistrez-la dans le fichier ksyms.d:

syscall::read:entry
/curpsinfo->pr_psargs == "strings -a /dev/ksyms"/
{
	printf("read %u bytes to user address %x\n", arg2, arg1);
}

Cette première clause utilise l'expression curpsinfo->pr_psargs pour accéder et s'adapter aux arguments de la ligne de commande de notre commande strings(1) de sorte que le script sélectionne les demandes read(2) appropriées avant de suivre les arguments. Notez qu'en utilisant l'opérateur == avec un argument gauche correspondant à un tableau de char et un argument droit correspondant à une chaîne, le compilateur D déduit que l'argument gauche doit être promu en chaîne et qu'une comparaison de chaîne doit être exécutée. Tapez et exécutez la commande dtrace -q -s ksyms.d dans un shell, puis tapez la commande strings -a /dev/ksyms dans un autre shell. Tandis que strings(1) s'exécute, vous obtenez une sortie à partir de DTrace similaire à l'exemple suivant :


# dtrace -q -s ksyms.d
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
...
^C
#

Il est possible d'étendre cet exemple à l'aide d'une technique de programmation en D classique pour suivre un thread plus profondément dans le noyau à partir de cette demande read(2) initiale. Une fois entré dans le noyau avec syscall::read:entry, le script suivant définit une variable d'indication de thread local signalant le thread qui nous intéresse et efface cet indicateur sur syscall::read:return. Une fois l'indicateur défini, vous pouvez l'utiliser comme prédicat sur d'autres sondes pour instrumenter les fonctions du noyau comme uiomove(9F). Le fournisseur de suivi des limites des fonctions de DTrace ( fbt) édite les sondes d'entrée et revient aux fonctions définies dans le noyau, y compris celles de la DDI. Tapez le code source suivant. Ce dernier utilise le fournisseur fbt pour instrumenter uiomove(9F). Enregistrez-le ensuite dans le fichier ksyms.d.


Exemple 7–2 ksyms.d : suivi de la relation deread(2) et uiomove(9F )

/*
 * When our strings(1) invocation starts a read(2), set a watched flag on
 * the current thread.  When the read(2) finishes, clear the watched flag.
 */
syscall::read:entry
/curpsinfo->pr_psargs == "strings -a /dev/ksyms"/
{
	printf("read %u bytes to user address %x\n", arg2, arg1);
	self->watched = 1;
}

syscall::read:return
/self->watched/
{
	self->watched = 0;
}

/*
 * Instrument uiomove(9F).  The prototype for this function is as follows:
 * int uiomove(caddr_t addr, size_t nbytes, enum uio_rw rwflag, uio_t *uio);
 */
fbt::uiomove:entry
/self->watched/
{
	this->iov = args[3]->uio_iov;

	printf("uiomove %u bytes to %p in pid %d\n",
	    this->iov->iov_len, this->iov->iov_base, pid);
}

La clause finale de l'exemple utilise la variable de thread local self->watched pour identifier à quel moment le thread de noyau qui nous intéresse lance la routine uiomove(9F) de la DDI. À ce stade, le script utilise le tableau args intégré pour permettre au quatrième argument (args[3]) d'accéder à uiomove(), cette fonction consistant en un pointeur vers la struct uio représentant la demande. Le compilateur D associe automatiquement chaque membre du tableau args au type qui correspond au prototype de fonction en langage C de la routine de noyau instrumentée. Le membre uio_iov contient un pointeur vers la struct iovec pour la demande. Une copie de ce pointeur est enregistrée pour être utilisée dans notre clause dans la variable de clause locale this->iov. Dans l'instruction finale, le script déréférence this->iov pour accéder aux membres de iovec iov_len et iov_base qui représentent respectivement la longueur en octets et l'adresse de base de la destination de uiomove(9F). Ces valeurs doivent correspondre aux paramètres d'entrée de l'appel système read(2) émis sur le pilote. Accédez à votre shell et exécutez dtrace -q -s ksyms.d. Entrez ensuite la commande strings -a /dev/ksyms dans un autre shell. Vous devez obtenir une sortie similaire à l'exemple suivant :


# dtrace -q -s ksyms.d
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
...
^C
#

Les adresses et les ID de processus sont différents dans votre sortie. Vous devez toutefois observer que les arguments en entrée vers read(2) correspondent aux paramètres transmis à uiomove(9F) par le pilote ksyms.