Detectando Vazamentos de Memória em Bibliotecas Dinâmicas com mtrace

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.

Tags: mtrace glibc C memory-leak debugging

Publicado em 7-25 03:50