O mtrace, uma ferramenta incluída no GNU Glibc, é valioso para identificar problemas de memória, especialmente vazamentos. Ele funciona rastreando as chamadas para funções de alocação e liberação de memória como malloc, free, etc., na biblioteca C padrão. Enquanto sua aplicação direta em executáveis é bem documentada, a detecção de vazamentos em bibliotceas dinâmicas (ou compartilhadas) exige uma abordagem ligeiramente diferente, que será detalhada a seguir.
Preparação do Ambiente e Código
Para ilustrar o processo, criaremos dois arquivos: um para a biblioteca dinâmica e outro para o executável principal.
Código da Biblioteca Dinâmica (mem_lib.c)
Este arquivo conterá uma função que aloca memória sem liberá-la, simulando um vazamento.
#include <stdlib.h>
#include <stdio.h>
// Função que aloca memória e causa um vazamento
void allocate_and_leak(void)
{
char *data_ptr = (char *)malloc(32 * sizeof(char)); // Aloca 32 bytes
if (data_ptr) {
printf("allocate_and_leak: Memória alocada em %p\n", data_ptr);
// intencionalmente não liberado para simular vazamento
} else {
fprintf(stderr, "Falha na alocação de memória em allocate_and_leak\n");
}
}
Compilação da Biblioteca Dinâmica
É crucial compilar com a flag -g para incluir informações de depuração, que são essenciais para o addr2line mapear endereços de volta para o código-fonte.
gcc -g mem_lib.c -fPIC -shared -o libmem_leak.so
Código do Executável Principal (main_app.c)
Este programa incluirá chamadas para mtrace e muntrace, além de invocar a função com vazamento da biblioteca dinâmica.
#include <mcheck.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h> // Para sleep
// Declaração externa da função da biblioteca compartilhada
extern void allocate_and_leak(void);
int main(int argc, char **argv)
{
// Inicia o rastreamento de memória
mtrace();
char *app_data = (char *)malloc(64); // Alocação no executável
if (app_data) {
printf("main_app: Alocou %p\n", app_data);
free(app_data);
app_data = NULL;
}
// Chama a função da biblioteca dinâmica que vaza memória
allocate_and_leak();
char *another_leak = (char *)malloc(128); // Outro vazamento no executável
if (another_leak) {
printf("main_app: Segundo vazamento em %p\n", another_leak);
}
// Termina o rastreamento de memória, gerando o arquivo de log
muntrace();
int hold_time = 15; // Tempo para manter o processo ativo para inspeção
printf("main_app: Execução terminada. Mantendo processo ativo por %d segundos...\n", hold_time);
while (hold_time--)
{
sleep(1);
printf("main_app: Aguardando (%d)...\n", hold_time);
}
return 0;
}
Compilação do Executável Principal
Linke o executável com a biblioteca dinâmica recém-criada, novamente usando -g para depuração.
gcc -g main_app.c -L. -lmem_leak -o main_app
O -L. instrui o linker a procurar bibliotecas no diretório atual, e -lmem_leak especifica a biblioteca a ser linkada (libmem_leak.so).
Execução e Análise
Configuração e Execução
Primeiro, defina a variável de ambiente MALLOC_TRACE para especificar onde o mtrace deve salvar o log.
export MALLOC_TRACE=./memory_trace.log
Em seguida, execute o programa em segundo plano para que você possa inspecionar seu estado enquanto ele ainda está em execução.
./main_app &
Identificando o PID e Endereços da Biblioteca
Enquanto o main_app está rodando, encontre seu Process ID (PID) usando ps.
ps aux | grep main_app
Suponha que o PID seja 12345. Use o PID para inspecionar os mapas de memória do processo em /proc/<pid>/maps</pid>. Isso revelará os endereços base nos quais as bibliotecas dinâmicas foram carregadas.
cat /proc/12345/maps
A saída será extensa, mas procure pela linha correspondente à sua biblioteca, libmem_leak.so:
...
7f59846ad000-7f59846ae000 r-xp 00000000 08:01 144706316 /home/user/dev/leak_tests/libmem_leak.so
7f59846ae000-7f59848ad000 ---p 00001000 08:01 144706316 /home/user/dev/leak_tests/libmem_leak.so
...
Observe o primeiro endereço da biblioteca (0x7f59846ad000 no exemplo), que é o endereço base. Este valor é crucial para calcular o offset do vazamento.
Análise do Log com mtrace
Após a execução do main_app (ou assim que ele terminar a seção de muntrace, o arquivo de log memory_trace.log será gerado. Use mtrace para interpretar este arquivo:
mtrace main_app ./memory_trace.log
A saída deve ser similar a esta:
Memory not freed:
-----------------
Address Size Caller
0x00000000021f84c0 0x80 at /home/user/dev/leak_tests/main_app.c:26
0x00000000021f84a0 0x20 at 0x7f59846ad72a
Aqui, vemos dois vazamentos. O primeiro, 0x00000000021f84c0 de tamanho 0x80 (128 bytes), é claramente identificado no arquivo main_app.c:26. O segundo, 0x00000000021f84a0 de tamanho 0x20 (32 bytes), é de uma localização genérica 0x7f59846ad72a. Este endereço genérico se refere à nossa biblioteca dinâmica.
Localizando o Vazamento na Biblioteca
Para o endereço da biblioteca dinâmica (0x7f59846ad72a), subtraia o endereço base da libmem_leak.so que você obteve de /proc/<pid>/maps</pid> (0x7f59846ad000):
0x7f59846ad72a - 0x7f59846ad000 = 0x72a
Este 0x72a é o offset relativo dentro da biblioteca. Agora, use addr2line com o nome da biblioteca e este offset:
addr2line -e libmem_leak.so 0x72a
A saída de addr2line finalmente revelará a localização exata do vazamento:
/home/user/dev/leak_tests/mem_lib.c:10
Isso indica que o vazamento ocorre na linha 10 do arquivo mem_lib.c, que corresponde à chamada malloc dentro da função allocate_and_leak.
É possível que o mtrace também reporte vazamentos em bibliotecas do sistema (como libc) com endereços genéricos. Geralmente, esses podem ser ignorados, pois são recursos gerenciados pelo próprio sistema e não indicam problemas de alocação no código da aplicação. A ausência de informações de depuração (-g) para essas bibliotecas do sistema é a razão pela qual o addr2line não consegue mapeá-las para o código-fonte.