Análisis completo de la tecnología de detección y reparación de fugas de memoria.

Este artículo es compartido por la comunidad en la nube de Huawei " Resolución de problemas de pérdida de memoria desde la fuente: análisis completo de la tecnología de detección y reparación de fugas de memoria " por Lion Long.

1. Antecedentes: ¿Qué es la detección de pérdidas de memoria?

1.1 Causas de las pérdidas de memoria

Las pérdidas de memoria son un problema común en los lenguajes de programación sin gc automático; debido a que no hay gc, el programa mismo debe liberar la memoria asignada. El núcleo es que la asignación y liberación de llamadas no cumplen con el principio de apertura y cierre y no están emparejadas, lo que forma un puntero que se asigna pero no se libera, lo que provoca una pérdida de memoria.
Por ejemplo:

función vacía (tamaño_t s1) 
{ 
	vacío p1 = malloc (s1); 
	vacío p2=malloc(s1); 
	// ... 
	libre(p1); 
}

El segmento de código anterior asigna dos bloques de memoria de tamaño s1, señalados por p1 y p2. Una vez ejecutado el bloque de código, se libera p1, pero no se libera p2. Se forma un puntero que se asigna pero no se libera, lo que provoca una pérdida de memoria.

1.2 Consecuencias causadas por pérdidas de memoria

A medida que aumenta la cantidad de código de ingeniería, la solución de problemas de pérdida de memoria se vuelve extremadamente problemática: la memoria virtual de un programa sigue creciendo y es imposible determinar con precisión si se trata de una necesidad del programa o una pérdida de memoria. Si hay asignación pero no liberación, la memoria del montón de procesos naturalmente será cada vez menor hasta que se agote . Esto hará que el código que se ejecute posteriormente no pueda asignar memoria correctamente. Incluso puede provocar que el programa falle.

1.3 ¿Cómo solucionar la pérdida de memoria?

Las pérdidas de memoria son causadas por lenguajes de programación sin gc automático, la primera solución es introducir gc. Esta es la mejor solución para curar las pérdidas de memoria. Sin embargo, esta solución pierde las ventajas del lenguaje c/c++. Opción 2: cuando se produce una pérdida de memoria, puede localizar con precisión qué línea de código la causó. Este es también el requisito de implementación principal para la detección de pérdidas de memoria.

(1) Capacidad para detectar pérdidas de memoria.

(2) Ser capaz de determinar qué línea de código provocó la pérdida de memoria.

La memoria virtual de un programa sigue creciendo y es imposible determinar con precisión si se trata de una necesidad del programa o una pérdida de memoria; si se trata de una pérdida de memoria, no sabemos en qué línea de código ocurre.

2. Convertir la dirección en información simbólica.

2.1 herramienta addr2line

Convierta direcciones en nombres de archivos y números de línea.

addr2line [-a|--addresses] 
          [-b bfdname|--target=bfdname] 
          [-C|--demangle[=estilo]] 
          [-e nombre de archivo|--exe=nombre de archivo] 
          [-f|--funciones ] [-s|--basename] 
          [-i|--inlines] 
          [-p|--pretty-print] 
          [-j|--section=nombre] 
          [-H|--help] [-V|- -versión] 
          [dirección dirección...]

describir:

addr2line convierte direcciones en nombres de archivos y números de línea. Dada una dirección en un archivo ejecutable o un desplazamiento en una sección de objeto reubicable, utiliza información de depuración para determinar el nombre del archivo y el número de línea asociado con él.

El objeto ejecutable o reubicable que se utilizará se especifica con la opción -e. El valor predeterminado es presentar a.out. La sección del objeto reubicable que se utilizará se especifica con la opción -j.

