dlclose не вызывает библиотечные деструкторы, dlopen вызывается только один раз

Рассмотрим следующий код для динамически загружаемой библиотеки, созданной с помощью g++-4.7 в Linux, -fPIC и связано с -rdynamic опция:

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(); }

Этот код нормально загружается с клиента с dlopen и RTLD_NOW флаг. Конструктор библиотеки называется. Проблема возникает, когда я звоню dlclose на ручке. Это дает статус ноль, что означает, что он был успешным. Но деструктор библиотеки library_fini не называется. dlopen вызывается в одном месте, поэтому подсчет ссылок не должен быть проблемой, но для того, чтобы быть абсолютно уверенным, что на самом деле нет references dangling я пытался сделать dlclose несколько раз:

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");

Все эти журналы отладки показывают, что каждый раз, когда я звоню dlclose результат статуса равен нулю. Успех!

Последнее утверждение, которое не является ошибочным, подтверждает, что библиотека больше не является резидентной. Но в этот момент library_fini еще не был вызван!

Такое поведение определенно является ошибкой, либо на GCC или ld.so (или что бы то ни было использует linux/ubuntu для динамической загрузки библиотек в наши дни). Такое поведение явно не соответствует стандарту, потому что деструкторы библиотеки должны быть гарантированно запущены, если счетчик ссылок равен нулю до возврата dlclose. Деструкторы библиотеки случайно запускаются после Wrapper.instance уничтожается, что делает деструктор библиотеки совершенно бесполезным, так как не может выполнить какую-либо финализацию данных

1 ответ

Решение

Это работает с glibc 2.17, gcc 4.8.0 или icc 13.1 или icc 12.1:

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

/ * test.c * /
#включают 
#включают 

int __attribute __ ((конструктор)) x_init(пустота)
{
    ставит ("init() работает");
    вернуть 0;
}

int __attribute __ ((деструктор)) x_fini(void)
{
    ставит ("fini() работает");
    вернуть 0;
}

против:

#include <dlfcn.h>

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

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

Также протестирован с glibc 2.10, glibc 2.12. И все RTLD_* флаги.

Редактировать:
Используя настоящую систему Ubuntu (gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2), библиотеку GNU C (Ubuntu EGLIBC 2.15-0ubuntu20), я должен сказать, что приведенный выше код также работает там. Так что, возможно, все-таки речь идет не о компиляторе и / или glibc.

Другие вопросы по тегам