Compreender chamadas de sistema no Linux com strace

A tradução do artigo foi preparada especialmente para alunos dos cursos básico e avançado de Administrador Linux.






O syscall é o mecanismo pelo qual os programas do usuário interagem com o kernel do Linux, e o strace é uma ferramenta poderosa para controlá-los. Para entender melhor como o sistema operacional funciona, é útil entender como eles funcionam.



O sistema operacional pode ser dividido em dois modos de operação:



  • O modo kernel é o modo privilegiado usado pelo kernel do sistema operacional.
  • O modo de usuário é o modo no qual a maioria dos aplicativos de usuário é executada.




Os usuários geralmente usam utilitários de linha de comando e uma interface gráfica (GUI) para seu trabalho diário. Ao mesmo tempo, as chamadas de sistema funcionam de forma invisível em segundo plano, referindo-se ao kernel para fazer o trabalho.



As chamadas do sistema são muito semelhantes às chamadas de função no sentido de que os argumentos são passados ​​e retornam valores. A única diferença é que as chamadas do sistema funcionam no nível do kernel, mas as funções não. A mudança do modo de usuário para o modo kernel é feita usando um mecanismo de interrupção especial .



A maioria desses detalhes são ocultados do usuário nas bibliotecas do sistema (glibc em sistemas Linux). As chamadas do sistema são genéricas por natureza, mas, apesar disso, a mecânica de sua execução depende em grande parte do hardware.



Este artigo explora vários exemplos práticos de análise de chamadas do sistema usando strace. Os exemplos usam Red Hat Enterprise Linux, mas todos os comandos devem funcionar em outras distribuições Linux:



