Dlclose фактически не выгружает общий объект, независимо от того, сколько раз он называется

Моя программа использует dlopen для загрузки общего объекта, а затем dlclose, чтобы выгрузить его. Иногда этот общий объект загружается еще раз. Я заметил, что статические переменные не были повторно инициализированы (что очень важно для моей программы), поэтому я добавил тест (dlopen с RTLD_NOLOAD) после dlclose, чтобы увидеть, действительно ли библиотека действительно выгружена. Разумеется, он все еще был в памяти.

Затем я попытался называть dlclose несколько раз, пока библиотека не выгрузится, но я получил бесконечный цикл. Это код, который я использую, чтобы проверить, была ли выгружена библиотека:

dlclose(handles[name]);

do {
  void *handle = dlopen(filenames[name], RTLD_NOW | RTLD_NOLOAD);
  if (!handle)
    break;

  dlclose(handle);
} while (true);

Мой вопрос: каковы возможные причины, по которым мой общий объект не выгружается после dlclose, учитывая, что мои вызовы dlopen являются единственными местами, где он загружается. Можете ли вы предложить курс действий для отслеживания источника проблемы? Кроме того, почему повторные вызовы на dlclose не влияют, они каждый уменьшают счетчик ссылок, не так ли?

EDIT: Только что выяснилось, что это происходит только при компиляции с gcc. С clang все в порядке.

Ответ 1

Стандарт POSIX на самом деле не требует dlclose для выгрузки библиотеки из адресного пространства:

Хотя для удаления структур не требуется операция dlclose() из адресного пространства, ни одна из реализаций запрещена делая это.

Источник: Базовые спецификации Open Group Issue 6

Это означает, что недействительный дескриптор дескриптора dlclose вообще не требуется.

Иногда разгрузка также задерживается системой, она просто помещает библиотеку как "удаляемую" и фактически выполняет эту операцию в более позднее время (для эффективности или потому, что просто невозможно будет выполнить эту операцию прямо сейчас). Однако, если вы снова вызываете dlopen еще до того, как он когда-либо был выполнен, флаг очищается и повторно загружаемая библиотека повторно используется.

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

Существуют и другие более неясные случаи в зависимости от вида операционной системы, а также часто и от версии. Например. общая проблема с Linux заключается в том, что если вы создали библиотеку, которая использует символы STB_GNU_UNIQUE, эта библиотека помечена как "не разгружаемая" и, следовательно, просто не будет выгружена. См. здесь, здесь (DF_1_NODELETE означает не разгружаемый) и . Таким образом, он также может зависеть от того, какие символы или вид символа генерирует компилятор. Попробуйте запустить readelf -Ws в своей библиотеке и найдите объекты с тегами UNIQUE.

В общем, вы не можете полагаться на dlclose на работу, как вы могли бы ожидать. На практике я видел, что он "терпел неудачу" чаще, чем "преуспел" за последние десять лет (ну, он никогда не проигрывал, он просто не выгружал библиотеку из памяти, но он работал в соответствии со стандартами).

Ответ 2

Это не ответ на все ваши вопросы, но это решение, которое может помочь вам избежать проблем с dlclose. Этот вопрос подсказывает, как повлиять на поведение при повторной загрузке разделяемых библиотек: вы можете использовать флаг компилятора -fno-gnu-unique.

Из g++ страниц для gcc/g++:

-fno-гну-уникальное

В системах с недавним GNU-ассемблером и библиотекой C компилятор C++ использует привязку "STB_GNU_UNIQUE", чтобы убедиться, что определения элементов статических данных шаблона и статических локальных переменных во встроенных функциях уникальны даже при наличии "RTLD_LOCAL"; это необходимо, чтобы избежать проблем с библиотекой, используемой двумя разными плагинами "RTLD_LOCAL", в зависимости от определения в одном из них и, следовательно, не соглашаясь с другим относительно привязки символа. Но это приводит к тому, что "dlclose" игнорируется для затронутых DSO; если ваша программа использует повторную инициализацию DSO с помощью "dlclose" и "dlopen", вы можете использовать -fno-gnu-unique.

То, -fno-gnu-unique ли -fno-gnu-unique по умолчанию или нет, зависит от того, как сконфигурирован GCC: --disable-gnu-unique-object включает этот флаг по умолчанию, --enable-gnu-unique-object отключает его.

Ответ 3

В динамической загрузке библиотеки существует множество причуд. Опора на ОС для инициализации статических переменных чревата проблемами. Вы намного лучше избегаете его вообще или используете плагин-загрузчик, который обрабатывает все особые случаи для вас.

Я рекомендую вам проверить glib-модули. Glib обеспечивает независимый от платформы способ загрузки динамических библиотек. Вы можете использовать эти обратные вызовы:

Они могут обрабатывать выделение и освобождение любых ресурсов. Вместо того, чтобы полагаться на ОС для надежного распределения статики, вы можете динамически распределять то, что вам нужно.

Все, что вам нужно сделать, это определить эти функции в динамической библиотеке, а затем загрузить и выгрузить их с помощью:

Ответ 4

В Windows используйте эквивалент, используя ifdef с WIN или LINUX:

  • LoadLibrary()= dlopen()
  • FreeLibrary()= dlclose()
  • GetProcAddress()= dlsym()

void *handle;
double (*cosine)(double);
char *error;

handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
if (!handle) {
  fputs (dlerror(), stderr);
  exit(1);
  }

cosine = dlsym(handle, "cos");
 if ((error = dlerror()) != NULL)  {
   fputs(error, stderr);
   exit(1);
   }

printf ("%f\n", (*cosine)(2.0));
dlclose(handle);