LINUX.ORG.RU

Приоритеты поиска имён в shared objects

 ,


0

2

Предположим, у меня есть два варианта одной so-библиотеки, с одинаковыми названиями функций но разными реализациями. lib1.so и lib1_a.so. Есть библиотека lib2.so которая скомпилена с зависимостью от lib1.so. Есть бинарник, которому нужно lib2.so и lib1_a.so. Есть ли надёжный способ сделать (или оно по-дефолту так и будет) чтобы импорты lib2.so резолвились в lib1_a.so, подгруженную для бинарника, а не в lib1.so, которая указана в самом lib2?

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

И ещё дополнение: обратная ситуация, в бинарнике зависимость от lib1.so, в lib2 - от lib1_a.so, надо чтобы всё резолвилось опять в lib1_a.so. Можно обобщить как «lib1_a.so имеет больший приоритет чем lib1.so, если они оказываются вместе».

Зачем это нужно: хочу сделать синглтред и мультитред версию библиотеки, и чтобы если хоть кто-то запросил мультитред - у всех был он.

★★★

делай зависимость по симлинку, а того направляй к кому хочешь. В целом же я рекомендую почитать статью «How To Write Shared Libraries» за авторством Дреппера и можешь глянуть книгу «Advanced C and C++ Compiling» за авторством Стевановича - в них очень подробно описано буквально всё что только можно по данной и множеству смежных проблем с библиотеками и компиляцией. И кстати - а зачем разделять многопоток и однопоток ? Не проще внутри библиотеки это разрешать ? Т.е. сделать внутри библиотеки две реализации и звать желаемую.

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

Направлять вручную симлинк это примерно то же самое что и LD_PRELOAD прописывать. А не вручную - ну можно и сам .so менять. Но тут речь о том чтобы для конкретной запущеной проги выбиралась нужная реализация в зависимости от того, есть ли в её дереве зависимостей хоть одно упоминание тяжёлого варианта библиотеки или можно обойтись лёгким. Ну или хотя бы не в дереве, а у главного бинарника.

И кстати - а зачем разделять многопоток и однопоток ? Не проще внутри библиотеки это разрешать ? Т.е. сделать внутри библиотеки две реализации и звать желаемую.

Можно конечно (более того, мультитред реализация очевидно может работать и в синглтреде безо всяких фиксов), но например у многопоточной есть зависимость от libpthread.so (и выйдет что чисто сингл-тред прога будет его требовать), да и просто лишние байты в памяти тратятся на ненужные ветки кода.

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

и выйдет что чисто сингл-тред прога будет его требовать

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

Будь это прошивка под МК, ещё можно было бы понять.

Dark_SavanT ★★★★★ ()

lib1_a.so имеет больший приоритет чем lib1.so, если они оказываются вместе

По идее опция -Bsymbolic должна помочь, если с ней собрать lib1_a.so:
cc -g -Wl,-Bsymbolic -shared -fPIC -o lib1_a.so 1_a.c

И приоритет поиска символов из библиотек происходит слева направо, т.е. при сборке бинарника -l1_a надо впереди указать.

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

А меня бесят простыни из всякой мути в выводе ldd и как итог в минимальном комплекте файлов для работы бинарника. Особенно если эта муть оказывается в итоге не нужна. В случае с прошивкой всё проще - она собирается в едином процессе и там сразу всё учтено под задачу.

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

Почитал про неё, кажется это не то: она только предотвращает использование внешних символов при наличии таких же внутренних кодом самого .so.

И приоритет поиска символов из библиотек происходит слева направо, т.е. при сборке бинарника -l1_a надо впереди указать.

То есть если я укажу его первым, то и сам бинарник, и все подгруженные к нему другие .so, формально зависящие от lib1, будут брать символы из lib1_a?

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

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

AKonia ★★ ()

Есть ли надёжный способ сделать (или оно по-дефолту так и будет) чтобы импорты lib2.so резолвились в lib1_a.so, подгруженную для бинарника, а не в lib1.so, которая указана в самом lib2?