addr2line tiene dos modos de funcionamiento.

  • En la primera línea de comando, las direcciones hexadecimales se especifican en la línea de comando y addr2line muestra el nombre del archivo y el número de línea para cada dirección.
  • En el segundo comando, addr2line lee direcciones hexadecimales de la entrada estándar e imprime el nombre del archivo y el número de línea de cada dirección en la salida estándar. En este modo, addr2line se puede utilizar en una canalización para traducir direcciones seleccionadas dinámicamente.

Aviso:

addr2line convierte la dirección en un número de archivo y el archivo se guarda en el disco. La dirección donde se ejecuta el programa está en la memoria virtual (segmento de código). En versiones superiores de Linux, es posible que no sea posible analizar dónde se encuentra la dirección. está en el archivo. addr2line solo puede mirar la dirección del área virtual.

2.2 Función dladdr1()

Convierte direcciones en información simbólica. Prototipo de función:

#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); 

// Enlace con -ldl.

describir:

La función dladdr()determina si la dirección especificada en addr se encuentra en un objeto compartido cargado por la aplicación que llama. Si es así, dladdr()se devuelve información sobre los objetos y símbolos compartidos que se superponen a la dirección. Esta información Dl_infose devuelve como una estructura:

typedef estructura { 
    const char *dli_fname; /* Nombre de ruta del objeto compartido que contiene la dirección */ 
    void *dli_fbase; /* Dirección base en la que se carga el objeto compartido */ 
    const char *dli_sname; /* Nombre del símbolo cuya definición se superpone a addr */ 
    void *dli_saddr; /* Dirección exacta del símbolo nombrado en dli_sname */ 
} Dl_info;

La función dladdr1()es similar dladdr()pero extra_infodevuelve información adicional a través de parámetros. La información devuelta depende del valor especificado en la bandera, que puede tener uno de los siguientes valores:

(1) RTLD_DL_LINKMAP. Obtiene un puntero al mapa de vínculos del archivo coincidente. El parámetro extra_info es un puntero a <link.h>una estructura link_map (es decir,) definida en struct link_map**.

struct link_map { 
    ElfW(Dirección) l_addr; /* Diferencia entre la dirección en el archivo ELF y la dirección en la memoria */ 
    char *l_name; /* Ruta absoluta donde se encontró el objeto */ 
    ElfW(Dyn) *l_ld; /* Sección dinámica del objeto compartido */ 
    struct link_map *l_next, *l_prev; 
                        /* Cadena de objetos cargados */ 

    /* Más campos adicionales privados para la implementación */ 
};

(2) RTLD_DL_SYMENT. Obtiene un puntero a la entrada de la tabla de símbolos ELF para el símbolo coincidente. extra_infoEl argumento es un puntero a un puntero de símbolo: const ElfW(Sym)**. ElfW()Una definición de macro convierte su argumento en el nombre de un tipo de datos ELF apropiado para la arquitectura del hardware. Por ejemplo, en plataformas de 64 bits ElfW(Sym)se genera el nombre del tipo de datos Elf64_Sym, el cual se <elf.h>define en:

estructura typedef { 
    Elf64_Word st_name; /* Nombre del símbolo */ 
    unsigned char st_info; /* Tipo de símbolo y enlace */ 
    unsigned char st_other; /* Visibilidad del símbolo */ 
    Elf64_Section st_shndx; /* Índice de sección */ 
    Elf64_Addr st_value; /* Valor del símbolo */ 
    Elf64_Xword st_size; /* Tamaño del símbolo */ 
} Elf64_Sym;

Paquete:

vacío * ConvertToElf(void *addr) 
{ 
	Dl_info información; 
	estructura link_map *enlace; 

	dladdr1(dirección, &info, (void **)&link, RTLD_DL_LINKMAP); 

	// 偏差纠正
	return (void *)((size_t)addr - link->l_addr); 
}

3. Implementación de la detección de fugas de memoria.

Las pérdidas de memoria se deben a la falta de coincidencia entre la asignación de memoria y la liberación de memoria. Ganchos de "secuestro" para funciones de asignación de memoria malloc/calloc/realloc y liberación de memoria gratuita. Puede contar la ubicación de asignación de memoria y la ubicación de liberación de memoria para determinar si hay una coincidencia.

