Вызов абсолютного указателя в машинный код x86

Какой «правильный» способ call абсолютный указатель на машинный код x86? Есть ли хороший способ сделать это в одной инструкции?

Что я хочу сделать:

Я пытаюсь создать своего рода упрощенную мини-JIT (по-прежнему) на основе «подпрограммы». Это в основном кратчайший шаг от интерпретатора байт-кода: каждый код операции реализован как отдельная функция, поэтому каждый базовый блок байт-кодов может быть «JITted» в новой процедуре, которая выглядит примерно так:

 {prologue} call {opcode procedure 1} call {opcode procedure 2} call {opcode procedure 3} ...etc {epilogue} 

Таким образом, идея состоит в том, что фактический машинный код для каждого блока может быть просто вставлен из шаблона (при необходимости увеличивая среднюю часть), и единственный бит, который должен быть «динамически» обработан, – это копирование указателей функций для каждого кода операции в правильные места как часть каждой команды вызова.

Проблема, с которой я сталкиваюсь, – это понять, что использовать для call ... часть шаблона. x86, похоже, не настроен с учетом этого вида использования и не поддерживает относительные и косвенные вызовы.

Похоже, что я могу использовать либо FF 15 EFBEADDE либо 2E FF 15 EFBEADDE чтобы гипотетически вызвать функцию DEADBEEF (в основном они были обнаружены, помещая материал в ассемблер и дизассемблер и видя, что дает достоверные результаты, а не понимает, что они делают), но Я недостаточно разбираюсь в том, что касается сегментов, привилегий и связанной информации, чтобы увидеть разницу или как они будут отличаться от более часто встречающейся инструкции call . Руководство по архитектуре Intel также предполагает, что они действительны только в 32-битном режиме и «недействительны» в 64-битном режиме.

Может ли кто-нибудь объяснить эти коды операций и как, или, если, я буду использовать их или другие для этой цели?

(Существует также очевидный ответ на использование косвенного вызова через регистр, но это похоже на «неправильный» подход – если на самом деле существует инструкция прямого вызова.)

Все здесь применимо к jmp к абсолютным адресам, и синтаксис для задания цели одинаковый. Вопрос задает вопрос о JITing, но я также включил синтаксис NASM и AT & T для расширения сферы действия.


x86 не имеет кодировки для обычного (ближнего) call или jmp для абсолютного адреса, закодированного в инструкции (т. е. прямого вызова / jmp). См. Инструкцию Intel по установке INN для ввода call . (См. Также вики-tags x86 для других ссылок на документы и руководства.) Большинство компьютерных архитектур используют относительные кодировки для обычных прыжков, таких как x86, BTW.

Лучшим вариантом (если вы можете сделать код, зависящий от позиции, который знает свой собственный адрес), является использование обычного call rel32 , прямого кодирования прямого вызова E8 rel32 , где поле rel32 является target - end_of_call_insn (двоичное целое двоичного дополнения 2).

См. Как работает $ в NASM? на примере ручной кодировки команды call ; делая это, в то время как JITing должен быть таким же простым.

В синтаксисе AT & T: call 0x1234567
В синтаксисе NASM: call 0x1234567
Также работает с именованным символом с абсолютным адресом (например, созданный с помощью equ или .set )

Они собираются и соединяются очень хорошо в зависимости от положения кода (а не для общего lib или исполняемого файла PIE). Но не в x86-64 OS X, где текстовый раздел отображается выше 4GiB, поэтому он не может достичь низкого адреса с rel32 . Выберите свой абсолютный адрес в диапазоне от того, от кого вы его вызываете.


Но если вам нужно сделать независимый от позиции код , который не знает свой собственный абсолютный адрес, или если адрес, который вам нужно позвонить, больше, чем + -2GiB от вызывающего (возможно в 64-битном, но лучше разместить код достаточно близко), вы должны использовать регистр-косвенный call

 ; use any register you like as a scratch mov eax, 0xdeadbeef ; 5 byte mov r32, imm32 ; or mov rax, 0x7fffdeadbeef ; for addresses that don't fit in 32 bits call rax ; 2 byte FF D0 

Или синтаксис AT & T

 mov $0xdeadbeef, %eax # movabs $0x7fffdeadbeef, %rax # mov r64, imm64 call *%rax 

