Una alternativa para la funcionalidad obsoleta __malloc_hook de glibc

6 minutos de lectura

avatar de usuario
Andreas Grapentin

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

avatar de usuario
Andreas Grapentin

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 glibcs malloc.

Ahora, para ser equivalente a la __malloc_hooks 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, glibcs __malloc_hooks 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 glibcs 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_hooks, 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.

  • ¿Ha tenido algún problema al usar esta técnica con valgrind? He visto problemas extraños cuando se combinan los dos.

    – davidA

    9 de marzo de 2016 a las 2:25

  • @meowsqueak No probé eso, pero valgrind tiende a hacer cosas extrañas.

    – Andreas Grapentin

    9 de marzo de 2016 a las 5:27

  • Hola, @AndreasGrapentin, usé tu método para escribir un verificador de montón de memoria de uso general llamado, a falta de un nombre mejor, MEM_depuración. ¡Gracias por compartirlo!

    – itaych

    23 de abril de 2019 a las 16:53


  • @itaych hola, gracias! Me alegra saber que salió algo útil de esto 🙂

    – Andreas Grapentin

    24 de abril de 2019 a las 18:31

  • @AndreasGrapentin “en glibc, malloc se define como un símbolo débil”, ¿Qué documento parece estar escrito? Veo que el código (malloc.c) no parece ser así.

    – UKeeySDis

    11 de julio de 2021 a las 11:36

Puede usar LD_PRELOAD y dlsym Consulte “Consejos para malloc y gratis” en http://www.slideshare.net/tetsu.koba/presentations

  • esto es genial, definitivamente lo intentaré. Aunque la necesidad de configurar LD_PRELOAD me molesta explícitamente.

    – Andreas Grapentin

    23 de julio de 2013 a las 15:03

  • Además, esto no parece funcionar para binarios vinculados estáticamente 🙁

    – Andreas Grapentin

    24 de julio de 2013 a las 6:02

  • Desafortunadamente, dlsym llama a calloc en ciertos casos.

    – Dhaval Kapil

    17 de junio de 2017 a las 10:06


Acabo de lograr el código de compilación NDK que contiene __malloc_hook.

Parece que se ha restablecido en Android API v28, según https://android.googlesource.com/platform/bionic/+/master/libc/include/malloc.hespecialmente:

extern void* (*volatile __malloc_hook)(size_t __byte_count, const void* __caller) __INTRODUCED_IN(28);

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad