LINUX.ORG.RU

dl и dependency hell в рантайме

 , ,


2

5

Возникло недавно желание разобраться с проблемой Dependency hell’а на онтопике, с этой проблемой на оффтопике кое-как разобрались с помошью SxS.

Речь вот о чём: У нас есть приложение main.exe которое линкуется с двумя плагинами (в линктайме или через dlopen, не важно): plugin1.so, plugin2.so.

Но каждый из этих плагинов использует библиотеку lib, причём разных мажорных версий, т.е lib1.so и lib2.so. С тем же успехом это могут быть совсем разные библиотеки но с одинаковым символом.

Я тут поигрался с примерами и оказывается что в линуксе райнтайм линкер тупо заставляет весь процесс иметь одну реализацию для одного символа, без учёта того откуда этот символ требуется.

Это конечно имеет смысл в определённых случаях, например с malloc/free, но после Windows это забавно выглядит. На винде нам достаточно или использовать SxS (Что мне не нравится, но это хотяб решение), или использовать lib с разными именами (опять же lib1.dll и lib2.dll). Система автоматически свяжет в рантайме все запрошенные символы из plugin2 с символами из lib2, и все символы из plugin1 с символами из lib1. Plugin1/Plugin2 не будут ничего знать о символах из соседнего плагина.

Я так понимаю что в линуксе это можно решить только если иметь доступ к исходникам lib и собрать обе версии с версионированным символами? Может ли использование lib через dlopen помочь? Просто спрашиваю чтоб убедиться что я ничего не упустил. Кажется -BDirect помогает бороться с этим но в Solaris.

Собственно интерес у меня в этом всём от того что я работаю в компании которая разрабатывает lib.dll/so. Пока основные заказчики на windows, но хочется разобраться с этой тонкостью на линуксе.

Ссылки по теме: https://habr.com/ru/post/220961 http://cryptonector.com/2012/02/dll-hell-on-linux-but-not-solaris/

Если интересно глянуть на конкретные исходники - накидал вот: https://github.com/olologin/dependency_hell_runtime



Последнее исправление: olologin (всего исправлений: 2)

Ну а RTLD_LOCAL в dlopen() не сгодится? Загруженный с ним модуль не будет участвовать в переразмещениях для других модулей. Есть ещё RTLD_DEEPBIND - у символов из загружаемого модуля приоритет выше.

pavlick ★★
()

Можно ещё несколько link map сделать, если не ошибаюсь.

pavlick ★★
()

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

pavlick ★★
()
Ответ на: комментарий от pavlick

Это не важно. Важно только что lib1 и lib2 экспортят одинаковый символ с разными реализациями. Этот символ используется только соответственно в plugin1 и plugin2.

Но вот пример который я для тестов накидал: https://github.com/olologin/dependency_hell_runtime

olologin
() автор топика
Ответ на: комментарий от pavlick

Я думаю это реально сделать при загрузке plugin1.so/plugin2.so из main, но пытаться загрузить lib из plugin1/plugin2 через dlopen будет извращением, так как там достаточно большой С++ API. Но конечно интересно попробовать.

olologin
() автор топика
Ответ на: комментарий от olologin

Это не важно.

В смысле? Это говорит либо об устаревании plugin1 или об бардаке в разработке и поддержке.

vodz ★★★★★
()
Ответ на: комментарий от vodz

plugin1/plugin2 разрабатываются разными компаниями, они не обязаны зависеть от одной и той же версии lib. Эти компании вообще могут быть конкурентами.

olologin
() автор топика
Ответ на: комментарий от olologin

они не обязаны зависеть от одной и той же версии lib.

Обязаны, ибо тот комментарий рекурсивно применим и к lib,

vodz ★★★★★
()
Ответ на: комментарий от pavlick

Кажется загрузка плагинов в отдельных неймспейсах через dlmopen помогла бы. https://manpages.debian.org/testing/manpages-dev/dlmopen.3.en.html

Правда как я уже сказал доступа к исходникам main и plugin1/plugin2 строго говоря нет. Но потенциально dlmopen мог бы помочь.

olologin
() автор топика
Ответ на: комментарий от olologin

Так и не загружай lib из plugin1/plugin2, линкуй через командную строку:

g++ ... -l_libname -o plugin1.so

В исполняемом файле

 auto m = dlopen("./plugin1.so",RTLD_LAZY|RTLD_LOCAL);

В результате символы из lib_libname унаследуют видимость от plugin1.so, т.е. тоже будут RTLD_LOCAL в нашем процессе. Когда будешь загружать plugin2.so, то они не будут участвовать в переразмещениях для plugin2.so.

pavlick ★★
()
Последнее исправление: pavlick (всего исправлений: 1)
Ответ на: комментарий от pavlick

Ага, ваш способ работает. Я правда думал что RTLD_LOCAL не наследуется. Но похоже что действительно наследуется.

olologin
() автор топика
Ответ на: комментарий от olologin

