Ответ 1
Ваш дистрибутив сконфигурировал gcc с --enable-default-pie
, поэтому он по умолчанию делает независимые от позиции исполняемые файлы (с учетом ASLR исполняемого файла, а также библиотек). В наши дни большинство дистрибутивов делают это.
Вы на самом деле создаете общий объект: исполняемые файлы PIE являются своего рода хаком, использующим общий объект с точкой входа. Динамический компоновщик уже поддерживал это, и ASLR хорош для безопасности, так что это был самый простой способ реализовать ASLR для исполняемых файлов.
32-разрядное абсолютное перемещение недопустимо в разделяемом объекте ELF; это предотвратит их загрузку за пределы младшего 2 ГБ (для 32-разрядных адресов с расширенными знаками). Допускаются 64-битные абсолютные адреса, но обычно вы хотите использовать их только для таблиц переходов или других статических данных, а не для инструкций. 1
Часть recompile with -fPIC
сообщения об ошибке является фиктивной для рукописного asm; он написан для людей, которые компилируют с gcc -c
, а затем пытаются связать с gcc -shared -o foo.so *.o
, с gcc, где -fPIE
не используется по умолчанию. Сообщение об ошибке, вероятно, должно измениться, потому что многие люди сталкиваются с этой ошибкой при связывании рукописного асма.
Как использовать RIP-относительную адресацию: основы
Всегда используйте RIP-относительную адресацию для простых случаев, когда нет недостатков. См. также сноску 1 ниже и этот ответ для синтаксиса. Рассматривайте возможность использования 32-битной абсолютной адресации только тогда, когда она полезна для размера кода, а не вредна например NASM default rel
вверху файла.
В AT & T foo(%rip)
или в GAS .intel_syntax noprefix
используйте [rip + foo]
.
Отключите режим PIE, чтобы 32-битная абсолютная адресация работала
Используйте gcc -fno-pie -no-pie
, чтобы переопределить это обратно к старому поведению. -no-pie
- это опция компоновщика, -fno-pie
- это опция генерации кода. Только с -fno-pie
gcc создаст код, подобный mov eax, offset .LC0
, который не связывается с все еще включенным -pie
.
(clang может также включать PIE по умолчанию: используйте clang -fno-pie -nopie
. Патч от июля 2017 года сделал -no-pie
псевдонимом для -nopie
, для совместим с gcc, но в clang4.0.1 его нет.)
Стоимость PIE для 64-битного (младшего) или 32-битного кода (мажорного)
Только с -no-pie
, (но все еще -fpie
) сгенерированный компилятором код (из источников C или C++) будет немного медленнее и больше, чем необходимо, но все равно будет связан с зависимым от позиции исполняемым файлом, который не получит выгоды от ASLR. "Слишком много PIE вредно для производительности" сообщает о среднем замедлении на 3% для x86-64 на SPEC CPU2006 (у меня нет копии документа, так что IDK, какое оборудование было на нем) :/). Но в 32-битном коде среднее замедление составляет 10%, в худшем - 25% (на SPEC CPU2006).
Наказание для исполняемых файлов PIE в основном за такие вещи, как индексация статических массивов, как описывает Агнер в вопросе, где использование статического адреса в качестве 32-битного непосредственного или как часть режима адресации [disp32 + index*4]
сохраняет инструкции и регистры по сравнению с RIP -относительный LEA, чтобы получить адрес в реестре. также 5-байтовый mov r32, imm32
вместо 7-байтового lea r64, [rel symbol]
для получения статического адреса в регистр удобен для передачи адреса строкового литерала или других статических данных в функцию.
-fPIE
по-прежнему не предполагает вставки символов для глобальных переменных/функций, в отличие от -fPIC
для разделяемых библиотек, которые должны пройти через GOT для доступа к глобальным переменным (что является еще одной причиной для использования static
для любых переменных, которые могут быть ограничены). подать область вместо глобальной). См. Плохое состояние динамических библиотек в Linux.
Таким образом, -fPIE
намного менее плох, чем -fPIC
для 64-битного кода, но все же плохо для 32-битной, поскольку относительная к RIP адресация недоступна. Смотрите некоторые примеры в проводнике компилятора Godbolt. В среднем, -fPIE
имеет очень маленький недостаток производительности/размера кода в 64-битном коде. Худший случай для конкретного цикла может составлять всего несколько%. Но 32-битный пирог может быть намного хуже.
Ни одна из этих опций -f
code-gen не имеет никакого значения при простом соединении,
или при сборке .S
рукописного ассм. gcc -fno-pie -no-pie -O3 main.c nasm_output.o
- это случай, когда вам нужны оба варианта.
Проверка настроек GCC
Если ваш GCC был настроен таким образом, gcc -v |& grep -o -e '[^ ]*pie'
печатает --enable-default-pie
. Поддержка этого параметра конфигурации была добавлена в gcc в начале 2015 года. Ubuntu включил его в 16.10 и Debian примерно в то же время в gcc 6.2.0-7
(что привело к ошибкам сборки ядра: https://lkml.org/lkml/2016/10/21/904).
Связано: Сборка сжатых ядер x86 как PIE также была изменена по умолчанию.
Почему Linux не рандомизирует адрес сегмента исполняемого кода? - более старый вопрос о том, почему он не был установлен по умолчанию ранее или был включен только для нескольких пакетов в более старой Ubuntu, прежде чем он был включен по всем направлениям.
Обратите внимание, что ld
сам не изменил значение по умолчанию. Он по-прежнему работает нормально (по крайней мере, в Arch Linux с binutils 2.28). Изменение состоит в том, что gcc
по умолчанию передает -pie
в качестве опции компоновщика, если вы явно не используете -static
или -no-pie
.
В исходном файле NASM я использовал a32 mov eax, [abs buf]
, чтобы получить абсолютный адрес. (Я проверял, есть ли 6-байтовый способ кодирования небольших абсолютных адресов (размер-адреса + mov eax, moffs: 67 a1 40 f1 60 00
)) срыв LCP на процессорах Intel. Это делает.)
nasm -felf64 -Worphan-labels -g -Fdwarf testloop.asm &&
ld -o testloop testloop.o # works: static executable
gcc -v -nostdlib testloop.o # does not work
...
..../collect2 ... -pie ...
/usr/bin/ld: testloop.o: relocation R_X86_64_32 against '.bss' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Nonrepresentable section on output
collect2: error: ld returned 1 exit status
gcc -v -no-pie -nostdlib testloop.o # works
gcc -v -static -nostdlib testloop.o # also works: -static implies -no-pie
связано: создание статических/динамических исполняемых файлов с/без libc, определение _start
или main
.
Проверка, является ли существующий исполняемый файл PIE или нет
file
и readelf
говорят, что PIE являются "общими объектами", а не исполняемыми файлами ELF. Статические исполняемые файлы не могут быть пирогом.
$ gcc -fno-pie -no-pie -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB executable, ...
$ gcc -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB shared object, ...
## Or with a more recent version of file:
a.out: ELF 64-bit LSB pie executable, ...
Об этом также спрашивали: Как проверить, был ли двоичный файл Linux скомпилирован как независимый от позиции код?
Полусвязанный (но не совсем): еще одна недавняя функция gcc - gcc -fno-plt
. Наконец, вызовы в общие библиотеки могут быть просто call [rip + [email protected]]
(AT & T call *[email protected](%rip)
), без батута PLT.
Надеюсь, дистрибутивы скоро начнут его включать, потому что он также избавляет от необходимости записываемых + исполняемых страниц памяти. Это значительное ускорение для программ, которые выполняют много вызовов совместно используемой библиотеки, например, x86-64 clang -O2 -g
компилирует tramp3d с 41,6 до 36,8 с на любом оборудовании , которое автор патча тестировал на. (Clang, возможно, является наихудшим сценарием для вызовов библиотеки общего доступа.)
Это требует раннего связывания вместо ленивого динамического связывания, поэтому оно медленнее для больших программ, которые выходят сразу. (например, clang --version
или компиляция hello.c
). Это замедление может быть уменьшено с помощью предварительной ссылки.
Это, однако, не устраняет накладные расходы GOT для внешних переменных в коде PIC совместно используемой библиотеки. (См. ссылку на крестник выше).
Сноски 1
64-разрядные абсолютные адреса фактически разрешены в общих объектах Linux ELF с перемещением текста, чтобы разрешить загрузку по разным адресам (ASLR и общие библиотеки). Это позволяет вам иметь таблицы переходов в section .rodata
или static const int *foo = &bar;
без инициализатора времени выполнения.
Итак, mov rdi, qword msg
работает (синтаксис NASM/YASM для 10-байтового mov r64, imm64
, он же AT & T синтаксис movabs
, единственная инструкция, которая может использовать 64-битную немедленную передачу). Но это больше и обычно медленнее, чем lea rdi, [rel msg]
, и это то, что вы должны использовать, если решите не отключать -pie
. По словам микроарханта Agner Fog pdf, 64-разрядные операционные системы медленнее извлекаются из кэша UOP на процессорах семейства Sandybridge. (Да, тот же человек, который задал этот вопрос. :)
Вы можете использовать NASM default rel
вместо указания его в каждом режиме адресации [rel symbol]
. См. также 64-разрядный формат Mach-O не поддерживает 32-разрядные абсолютные адреса. NASM Accessing Array для более подробного описания того, как избежать 32-битной абсолютной адресации. OS X вообще не может использовать 32-битные адреса, поэтому RIP-адресация также является лучшим способом.
В позиционно-зависимом коде (-no-pie
) вы должны использовать mov edi, msg
, когда вам нужен адрес в регистре; 5-байтовый mov r32, imm32
даже меньше, чем REA-относительный LEA, и больше исполнительных портов может его запустить.