Guia de rastreamento dinâmico Solaris

Ponteiros para structs

Fazer referência a structs usando ponteiros é muito comum em C e em D. Você pode usar o operador -> para acessar membros de struct através de um ponteiro. Se uma struct s tiver um membro m e você possuir um ponteiro para esse struct chamado sp (ou seja, sp é uma variável do tipo struct s *), use o operador * para primeiro cancelar a referência do ponteiro sp a fim de acessar o membro:

struct s *sp;

(*sp).m

ou use o operador -> como um atalho para essa notação. Os dois fragmentos seguintes de D terão significados equivalentes, se sp for um ponteiro para uma struct:

(*sp).m				sp->m

O DTrace oferece algumas variáveis incorporadas que são ponteiros para structs, incluindo curpsinfo e curlwpsinfo. Esses ponteiros se referem respectivamente às structs psinfo e lwpsinfo, e seu conteúdo fornece um instantâneo de informações sobre o estado do processo atual e o processo leve (LWP) associado ao segmento que acionou o teste atual. Um Solaris LWP é a representação do kernel de um segmento do usuário, sobre o qual os segmentos do Solaris e as interfaces dos segmentos do POSIX são construídos. Por questões de conveniência, o DTrace exporta as informações no mesmo formato que os arquivos do sistema de arquivos /proc /proc/ pid/psinfo e /proc/pid/lwps/ lwpid/lwpsinfo. As estruturas /proc são usadas por ferramentas de observação e de depuração, tais como ps(1), pgrep(1) e truss(1) e são definidas no arquivo de cabeçalho do sistema de arquivos <sys/procfs.h> e são descritas na página do manual proc(4). Aqui estão algumas expressões de exemplo que usam curpsinfo, seus tipos e significados:

curpsinfo->pr_pid

pid_t

ID do processo atual 

curpsinfo->pr_fname

char []

nome do arquivo executável 

curpsinfo->pr_psargs

char []

argumentos iniciais da linha de comando 

Você deve revisar a definição de estrutura completa mais tarde, examinando o arquivo de cabeçalho <sys/procfs.h> e as descrições correspondentes em proc(4). O próximo exemplo usa o membro pr_psargs para identificar um processo do seu interesse, coincidindo os argumentos da linha de comando.

As structs são usadas freqüentemente para criar estruturas de dados complexas de programas em C,sendo assim, a capacidade de descrever e referenciar structs a partir de D também oferece um recurso poderoso de observação do funcionamento interno do kernel do sistema operacional Solaris e suas interfaces de sistema. Além disso, para usar a struct curpsinfo, mencionada anteriormente, o próximo exemplo também examina algumas structs do kernel, observando a relação entre o driver de ksyms(7D) e as solicitações de read(2) O driver usa duas structs comuns, conhecidas como uio(9S) and iovec(9S), para responder a solicitações de leitura a partir do arquivo de dispositivo de caractere /dev/ksyms.

A struct uio, acessada através do nome struct uio ou do alias de tipo uio_t, é descrita na página do manual uio(9S) e é usada para descrever uma solicitação de E/S que envolve a cópia de dados entre o kernel e um processo do usuário. uio, por sua vez, contém uma matriz de uma ou mais estruturas iovec(9S), cada uma descrevendo uma parte da E/S solicitada, caso vários blocos sejam solicitados por meio das chamadas do sistemareadv(2) ou writev(2) Uma das rotinas de interface de driver de dispositivo (DDI) do kernel que opera na struct uio é a função uiomove(9F), que é uma das famílias que os drivers do kernel de funções usam para responder às solicitações read(2) do processo do usuário e copiar os dados de volta para os processos do usuário.

O driver ksyms gerencia um arquivo de dispositivo de caractere chamado /dev/ksyms, que parece ser um arquivo ELF que contém informações sobre a tabela de símbolo do kernel, mas que é, de fato, uma ilusão criada pelo driver usando o conjunto de módulos que estão carregados no momento no kernel. O driver usa a rotina uiomove(9F) para responder às solicitações de read(2). O próximo exemplo ilustra que os argumentos e as chamadas para read(2) de /dev/ksyms correspondem às chamadas do driver para uiomove(9F) a fim de copiar os resultados de volta para o espaço de endereço do usuário no local especificado para read(2).

Podemos usar o utilitário strings(1) com a opção -a para forçar várias leituras de /dev/ksyms . Tente executar strings -a /dev/ksyms em seu shell e veja qual saída é produzida. Em um editor, digite a primeira cláusula do script de exemplo e salve-a em um arquivo chamado ksyms.d:

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

Esta primeira cláusula usa a expressão curpsinfo->pr_psargs para acessar e coincidir os argumentos da linha de comando do nosso comando strings(1), para que o script selecione as solicitações corretas de read(2) antes de rastrear os argumentos. Observe que usando o operador == com um argumento esquerdo que seja uma matriz de char e um argumento direito que seja uma seqüência, o compilador de D deduz que o argumento esquerdo deve ser promovido para uma seqüência e uma comparação de seqüência deve ser realizada. Digite e execute o comando dtrace -q -s ksyms.d em um shell e, em seguida, digite o comando strings -a /dev/ksyms em outro shell. Enquanto strings(1) é executado, você verá a saída do DTrace semelhante ao exemplo seguinte:


# 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 exemplo pode ser estendido com uma técnica de programação comum em D para seguir um segmento a partir desta solicitação de read(2) inicial até um local mais profundo no kernel. Na entrada do kernel em syscall::read:entry, o próximo script define uma variável de sinalizador local de segmento, indicando que esse segmento é do seu interesse, e limpa esse sinalizador em syscall::read:return. Quando é definido, o sinalizador pode ser usado como um predicado em outros testes para instrumentar as funções do kernel, tal como uiomove(9F). O provedor de rastreio de limite de função do DTrace ( fbt) publica os testes de entrada e retorna para as funções definidas no kernel, incluindo as da DDI. Digite o seguinte código-fonte, que usa o provedor fbt para instrumentaruiomove(9F) e salve-o novamente no arquivo ksyms.d:


Exemplo 7–2 ksyms.d: rastreio de relação de read(2) e 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);
}

A cláusula final do exemplo usa a variável local de segmento self->watched para identificar quando um segmento do kernel de seu interesse entra na rotina DDI uiomove(9F). Uma vez lá, o script usa a matriz incorporada args para acessar o quarto argumento (args[3]) para uiomove(), que é um ponteiro para a struct uio que representa a solicitação. O compilador de D associa automaticamente cada membro da matriz args com o tipo correspondente ao protótipo da função de C da rotina do kernel instrumentado. O membro uio_iov contém um ponteiro para struct iovec da solicitação. Uma cópia deste ponteiro é salva para que seja usada na nossa cláusula na variável local de cláusula this->iov. Na declaração final, o script cancela a referência de this->iov para acessar os membros iovec, iov_len e iov_base, que representam o tamanho em bytes e o endereço base de destino de uiomove(9F), respectivamente. Esses valores devem corresponder aos parâmetros de entrada para a chamada do sistema de read(2) emitida no driver. Vá para o shell, execute dtrace -q -s ksyms.d e insira novamente o comando strings -a /dev/ksyms em outro shell. Você deve ver uma saída semelhante ao exemplo seguinte:


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

Os endereços e os IDs de processo serão diferentes na sua saída, mas você deve observar que os argumentos de entrada para read(2) correspondem aos parâmetros passados para uiomove(9F) pelo driver ksyms.