Все подгружаемые либы попадают в так называемый «Link map» (можно проитерироваться по нему в рантайме и посмотреть содержание). Когда встречается вызов на что-то внешнее, то загрузчик берет этот link map и по очереди штудирует либы для резолва вызова. Следовательно, lib1_a.so должна оказаться раньше, зависит от положения в строке при компиляции (гарантируют ли тут компиляторы что-то? не знаю). Кстати, LD_PRELOAD помещает либу в начало линк мэпа.

Есть еще вариант - не скармливать линкеру либы при сборке, разрешить нерезолвенные символы, и в рантайме подгрузить всё через dlopen (всё это тоже пойдет в link map). Не резолвить символы при сборке поможет что-то из этого -Wl,–unresolved-symbols= -Wl,–allow-shlib-undefined + -rdynamic

kvpfs ()
Ответ на: комментарий от firkax

Почитал про неё, кажется это не то: она только предотвращает использование внешних символов при наличии таких же внутренних кодом самого .so.

Допустим, у тебя в основной программе так:

abc() { printf("main-abc\n"); }

main() { func(); }

А в подключённой библиотеке lib1_a так:

abc() { printf("lib-abc\n"); }

func() { abc(); }

С опцией -Bsymbolic выведет «lib-abc», а без неё будет «main-abc». Решай, нужно ли тебе такое для надёжности.

То есть если я укажу его первым, то и сам бинарник, и все подгруженные к нему другие .so, формально зависящие от lib1, будут брать символы из lib1_a?

Да, эффект как LD_PRELOAD с lib1_a.so

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

Решай, нужно ли тебе такое для надёжности.

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

Ну видимо вариант с «просто указать нужную библиотеку первой» самый безболезненный и не требующий от других библиотек каких-то специальных мер.

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

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

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

А вообще в моих личных предпочтениях вообще пустой вывод ldd aka статический бинарь. Чего и вам желаю.

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

Ну вот смотри, делаю я ldd xfce4-terminal - программе, которая должна всего лишь интерактивно конвертировать текстовый поток в x11 картинку, принимать и отправлять в терминал клавиатурные/мышиные события и иногда взаимодействовать с буфером обмена.

