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

1

considere o seguinte código para uma biblioteca carregada dinâmica construída com o g ++ - 4.7 no linux, -fPIC e vinculada com -rdynamic option:

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 dlopen e o RTLD_NOW sinalizador. O construtor da biblioteca é chamado. O problema acontece quando eu chamo dlclose no identificador. Ele dá status zero, significando que foi bem-sucedido. Mas o destrutor da biblioteca library_fini não é chamado. dlopen é chamado em um único local, portanto, a contagem de referência não deve ser um problema, mas para ter certeza absoluta de que não há realmente references dangling , tentei fazer o dlclose vá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 dlclose , o resultado do status é zero. Sucesso!

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

Esse comportamento é definitivamente um bug, seja no gcc ou ld.so (ou o que estiver usando o linux / ubuntu para carregar dinamicamente bibliotecas atualmente). Esse comportamento claramente não é o que o padrão diz, porque os destruidores de biblioteca devem ter a garantia de serem executados se a contagem de referência for zero antes que o dlclose retorne . destruidores de bibliotecas são executados depois que o Wrapper.instance é destruído, o que torna o destruidor da biblioteca completamente inútil, já que não é possível fazer qualquer finalização dos dados

    
por lurscher 03.04.2013 / 00:29

1 resposta

2

Isso funciona com o 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

/* test.c */
#include 
#include 

int __attribute__((constructor)) x_init(void)
{
    puts("init() works");
    return 0;
}

int __attribute__((destructor)) x_fini(void)
{
    puts("fini() works");
    return 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 todos os RTLD_* flags.

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 funciona lá também. Então, talvez, afinal, não é sobre o compilador e / ou glibc.

    
por hroptatyr 03.04.2013 / 05:57