dlclose não chama destruidores de biblioteca, dlopen chamado apenas uma vez

dlclose não chama destruidores de biblioteca, dlopen chamado apenas uma vez

considere o seguinte código para uma biblioteca carregada dinâmica construída com g++-4.7 no Linux -fPICe vinculada à -rdynamicopção:

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 carrega bem de um cliente com dlopeno RTLD_NOWsinalizador. O construtor da biblioteca é chamado. O problema acontece quando eu ligo dlclosepara o identificador. Dá status zero, o que significa que foi bem-sucedido. Mas o destruidor da biblioteca library_fininão é chamado. dlopené chamado em um único local, então a contagem de referências não deve ser um problema, mas para ter certeza absoluta de que realmente não há nenhuma, references danglingtentei fazer dlclosevárias vezes:

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 esses logs de depuração mostram que cada vez que eu chamo, dlcloseo resultado do status é zero. Sucesso!

A última afirmação, que não falha, confirma que a biblioteca não é mais residente. Mas neste momento library_finiainda não foi chamado!

Esse comportamento é definitivamente um bug, seja no gcc ou ld.so(ou o que quer que esteja usando o linux/ubuntu para carregar bibliotecas dinamicamente atualmente). Esse comportamento claramente não é o que o padrão diz, porque os destruidores de bibliotecas devem ter execução garantida se a contagem de referência for zeroantes de dlclose retornar. os destruidores da biblioteca são executados após a Wrapper.instancedestruição, o que torna o destruidor da biblioteca completamente inútil, pois não é capaz de fazer nenhuma finalização dos dados

Responder1

Isso funciona com glibc 2.17, gcc 4.8.0 ou icc 13.1 ou icc 12.1:

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

/* teste.c */
#incluir
#incluir

int __attribute__((construtor)) x_init(void)
{
    puts("init() funciona");
    retornar 0;
}

int __attribute__((destruidor)) x_fini(void)
{
    puts("fini() funciona");
    retornar 0;
}

contra:

#include <dlfcn.h>

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

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

Também testado com glibc 2.10, glibc 2.12. E todas RTLD_*as bandeiras.

Editar:
Usando um sistema Ubuntu real (gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2), Biblioteca GNU C (Ubuntu EGLIBC 2.15-0ubuntu20), devo dizer que o código acima também funciona lá. Então talvez afinal não se trate do compilador e/ou glibc.

informação relacionada