Динамическая загрузка и слабое разрешение символов

Анализируя этот вопрос, я узнал некоторые вещи о поведении слабого разрешения символов в контексте динамической загрузки (dlopen) в Linux. Теперь я ищу спецификации, регулирующие это.

Возьмем пример. Предположим, есть программа a, которая динамически загружает библиотеки b.so и c.so в указанном порядке. Если c.so зависит от двух других библиотек foo.so (фактически libgcc.so в этом примере) и bar.so (фактически libpthread.so), то обычно символы, экспортированные bar.so, могут использоваться для удовлетворения слабых связей символов в foo.so. Но если b.so тоже зависит от foo.so, но не от bar.so, то эти слабые символы, по-видимому, не будут связаны с bar.so. Кажется, что чернила foo.so ищут только символы из a и b.so и все их зависимости.

В какой-то степени это имеет смысл, поскольку в противном случае загрузка c.so может изменить поведение foo.so в какой-то момент, когда b.so уже использует библиотеку. С другой стороны, в вопросе, который заставил меня начать, это вызвало немало проблем, поэтому мне интересно, есть ли способ обойти эту проблему. И чтобы найти обходные пути, мне сначала нужно хорошо понимать очень точные детали того, как определяется разрешение символов в этих случаях.

Какая спецификация или другой технический документ определяет правильное поведение в этих сценариях?


person MvG    schedule 18.12.2013    source источник
comment
Вы просматривали этот файл PDF? Много интересных данных, но не уверен, включает ли они то, что вы ищете.   -  person rodrigo    schedule 16.01.2014
comment
@rodrigo: Не уверен, что это было так или что-то подобное, но до сих пор все документы ELF, которые я нашел, описывают только динамическое связывание до выполнения двоичного файла, а не связывание, связанное с динамически загружаемыми объектами. Это длинный документ, и я мог искать не в том месте, но пока это не то, что я ищу.   -  person MvG    schedule 16.01.2014
comment
А как насчет этого сообщения Дреппера и его более или менее менее связанный документ (см. раздел 1.5.2)? Как я понимаю, слабые символы используются только для статического связывания. Таким образом, dlopen() не будет иметь значения между слабыми и сильными символами.   -  person rodrigo    schedule 16.01.2014
comment
Эти разглагольствования кажутся актуальными notmysock.org/blog/php/weak-symbols-arent. html   -  person onionjake    schedule 22.01.2014
comment
@rodrigo: Извините, мне потребовалось так много времени, чтобы попробовать это. Кажется, вы правы, вопреки моему убеждению, неразрешенный символ при загрузке общего объекта не приведет к сбою загрузки. Я думал, что для этого нужна слабость, но это не так. В любом случае, раздел 1.5.4 документа, на который вы ссылаетесь, является наиболее полезным, который я когда-либо читал, поскольку он очень подробно описывает порядок, в котором обрабатываются библиотеки, и различные настройки, которые его контролируют. Если бы вы (или, возможно, кто-то другой) могли подытожить это, я бы с радостью присудил за это свою награду. Я бы сделал это сам, но в этом случае награда будет потеряна, так что я этого не делаю.   -  person MvG    schedule 23.01.2014
comment
@MvG: Я ценю предложение, но я не уверен, что напишу правильный ответ на этот вопрос, иначе я бы написал его некоторое время назад. Я просто случайно узнал об этих документах и ​​кое-каких материалах высокого уровня, но я потерялся в деталях.   -  person rodrigo    schedule 23.01.2014


Ответы (1)


К сожалению, официальной документацией является исходный код. Большинство дистрибутивов Linux используют glibc или его ответвление, eglibc. В исходном коде обоих файлов файл, который должен документировать dlopen(), выглядит следующим образом:

руководство/libdl.texi

@c FIXME these are undocumented:
@c dladdr
@c dladdr1
@c dlclose
@c dlerror
@c dlinfo
@c dlmopen
@c dlopen
@c dlsym
@c dlvsym

