Сейчас я читаю эта книга и главу о динамической компоновке со следующим кодом:
link_example.s
.globl main
.section .data
output:
.ascii "Yeet\n\0"
.section .text
main:
enter $0, $0
movq stdout, %rdi
movq $output, %rsi
call fprintf
movq $0, %rax
leave
ret
Теперь, согласно книге, мне нужно скомпилировать ее следующим образом, чтобы динамически связать C-библиотеку:
gcc -rdynamic link_example.s -o link_example
Но я получаю следующее сообщение об ошибке:
/usr/bin/ld: /tmp/cchUlvqS.o: relocation R_X86_64_32S against symbol `stdout@@GLIBC_2.2.5' can not be used when making a PIE object; recompile with -fPIE
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status
Что я делаю не так?
-fPIE
Я попробовал то, что предложил компилятор, добавив флаг -fPIE
:
gcc -rdynamic -fPIE link_example.s -o link_example
но я все еще получаю ту же ошибку снова.
Я нашел похожий пост, в котором говорилось, что мне просто нужно использовать флаг -shared
:
gcc -shared link_example.s -o link_example
но это дает мне:
/usr/bin/ld: /tmp/ccxktZan.o: relocation R_X86_64_32S against symbol `stdout@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status
и если я добавлю флаг -fPIC
:
gcc -shared -fPIC link_example.s -o link_example
то я получаю это:
/usr/bin/ld: /tmp/ccKIQ9sl.o: relocation R_X86_64_32S against symbol `stdout@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status
О, теперь работает. Благодарю вас! Не могли бы вы написать свой комментарий в качестве ответа, пожалуйста, чтобы я мог отметить этот вопрос как ответ @Jester? Я также был бы рад получить некоторые подробности, почему мне нужен этот флаг, а не -rdynamic
, потому что, согласно man gcc
о -no-pie
: Don't produce a dynamically linked position independent executable.
Что он здесь делает? Похоже, что это отключит динамическое связывание.
@TornaxO7 PIE означает, что основной двоичный файл (а не только общие библиотеки) не зависит от позиции. Это защищает код от определенных атак, но также требует для работы специальных методов программирования на ассемблере. -fpie
указывает компилятору генерировать такой код, но если вы пишете на ассемблере, вам придется делать это самостоятельно. -no-pie
отключает PIE, позволяя вам писать обычный позиционно-зависимый код в вашем основном двоичном файле.
Позвольте мне показать вам, как исправить язык ассемблера из вашей книги, чтобы он работал с настройками вашего компилятора по умолчанию.
Как говорится в комментариях к вопросу, проблема в том, что ваш компилятор по умолчанию генерирует позиционно-независимые исполняемые файлы. Это означает, что адреса stdout
, fprintf
и output
неизвестны во время компоновки, поэтому компоновщик не может «переместить» инструкции, которые к ним относятся.
Однако тот является, который известен во время компоновки, является смещения между адресами этих вещей и программным счетчиком. Это означает, что если вы просто напишете сборку немного по-другому, она будет работать. Как это:
.globl main
.section .data
output:
.ascii "Yeet\n\0"
.section .text
main:
enter $0, $0
movq stdout(%rip), %rdi
leaq output(%rip), %rsi
call fprintf@PLT
movq $0, %rax
leave
ret
Обратите внимание, что изменение немного отличается для всех трех. mov stdout, %rdi
становится mov stdout(%rip), %rdi
— просто другой режим адресации для той же инструкции. Загрузка из памяти по фиксированному адресу stdout
становится загрузкой из памяти по фиксированному смещениеstdout
из регистра RIP (он же счетчик программ). Загрузка фиксированный адресoutput
с mov $output, %rsi
, с другой стороны, становится lea output(%rip), %rsi
. Я бы посоветовал вам думать об этом как о всегда был операции загрузки эффективного адреса, но старый код с исполняемым файлом по фиксированному адресу мог выражать эту операцию с немедленным перемещением вместо фактической инструкции lea. Наконец, call fprintf
становится call fprintf@PLT
. Это сообщает компоновщику, что вызов должен пройти через таблица компоновки процедур — ваша книга должна объяснять, что это такое и зачем это нужно.
Между прочим, я вижу несколько других проблем с этим языком ассемблера, из которых наиболее важными являются:
"Yeet\n\0"
относится к секции данных только для чтения.fprintf
, необходимо сообщить количество аргументов с плавающей запятой, которые они получают, установив eax
соответствующим образом.enter
и leave
не нужны на x86-64. (Кроме того, enter
— это болезненно медленная микрокодированная инструкция, которую вообще не следует использовать.)Вместо этого я бы написал что-то вроде этого:
.section .rodata, "a", @progbits
.output:
.string "Yeet\n"
.section .text, "ax", @progbits
.globl main
.type main, @function
main:
sub $8, %rsp
mov stdout(%rip), %rdi
lea .output(%rip), %rsi
xor %eax, %eax
call fprintf@PLT
xor %eax, %eax
add $8, %rsp
ret
(Вам нужно вычесть 8 из %rsp
в начале функции, а затем добавить обратно, потому что ABI говорит, что %rsp
всегда должно быть кратно 16 по инструкции call
— это означает, что нет кратно 16 при входе в любой функция, но вместо этого %rsp
мод 16 равен 8, потому что call
проталкивает восемь байтов (обратный адрес). Вы получали это бесплатно как побочный эффект enter
и leave
, но уберите их, и вам придется делать это вручную. )
На самом деле вам нужно добавить
-no-pie
(или переписать код, чтобы он был PIC).