web-dev-qa-db-ja.com

dlcloseはライブラリデストラクターを呼び出しません。dlopenは一度だけ呼び出されます

linuxでg ++-4.7を使用してビルドされ、-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
lurscher

これは、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 */
#include 
#include 
 
 int __attribute __((constructor))x_init(void)
 {
 puts( "init()works"); 
 return 0; 
} 
 
 int __attribute __((destructor))x_fini (void)
 {
 puts( "fini()works"); 
 return 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についてのものではないのかもしれません。

2
hroptatyr