Análise abrangente da tecnologia de detecção e reparo de vazamento de memória

Este artigo foi compartilhado da comunidade Huawei Cloud " Resolvendo problemas de vazamento de memória da fonte: análise abrangente da tecnologia de detecção e reparo de vazamento de memória " por Lion Long.

1. Antecedentes: O que é detecção de vazamento de memória?

1.1. Causas de vazamentos de memória

Vazamentos de memória são um problema comum em linguagens de programação sem GC automático; como não há GC, a memória alocada precisa ser liberada pelo próprio programa. O núcleo é que a alocação e a liberação da chamada não obedecem ao princípio de abertura e fechamento e não estão emparelhadas, formando um ponteiro que é alocado, mas não liberado, resultando em vazamento de memória.
Por exemplo:

void func(tamanho_t s1) 
{ 
	void p1=malloc(s1); 
	vazio p2=malloc(s1); 
	// ... 
	grátis(p1); 
}

O segmento de código acima aloca dois blocos de memória de tamanho s1, apontados por p1 e p2. Após a execução do bloco de código, p1 é liberado, mas p2 não é liberado. Um ponteiro alocado, mas não liberado, é formado, resultando em vazamento de memória.

1.2. Consequências causadas por vazamentos de memória

À medida que a quantidade de código de engenharia aumenta, a solução de problemas de vazamento de memória torna-se extremamente problemática. A memória virtual de um programa continua crescendo e é impossível determinar com precisão se é uma necessidade do programa ou um vazamento de memória. Se houver alocação, mas não liberação, a memória do heap do processo naturalmente diminuirá cada vez mais até se esgotar . Isso fará com que o código em execução subsequente não consiga alocar memória com êxito. Pode até fazer com que o programa trave.

1.3.Como resolver vazamento de memória?

Vazamentos de memória são causados ​​por linguagens de programação sem gc automático. A primeira solução é introduzir gc. Esta é a melhor solução para curar vazamentos de memória. No entanto, tal solução perde as vantagens da linguagem c/c++. Opção 2: quando ocorre um vazamento de memória, você pode localizar com precisão qual linha de código o causou. Este também é o principal requisito de implementação para detecção de vazamento de memória.

(1) Capaz de detectar vazamentos de memória.

(2) Ser capaz de determinar qual linha de código causou o vazamento de memória.

A memória virtual de um programa continua crescendo e é impossível determinar com precisão se é uma necessidade do programa ou um vazamento de memória; se for um vazamento de memória, não sabemos em qual linha de código ele ocorre.

2. Converta endereço em informação simbólica

2.1.ferramenta addr2line

Converta endereços em nomes de arquivos e números de linha.

addr2line [-a|--addresses] 
          [-b bfdname|--target=bfdname] 
          [-C|--demangle[=style]] 
          [-e nome do arquivo|--exe=nome do arquivo] 
          [-f|--functions ] [-s|--basename] 
          [-i|--inlines] 
          [-p|--pretty-print] 
          [-j|--section=nome] 
          [-H|--help] [-V|- -versão] 
          [endereço endereço ...]

descrever:

addr2line converte endereços em nomes de arquivos e números de linha. Dado um endereço em um arquivo executável ou um deslocamento em uma seção de objeto relocável, ele usa informações de depuração para determinar o nome do arquivo e o número da linha associado a ele.

O objeto executável ou relocável a ser usado é especificado com a opção -e. O padrão é arquivar a.out. A seção no objeto relocável a ser usada é especificada com a opção -j.

addr2line possui dois modos de operação.

  • Na primeira linha de comando, os endereços hexadecimais são especificados na linha de comando e addr2line exibe o nome do arquivo e o número da linha para cada endereço.
  • No segundo comando, addr2line lê endereços hexadecimais da entrada padrão e imprime o nome do arquivo e o número da linha de cada endereço na saída padrão. Neste modo, addr2line pode ser usado em um pipeline para traduzir endereços selecionados dinamicamente.

Perceber:

addr2line converte o endereço em um número de arquivo, e o arquivo é salvo no disco. O endereço onde o programa é executado está na memória virtual (segmento de código). Em versões superiores do Linux, pode não ser possível analisar onde está o endereço está no arquivo. addr2line só pode ver o endereço da área virtual.

2.2.função dladdr1()

Converta endereços em informações simbólicas. Protótipo de função:

#define _GNU_SOURCE 
#include <dlfcn.h> 

