Подтвердить что ты не робот

Почему ld необходимо -rpath-link при связывании исполняемого файла с a так, чтобы он нуждался в другом?

Мне просто интересно. Я создал общий объект:

gcc -o liba.so -fPIC -shared liba.c

И еще один общий объект, который ссылается на прежний:

gcc -o libb.so -fPIC -shared libb.c liba.so

Теперь при создании исполняемого файла, который ссылается на libb.so, мне нужно будет указать -rpath-link для ld, чтобы он мог найти liba.so, когда обнаружил, что libb.so зависит от него:

gcc -o test -Wl,-rpath-link,./ test.c libb.so

иначе ld будет жаловаться.

Почему это, что ld ДОЛЖЕН быть в состоянии найти liba.so при связывании test? Поскольку мне кажется, что ld не делает ничего, кроме подтверждения существования liba.so. Например, при запуске readelf --dynamic ./test при необходимости отображается только libb.so, поэтому я предполагаю, что динамический компоновщик должен самостоятельно определить зависимость libb.so -> liba.so и сделать его собственным поиском liba.so.

Я на платформе GNU/Linux x86-64, а основная() - процедура в test вызывает функцию в libb.so, которая, в свою очередь, вызывает функцию в liba.so.

4b9b3361

Ответ 1

  Почему ld ДОЛЖЕН быть в состоянии найти liba.so при связывании test? Потому что мне не кажется, что ld делает что-то еще, кроме подтверждения существования liba.so. Например, при запуске readelf --dynamic ./test только перечисляются libb.so, как нужно, поэтому я предполагаю, что динамический компоновщик должен самостоятельно обнаружить зависимость libb.so -> liba.so и выполнить собственный поиск liba.so.

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

Но Л.Д. идет еще дальше. Когда вы связываете test, ld также проверяет, что все неразрешенные ссылки в libb.so находятся в общих библиотеках, от которых зависит libb.so (в нашем случае libb.so зависит от liba.so, поэтому для него требуется liba.so: находиться во время ссылки). Ну, на самом деле ld уже сделал эту проверку, когда связывался libb.so. Почему он делает эту проверку во второй раз... Может быть, разработчики ld сочли эту двойную проверку полезной для обнаружения нарушенных зависимостей, когда вы пытаетесь связать вашу программу с устаревшей библиотекой, которая могла быть загружена во времена, когда она была связана, но теперь она может не будет загружен, потому что библиотеки, от которых он зависит, обновлены (например, liba.so был позже переработан, а некоторые функции были удалены из него).

UPD

Просто сделал несколько экспериментов. Кажется, мое предположение "на самом деле ld уже сделал эту проверку, когда связывалось libb.so", неверно.

Предположим, что liba.c имеет следующее содержание:

int liba_func(int i)
{
    return i + 1;
}

и libb.c имеет следующее:

int liba_func(int i);
int liba_nonexistent_func(int i);

int libb_func(int i)
{
    return liba_func(i + 1) + liba_nonexistent_func(i + 2);
}

и test.c

#include <stdio.h>

int libb_func(int i);

int main(int argc, char *argv[])
{
    fprintf(stdout, "%d\n", libb_func(argc));
    return 0;
}

При ссылке libb.so:

gcc -o libb.so -fPIC -shared libb.c liba.so

компоновщик не генерирует никаких сообщений об ошибках, которые liba_nonexistent_func не могут быть разрешены, вместо этого он просто тихо генерирует сломанную общую библиотеку libb.so. Поведение такое же, как при создании статической библиотеки (libb.a) с аргументом ar, который также не разрешает символы сгенерированной библиотеки.

Но когда вы пытаетесь связать test:

gcc -o test -Wl,-rpath-link=./ test.c libb.so

вы получаете ошибку:

libb.so: undefined reference to 'liba_nonexistent_func'
collect2: ld returned 1 exit status

Обнаружение такой ошибки было бы невозможным, если бы ld не проверял рекурсивно все общие библиотеки. Таким образом, кажется, что ответ на вопрос такой же, как я сказал выше: ld нужна ссылка -rpath, чтобы убедиться, что связанный исполняемый файл может быть загружен позднее динамически загруженным. Просто проверка работоспособности.

UPD2

Было бы целесообразно как можно раньше проверить наличие неразрешенных ссылок (при связывании libb.so), но ld по некоторым причинам этого не делает. Вероятно, это позволяет создавать циклические зависимости для разделяемых библиотек.

liba.c может иметь следующую реализацию:

int libb_func(int i);

int liba_func(int i)
{
    int (*func_ptr)(int) = libb_func;
    return i + (int)func_ptr;
}

Так что liba.so использует libb.so, а libb.so использует liba.so (лучше никогда этого не делать). Это успешно компилируется и работает:

$ gcc -o liba.so -fPIC -shared liba.c
$ gcc -o libb.so -fPIC -shared libb.c liba.so
$ gcc -o test test.c -Wl,-rpath=./ libb.so
$ ./test
-1217026998

Хотя Риделф говорит, что liba.so не нужен libb.so:

