Estoy escribiendo un generador de perfiles de memoria para C y para eso estoy interceptando llamadas al malloc
, realloc
y free
funciones a través de malloc_hooks. Desafortunadamente, estos están en desuso debido a su mal comportamiento en entornos de subprocesos múltiples. No pude encontrar un documento que describa la solución alternativa de mejores prácticas para lograr lo mismo, ¿alguien puede iluminarme?
He leído que un simple #define malloc(s) malloc_hook(s)
haría el truco, pero eso no funciona con la configuración del sistema que tengo en mente, porque es demasiado intrusivo para el código base original para ser adecuado para su uso en una herramienta de creación de perfiles/rastreo. Tener que cambiar manualmente el código de la aplicación original es un asesino para cualquier generador de perfiles decente. Óptimamente, la solución que estoy buscando debe habilitarse o deshabilitarse simplemente vinculando a una biblioteca compartida opcional. Por ejemplo, mi configuración actual usa una función declarada con __attribute__ ((constructor))
para instalar la interceptación malloc
manos.
Gracias
Después de probar algunas cosas, finalmente logré descubrir cómo hacer esto.
En primer lugar, en glibc
, malloc
se define como un símbolo débil, lo que significa que la aplicación o una biblioteca compartida pueden sobrescribirlo. Por eso, LD_PRELOAD
no es necesariamente necesario. En cambio, implementé la siguiente función en una biblioteca compartida:
void*
malloc (size_t size)
{
[ ... ]
}
Que es llamado por la aplicación en lugar de glibc
s malloc
.
Ahora, para ser equivalente a la __malloc_hook
s funcionalidad, todavía faltan un par de cosas.
1.) la dirección de la persona que llama
Además de los parámetros originales para malloc
, glibc
s __malloc_hook
s también proporciona la dirección de la función de llamada, que en realidad es la dirección de retorno de donde malloc
volvería a. Para lograr lo mismo, podemos usar el __builtin_return_address
función que está disponible en gcc. No he buscado en otros compiladores, porque estoy limitado a gcc de todos modos, pero si sabes cómo hacer algo así de forma portátil, por favor déjame un comentario 🙂
Nuestro malloc
la función ahora se ve así:
void*
malloc (size_t size)
{
void *caller = __builtin_return_address(0);
[ ... ]
}
2.) acceder glibc
s malloc desde dentro de tu gancho
Como estoy limitado a glibc en mi aplicación, elegí usar __libc_malloc
para acceder a la implementación malloc original. Alternativamente, dlsym(RTLD_NEXT, "malloc")
se puede utilizar, pero en el posible escollo que esta función utiliza calloc
en su primera llamada, lo que posiblemente resulte en un bucle infinito que conduzca a un error de segmento.
anzuelo malloc completo
Mi función de enganche completa ahora se ve así:
extern void *__libc_malloc(size_t size);
int malloc_hook_active = 0;
void*
malloc (size_t size)
{
void *caller = __builtin_return_address(0);
if (malloc_hook_active)
return my_malloc_hook(size, caller);
return __libc_malloc(size);
}
donde my_malloc_hook
Se ve como esto:
void*
my_malloc_hook (size_t size, void *caller)
{
void *result;
// deactivate hooks for logging
malloc_hook_active = 0;
result = malloc(size);
// do logging
[ ... ]
// reactivate hooks
malloc_hook_active = 1;
return result;
}
Por supuesto, los ganchos para calloc
, realloc
y free
trabajar de manera similar.
enlace dinámico y estático
Con estas funciones, la vinculación dinámica funciona de inmediato. Vincular el archivo .so que contiene la implementación del gancho malloc resultará en todas las llamadas a malloc
desde la aplicación y también todas las llamadas de la biblioteca que se enrutarán a través de mi gancho. Sin embargo, la vinculación estática es problemática. Todavía no lo he entendido por completo, pero en el enlace estático malloc no es un símbolo débil, lo que resulta en un error de definición múltiple en el momento del enlace.
Si necesita un enlace estático por cualquier motivo, por ejemplo, traducir direcciones de funciones en bibliotecas de terceros a líneas de código a través de símbolos de depuración, entonces puede vincular estas bibliotecas de terceros estáticamente mientras aún vincula los ganchos malloc dinámicamente, evitando el problema de definición múltiple. Todavía no he encontrado una mejor solución para esto, si conoces una, no dudes en dejarme un comentario.
Aquí hay un breve ejemplo:
gcc -o test test.c -lmalloc_hook_library -Wl,-Bstatic -l3rdparty -Wl,-Bdynamic
3rdparty
se vinculará de forma estática, mientras que malloc_hook_library
se vinculará dinámicamente, dando como resultado el comportamiento esperado, y direcciones de funciones en 3rdparty
ser traducible a través de símbolos de depuración en test
. Bastante ordenado, ¿eh?
conclusión
las técnicas anteriores describen un enfoque no desaprobado, bastante equivalente a __malloc_hook
s, pero con un par de limitaciones medias:
__builtin_caller_address
solo funciona con gcc
__libc_malloc
solo funciona con glibc
dlsym(RTLD_NEXT, [...])
es una extensión GNU en glibc
las banderas del enlazador -Wl,-Bstatic
y -Wl,-Bdynamic
son específicos de los binutils de GNU.
En otras palabras, esta solución es absolutamente no portátil y se tendrían que agregar soluciones alternativas si la biblioteca de ganchos se trasladara a un sistema operativo que no sea GNU.
¿Estaba en desuso? sourceware.org/ml/libc-alpha/2011-07/msg00136.html 2011 glibc malloc desaprobación del gancho considerada dañina. Los ganchos todavía están aquí: sourceware.org/git/?p=glibc.git;a=blob;f=malloc/hooks.c;hb=HEAD. Solamente
__malloc_initialize_hook
la variable se marcó como obsoleta desde glibc 2.24, verifique el hombre real man7.org/linux/man-pages/man3/malloc_hook.3.htmlsección NOTAS– osgx
29/10/2016 a las 13:47
la página de manual dice que
these functions are deprecated
y solo__malloc_initialize_hook
era remoto desde entonces.– Andreas Grapentin
29 de octubre de 2016 a las 14:16