Ponteiros!

Outro dia no trabalho resolvemos brincar com ponteiros, não que o código que resultou da brincadeira seja algo bom, mas foi divertido. Basicamente tinhamos uma função que traduzia uma string interna passada para a string que utilizariamos em tela, software foi crescendo e mais traduções eram necessárias para a mesma string.

Então aos exemplos, do que tinhamos, da brincadeira que fizemos com os ponteiros e o que seria a melhor solução para o código. Antes algumas definições, a estrutura que contém as strings é a seguinte, basicamente um mapa 1 pra n:

enum {
	KERNEL = 1,
	WEB,
	CLI
};

struct test {
	int idx;
	char *kernel;
	char *web;
	char *cli;
};

struct test example[] = {
	{.idx = 0, .kernel = "eth0", .web = "Ethernet 0", .cli = "ethernet0" },
	{.idx = 1, .kernel = "eth1", .web = "Ethernet 1", .cli = "ethernet1" },
	{.idx = 2, .kernel = "eth2", .web = "Ethernet 2", .cli = "ethernet2" },
	{.idx = 3, .kernel = NULL, .web = NULL, .cli = NULL}
};

A versão original da função que procurava pela string correta era algo parecido com:

char *get_str_for_v1(const char *eth, enum str_type type)
{
	char *rtn = NULL;
	int i;
	
	switch (type) {
	case KERNEL:
		for (i = 0; i < sizeof (example); i++) {
			if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
				rtn = example&#91;i&#93;.kernel;
				break;
			}
		}
		break;
	case WEB:
		for (i = 0; i < sizeof (example); i++) {
			if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
				rtn = example&#91;i&#93;.web;
				break;
			}
		}
		break;
	case CLI:
		for (i = 0; i < sizeof (example); i++) {
			if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
				rtn = example&#91;i&#93;.cli;
				break;
			}
		}
		break;
	}
	
	return rtn;
}
&#91;/sourcecode&#93;

O que é obviamente muito feio! Com excesso de código repetido, péssimo de ler e dar manutenção. Vendo esse código propus o desafio de escrever a mesma função com um <code>for</code> só. Meu colega de trabalho resolveu usar ponteiros pra resolver isso, calculando o offset de cada um dos membros da estrutura e recuperando o ponteiro da string a partir do offset. Claro, isso parece muito simples de se fazer... mas na hora de escrever o código, demorou bem mais do que imaginavamos, mas foi bastante divertido entender como funcionam os casts em C.

O resultado foi o seguinte código, as macros eu adicionei para facilitar a leitura do código, mais sobre elas daqui um pouco...

#define member_offset(type, member) \
			((unsigned long)(&((type *)0)->member))
#define get_member_by_offset(ptr, type, offset) \
			((type *)((void *)(ptr) + offset))

char *get_str_for_pointer_fun(const char *eth, enum str_type type)
{
	int i;
	unsigned long offset;
	
	switch(type) {
	case KERNEL:
		offset = member_offset(struct test, kernel);
		break;
	case WEB:
		offset = member_offset(struct test, web);
		break;
	case CLI:
		offset = member_offset(struct test, cli);
		break;
	}
	
	for (i = 0; i < sizeof (example); i++) {
		if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
			return *get_member_by_offset(&example&#91;i&#93;, char *, offset);
		}
	}
	
	return NULL;
}
&#91;/sourcecode&#93;

Com certeza não é a melhor implementação possível, talvez seja a mais complexa de se entender, especialmente se não for utilizada uma macro para esconder a implementação. Mas é uma implementação divertida, ou pelo menos foi divertido tentar achar a combinação correta de ponteiros para gerá-la.

