Quero detectar erros na lógica de execução do aplicativo. Por exemplo:
- esqueci de ligar
free()
no endereço retornado pormalloc()
- não fechou o identificador de arquivo retornado por
open()
- sinalizadores inválidos passados para
open()
- identificador de arquivo inválido passado para
poll()
write()
chamado em fd que não foi aberto para escrita- passar sinalizadores inválidos para,
open()
por exemplo, open("/etc/fstab", 4) - chamando
close()
um fd inválido - ...
Acho que tem centenas mais.
Talvez a ferramenta possa ser executada de forma semelhante a ftrace
ou strace
, mas um log do kernel contendo as chamadas defeituosas também seria suficiente.
Bem,
malloc
efree
não são chamadas de kernel! Omalloc()
que (que é uma função de biblioteca libc, código de processo de usuário normal!)sbrk
(ou equivalentemente, mallocação de memória anônima era comum) para solicitar ao kernel uma quantidade de novas páginas de memória virtual, adicione-as ao pool e, em seguida, satisfazer o pedido do programa.free
apenas leva s pedaço de memória retornado anteriormentemalloc
; em caso afirmativo, ele o marca como não utilizado no pool de memória. (Se não, um comportamento indefinido acontece, mas a maioria das libc's abortará nesse ponto.) A maioria das implementações defree
nem mesmo tenta retornar a memória para o sistema operacional!Agora, se você quiser limpeza de memória, existem ferramentas (valgrind
gcc -fsanitize
e mais) que observam essasfree
emalloc
chamadas, e até mesmo rastreiam se o endereço de ummalloc
pedaço de memória ed ainda está "salvo" em algum lugar do programa, ou se, por exemplo, no final de uma função, o ponteiro que contém esse endereço simplesmente deixa de existir, de modo que ninguém pode lembrar que a memória foi alocada. Isso seria uma falha real; apenas não liberar memória imediatamente ou adiar a liberação para o final do programa não é um problema. Todo o ponto demalloc
é que você obtém memória com um tempo de vida potencialmente infinito! (dica: se você se preocupa com esse tipo de coisa, e você está certo, não escreva C. Escreva em uma linguagem que permita que os tempos de vida dos objetos sejam rastreados corretamente. Isso seria linguagens como Rust ou C++ , mas o último apenas se não for ensinado por alguém que pensa em C++ como uma extensão para C . Tenho grandes programas onde nunca useinew
ou pior,malloc
em meu código C++. Ponteiros inteligentes podem levar muitas armadilhas de seus ombros, mesmo em C++, que permite que você faça o controle manual da memória, mas nas variantes modernas também o incentiva a não oferecer rastreamento de vida útil de objeto de custo zero. )Isso não é um problema! Ainda mais do que com memória, é perfeitamente aceitável e até sensato manter os arquivos abertos até o final de um programa; por exemplo, bloqueios em arquivos não funcionariam se você os abandonasse imediatamente. E uma interface de controle precisaria ser mantida aberta até que o programa fosse encerrado.
Novamente, se você está preocupado que dentro do fluxo de controle do seu programa, você pode estar abrindo milhares de arquivos e esquecendo de fechá-los, não escreva em C, mas em uma linguagem onde um identificador de arquivo tem um tempo de vida e pode fechar o descritor de arquivo subjacente quando não for mais necessário.
Apenas: "há um arquivo aberto, ele ainda não foi fechado" simplesmente não é um problema, especialmente em sistemas POSIX, onde o acesso simultâneo a arquivos é normal e em muitos aspectos até bem definido.
Como você sabe disso, além de coisas que retornam um código de erro de qualquer maneira?
Quero dizer, é muito normal para uma biblioteca verificar se um arquivo pode ser aberto no modo "gravar + anexar", mas não é um problema se não puder ser.
Se você quiser observar a qualquer momento que uma chamada do sistema é feita, obter seus argumentos e o que ele "retorna" para a terra do usuário, o
ptrace
syscall é seu amigo, como por exemplo, usado pelostrace
programa popular. Outras opções envolvem a gravação de testes ou uprobes eBPF, que podem ser usados para um registro muito eficiente e até mesmo de "filtragem inteligente" de tais coisas.Mesmo problema de antes, isso pode ser apenas o seu programa verificando se um identificador de arquivo pode ser pesquisado; esse não é o caso de todos os sistemas de arquivos (pseudo).
Além disso,
poll
na verdade também é o nome de uma função wrapper (símbolo) fornecida pelo menos pela glibc, se necessário, e "invalid" para isso pode ser diferente de "invalid" para opoll
syscall.A primeira delas, não liberar um bloco de memória alocado por
malloc()
, não envolve necessariamente o kernel — as alocações de memória são tratadas pela biblioteca C. O Memcheck de Valgrind pode detectá-los.Para rastrear erros retornados pelo kernel, você pode executar seu programa com
strace -Z
(disponível desdestrace
5.2): isso rastreará apenas chamadas de sistema que retornam um erro. Você ainda precisará pós-processar o resultado para procurar, por exemploEBADFD
,EINVAL
etc.Observe que não chamar
free()
ouclose()
não é necessariamente um erro; esses recursos são liberados quando o programa é encerrado de qualquer maneira, portanto, há casos em que é perfeitamente aceitável não liberar recursos explicitamente.Existem várias ferramentas de análise de código estático disponíveis para encontrar erros de codificação comuns como este. Não posso dizer se abrange todos os seus cenários, mas o SonarQube é uma dessas ferramentas com suporte para a linguagem C: https://www.sonarqube.org/features/multi-languages/c
Existem centenas de regras do SonarQube para C: https://rules.sonarsource.com/c
Se a análise de código estático não for suficiente, você pode precisar de análise dinâmica para examinar o programa em execução: https://en.wikipedia.org/wiki/Dynamic_program_analysis