Decodificando informações da saída do strace
Quando um programa é executado, ele alterna várias vezes entre o modo de usuário e o modo kernel . No modo de usuário, um processo tem acesso limitado aos recursos, enquanto no modo kernel ele tem acesso aos recursos de hardware privilegiados e seus dados. Um processo alterna do modo de usuário para o modo kernel usando chamadas do sistema .
Strace é uma ferramenta para analisar as atividades de chamada do sistema de um processo. Fornece informações sobre:
- Arquivos acessados.
- Chamadas de sistema usadas durante a execução
- Tempo gasto por cada chamada de sistema para um processo etc.
Analisar chamadas de sistema ajuda muito quando você não tem acesso ao código-fonte e a depuração é feita usando apenas binários executáveis. Este artigo não é sobre como usar a ferramenta Strace , é mais sobre como analisar a saída da ferramenta Strace porque, ao executar o Strace em um processo, ela despeja muitas informações relacionadas às chamadas do sistema. À primeira vista, parece muito assustador e analisar cada chamada de sistema seria uma tarefa muito demorada. Além disso, pode não ser necessário porque a maioria das chamadas de sistema iniciais são para fins de manutenção e não agregam muito valor à depuração. Uma vez que o fluxo de chamadas do sistema é compreendido para um processo, então ele pode ser facilmente identificado e remover chamadas do sistema de manutenção e se concentrar em uma importante para depurar nosso problema real.
Programa:
// C program to print Hello World!
// filename: hello.c
#include <stdio.h>
// Driver Code
int main(int argc, char* argv[])
{
// Print Hello World
printf(" geeksforgeeks: hello world !! \n");
return 0;
}
geeksforgeeks: olá, mundo !!
Compile o programa acima usando o comando abaixo:
$ gcc hello.c
Encontre o Strace fora do programa compilado acima usando o comando abaixo:
$ strace ./a.out
Agora, a saída do Strace para o programa acima é:
Antes de começar a analisar as chamadas do sistema, vamos falar brevemente sobre a execução do programa em chamadas do sistema:
- O programa “Hello World” será aberto e mapas de memória para todas as bibliotecas compartilhadas na memória virtual do processo. A maioria das chamadas do sistema está relacionada a esta atividade.
- Define o acesso correto para as seções de memória.
- E finalmente executa o programa, que irá escrever a mensagem “geeksforgeeks: hello world !!” em stdout do processo.
Saída de decodificação do Strace :
Agora divida a saída do Strace em partes significativas para melhor compreensão:
- execve():
Quando executamos o executável ./a.out no console bash, um novo processo filho é bifurcado e execve() é executado para carregar o novo programa “a.out” . Possui 3 parâmetros:
- O primeiro parâmetro é o nome do executável
- O segundo parâmetro é uma array de argumentos do executável, dos quais o primeiro argumento é o próprio nome do executável. Como nenhum argumento é fornecido ao executável, vemos apenas ./a.out na lista de argumentos.
- O terceiro parâmetro é uma string de variáveis de ambiente.
Aqui, o valor de retorno é 0 , o que significa sucesso. Mais detalhes sobre a chamada de sistema execve() podem ser encontrados na página de manual de execve usando “man 2 execve” .
- brk(): Esta chamada de sistema define o tamanho do segmento de dados para o endereço especificado. Aqui, brk (NULL) é usado para obter o topo do endereço do segmento de dados, que é o endereço inicial do heap. Conseqüentemente, chamar brk() com NULL retorna, endereço de início de heap que mais tarde é usado para alocar memória heap.
- access(): Esta chamada de sistema verifica as permissões de arquivo. Possui 2 parâmetros:
- O primeiro parâmetro é um nome de arquivo para o qual a permissão deve ser verificada.
- O segundo parâmetro é um modo, que especifica a verificação de acessibilidade. Acessibilidade de leitura, gravação e executável são verificadas para um arquivo. Aqui F_OK é para verificação de existência e R_OK é para verificação de leitura.
- Se o valor de retorno for -1 , significa que o arquivo verificado não está presente.
- ld.so.nohwcap: A presença deste arquivo desativa o carregamento de bibliotecas otimizadas. Nas últimas distribuições, este arquivo não está presente. Arquivo ld.so.preload contendo uma lista de arquivos de objetos compartilhados a serem carregados antes do programa.
- openat() abre um arquivo /etc/ld.so.cache e retorna o descritor de arquivos 3 . /etc/ld.so.cache , contém a lista de diretórios nos quais as bibliotecas compartilhadas devem ser pesquisadas.
- fstat() obtém os atributos do arquivo como modos, tamanho, timestamps de criação / modificação, etc. para o mesmo descritor de arquivo. O segundo parâmetro são os detalhes dos atributos lidos.
- mmap() usa o tamanho de arquivo 127481, lido por fstat() e mapeia o arquivo inteiro na memória virtual do processo e retorna o endereço de memória virtual mapeado 0x7ff58cf81000.
- Após o mapeamento bem-sucedido, o arquivo é fechado usando a chamada de sistema close() .
- /etc/ld.so.cache: Contém a lista de diretórios nos quais as bibliotecas compartilhadas devem ser pesquisadas.
É o mesmo que a linha 3 .
O bloco de chamadas de sistema acima é sobre como abrir a biblioteca libc e mapeá-la na memória virtual do processo.
- openat() abre /lib/x86_64-linux-gnu/libc.so.6 e retorna o descritor de arquivos 3 . O descritor de arquivo 3 é usado posteriormente para trabalhar no arquivo libc .
- read() lê 832 bytes do arquivo libc.so. O segundo parâmetro é lido 832 bytes de dados, que são informações de cabeçalho do arquivo ELF e provavelmente usado para verificação do arquivo ELF.
- fstat() obtém os atributos do arquivo libc .
- mmap() mapeia o arquivo na memória virtual.
- mprotect() atualiza a proteção da região da memória.
- close() libera o descritor de arquivo, pois o arquivo é mapeado com sucesso na memória virtual do processo e não é mais necessário acessar por meio de um descritor de arquivo.
- arch_prctl(): define o estado da thread específica da arquitetura. Aqui está configurando a base de 64 bits para o registro FS para o endereço 0x7ff58cf804c0 .
- mprotect(): Chama para definir a proteção para diferentes regiões de memória. PROT_READ é usado para tornar as regiões de memória legíveis.
- munmap(): Esta chamada para desmapear o arquivo /etc/ld.so.cache . O endereço 0x7ff58cf81000 foi mapeado para ld.so.cache na linha 7.
- fstat(): Isso é feito no descritor de arquivo 1 , para obter seus atributos, já que printf() gravará dados usando o descritor stdout. Quando um processo é iniciado, ele abre 3 arquivos padrão:
- Descritor de arquivo 0 para stdin .
- Descritor de arquivo 1 para stdout .
- Descritor de arquivo 2 para stderr .
- brk(): chama para obter e definir o limite da seção de dados.
- Isso corresponde à instrução printf() , que coloca os dados no stdout do processo usando a chamada do sistema write() .
- O programa sai com 0 (SUCESSO) .
Agora ele viu que a maioria das chamadas de sistema eram para preparar o processo para execução. Principalmente o mapeamento da biblioteca compartilhada estava causando a maioria das chamadas do sistema. Ele pode verificar isso executando o Strace em um binário executável estaticamente construído.
Abaixo está a saída do Strace para o programa “Hello World” construído usando a opção “-static” :
Ele pode ver que o executável estaticamente construído não chama open() , mmap() , close() etc, o que foi feito para mapear bibliotecas compartilhadas. Agora temos conhecimento suficiente sobre chamadas de sistema para analisar a saída da ferramenta Strace e para filtrar chamadas de sistema interessadas para depuração. Para qualquer detalhe de chamada do sistema, o melhor lugar seria sua página de manual. Que pode ser acessado usando o comando abaixo.
$ man 2 <System Call>
As postagens do blog Acervo Lima te ajudaram? Nos ajude a manter o blog no ar!
Faça uma doação para manter o blog funcionando.
70% das doações são no valor de R$ 5,00...
Diógenes Lima da Silva