Абсолютная адресация для замены кода во время выполнения в x86_64

#gcc #assembly #64-bit #x86-64 #addressing

#gcc #сборка #64-разрядный #x86-64 #адресация

Вопрос:

В настоящее время я использую некоторую схему замены кода в 32-разрядной версии, где код, который перемещается в другую позицию, считывает переменные и указатель класса. Поскольку x86_64 не поддерживает абсолютную адресацию, у меня возникают проблемы с получением правильных адресов для переменных в новой позиции кода. Подробно проблема заключается в том, что из-за относительной адресации rip адрес указателя инструкции отличается от адреса во время компиляции.

Итак, есть ли способ использовать абсолютную адресацию в x86_64 или другой способ получить адреса переменных, а не относительный указатель инструкции?

Что-то вроде: leaq variable(%%rax), %%rbx также помогло бы. Я только хочу не зависеть от указателя инструкции.

Ответ №1:

Попробуйте использовать модель большого кода для x86_64. В gcc это можно выбрать с помощью -mcmodel=large . Компилятор будет использовать 64-разрядную абсолютную адресацию как для кода, так и для данных.

Вы также можете добавить -fno-pic, чтобы запретить генерацию позиционно-независимого кода.

Редактировать: я создал небольшое тестовое приложение с помощью -mcmodel=large, и полученный двоичный файл содержит такие последовательности, как

 400b81:       48 b9 f0 30 60 00 00    movabs $0x6030f0,%rcx
400b88:       00 00 00 
400b8b:       49 b9 d0 09 40 00 00    movabs $0x4009d0,%r9
400b92:       00 00 00 
400b95:       48 8b 39                mov    (%rcx),%rdi
400b98:       41 ff d1                callq  *%r9
  

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

 moveabs $variable, %rbx
addq %rax, %rbx
  

эквивалентно «leaq offset64bit (%rax), %rbx» (которого не существует), с некоторыми побочными эффектами, такими как смена флага и т. Д.

Комментарии:

1. решением должен быть -mcmodel=large . я должен выяснить, почему компилятор gcc osx не поддерживает его

2. Может быть, это слишком старый. Модели малого (стандартного) и среднего кода были добавлены раньше, большая модель появилась позже.

3. большое вам спасибо, это выглядит очень хорошо и определенно решает проблему абсолютной адресации

4. movabs variable, %rbx будет нагрузкой (и не может быть закодирован с 64-разрядным абсолютным адресом, потому что адресатом не является RAX); вы имели в виду movabs $variable, %rbx for mov $imm64, %r64 (работает для любого регистра, в отличие от moffs нагрузок, которые работают только для AL / AX / EAX / RAX )?

5. @PeterCordes Да, я пропустил «$». Хорошая находка.

Ответ №2:

То, о чем вы спрашиваете, выполнимо, но не очень просто.

Один из способов сделать это — компенсировать перемещение кода в его инструкциях. Вам нужно найти все инструкции, которые используют адресацию RIP-relative (они имеют ModRM байт 05h, 0dh, 15h, 1dh, 25h, 2dh, 35h или 3dh) и скорректировать их disp32 поле на величину перемещения (поэтому перемещение ограничено /- 2 ГБ в виртуальном адресепространство, которое не может быть гарантировано, учитывая, что 64-разрядное адресное пространство больше 4 ГБ).

Вы также можете заменить эти инструкции их эквивалентами, скорее всего, заменив каждую исходную инструкцию несколькими, например:

 ; These replace the original instruction and occupy exactly as many bytes as the original instruction:
  JMP Equivalent1
  NOP
  NOP
Equivalent1End:

; This is the code equivalent to the original instruction:
Equivalent1:
  Equivalent subinstruction 1
  Equivalent subinstruction 2
  ...
  JMP Equivalent1End
  

Оба метода потребуют, по крайней мере, некоторых элементарных процедур разборки x86.

Для первого может потребоваться использование VirtualAlloc() в Windows (или какой-либо эквивалент в Linux), чтобы гарантировать, что память, содержащая исправленную копию исходного кода, находится в пределах / — 2 ГБ от этого исходного кода. И распределение по определенным адресам все равно может завершиться неудачей.

Последнее потребует не только примитивной разборки, но и полного декодирования и генерации инструкций.

Могут быть и другие проблемы, которые нужно обойти.

Границы инструкций также можно найти, установив TF флаг в RFLAGS регистре, чтобы заставить процессор генерировать single-step прерывание отладки в конце выполнения каждой инструкции. Обработчик исключений отладки должен будет перехватить их и записать значение RIP следующей инструкции. Я считаю, что это можно сделать с помощью Structured Exception Handling (SEH) Windows (никогда не пробовал с прерываниями отладки), не уверен насчет Linux. Чтобы это сработало, вам нужно выполнить весь код, каждую инструкцию.

Кстати, в 64-битном режиме существует абсолютная адресация, см., Например, Инструкции по перемещению в / из накопителя с кодами операций от 0A0h до 0A3h.

Комментарии:

1. спасибо за ответ. насколько я вас понимаю, подход заключается в пересчете значения адреса для movs во время выполнения. Это выглядит тяжелым для схемы, поэтому, если это единственный способ, я бы подумал о другой реализации