$ ldd /usr/bin/xfce4-terminal
	linux-gate.so.1 (0xb7f3d000)
	libX11.so.6 => /usr/lib/i386-linux-gnu/libX11.so.6 (0xb7d68000)
	libvte-2.91.so.0 => /usr/lib/i386-linux-gnu/libvte-2.91.so.0 (0xb7cdc000)
	libxfce4ui-2.so.0 => /usr/lib/i386-linux-gnu/libxfce4ui-2.so.0 (0xb7cbf000)
	libgtk-3.so.0 => /usr/lib/i386-linux-gnu/libgtk-3.so.0 (0xb7406000)
	libgdk-3.so.0 => /usr/lib/i386-linux-gnu/libgdk-3.so.0 (0xb72f0000)
	libpango-1.0.so.0 => /usr/lib/i386-linux-gnu/libpango-1.0.so.0 (0xb729a000)
	libcairo.so.2 => /usr/lib/i386-linux-gnu/libcairo.so.2 (0xb7147000)
	libgdk_pixbuf-2.0.so.0 => /usr/lib/i386-linux-gnu/libgdk_pixbuf-2.0.so.0 (0xb711c000)
	libxfce4util.so.7 => /usr/lib/i386-linux-gnu/libxfce4util.so.7 (0xb7109000)
	libxfconf-0.so.3 => /usr/lib/i386-linux-gnu/libxfconf-0.so.3 (0xb70ec000)
	libgio-2.0.so.0 => /usr/lib/i386-linux-gnu/libgio-2.0.so.0 (0xb6ebe000)
	libgobject-2.0.so.0 => /usr/lib/i386-linux-gnu/libgobject-2.0.so.0 (0xb6e5e000)
	libglib-2.0.so.0 => /usr/lib/i386-linux-gnu/libglib-2.0.so.0 (0xb6d14000)
	libutempter.so.0 => /usr/lib/i386-linux-gnu/libutempter.so.0 (0xb6d0f000)
	libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb6ced000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb6b04000)
	libxcb.so.1 => /usr/lib/i386-linux-gnu/libxcb.so.1 (0xb6ad6000)
	libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb6ad0000)
	libfribidi.so.0 => /usr/lib/i386-linux-gnu/libfribidi.so.0 (0xb6ab4000)
	libgnutls.so.30 => /usr/lib/i386-linux-gnu/libgnutls.so.30 (0xb688e000)
	libicuuc.so.67 => /usr/lib/i386-linux-gnu/libicuuc.so.67 (0xb669f000)
	libpcre2-8.so.0 => /usr/lib/i386-linux-gnu/libpcre2-8.so.0 (0xb65ff000)
	libsystemd.so.0 => /usr/lib/i386-linux-gnu/libsystemd.so.0 (0xb653f000)
	libz.so.1 => /lib/i386-linux-gnu/libz.so.1 (0xb6522000)
	libpangocairo-1.0.so.0 => /usr/lib/i386-linux-gnu/libpangocairo-1.0.so.0 (0xb6512000)
	libatk-1.0.so.0 => /usr/lib/i386-linux-gnu/libatk-1.0.so.0 (0xb64e9000)
	libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xb631e000)
	libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb621a000)
	libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb61fb000)
	libSM.so.6 => /usr/lib/i386-linux-gnu/libSM.so.6 (0xb61f0000)
	libICE.so.6 => /usr/lib/i386-linux-gnu/libICE.so.6 (0xb61d3000)
	libstartup-notification-1.so.0 => /usr/lib/i386-linux-gnu/libstartup-notification-1.so.0 (0xb61c5000)
	libgmodule-2.0.so.0 => /usr/lib/i386-linux-gnu/libgmodule-2.0.so.0 (0xb61bf000)
	libXi.so.6 => /usr/lib/i386-linux-gnu/libXi.so.6 (0xb61ab000)
	libXfixes.so.3 => /usr/lib/i386-linux-gnu/libXfixes.so.3 (0xb61a3000)
	libcairo-gobject.so.2 => /usr/lib/i386-linux-gnu/libcairo-gobject.so.2 (0xb6199000)
	libatk-bridge-2.0.so.0 => /usr/lib/i386-linux-gnu/libatk-bridge-2.0.so.0 (0xb6161000)
	libepoxy.so.0 => /usr/lib/i386-linux-gnu/libepoxy.so.0 (0xb6050000)
	libpangoft2-1.0.so.0 => /usr/lib/i386-linux-gnu/libpangoft2-1.0.so.0 (0xb6036000)
	libharfbuzz.so.0 => /usr/lib/i386-linux-gnu/libharfbuzz.so.0 (0xb5f37000)
	libfontconfig.so.1 => /usr/lib/i386-linux-gnu/libfontconfig.so.1 (0xb5eea000)
	libfreetype.so.6 => /usr/lib/i386-linux-gnu/libfreetype.so.6 (0xb5e21000)
	libXinerama.so.1 => /usr/lib/i386-linux-gnu/libXinerama.so.1 (0xb5e1c000)
	libXrandr.so.2 => /usr/lib/i386-linux-gnu/libXrandr.so.2 (0xb5e0f000)
	libXcursor.so.1 => /usr/lib/i386-linux-gnu/libXcursor.so.1 (0xb5e02000)
	libXcomposite.so.1 => /usr/lib/i386-linux-gnu/libXcomposite.so.1 (0xb5dfd000)
	libXdamage.so.1 => /usr/lib/i386-linux-gnu/libXdamage.so.1 (0xb5df8000)
	libxkbcommon.so.0 => /usr/lib/i386-linux-gnu/libxkbcommon.so.0 (0xb5db1000)
	libwayland-cursor.so.0 => /usr/lib/i386-linux-gnu/libwayland-cursor.so.0 (0xb5da7000)
	libwayland-egl.so.1 => /usr/lib/i386-linux-gnu/libwayland-egl.so.1 (0xb5da2000)
	libwayland-client.so.0 => /usr/lib/i386-linux-gnu/libwayland-client.so.0 (0xb5d92000)
	libXext.so.6 => /usr/lib/i386-linux-gnu/libXext.so.6 (0xb5d7a000)
	librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xb5d6f000)
	libthai.so.0 => /usr/lib/i386-linux-gnu/libthai.so.0 (0xb5d63000)
	libpixman-1.so.0 => /usr/lib/i386-linux-gnu/libpixman-1.so.0 (0xb5cb4000)
	libpng16.so.16 => /usr/lib/i386-linux-gnu/libpng16.so.16 (0xb5c74000)
	libxcb-shm.so.0 => /usr/lib/i386-linux-gnu/libxcb-shm.so.0 (0xb5c6f000)
	libxcb-render.so.0 => /usr/lib/i386-linux-gnu/libxcb-render.so.0 (0xb5c60000)
	libXrender.so.1 => /usr/lib/i386-linux-gnu/libXrender.so.1 (0xb5c54000)
	libmount.so.1 => /usr/lib/i386-linux-gnu/libmount.so.1 (0xb5bea000)
	libselinux.so.1 => /lib/i386-linux-gnu/libselinux.so.1 (0xb5bba000)
	libresolv.so.2 => /lib/i386-linux-gnu/libresolv.so.2 (0xb5b9f000)
	libffi.so.7 => /usr/lib/i386-linux-gnu/libffi.so.7 (0xb5b95000)
	libpcre.so.3 => /lib/i386-linux-gnu/libpcre.so.3 (0xb5b1e000)
	/lib/ld-linux.so.2 (0xb7f3f000)
	libXau.so.6 => /usr/lib/i386-linux-gnu/libXau.so.6 (0xb5b19000)
	libXdmcp.so.6 => /usr/lib/i386-linux-gnu/libXdmcp.so.6 (0xb5b12000)
	libp11-kit.so.0 => /usr/lib/i386-linux-gnu/libp11-kit.so.0 (0xb59bd000)
	libidn2.so.0 => /usr/lib/i386-linux-gnu/libidn2.so.0 (0xb599b000)
	libunistring.so.2 => /usr/lib/i386-linux-gnu/libunistring.so.2 (0xb5819000)
	libtasn1.so.6 => /usr/lib/i386-linux-gnu/libtasn1.so.6 (0xb5802000)
	libnettle.so.8 => /usr/lib/i386-linux-gnu/libnettle.so.8 (0xb57b7000)
	libhogweed.so.6 => /usr/lib/i386-linux-gnu/libhogweed.so.6 (0xb576c000)
	libgmp.so.10 => /usr/lib/i386-linux-gnu/libgmp.so.10 (0xb56de000)
	libicudata.so.67 => /usr/lib/i386-linux-gnu/libicudata.so.67 (0xb3bc5000)
	liblzma.so.5 => /lib/i386-linux-gnu/liblzma.so.5 (0xb3b99000)
	libzstd.so.1 => /usr/lib/i386-linux-gnu/libzstd.so.1 (0xb3ac8000)
	liblz4.so.1 => /usr/lib/i386-linux-gnu/liblz4.so.1 (0xb3aa4000)
	libgcrypt.so.20 => /usr/lib/i386-linux-gnu/libgcrypt.so.20 (0xb39bd000)
	libuuid.so.1 => /usr/lib/i386-linux-gnu/libuuid.so.1 (0xb39b3000)
	libbsd.so.0 => /usr/lib/i386-linux-gnu/libbsd.so.0 (0xb399b000)
	libxcb-util.so.1 => /usr/lib/i386-linux-gnu/libxcb-util.so.1 (0xb3993000)
	libX11-xcb.so.1 => /usr/lib/i386-linux-gnu/libX11-xcb.so.1 (0xb398c000)
	libdbus-1.so.3 => /lib/i386-linux-gnu/libdbus-1.so.3 (0xb392a000)
	libatspi.so.0 => /usr/lib/i386-linux-gnu/libatspi.so.0 (0xb38f1000)
	libgraphite2.so.3 => /usr/lib/i386-linux-gnu/libgraphite2.so.3 (0xb38c4000)
	libexpat.so.1 => /lib/i386-linux-gnu/libexpat.so.1 (0xb3897000)
	libbrotlidec.so.1 => /usr/lib/i386-linux-gnu/libbrotlidec.so.1 (0xb3889000)
	libdatrie.so.1 => /usr/lib/i386-linux-gnu/libdatrie.so.1 (0xb387e000)
	libblkid.so.1 => /usr/lib/i386-linux-gnu/libblkid.so.1 (0xb3823000)
	libgpg-error.so.0 => /lib/i386-linux-gnu/libgpg-error.so.0 (0xb37f9000)
	libmd.so.0 => /usr/lib/i386-linux-gnu/libmd.so.0 (0xb37ea000)
	libbrotlicommon.so.1 => /usr/lib/i386-linux-gnu/libbrotlicommon.so.1 (0xb37c7000)

