EBP + 6 invece di +8 in un compilatore JIT
-
26-09-2019 - |
Domanda
Sto implementando un compilatore JIT semplicistico in una VM che sto scrivendo per divertimento (principalmente per saperne di più sulla progettazione del linguaggio) e sto ottenendo un comportamento strano, forse qualcuno può dirmi perché.
Per prima cosa definisco un "prototipo" JIT sia per C che per C ++:
#ifdef __cplusplus
typedef void* (*_JIT_METHOD) (...);
#else
typedef (*_JIT_METHOD) ();
#endif
Ho un compile()
Funzione che compilerà cose in ASM e le infilerà da qualche parte in memoria:
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 infine ho il pezzo principale del programma:
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;
}
Come puoi vedere, il compile()
La funzione ha un paio di test che ho corso per assicurarmi di ottenere risultati attesi e praticamente tutto funziona ma ho una domanda ...
Sulla maggior parte dei tutorial o delle risorse di documentazione, per far passare il primo valore di una funzione (nel caso di INT) [ebp+8]
, il secondo [ebp+12]
e così via. Per qualche motivo, devo farlo [ebp+6]
poi [ebp+10]
e così via. Qualcuno potrebbe dirmi perché?
Soluzione
I tuoi Opcodi sembrano sospettosi: sono pieni di 0x66
e 0x67
La dimensione dell'indirizzo/dati di sostituzione dei prefissi, che (in un segmento di codice a 32 bit) trasformerà le operazioni a 32 bit in quelle a 16 bit. per esempio
buffer[0] = 0x66;
buffer[1] = 0x55;
buffer[2] = 0x66;
buffer[3] = 0x89;
buffer[4] = 0xE5;
...
è
push bp
mov bp, sp
piuttosto che
push ebp
mov ebp, esp
(che sembra spiegare il comportamento osservato: spingere bp
diminuisce il puntatore dello stack di 2 anziché 4).
Altri suggerimenti
Il tuo problema è il 66
e 67
BYTES - Override dimensione dell'operando e dimensione dell'indirizzo Override, rispettivamente.
Dato che stai eseguendo questo codice in modalità a 32 bit, questi byte dicono al processore che si desidera operandi e indirizzi a 16 bit invece di quelli a 32 bit. Il 66 55
smontare a PUSH BP
, che spinge solo 2 byte anziché 4, quindi i tuoi indirizzi sono spenti di 2.
Il 67
Anche i byte nei primi due esempi non sono inutili, ma poiché stai accedendo solo ai registri e non alla memoria, non hanno alcun effetto e non rompono nulla (ancora). Quei byte dovrebbero anche essere rimossi.
Sembra che tu stia usando un framework progettato per il codice a 16 bit, o forse c'è un modo per dire che desideri un codice a 32 bit.