Кстати, создавать отдельные link map’ы (неймспейсы) даже надёжней - ведь в плагине lib может дергаться через dlopen(), тогда видимость символов будет зависить лишь от флагов в этом dlopen, а не исполняемом файле. Поэтому да, dlmopen самое надёжное.

Правда как я уже сказал доступа к исходникам main и plugin1/plugin2

исходники плагинов и не нужны, а вот main - думал, что твой. Тогда не знаю что можешь сделать.

pavlick ★★
()

Не ради троллинга а ради расширения кругозора тех кто интересуется этим могу сказать такое: В винде для разрешения проблемы нам нужно было бы выполнить одно из двух условий:

  • сделать так чтоб lib имел разное имя для каждой мажорной версии
  • встроить в каждый плагин и lib SxS манифест описывающий версии зависимостей, убедиться что плагины грузят lib с помошью IsolationAwareLoadLibrary вместо LoadLibrary если они вообще пользовались LoadLibrary.

Получается что модификация main никогда не требуется, модификация plugin1/plugin2 может понадобиться для второго способа.

olologin
() автор топика
Ответ на: комментарий от olologin

Могу сказать такое: автор плагиноспособной софтины должен быть достаточно адекватен и сделать загрузку модулей либо локально, либо в разные link map. Вполне нормалное условие, как можно грузить непонятные символы от непонятно кого в глобальный link map? Рано или поздно будет конфликт имён.

pavlick ★★
()

Слушайте, я так не пробовал делать. Но. Если plugin1.so будет лежать в каталоге plugin1/ и тут же будет лежать lib.so (нужной версии) и у plugin1.so будет прописан rpath в виде ./ . И точно также будет с plugin2.so . Так прокатит?

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Не прокатит. Проблема не в файловой системе в а рантайм линкере который видит 2 одинаковых символа и предоставляет только одну реализацию.

olologin
() автор топика

Да нет никакой проблемы с линкером, он прозрачно отрабатывает согласно очереди в link map, это делает возможными всякие плюшки в виде LD_PRELOAD.

Вообще странная ситуация - нет доступа к исполняемому файлу, плагинам и тем более либам, которые плагины используют. Значит софтина чужая полностью. Тогда автору надо писать раз он такой говнокод родил без разделения неймспейсов. При доступе хоть к чему-то (плагины или исполняемый файл - вопрос элементарно решается. Но тут какая-то проблема высосанная из пальца обсуждается)

ЗЫ: Как-то игрался с выводом link map:

     #define _GNU_SOURCE
     #include <link.h>
     #include <dlfcn.h>
     #include <stdio.h>
     int main()
     {
        static char addr_in_mod;
        Dl_info __info;
        struct link_map *lm;
        if(dladdr1(&addr_in_mod, &__info, (void**)&lm, RTLD_DL_LINKMAP) != 0) {
           printf("link_map:\n");
           struct link_map *i = lm;
           for(; i->l_prev != NULL; i = i->l_prev);
           for (; i != NULL; i = i->l_next)
              printf("addr diff=%p  name=%s%s",(void*)i->l_addr,  i->l_name, i==lm?"  <--cur\n":"\n");
        }
     }
     //output:
     //link_map:
     //addr diff=0x41f000  name=  <--current module
     //addr diff=0xb7fc4000  name=linux-gate.so.1
     //addr diff=0xb7fa3000  name=/lib/libdl.so.2
     //addr diff=0xb7dc5000  name=/lib/libc.so.6
     //addr diff=0xb7fc6000  name=/lib/ld-linux.so.2
pavlick ★★
()
Ответ на: комментарий от pavlick

Может не совсем верно обьяснил: К исходникам lib доступ есть. К исходникам plugin доступа нет, но в целом изменения туда можно заставить сделать разрабов. К исходникам main доступа нет.

Но я в целом понял что эта всё скорее головная боль main который со своей стороны не грузит плагины в отдельных неймспейсах.

olologin
() автор топика
Ответ на: комментарий от olologin

Но я в целом понял что эта всё скорее головная боль main который со своей стороны не грузит плагины в отдельных неймспейсах.

В мане dlopen сказано, что RTLD_LOCAL это дефолт. main нарочно грузит с RTLD_GLOBAL?

anonymous
()

Можно использовать symbol versioning. Он позволяет иметь разные версии символа с одним именем а также искать символ в конкретной динамической библиотеке вместо поиска по всем загруженным библиотекам.

X512 ★★★★★
()
Ответ на: комментарий от anonymous

Поддерживаю.

Ещё, думаю, что ТС немного путает разделяемую библиотеку и плагин судя по его: «(в линктайме или через dlopen, не важно)». На macos (если не ошибаюсь, не проверял, но люди говорят) вообще нельзя открыть либу как плагин, они не взаимозаменяемы. Плагины должны открываться лишь через dlopen, а не через -l. Если автор main чего-то там наговнокодил, то это не проблема автора lib и плагинов.

pavlick ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.