2010-06-09 3 views
1

Je suis en train d'implémenter un compilateur JIT simpliste dans une machine virtuelle que j'écris pour m'amuser (surtout pour en apprendre plus sur la conception de langage) et j'obtiens un comportement étrange Quelqu'un peut me dire pourquoi.ebp + 6 au lieu de +8 dans un compilateur JIT

D'abord, je définir un JIT "prototype" à la fois pour C et C++:

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

J'ai une fonction compile() qui compile des choses dans l'ASM et le coller quelque part en mémoire:

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

Et enfin j'ai le morceau principal du programme:

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

Comme vous c un voir, la fonction compile() a un couple de tests que j'ai couru pour m'assurer que les résultats attendus, et à peu près tout fonctionne mais j'ai une question ...

Sur la plupart des tutoriels ou des ressources de documentation, pour obtenir la première valeur d'une fonction passée (dans le cas d'ints) vous faites [ebp+8], le second [ebp+12] et ainsi de suite. Pour une raison quelconque, je dois faire [ebp+6] puis [ebp+10] et ainsi de suite. Quelqu'un pourrait-il me dire pourquoi?

Répondre

8

Vos codes d'opération sont suspects: ils sont remplis de 0x66 et de 0x67 préfixes de remplacement de taille d'adresse/de données, qui (dans un segment de code 32 bits) transformeront les opérations 32 bits en opérations 16 bits. par exemple.

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

est

push bp 
mov bp, sp 

plutôt que

push ebp 
mov ebp, esp 

(qui semble expliquer le comportement observé: pousser bp décrémente le pointeur de pile par deux au lieu de 4).

+0

Merci, donc que dois-je utiliser à la place de '0x66' et' 0x67'. De plus, le bytecode ASM a été assemblé avec TASM (probablement en mode 16 bits). –

+0

Je ne suis pas familier avec TASM ... mais vous devez assembler en mode 32 bits. (Bien que, dans ces exemples simples, probablement tout ce que vous devez faire est de supprimer complètement les octets '0x66' et' 0x67': ils sont juste des octets de préfixe qui modifient la signification de l'opcode suivant, par exemple 'push ebp; mov ebp, esp 'est juste' 0x55', '0x89',' 0xE5' en mode 32 bits. –

+0

Génial, merci pour l'aide, je l'apprécie! –

8

Votre problème est le 66 et 67 octets - remplacement de taille d'opérande et remplacement de la taille d'adresse, respectivement.

Étant donné que vous exécutez ce code en mode 32 bits, ces octets indiquent au processeur que vous souhaitez des opérandes et des adresses 16 bits plutôt que 32 bits. Les 66 55 désassemble à PUSH BP, qui ne fait que repousser 2 octets au lieu de 4, d'où vos adresses étant hors de 2.

Les 67 octets dans les deux premiers exemples sont également unncessary, mais parce que vous êtes seulement accès aux registres et pas de mémoire , ils n'ont aucun effet et ne cassent rien (pour l'instant). Ces octets devraient également être supprimés. Il semble que vous utilisiez un cadre conçu pour le code 16 bits, ou peut-être que vous pouvez lui dire que vous voulez un code 32 bits.

Questions connexes