O rastreio de chamada do sistema é uma maneira eficiente de observar o comportamento da maioria dos processos do usuário. Se você já tiver usado o recurso truss(1) do Solaris antes como administrador ou desenvolvedor, provavelmente já aprendeu que ele é uma ferramenta útil de se ter por perto sempre que houver um problema. Se você nunca tiver usado truss antes, faça uma experiência agora digitando este comando em um dos shells:
$ truss date |
Você verá um rastreio formatado de todas as chamadas do sistema executadas por date(1) seguido pelo resultado de uma linha no final. O exemplo a seguir apresenta uma melhoria em relação ao programa rw.d anterior formatando seu resultado para ficar mais parecido com truss(1) para que você possa compreender o resultado com mais facilidade. Digite o seguinte programa e salve-o em um arquivo chamado trussrw.d :
syscall::read:entry, syscall::write:entry /pid == $1/ { printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2); } syscall::read:return, syscall::write:return /pid == $1/ { printf("\t\t = %d\n", arg1); }
Neste exemplo, a constante 12345 é substituída pelo rótulo $1 em cada predicado. Esse rótulo permite que você especifique o processo de seu interesse como um argumento para o script: $1 é substituído pelo valor do primeiro argumento quando o script é compilado. Para executar trussrw.d, use as opções -q e -s do dtrace, seguidas pelo ID do processo do shell como o argumento final. A opção - q indica que dtrace deve ser silencioso e suprimir a linha do cabeçalho e as colunas CPU e ID mostradas nos exemplos anteriores. Como resultado, você verá somente a saída dos dados que rastreou explicitamente. Digite o seguinte comando (substituindo 12345 pelo ID de um processo do shell) e depois pressione a tecla de retorno algumas vezes no shell especificado:
# dtrace -q -s trussrw.d 12345 = 1 write(2, 0x8089e48, 1) = 1 read(63, 0x8090a38, 1024) = 0 read(63, 0x8090a38, 1024) = 0 write(2, 0x8089e48, 52) = 52 read(0, 0x8089878, 1) = 1 write(2, 0x8089e48, 1) = 1 read(63, 0x8090a38, 1024) = 0 read(63, 0x8090a38, 1024) = 0 write(2, 0x8089e48, 52) = 52 read(0, 0x8089878, 1) = 1 write(2, 0x8089e48, 1) = 1 read(63, 0x8090a38, 1024) = 0 read(63, 0x8090a38, 1024) = 0 write(2, 0x8089e48, 52) = 52 read(0, 0x8089878, 1)^C # |
Agora, vamos examinar o programa em D e a sua saída mais detalhadamente. Primeiro, uma cláusula similar ao programa anterior instrumenta cada uma das chamadas do shell para read(2) e write(2). Mas, para este exemplo, uma nova função, printf(), é usada para rastrear dados e imprimi-los em um formato específico:
syscall::read:entry, syscall::write:entry /pid == $1/ { printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2); }
A função printf() combina a capacidade de rastrear dados, como se fosse pela função trace() usada anteriormente, com a capacidade de retornar os dados e outro texto em um formato específico descrito por você. A função printf() informa ao DTrace para rastrear os dados associados a cada argumento após o primeiro argumento e depois para formatar os resultados usando as regras descritas pelo primeiro argumento de printf (), conhecido como seqüência de formato.
A seqüência de formato é uma seqüência regular que contém inúmeras conversões de formato, cada uma começando com o caractere %, que descreve como formatar o argumento correspondente. A primeira conversão na seqüência de formato corresponde ao segundo argumento printf(), a segunda conversão ao terceiro argumento, e assim por diante. Todo o texto entre conversões é impresso textualmente. O caractere que segue o caractere de conversão % descreve o formato a ser usado para o argumento correspondente. Veja a seguir os significados das três conversões de formato usadas em trussrw.d:
%d |
Imprimir o valor correspondente como inteiro decimal |
%s |
Imprimir o valor correspondente como uma seqüência |
%x |
Imprimir o valor correspondente como um inteiro hexadecimal |
A função printf() do DTrace funciona como a rotina de biblioteca de C printf(3C) ou o recurso printf(1) do shell. Se você nunca tiver visto a função printf() antes, os formatos e as opções são explicados em detalhes no Capítulo 12Formatação de saída. Você deve ler esse capítulo atentamente mesmo que já esteja familiarizado com a printf() de outra linguagem. Em D, printf() é interna e algumas conversões de formato novas específicas para o DTrace estão disponíveis.
Para ajudá-lo a escrever programas corretos, o compilador de D valida cada seqüência de formato de printf() em comparação com a lista de argumentos. Tente alterar probefunc na cláusula acima para o inteiro 123. Se você executar o programa modificado, verá uma mensagem de erro informando que a conversão de formato de seqüência %s não é apropriada para uso com um argumento de inteiro:
# dtrace -q -s trussrw.d dtrace: failed to compile script trussrw.d: line 4: printf( ) argument #2 is incompatible with conversion #1 prototype: conversion: %s prototype: char [] or string (or use stringof) argument: int # |
Para imprimir o nome da chamada do sistema read ou write e seus argumentos, use a instrução printf():
printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2);
para rastrear o nome da função atual do teste e os primeiros três argumentos de inteiro para a chamada do sistema, disponíveis nas variáveis arg0, arg1 e arg2 do DTrace. Para obter mais informações sobre os argumentos do teste, consulte o Capítulo 3Variáveis. O primeiro argumento para read(2) e write(2) é um descritor de arquivo, impresso em decimal. O segundo argumento é um endereço de buffer, formatado como um valor hexadecimal. O argumento final é o tamanho do buffer, formatado como um valor decimal. O especificador de formato %4d é usado para o terceiro argumento a fim de indicar que o valor deve ser impresso usando-se a conversão de formato %d com uma largura de campo mínima de 4 caracteres. Se o inteiro tiver menos que 4 caracteres, printf() irá inserir espaços em branco extras para alinhar o resultado.
Para imprimir o resultado da chamada do sistema e completar cada linha do resultado, use a seguinte cláusula:
syscall::read:return, syscall::write:return /pid == $1/ { printf("\t\t = %d\n", arg1); }
Observe que o provedor syscall também publica um teste chamado return para cada chamada do sistema além de entry. A variável arg1 do DTrace para os testes return de syscall tem o valor de return da chamada do sistema. O valor de return é formatado como um inteiro decimal. As seqüências de caractere que começam com barras invertidas na seqüência de formato se expandem para tabulação (\t) e nova linha (\n) respectivamente. Essas seqüências de escape facilitam a impressão ou o registro de caracteres difíceis de digitar. D oferece suporte ao mesmo conjunto de seqüências de escape que as linguagens de programação C, C++ e Java. A lista completa de seqüências de escape pode ser encontrada no Capítulo 2Tipos, operadores e expressões.