[root@sandbox ~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.7 (Maipo)
[root@sandbox ~]#
[root@sandbox ~]# uname -r
3.10.0-1062.el7.x86_64
[root@sandbox ~]#




Primeiro, certifique-se de ter as ferramentas necessárias instaladas em seu sistema. Você stracepode verificar se ele está instalado usando o comando abaixo. Para visualizar a versão, straceexecute-o com o parâmetro -V:



[root@sandbox ~]# rpm -qa | grep -i strace
strace-4.12-9.el7.x86_64
[root@sandbox ~]#
[root@sandbox ~]# strace -V
strace -- version 4.12
[root@sandbox ~]#




Se stracenão estiver instalado, instale executando:



yum install strace




Por exemplo, crie um diretório de teste em /tmpe dois arquivos usando o comando touch:



[root@sandbox ~]# cd /tmp/
[root@sandbox tmp]#
[root@sandbox tmp]# mkdir testdir
[root@sandbox tmp]#
[root@sandbox tmp]# touch testdir/file1
[root@sandbox tmp]# touch testdir/file2
[root@sandbox tmp]#




(Eu /tmpuso um diretório porque todos têm acesso a ele, mas você pode usar qualquer outro diretório .)



Use o comando para lsverificar se os testdirarquivos foram criados no diretório :



[root@sandbox tmp]# ls testdir/
file1  file2
[root@sandbox tmp]#




Você provavelmente usa o comando lstodos os dias sem perceber que as chamadas do sistema estão sendo executadas nos bastidores. É aqui que a abstração entra em jogo. É assim que funciona este comando:



   ->    (glibc) ->  




O comando lschama funções das bibliotecas do sistema Linux (glibc). Essas bibliotecas, por sua vez, chamam chamadas de sistema, que fazem a maior parte do trabalho.



Se você quiser saber quais funções foram chamadas da biblioteca glibc, use o comando ltraceseguido pelo comando ls testdir/:



ltrace ls testdir/




Se ltracenão estiver instalado, instale:



yum install ltrace




Haverá muitas informações na tela, mas não se preocupe - falaremos disso mais tarde. Aqui estão algumas das funções de biblioteca importantes da saída ltrace:



opendir("testdir/")                                  = { 3 }
readdir({ 3 })                                       = { 101879119, "." }
readdir({ 3 })                                       = { 134, ".." }
readdir({ 3 })                                       = { 101879120, "file1" }
strlen("file1")                                      = 5
memcpy(0x1665be0, "file1\0", 6)                      = 0x1665be0
readdir({ 3 })                                       = { 101879122, "file2" }
strlen("file2")                                      = 5
memcpy(0x166dcb0, "file2\0", 6)                      = 0x166dcb0
readdir({ 3 })                                       = nil
closedir({ 3 })    




Examinando essa saída, você provavelmente pode entender o que está acontecendo. O diretório nomeado é testdiraberto usando uma função de biblioteca opendir, seguida por chamadas para funções readdirque leem o conteúdo do diretório. Finalmente, é chamada uma função closedirque fecha o diretório aberto anteriormente. Por enquanto, ignore outras funções, como strlene memcpy.



Como você pode ver, é fácil ver as funções da biblioteca chamadas, mas neste artigo vamos nos concentrar nas chamadas do sistema que são chamadas pelas funções da biblioteca do sistema.



Para visualizar as chamadas do sistema, use straceo comando ls testdirconforme mostrado abaixo. E novamente você obtém um monte de informações incoerentes:



[root@sandbox tmp]# strace ls testdir/
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
brk(NULL)                               = 0x1f12000
<<< truncated strace output >>>
write(1, "file1  file2\n", 13file1  file2
)          = 13
close(1)                                = 0
munmap(0x7fd002c8d000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
[root@sandbox tmp]#


Como resultado da execução, stracevocê receberá uma lista de chamadas de sistema executadas durante a execução do comando ls. Todas as chamadas do sistema podem ser divididas nas seguintes categorias:



  • Gerenciamento de processos
  • Gerenciamento de arquivos
  • Gerenciamento de diretório e sistema de arquivos
  • De outros




Há uma maneira conveniente de analisar as informações recebidas - grave a saída em um arquivo usando a opção -o.



[root@sandbox tmp]# strace -o trace.log ls testdir/
file1  file2
[root@sandbox tmp]#




Desta vez, não haverá dados na tela - o comando lsfuncionará conforme o esperado, exibindo uma lista de arquivos e gravando todas as saídas straceem um arquivo trace.log. Para um comando simples, o lsarquivo contém quase 100 linhas:



[root@sandbox tmp]# ls -l trace.log
-rw-r--r--. 1 root root 7809 Oct 12 13:52 trace.log
[root@sandbox tmp]#
[root@sandbox tmp]# wc -l trace.log
114 trace.log
[root@sandbox tmp]#




Dê uma olhada na primeira linha do arquivo trace.log:



execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0




  • No início da linha está o nome da chamada do sistema sendo executada - execve.
  • O texto entre parênteses são os argumentos passados ​​para a chamada do sistema.
  • O número após o sinal = (neste caso, 0) é o valor retornado pela chamada do sistema.




Agora, o resultado não parece muito assustador, não é? E você pode aplicar a mesma lógica para outras linhas também.



Preste atenção ao único comando que você chamou - ls testdir. Você sabe o nome do diretório usado pelo comando ls, então por que não usar greppara testdirno arquivo trace.loge ver o que ele encontra? Observe o resultado com atenção:



[root@sandbox tmp]# grep testdir trace.log
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[root@sandbox tmp]#




Voltando à análise acima execve, você pode dizer o que a próxima chamada do sistema está fazendo?



execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0




Você não precisa se lembrar de todas as chamadas de sistema e o que elas fazem: tudo está na documentação. As páginas de manual estão com pressa em ajudar! Certifique-se de que o pacote esteja instalado antes de executar o comando man man-pages:



[root@sandbox tmp]# rpm -qa | grep -i man-pages
man-pages-3.53-5.el7.noarch
[root@sandbox tmp]#




Lembre-se de que você precisa adicionar "2" entre o comando mane o nome do syscall. Se você ler mansobre man( man man), verá que a seção 2 é reservada para chamadas do sistema. Da mesma forma, se precisar de informações sobre as funções da biblioteca, você precisará adicionar 3 entre mane o nome da função da biblioteca.



Abaixo estão os números das seções man:



1.       .
2.   (,  ).
3.   (  ).
4.   (    /dev).




Para visualizar a documentação de uma chamada de sistema, execute man com o nome dessa chamada de sistema.



man 2 execve




De acordo com a documentação, uma chamada de sistema execveexecuta um programa que é passado a ela em parâmetros (neste caso é ls). Parâmetros adicionais para ls também são passados ​​para ele. Neste exemplo, é testdir. Assim, esta chamada de sistema simplesmente funciona lscom testdircomo um parâmetro:



'execve - execute program'

'DESCRIPTION
       execve()  executes  the  program  pointed to by filename'




A próxima chamada do sistema statrecebe um parâmetro testdir:



stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0




Para ver o uso da documentação man 2 stat. A chamada do sistema stat retorna informações sobre o arquivo especificado. Lembre-se de que tudo no Linux é um arquivo, incluindo diretórios.



Em seguida, a chamada do sistema é openataberta testdir. Observe que o valor de retorno é 3. Este é o descritor de arquivo que será usado em chamadas de sistema subsequentes:



openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3




Agora abra o arquivo
trace.log
e observe a linha após a chamada do sistema openat. Você verá uma chamada de sistema getdentsque faz a maior parte do trabalho necessário para executar o comando ls testdir. Agora vamos executar grep getdentspara o arquivo trace.log:



[root@sandbox tmp]# grep getdents trace.log
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
[root@sandbox tmp]#




A documentação ( man getdents) diz que getdentslê as entradas do diretório, que é o que realmente precisamos. Observe que o argumento para getdent3 é o descritor de arquivo obtido anteriormente na chamada do sistema openat.



Agora que o conteúdo do diretório foi recebido, precisamos encontrar uma maneira de exibir as informações no terminal. Então, fazemos greppara outra chamada de sistema write, que é usada para enviar para o terminal:



[root@sandbox tmp]# grep write trace.log
write(1, "file1  file2\n", 13)          = 13
[root@sandbox tmp]#




Nos argumentos, você pode ver os nomes dos arquivos a serem produzidos: file1e file2. Para o primeiro argumento (1), lembre-se de que no Linux, três descritores de arquivo são abertos por padrão para qualquer processo:



  • 0 - fluxo de entrada padrão
  • 1 - fluxo de saída padrão
  • 2 - fluxo de erro padrão




Assim, a chamada do sistema writerecebe file1e file2a saída padrão, que é um terminal, denota o número 1.



Agora que você sabe o que as chamadas do sistema fazem a maior parte do trabalho para a equipe ls testdir/. Mas e quanto às outras 100+ chamadas de sistema no arquivo trace.log?



O sistema operacional faz várias coisas de suporte para iniciar um processo, portanto, muito do que você vê no arquivo trace.logé a inicialização e limpeza do processo. Dê uma olhada completa no arquivo trace.log e tente entender o que acontece quando o comando é executado ls.



Agora você pode analisar chamadas de sistema para qualquer programa. O utilitário strace também fornece muitas opções de linha de comando úteis, algumas das quais são descritas a seguir.



Por padrão strace, ele não exibe todas as informações sobre as chamadas do sistema. No entanto, tem uma opção -v verboseque mostrará informações adicionais sobre cada chamada do sistema:



strace -v ls testdir




É uma boa prática usar um parâmetro -fpara controlar os processos filhos criados por um processo em execução:



strace -f ls testdir




Mas e se você quiser apenas os nomes das chamadas do sistema, o número de vezes que foram executadas e a porcentagem de tempo gasto na execução? Você pode usar a opção -cpara obter estas estatísticas:



strace -c ls testdir/




Se quiser rastrear uma chamada de sistema específica, por exemplo, opene ignorar outras, você pode usar a opção -ecom o nome da chamada de sistema:



[root@sandbox tmp]# strace -e open ls testdir
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
file1  file2
+++ exited with 0 +++
[root@sandbox tmp]#




E se você precisar filtrar por várias chamadas de sistema? Não se preocupe, você pode usar a mesma opção -ee separar as chamadas de sistema necessárias com uma vírgula. Por exemplo, para writee getdent:



[root@sandbox tmp]# strace -e write,getdents ls testdir
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
write(1, "file1  file2\n", 13file1  file2
)          = 13
+++ exited with 0 +++
[root@sandbox tmp]#




Até agora, rastreamos apenas execuções de comandos explícitos. Mas e quanto aos comandos que foram executados anteriormente? E se você quiser rastrear demônios? Para isso, você stracetem uma opção especial -ppara a qual pode passar o ID do processo.



Não iniciaremos o daemon, mas usaremos um comando catque exibe o conteúdo do arquivo passado a ele como um argumento. Mas se você não especificar um argumento, o comando catapenas aguardará a entrada do usuário. Depois de inserir o texto, ele exibirá o texto inserido na tela. E assim por diante até que o usuário clique Ctrl+Cpara sair.



Execute o comando catem um terminal.



[root@sandbox tmp]# cat




Em outro terminal, encontre o ID do processo (PID) com o comando ps:



[root@sandbox ~]# ps -ef | grep cat
root      22443  20164  0 14:19 pts/0    00:00:00 cat
root      22482  20300  0 14:20 pts/1    00:00:00 grep --color=auto cat
[root@sandbox ~]#




Agora comece stracecom a opção -pe o PID que você encontrou ps. Depois de inicializado, straceexibirá informações sobre o processo ao qual se conectou, bem como seu PID. Agora stracemonitora as chamadas do sistema feitas pelo comando cat. A primeira chamada do sistema que você verá é lida, aguardando a entrada do thread 0, ou seja, da entrada padrão, que agora é o terminal em que o comando está sendo executado cat:



[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0,




Agora volte para o terminal onde você deixou o comando em execução cate digite algum texto. Para demonstração, entrei x0x0. Observe que eu catsimplesmente repeti o que inseri e x0x0a tela aparecerá duas vezes.



[root@sandbox tmp]# cat
x0x0
x0x0




Volte para o terminal onde você se straceconectou ao processo cat. Agora você vê duas novas chamadas de sistema: a anterior read, que agora foi lida x0x0, e mais uma para gravação write, que grava de x0x0volta no terminal, e novamente uma nova read, que está aguardando uma leitura do terminal. Observe que a entrada padrão (0) e a saída padrão (1) estão no mesmo terminal:



[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0, "x0x0\n", 65536)                = 5
write(1, "x0x0\n", 5)                   = 5
read(0,




Imagine os benefícios de lançar stracepara daemons: você pode ver tudo o que está acontecendo em segundo plano. Complete o comando
cat
Clicando
Ctrl+C
... Isso também encerrará a sessão
strace
uma vez que o processo monitorado foi encerrado.



Para ver os carimbos de data / hora das chamadas do sistema, use a opção -t:



[root@sandbox ~]#strace -t ls testdir/

14:24:47 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
14:24:47 brk(NULL)                      = 0x1f07000
14:24:47 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2530bc8000
14:24:47 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
14:24:47 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3




E se você quiser saber o tempo gasto entre as chamadas do sistema? Existe uma opção útil -rque mostra o tempo necessário para completar cada chamada do sistema. Muito útil, não é?



[root@sandbox ~]#strace -r ls testdir/

0.000000 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
0.000368 brk(NULL)                 = 0x1966000
0.000073 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb6b1155000
0.000047 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
0.000119 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3




Conclusão



O utilitário é stracemuito útil para aprender chamadas de sistema no Linux. Para outras opções de linha de comando, consulte man e a documentação online.






All Articles