int dladdr(void *addr, Dl_info *info); 

int dladdr1(void *addr, Dl_info *info, void **extra_info, int flags); 

// Link com -ldl.

descrever:

A função dladdr()determina se o endereço especificado em addr está localizado em um objeto compartilhado carregado pelo aplicativo chamador. Nesse caso, dladdr()serão retornadas informações sobre objetos e símbolos compartilhados que se sobrepõem a addr. Esta informação Dl_infoé retornada como uma estrutura:

typedef struct { 
    const char *dli_fname; /* Caminho do objeto compartilhado que contém o endereço */ 
    void *dli_fbase; /* Endereço base no qual o objeto compartilhado é carregado */ 
    const char *dli_sname; /* Nome do símbolo cuja definição se sobrepõe ao addr */ 
    void *dli_saddr; /* Endereço exato do símbolo nomeado em dli_sname */ 
} Dl_info;

A função dladdr1()é semelhante dladdr(), mas extra_inforetorna informações adicionais por meio de parâmetros. A informação retornada depende do valor especificado na flag, que pode ter um dos seguintes valores:

(1) RTLD_DL_LINKMAP. Obtém um ponteiro para o mapa de links do arquivo correspondente. O parâmetro extra_info é um ponteiro para <link.h>uma estrutura link_map (ou seja) definida em struct link_map**.

struct link_map { 
    ElfW(Addr) l_addr; /* Diferença entre o endereço no arquivo ELF e o endereço na memória */ 
    char *l_name; /* Caminho absoluto onde o objeto foi encontrado */ 
    ElfW(Dyn) *l_ld; /* Seção dinâmica do objeto compartilhado */ 
    struct link_map *l_next, *l_prev; 
                        /* Cadeia de objetos carregados */ 

    /* Mais campos adicionais privados para a implementação */ 
};

(2) RTLD_DL_SYMENT. Obtém um ponteiro para a entrada da tabela de símbolos ELF para o símbolo correspondente. extra_infoO argumento é um ponteiro para um ponteiro de símbolo: const ElfW(Sym)**. ElfW()Uma definição de macro converte seu argumento no nome de um tipo de dados ELF apropriado para a arquitetura de hardware. Por exemplo, em plataformas de 64 bits, ElfW(Sym)é gerado o nome do tipo de dados Elf64_Sym, que é <elf.h>definido em:

typedef struct { 
    Elf64_Word st_name; /* Nome do símbolo */ 
    unsigned char st_info; /* Tipo de símbolo e ligação */ 
    unsigned char st_other; /* Visibilidade do símbolo */ 
    Elf64_Section st_shndx; /* Índice da seção */ 
    Elf64_Addr st_value; /* Valor do símbolo */ 
    Elf64_Xword st_size; /* Tamanho do símbolo */ 
} Elf64_Sym;

Pacote:

void * ConvertToElf(void *addr) 
{ 
	Dl_info info; 
	estrutura link_map *link; 

	dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP); 

	// retornareturn 
	(void *)((size_t)addr - link->l_addr); 
}

3. Implementação de detecção de vazamento de memória

Vazamentos de memória são causados ​​pela incompatibilidade entre a alocação e a liberação de memória. Ganchos "Hijack" para funções de alocação de memória malloc/calloc/realloc e liberação de memória livre. Ele pode contar o local de alocação de memória e o local de liberação de memória para determinar se há uma correspondência.

3.1. Método 1: Use mtrace

Funções mtrace() e muntrace()

log de rastreamento mtrace. Protótipo de função:

#include <mcheck.h> 

void mtrace(void); 

void muntrace(void);

descrever:

A função mtrace() instala funções de gancho [malloc(), realloc(), memalign(), free()] para a função de alocação de memória. Essas funções de gancho registram informações de rastreamento sobre alocação e desalocação de memória. As informações de rastreamento podem ser usadas para descobrir vazamentos de memória e tentar liberar memória não alocada no programa.

muntrace()A função desativa mtrace()as funções de gancho instaladas para que as informações de rastreamento não sejam mais registradas para funções de alocação de memória. Se mtrace()nenhuma função de gancho for instalada com êxito, muntrace()nenhuma ação será executada.

Quando chamado mtrace(), ele verifica MALLOC_TRACEo valor de uma variável de ambiente, que deve conter o nome do caminho do arquivo no qual as informações de rastreamento serão registradas. Se o nome do caminho for aberto com sucesso, seu comprimento será truncado para zero.