E finalmente o código que eu julgo o mais aceitável:
&#91;sourcecode lang="c"&#93;
char *get_str_for_v2(const char *eth, enum str_type type)
{
	int i;

	for (i = 0; i < sizeof (example); i++) {
		if (strcmp(eth, example&#91;i&#93;.kernel) == 0) {
			switch (type) {
			case KERNEL:
				return example&#91;i&#93;.kernel;
			case WEB:
				return example&#91;i&#93;.web;
			case CLI:
				return example&#91;i&#93;.cli;
			}
		}
	}
	
	return NULL;
}
&#91;/sourcecode&#93;

<h2>Macros</h2>
Bom, disse que ia explicar o funcionamento das duas macros utilizadas, então recapitulando elas:

#define member_offset(type, member) \
			((unsigned long)(&((type *)0)->member))

#define get_member_by_offset(ptr, type, offset) \
			((type *)((void *)(ptr) + offset))

A macro member_offset calcula o offset de um membro qualquer de um estrutura. Para fazer isso, ela dá um cast para um ponteiro do tipo da estrutura requerida ao valor 0. O que parece estranho a princípio, mas faz todo o sentido! Do ponteiro para zero até o membro requerido da estrutura existem os n bytes que representam o offset em memória do início da estrutura até o membro.

Munido do valor do offset, podemos então recuperar a estrutura que se encontra naquele offset utilizando a macro get_member_by_offset. Obviamente é necessário sabermos qual o tipo de dado que se encontra naquele offset! Então a partir do ponteiro (endereço) da estrura que queremos acessar podemos somar o offset do membro requerido. Mas só somar o offset não resolve, é necessário dar os casts apropriados.

Como queremos um que o endereço calculado (ponteiro original + offset) seja um ponteiro, é necessário indicar isto ao compilador, colocando um * no cast do cálculo. Além disso, é necessário indicar que tipo de ponteiro estamos recuperando naquele endereço, portanto o cast vira (type *). No exemplo, o cast é (char **) indicando corretamente que naquele endereço encontra-se uma string.

Esse mesmo método de recuperar um membro de uma estrutura é utilizado na implementação de listas no kernel do linux. A macro definida é um pouco diferente, uma vez que ela realiza estas duas operações de uma só vez, mas a explicação do seu funcionamento é a mesma! Segue a sua implementação:

#define list_entry(ptr, type, member) \
	((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

Basicamente o que este código faz é recuperar o ponteiro para a estrutura a partir de um membro qualquer dela. No caso das listas, o membro utilizado para recuperar a estrutura original é o struct list_head. Com isso, é possível utilizar diversos struct list_head numa mesma estrutura, adicionando ou não a estrutura alocada a uma das listas.

Análise Estática com LLVM

LLVM é uma coleção de tecnologias de compiladores (ou algo assim, é que eles usam para se autodefinir), mas esse não é o ponto mais interessante – apesar de existirem diversos subprojetos bastante interessantes, a parte mais legal do LLVM é o subprojeto Clang Static Analyzer – ok, ele é parte do subprojeto Clang na verdade. O Clang utiliza as tecnologias oferecidas pelo LLVM para implementar um compilador completo, com algumas vantagens é extremamente rápido e dá ótimas mensagens de erro.

Meu interessante por análise estática surgiu primeiro quando eu ainda brincava de descompilar programas e entender o que eles faziam, afinal, quando se lê um código sem executá-lo estamos fazendo uma análise. Mas cansei de ler ASM, passei a programar mais e fui atrás de algo que fizesse algo parecido com o que eu fazia. Descobri várias ferramentas que fazem algum tipo de análise estática: lint, sparse e o clang. Sim, lint aquele programa velhão que verificava se um código C era bem formado fazia nada mais do que uma análise estática do código – ok, sem grandes interpretações do que o código fazia. O Sparse é um parser semântico para C, faz mais ou menos a mesma coisa que o lint fazia, mas é um código mais novo, originalmente escrito pelo Linus Torvalds. E finalmente há o Clang…

Clang Static Analyzer

Primeiro passo para testar e usar o Clang é obter os arquivos fonte e compilá-lo, pelo menos não achei uma versão para download (ok, há uma versão para Mac OSX). Há um bom passo-a-passo aqui. Em linhas gerais:

cd tools
svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
cd ..
./configure
make
make install

Atualmente o Clang não instala os scripts scan-build e scan-view, então é necessário copiá-los manualmente. Instalei os scripts em $HOME/bin/, pois já tinha esse caminho no meu path. scan-build é a ferramenta responsável por criar o ambiente necessário para executar o Clang. Com a ferramenta instalada é hora de utilizá-la!

Ah, ele também tem um suporte bem legal a Objective-C, tanto para programas para Mac quanto para aplicativos de iPhone / iPad! Mas, ainda não testei essa funcionalidade.

Usando o Clang

Precisamos de um código com alguns problemas para testar o clang. Resolvi reutilizar o código do post sobre valgrind. Ok, não é o melhor código para demonstrar as funcionalidades do clang, mas é suficientemente bom. Há quatro bugs naquele código, todos foram corretamente identificados pelo valgrind. Quantos desses erros o Clang conseguirá identificar? Usando o scan-build, habilitando verificações experimentais, obtemos:

pedro@urubu:~/code$ scan-build --experimental-checks gcc -g -O1 -o valtest valtest.c
valtest.c:13:2: warning: Access out-of-bound array element (buffer overflow)
        x[5] = 'a';
        ^~~~
valtest.c:20:2: warning: Value stored to 'x' is never read
        x = malloc(5 * sizeof(char));
        ^   ~~~~~~~~~~~~~~~~~~~~~~~~
valtest.c:20:4: warning: Allocated memory never released. Potential memory leak.
        x = malloc(5 * sizeof(char));
        ~~^~~~~~~~~~~~~~~~~~~~~~~~~~
3 warnings generated.
scan-build: 3 bugs found.
scan-build: Run 'scan-view /tmp/scan-build-2010-06-01-4' to examine bug reports.

Dentro de /tmp/scan-build-2010-06-01-4 há um relatório em html com cada erro encontrado, basicamente o código fonte anotado. Um dos erros acima gerou o seguinte relatório:

clang-report

Exemplo de relatório gerado

Resultado, o Clang encontrou três problemas no código, sendo que dois são na mesma linha de código – gerados pelo mesmo problema. Então dos quatro bugs existentes no código a ferramenta identificou dois… nada mal para uma ferramenta que só analizou o código fonte! Claro, há outros bugs que a ferramenta irá identificar e o valgrind não. There is no silver bullet, mas várias ferramentas utilizadas em conjunto conseguem aumentar bastante a qualidade do código escrito.

Duas coisas para terminar este post, não utilizei o clang para gerar código, somente para fazer análise do código fonte, mas isso é perfeitamente viável. E para completar, se formos utilizar o Clang junto a um projeto com autotools, fariamos:

pedro@urubu:~/code/myproject$ scan-build ./configure
pedro@urubu:~/code/myproject$ scan-build make

Valgrind & Embedded Linux

Como continuação ao último post, resolvi testar o comportamento do valgrind em um sistema embarcado. Primeiro passo obviamente é ter um ambiente de desenvolvimento apropriado, isto é, um toolchain para cross-compilar os pacotes necessários, a glibc compilada para a plataforma em questão, etc. A maioria dos fabricantes de processadores para arquiteturas embarcadas fornecem um pacote contendo toda a toolchain necessária para compilar para a arquitetura em questão. Para escrever este blog eu utilizei o toolchain de powerpc fornecido pela Freescale.

Começei por instalar o pacote em um local conhecido digamos $HOME/toolchains/powerpc-linux-gnu, ajustei minha variável $PATH de modo que fosse possível encontrar os compiladores e ferramentas da toolchain – algo como export PATH=$PATH:$HOME/toolchains/powerpc-linux-gnu/bin, isto irá mudar dependendo do toolchain usado e onde ele foi instalado.

Com as ferramentas instaladas, hora de conseguir o source do valgrind! Primeiro baixei o tarball (versão 3.5) do site do valgrind, mas logo descobri que havia um problema com umas macros usadas que não permitiam cross-compilar o pacote – vide bug, que já está corrigido, mas não liberaram um pacote novo. Bom, hora de baixar o código fonte do SVN! Vamos colocar o código dentro do diretório pkgs do projeto my-project.

cd $HOME/my-project/pkgs/
svn co svn://svn.valgrind.org/valgrind/trunk valgrind

Feito isso, hora de compilar o valgrind! Antes um aviso para os navegantes: o valgrind coloca o caminho $PREFIX/bin hardcoded no binário gerado, e utiliza esse caminho para achar as diversas ferramentas geradas (callgrind, memcheck, etc). Obviamente eu não percebi isso na primeira vez que compilei, mas depois de umas tentativas consegui acertar os valores corretos. A melhor solução é deixar o $PREFIX apontando para /usr como em qualquer instalação normal, compilar normalmente o código e na hora de instalar usar a variável DESTDIR para apontar o diretório correto para realizar a instalação. No meu caso isso é necessário, pois quero instalar num diretório que será montado por NFS no dispositivo embarcado ($HOME/my-project/rootfs). O -j4 é por que eu sou impaciente e tenho cores demais na máquina.

cd valgrind
./configure --host=powerpc-linux-gnu --prefix=/usr --disable-tls
make -j4
make install DESTDIR=$HOME/my-project/rootfs

Com isso temos o valgrind instalado no diretório raíz utilizado pelo sistema embarcado que queremos testar! Hora de bootar o sistema, esperar ele carregar e fire it away! Abaixo um exemplo do valgrind rodando em um sistema embarcado.

-sh-4.0# valgrind test
==1653== Memcheck, a memory error detector
==1653== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==1653== Using Valgrind-3.6.0.SVN and LibVEX; rerun with -h for copyright info
==1653== Command: test
==1653==
Prompt>exit
==1653== Invalid read of size 1
==1653==    at 0x10007F88: test_execute (in /bin/test)
==1653==    by 0x10009A23: main (in /bin/test)
==1653==  Address 0x403e0fd is 0 bytes after a block of size 5 alloc'd
==1653==    at 0xFFBA4B0: malloc (vg_replace_malloc.c:236)
==1653==    by 0xFF5FA2F: xmalloc (in /lib/libreadline.so.6.0)
==1653==    by 0xFF419CB: readline_internal_teardown (in /lib/libreadline.so.6.0)
==1653==    by 0xFF41D1B: readline (in /lib/libreadline.so.6.0)
==1653==    by 0x100098AB: main (in /bin/test)
==1653==
==1653==
==1653== HEAP SUMMARY:
==1653==     in use at exit: 24,589 bytes in 145 blocks
==1653==   total heap usage: 229 allocs, 84 frees, 53,399 bytes allocated
==1653==
==1653== LEAK SUMMARY:
==1653==    definitely lost: 0 bytes in 0 blocks
==1653==    indirectly lost: 0 bytes in 0 blocks
==1653==      possibly lost: 0 bytes in 0 blocks
==1653==    still reachable: 24,589 bytes in 145 blocks
==1653==         suppressed: 0 bytes in 0 blocks
==1653== Rerun with --leak-check=full to see details of leaked memory
==1653==
==1653== For counts of detected and suppressed errors, rerun with: -v
==1653== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 5 from 3)

Só para completar, o valgrind ajuda bastante a achar os problemas no software, mas lembre-se que um sistema embarcado possuiu outras limitações, como pouca memória, processador com clock baixo, etc. Essas limitações se refletem no desempenho do valgrind, o software ficará bastante lento, algumas flags do valgrind não funcionarão – no meu caso foi por falta de memória, precisaria de mais do que os 64M de RAM disponíveis para utilizar o --track-origins (Ah, o manual do valgrind informa que é necessário no mínimo 100M de RAM para este parâmetro funcionar!).

Happy Hacking!

Valgrind

Valgrind tem esse nome estranho mas é a ferramenta que todo o programador deveria ter no seu cinto de utilidades do Batman! O nome provêm da mitologia nórdica e significa a entrada de Valhalla – wikipédia tem mais explicações. Agora, por que todo programador deve ter essa ferramenta no bat-cinto? A resposta é simples: ela instrumenta o aplicativo em tempo de execução e identifica leaks de memória (memória que foi alocada mas nunca liberada), uso inválidos de ponteiros, variáveis não inicializadas, múltiplos frees na mesma váriavel, etc. Se isso não bastasse, há ainda outros backends que identificam chamadas de função, uso de cache, memória alocada, etc, e obviamente você pode escrever o seu próprio backend!

O foco deste post é mostrar o uso do backend memcheck para solucionar problemas de memória. Necessitamos, para isso, de um código com alguns bugs. Sabemos que um bom programador, lendo este código, identificará todos os problemas que existem nele, mas muitas vezes estes mesmo problemas surgem de forma escondida no código, o que torna necessário o uso de uma ferramenta para ajudar a identificá-los.

O código a seguir exercita a maior parte das funcionalidades do backend memcheck – o padrão do valgrind. Compile o código executando a seguinte linha, observe que o gcc é chamado com a flag -Wall que habilita diversos warnings durante a compilação, apesar disso, o código deve compilar sem nenhum problema.

gcc -Wall -O1 -g -o valtest valtest.c
#include <stdio.h>
#include <stdlib.h>

struct uninit {
	char *x;
	char *y;
};

void heap(void)
{
	char *x;
	x = malloc(5 * sizeof(char));
	x[5] = 'a';
	free(x);
}

void leak(void)
{
	char *x;
	x = malloc(5 * sizeof(char));
}

void uninitialized(void)
{
	struct uninit *u;
	u = malloc(sizeof(struct uninit));
	if (u->y == 0) {
		printf("y is 0\n");
	}
	free(u);
}

char * double_free(void)
{
	char *x;
	x = malloc(5 * sizeof(char));
	free(x);
	return x;
}

int main(int argc, char *argv[])
{
	char *x;
	
	printf("Calling heap()\n");
	heap();
	
	printf("Calling leak()\n");
	leak();
	
	printf("Calling uninitialized()\n");
	uninitialized();
	
	printf("Calling double_free()\n");
	x = double_free();
	free (x);
	
	return 0;
}

Se executarmos este código, ainda sem utilizar o valgrind, obteremos um erro por causa do duplo free. A saída da execução será algo parecido com as linhas abaixo, talvez inclua um backtrace ou mais alguma informação, mas isso depende da libc utilizada. Repare que a mensagem de erro não indica onde ocorreu o problema no código.

pedro@urubu:~/code$ ./valtest
Calling heap()
Calling leak()
Calling uninitialized()
y is 0
Calling double_free()
*** glibc detected *** ./valtest: double free or corruption (fasttop): 0x08b09028 ***

Let the Bug Hunting begin!

Sabemos que existe um problema com o código, temos uma vaga idéia de onde ele ocorre – depois da função double_free(), mas há mais problemas no código e nenhum deles foi reportado durante a execução. Hora de usar o valgrind! Executando a seguinte linha, obteremos um relatório dos erros encontrados pelo valgrind. Abreviei a saída gerada pelo valgrind para mostrar justamente o relatório de erros encontrados.

pedro@urubu:~/code$ valgrind ./valtest
==13586== Memcheck, a memory error detector
==13586== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==13586== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==13586== Command: ./valtest
==13586==
...
==13586== HEAP SUMMARY:
==13586==     in use at exit: 5 bytes in 1 blocks
==13586==   total heap usage: 4 allocs, 4 frees, 23 bytes allocated
==13586==
==13586== LEAK SUMMARY:
==13586==    definitely lost: 5 bytes in 1 blocks
==13586==    indirectly lost: 0 bytes in 0 blocks
==13586==      possibly lost: 0 bytes in 0 blocks
==13586==    still reachable: 0 bytes in 0 blocks
==13586==         suppressed: 0 bytes in 0 blocks
==13586== Rerun with --leak-check=full to see details of leaked memory
==13586==
==13586== For counts of detected and suppressed errors, rerun with: -v
==13586== Use --track-origins=yes to see where uninitialised values come from
==13586== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 13 from 8 )

A ferramenta encontrou 3 erros em 3 contextos diferentes e 5 bytes de memória alocada. É claro que existe alguns bugs neste código! No texto de saída há ainda a sugestão do uso das flags --leak-check=full e -v. Vamos rodar novamente o valgrind com estes dois parâmetros antes de chamar o valtest. O número de mensagens aumenta bastante, mas dá novos indicativos do que está acontecendo de errado no código.

Heap overrun

O primeiro erro apresentado pela ferramenta é no métodoheap(), meus grifos:

Calling heap()
--13590-- REDIR: 0x40a7f40 (malloc) redirected to 0x4024e9b (malloc)
==13590== Invalid write of size 1
==13590==    at 0x80484FC: heap (valtest.c:13)
==13590==    by 0x804852B: main (valtest.c:46)
==13590==  Address 0x419402d is 0 bytes after a block of size 5 alloc'd
==13590==    at 0x4024F20: malloc (vg_replace_malloc.c:236)
==13590==    by 0x80484FB: heap (valtest.c:12)
==13590==    by 0x804852B: main (valtest.c:46)
==13590==

Isto indica que tentou-se escrever 1 byte após os 5 bytes alocados, se olharmos o código isto fica bastante claro! Alocamos 5 bytes para a variável x, isto em um array significa os índices entre 0 e 4. Na linha 13 do código tentamos escrever na posição 5, que é uma área de memória além do espaço alocado. A saída da ferramenta ainda nos indica em que lugar no código a memória que está sendo utilizada foi alocada, no caso na linha 12.

Com isso podemos arrumar o primeiro bug do código, acertando o índice utilizado na linha 13 para ser um valor entre 0 e 4. One down, three to go!

void heap(void)
{
	char *x;
	x = malloc(5 * sizeof(char));
	x[0] = 'a';
	free(x);
}

Uninitialized value

O próximo erro apresentado é do uso de uma variável não inicializada e é apresentado pelo valgrind da seguinte forma. Repare que a função leak() foi executada e não apresentou nenhum erro, mas como veremos a ferramenta identificou o erro que acontece na função.

Calling leak()
Calling uninitialized()
==13590== Conditional jump or move depends on uninitialised value(s)
==13590==    at 0x80484B2: uninitialized (valtest.c:27)
==13590==    by 0x804855D: main (valtest.c:52)
==13590==
y is 0

Olhando a linha 27, conforme indicado pelo valgrind, não parece ter nada de errado com o teste realizado ali. Se olharmos as linhas anteriores fica claro que a estrutura u foi alocada, mas que seus valores nunca foram inicializados e dependendo da implementação do malloc os valores dos membros de uma estrutura não serão nulos. Alterando o código da função uninitialized para que os valores de x e y sejam inicializados resolve este problema.

void uninitialized(void)
{
	struct uninit *u;
	u = malloc(sizeof(struct uninit));
	u->x = NULL;
	u->y = NULL;

	if (u->y == 0) {
		printf("y is 0\n");
	}
	free(u);
}

Double free

O próximo problema já foi apresentado pela própria libc, mas não tinhamos um indicativo preciso do local no código onde ocorreu o erro. O valgrind nos fornece este dado!

Calling double_free()
==13590== Invalid free() / delete / delete[]
==13590==    at 0x4024B3A: free (vg_replace_malloc.c:366)
==13590==    by 0x804857E: main (valtest.c:56)
==13590==  Address 0x41940d0 is 0 bytes inside a block of size 5 free'd
==13590==    at 0x4024B3A: free (vg_replace_malloc.c:366)
==13590==    by 0x8048490: double_free (valtest.c:37)
==13590==    by 0x8048576: main (valtest.c:55)
==13590==

A ferramenta indica onde ocorreu o segundo free, linha 56, como também onde está o primeiro, linha 37! Olhando o código percebemos que o free da linha 37 é desnecessário, pois queremos retornar um ponteiro para a memória alocada pela função. Removendo esta linha, a função double_free fica:

char * double_free(void)
{
	char *x;
	x = malloc(5 * sizeof(char));
	return x;
}

Leak

O último problema apresentado pelo código é uma área de memória alocada e nunca liberada. Durante a execução do programa, este problema não é manifestado, somente quando finalizamos o programa é que a ferramenta retorna uma relatório identificando que áreas de memória foram usadas e nunca liberadas.

==13590== HEAP SUMMARY:
==13590==     in use at exit: 5 bytes in 1 blocks
==13590==   total heap usage: 4 allocs, 4 frees, 23 bytes allocated
==13590==
==13590== Searching for pointers to 1 not-freed blocks
==13590== Checked 56,484 bytes
==13590==
==13590== 5 bytes in 1 blocks are definitely lost in loss record 1 of 1
==13590==    at 0x4024F20: malloc (vg_replace_malloc.c:236)
==13590==    by 0x80484E7: leak (valtest.c:20)
==13590==    by 0x8048544: main (valtest.c:49)

Analisando a linha 20, vemos que uma área de memória é alocada e o método acaba logo em seguida, sem retornar o ponteiro alocado (para que outra função utilize e libere a memória, por exemplo) ou liberar a memória. Portando estamos vazando essa memória! Se essa função for chamadas diversas vezes durante a execução do programa ocuparemos muita mais memória do que necessitamos, e dependendo do sistema podemos esgotar a memória disponível (pense em um sistema embarcado com 8M de ram, ou menos). É necessário liberar a memória, então:

void leak(void)
{
	char *x;
	x = malloc(5 * sizeof(char));
	free(x);
}

Conclusão

Tendo corrigido os 4 problemas que existiam no código, podemos rodar novamente o valgrind e desta vez não obteremos nenhum erro!

pedro@urubu:~/code$ valgrind --leak-check=full ./valtest-fix
==13845== Memcheck, a memory error detector
==13845== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==13845== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==13845== Command: ./valtest-fix
==13845== 
Calling heap()
Calling leak()
Calling uninitialized()
y is 0
Calling double_free()
==13845== 
==13845== HEAP SUMMARY:
==13845==     in use at exit: 0 bytes in 0 blocks
==13845==   total heap usage: 4 allocs, 4 frees, 23 bytes allocated
==13845== 
==13845== All heap blocks were freed -- no leaks are possible
==13845== 
==13845== For counts of detected and suppressed errors, rerun with: -v
==13845== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 13 from 8 )