3.1 Método 1: utilizar mtrace

Funciones mtrace() y muntrace()

registro de seguimiento de mtrace. Prototipo de función:

#incluye <mcheck.h> 

void mtrace(void); 

muntrace nulo(nulo);

describir:

La función mtrace() instala funciones de enlace [malloc(), realloc(), memalign(), free()] para la función de asignación de memoria. Estas funciones de enlace registran información de seguimiento sobre la asignación y desasignación de memoria. La información de seguimiento se puede utilizar para descubrir pérdidas de memoria e intentar liberar memoria no asignada en el programa.

muntrace()La función deshabilita mtrace()las funciones de enlace instaladas para que la información de seguimiento ya no se registre para las funciones de asignación de memoria. Si mtrace()no se instala correctamente ninguna función de enlace, muntrace()no se realiza ninguna acción.

Cuando se llama mtrace(), verifica MALLOC_TRACEel valor de una variable de entorno, que debe contener la ruta del archivo en el que se registrará la información de seguimiento. Si el nombre de la ruta se abre correctamente, su longitud se truncará a cero.

Si no se establece MALLOC_TRACE, o si el nombre de ruta que especifica no es válido o no se puede escribir, la función de enlace no se instalará y mtrace()no tendrá ningún efecto. En los programas set-user-ID y , se ignora y no tiene ningún efecto. set-group-IDMALLOC_TRACEmtrace()

Funciones setenv() y unsetenv()

Cambie o agregue variables de entorno. Prototipo de función:

#include <stdlib.h> 

int setenv(const char *nombre, const char *valor, int sobrescribir); 

int unsetenv(const char *nombre); 

