Статические объекты уничтожены ДО dlclose () - PullRequest
4 голосов
/ 28 июня 2019

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

Чтобы исследовать проблему, я создал простой проект, который состоит из трех исходных файлов: main.cpp, lib1.cpp и lib2.cpp (для исполняемого файла и двух библиотек соответственно). Основной исполняемый файл динамически загружает lib1, а lib1, в свою очередь, динамически загружает lib2.

main.cpp:

Logger mainGlobal("mainGlobal");

int main(int argc, char * argv[])
{
    Logger mainFunction("mainFunction");
    try
    {
        Logger mainTry("mainTry");
        libutil::AutoLib lib("lib1");
        lib.call("loadLib2");
    }
    catch (std::exception & e)
    {
        std::cerr << "Fatal: " << e.what() << std::endl;
    }
    std::cout << "Exiting main" << std::endl;
}

lib1.cpp:

Logger lib1Global("lib1Global");

std::auto_ptr<libutil::AutoLib> lib2;

DLL_EXPORT void loadLib2()
{
    std::cout << "loadLib2" << std::endl;
    lib2.reset(new libutil::AutoLib("lib2"));
}

lib2.cpp:

Logger lib2Global("lib2Global");

Logger - это простая структура, которая просто регистрирует свой конструктор и деструктор. libutil::AutoLib - это загрузчик разделяемой библиотеки, который вызывает dlopen(path, RTLD_LAZY) в своем ctor, и вызывает dlclose() в своем dtor, и позволяет вызывать функции, экспортированные из разделяемой библиотеки. Код для этих классов тривиален, но я могу опубликовать его здесь, если это необходимо.

Короче говоря, если я вызываю основной исполняемый файл, я вижу следующий журнал:

mainGlobal ctor
mainFunction ctor
mainTry ctor
Loading library lib1.so
lib1Global ctor
dlopen(lib1.so) returned 0x14cd050
Library lib1.so loaded with handle 0x14cd050
Calling loadLib2 in library 0x14cd050
loadLib2
Loading library lib2.so
lib2Global ctor
dlopen(lib2.so) returned 0x14cd710
Library lib2.so loaded with handle 0x14cd710
Unloading library 0x14cd050
Calling dlclose(0x14cd050)
Library unloaded 0x14cd050
mainTry dtor
Exiting main
mainFunction dtor
lib2Global dtor
Unloading library 0x14cd710
Calling dlclose(0x14cd710)
Library unloaded 0x14cd710
lib1Global dtor
mainGlobal dtor

Обратите внимание, что строка lib2Global dtor идет перед строкой Calling dlclose(0x14cd710).

Итак, вопрос в том, является ли это ошибкой или правильным поведением?

Здесь, в SO, есть вопросы о том, что статические объекты не разрушаются после dlclose(), но я не нашел вопросов о несколько противоположной ситуации.

Я использую GCC 5.4.0-6ubuntu1 ~ 16.04.10.

1 Ответ

0 голосов
/ 02 июля 2019

Спасибо Дэвису Херрингу за подсказку, я нашел причину.Так что что-то держало в памяти lib1.so, не позволяя его выгрузить.Как оказалось, lib1.so вызвал функцию inline, которая содержала переменную static const, и это заставило gcc создать привязку STB_GNU_UNIQUE для этой переменной.Что, в свою очередь, фактически сделало lib1.so выгружаемым, даже если он был загружен с помощью RTLD_LOCAL.Поэтому, чтобы устранить проблему, я мог бы либо удалить квалификатор static из определения переменной, либо удалить квалификатор inline из определения функции, либо использовать флаг -fno-gnu-unique g ++.После того, как я это сделал, проблема исчезла:

mainGlobal ctor
mainFunction ctor
mainTry ctor
Loading library lib1.so
lib1Global ctor
dlopen(lib1.so) returned 0x1cfe050
Library lib1.so loaded with handle 0x1cfe050
Calling loadLib2 in library 0x1cfe050
loadLib2
Loading library lib2.so
lib2Global ctor
dlopen(lib2.so) returned 0x1cfe710
Library lib2.so loaded with handle 0x1cfe710
Unloading library 0x1cfe050
Calling dlclose(0x1cfe050)
Unloading library 0x1cfe710
Calling dlclose(0x1cfe710)
Library unloaded 0x1cfe710
lib1Global dtor
lib2Global dtor
Library unloaded 0x1cfe050
mainTry dtor
Exiting main
mainFunction dtor
mainGlobal dtor

Вот выдержка из справки GNU GCC, касающаяся этого:

-fno-gnu-unique
    On systems with recent GNU assembler and C library, the C++ compiler 
    uses the STB_GNU_UNIQUE binding to make sure that definitions of template 
    static data members and static local variables in inline functions are unique
    even in the presence of RTLD_LOCAL ; this is necessary to avoid problems 
    with a library used by two different RTLD_LOCAL plugins depending on a definition
    in one of them and therefore disagreeing with the other one about the binding of 
    the symbol. But this causes dlclose to be ignored for affected DSOs; if your
    program relies on reinitialization of a DSO via dlclose and dlopen , you can use 
    -fno-gnu-unique .

Здесь - это вопрос ссвязанная с этим проблема.

...