Com isso, vimos os principais tipos de erro de memória que o valgrind identifica. Em um código não fictício é mais complexo encontrar o local exato onde ocorrem e por que ocorrem os erros, mas o valgrind dá boas indicações. Costumo utilizar o valgrind sempre que acabo de escrever um pedaço de código, que compila sem warnings, mas que não tenho certeza se desaloquei a memória no lugar certo. É boa prática rodar sempre o valgrind para identificar os mais diversos bugs que existem no seu código.

Happy bug hunting!

The Joy of Autotools!

Toda a vez que eu leio Autotools eu lembro dos Autobots, não sei porque a associação. Mas esse post não é sobre isso, e talvez ele renda mais do que um post. Nas últimas semanas ajudei a migrar dois projetos que utilizavam Makefiles hand-crafted para utilizarem todo o pacote das Autotools.O primeiro passo para que a migração fosse tranquila foi ensinar os conceitos básicos das Autotools para todos os envolvidos no projeto.

Existem diversos tutoriais, livros e blogs explicando como utilizar estas ferramentas, mas a maioria exige bastante tempo para deglutir todas as informações expostas e conseguir utilizar o sistema. Para facilitar, criei uma página na wiki interna mostrando os arquivos necessários para se criar um projeto básico utilizando autotools. Este post é uma tentativa de extender este conteúdo e divulgar o uso das Autotools.