/* 
Requisitos de macros de prueba de funciones para glibc (consulte feature_test_macros(7)): 

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

describir:

Si el nombre no existe, la función setenv() agrega el nombre de la variable al entorno con el valor valor. Si el nombre existe en el entorno y la sobrescritura no es cero, su valor se cambiará a valor; si la sobrescritura es cero, el valor del nombre no cambiará (setenv() devuelve un estado exitoso). Esta función copia la cadena señalada por nombre y valor (a diferencia de putenv(3)).

La función unsetenv() se utiliza para eliminar un nombre de variable del entorno. Si el nombre no existe en el entorno, la función tiene éxito y el entorno no cambia.

valor de retorno:

La función setenv() devuelve cero en caso de éxito y -1 en caso de error, y establece errno para indicar la causa del error.

La función unsetenv() devuelve cero en caso de éxito y -1 en caso de error, y establece errno para indicar la causa del error.

error:

código de error significado
ELECCIÓN ÚNICA El nombre es NULL, apunta a una cadena de longitud 0 o contiene el carácter "=".
ENOMEMA Memoria insuficiente para agregar nuevas variables al entorno.

Pasos para el uso

(1) Llame a mtrace() antes de llamar a la función de asignación de memoria;
(2) Llame a muntrace() al final del programa o cuando no haya necesidad de realizar un seguimiento de las pérdidas de memoria;
(3) Establezca el valor de la variable de entorno MALLOC_TRACE ( función setenv o comando de exportación);
(4) Traiga el parámetro -g al compilar.
(5) Cuando se produzca una pérdida de memoria, utilice la herramienta addr2line para localizar la ubicación de la pérdida de memoria.

$ addr2line -f -e memleak -a 0x4006b8

En el ejemplo, memleak es el nombre del programa y 0x4006b8 es la dirección de la pérdida de memoria.

Por ejemplo:

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

Código de muestra

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

#include <mcheck.h> 

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

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

	mtraza(); 

	vacío *p1 = malloc(10); 
	vacío *p2 = malloc(20); 
	vacío *p3 = malloc(30); 

	libre(p3); 
	libre(p2); 
	muntrace(); 

	unsetenv("MALLOC_TRACE"); 

	devolver 0; 
}

Contenido del archivo de detección de pérdida de memoria:

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

Localice la ubicación de la pérdida de memoria:

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

3.2 Método 2: utilizar definición de macro

Hay dos macros __FILE__, __func__y en Linux __LINE__, que indican respectivamente el nombre del archivo actual, el nombre de la función y el número de línea. Las definiciones de macro se utilizan para encapsular la función de asignación de memoria y la función de liberación.

#definir malloc(tamaño) _malloc(tamaño,__FILE__,__LINE__) 
#definir free(p) _free(p,__FILE__,__LINE__)

Llame a la función malloc y a la función free en la función _malloc y a la función _free usted mismo y realice algunas operaciones.

Requisito previo: las macros deben definirse antes de la asignación de memoria, de modo que la fase de precompilación reemplace malloc con nuestra propia _mallocsuma implementada _free.

Código de muestra:

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

void *_malloc(size_t size,const char*filename,int line) 
{ 
	void *p = malloc(size); 
	printf("[+] %s: %d, %p\n", nombre de archivo, línea,p); 
	devolver p; 
} 

void _free(void *p, const char*nombre de archivo, int línea) 
{ 
	printf("[-] %s: %d, %p\n", nombre de archivo, línea,p); 
	devolver gratis(p); 
} 

#define malloc(tamaño) _malloc(tamaño,__FILE__,__LINE__) 
#define free(p) _free(p,__FILE__,__LINE__) 

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

	libre(p3); 
	libre(p2); 

	devolver 0; 
}

Ventajas y desventajas de utilizar el método de definición de macros:

(1) Ventajas: implementación simple.

(2) Desventaja: solo es adecuado para un solo archivo y la definición de macro debe colocarse al frente del archivo.

Imprimir usando reemplazo de archivos:

Cuando el programa se está ejecutando, siempre se imprime información innecesaria, lo que afecta la eficiencia y es antiestético. Puede crear y eliminar archivos en una carpeta para contar las pérdidas de memoria.

Utilice el valor del puntero como nombre de archivo, asigne memoria para crear el archivo, libere la memoria para eliminar el archivo y registre el nombre del archivo y el número de línea de la memoria asignada en el archivo.

Si hay archivos en la carpeta, hay una pérdida de memoria. Si no hay archivos, no hay pérdida de memoria.

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

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

void *_malloc(size_t size,const char*filename,int line) 
{ 
	vacío *p = malloc(tamaño); 
	//printf("[+] %s: %d, %p\n", nombre de archivo, línea,p); 
	beneficio de carbón [128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, p); 
	ARCHIVO *fp = fopen(buff, "w"); 
	fprintf(fp, "[+] %s: %d, dirección: %p, tamaño: %ld\n", nombre de archivo, línea, p, tamaño); 
	fflush(fp);//刷新数据到文件中
	fclose(fp); 

	devolver p; 
} 

void _free(void *p, const char*nombre de archivo, int línea) 
{ 
	//printf("[-] %s: %d, %p\n", nombre de archivo, línea,p); 
	beneficio de carbón [128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, p); 
	if (unlink(buff) < 0) 
	{ 
		printf("doble libre %p\n", p); 
		devolver; 
	} 
	devolver gratis(p); 
} 

#define malloc(tamaño) _malloc(tamaño,__FILE__,__LINE__) 
#define free(p) _free(p,__FILE__,__LINE__) 

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

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

	libre(p3); 
	libre(p2); 

	devolver 0; 
}

Tenga en cuenta que la herramienta solo puede acelerar el análisis y no puede determinar al 100% la pérdida de memoria, porque la situación de los sistemas complejos es más complicada.

La detección de pérdidas de memoria no se agrega al principio, generalmente se activa cuando es necesario mediante una "actualización en caliente", es decir, hay una bandera en el archivo de configuración que activa la detección de pérdidas de memoria. Actívelo solo cuando sea necesario, para que no afecte la eficiencia del programa.

3.3 Método tres: gancho (gancho)

Pasos de uso del gancho:

(1) Definir punteros de función.

typedef void *(*malloc_t)(size_t tamaño); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL;

(2) Implementación de la función, el nombre de la función es coherente con el nombre de la función de destino.

void *malloc(size_t tamaño) 
{ 
// ... 
} 

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

(3) Inicialice el gancho y llame a dlsym().

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

	si (free_f == NULL) 
		free_f = (malloc_t)dlsym(RTLD_NEXT, "gratis"); 
}

Aviso:

Al realizar el enlace, debe tener en cuenta que otras funciones también utilizan la función de enlace, por ejemplo, malloc también se llama en la función printf (), por lo que debe evitar que la recursividad interna entre en un bucle infinito.

Por ejemplo:

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

#incluye <dlfcn.h> 

typedef void *(*malloc_t)(size_t tamaño); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL; 
void *malloc(size_t tamaño) 
{ 
	printf("malloc tamaño: %ld", tamaño); 
	devolver NULO; 
} 

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

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

	si (free_f == NULL) 
		free_f = (free_t)dlsym(RTLD_NEXT, "gratis"); 

	devolver 0; 
} 

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

	libre(p3); 
	libre(p2); 
	devolver 0; 
}

El código anterior provocará un error de segmentación. Al utilizar gdb para depurar, encontrará que la llamada printf() en la función malloc ha entrado en recursividad infinita; un desbordamiento de pila.

La solución es añadir un logo.

 

Función incorporada de gcc: void *__builtin_return_address (nivel entero sin signo)

Esta función devuelve la dirección de retorno de la función actual o de uno de sus llamadores. El argumento es el número de fotogramas para escanear la pila de llamadas. El valor produce la dirección de retorno de la función actual, el valor produce la dirección de retorno de la persona que llama a la función actual, y así sucesivamente. Al incluir el comportamiento esperado, la función devuelve la dirección de la función devuelta. Para solucionar este problema, utilice atributos de función.

 

nivel:

Este parámetro debe ser un número entero constante.

En algunas computadoras, es posible que no se determine la dirección de retorno de cualquier función que no sea la función actual; en este caso, o cuando se alcanza la parte superior de la pila, esta función devuelve un valor no especificado. Además, se puede utilizar para determinar si se ha alcanzado la parte superior de la pila.

Código de muestra:

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

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

#incluye <dlfcn.h> 

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

typedef void *(*malloc_t)(size_t tamaño); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL; 
void *malloc(size_t tamaño) 
{ 
	void * p; 
	if (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 
		
		p = malloc_f(size); 
		printf("malloc size: %ld,p=%p\n", size,p); // 
		Obtener la ubicación desde donde se llama a malloc la dirección de la capa anterior, esta dirección es utilizada por la herramienta addr2line para convertirla en un número de línea 
		void *caller = __builtin_return_address(0); 

		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, p); 
		FILE * fp = fopen(buff, "w"); 
		fprintf(fp, "[+] %p, dirección: %p, tamaño: %ld\n", llamador, p, tamaño); fflush (fp);// 
		Actualizar datos al archivo 
		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("doble libre %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"); 

	devuelve 0; 
} 

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

	init_hook() ; 
	vacío *p1 = malloc (10); 
	vacío *p2 = malloc(20); 
	vacío *p3 = malloc(30); 

	libre(p3); 
	libre(p2); 
	
	retorno 0; 
}

La dirección obtenida __builtin_return_address(0)necesita la herramienta addr2line para convertirla en un número de línea de archivo para localizar la ubicación de la pérdida de memoria.

3.4 Método 4: utilizar __libc_malloc y __libc_free

La idea es la misma que la del gancho, porque las llamadas subyacentes de malloc y free también son __libc_mallocy __libc_free.

Código de muestra:

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

// Recuerda crear una carpeta mem manualmente 
#define LEAK_FILE_PATH "./mem/%p.mem" 

extern void * __libc_malloc (tamaño_t tamaño); 
extern void __libc_free(void *p); 

static int enable_malloc_hook = 1; 

void *malloc(size_t tamaño) 
{ 
	void *p; 
	if (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 

		p = __libc_malloc(tamaño); 
		printf ( "malloc size: %ld,p=%p\n", size, p); // 
		Obtiene la dirección del lugar donde se llama a malloc en la capa anterior. Esta dirección es utilizada por la herramienta addr2line para convertirla en un número de línea 
		void *caller = __builtin_return_address (0); 

		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, p); 
		FILE *fp = fopen(buff, "w"); 
		fprintf(fp, "[+ ] %p, dirección: % p, tamaño: %ld\n", llamador, p, tamaño); 
		fflush(fp);//Actualiza datos en el archivo 
		fclose(fp); 

		enable_malloc_hook = 1; 
	} 
	else 
		p = __libc_malloc (tamaño); 

	return p; 
} 

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

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

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

	gratis( p3); 
	gratis(p2); 

	retorno 0; 
}

3.5 Método 5: __malloc_hook (no recomendado)

Este método es adecuado para versiones anteriores de Linux, pertenece a la API de la versión anterior, es __malloc_hookun método de puntero y un valor fijo. Es esencialmente una tecnología de gancho.

Prototipo de función:

#include <malloc.h> 

void *(*__malloc_hook)(size_t tamaño, const void *caller); 

void *(*__realloc_hook)(void *ptr, size_t tamaño, const void *caller); 

void *(*__memalign_hook)(alineación size_t, tamaño size_t, const void *caller); 

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

vacío (*__malloc_initialize_hook)(vacío); 

vacío (*__after_morecore_hook)(vacío);

describir:

La biblioteca GNUC le permite modificar el comportamiento de malloc, realloc y free especificando las funciones de enlace apropiadas. Por ejemplo, puede utilizar estos enlaces para ayudar a depurar programas que utilizan asignación de memoria dinámica.

La variable __malloc_initialize_hook apunta a una función que se llama una vez al inicializar la implementación de malloc. Esta es una variable débil, por lo que puede anularse en su aplicación usando una definición como esta:

vacío(*__malloc_initialize_hook)(void)=my_init_hook();

Ahora la función my_init_hook() puede inicializar todos los ganchos.

__malloc_hookLos prototipos de las cuatro funciones señaladas por , __realloc_hooks, __memalign_hooke, __free_hookyestán alineados respectivamente con las funciones malloc, realloc y memalign.

plan:

Intercambie métodos, personalice punteros de funciones, implemente funciones específicas e intercambie las funciones que implemente con el __malloc_hook proporcionado por el sistema.

Código de muestra:

#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)(size_t tamaño); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL; 
*/ 

