Em muitos casos, compilamos um programa C e executamos o binário sem nos preocuparmos com mudanças no arquivo fonte original. No entanto, estou curioso:
É possível que um programa C em execução detecte se seu próprio .c
arquivo de origem foi modificado após a compilação?
Algumas restrições a considerar:
- O programa deve verificar se há modificações em seu próprio arquivo de origem, não apenas no binário (
a.out
) - Não deve depender de ferramentas externas como
md5sum
oustat
por meiosystem()
de chamadas. - A solução deve funcionar em sistemas operacionais comuns (Linux/Windows)
- Isso deve ser feito puramente em C, usando bibliotecas de sistema padrão ou amplamente disponíveis.
Isso exigiria a leitura do arquivo de origem em tempo de execução e a comparação de registros de data e hora ou hashes, ou existe uma maneira mais elegante de fazer isso?
Há grandes problemas para identificar quais arquivos são "seu código-fonte". Pode haver milhões de cópias na máquina, ou nenhuma. E talvez haja cópias, mas nenhuma delas foi usada para compilar o programa. O programa pode ter sido compilado em uma máquina diferente.
Mas, fora isso, claro. Ver se um arquivo mudou é uma questão de lê-lo e compará-lo com uma cópia armazenada anteriormente. Ler arquivos e copiar blocos de memória são triviais de fazer em C.
Para economizar espaço, você pode salvar um hash criptográfico dos arquivos de origem no binário em vez dos próprios arquivos de origem. Mais complicado, mas ainda fácil de fazer.
Para economizar tempo, você também pode usar heurísticas relacionadas a tamanhos de arquivo e tempos de modificação de arquivo para pular a verificação dos arquivos byte por byte. Isso reduz a confiabilidade, no entanto. A verificação de tamanhos de arquivo e tempos de modificação de arquivo é feita facilmente com a
stat
chamada da biblioteca (em oposição à ferramenta externa).Não sem fazer suposições sobre quais arquivos são esses. Não há conexão duradoura entre binários compilados e os arquivos de origem C (e pode haver muitos contribuindo para um único programa) dos quais os binários foram construídos. Antes que o programa pudesse tentar tal verificação, ele precisaria de alguma forma determinar quais arquivos precisavam ser verificados.
E não de forma confiável. Na melhor das hipóteses, o programa poderia verificar se os arquivos de origem selecionados para teste têm os mesmos atributos e conteúdos das fontes das quais foi construído, mas não poderia descartar que alguns deles tenham sido alterados e depois alterados de volta entre a construção e a execução.
E mesmo que a informação possa ser determinada, ela não é muito útil, porque fica imediatamente obsoleta. O programa pode ser capaz de determinar se certos arquivos que ele pode observar em tempo de execução correspondem aos arquivos dos quais ele foi criado, mas então ele sabe apenas que eles corresponderam (ou não) no momento em que foram testados. Não é seguro assumir que eles continuam a corresponder por qualquer período de tempo após o teste, nem mesmo que o resultado do teste esteja correto no momento em que é inicialmente determinado. Por exemplo, talvez o arquivo esteja sendo modificado ao mesmo tempo em que está sendo testado, de modo que o estado lido pelo teste já seja diferente no momento em que o teste é concluído.
Depois de identificar os arquivos a serem testados — o que não é nada trivial — surge a questão de quais informações você realmente quer saber.
Você pode testar o timestamp atual em relação a um registrado no binário, mas isso informa apenas se os timestamps correspondem. Esse pode ser seu critério, mas é muito fraco, porque timestamps são facilmente manipulados. Por outro lado, é rápido e fácil, pois requer a leitura apenas dos metadados dos arquivos, não de seus conteúdos.
Você pode calcular um hash ou checksum dos arquivos, individualmente ou coletivamente, e comparar isso a um valor esperado armazenado no binário. Isso requer a leitura de cada byte de cada um dos arquivos, e fornece um resultado probabilístico, não um certo. No entanto, requer apenas que um valor de hash ou checksum seja armazenado no executável. (Ou armazenado em um arquivo externo, mas isso apresenta outra maneira pela qual o teste pode ser confundido.)
Você pode ler cada byte de cada um dos arquivos e comparar com uma cópia armazenada em algum lugar. O C em si não fornece um meio para que esse "algum lugar" esteja dentro do binário, mas há maneiras de fazer os linkers fazerem esse tipo de coisa. Se o armazenamento for externo, essa abordagem será tão boa quanto as cópias de origem contra as quais o programa testa. Se for interno, as cópias ocuparão uma quantidade razoável de espaço no binário.
Não tenho certeza de que tipo de solução "mais elegante" você imagina que possa haver. Se você quer aprender algo sobre o estado de certos arquivos em tempo de execução, então você tem que examinar esses arquivos em tempo de execução.