Se not set MALLOC_TRACE, ou se o nome do caminho especificado for inválido ou não gravável, a função hook não será instalada e mtrace()não terá efeito. Nos programas set-user-ID e , é ignorado e não tem efeito. set-group-IDMALLOC_TRACEmtrace()

Funções setenv() e unsetenv()

Altere ou adicione variáveis ​​de ambiente. Protótipo de função:

#include <stdlib.h> 

int setenv(const char *nome, const char *valor, int overwrite); 

int unsetenv(const char *nome); 

/* 
Requisitos de macro de teste de recursos para glibc (consulte feature_test_macros(7)): 

setenv(), unsetenv(): 
    _BSD_SOURCE || _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 
*/

descrever:

Se o nome não existir, a função setenv() adiciona o nome da variável ao ambiente com valor valor. Se o nome existir no ambiente e a substituição for diferente de zero, seu valor será alterado para valor; se a substituição for zero, o valor do nome não será alterado (setenv() retorna um status de sucesso). Esta função copia a string apontada por nome e valor (em oposição a putenv(3)).

A função unsetenv() é usada para remover um nome de variável do ambiente. Se o nome não existir no ambiente, a função será bem-sucedida e o ambiente permanecerá inalterado.

valor de retorno:

A função setenv() retorna zero em caso de sucesso e -1 em caso de erro, e define errno para indicar a causa do erro.

A função unsetenv() retorna zero em caso de sucesso e -1 em caso de erro, e define errno para indicar a causa do erro.

erro:

Erro de código significado
ESCOLHA ÚNICA name é NULL, apontando para uma string de comprimento 0 ou contendo o caractere "=".
ENOMEMA Memória insuficiente para adicionar novas variáveis ​​ao ambiente.

Etapas para uso

(1) Chame mtrace() antes de chamar a função de alocação de memória;
(2) Chame muntrace() no final do programa ou onde não há necessidade de rastrear vazamentos de memória;
(3) Defina o valor da variável de ambiente MALLOC_TRACE ( função setenv ou comando de exportação);
(4) Traga o parâmetro -g ao compilar.
(5) Quando ocorre um vazamento de memória, use a ferramenta addr2line para localizar o local do vazamento de memória.

$ addr2line -f -e memleak -a 0x4006b8

No exemplo, memleak é o nome do programa e 0x4006b8 é o endereço do vazamento de memória.

Por exemplo:

$ cc -g t_mtrace.c -o t_mtrace 
$ exportar MALLOC_TRACE=/tmp/t 
$ ./t_mtrace 
$ mtrace ./t_mtrace $MALLOC_TRACE

Código de amostra

#include <stdio.h> 
#include <stdlib.h> 

#include <mcheck.h> 

int main(int argc,char **argv) 
{ 

	setenv("MALLOC_TRACE", "./mem.txt", 1); 

	mtrace(); 

	vazio *p1 = malloc(10); 
	vazio *p2 = malloc(20); 
	vazio *p3 = malloc(30); 

	grátis(p3); 
	grátis(p2); 
	Muntrace(); 

	unsetenv("MALLOC_TRACE"); 

	retornar 0; 
}

Conteúdo do arquivo de detecção de vazamento de memória:

$ cat mem.txt 
= Iniciar 
@ ./memleak:[0x4006b8] + 0x1886580 0xa 
@ ./memleak:[0x4006c6] + 0x18865a0 0x14 
@ ./memleak:[0x4006d4] + 0x18865c0 0x1e 
@ ./memleak:[0 x4006e4]-0x18865c0 
@ ./memleak:[0x4006f0] - 0x18865a0 
= Fim

Localize o local do vazamento de memória:

$ addr2line -f -e memleak -a 0x4006b8 
0x00000000004006b8 memleak 
principal.c 
:13

3.2. Método 2: Use definição de macro

Existem duas macros __FILE__, __func__e no Linux __LINE__, que indicam respectivamente o nome do arquivo atual, o nome da função e o número da linha.As definições de macro são usadas para encapsular a função de alocação de memória e a função de liberação.

#define malloc(tamanho) _malloc(tamanho,__FILE__,__LINE__) 
#define livre(p) _free(p,__FILE__,__LINE__)

Chame a função malloc e a função free na função _malloc e na função _free você mesmo e execute algumas operações.