static int enable_malloc_hook = 1; 

vacío estático my_init_hook(void); 
vacío estático *my_malloc_hook(size_t, const void *); 
vacío estático my_free_hook(void *, const void *); 

/* Variables para guardar los ganchos originales. */ 
static void *(*old_malloc_hook)(size_t, const void *); 
static void(*old_free_hook)(void *, const void *); 
/* Anula el gancho de inicialización de la biblioteca C. */ 
void(*__malloc_initialize_hook) (void) = my_init_hook; 

vacío estático 
my_init_hook(void) 
{ 
	old_malloc_hook = __malloc_hook; 
	__malloc_hook = mi_malloc_hook; 

	old_free_hook = __free_hook; 
	__free_hook = mi_free_hook; 
} 

static void * 
my_malloc_hook(size_t tamaño, const void *persona que llama) 
{ 
	void *resultado; 

	/* Restaurar todos los ganchos antiguos */ 
	__malloc_hook = old_malloc_hook; 

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

	si (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 

		resultado = malloc(tamaño); 
		/* printf() podría llamar a malloc(), así que protégelo también. */ 
		printf("malloc(%u) llamado desde %p devuelve %p\n", 
			(unsigned int)tamaño, llamador, resultado); 

		beneficio de carbón [128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, resultado); 

		ARCHIVO *fp = fopen(buff, "w"); 
		fprintf(fp, "[+] %p, dirección: %p, tamaño: %ld\n", llamador, resultado, tamaño); 
		fflush(fp);//刷新数据到文件中
		fclose(fp); 

		enable_malloc_hook = 1; 
	} 
	else 
		resultado = malloc(tamaño); 

	/* Guardar ganchos subyacentes */ 
	old_malloc_hook = __malloc_hook; 
	/* Restaurar nuestros propios ganchos */ 
	__malloc_hook = my_malloc_hook; 

	resultado de devolución; 
} 

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

	libre(ptr); 

	old_free_hook = __free_hook; 

	/* printf() podría llamar a malloc(), así que protégelo también. */ 
	printf("free(%p) llamado desde %p\n", 
		ptr, llamador); 

	beneficio de carbón [128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, ptr); 
	if (unlink(buff) < 0) 
	{
		printf("doble libre %p\n", ptr); 
	} 

	/* Restaurar nuestros propios ganchos */ 
	__free_hook = my_free_hook; 
} 

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

	my_init_hook(); 

	vacío *p1 = malloc(10); 
	vacío *p2 = malloc(20); 
	vacío *p3 = malloc(30); 

	libre(p3); 
	libre(p2); 

	devolver 0; 
}

