Я пишу свой собственный JIT-интерпретатор. Как выполнить сгенерированные инструкции? - PullRequest
7 голосов
/ 26 января 2012

Я собираюсь написать свой собственный JIT-интерпретатор в рамках курса по виртуальным машинам.У меня много знаний о языках высокого уровня, компиляторах и интерпретаторах, но мало или совсем нет знаний о сборке x86 (или C в этом отношении).

На самом деле я не знаю, как работает JIT, ноВот мое мнение: читать в программе на некотором промежуточном языке.Скомпилируйте это в инструкции x86.Убедитесь, что последняя инструкция вернулась в код VM.Храните инструкции где-нибудь в памяти.Сделайте безусловный переход к первой инструкции.Вуаля!

Итак, имея в виду, у меня есть следующая небольшая программа на C:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int *m = malloc(sizeof(int));
    *m = 0x90; // NOP instruction code

    asm("jmp *%0"
               : /* outputs:  */ /* none */
               : /* inputs:   */ "d" (m)
               : /* clobbers: */ "eax");

    return 42;

}

Хорошо, поэтому я намерен сохранить эту программу для храненияИнструкция NOP где-нибудь в памяти, перейти в это место и затем, вероятно, вылететь (потому что я не настроил программу для возврата к основному).

Вопрос: Являюсь ли яна правильном пути?

Вопрос: Не могли бы вы показать мне модифицированную программу, которой удается найти путь назад где-то внутри main?

Вопрос: Другие вопросы, которые мне следует остерегаться?

PS: Моя цель - получить понимание, не обязательно делать все правильно.


Спасибо за все отзывы.Следующий код, кажется, является местом для запуска и работает на моем Linux-компьютере:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>

unsigned char *m;

int main() {
        unsigned int pagesize = getpagesize();
        printf("pagesize: %u\n", pagesize);

        m = malloc(1023+pagesize+1);
        if(m==NULL) return(1);

        printf("%p\n", m);
        m = (unsigned char *)(((long)m + pagesize-1) & ~(pagesize-1));
        printf("%p\n", m);

        if(mprotect(m, 1024, PROT_READ|PROT_EXEC|PROT_WRITE)) {
                printf("mprotect fail...\n");
                return 0;
        }

        m[0] = 0xc9; //leave
        m[1] = 0xc3; //ret
        m[2] = 0x90; //nop

        printf("%p\n", m);


asm("jmp *%0"
                   : /* outputs:  */ /* none */
                   : /* inputs:   */ "d" (m)
                   : /* clobbers: */ "ebx");

        return 21;
}

Ответы [ 2 ]

8 голосов
/ 26 января 2012

Вопрос: Я на правильном пути?

Я бы сказал, да.

Вопрос: Не могли бы вы показать мне измененную программу, которая управляетнайти путь назад куда-нибудь внутри main?

У меня нет кода для вас, но лучший способ добраться до сгенерированного кода и обратно - использовать пару call /ret инструкции, так как они будут управлять обратным адресом автоматически.

Вопрос: Другие вопросы, о которых я должен опасаться?

Да - в качестве меры безопасности многие работаютсистемы будут препятствовать выполнению кода в куче без специальных мер.Эти специальные меры обычно сводятся к тому, что вам необходимо пометить соответствующие страницы памяти как исполняемые.

В Linux это делается с помощью mprotect() с PROT_EXEC.

3 голосов
/ 26 января 2012

Если ваш сгенерированный код следует надлежащему соглашению о вызовах , тогда вы можете объявить тип указателя на функцию и вызвать функцию следующим образом:

typedef void (*generated_function)(void);

void *func = malloc(1024);
unsigned char *o = (unsigned char *)func;
generated_function *func_exec = (generated_function *)func;

*o++ = 0x90;     // NOP
*o++ = 0xcb;     // RET

func_exec();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...