dlclose no llama a los destructores de bibliotecas, dlopen llama solo una vez

dlclose no llama a los destructores de bibliotecas, dlopen llama solo una vez

considere el siguiente código para una biblioteca cargada dinámicamente construida con g++-4.7 en Linux -fPICy vinculada con -rdynamicla opción:

typedef std::vector< void* > cbRegister_t;

struct Wrapper
{
    cbRegister_t instance;
    Wrapper() : instance() { HDebugLog("Wrapper CTOR!");}
    ~Wrapper() { HDebugLog("Wrapper DESTRUCTOR!"); }
};
inline cbRegister_t& getLibraryUnregisterMap()
{
    static Wrapper unregisterLibraryMap;
    HDebugLog("getLibraryUnregisterMap: we have " <<unregisterLibraryMap.instance.size() << " elements. the address of the map is " << &unregisterLibraryMap.instance);
    return unregisterLibraryMap.instance;
}

void registerLibrary(void* p)
{
  auto& map = getLibraryUnregisterMap();
  map.push_back(p);
}

void unregisterLibrary()
{
  auto& map = getLibraryUnregisterMap();
}

void __attribute__ ((constructor)) library_init()
{
  static SomeData cbContainer;
  HDebugLog("Library constructor: address of static cbContainer is: " << &cbContainer );
  registerLibrary( &cbContainer);
} 
void __attribute__ ((destructor)) library_fini()
{ unregisterLibrary(); }

Este código se carga bien desde un cliente con dlopeny la RTLD_NOWbandera. Se llama al constructor de la biblioteca. El problema ocurre cuando llamo dlcloseal identificador. Da estado cero, lo que significa que fue exitoso. Pero library_finino se llama al destructor de la biblioteca. dlopense llama en un solo lugar, por lo que el recuento de referencias no debería ser un problema, pero para estar absolutamente seguro de que realmente no hay ninguna, references danglingintenté hacer esto dlclosevarias veces:

int result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: closing library: " << libraryPath);
HDebugLog("Library::dynamicLibraryClose: dlclose 1 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 2 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 3 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 4 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 5 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 6 failed with error: " << result << " => ");
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 7 failed with error: " << result << " => ");
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 8 failed with error: " << result << " => " );
HAssertMsg( !libraryPath.empty(), "library path is not set");
HAssertMsg( 0 == dlopen(libraryPath.c_str(), RTLD_NOLOAD) , "library is still loaded");

Todos esos registros de depuración muestran que cada vez que llamo, dlcloseel resultado del estado es cero. ¡Éxito!

La última afirmación, que no falla, confirma que la biblioteca ya no es residente. ¡Pero a estas alturas library_finiaún no ha sido llamado!

Este comportamiento es definitivamente un error, ya sea en gcc o ld.so(o lo que sea que esté usando Linux/ubuntu para cargar bibliotecas dinámicamente en estos días). Este comportamiento claramente no es lo que dice el estándar, porque se debe garantizar que los destructores de biblioteca se ejecuten si el recuento de referencias es cero.antes de dlclose regresa. Los destructores de la biblioteca se ejecutan después de Wrapper.instanceser destruido, lo que hace que el destructor de la biblioteca sea completamente inútil, ya que no puede finalizar los datos.

Respuesta1

Esto funciona con glibc 2.17, gcc 4.8.0 o icc 13.1 o icc 12.1:

icc -std=c99 -nostdlib -shared test.c -o /tmp/test

/* prueba.c */
#incluir
#incluir

int __atributo__((constructor)) x_init(vacío)
{
    pone("init() funciona");
    devolver 0;
}

int __atributo__((destructor)) x_fini(vacío)
{
    puts("fini() funciona");
    devolver 0;
}

contra:

#include <dlfcn.h>

int
main(void)
{
    void *foo = dlopen("/tmp/test", RTLD_LAZY);

    if (dlclose(foo) < 0) {
        return 1;
    }
    return 0;
}

También probado con glibc 2.10, glibc 2.12. Y todas RTLD_*las banderas.

Editar:
Usando un sistema Ubuntu real (gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2), Biblioteca GNU C (Ubuntu EGLIBC 2.15-0ubuntu20), debo decir que el código anterior también funciona allí. Entonces, tal vez, después de todo, no se trate del compilador y/o glibc.

información relacionada