Pular para o conteúdo principal

Porting C to ... C

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:

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

Postagens mais visitadas deste blog

Análise vs. Projeto

Eu diria que a ruptura entre Análise e o Projeto acontece quando acabam as considerações sobre o problema e começam as considerações sobre a solução. Em outras palavras, o papel do Analista é considerar o problema, enquanto o papel do Projetista é o de considerar a solução. Assim, o Analista aparece quando um problema aparece, e dá lugar ao Projetista quando o problema foi devidamente analisado, restando produzir para ele uma solução. E diria que a ruptura entre a Especificação e a Implementação acontece quando acabam os artefatos para consumo por humanos e começam os artefatos para consumo por máquinas. Em outras palavras, o papel do Especificador é produzir artefatos para consumo por humanos, enquanto o papel do Implementador é produzir artefatos para consumo por máquinas. Assim, o Especificador aparece quando a atividade é produzir efeitos sobre humanos, e dá lugar ao Implementador quando os humanos estão devidamente satisfeitos e a atividade prossegue para produzir efeito...

JNDI e Plan 9

Acabei de realizar quão similar é o J2EE e o Plan 9 quando observados pela lente da arquitetura de componentes: no Plan 9, o sistema de arquivos é tão capaz quanto o JNDI no J2EE. Um componente no qual eu estou trabalhando agora depende de um componente de banco de dados e de um componente de correio eletrônico. O modo de ligação entre esses componentes se dá através de JNDI: meu componente faz referência aos nomes "dataSource" e "mailSession" no seu contexto local de nomes. Na configuração do componente em uma instalação, esses nomes locais são ligados aos nomes globais que o administrador preparou para oferecer as dependências. É claro que tanto o meu componente quanto suas dependências mantém um contrato entre si, na forma das interfaces java EntityManager e Session. No Plan 9, meu componente seria um programa que faz referência a dois arquivos no seu file system -- um arquivo que repr...

O Problema de Gestão de I/O

A palestra que dei no Sétimo Encontro do Grupo de Usuários de C e C++ do Brasil apresentou meu resultado atual na pesquisa de um framework baseado em uma forma de orientação a aspectos em C++ descrita no "Modern C++ Design" do Alexandrescu. Minha palestra não apresentou, porém, o que é exatamente o problema a resolver, assumindo que fosse razoavelmente conhecido. Seu foco foi apresentar todos os mecanismos disponíveis para se resolver o problema no Linux 2.6, suas peculiaridades e seus aspectos comuns. Uma certa razão me motivou, após esta apresentação, a rebobinar meu raciocínio e considerar o problema original. [1] O que há exatamente de tão problemático na gestão de I/O em um programa? A imagem inicial é a de um programa cujo objetivo é interagir com um dos dispositivos ligados ao computador. A forma básica dessa interação é a cópia de dados do dispositivo para a memória e da memória para o dispositivo, sendo que trabalho útil por parte do programa ocorre enquanto os...