Перемещение R_X86_64_32S против символа `stdout@@GLIBC_2.2.5' не может использоваться при создании объекта PIE

Эта проблема

Сейчас я читаю эта книга и главу о динамической компоновке со следующим кодом:

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

На самом деле вам нужно добавить -no-pie (или переписать код, чтобы он был PIC).

Jester 15.03.2022 13:14

О, теперь работает. Благодарю вас! Не могли бы вы написать свой комментарий в качестве ответа, пожалуйста, чтобы я мог отметить этот вопрос как ответ @Jester? Я также был бы рад получить некоторые подробности, почему мне нужен этот флаг, а не -rdynamic, потому что, согласно man gcc о -no-pie: Don't produce a dynamically linked position independent executable. Что он здесь делает? Похоже, что это отключит динамическое связывание.

TornaxO7 15.03.2022 13:20

@TornaxO7 PIE означает, что основной двоичный файл (а не только общие библиотеки) не зависит от позиции. Это защищает код от определенных атак, но также требует для работы специальных методов программирования на ассемблере. -fpie указывает компилятору генерировать такой код, но если вы пишете на ассемблере, вам придется делать это самостоятельно. -no-pie отключает PIE, позволяя вам писать обычный позиционно-зависимый код в вашем основном двоичном файле.

fuz 15.03.2022 13:42
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Позвольте мне показать вам, как исправить язык ассемблера из вашей книги, чтобы он работал с настройками вашего компилятора по умолчанию.

Как говорится в комментариях к вопросу, проблема в том, что ваш компилятор по умолчанию генерирует позиционно-независимые исполняемые файлы. Это означает, что адреса 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" относится к секции данных только для чтения.
  • x86-64 ABI говорит, что функциям с переменным числом переменных, таким как 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, но уберите их, и вам придется делать это вручную. )

Другие вопросы по теме