Если вам действительно нужно избегать изменения каких-либо регистров, возможно, сохранить абсолютный адрес в качестве константы в памяти и использовать косвенный косвенный call с режимом адресации, ориентированным на RIP, например

call [rel function_pointer] NASM call [rel function_pointer] ; Если вы не можете
AT & T call *function_pointer(%rip)


Обратите внимание, что косвенные вызовы / переходы делают ваш код потенциально уязвимым для атак Spectre , особенно если вы JITing как часть изолированной программы для ненадежного кода в рамках одного и того же процесса. (В этом случае патчи ядра не будут защищать вас).

Косвенные прыжки также будут иметь несколько худшие штрафные санкции за неверный outlook, чем прямой ( call rel32 ) . Назначение обычного прямого call insn известно, как только оно декодируется, ранее в конвейере, как только обнаруживается, что там есть ветка вообще.

Непрямые ветви обычно хорошо предсказывают современные аппаратные средства x86 и обычно используются для вызовов динамических библиотек / библиотек DLL. Это не страшно, но call rel32 определенно лучше.

Однако даже прямой call требует некоторого предсказания ветвления, чтобы избежать пузырьков трубопровода. (Перед декодированием требуется предсказание, например, учитывая, что мы просто извлекли этот блок, какой блок должен jmp next_instruction следующий этап выборки. Последовательность jmp next_instruction замедляется, когда у вас заканчиваются записи ветви-предсказателя ). mov + косвенный call reg также хуже даже при идеальном предсказании ветвления, потому что это больший размер кода и больше uops, но это довольно минимальный эффект. Если дополнительный mov является проблемой, вложение кода вместо его вызова является хорошей идеей, если это возможно.


call 0xdeadbeef факт: call 0xdeadbeef будет собираться, но не связываться с 64-битным статическим исполняемым файлом в Linux , если только вы не используете скрипт компоновщика, чтобы поместить сегмент .text / text ближе к этому адресу. Раздел .text обычно начинается с 0x400080 в статическом исполняемом файле (или динамическом исполняемом файле , отличном от PIE ), то есть в низком виртуальном адресном пространстве 2GiB, где все статические коды / данные живут в модели кода по умолчанию. Но 0xdeadbeef находится в верхней половине низких 32 бит (т. 0xdeadbeef низком 4G, но не в низком 2G), поэтому он может быть представлен как 32-разрядное целое число с расширенным нулем, но не 32-битное расширенное расширение. И 0x00000000deadbeef - 0x0000000000400080 не помещается в 32-битное целое число, которое будет правильно распространено до 64 бит. (Часть адресного пространства, с которым вы можете связаться с отрицательным rel32 который обтекает с низкого адреса, является верхним 2GiB 64-разрядного адресного пространства, обычно верхняя половина адресного пространства зарезервирована для использования kernelм.)

Он собирает ok с yasm -felf64 -gdwarf2 foo.asm , а objdump -drwC -Mintel показывает:

 foo.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <.text>: 0: e8 00 00 00 00 call 0x5 1: R_X86_64_PC32 *ABS*+0xdeadbeeb 

Но когда ld пытается связать его с статическим исполняемым файлом, где .text начинается с 0000000000400080 , ld -o foo foo.o говорит foo.o:/tmp//foo.asm:1:(.text+0x1): relocation truncated to fit: R_X86_64_PC32 against '*ABS*' .

В 32-битном кодовом call 0xdeadbeef собирает и ссылки просто отлично, потому что rel32 может достигать где угодно из любого места. Относительное смещение не должно быть расширено до 64 бит, это просто 32-битное двоичное добавление, которое может обернуться вокруг или нет.


Прямые кодировки кодов (медленные, не используемые)

В записях руководства для call и jmp вы можете заметить, что существуют кодировки с абсолютными целевыми адресами, закодированными прямо в инструкции. Но они существуют только для «дальнего» call / jmp которые также устанавливают CS в новый селектор сегмента кода, который медленный (см. Руководства Agner Fog) .

CALL ptr16:32 («Звонок далеко, абсолютный, адрес, заданный в операнде») имеет 6-байтовый сегмент: смещение, закодированное прямо в инструкции, вместо того, чтобы загружать его как данные из местоположения, заданного нормальным режимом адресации. Таким образом, это прямой вызов абсолютного адреса.

