Chegou, então, a hora de ressuscitar um programa desenvolvido pelo fundador da empresa em um inominável sistema UNIX rodando em i386 no passado remoto.
O programa, cujo sistema de construção permitia a configuração para inúmeras variantes do UNIX de então, permaneceu compilável com mínimas modificações até o Fedora 17. Impressionante.
Infelizmente, ao executar o programa, segmentation fault a torto e a direito. Com a ajuda de um debugger, alcançamos a seguinte iteração, simplificada para este artigo:
Simples, certo? O programa levantava SIGSEGV ao avaliar a expressão *p.
Como é possível? Certamente getenv retorna um endereço válido ou NULL. Certamente p não é NULL quando avalia-se *p.
Qual é o tipo de p? char * p . Por acaso o programa está sobrescrevendo memória do sistema inadvertidamente?
Num ato de desespero, escrevi o seguinte programa:
que obviamente não deve levantar SIGSEGV certo? Pois este programa também falha com SIGSEGV.
A dica estava no alerta do compilador: conversão de int para char * causa possível truncamento do valor.
Ora, porque haveria uma conversão de int para ponteiro naquela sentença? Sabemos que getenv retorna char *.
Não, calma. Onde está declarada getenv? Em stdlib.h. Eu confundi.
Ora, como pode compilar este código se não há declaração para getenv? Ah, sim; para o C, na ausência de uma declaração, assume-se que foo é int foo (...).
Por que isso causaria um crash? Por que esta máquina é x86_64 e um ponteiro é maior que um int. Portanto, o valor retornado por getenv estava truncado para int e posteriormente estendido para ponteiro.
O programa funcionava por sorte já que nunca encontrou uma máquina onde sizeof(int) != sizeof(void *).
Infelizmente, ao executar o programa, segmentation fault a torto e a direito. Com a ajuda de um debugger, alcançamos a seguinte iteração, simplificada para este artigo:
for (int i = 0; i < name_list_size; ++i)
{
p = (char *) getenv(name_list[i]);
if (p != NULL)
if (*p != NULL)
strcpy(value_list[i], name_list[i]);
}
Simples, certo? O programa levantava SIGSEGV ao avaliar a expressão *p.
Como é possível? Certamente getenv retorna um endereço válido ou NULL. Certamente p não é NULL quando avalia-se *p.
Qual é o tipo de p? char * p . Por acaso o programa está sobrescrevendo memória do sistema inadvertidamente?
Num ato de desespero, escrevi o seguinte programa:
#include
int main (int argc, char * argv [])
{
char * p = getenv("SHELL");
return 0;
}
que obviamente não deve levantar SIGSEGV certo? Pois este programa também falha com SIGSEGV.
A dica estava no alerta do compilador: conversão de int para char * causa possível truncamento do valor.
Ora, porque haveria uma conversão de int para ponteiro naquela sentença? Sabemos que getenv retorna char *.
Não, calma. Onde está declarada getenv? Em stdlib.h. Eu confundi.
Ora, como pode compilar este código se não há declaração para getenv? Ah, sim; para o C, na ausência de uma declaração, assume-se que foo é int foo (...).
Por que isso causaria um crash? Por que esta máquina é x86_64 e um ponteiro é maior que um int. Portanto, o valor retornado por getenv estava truncado para int e posteriormente estendido para ponteiro.
O programa funcionava por sorte já que nunca encontrou uma máquina onde sizeof(int) != sizeof(void *).
Comentários
Postar um comentário