Подтвердить что ты не робот

Как генерировать и запускать собственный код динамически?

Я бы хотел написать очень маленький JIT-компилятор для разработки игрушечного процессора, который я написал (чисто академический), но у меня проблемы с дизайном среднего уровня. Концептуально, я знаком с тем, как работает JIT - вы компилируете байт-код в (машинный или сборный?) Код для запуска. Однако на уровне орехов и болтов я не совсем понимаю, как вы на самом деле делаете это.

Моя (очень "новая" ) реакция коленного рефлекса, так как у меня нет первой подсказки, с которой начать, было бы попробовать что-то вроде следующего:

  • mmap() блок памяти, установив доступ к PROT_EXEC
  • записать собственный код в блок
  • хранить текущие регистры (указатель стека и т.д.) где-то уютно
  • изменить текущие регистры, чтобы указать на собственный блок кода в отображаемой области
  • исходный код теперь будет выполняться машиной
  • восстановить предыдущие регистры

Является ли это даже близким к/правильному алгоритму? Я пробовал изучать разные проекты, которые, как я знаю, имеют JIT-компиляторы для изучения (например, V8), но эти кодовые базы оказываются трудными потребляют из-за их размера, и я не знаю, с чего начать искать.

4b9b3361

Ответ 1

Не уверен в Linux, но это работает на x86/windows.
Обновление: http://codepad.org/sQoF6kR8

#include <stdio.h>
#include <windows.h>

typedef unsigned char byte;

int arg1;
int arg2;
int res1;

typedef void (*pfunc)(void);

union funcptr {
  pfunc x;
  byte* y;
};

int main( void ) {

  byte* buf = (byte*)VirtualAllocEx( GetCurrentProcess(), 0, 1<<16, MEM_COMMIT, PAGE_EXECUTE_READWRITE );

  if( buf==0 ) return 0;

  byte* p = buf;

  *p++ = 0x50; // push eax
  *p++ = 0x52; // push edx

  *p++ = 0xA1; // mov eax, [arg2]
  (int*&)p[0] = &arg2; p+=sizeof(int*);

  *p++ = 0x92; // xchg edx,eax

  *p++ = 0xA1; // mov eax, [arg1]
  (int*&)p[0] = &arg1; p+=sizeof(int*);

  *p++ = 0xF7; *p++ = 0xEA; // imul edx

  *p++ = 0xA3; // mov [res1],eax
  (int*&)p[0] = &res1; p+=sizeof(int*);

  *p++ = 0x5A; // pop edx
  *p++ = 0x58; // pop eax
  *p++ = 0xC3; // ret

  funcptr func;
  func.y = buf;

  arg1 = 123; arg2 = 321; res1 = 0;

  func.x(); // call generated code

  printf( "arg1=%i arg2=%i arg1*arg2=%i func(arg1,arg2)=%i\n", arg1,arg2,arg1*arg2,res1 );

}

Ответ 2

Вы хотите взглянуть на libjit, который обеспечивает именно ту инфраструктуру, которую вы ищете:

Библиотека libjit реализует сборка "точно в срок" функциональность. В отличие от других JIT, это один из них не зависит от любая конкретная виртуальная машина формат байт-кода или язык.

http://freshmeat.net/projects/libjit

Ответ 3

Компилятор Android Dalvik JIT также стоит посмотреть. Предполагается, что он довольно маленький и худой (не уверен, что это помогает понять его или усложнить). Он также нацелен на Linux.

Если ситуация становится более серьезной, поиск LLVM также может быть хорошим выбором.

Метод указателя функции, предложенный Джеремией, звучит хорошо. В любом случае вы можете использовать стек вызывающего абонента, и, вероятно, останется только несколько регистров (на x86), которые вам необходимо сохранить или не коснуться. В этом случае, вероятно, проще всего, если ваш скомпилированный код (или заглушка записи) сохранит их в стеке, прежде чем продолжить. В конце концов, все это сводится к написанию функции ассемблера и сопряжению с ним с C.

Ответ 4

Как JIT - введение - это новая статья (с сегодняшнего дня!), которая затрагивает некоторые из этих проблем и описывает также большую картину.

Ответ 5

Ответ зависит от вашего компилятора и места размещения кода. См. http://encode.ru/threads/1273-Just-In-Time-Compilation-Improvement-For-ZPAQ?p=24902&posted=1#post24902

Тестирование в 32-битной Vista, Visual С++ дает ошибку DEP (предотвращение выполнения данных), независимо от того, помещен ли код в стек, кучу или статическую память. g++, Borland и Mars могут работать иногда. Данные, к которым обращается код JIT, должны быть объявлены изменчивыми.

Ответ 6

В дополнение к предложенным до сих пор методам, возможно, стоит рассмотреть функции создания потоков. Если вы создаете новый поток, при стартовом адресе, заданном вашим сгенерированным кодом, вы точно знаете, что нет старых регистров, которые требуют сохранения или восстановления, а ОС обрабатывает настройки соответствующих регистров для вас. I.Вы исключаете шаги 3, 4 и 6 вашего списка.

Ответ 7

Возможно, вас заинтересует почему повезло жесткое Potion язык программирования. Это небольшой, неполный язык, который содержит компиляцию точно в срок. Маленький размер зелья облегчает понимание. Репозиторий включает описание внутренних языков (содержимое JIT начинается с заголовка ~ jit ~").

Реализация осложняется тем, что она выполняется в контексте Potion VM. Однако не позволяйте этому отпугивать вас. Это не займет много времени, чтобы увидеть, что он делает. В основном, используя небольшой набор кодов операций VM, некоторые действия можно моделировать как оптимизированная сборка.