dlclose () не работает с заводской функцией и сложной статической функцией?

Я делаю простую инфраструктуру плагинов, в которой я хотел бы иметь возможность dlopen () совместно используемой библиотеки (то есть плагина), проверять и использовать любые функции фабрики, которые она предоставляет, и в конечном итоге dlclose (), не оставляя следов.

Моя фабричная система тривиальна, с единственной экспортируемой функцией, которая возвращает указатель на общий базовый класс. Чтобы проверить, что плагин был выгружен правильно, у меня есть статический объект, чей деструктор устанавливает 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? Есть ли обходной путь, чтобы сложные объекты могли быть объявлены статически в функции-члене факторизованного класса?

 Hristo Iliev15 июн. 2012 г., 18:48
Код ассемблера, сгенерированный в обоих случаях, существенно отличается. когдаns определяется как статическая локальная переменная, с которой регистрируется множество дополнительных обработчиков__cxa_atexit() и отслеживаниеld-linux.so поведение показывает, что дополнительные символы разрешаются. К сожалению, Intel C ++ не поддерживает списки инициализаторов, и трудно проверить, относится ли это поведение только к GCC.
 gavwould15 июн. 2012 г., 18:52
@HristoIliev: Обратите внимание, что список инициализаторов не требуется (или, по крайней мере, не должен) для случая сбоя - простого конструктора (или, возможно, даже конструктора по умолчанию), вероятно, достаточно для его сбоя.
 Nikolai Fetissov15 июн. 2012 г., 18:23
Откуда вы взяли, что одно поведение является «правильным»? а другой не такой? Я не думаю, что POSIX.1-2001 дает какие-либо гарантии тем или иным способом.
 gavwould15 июн. 2012 г., 18:49
@NikolaiNFetissov: Согласно справочной странице: «Функция dlclose () уменьшает счетчик ссылок на дескриптор динамической библиотеки. Если количество ссылок падает до нуля, и никакие другие загруженные библиотеки не используют в нем символы, динамическая библиотека выгружается. & Quot; Я не вижу очевидной причины, по которой его следует выгружать в одних тестовых случаях, а не в других.
 Hristo Iliev15 июн. 2012 г., 18:56
ОК, удалил инициализацию и перекомпилировал библиотеку модулей с помощью Intel C ++ Compiler (icpc) - сg++ поведение по-прежнему отличается в каждом случае в то время как сicpc библиотека выгружается вdlclose() время в обоих случаях. Так что да, поведение специфично для GCC, но я не могу утверждать, что это ошибка.

Ответы на вопрос(1)

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_UNIQUE с--no-gnu-unique флаг.

Ваш ответ на вопрос