Как загрузить адрес функции или метки в регистр в 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 указатель в регистр.

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