Alguns arquivos essênciais

Basicamente todo o conjunto de ferramentas necessita de dois arquivos para funcionar:

  • configure.ac: diretivas para gerar o script de configuração, testar a existência de bibliotecas, headers, comandos, etc.
  • Makefile.am: o que deve ser compilado, bibliotecas, binários, sub-diretórios, programas de teste, etc.

Com esses dois arquivos na raiz do projeto e mais alguns Makefile.am nos subdiretórios, é tudo o que se necessita para utilizar as Autotools. Obviamente isto é só o inicio da brincadeira, diversos detalhes devem ser adicionados para deixar o sistema de build de forma adequada ao projeto.

Aos arquivos!

configure.ac

AC_INIT([project_name],[version],[maintainer_email])
AC_CONFIG_SRCDIR([.])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])

AM_INIT_AUTOMAKE([foreign])
AM_MAINTAINER_MODE

AC_PROG_LIBTOOL
AC_PROG_CC

AC_CONFIG_FILES([Makefile])
AC_OUTPUT

Bom, olhando assim, esses comandos parecem uma grande sopa de letrinhas, sem sentido algum. Então, como Jack faria (piada fraca, eu sei), vamos por partes. A primeira linha inicializa o autoconf, e portanto ali estão definidos o nome do projeto, a versão, e o endereço de email do mantenedor. As próximas três linhas definem configurações gerais do autoconf, como diretório onde se encontram os arquivos de source, onde devem ser guardadas e lidas as macros utilizadas pelo autoconf, e finalmente qual é o nome do arquivo de header que o autoheader irá gerar.