Pré-requisito: As macros devem ser definidas antes da alocação de memória, para que a fase de pré-compilação substitua malloc pelo nosso próprio _mallocsum implementado _free.

Código de amostra:

#include <stdio.h> 
#include <stdlib.h> 

void *_malloc(tamanho_t tamanho,const char*nome do arquivo,linha int) 
{ 
	void *p = malloc(tamanho); 
	printf("[+] %s : %d, %p\n", nome do arquivo, linha,p); 
	retornar p; 
} 

void _free(void *p, const char*nome do arquivo, int line) 
{ 
	printf("[-] %s : %d, %p\n", nome do arquivo, linha,p); 
	retornar grátis(p); 
} 

#define malloc(tamanho) _malloc(tamanho,__FILE__,__LINE__) 
#define free(p) _free(p,__FILE__,__LINE__) 

int main(int argc,char **argv) 
{ 
	void *p1 = malloc(10); 
	vazio *p2 = malloc(20); 
	vazio *p3 = malloc(30); 

	grátis(p3); 
	grátis(p2); 

	retornar 0; 
}

Vantagens e desvantagens de usar o método de definição de macro:

(1) Vantagens: implementação simples.

(2) Desvantagem: Só é adequado para um único arquivo e a definição da macro deve ser colocada na frente do arquivo.

Imprimir usando substituição de arquivo:

Quando o programa está em execução, sempre são impressas informações desnecessárias, o que afeta a eficiência e é desagradável.Você pode criar e excluir arquivos de uma pasta para contar vazamentos de memória.

Use o valor do ponteiro como o nome do arquivo, aloque memória para criar o arquivo, libere a memória para excluir o arquivo e registre o nome do arquivo e o número da linha da memória alocada no arquivo.

Se houver arquivos na pasta, há vazamento de memória. Se não houver arquivos, não há vazamento de memória.

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

#define LEAK_FILE_PATH "./mem/%p.mem" 

void *_malloc(tamanho_t tamanho,const char*nome do arquivo,linha int) 
{ 
	void *p = malloc(tamanho); 
	//printf("[+] %s : %d, %p\n", nome do arquivo, linha,p); 
	char buff[128] = { 0 }; 
	sprintf(buff,LEAK_FILE_PATH, p); 
	ARQUIVO *fp = fopen(buff, "w"); 
	fprintf(fp, "[+] %s: %d, endereço: %p, tamanho: %ld\n", nome do arquivo, linha, p, tamanho); 
	fflush(fp);//刷新数据到文件中
	fclose(fp); 

	retornar p; 
} 

void _free(void *p, const char*nome do arquivo, int line) 
{ 
	//printf("[-] %s : %d, %p\n", nome do arquivo, linha,p); 
	char buff[128] = { 0 }; 
	sprintf(buff,LEAK_FILE_PATH,p); 
	if (unlink(buff) < 0) 
	{ 
		printf("double free %p\n", p); 
		retornar; 
	} 
	retornar grátis(p); 
} 

#define malloc(tamanho) _malloc(tamanho,__FILE__,__LINE__) 
#define free(p) _free(p,__FILE__,__LINE__) 

int main(int argc,char **argv) 
{ 

	void *p1 = malloc(10); 
	vazio *p2 = malloc(20); 
	vazio *p3 = malloc(30); 

	grátis(p3); 
	grátis(p2); 

	retornar 0; 
}

Observe que a ferramenta só pode acelerar a análise e não pode determinar 100% o vazamento de memória, pois a situação de sistemas complexos é mais complicada.

A detecção de vazamento de memória não é adicionada no início, geralmente é ativada quando necessário através de "atualização a quente", ou seja, há um sinalizador no arquivo de configuração que ativa a detecção de vazamento de memória. Ligue-o apenas quando necessário, para que não afete a eficiência do programa.

3.3. Método três: gancho (gancho)

Etapas de uso do gancho:

(1) Defina ponteiros de função.

typedef void *(*malloc_t)(tamanho_t tamanho); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULO; 
livre_t livre_f = NULO;

(2) Implementação da função, o nome da função é consistente com o nome da função alvo.

void *malloc(tamanho_t tamanho) 
{ 
// ... 
} 

void free(void *ptr) 
{ 
// ... 
}

(3) Inicialize o gancho e chame dlsym().