Ну и что это за ужас? Зачем ему целых три криптографические библиотеки gnutls, libnettle и libhogweed (и libtasn1 видимо от них же)? Зачем ему библиотека идентификаторов блочных устройств libblkid? Зачем ему пачка библиотек алгоритмов сжатия (brotli, lzma, zstd, lz4 и мб ещё что)? И это не говоря уже про всякую гуи-ерунду, которая идёт автоматом в нагрузку к тулкиту, хотя конкретно терминалу большинство перечисленного не нужно. Вот там тоже рассуждали небось «а почему бы очередную безобидную хрень не прописать в зависимость, вроде пара (10, 100) кб всего, зато возиться с тонкой настройкой не надо».

firkax ★★★ ()

dl_open

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

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

Как всё сложно.

dlopen выглядит костыльно, его надо вызывать вручную, а кто и когда это будет делать? Все знают что для использования библиотеки достаточно написать #include .h в исходник и -l в аргументы линкера. А тут придётся ещё и компиляцию с unresolved символами разрешать всем кто пользуется библиотекой.

+ его будет не видно в ldd и не знаю что там с переносимостью на всё подряд.

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

Это на какой такой системе есть поддержка динамических библиотек, но нет поддержки тредов?

Если мы говорим про микроконтроллеры без ОС или с какой-нибудь FreeRTOS, то там всё статически линкуется без вариантов, а библиотеки настраиваются с помощью дефайнов (соответственно, тебе нужно лишь предоставить дефайн отключающий использование pthread на этапе компиляции).

Если мы говорим про Embedded Linux, то там надо очень постараться поискать систему без pthread. А если найдём, то там скорее всего опять же всё будет ручками из исходников собираться под какую-то кастомную железку и опять же хватит дать юзеру в руки дефайн.

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

Всё проще. Вместо использования pthreads напрямую, ты грузи его при первом обращении к многопоточному коду и через dl_sym находи все необходимые тебе функции и сохраняй в глобальные переменные, а затем дёргай их. От юзера не будет требоваться никаких особых настроек линковки, только вызывать правильные функции твоей библиотеки в зависимости от желания использовать многопоточность. Но вообще проблема высосана из пальца, так как pthreads везде есть. А где нет, там твою библиотеку соберут руками и отключат поддержку тредов через дефайн, если ты его им предоставишь.

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

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

firkax ★★★ ()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.