Guía de seguimiento dinámico de Solaris

Punteros a las estructuras

La utilización de punteros que hagan referencia a la estructura es una situación habitual en C y D. Puede utilizar el operador -> para acceder a los miembros de la estructura mediante un puntero. Si una struct s cuenta con un miembro m y tiene un puntero a esta estructura que se llama sp (es decir, sp es una variable del tipo struct s *), puede utilizar el operador * para en primer lugar anular la referencia del puntero sp para poder acceder al miembro:

struct s *sp;

(*sp).m

o puede utilizar el operador -> como un atajo a esta notación. Los siguientes dos fragmentos de D son equivalentes en significado si sp es un puntero a una estructura:

(*sp).m				sp->m

DTrace proporciona varias variables incorporadas que son punteros a las estructuras, incluidas curpsinfo y curlwpsinfo. Estos punteros hacen referencia a las estructuras psinfo y lwpsinfo respectivamente, y su contenido proporciona una instantánea de información acerca del estado del proceso actual y el proceso ligero (LWP) asociado con el subproceso que ha iniciado el sondeo actual. Un LWP Solaris es la representación del núcleo de un subproceso de usuario, a partir del cual se crean las interfaces de los subprocesos de Solaris y POSIX. Para mayor comodidad, DTrace exporta esta información en el mismo formato que los archivos del sistema de archivos /proc, /proc/pid/psinfo y /proc/pid/lwps/lwpid/lwpsinfo. Las estructuras /proc las utilizan herramientas de depuración y supervisión como ps(1), pgrep(1) y truss(1), se definen en el archivo de cabecera del sistema <sys/procfs.h> y se describen en la página de comando man proc(4). A continuación se muestran algunas expresiones de ejemplo utilizando curpsinfo, sus tipos y sus significados:

curpsinfo->pr_pid

pid_t

Id. de proceso actual 

curpsinfo->pr_fname

char []

nombre de archivo ejecutable 

curpsinfo->pr_psargs

char []

Argumentos iniciales de la línea de comandos 

Debe revisar toda la definición de la estructura más adelante examinando el archivo de cabecera <sys/procfs.h> y las descripciones correspondientes en proc(4). El siguiente ejemplo utiliza el miembro pr_psargs para identificar un proceso de interés haciendo coincidir los argumentos de la línea de comandos.

Las estructuras se utilizan frecuentemente para crear estructuras de datos complejas en los programas C, de forma que la capacidad de describir y hacer referencia a las estructuras desde D también proporciona una potente capacidad para observar las operaciones internas del núcleo del sistema operativo Solaris y sus interfaces del sistema. Además de utilizar la estructura curpsinfo mencionada anteriormente, el siguiente ejemplo examina algunas estructuras del núcleo, así como observa la relación entre el controlador ksyms(7D) y las solicitudes read(2). El controlador utiliza dos estructuras comunes, conocidas como uio(9S) y iovec(9S), para responder a las solicitudes que se leerán desde el archivo del dispositivo de caracteres /dev/ksyms.

La estructura uio, a la que se accede utilizando el nombre struct uio o el alias de tipo uio_t, se describe en la página de comando man uio(9S) y se utiliza para describir una solicitud de E/S que implique la copia de datos entre el núcleo y un proceso de usuario. A su vez uio contiene una matriz de una o más estructuras iovec(9S) cada una de las cuales describe una parte de las E/S solicitadas, en el caso de que se soliciten varias partes utilizando las llamadas de sistema readv(2) o writev(2). Una de las rutinas de la interfaz del controlador del dispositivo (DDI) del núcleo que funciona con struct uio es la función uiomove(9F), que pertenece a una familia de funciones que utilizan los controladores de núcleo para responder a las solicitudes read(2) del proceso del usuario y copiar los datos de nuevo a los procesos del usuario.