Дальний call также подталкивает CS: EIP в качестве обратного адреса вместо EIP, поэтому он даже не совместим с обычным (ближним) call который только подталкивает EIP. Это не проблема для jmp ptr16:32 , просто медлительность и выяснение того, что поставить для части сегмента.

Изменение CS обычно полезно только для перехода от 32 до 64-битного режима или наоборот. Обычно это будут только ядра, хотя вы можете сделать это в пользовательском пространстве в большинстве обычных ОС, которые сохраняют дескрипторы сегментов 32 и 64 бита в GDT. Тем не менее, это был бы глупый компьютерный трюк, чем что-то полезное. (64-разрядные ядра возвращаются в 32-разрядное пользовательское пространство с помощью iret или, возможно, с sysexit . Большинство ОС используют только один jmp один раз во время загрузки, чтобы переключиться на 64-битный сегмент кода в режиме ядра.)

Mainstream OSes использует модель с плоской памятью, где вам никогда не нужно менять cs , и не стандартизировано, какое значение cs будет использоваться для процессов пользовательского пространства. Даже если вы хотите использовать jmp , вам нужно выяснить, какое значение следует добавить в селектор сегмента. (Легко, когда JITing: просто прочитайте текущие cs с помощью mov eax, cs . Но трудно быть переносимым для компиляции в режиме времени).


call ptr16:64 не существует, далекие прямые кодировки существуют только для 16 и 32-битного кода. В 64-битном режиме вы можете call только с 10-байтовым m16:64 памяти m16:64 , например, call far [rdi] . Или нажмите сегмент: смещение в стеке и используйте retf .

Вы не можете сделать это только с одной инструкцией. Достойный способ сделать это с MOV + CALL:

 0000000002347490: 48b83412000000000000 mov rax, 0x1234 000000000234749a: 48ffd0 call rax 

Если адрес процедуры для вызова изменений, измените восемь байтов, начиная со смещения 2. Если адрес кода, вызывающего 0x1234, изменится, вам не нужно ничего делать, потому что адресация является абсолютной.

  • Распределение стека, отступы и выравнивание
  • x86_64 ASM - максимальные байты для команды?
  • Как слить скаляр в вектор без компилятора, теряющего инструкцию обнуления верхних элементов? Ограничение дизайна в встроенных средах Intel?
  • Каков наилучший способ установить регистр в ноль в сборке x86: xor, mov или или?
  • Относительная производительность команды x86 inc vs. add
  • Изменение режима округления с плавающей запятой
  • Как вы используете gcc для генерации кода сборки в синтаксисе Intel?
  • Использование LEA для значений, которые не являются адресами / указателями?
  • Какова цель XORing регистрации с собой?
  • Как написать привет мир в ассемблере под Windows?
  • Какие целые операции с дополнением 2 можно использовать без обнуления высоких бит в входах, если требуется только низкая часть результата?
  • Interesting Posts

    Как читать текст из скрытого элемента с помощью Selenium WebDriver?

    Редактировать команду в Windows 8

    Java Calendar.set (Calendar.DAY_OF_WEEK, Calendar.WUNDAY), будет ли он откатываться назад, вперед или неизвестно?

    Ошибка установки Intel HAXM. Этот компьютер не поддерживает технологию Intel Virtualization Technology (VT-x)

    Возможно ли связать домен с моим сервером без DNS?

    Генерировать gcda-файлы с Xcode5, iOS7-симулятором и XCTest

    Android Telegram App -> java.lang.UnsatisfiedLinkError: не реализована реализация для void

    Расписание доставки FCM или время push-уведомления

    Создание URL-адреса в controllerе .NET MVC

    Почему LINQ to Entities не распознает метод «System.String ToString ()?

    C # Делегирование делегата или просто передача ссылки на метод

    Использование ELMAH в консольном приложении

    Есть ли команда изменить путь из текущего местоположения в каталог по умолчанию, в командной строке Windows?

    неверное преобразование из `void * ‘в` char *’ при использовании malloc?

    GCC (/ Clang): функции объединения с идентичными инструкциями (сложение COMDAT)

    Давайте будем гением компьютера.