Destas diretivas, duas devem ser destacadas,AC_CONFIG_SRCDIR que define qual é o diretório onde se encontram os arquivos fontes do programa, que é de escolha do programador; e a AC_CONFIG_MACRO_DIR que define o diretório onde estão as macros do autoconf, no exemplo, os arquivos são salvos no subdiretório m4. É importante que este diretório exista, pois caso contrário, gerará um erro quando rodarmos o autoreconf – mais sobre esta ferramenta no final do post.

Depois de inicializado o autoconf deve-se inicializar o automake, uma vez que optamos por utilizar todo o conjunto das Autotools. As duas diretivas iniciadas em AM são responsáveis por inicializar o automake e por optar pelo modo maintainer, que evita alguns arquivos sejam refeitos quando distribuidos para o usuário final. A diretiva AC_PROG_CC é responsável por testar a existência de um compilador C instalado no sistema. Já a AC_PROG_LIBTOOL testa e inicializa o uso da ferramenta libtool, necessária para gerar bibliotecas.

As últimas duas linhas são extremamente importantes, e deve-se tomar bastante cuidado para que todos os arquivos que se deseja gerar após rodar um ./configure estejam listados na diretiva AC_CONFIG_FILES.

Makefile.am

Com o arquivo configure.ac escrito, é hora de definir o que será gerado: um programa, uma biblioteca, ambos, arquivos extras que devem ser instalados, etc. Para isso é necessário escrever as diretivas necessárias no Makefile.am.

