Вам не нужно -llibc
, gcc уже связывает это по умолчанию.
Проблема здесь в том, что современный GCC по умолчанию делает PIE исполняемым (независимым от позиции), который является ELF "разделяемым объектом". Линкер обрабатывает его больше как библиотеку и не создает автоматически заглушки PLT для вызовов неопределенных имен символов. (Я не думаю, что такое поведение необходимо, ld
может позволить вам сделать это.)
Простое решение здесь gcc -no-pie -fno-pie -o mod mod.s
Тогда вы можете написать call printf
, и это просто работает.
С помощью этой командной строки вы создадите динамически связанный исполняемый файл ELF. Компоновщик переписывает ваш call printf
в call printf@plt
для вас (разберите его и посмотрите, с помощью objdump -drwC
распечатайте перемещения). Смещение между адресом загрузки libc и адресом вашего кода не является константой времени соединения. (И может быть больше 2 ^ 32 в любом случае).
Если вы используете -static
, call printf
преобразуется в фактический адрес определения printf
, скопированный в ваш исполняемый файл из libc.a
.
Я предполагаю, что вариант создания статического или динамического исполняемого файла из того же источника - вот почему ld
готов переписать вызовы к заглушкам PLT для исполняемых файлов ELF, но не к общим объектам ELF (например, к исполняемым файлам PIE).
См. 32-разрядные абсолютные адреса, более не разрешенные в x86-64 Linux? для получения дополнительной информации о PIE.
Другой способ вызова функций совместно используемой библиотеки - call *printf@GOTPCREL(%rip)
, как это делает gcc, если вы компилируете с -fno-plt
. Это полностью обходит PLT, просто делая вызов через указатель функции в GOT (доступ к которому осуществляется в режиме адресации RIP-относительной).