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

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

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

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

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

Последнее утверждение, которое не подводит, подтверждает, что библиотека больше не является резидентом. Но на данный момент 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

/* тест.c */
#включать
#включать

int __attribute__((конструктор)) x_init(void)
{
    puts("init() работает");
    вернуть 0;
}

int __attribute__((деструктор)) x_fini(void)
{
    puts("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.

Связанный контент