Какие технические спецификации существуют, можно взять из спецификации ELF и стандарта POSIX. Спецификация ELF — это то, что делает слабый символ значимым. POSIX — это фактическая спецификация самой функции dlopen().

Это то, что я считаю наиболее важной частью спецификации ELF.

Когда редактор ссылок выполняет поиск в архивных библиотеках, он извлекает элементы архива, содержащие определения неопределенных глобальных символов. Определение члена может быть либо глобальным, либо слабым символом.

В спецификации ELF не упоминается динамическая загрузка, поэтому остальная часть этого абзаца является моей собственной интерпретацией. Причина, по которой я считаю вышеизложенное актуальным, заключается в том, что разрешение символов происходит в одно «когда». В приведенном вами примере, когда программа a динамически загружает b.so, динамический загрузчик пытается разрешить неопределенные символы. В конечном итоге это может произойти с глобальными или слабыми символами. Когда программа затем динамически загружает c.so, динамический загрузчик снова пытается разрешить неопределенные символы. В описанном вами сценарии символы в b.so были разрешены слабыми символами. После разрешения эти символы больше не являются неопределенными. Неважно, использовались ли для их определения глобальные или слабые символы. К моменту загрузки c.so они уже не являются неопределенными.

Спецификация ELF не дает точного определения того, что такое редактор ссылок или когда редактор ссылок должен объединять объектные файлы. Предположительно, это не проблема, потому что документ имеет в виду динамическую компоновку.

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

http://pubs.opengroup.org/onlinepubs/9699919799/functions/dlopen.html

Соответствие POSIX является частью другого стандарта, Linux Standard Base. Дистрибутивы Linux могут следовать этим стандартам, а могут и не следовать, а также могут или не должны быть сертифицированы. Например, я понимаю, что официальная сертификация Unix от Open Group стоит довольно дорого — отсюда и обилие «Unix-подобных» систем.

Интересный момент о соответствии стандартам dlopen() упоминается в статье Википедии о динамической загрузке. dlopen() в соответствии с требованиями POSIX возвращает void*, но C, согласно требованиям ISO, говорит, что void* является указателем на объект, и такой указатель не обязательно совместим с указателем на функцию.

Факт остается фактом: любое преобразование между указателями на функции и объекты должно рассматриваться как (по своей сути непереносимое) расширение реализации, и что не существует «правильного» способа прямого преобразования, поскольку в этом отношении стандарты POSIX и ISO противоречат друг другу. разное.

Существующие стандарты противоречат друг другу, а существующие документы стандартов в любом случае могут не иметь особого смысла. Вот Ульрих Дреппер, который пишет о своем презрении к Open Group и их «спецификациям».

http://udrepper.livejournal.com/8511.html

Похожее мнение выражено в посте, на который ссылается Родриго.

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

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

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

main.c

#include <dlfcn.h>

void say_hi(void);

int main(void) {
    void* symbols_b = dlopen("./dyload.so", RTLD_NOW | RTLD_GLOBAL);
    /* uh-oh, forgot to define this function */
    /* better remember to define it in dyload.so */
    say_hi();
    return 0;
}

dyload.c

#include <stdio.h>
void say_hi(void) {
    puts("dyload.so: hi");
}

Скомпилируйте и запустите.

gcc-4.8 main -fpic -ldl -Wl,--unresolved-symbols=ignore-all -o main
gcc-4.8 dyload.c -shared -fpic -o dyload.so
$ ./main
dyload.so: hi

Обратите внимание, что сам основной исполняемый файл был скомпилирован как PIC.

person Praxeolitic    schedule 25.09.2014
comment
Вот это я называю отличным ответом! - person paulotorrens; 24.11.2015
comment
Компоновщик Glibc игнорирует слабость символов, если не указано LD_DYNAMIC_WEAK (см. справочную страницу ). - person yugr; 24.08.2018