3

私は単純なプラグイン フレームワークを作成しています。このフレームワークでは、共有ライブラリ (つまりプラグイン) を dlopen() し、ファクトリ関数が提供するものを検査して使用し、最終的に dlclose() してトレースを残さないようにしたいと考えています。

私のファクトリ システムは簡単なもので、共通の Base クラスへのポインターを返す単一のエクスポートされた関数を備えています。プラグインが適切にアンロードされたことを確認するために、デストラクタがメイン プログラムから bool を設定する静的オブジェクトがあります。

主なプログラムは次のとおりです。

// dltest.cpp follows. Compile with g++ -std=c++0x dltest.cpp -o dltest -ldl
#include <dlfcn.h>
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
    if (argc > 1)
    {
        void* h = dlopen(argv[1], RTLD_NOW|RTLD_LOCAL);
        if (!h)
        {
            cerr << "ERROR: " << dlerror() << endl;
            return 1;
        }
        bool isFinilized = false;
        *(bool**)dlsym(h, "g_finilized") = &isFinilized;
        cout << boolalpha << isFinilized << endl;
        if (dlclose(h))
        {
            cerr << "ERROR: " << dlerror() << endl;
            return 2;
        }
        cout << boolalpha << isFinilized << endl;
    }
    return 0;
}

プラグインのコードは次のとおりです。

// libempty.cpp follows. Compile with g++ -std=c++0x libempty.cpp -o libempty.so -fPIC -shared
#include <iostream>
#include <vector>
using namespace std;
bool* g_finilized = nullptr;
struct Finilizer
{
    ~Finilizer()
    {
        cout << "~Finilizer()" << endl;
        if (g_finilized) *g_finilized = true;
    }
} g_finilizer;
class Base
{
public:
    virtual void init() = 0;
};
class Foo: public Base
{
    virtual void init()
    {
        static const vector<float> ns = { 0.f, 0.75f, 0.67f, 0.87f };
    }
};
extern "C" __attribute__ ((visibility ("default"))) Base* newBase() { return new Foo; }

実行すると、出力は次のようになります。

false
false
~Finilizer()

これは、dlclose() の呼び出しが期待どおりに機能せず、プログラムが終了するまでライブラリがアンロードされなかったことを示しています。

ただし、ベクトルを関数の外に移動すると、最後の 8 行は次のようになります。

class Foo: public Base
{
    virtual void init()
    {
    }
};
static const vector<float> ns = { 0.f, 0.75f, 0.67f, 0.87f };
extern "C" __attribute__ ((visibility ("default"))) Base* newBase() { return new Foo; }

その後、dlclose() は適切に機能し、出力は次のようになります。

false
~Finilizer()
true

ベクトルが関数に残されていて、ファクトリがエクスポートされていない場合、同じ結果が生成されます。

class Foo: public Base
{
    virtual void init()
    {
        static const vector<float> ns = { 0.f, 0.75f, 0.67f, 0.87f };
    }
};
//extern "C" __attribute__ ((visibility ("default"))) Base* newBase() { return new Foo; }

ベクトルを C 配列に置き換えると、肯定的な結果が得られます。

class Foo: public Base
{
    virtual void init()
    {
        static const float ns[] = { 0.f, 0.75f, 0.67f, 0.87f };
    }
};
extern "C" __attribute__ ((visibility ("default"))) Base* newBase() { return new Foo; }

これは GCC/Linux のバグですか? 因数分解されたクラスのメンバー関数で複雑なオブジェクトを静的に宣言できるようにするための回避策はありますか?

4

1 に答える 1

6

何が起こっているかというと、 にSTB_GNU_UNIQUEシンボルがあるということですlibempty.so:

readelf -Ws libempty.so | grep _ZGVZN3Foo4initEvE2ns
 91: 0000000000203e80     8 OBJECT  UNIQUE DEFAULT   25 _ZGVZN3Foo4initEvE2ns
 77: 0000000000203e80     8 OBJECT  UNIQUE DEFAULT   25 _ZGVZN3Foo4initEvE2ns

問題は、STB_GNU_UNIQUEシンボルが非常に非直感的に機能し、dlopen/dlclose呼び出し全体で持続することです。

そのシンボルを使用すると、glibc はライブラリをここでアンロード不可としてマークするように強制されます。

シンボルには他にも驚きがあります。GNU_UNIQUE十分に最近のゴールド リンカーを使用する場合は、GNU_UNIQUEwith--no-gnu-uniqueフラグを無効にすることができます。

于 2012-07-23T02:55:01.420 に答える