init_hook estático() 
{ 
	if (malloc_f == NULL) 
		malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc"); 

	if (free_f == NULL) 
		free_f = (malloc_t)dlsym(RTLD_NEXT, "grátis"); 
}

Perceber:

Ao conectar, você deve considerar que outras funções também usam a função conectada. Por exemplo, malloc também é chamado na função printf(), portanto, você precisa evitar que a recursão interna entre em um loop infinito.

Por exemplo:

#define _GNU_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

#include <dlfcn.h> 

typedef void *(*malloc_t)(tamanho_t tamanho); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULO; 
livre_t livre_f = NULO; 
void *malloc(tamanho_t tamanho) 
{ 
	printf("tamanho malloc: %ld", tamanho); 
	retornar NULO; 
} 

void free(void *ptr) 
{ 
	printf("free: %p\n",ptr); 
} 

static int init_hook() 
{ 
	if (malloc_f == NULL) 
		malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc"); 

	if (free_f == NULL) 
		free_f = (free_t)dlsym(RTLD_NEXT, "free"); 

	retornar 0; 
} 

int main(int argc,char **argv) 
{ 
	init_hook(); 
	vazio *p1 = malloc(10); 
	vazio *p2 = malloc(20); 
	vazio *p3 = malloc(30); 

	grátis(p3); 
	grátis(p2); 
	retornar 0; 
}

O código acima causará um segfault. Se você usar gdb para depurar, descobrirá que a chamada printf() na função malloc entrou em recursão infinita; um estouro de pilha.

A solução é adicionar um logotipo.

 

função interna do gcc: void *__builtin_return_address (nível inteiro não assinado)

Esta função retorna o endereço de retorno da função atual ou de um de seus chamadores. O argumento é o número de quadros para verificar a pilha de chamadas. O valor produz o endereço de retorno da função atual, o valor produz o endereço de retorno do chamador da função atual e assim por diante. Ao incorporar o comportamento esperado, a função retorna o endereço da função retornada. Para contornar esse problema, use atributos de função.

 

nível:

Este parâmetro deve ser um número inteiro constante.

Em alguns computadores, o endereço de retorno de qualquer função diferente da função atual pode não ser determinado; neste caso, ou quando o topo da pilha é atingido, esta função retorna um valor não especificado. Além disso, pode ser usado para determinar se o topo da pilha foi alcançado.

Código de amostra:

#define _GNU_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

#define LEAK_FILE_PATH "./mem/%p.mem" 

#include <dlfcn.h> 

static int enable_malloc_hook = 1; 
static int enable_free_hook = 1; 

typedef void *(*malloc_t)(tamanho_t tamanho); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL; 
void *malloc(tamanho_t tamanho) 
{ 
	void * p; 
	if (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 
		
		p = malloc_f(size); 
		printf("malloc size: %ld,p=%p\n", size,p); // 
		Obtém o local de onde malloc é chamado a camada anterior Endereço, este endereço é usado pela ferramenta addr2line para convertê-lo em um número de linha 
		void *caller = __builtin_return_address(0); 

		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, p); 
		FILE * fp = fopen(buff, "w"); 
		fprintf(fp, "[+] %p , addr: %p, size: %ld\n", caller, p, size); fflush (fp);// 
		Atualizar dados para o arquivo 
		fclose( fp); 

		enable_malloc_hook = 1; 
	} 
	else 
		p = malloc_f(size); 
	
	return p; 
} 

void free(void *p) 
{ 
	if (enable_free_hook) 
	{ 
		enable_free_hook = 0; 
		//printf("free: %p\n", p); 
		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, p); 
		if (unlink(buff) < 0) 
		{ 
			printf("double free %p\n", p ); 
			//enable_free_hook = 1; 
			free_f(p); 
			return; 
		} 
		free_f(p); 
		enable_free_hook = 1; 
	} 
	else 
		free_f(p); 
} 

static int init_hook() 
{ 
	if (malloc_f == NULL) 
		malloc_f = (malloc_t )dlsym(RTLD_NEXT, "malloc"); 

	if (free_f == NULL) 
		free_f = (free_t)dlsym(RTLD_NEXT, "free"); 

	return 0; 
} 

int main(int argc,char **argv) 
{ 

	init_hook() ; 
	void *p1 = malloc(10); 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 

	free(p3); 
	free(p2); 
	
	return 0; 
}

O endereço obtido __builtin_return_address(0)precisa da ferramenta addr2line para convertê-lo em um número de linha de arquivo para localizar o local do vazamento de memória.