ACLOCAL_AMFLAGS = -I m4

bin_PROGRAMS = myproject
lib_LTLIBRARIES = libproject.la

myproject_SOURCES = \
	myproject.c \
	mymem.c 

libproject_la_SOURCES = \
	myhash.c \
	mylib.c
libproject_ladir = $(includedir)/libproject
libproject_la_HEADERS = \
	mylib.h

A primeira diretiva só adiciona a flag -I m4 quando o aclocal é chamado, indicando qual o diretório que contêm os script m4. Se o seu projeto possuiu um hierarquia de makefiles você só precisa adicionar essa diretiva no Makefile.am da raiz do projeto.

As próximas duas linhas definem o que será gerado pelo automake. A primeira linha define os executáveis a serem gerados, já a segunda define as bibliotecas geradas pelo projeto. Para programas gerados, podemos usar alguns prefixos diferentes a diretiva PROGRAMS como: bin, sbin, libexec e pkglib, isso instruirá o autotools o local onde o binário será instalado. Também há a opção de não instalar o programa usando o prefixo noinst, ou ainda check que só irá gerar o binário quando executado make check. Para bibliotecas os prefixos possíveis são lib, pkglib e noinst, seguidos da diretiva LTLIBRARIES. O que segue após o sinal de igualdade são os nomes dos binários ou bibliotecas que serão gerados.

