我打算编写自己的JIT解释器作为VM课程的一部分。我对高级语言,编译器和解释器有很多了解,但很少或根本没有关于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;
}
好的,所以我的目的是让这个程序将NOP指令存储在内存中的某个位置,跳转到该位置然后可能会崩溃(因为我没有设置任何方式让程序返回main)。
问题:我是否在正确的道路上?
问题:你能告诉我一个修改后的程序,它可以找到回到main中某个地方的方法吗?
问题:我应该注意的其他问题?
PS:我的目标是获得理解,而不是以正确的方式做所有事情。感谢所有反馈。以下代码似乎是在我的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;
}
答案 0 :(得分:8)
问题:我是否在正确的道路上?
我会说是的。
问题:你能告诉我一个经过修改的程序,它可以找到回到main中某个地方的方法吗?
我没有为您提供任何代码,但更好地了解生成的代码并返回使用一对call
/ ret
指令,因为它们将管理返回自动地址。
问题:我应该注意的其他问题?
是 - 作为一种安全措施,许多操作系统会阻止您在没有做出特殊安排的情况下在堆上执行代码。这些特殊安排通常相当于您必须将相关的内存页面标记为可执行文件。
在Linux上,这是使用mprotect()
和PROT_EXEC
完成的。
答案 1 :(得分:3)
如果生成的代码遵循正确的calling convention,则可以声明指向函数的类型并以这种方式调用函数:
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();