El controlador ksyms administra un archivo de dispositivo de caracteres que se llama /dev/ksyms, que parece ser un archivo ELF que contiene información acerca de la tabla de símbolos del núcleo, pero que en realidad es una ilusión creada por el controlador utilizando un conjunto de módulos que se han cargado en la actualidad en el núcleo. El controlador utiliza la rutina uiomove(9F) para responder a las solicitudes read(2). El siguiente ejemplo muestra que los argumentos y las llamadas a read(2) desde /dev/ksyms coinciden con las llamadas realizadas por el controlador a uiomove(9F) para copiar los resultados de nuevo en el espacio de direcciones del usuario en la ubicación especificada en read(2).

Podemos utilizar la utilidad strings(1) con la opción -a para forzar un conjunto de lecturas desde /dev/ksyms. Intente ejecutar strings -a /dev/ksyms en la shell y ver el resultado que produce. En un editor, escriba la primera cláusula de la secuencia de comandos de ejemplo y guárdela en un archivo que se llame ksyms.d:

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

La primera cláusula utiliza la expresión curpsinfo->pr_psargs para acceder y hacer coincidir los argumentos de la línea de comandos de nuestro comando strings(1) de forma que la secuencia de comandos selecciona las solicitudes deread(2) correctas antes de realizar el seguimiento de los argumentos. Tenga en cuenta que si se utiliza el operador == con un argumento a la izquierda que se encuentre en una matriz de char y un argumento de sección derecha que sea una cadena, el compilador D deduce que el argumento de la izquierda se debe asignar a una cadena y se debería realizar una comparación de cadenas. Escriba y ejecute a continuacióndtrace -q -s ksyms.d en una shell y a continuación, escriba el comando strings -a /dev/ksyms en otra shell. A medida que se ejecuta strings(1), verá el resultado de DTrace, que deberá ser parecido al siguiente ejemplo:


# 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
#

Este ejemplo se puede ampliar utilizando una técnica de programación de D común para realizar el seguimiento de un subproceso desde su solicitud read(2) inicial hasta el núcleo. Tras introducir el núcleo en syscall::read:entry, la siguiente secuencia de comandos define una variable de interés de subproceso local que indica que este subproceso es de interés, y borra este indicador en syscall::read:return. Una vez que se ha definido el indicador, se puede utilizar como un predicado en otros sondeos de funciones del núcleo del instrumento como uiomove(9F). El proveedor de seguimiento de límites de las funciones de DTrace ( fbt) publica los sondeos de entrada y devolución en las funciones definidas en el núcleo, incluidas las de DDI. Escriba el siguiente código fuente que utiliza el proveedor fbt para instrumentar uiomove(9F) y de nuevo guárdelo en el archivo ksyms.d:


Ejemplo 7–2 ksyms.d: Relación entre seguimiento read(2) y 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 cláusula final del ejemplo utiliza la variable de subproceso local self->watched para identificar cuándo un subproceso de núcleo de interés accede a la rutina de DDI uiomove(9F). Una vez allí, la secuencia de comandos utiliza la matriz args incorporada para acceder al cuarto argumento (args[3]) en uiomove(), que es un puntero a struct uio que representa la solicitud. El compilador D asocia automáticamente cada miembro de la matriz args con el tipo correspondiente al prototipo de la función C para la rutina de núcleo instrumentada. El miembro uio_iov contiene un puntero a struct iovec para la solicitud. Una copia de este puntero se guarda para utilizarla en nuestra variable de cláusula local this->iov. En la instrucción final, la secuencia de comandos anula la referencia de hits->Ivo para acceder a los miembros de iovec iov_len e iov_base, que representan la longitud en bytes y la dirección base de destino de uiomove(9F), respectivamente. Estos valores deben coincidir con los parámetros de entrada de la llamada del sistema read(2) emitida en el controlador. Vaya a al shell y ejecute dtrace -q -s ksyms.d y, a continuación, vuelva a introducir el comando strings -a /dev/ksyms en otra shell. Deberá ver un resultado parecido al siguiente ejemplo:


# 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
#

Las direcciones y los procesos serán distintos en su resultado, pero deberá ver que los argumentos de entrada de read(2)coinciden con los parámetros enviados a uiomove(9F) por el controlador ksyms.