3.4. Método 4: Use __libc_malloc e __libc_free

A ideia é a mesma do hook, porque as chamadas subjacentes de malloc e free também são __libc_mallocand __libc_free.

Código de amostra:

#define _GNU_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

// Lembre-se de criar uma pasta mem manualmente 
#define LEAK_FILE_PATH "./mem/%p.mem" 

extern void * __libc_malloc (tamanho_t tamanho); 
extern void __libc_free(void *p); 

static int enable_malloc_hook = 1; 

void *malloc(tamanho_t tamanho) 
{ 
	void *p; 
	if (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 

		p = __libc_malloc(tamanho); 
		printf ( "malloc size: %ld,p=%p\n", size, p); // 
		Obtém o endereço do local onde malloc é chamado na camada anterior. Este endereço é usado pela ferramenta addr2line para convertê-lo em um número da linha 
		void *caller = __builtin_return_address (0); 

		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, p); 
		FILE *fp = fopen(buff, "w"); 
		fprintf(fp, "[+ ] %p , addr: % p, size: %ld\n", caller, p, size); 
		fflush(fp);//Atualiza dados para o arquivo 
		fclose(fp); 

		enable_malloc_hook = 1; 
	} 
	else 
		p = __libc_malloc (tamanho); 

	return p; 
} 

void free(void *p) 
{ 
	char buff[128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, p); 
	if (unlink(buff) < 0) 
	{ 
		printf("double free %p\n", p ); 
	} 
	__libc_free(p); 
} 

int main(int argc,char **argv) 
{ 

	void *p1 = malloc(10); 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 

	grátis(p3); 
	grátis(p2); 

	return 0; 
}

3.5. Método 5: __malloc_hook (não recomendado)

Este método é adequado para versões mais antigas do Linux. Pertence à API da versão antiga. É __malloc_hookum método de ponteiro e um valor fixo. É essencialmente uma tecnologia de gancho.

Protótipo de função:

#include <malloc.h> 

void *(*__malloc_hook)(size_t tamanho, const void *caller); 

void *(*__realloc_hook)(void *ptr, size_t size, const void *caller); 

void *(*__memalign_hook)(alinhamento size_t, tamanho size_t, const void *caller); 

void (*__free_hook)(void *ptr, const void *caller); 

vazio (*__malloc_initialize_hook)(void); 

vazio (*__after_morecore_hook)(void);

descrever:

A biblioteca GNUC permite modificar o comportamento de malloc, realloc e free especificando as funções de gancho apropriadas. Por exemplo, você pode usar esses ganchos para ajudar a depurar programas que usam alocação dinâmica de memória.

A variável __malloc_initialize_hook aponta para uma função que é chamada uma vez ao inicializar a implementação do malloc. Esta é uma variável fraca, portanto pode ser substituída em sua aplicação usando uma definição como esta:

void(*__malloc_initialize_hook)(void)=my_init_hook();

Agora a função my_init_hook() pode inicializar todos os ganchos.

__malloc_hookOs protótipos das quatro funções apontadas por , __realloc_hooks, __memalign_hooke, __free_hookyestão respectivamente alinhados com as funções malloc, realloc e memalign.

plano:

Troque o método, personalize ponteiros de função, implemente funções específicas e troque as funções que você implementa com o __malloc_hook fornecido pelo sistema.

Código de amostra:

#define _GNU_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

// 要记得手动创建一个mem文件夹
#define LEAK_FILE_PATH "./mem/%p.mem" 

#include < malloc.h> 

/* 
typedef void *(*malloc_t)(tamanho_t tamanho); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULO; 
livre_t livre_f = NULO; 
*/ 

static int enable_malloc_hook = 1; 

vazio estático my_init_hook(void); 
vazio estático *my_malloc_hook(tamanho_t, const void *); 
static void my_free_hook(void *, const void *); 

/* Variáveis ​​para salvar os ganchos originais. */ 
static void *(*old_malloc_hook)(size_t, const void *); 
static void(*old_free_hook)(void *, const void *); 
/* Substitui o gancho de inicialização da biblioteca C. */ 
void(*__malloc_initialize_hook) (void) = meu_init_hook; 

static void 
my_init_hook(void) 
{ 
	old_malloc_hook = __malloc_hook; 
	__malloc_hook = meu_malloc_hook; 

	old_free_hook = __free_hook; 
	__free_hook = meu_free_hook; 
} 

