EBP + 6 em vez de +8 em um compilador JIT
-
26-09-2019 - |
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ê?
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.