C I';我正在编写自己的JIT解释器。如何执行生成的指令?

C I';我正在编写自己的JIT解释器。如何执行生成的指令?,c,assembly,x86,C,Assembly,X86,我打算编写自己的JIT解释器,作为虚拟机课程的一部分。我对高级语言、编译器和解释器有很多知识,但对x86汇编语言(或C语言)知之甚少或一无所知 实际上,我不知道JIT是如何工作的,但我的看法是:用某种中间语言读入程序。将其编译为x86指令。确保最后一条指令返回到VM代码中正常的地方。将指令存储在内存中的某个位置。无条件跳转到第一条指令。瞧 因此,考虑到这一点,我有以下小型C程序: #include <stdlib.h> #include <stdio.h> #includ

我打算编写自己的JIT解释器,作为虚拟机课程的一部分。我对高级语言、编译器和解释器有很多知识,但对x86汇编语言(或C语言)知之甚少或一无所知

实际上,我不知道JIT是如何工作的,但我的看法是:用某种中间语言读入程序。将其编译为x86指令。确保最后一条指令返回到VM代码中正常的地方。将指令存储在内存中的某个位置。无条件跳转到第一条指令。瞧

因此,考虑到这一点,我有以下小型C程序:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int *m = malloc(sizeof(int));
    *m = 0x90; // NOP instruction code

    asm("jmp *%0"
               : /* outputs:  */ /* none */
               : /* inputs:   */ "d" (m)
               : /* clobbers: */ "eax");

    return 42;
#包括
#包括
#包括
int main(){
int*m=malloc(sizeof(int));
*m=0x90;//NOP指令代码
asm(“jmp*%0”
:/*输出:*//*无*/
:/*输入:*/“d”(m)
:/*重击者:/“eax”);
返回42;
}

好的,我的目的是让这个程序将NOP指令存储在内存中的某个地方,跳转到那个位置,然后可能会崩溃(因为我还没有设置程序返回main的任何方式)

问题:我走的路对吗

问题:你能给我看一个经过修改的程序,它能找到返回main内部某个地方的路径吗

问题:我应该注意的其他问题

附言:我的目标是获得理解,而不是以正确的方式做每件事


感谢所有的反馈。下面的代码似乎是在我的Linux机器上开始工作的地方:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>

unsigned char *m;

int main() {
        unsigned int pagesize = getpagesize();
        printf("pagesize: %u\n", pagesize);

        m = malloc(1023+pagesize+1);
        if(m==NULL) return(1);

        printf("%p\n", m);
        m = (unsigned char *)(((long)m + pagesize-1) & ~(pagesize-1));
        printf("%p\n", m);

        if(mprotect(m, 1024, PROT_READ|PROT_EXEC|PROT_WRITE)) {
                printf("mprotect fail...\n");
                return 0;
        }

        m[0] = 0xc9; //leave
        m[1] = 0xc3; //ret
        m[2] = 0x90; //nop

        printf("%p\n", m);


asm("jmp *%0"
                   : /* outputs:  */ /* none */
                   : /* inputs:   */ "d" (m)
                   : /* clobbers: */ "ebx");

        return 21;
}
#包括
#包括
#包括
#包括
无符号字符*m;
int main(){
unsigned int pagesize=getpagesize();
printf(“页面大小:%u\n”,页面大小);
m=malloc(1023+pagesize+1);
如果(m==NULL)返回(1);
printf(“%p\n”,m);
m=(无符号字符*)((长)m+pagesize-1)和(pagesize-1));
printf(“%p\n”,m);
if(mprotect(m,1024,PROT_READ | PROT_EXEC | PROT_WRITE)){
printf(“mprotect失败…\n”);
返回0;
}
m[0]=0xc9;//离开
m[1]=0xc3;//ret
m[2]=0x90;//否
printf(“%p\n”,m);
asm(“jmp*%0”
:/*输出:*//*无*/
:/*输入:*/“d”(m)
:/*重击者:/“ebx”);
返回21;
}
问题:我走的路对吗

我会说是的

问:你能给我看一个经过修改的程序,它能设法找到回到main内部的某个地方吗

我没有为您准备任何代码,但是访问生成的代码并返回的更好方法是使用一对
call
/
ret
指令,因为它们将自动管理返回地址

问题:我应该注意的其他问题

是-作为一种安全措施,许多操作系统会阻止您在堆上执行代码,而无需进行特殊安排。这些特殊安排通常意味着您必须将相关内存页标记为可执行


在Linux上,这是通过使用
PROT_EXEC

完成的。如果生成的代码遵循正确的顺序,则可以声明指向函数类型的指针,并通过以下方式调用函数:

typedef void (*generated_function)(void);

void *func = malloc(1024);
unsigned char *o = (unsigned char *)func;
generated_function *func_exec = (generated_function *)func;

*o++ = 0x90;     // NOP
*o++ = 0xcb;     // RET

func_exec();

此外,指令缓存通常不监视底层内存,因此在执行跳转之前可能需要显式缓存刷新。@Simon:同意,通常是“在将指令写入内存之后,但在执行之前”。因此,根据我的经验,您在完成编写之后编写代码,而不是在执行之前。在这个示例代码中,这些代码位于同一个位置,但实际上执行的次数可能比编写的次数多。正如Simon所说,刷新指令缓存而不是数据缓存是很重要的。在x86上不需要刷新I缓存,只有当SMC非常接近eip时,SMC才会工作。适度接近SMC会带来巨大的惩罚。关于自我修改代码,有一个问题,我记得我发现为了访问内存,你必须在对齐的边界上映射mmap。任何人都知道票证在哪里,这应该告诉你如何在内存中生成指令,然后执行它们。这里的另一个选项是只解释指令或中间代码,而不直接执行任何东西。@Alex:这是实现语言的另一个选项,但根据定义,它不是JIT。