Как загрузить адрес функции или метки в регистр в GNU Assembler
Я пытаюсь загрузить адрес main в регистр (R10) в GNU Assembler. Я не в состоянии. Вот я, что у меня есть и сообщение об ошибке я получаю.
main:
lea main, %r10
Я также попробовал следующий синтаксис (на этот раз используя mov)
main:
movq $main, %r10
С обоими из вышеупомянутых я получаю следующую ошибку:
/usr/bin/ld: /tmp/ccxZ8pWr.o: relocation R_X86_64_32S against symbol `main' 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
Компиляция с -fPIC не решает проблему и просто дает мне точно такую же ошибку.
1 ответ
В x86-64 большинство немедленных и смещений все еще 32-битные, потому что 64-битные будут тратить слишком много размера кода (занимаемая площадь I-кэша и пропускная способность выборки / декодирования)
lea main, %reg
это абсолют disp32
режим адресации, который не позволяет случайной выборке адресов во время загрузки (ASLR) выбирать случайный 64-битный (или 47-битный) адрес, поэтому он не поддерживается в Linux вне зависимых от позиции исполняемых файлов или вообще в MacOS. (См. Вики-тег x86 для ссылок на документы и руководства.)
Стандартный эффективный способ поместить статический адрес в регистр - это LEA относительно RIP:
# Use this, works everywhere
lea main(%rip), %r10 # 7 bytes
lea r10, [rip+main] # GAS .intel_syntax noprefix equivalent
lea r10, [rel main] # NASM equivalent, or use default rel
См. Как работают относительные RIP-ссылки на переменные, такие как "[RIP + _a]" в x86-64 GAS Intel-синтаксис? для объяснения 3-х синтаксисов.
Это использует 32-битное относительное смещение от конца текущей инструкции, как jmp
/ call
, Это может достигать любых статических данных в .data
, .bss
, .rodata
или функционировать в .text
при условии обычного ограничения общего размера в 2 ГБ для статического кода + данные.
В позиционно- зависимом коде gcc -fno-pie -no-pie
например) в Linux вы можете использовать 32-битную абсолютную адресацию для экономии размера кода. Также, mov r32, imm32
имеет немного лучшую пропускную способность, чем REA-относительный LEA на процессорах Intel/AMD, поэтому выполнение вне порядка может лучше перекрывать его с окружающим кодом. (Оптимизация по размеру кода обычно менее важна, чем большинство других вещей, но когда все остальное равно, выбирайте более короткую инструкцию. В этом случае все остальное по крайней мере равно, или также лучше с mov imm32
.)
Видите, что 32-разрядные абсолютные адреса больше не разрешены в x86-64 Linux? для получения дополнительной информации о том, как исполняемые файлы PIE по умолчанию. (Вот почему вы получили ошибку ссылки о -fPIC
с вашим использованием 32-битного абсолюта.)
# in a non-PIE executable, mov imm32 into a 32-bit register is even better
mov $main, %r10d # 6 bytes
mov $main, %edi # 5 bytes: no REX prefix needed for a "legacy" register
Обратите внимание, что запись любого 32-битного регистра всегда начинается с нуля в полный 64-битный регистр (R10 и RDI).
lea main, %edi
или же lea main, %rdi
будет также работать в исполняемом файле Linux без PIE, но никогда не использовать LEA с [disp32]
режим абсолютной адресации (даже в 32-битном коде, где для этого не требуется байт SIB); mov
всегда по крайней мере так же хорошо.
Суффикс размера операнда избыточен, если у вас есть операнд регистра, который однозначно определяет его; Я предпочитаю просто написать mov
вместо movl
или же movq
,
Глупый / плохой путь - это 10-байтовый 64-битный абсолютный адрес как непосредственный:
# Inefficient, DON'T USE
movabs $main, %r10 # 10 bytes including the 64-bit absolute address
Это то, что вы получаете в NASM, если вы используете mov rdi, main
вместо mov edi, main
так много людей в итоге делают это. Динамическое связывание в Linux действительно поддерживает исправления во время выполнения для 64-битных абсолютных адресов. Но сценарий использования этого для таблиц переходов, а не для абсолютных адресов, как сразу.
movq $sign_extended_imm32, %reg
(7 байт) все еще использует 32-битный абсолютный адрес, но тратит байты кода на расширенный знак mov
в 64-битный регистр вместо неявного расширения нуля до 64-битной от записи 32-битного регистра.
Используя movq
говоришь ГАЗу хочешь R_X86_64_32S
переезд вместо R_X86_64_64
64-битное абсолютное перемещение.
Единственная причина, по которой вы захотите эту кодировку, - это код ядра, где статические адреса находятся в верхних 2 ГБ 64-битного виртуального адресного пространства, а не в нижних 2 ГБ. mov
имеет небольшие преимущества по сравнению с lea
на некоторых процессорах (например, работающих на нескольких портах), но обычно, если вы можете использовать 32-битный абсолют, он находится в низком 2 ГБ виртуального адресного пространства, где mov r32, imm32
работает.
PS: Я намеренно пропустил обсуждение "больших" или "огромных" моделей памяти / кода, где RIP-относительная адресация +-2GiB не может достигать статических данных или, возможно, не может даже достигать других адресов кода. Вышеприведенное относится к моделям кода x86-64 System V ABI "small" и / или "small-PIC". Вам может понадобиться movabs $imm64
для средних и больших моделей, но это очень редко.
Я не знаю, если mov $imm32, %r32
работает в исполняемых файлах Windows x64 или DLL с исправлениями во время выполнения, но REA-относительный LEA, безусловно, делает.
Полусвязанный: вызов абсолютного указателя в машинном коде x86 - если вы используете JIT, попробуйте поместить буфер JIT рядом с существующим кодом, чтобы вы могли call rel32
, в противном случае movabs
указатель в регистр.