Ввод кода в исполняемый файл во время выполнения

Я работаю над приложением (написанным на С++), которое генерирует некоторый машинный код во время выполнения (Linux, x86-64 сейчас, но я планирую мигрировать на ARM). Затем он сохраняет сгенерированный код в памяти и выполняет его, перепрыгивая в ячейку памяти. Долгое время у меня была проблема с распределением исполняемой памяти, но я, наконец, решил ее, используя:

uint8_t *memory = mmap (NULL, length, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

Пока это работает, но я не уверен, что это элегантный способ делать такие вещи. Интересно, как это делает исполняемый загрузчик?

Ответы

Ответ 1

Это, по существу, то, как исполняемые загрузчики делают вещи; в их случае они выполняют mmap файла, а не анонимное сопоставление, но помимо этого он по существу тот же.

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

Ответ 2

Ваше решение в основном заключается в том, что нужно делать: ОС рассматривает страницы как исполняемые. Однако некоторые операционные системы будут применять так называемую политику W ^ X, в которой страница может быть либо записываемой, либо исполняемой, но не одновременно одновременно, Для таких систем (а именно OpenBSD, но есть модифицированные версии Linux, которые тоже это делают), ваш mmap() выше не будет работать. Таким образом, полное решение повлечет за собой сначала выделение некоторых страниц с помощью mmap() и PROT_READ | PROT_WRITE, затем используйте mprotect() для переключения страниц на PROT_READ | PROT_EXEC, когда код был сгенерирован.

Даже если ОС не применяет W ^ X, настоятельно рекомендуется использовать вызов mprotect() из-за эффектов кеша (доступ к данным и их выполнение совершенно отделены друг от друга в ЦП, вы хотите быть уверены, что CPU будет использовать ваши недавно написанные коды операций, а не то, что было в ОЗУ сразу, mprotect() содержит для этого необходимую магию).