EBP + 6 вместо +8 в JIT-компиляторе - PullRequest
1 голос
/ 09 июня 2010

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

Сначала я определяю JIT "прототип" как для C, так и для C ++:

#ifdef __cplusplus 
    typedef void* (*_JIT_METHOD) (...);
#else
    typedef (*_JIT_METHOD) ();
#endif

У меня есть compile() функция, которая будет компилировать вещи в ASM и вставлять их в память:

void* compile (void* something)
{
    // grab some memory
    unsigned char* buffer = (unsigned char*) malloc (1024);

    // xor eax, eax
    // inc eax
    // inc eax
    // inc eax
    // ret -> eax should be 3
    /* WORKS!
    buffer[0] = 0x67;
    buffer[1] = 0x31;
    buffer[2] = 0xC0;
    buffer[3] = 0x67;
    buffer[4] = 0x40;
    buffer[5] = 0x67;
    buffer[6] = 0x40;
    buffer[7] = 0x67;
    buffer[8] = 0x40;
    buffer[9] = 0xC3; */

    // xor eax, eax
    // mov eax, 9
    // ret 4 -> eax should be 9
    /* WORKS!
    buffer[0] = 0x67;
    buffer[1] = 0x31;
    buffer[2] = 0xC0;
    buffer[3] = 0x67;
    buffer[4] = 0xB8;
    buffer[5] = 0x09;
    buffer[6] = 0x00;
    buffer[7] = 0x00;
    buffer[8] = 0x00;
    buffer[9] = 0xC3; */


    // push ebp
    // mov ebp, esp
    // mov eax, [ebp + 6] ; wtf? shouldn't this be [ebp + 8]!?
    // mov esp, ebp
    // pop ebp
    // ret -> eax should be the first value sent to the function
    /* WORKS! */
    buffer[0] = 0x66;
    buffer[1] = 0x55;
    buffer[2] = 0x66;
    buffer[3] = 0x89;
    buffer[4] = 0xE5;
    buffer[5] = 0x66;
    buffer[6] = 0x66;
    buffer[7] = 0x8B;
    buffer[8] = 0x45;
    buffer[9] = 0x06;
    buffer[10] = 0x66;
    buffer[11] = 0x89;
    buffer[12] = 0xEC;
    buffer[13] = 0x66;
    buffer[14] = 0x5D;
    buffer[15] = 0xC3;

    // mov eax, 5
    // add eax, ecx
    // ret -> eax should be 50
    /* WORKS!
    buffer[0] = 0x67;
    buffer[1] = 0xB8;
    buffer[2] = 0x05;
    buffer[3] = 0x00;
    buffer[4] = 0x00;
    buffer[5] = 0x00;
    buffer[6] = 0x66;
    buffer[7] = 0x01;
    buffer[8] = 0xC8;
    buffer[9] = 0xC3; */

    return buffer;
}

И, наконец, у меня есть основной кусок программы:

int main (int argc, char **args)
{
    DWORD oldProtect = (DWORD) NULL;
    int i = 667, j = 1, k = 5, l = 0;

    // generate some arbitrary function
    _JIT_METHOD someFunc = (_JIT_METHOD) compile(NULL);

    // windows only
#if defined _WIN64 || defined _WIN32
    // set memory permissions and flush CPU code cache
    VirtualProtect(someFunc,1024,PAGE_EXECUTE_READWRITE, &oldProtect);  
    FlushInstructionCache(GetCurrentProcess(), someFunc, 1024);
#endif

    // this asm just for some debugging/testing purposes
    __asm mov ecx, i

    // run compiled function (from wherever *someFunc is pointing to)
    l = (int)someFunc(i, k);

    // did it work?
    printf("result: %d", l);

    free (someFunc);
    _getch();

    return 0;
}

Как видите, функция compile() имеет несколько тестов, которые я провел, чтобы убедиться, что я получил ожидаемые результаты, и почти все работает, но у меня есть вопрос ...

В большинстве учебных пособий или ресурсов документации, чтобы получить первое значение переданной функции (в случае целочисленных значений), вы делаете [ebp+8], второе [ebp+12] и так далее. По какой-то причине я должен сделать [ebp+6], затем [ebp+10] и так далее. Кто-нибудь может сказать мне, почему?

Ответы [ 2 ]

8 голосов
/ 09 июня 2010

Ваши коды операций выглядят подозрительно: они полны 0x66 и 0x67 префиксов переопределения адреса / размера данных, которые (в 32-битном сегменте кода) превратят 32-битные операции в 16-битные. например,

buffer[0] = 0x66;
buffer[1] = 0x55;
buffer[2] = 0x66;
buffer[3] = 0x89;
buffer[4] = 0xE5;
...

есть

push bp
mov  bp, sp

вместо

push ebp
mov  ebp, esp

(что объясняет наблюдаемое поведение: нажатие bp уменьшает указатель стека на 2 вместо 4).

8 голосов
/ 09 июня 2010

Ваша проблема в байтах 66 и 67 - переопределение размера операнда и переопределение размера адреса соответственно.

Поскольку этот код выполняется в 32-разрядном режиме, эти байты сообщают процессору, что вы хотите использовать 16-разрядные операнды и адреса вместо 32-разрядных. 66 55 разбирается на PUSH BP, который выдвигает только 2 байта вместо 4, следовательно, ваши адреса отключены на 2.

Байты 67 в первых двух примерах также не нужны, но поскольку вы обращаетесь только к регистрам, а не к памяти, они не действуют и ничего не нарушают (пока). Эти байты также должны быть удалены.

Похоже, вы используете фреймворк, предназначенный для 16-битного кода, или, возможно, вы можете сказать, что хотите 32-битный код.

...