Aparecerá una advertencia durante la compilación y el sistema no recomienda este método.

4. Código de muestra completo

El código es relativamente largo, para evitar que sea demasiado largo y difícil de leer, no lo he publicado aquí. Si es necesario, puede ponerse en contacto con el blogger o seguir la cuenta pública de WeChat "Lion" para obtenerlo.

Resumir

  • El núcleo de la detección de pérdidas de memoria es saber si existe una pérdida de memoria y dónde se produjo la pérdida de memoria.
  • Las formas de detectar pérdidas de memoria incluyen: mtrace, gancho, definición de macro, libc_malloc, __malloc_hook. Entre ellos, mtrace necesita configurar la variable de entorno MALLOC_TRACE y debe reiniciarse; la definición de macro es adecuada para archivos individuales; __malloc_hook se ha eliminado.
  • Agregar -g al compilar el programa puede usar la herramienta addr2line para localizar la ubicación de la pérdida de memoria en el archivo.
  • Para mejorar la eficiencia del programa, el programa de lanzamiento utiliza un método de "actualización en caliente" para configurar el identificador del archivo de configuración para la detección de pérdidas de memoria cuando sea necesario.

Haga clic para seguir y conocer las nuevas tecnologías de Huawei Cloud lo antes posible ~

Lei Jun: La versión oficial del nuevo sistema operativo de Xiaomi, ThePaper OS, ha sido empaquetada. La ventana emergente en la página de lotería de la aplicación Gome insulta a su fundador. Ubuntu 23.10 se lanza oficialmente. ¡También podrías aprovechar el viernes para actualizar! Episodio de lanzamiento de Ubuntu 23.10: La imagen ISO fue "retirada" urgentemente debido a que contenía discurso de odio. Un estudiante de doctorado de 23 años solucionó el "error fantasma" de 22 años en Firefox. Se lanzó el escritorio remoto RustDesk 1.2.3. Wayland mejorado para soportar TiDB 7.4 Lanzamiento: Oficial Compatible con MySQL 8.0. Después de desconectar el receptor USB Logitech, el kernel de Linux falló. El maestro usó Scratch para frotar el simulador RISC-V y ejecutó con éxito el kernel de Linux. JetBrains lanzó Writerside, una herramienta para la creación de documentos técnicos.
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

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