Para cada objeto a ser gerado é necessário definir quais são os arquivos fontes necessários. Também é possível definir flags, biblotecas, etc. específicas para cada objeto, para mais informações sobre estas outras variáveis leia aqui. O mais comum é usar a diretiva SOURCES com uma lista de arquivos fonte necessários para gerar o objeto em questão. Para a biblioteca também foi definido o diretório de instalação dos headers (ladir) e o header da biblioteca – necessário para outros projetos poderem linkar com a nossa biblioteca.

Com isso temos um Makefile.am básico, hora de gerarmos os scripts de configuração.

Gerando os arquivos!

Tendo os dois arquivos no diretório do projeto (configure.ac e Makefile.am) é hora de gerar o script de configuração e o Makefile do projeto. Por muito tempo os diversos projetos que usam Autotools mantiveram scripts próprios para rodar as diversas ferramentas necessárias (automake, autoconf, autoheader, aclocal, etc) para gerar todo o sistema de build, os famosos scripts autogen.sh. Atualmente não é mais necessário utilizar estes scripts, basta chamar o programa autoreconf. Eu gosto de utilizar as flags -i, para copiar os arquivos necessários para o diretório do projeto, e -v para exibir as mensagens dos programas executados.

autoreconf -iv

Wrap up & Links

Este post talvez nunca esteja pronto, e irá evoluir aos poucos. Seguem alguns link úteis!