Este capítulo descreve o provedor Function Boundary Tracing ( FBT), que fornece testes associados à entrada e ao retorno da maioria das funções no kernel do Solaris. A função é a unidade fundamental de texto do programa. Em um sistema bem projetado, cada função realiza uma operação discreta e bem definida em um objeto especificado ou uma série de objetos semelhantes. Portanto, inclusive nos menores sistemas Solaris, o FBT fornecerá na ordem de 20.000 testes.
Semelhante a outros provedores do DTrace, o FBT não tem efeito de teste quando não é ativado explicitamente. Quando ativado, o FBT apenas induz um efeito de teste em funções testadas. Embora a implementação do FBT seja altamente específica da arquitetura do conjunto de instruções, o FBT foi implementado em plataformas SPARC e x86. Para cada conjunto de instruções, existe um pequeno número de funções que não chamam outras funções, e são altamente otimizadas pelo compilador (chamadas de funções em folha) que não podem ser instrumentadas pelo FBT. Testes destas funções não estão presentes no DTrace.
O uso efetivo de testes do FBT requer o conhecimento da implementação do sistema operacional. Portanto, recomenda-se o uso do FBT somente quando você estiver desenvolvendo o software do kernel ou quanto outros provedores não são suficientes. Outros provedores do DTrace, incluindo syscall, sched, proc e io, podem ser usados para responder à maioria das questões de análise do sistema sem requerer o conhecimento da implementação do sistema operacional.
FBT fornece um teste no limite da maioria das funções do kernel. O limite de uma função é ultrapassado ao entrar na função e retornar da função. O FBT, portanto, fornece duas funções para cada função no kernel: uma na entrada para a função e uma no retorno da função. Estes testes são chamados de entry e return, respectivamente. O nome da função e o nome do módulo são especificados como parte do teste. Todos os testes do FBT especificam um nome de função e um nome de módulo.
Os argumentos de testes entry são os mesmos argumentos da função do kernel do sistema operacional correspondente. Estes argumentos podem ser acessados em forma de digitação, usando a matriz args[]. Estes argumentos podem ser acessados como int64_t usando o arg0 .. variáveis argn.
Enquanto uma determinada função possui somente um ponto de entrada, ela pode ter muitos pontos diferentes onde ela retorna para o seu chamador. Você está geralmente interessado no valor que uma função retornou ou no fato de a função ter retornado em vez do caminho de retorno específico que foi tomado. O FBT , portanto, coleta os vários locais de retorno de uma função em um único teste return. Se o caminho de retorno exato for do seu interesse, examine o valor args[0] do teste return, que indica o deslocamento (em bytes) da instrução de retorno no texto da função.
Se a função tiver um valor de retorno, este valor será armazenado em args[1]. Se uma função não tiver um valor de retorno, args[1] não é definida.
Você pode usar o FBT para explorar facilmente a implementação do kernel. O script de exemplo seguinte registra o primeiro ioctl(2) de qualquer processo xclock e, em seguida, segue o caminho de código subseqüente através do kernel:
/* * To make the output more readable, we want to indent every function entry * (and unindent every function return). This is done by setting the * "flowindent" option. */ #pragma D option flowindent syscall::ioctl:entry /execname == "xclock" && guard++ == 0/ { self->traceme = 1; printf("fd: %d", arg0); } fbt::: /self->traceme/ {} syscall::ioctl:return /self->traceme/ { self->traceme = 0; exit(0); }
Executar este script acima resultará numa saída semelhante ao exemplo seguinte:
# dtrace -s ./xioctl.d dtrace: script './xioctl.d' matched 26254 probes CPU FUNCTION 0 => ioctl fd: 3 0 -> ioctl 0 -> getf 0 -> set_active_fd 0 <- set_active_fd 0 <- getf 0 -> fop_ioctl 0 -> sock_ioctl 0 -> strioctl 0 -> job_control_type 0 <- job_control_type 0 -> strcopyout 0 -> copyout 0 <- copyout 0 <- strcopyout 0 <- strioctl 0 <- sock_ioctl 0 <- fop_ioctl 0 -> releasef 0 -> clear_active_fd 0 <- clear_active_fd 0 -> cv_broadcast 0 <- cv_broadcast 0 <- releasef 0 <- ioctl 0 <= ioctl |
A saída mostra que um processo xclock chamou ioctl() em um descritor de arquivo que parece estar associado a um soquete.
Você também pode usar o FBT ao tentar entender os drivers do kernel. Por exemplo, o driver ssd(7D) possui muitos caminhos de código, pelos quais EIO pode ser retornado. O FBT pode facilmente ser usado para determinar o caminho de código exato que resultou em uma condição de erro, como mostrado no exemplo seguinte:
fbt:ssd::return /arg1 == EIO/ { printf("%s+%x returned EIO.", probefunc, arg0); }
Para obter mais informações sobre qualquer retorno de EIO, talvez você queira rastrear especulativamente todos os testes de fbt e usar commit()(ou discard()) com base no valor de retorno de uma função específica. Consulte o Capítulo 13Rastreio especulativo para obter mais detalhes sobre rastreios especulativos.
Alternativamente, você pode usar o FBT para entender as funções chamadas em um módulo especificado. O exemplo seguinte lista todas as funções chamadas em UFS:
# dtrace -n fbt:ufs::entry'{@a[probefunc] = count()}' dtrace: description 'fbt:ufs::entry' matched 353 probes ^C ufs_ioctl 1 ufs_statvfs 1 ufs_readlink 1 ufs_trans_touch 1 wrip 1 ufs_dirlook 1 bmap_write 1 ufs_fsync 1 ufs_iget 1 ufs_trans_push_inode 1 ufs_putpages 1 ufs_putpage 1 ufs_syncip 1 ufs_write 1 ufs_trans_write_resv 1 ufs_log_amt 1 ufs_getpage_miss 1 ufs_trans_syncip 1 getinoquota 1 ufs_inode_cache_constructor 1 ufs_alloc_inode 1 ufs_iget_alloced 1 ufs_iget_internal 2 ufs_reset_vnode 2 ufs_notclean 2 ufs_iupdat 2 blkatoff 3 ufs_close 5 ufs_open 5 ufs_access 6 ufs_map 8 ufs_seek 11 ufs_addmap 15 rdip 15 ufs_read 15 ufs_rwunlock 16 ufs_rwlock 16 ufs_delmap 18 ufs_getattr 19 ufs_getpage_ra 24 bmap_read 25 findextent 25 ufs_lockfs_begin 27 ufs_lookup 46 ufs_iaccess 51 ufs_imark 92 ufs_lockfs_begin_getpage 102 bmap_has_holes 102 ufs_getpage 102 ufs_itimes_nolock 107 ufs_lockfs_end 125 dirmangled 498 dirbadname 498 |
Se você souber a finalidade ou os argumentos de uma função do kernel, use o FBT para entender como ou por que a função está sendo chamada. Por exemplo, putnext(9F) considera um ponteiro para uma estrutura queue(9S) como seu primeiro membro. O membro q_qinfo da estrutura queue é um ponteiro para uma estrutura qinit(9S). O membro qi_minfo da estrutura qinit tem um ponteiro para uma estrutura module_info(9S), que contém o nome do módulo em seu membro mi_idname. O exemplo seguinte reúne suas informações usando o teste do FBT em putnext para controlar chamadas putnext(9F) por nome de módulo:
fbt::putnext:entry { @calls[stringof(args[0]->q_qinfo->qi_minfo->mi_idname)] = count(); }
Executar o script acima resultará numa saída semelhante ao exemplo seguinte:
# dtrace -s ./putnext.d ^C iprb 1 rpcmod 1 pfmod 1 timod 2 vpnmod 2 pts 40 conskbd 42 kb8042 42 tl 58 arp 108 tcp 126 ptm 249 ip 313 ptem 340 vuid2ps2 361 ttcompat 412 ldterm 413 udp 569 strwhead 624 mouse8042 726 |
Você também pode usar o FBT para determinar o tempo gasto em uma função específica. O exemplo seguinte mostra como determinar os chamadores das rotinas de atraso DDI drv_usecwait(9F) e delay(9F).
fbt::delay:entry, fbt::drv_usecwait:entry { self->in = timestamp } fbt::delay:return, fbt::drv_usecwait:return /self->in/ { @snoozers[stack()] = quantize(timestamp - self->in); self->in = 0; }
Este script de exemplo é particularmente interessante de ser executado durante a reinicialização. O Capítulo 36Rastreio anônimo descreve o procedimento para realizar o rastreio anônimo durante a inicialização do sistema. Durante a reinicialização, você talvez veja uma saída semelhante ao exemplo seguinte:
# dtrace -ae ata`ata_wait+0x34 ata`ata_id_common+0xf5 ata`ata_disk_id+0x20 ata`ata_drive_type+0x9a ata`ata_init_drive+0xa2 ata`ata_attach+0x50 genunix`devi_attach+0x75 genunix`attach_node+0xb2 genunix`i_ndi_config_node+0x97 genunix`i_ddi_attachchild+0x4b genunix`devi_attach_node+0x3d genunix`devi_config_one+0x1d0 genunix`ndi_devi_config_one+0xb0 devfs`dv_find+0x125 devfs`devfs_lookup+0x40 genunix`fop_lookup+0x21 genunix`lookuppnvp+0x236 genunix`lookuppnat+0xe7 genunix`lookupnameat+0x87 genunix`cstatat_getvp+0x134 value ------------- Distribution ------------- count 2048 | 0 4096 |@@@@@@@@@@@@@@@@@@@@@ 4105 8192 |@@@@ 783 16384 |@@@@@@@@@@@@@@ 2793 32768 | 16 65536 | 0 kb8042`kb8042_wait_poweron+0x29 kb8042`kb8042_init+0x22 kb8042`kb8042_attach+0xd6 genunix`devi_attach+0x75 genunix`attach_node+0xb2 genunix`i_ndi_config_node+0x97 genunix`i_ddi_attachchild+0x4b genunix`devi_attach_node+0x3d genunix`devi_config_one+0x1d0 genunix`ndi_devi_config_one+0xb0 genunix`resolve_pathname+0xa5 genunix`ddi_pathname_to_dev_t+0x16 consconfig_dacf`consconfig_load_drivers+0x14 consconfig_dacf`dynamic_console_config+0x6c consconfig`consconfig+0x8 unix`stubs_common_code+0x3b value ------------- Distribution ------------- count 262144 | 0 524288 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 221 1048576 |@@@@ 29 2097152 | 0 usba`hubd_enable_all_port_power+0xed usba`hubd_check_ports+0x8e usba`usba_hubdi_attach+0x275 usba`usba_hubdi_bind_root_hub+0x168 uhci`uhci_attach+0x191 genunix`devi_attach+0x75 genunix`attach_node+0xb2 genunix`i_ndi_config_node+0x97 genunix`i_ddi_attachchild+0x4b genunix`i_ddi_attach_node_hierarchy+0x49 genunix`attach_driver_nodes+0x49 genunix`ddi_hold_installed_driver+0xe3 genunix`attach_drivers+0x28 value ------------- Distribution ------------- count 33554432 | 0 67108864 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3 134217728 | 0 |
Quando uma função termina ao chamar outra função, o compilador pode se envolver na otimização de chamada de laço, na qual a função que está sendo chamada reutiliza o quadro de pilha do chamador. Este procedimento é mais usado na arquitetura SPARC, onde o compilador reutiliza a janela do registro do chamador na função que está sendo chamada para minimizar a pressão da janela do registro.
A presença desta otimização faz com que o teste return da função que está chamando seja acionado antes do teste entry da função chamada. Esta ordem pode causar um pouco de confusão. Por exemplo, se você quisesse registrar todas as funções de uma função específica e quaisquer funções que esta função chama, teria que usar o script seguinte:
fbt::foo:entry { self->traceme = 1; } fbt:::entry /self->traceme/ { printf("called %s", probefunc); } fbt::foo:return /self->traceme/ { self->traceme = 0; }
Entretanto, se foo() terminar em uma chamada de laço otimizada, a função de chamada de laço, e portanto quaisquer funções que ela chama, não seriam capturadas. O kernel não pode ser dinamicamente desotimizado durante o uso, e o DTrace não quer entrar em uma situação falsa sobre como o código é reestruturado. Portanto, você deve estar ciente de quando a otimização de chamada de laço deve ser usada.
A otimização de chamada de laço deve ser usada em um código-fonte semelhante ao exemplo seguinte:
return (bar());
Ou em um código-fonte semelhante ao exemplo seguinte:
(void) bar(); return;
Contrariamente, o código-fonte da função que termina como o exemplo seguinte não pode ter a sua chamada à bar() otimizada, pois a chamada à bar() não é uma chamada de laço:
bar(); return (rval);
Você pode determinar se uma chamada sofreu otimização de chamada de laço usando a técnica seguinte:
Ao executar o DTrace, rastreie arg0 do teste return em questão. arg0 contém o deslocamento da instrução de retorno na função.
Depois que o DTrace tiver parado, use mdb(1) para examinar a função. Se o deslocamento rastreado contiver uma chamava para outra função em vez de uma instrução para retornar da função, a chamada sofreu otimização de chamada de laço.
Devido à arquitetura do conjunto de instruções, a otimização de chamada de laço é muito mais comum em sistemas SPARC do que em sistemas x86. O exemplo seguinte usa mdb para descobrir a otimização de chamada de laço na função dup() do kernel:
# dtrace -q -n fbt::dup:return'{printf("%s+0x%x", probefunc, arg0);}' |
Enquanto este comando está sendo executado, execute um programa que realize um dup(2), tal como um processo bash. O comando acima deve fornecer uma saída semelhante ao exemplo seguinte:
dup+0x10 ^C |
Agora examine a função com mdb:
# echo "dup::dis" | mdb -k dup: sra %o0, 0, %o0 dup+4: mov %o7, %g1 dup+8: clr %o2 dup+0xc: clr %o1 dup+0x10: call -0x1278 <fcntl> dup+0x14: mov %g1, %o7 |
A saída mostra que dup+0x10 é uma chamada para a função fcntl() e não uma instrução ret. Portanto, a chamada à fcntl() é um exemplo de otimização de chamada de laço.
Você talvez observe funções que parecem entrar mas nunca retornar ou vice-versa. Funções tão raras são geralmente rotinas de composição codificadas à mão que se ramificam para o meio das funções de composição codificadas à mão. Estas funções não devem impedir a análise: a função ramificada-para ainda devem retornar para o chamador da função ramificada-de. Ou seja, se você ativar todos os testes de FBT, verá a entrada para uma função e o retorno de outra função na mesma profundidade da pilha.
Algumas funções não podem ser instrumentadas pelo FBT. A natureza exata das funções não-instrumentáveis é específica da arquitetura do conjunto de instruções.
As funções que não criam um quadro de pilha em sistemas x86 não podem ser instrumentadas por FBT. Como o conjunto de registros de x86 é extraordinariamente pequeno, muitas funções devem colocar dados na pilha e, portanto, criar um quadro de pilha. Entretanto, algumas funções de x86 não criam um quadro de pilha e, portanto, não podem ser instrumentadas. Os números reais variam, mas geralmente menos de 5% das funções não podem ser instrumentadas na plataforma x86.
As rotinas em folha codificadas à mão em linguagem de composição em sistemas SPARC não podem ser instrumentadas por FBT. A maior parte do kernel é escrita em C, e todas as funções escritas em C podem ser instrumentadas por FBT.
FBT funciona modificando dinamicamente o texto do kernel. Como os pontos de interrupção do kernel também funcionam modificando o texto do kernel, se um ponto de interrupção do kernel for colocado em um local de entrada ou de retorno antes de carregar o DTrace, o FBT se recusará a fornecer um teste da função, mesmo se o ponto de interrupção do kernel for subseqüentemente removido. Se o ponto de interrupção do kernel for colocado depois de carregar o DTrace, o ponto de interrupção do kernel e o teste do DTrace corresponderão ao mesmo ponto no texto. Nesta situação, o ponto de interrupção disparará primeiro e, em seguida, o teste será acionado quando o depurador continuar o kernel. É recomendado que os pontos de interrupção do kernel não sejam usados ao mesmo tempo que o DTrace. Se os pontos de interrupção forem necessários, use a ação breakpoint () do DTrace.
O kernel do Solaris pode carregar e descarregar dinamicamente os módulos do kernel. Quando o FBT é carregado e um módulo é dinamicamente carregado, o FBT fornece automaticamente novos testes associados ao novo módulo. Se um módulo carregado possuir testes FBT não-ativados, o módulo pode ser descarregado; os testes correspondentes serão destruídos quando o módulo for descarregado. Se um módulo carregado tiver testes FBT ativados, o módulo será considerado ocupado, e não poderão ser descarregados.
O provedor FBT usa o mecanismo de estabilidade do DTrace para descrever suas estabilidades, conforme mostrado na tabela seguinte. Para obter mais informações sobre o mecanismo de estabilidade, consulte o Capítulo 39Estabilidade.
Elemento |
Estabilidade de nome |
Estabilidade de dados |
Classe de dependência |
---|---|---|---|
Provedor |
Desenvolvendo |
Desenvolvendo |
ISA |
Módulo |
Privada |
Privada |
Desconhecida |
Função |
Privada |
Privada |
Desconhecida |
Nome |
Desenvolvendo |
Desenvolvendo |
ISA |
Argumentos |
Privada |
Privada |
ISA |
Como o FBT expõe a implementação do kernel, nada sobre seu nome Estável — e do Módulo e da Função e a estabilidade dos dados é explicitamente Privado. A estabilidade dos dados de Provedor e Nome são Desenvolvendo, mas todas as outras estabilidades de dados são Privadas: elas são artefatos da implementação atual. A classe de dependência de FBT é ISA: embora o FBT esteja disponível em todas as arquiteturas do conjunto de instruções atual, não há garantia de que o FBT esteja disponível em futuras arquiteturas de conjunto de instruções arbitrárias.