在JIT编译器中使用ebp + 6而不是+8

Dav*_*nco 1 c c++ assembly jit vm-implementation

我正在VM中实现一个简单的JIT编译器,我正在为了好玩而写作(主要是为了更多地了解语言设计)而且我得到了一些奇怪的行为,也许有人可以告诉我为什么.

首先,我为C和C++定义了一个JIT"原型":

#ifdef __cplusplus 
    typedef void* (*_JIT_METHOD) (...);
#else
    typedef (*_JIT_METHOD) ();
#endif
Run Code Online (Sandbox Code Playgroud)

我有一个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;
}
Run Code Online (Sandbox Code Playgroud)

最后我有了该程序的主要部分:

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;
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,该compile()函数有几个测试我跑,以确保我得到预期的结果,几乎一切都有效,但我有一个问题...

在大多数教程或文档资源上,要获取函数的第一个值(在int的情况下)[ebp+8],第二个[ebp+12]等等.出于某种原因,我必须这样做[ebp+6],然后[ebp+10]依此类推.谁能告诉我为什么?

zil*_*n01 8

您的问题分别是6667字节 - 操作数大小覆盖和地址大小覆盖.

由于您在32位模式下运行此代码,因此这些字节告诉处理器您需要16位操作数和地址而不是32位操作数.的66 55反汇编到PUSH BP,这仅推压2个字节,而不是4,故您的地址由2被关断.

67前两个例子中的字节也是不必要的,但因为你只是访问寄存器而不是内存,所以它们没有效果,也没有破坏任何东西.那些字节也应该被删除.

看起来你正在使用一个专为16位代码设计的框架,或者有一种方法可以告诉你需要32位代码.


Mat*_*ery 8

您的操作码看起来很可疑:它们已满0x66并且0x67地址/数据大小覆盖前缀,(在32位代码段中)将32位操作转换为16位操作.例如

buffer[0] = 0x66;
buffer[1] = 0x55;
buffer[2] = 0x66;
buffer[3] = 0x89;
buffer[4] = 0xE5;
...
Run Code Online (Sandbox Code Playgroud)

push bp
mov  bp, sp
Run Code Online (Sandbox Code Playgroud)

而不是

push ebp
mov  ebp, esp
Run Code Online (Sandbox Code Playgroud)

(这似乎解释了观察到的行为:推动bp将堆栈指针递减2而不是4).