Pergunta

Estou implementando um compilador JIT simplista em uma VM que estou escrevendo para me divertir (principalmente para aprender mais sobre design de idiomas) e estou recebendo um comportamento estranho, talvez alguém possa me dizer o porquê.

Primeiro, defino um "protótipo" JIT para C e C ++:

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

eu tenho um compile() função que compilará as coisas no ASM e colá -las em algum lugar na memória:

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;
}

E finalmente eu tenho a parte principal do programa:

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;
}

Como você pode ver, o compile() A função tem alguns testes que executei para garantir que eu tenha resultados esperados, e praticamente tudo funciona, mas tenho uma pergunta ...

Na maioria dos tutoriais ou recursos de documentação, para obter o primeiro valor de uma função (no caso de ints) que você faz [ebp+8], o segundo [ebp+12] e assim por diante. Por algum motivo, eu tenho que fazer [ebp+6] então [ebp+10] e assim por diante. Alguém poderia me dizer o porquê?

Foi útil?

Solução

Seus códigos de opções parecem suspeitos: eles estão cheios de 0x66 e 0x67 Prefixos de substituição de tamanho de endereço/tamanho de dados, que (em um segmento de código de 32 bits) transformarão operações de 32 bits em 16 bits. por exemplo

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

é

push bp
mov  bp, sp

ao invés de

push ebp
mov  ebp, esp

(que parece explicar o comportamento observado: empurrando bp diminui o ponteiro da pilha em 2 em vez de 4).

Outras dicas

Seu problema é o 66 e 67 Bytes - Substituição do tamanho do operador e substituição do tamanho do endereço, respectivamente.

Como você está executando esse código no modo de 32 bits, esses bytes informam ao processador que você deseja operando e endereços de 16 bits em vez de 32 bits. o 66 55 desmonta para PUSH BP, que empurra apenas 2 bytes em vez de 4, portanto, seus endereços estão desligados em 2.

o 67 Os bytes nos dois primeiros exemplos também são desnecessários, mas como você está acessando apenas registros e não a memória, eles não têm efeito e ainda não quebram nada (ainda). Esses bytes também devem ser removidos.

Parece que você está usando uma estrutura projetada para código de 16 bits, ou talvez haja uma maneira de dizer que deseja um código de 32 bits.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top