static void * 
my_malloc_hook(tamanho_t tamanho, const void *chamador) 
{ 
	void *resultado; 

	/* Restaura todos os ganchos antigos */ 
	__malloc_hook = old_malloc_hook; 

	/* Chama recursivamente */ 
	//resultado = malloc(size); 

	if (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 

		resultado = malloc(tamanho); 
		/* printf() pode chamar malloc(), então proteja-o também. */ 
		printf("malloc(%u) chamado de %p retorna %p\n", 
			(unsigned int)tamanho, chamador, resultado); 

		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, resultado); 

		ARQUIVO *fp = fopen(buff, "w"); 
		fprintf(fp, "[+] %p , endereço: %p, tamanho: %ld\n", chamador, resultado, tamanho); 
		fflush(fp);//刷新数据到文件中
		fclose(fp); 

		enable_malloc_hook = 1; 
	} 
	senão 
		resultado = malloc(tamanho); 

	/* Salva ganchos subjacentes */ 
	old_malloc_hook = __malloc_hook; 
	/* Restaura nossos próprios ganchos */ 
	__malloc_hook = my_malloc_hook; 

	resultado de retorno; 
} 

static void my_free_hook(void *ptr, const void *caller) 
{ 
	__free_hook = old_free_hook; 

	grátis(ptr); 

	old_free_hook = __free_hook; 

	/* printf() pode chamar malloc(), então proteja-o também. */ 
	printf("free(%p) chamado de %p\n", 
		ptr, caller); 

	char buff[128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, ptr); 
	if (desvincular(buff) < 0) 
	{
		printf("duplo livre %p\n", ptr); 
	} 

	/* Restaure nossos próprios ganchos */ 
	__free_hook = my_free_hook; 
} 

int main(int argc,char **argv) 
{ 

	my_init_hook(); 

	vazio *p1 = malloc(10); 
	vazio *p2 = malloc(20); 
	vazio *p3 = malloc(30); 

	grátis(p3); 
	grátis(p2); 

	retornar 0; 
}

Um aviso aparecerá durante a compilação e o sistema não recomenda este método.

4. Código de exemplo completo

O código é relativamente longo. Para evitar que fique muito longo e difícil de ler, não o postei aqui. Se necessário, você pode entrar em contato com o blogueiro ou seguir a conta pública "Lion" do WeChat para obtê-lo.

Resumir

  • O núcleo da detecção de vazamento de memória é saber se há um vazamento de memória e onde ocorreu o vazamento de memória.
  • As formas de detectar vazamentos de memória incluem: mtrace, hook, definição de macro, libc_malloc, __malloc_hook. Entre eles, mtrace precisa definir a variável de ambiente MALLOC_TRACE e precisa ser reiniciado; a definição de macro é adequada para arquivos únicos; __malloc_hook foi eliminado.
  • Adicionar -g ao compilar o programa pode usar a ferramenta addr2line para localizar o local do vazamento de memória no arquivo.
  • Para melhorar a eficiência do programa, o programa de lançamento usa um método de "atualização a quente" para definir o identificador do arquivo de configuração para detecção de vazamento de memória quando necessário.

Clique para seguir e conhecer as novas tecnologias da Huawei Cloud o mais rápido possível~

Lei Jun: A versão oficial do novo sistema operacional da Xiaomi, ThePaper OS, foi empacotada. A janela pop-up na página de loteria do Gome App insulta seu fundador. O Ubuntu 23.10 foi lançado oficialmente. Você também pode aproveitar a sexta-feira para atualizar! Episódio de lançamento do Ubuntu 23.10: A imagem ISO foi "recuperada" com urgência devido a conter discurso de ódio. Um estudante de doutorado de 23 anos corrigiu o "bug fantasma" de 22 anos no Firefox. O desktop remoto RustDesk 1.2.3 foi lançado, Wayland aprimorado para suportar a versão TiDB 7.4: Oficial compatível com MySQL 8.0. Depois de desconectar o receptor USB da Logitech, o kernel do Linux travou. O mestre usou o Scratch para esfregar o simulador RISC-V e executou com sucesso o kernel do Linux. JetBrains lançou o Writerside, uma ferramenta para criação de documentos técnicos.
{{o.nome}}
{{m.nome}}

Acho que você gosta

Origin my.oschina.net/u/4526289/blog/10120087
Recomendado
Clasificación