Почему порядок "-l" в gcc имеет значение?

Я пытаюсь скомпилировать программу, которая использует библиотеку udis86. Фактически, я использую примерную программу, приведенную в руководстве пользователя библиотеки. Но при компиляции он дает ошибку. Я получаю следующие ошибки:

example.c:(.text+0x7): undefined reference to 'ud_init'
example.c:(.text+0x7): undefined reference to 'ud_set_input_file'
.
.
example.c:(.text+0x7): undefined reference to 'ud_insn_asm'

Используемая мной команда:

$ gcc -ludis86 example.c -o example 

как указано в руководстве пользователя.

Очевидно, что компоновщик не может связывать библиотеку libudis. Но если я изменю свою команду на:

$ gcc example.c -ludis86 -o example 

Он начинает работать. Так может ли кто-нибудь объяснить, в чем проблема с первой командой?

Ответ 1

В связи с тем, как работает алгоритм связывания, используемый GNU-компоновщиком (как минимум, когда речь идет о связывании статических библиотек).

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

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

Итак, если вы включили некоторую библиотеку с помощью -l, компоновщик использует эту библиотеку для разрешения как можно большего количества текущих ожидающих символов, а затем полностью забывает об этой библиотеке. Если позже выяснится, что теперь он нуждается в дополнительных объектных файлах из этой библиотеки, компоновщик не будет "возвращаться" к этой библиотеке для извлечения этих дополнительных объектных файлов. Уже слишком поздно.

По этой причине всегда полезно использовать опцию -l в конце командной строки компоновщика, так что к тому времени, когда компоновщик достигнет этого -l, он может надежно определить, какие файлы объектов ему нужны, а какие это не нужно. Размещение параметра -l в качестве самого первого параметра компоновщика вообще не имеет никакого смысла: в самом начале список ожидающих символов пуст (или, точнее, состоит из одного символа main), что означает, что линкер вообще ничего не берет из библиотеки.

В вашем случае ваш объектный файл example.o содержит ссылки на символы ud_init, ud_set_input_file и т.д. Компонент должен сначала получить этот объектный файл. Он добавит эти символы в список ожидающих символов. После этого вы можете использовать опцию -l, чтобы добавить вашу библиотеку: -ludis86. Компилятор будет искать вашу библиотеку и взять все, что разрешает эти ожидающие символы.

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

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

Ответ 2

Я ударил эту же проблему некоторое время назад. Итог заключается в том, что инструменты gnu не всегда будут "искать назад" в списке библиотек для устранения недостающих символов. Легкие исправления являются следующими:

  • Просто укажите libs и objs в порядке зависимости (как вы уже обнаружили выше)

  • ИЛИ если у вас есть циклическая зависимость (где libA ссылается на функцию в libB, но libB ссылается на функцию в libA), то просто укажите libs в командной строке дважды. Это то, что предлагает справочная страница. Например.

    gcc foo.c -lfoo -lbar -lfoo
    
  • Используйте параметры -( и -), чтобы указать группу архивов с такими циклическими зависимостями. Посмотрите руководство по компоновке GNU для --start-group и --end-group. Подробнее см. здесь.

Когда вы используете опцию 2 или 3, вы, вероятно, вводите стоимость исполнения для привязки. Если у вас нет такой ссылки, это может не иметь значения.

Ответ 3

Или используйте rescan

из pg 41 из Руководство по компоновщикам и библиотекам Oracle Solaris 11.1:

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

$ cc -o prog .... -lA -lB -lC -lA -lB -lC -lA 

Определение и обслуживание повторяющихся архивных спецификаций могут быть утомительным.

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

$ cc -o prog .... -lA -lB -lC -z rescan-now 

В качестве альтернативы опции -z rescan-start и -z rescan-end могут использоваться для группировки взаимно зависимые архивы вместе в группу архива. Эти группы перерабатываются редактором ссылок сразу после закрытия разделитель встречается в командной строке. Архивы, найденные внутри группа перерабатывается в попытке найти дополнительный архив членов, которые разрешают ссылки на символы. Этот архив повторного сканирования продолжается до тех пор, пока не произойдет проход по группе архивов, в которой нет новых члены извлекаются. Используя архивные группы, предыдущий пример может записываться следующим образом.

$ cc -o prog .... -z rescan-start -lA -lB -lC -z rescan-end