2011-02-06 11 views
29

Me gustaría escribir un pequeño compilador JIT de prueba de concepto para un procesador de lenguaje de juguete que he escrito (puramente académico), pero estoy teniendo algunos problemas en las altitudes medias del diseño. Conceptualmente, estoy familiarizado con el funcionamiento de JIT: ¿compila el código de bytes en el código (máquina o ensamblaje?) Para ejecutar. En el nivel de tuercas y pernos, sin embargo, no estoy realmente agarrando cómo realmente va sobre haciendo que.¿Cómo generar y ejecutar código nativo de forma dinámica?

Mi (muy "Novato") reacción automática, ya que tengo no es la primera idea de por dónde empezar, sería la de intentar algo así como lo siguiente:

  1. mmap() un bloque de memoria , el establecimiento de acceso a PROT_EXEC
  2. escribir el código nativo en el bloque
  3. tienda de los registros actuales (stack pointer, et al.) un lugar acogedor
  4. modificar los registros actuales para apuntar en el bloque de código nativo en la región asignada
  5. el código nativo ahora conseguiría ejecutado por la máquina
  6. restaurar los registros anteriores

es que incluso cerca de un/el algoritmo correcto? He intentado leer diferentes proyectos que sé que tienen compiladores JIT para estudiar (como V8), pero estas bases de datos resultan difíciles de consumir debido a su tamaño, y tengo poca idea de dónde empezar a buscar.

+9

Probablemente pueda simplificar aún más las cosas: a menudo puede simplemente tomar la dirección inicial de su código dentro del bloque 'mmap''ed y convertirla en un puntero de función. En ese caso, el código necesitaría guardar y restaurar sus propios registros y tal. Le recomendamos que consulte las convenciones de llamadas en sus plataformas ABI (Interfaz binaria de aplicaciones) para saber exactamente lo que necesita guardar (y cómo obtener argumentos del código C, funciones de llamada C, etc.). –

+0

No es que tenga mucha experiencia en esto, pero podría ver el intérprete de python de PiPi. He revisado el intérprete de CPython y es muy bueno para leer. – Falmarri

+3

¡Muy buena pregunta, Chris! – jweyrich

Respuesta

26
No

seguro acerca de Linux, pero esto funciona en x86/ventanas.
Actualización: 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); 

} 
+3

¿Lo has probado? Porque Windows tiene DEP para evitar exactamente esto, y produce una infracción de acceso para mí. – Puppy

+2

'buf' necesita ser marcado con privilegios de lectura/escritura/ejecución para ejecutarse correctamente con DEP, ya sea a través de una sección personalizada, reglas definidas pragma o' VirtualAlloc (Ex) ' – Necrolis

+0

Actualizado, pero era portable antes y ahora no es t. – Shelwien

3

El compilador Android Dalvik JIT también puede valer la pena mirar. Se supone que es bastante pequeño y delgado (no estoy seguro de si esto ayuda a comprenderlo o hace las cosas más complicadas). Se dirige a Linux también.

Si las cosas se ponen más serias, mirar a LLVM también podría ser una buena opción.

El enfoque del puntero a la función sugerido por Jeremiah suena bien. Es posible que desee utilizar la pila de la persona que llama de todos modos y probablemente solo quede unos pocos registros (en x86) que debe conservar o no. En este caso, probablemente sea más fácil si su código compilado (o el apéndice de entrada) los guarda en la pila antes de continuar. Al final, todo se reduce a escribir una función de ensamblador y la interconexión a ella a partir de C.

+0

¡Gran sugerencia! ¡Gracias! –

4

Youmay que desee echar un vistazo a libjit que proporciona exactamente la infraestructura que está buscando:

Las bibliotecas libjit implementos justo a tiempo funcionalidad de compilación . A diferencia de otros JIT, este está diseñado para ser independiente de cualquier máquina virtual particular formato de código de bytes o idioma.

http://freshmeat.net/projects/libjit

+0

Interesante encontrar. Esto puede ser útil si decido que realmente quiero implementar un JIT no trivial. –

0

Además de las técnicas sugeridas hasta ahora, podría ser útil para estudiar las funciones de creación de hilo. Si crea un nuevo hilo, con la dirección inicial configurada para su código generado, sabe con certeza que no hay registros viejos que deban guardarse o restaurarse, y el sistema operativo maneja la configuración de los registros relevantes por usted. Es decir, elimina los pasos 3, 4 y 6 de su lista.

+0

Lo único que dudaría aquí es que con los hilos, entonces tiene que lidiar con la sincronización, et al. - De lo contrario (si la sincronización se puede ignorar y/o diferir) esa es una idea bastante inteligente. –

0

Puede que esté interesado en el lenguaje de programación why the lucky stiffPotion. Es un lenguaje pequeño e incompleto que presenta compilación justo a tiempo. El tamaño pequeño de la poción lo hace más fácil de entender. El repositorio incluye una descripción del language's internals (el contenido de JIT comienza en el encabezado "~ the jit ~").

La implementación se complica por el hecho de que se ejecuta en el contexto de Potion's VM. Sin embargo, no dejes que esto te asuste. No lleva mucho tiempo ver lo que está tramando. Básicamente, el uso de un pequeño conjunto de códigos de operación de VM permite modelar algunas acciones como optimized assembly.

2

La respuesta depende de su compilador y dónde coloca el código. Ver http://encode.ru/threads/1273-Just-In-Time-Compilation-Improvement-For-ZPAQ?p=24902&posted=1#post24902

Prueba de 32 bits de Vista, Visual C++ da un error de DEP (Prevención de ejecución de datos) si el código se pone en la pila, montón, o la memoria estática. g ++, Borland y Mars pueden hacerse funcionar a veces. Los datos a los que se accede mediante el código JIT deben declararse como volátiles.

+1

¡Un gran punto, sobre declarar el respaldo como volátil! +1 –

2

How to JIT - an introduction es un nuevo artículo (a partir de hoy) que aborda algunos de estos problemas y describe la imagen más grande también.

Cuestiones relacionadas