$ readelf -d liba.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
$ readelf -d libb.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [liba.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

Если бы ld проверял наличие неразрешенных символов во время связывания с общей библиотекой, связывание liba.so было бы невозможно.

Обратите внимание, что я использовал -rpath ключ вместо -rpath -link. Разница в том, что ссылка -rpath используется во время связывания только для проверки того, что все символы в конечном исполняемом файле могут быть разрешены, тогда как -rpath фактически вставляет путь, который вы указали в качестве параметра, в ELF:

$ readelf -d test | grep RPATH
 0x0000000f (RPATH)                      Library rpath: [./]

Теперь можно запускать test, если общие библиотеки (liba.so и libb.so) расположены в вашем текущем рабочем каталоге (./). Если вы просто использовали -rpath -link, в ELF test такой записи не было бы, и вам пришлось бы добавить путь к разделяемым библиотекам в файл /etc/ld.so.conf или в переменную среды LD_LIBRARY_PATH.

UPD3

На самом деле можно проверять наличие неразрешенных символов во время связывания с общей библиотекой, для этого необходимо использовать опцию --no-undefined:

$ gcc -Wl,--no-undefined -o libb.so -fPIC -shared libb.c liba.so
/tmp/cc1D6uiS.o: In function 'libb_func':
libb.c:(.text+0x2d): undefined reference to 'liba_nonexistent_func'
collect2: ld returned 1 exit status

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

Ответ 2

Вы используете систему ld.so.conf, ld.so.conf.d, а системная среда LD_LIBRARY_PATH и т.д. предоставляет системные пути поиска библиотек, которые дополняются установленными библиотеками через pkg-config информации и тому подобное, когда вы создаете против стандартных библиотек. Когда библиотека находится в определенном пути поиска, автоматически отслеживаются пути поиска стандартной библиотеки, позволяя найти все необходимые библиотеки.

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

rpath предоставляет способ встраивания вашего пользовательского пути поиска библиотеки времени выполнения в заголовок ELF, чтобы ваши пользовательские библиотеки могли быть найдены также без указания пути поиска каждый раз, когда он используется. Это относится и к библиотекам, которые также зависят от библиотек. Как вы нашли, не только порядок, в котором вы указываете библиотеки в командной строке, важно, вы также должны предоставить путь поиска библиотеки времени выполнения или информацию о rpath для каждой зависимой библиотеки, с которой вы также связываете, чтобы заголовок содержит расположение всех библиотек, необходимых для запуска.

Addemdum из комментариев

Мой вопрос в первую очередь заключается в том, почему ld должен "автоматически попытаться найти (liba.so) и" включить его в ссылку".

Это просто способ ld. From man ld "Параметр -rpath также используется при поиске общих объектов, которые необходимы общедоступным объектам, явно включенным в ссылку... Если -rpath не используется при связывании исполняемого файла ELF, содержимое переменной окружения" LD_RUN_PATH "будет использоваться, если оно определено". В вашем случае liba не находится в LD_RUN_PATH, поэтому ld потребуется способ нахождения liba во время компиляции вашего исполняемого файла либо с помощью rpath (описанного выше), либо путем предоставления явного пути поиска к нему.

Во-вторых, что означает "включить его в ссылку" на самом деле. Мне кажется что это просто означает: "подтвердить это существование" (liba.so), поскольку Заголовки ELB libb.so не изменяются (у них уже был тег NEEDED против liba.so), а заголовки exec объявляют libb.so как ТРЕБУЕТСЯ. Почему ld заботится о поиске liba.so, может ли он не просто уйти задача для компоновщика времени выполнения?

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

Ответ 3

Я думаю, вам нужно знать, когда использовать опцию -rpath и опцию -rpath-link. Сначала я цитирую то, что указано в man ld:

  1. Разница между ссылками -rpath и -rpath заключается в том, что каталоги, указанные в -rpath параметры включены в исполняемый файл и используются во время выполнения, тогда как опция -rpath -link действует только во время соединения. поиск Таким образом, -rpath поддерживается только встроенными компоновщиками и перекрестными компоновщиками, которые были настроены с параметром --with-sysroot.

Вы должны различать время соединения и время выполнения. Согласно принятому вами ответу anton_rh, проверка неопределенных символов не включена при компиляции и компоновке общих библиотек или статических библиотек, но включена при компиляции и компоновке исполняемых файлов. (Однако, обратите внимание, что существуют некоторые файлы, которые являются общей библиотекой, а также исполняемыми файлами, например, ld.so. Введите man ld.so, чтобы изучить это, и я не знаю, включена ли проверка неопределенных символов, когда составление этих файлов "двойного" вида).

Таким образом, -rpath-link используется для проверки времени соединения, а -rpath используется для времени соединения и времени выполнения, поскольку rpath встроен в заголовки ELF. Но вы должны быть осторожны, что опция -rpath-link переопределит опцию -rpath во время соединения, если они оба указаны.

Но все же, почему вариант -rpath-option и -rpath? Я думаю, что они используются для устранения "связывания". Посмотрите это Лучшее понимание решения вторичных зависимостей Linux с помощью примеров., просто используйте ctrl + F, чтобы перейти к содержанию, связанному с "пересылкой ссылок". Вы должны сосредоточиться на том, почему "связывание" является плохим, и из-за метода, который мы используем, чтобы избежать "связывания", существование опций ld -rpath-link и -rpath является разумным: мы намеренно опускаем некоторые библиотеки в командах для компиляция и компоновка, чтобы избежать "навязывания", и из-за пропуска ld нужно -rpath-link или -rpath, чтобы найти эти пропущенные библиотеки.

Ответ 4

На самом деле вы не говорите ld (при привязке libb к liba), где liba - это только зависимость. Быстрый ldd libb.so покажет вам, что он не может найти liba.

Так как предположительно эти библиотеки не находятся в вашем пути поиска компоновщика, при связывании исполняемого файла вы получите ошибку компоновщика. Имейте в виду, что при связывании самой liba функция в libb по-прежнему остается неразрешенной, но поведение ld по умолчанию не заботится о неразрешенных символах в DSO, пока вы не свяжете окончательный исполняемый файл.