Как проверить, зачем нужен какой-то символ для привязки? - PullRequest
2 голосов
/ 18 июня 2020

Этот вопрос становится громоздким, давайте попробуем короткую версию: Обычно, когда вы терпите неудачу с unresolved symbol reference, это довольно прямолинейно, здесь вы вызываете то, что компоновщик не может найти. Вы просто загружаете свой линкер библиотекой, и он просто работает. Иногда бывают случаи, когда вы бьетесь головой об стену и не понимаете, зачем компоновщику нужен этот символ здесь и там, он не вызывается, по крайней мере, напрямую. Есть ли переключатель инструмент / компоновщик, который может объяснить, почему он считает, что символ нужен «здесь»?

Исходный вопрос: все дело в связи c stati. У меня есть небольшая утилита, пара строк кода, пара включений. Утилита статически связана с библиотекой lib1. Допустим, lib1 имеет зависимость от другой библиотеки, lib2, поскольку lib1 использует символ sym1 из lib2. Однако ничего, что использует sym1 из lib2, не используется / не вызывается из утилиты, а также ничего из lib1, которое может зависеть от lib2. Однако вышеупомянутая крошечная утилита не работает с неразрешенным символом для sym1. Первый вопрос: почему? Поскольку в утилите sym1 нигде не требуется и даже не используется символ из lib1, который использует sym1, используемый в утилите, зачем компоновщику вообще нужно искать этот символ? Второй вопрос: есть вероятность, что цепочка включения вводит в мою утилиту символ sym1, тогда она отвечает на вопрос «почему», но не должна вводить его (по крайней мере, для этого нет очевидной причины), поэтому второй вопрос как мне узнать, почему компоновщик считает, что утилите требуется sym1 из lib2?

What / whenre / why: Linux, C / C ++, G CC -9 / Clang-9

1 Ответ

2 голосов
/ 18 июня 2020

Ну, видимо, мне удалось ответить на вопрос, не увидев кода и сообщения об ошибке. Пришло время открыть мое psi-консультирование.

Что касается линковки на цель Linux / ELF, важно помнить, что компоновщик, пытаясь удовлетворить / разрешить символы, объединяет (и копирует в окончательный исполняемый файл) разделы (иначе сегменты). Обычно в приложении есть .text (сегмент кода), .rodata (данные только для чтения), сегмент .data (r / w инициализированные данные), .bss (неинициализированные данные) и c. Таким образом, если необходимый символ находится, скажем, среди трех функций в одном скомпилированном файле, будет выбран весь раздел .text файла. И если неиспользуемые, но присутствующие в разделе функции вызывают что-то еще, компоновщик начнет поиск этого "чего-то еще", чтобы удовлетворить его, даже если это не имеет отношения к приложению.

Кроме того, есть некоторые особенности C ++ c вещь: для класса с виртуальными функциями компилятор генерирует vtable с указателями на каждую виртуальную функцию и перемещает эту таблицу в раздел .rodata. Обратите внимание, что то, что мы считаем кодом, на самом деле попадает в раздел DATA (только для чтения).

Если у вас определены все виртуальные функции, кроме одной, компоновщик, скорее всего, пожалуется с сообщением об ошибке, например

/tmp/cc5YTcBb.o:(.rodata._ZTV3CL1[_ZTV3CL1]+0x18): undefined reference to `CL1::fnc2()

где вы могли видеть, что проблема связана с .rodata, а не с .text.

Мораль истории: разбейте свой код и данные на большое количество наименьших возможных разделов / сегментов, ваших атомов связывания . В идеале каждая функция идет в свой собственный раздел, а также часть инициализированных или r / o данных.

Последний шаг - указать компоновщику (через параметр -Wl) удалить (собрать мусор) все неиспользуемые разделы .

В общем, следует ожидать, что компоновщиком будет использоваться больше ОЗУ, возможно, более медленная стадия компоновки, но меньшее и быстрое приложение.

Командная строка для использования, взгляните на G CC manual значение параметров wrt.

g++ -fdata-sections -ffunction-sections -fipa-pta main.cpp -Wl,--gc-sections -Wl,-O1 -Wl,--as-needed
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...