首页
社区
课程
招聘
[分享]vm解释器入门
发表于: 3天前 708

[分享]vm解释器入门

3天前
708

前言

我们日常接触到的Cpu、VMware、Java JVM、VMProtect等等 都有核心的解释指令的功能,解释器也在多个领域有着举足轻重的地位,今天笔者将实现一个简单的自定义解释器程序作为入门练习,并使用自定义语法实现一个简单的阶乘练习

一、定义解释器结构

这是一个极简的解释器,里面仅包含5个通用寄存器,一个程序计数器,以及一个运行状态标识:

struct VM {
    int reg[5];   // 5个通用寄存器 R0~R4
    int pc;       // 程序计数器
    bool running; // 是否运行
};

二、定义指令

指令方面定义了:寄存器赋值、加法、减法、自定义输出寄存器、直接跳转、条件跳转、停止功能

其中R代表 Register 寄存器,IMM代表 Immediate 立即数,笔者暂时没有做MOV R,R\IMM的整合,因为是简单实现就分开为OP_MOV_IMM 和 OP_MOV_REG

/// < summary >
/// | 指令              | 含义        |
/// | ----------        | ------      |
/// | OP_MOV_IMM R1, R2 | 寄存器赋值1 |
/// | OP_MOV_REG R, IMM | 寄存器赋值2 |
/// | ADD R1, R2        | 加法        |
/// | SUB R1, R2        | 减法        |
/// | PRINT R           | 输出        |
/// | OP_JMP IMM        | 跳转        |
/// | OP_JZ R, IMM      | 条件跳转    |
/// | OP_HALT           | 停止        |
/// < summary >
enum {
    OP_MOV_IMM,
    OP_MOV_REG,
    OP_ADD,
    OP_SUB,
    OP_PRINT,
    OP_JMP,
    OP_JZ,
    OP_HALT
};

三、解释器实现

run方法的实现是整个解释器的核心,其中包含解释每个指令的逻辑,所有指令是混合在一起的,这会导致不同版本的解释器和不同版本的代码要做兼容,这就是微软一直以来在干的工作

例如其中OP_MOV_IMM后是接收2个参数,OP_PRINT后是接收一个参数,如果一个对不上后续指令都会出错

//解释器
void run(VM& vm, int* code) {
    //初始化解释器
    vm.pc = 0;//计数器置零
    vm.running = true;//设置运行态

    //循环解释
    while (vm.running) {
        int op = code[vm.pc++]; // 取指令

        switch (op) {//判断指令
        case OP_MOV_IMM: {
            int reg = code[vm.pc++];//取寄存器编号
            int val = code[vm.pc++];//取值
            vm.reg[reg] = val;//寄存器赋值
            break;
        }
        case OP_MOV_REG: {
            int dst = code[vm.pc++];//R1编号
            int src = code[vm.pc++];//R2编号
            vm.reg[dst] = vm.reg[src];//R1 = R2
            break;
        }
        case OP_ADD: {
            int r1 = code[vm.pc++];//取操作数1
            int r2 = code[vm.pc++];//取操作数2
            vm.reg[r1] += vm.reg[r2];//加法操作
            break;
        }
        case OP_SUB: {
            int r1 = code[vm.pc++];//取操作数1
            int r2 = code[vm.pc++];//取操作数2
            vm.reg[r1] -= vm.reg[r2];//减法操作
            break;
        }
        case OP_PRINT: {
            int reg = code[vm.pc++];//取寄存器编号
            printf("R%d = %d\n", reg, vm.reg[reg]);//输出寄存器的值
            break;
        }
        case OP_JMP: {
            int addr = code[vm.pc++];
            vm.pc = addr;
            break;
        }
        case OP_JZ: {
            int reg = code[vm.pc++];
            int addr = code[vm.pc++];
            if (vm.reg[reg] == 0) {
                vm.pc = addr;
            }
            break;
        }
        case OP_HALT: {
            vm.running = false;//改变运行态
            break;
        }
        default://未知指令
            printf("Unknown opcode: %d\n", op);
            vm.running = false;
            break;
        }
    }
}

四、阶乘练习

最后是实现的阶乘练习,实现为5!=54321=120

要特别说明一下,本轮练习笔者并未实现乘法功能,具体实现实在 inner_loop 中用加法实现的乘法功能

入参R0=5,内部用到R3作为临时变量,R1作为返回结果,R4为乘法计数

大体思路为:首先初始化参数,进入outer_loop先判断n是否等于0,因为n每次循环会--,等于0则退出,如果不为0则做resultn=15=5,此时第一轮result=5 n--,第二轮为resultn=54,以此类推

//阶乘练习
int main() {
    VM vm = { 0 };

    //入参
    int n = 5;

    //| 寄存器 | 含义    |
    //| ----   | ------  |
    //| R0     | n       |
    //| R1     | result  |
    //| R2     | 常量1   |
    //| R3     | temp    |
    //| R4     | counter |
    int program[] = {

        // 初始化
        OP_MOV_IMM, R0, 5,  // n = 5
        OP_MOV_IMM, R1, 1,  // result = 1
        OP_MOV_IMM, R2, 1,  // const 1
        
        // ===== outer_loop (addr = 9) =====
        OP_JZ, R0, 37,      // if n == 0 -> end 
        OP_MOV_IMM, R3, 0,  // temp = 0
        OP_MOV_REG, R4, R0, // counter = n
        
        // ===== inner_loop (addr = 18) ===== 乘法功能
        OP_JZ, R4, 29,      // if counter == 0 -> inner_end
        OP_ADD, R3, R1,     // temp += result
        OP_SUB, R4, R2,     // counter--
        OP_JMP, 18,         // 回到 inner_loop
        
        // ===== inner_end (addr = 29) =====
        OP_MOV_REG, R1, R3, // result = temp
        OP_SUB, R0, R2,     // n--
        OP_JMP, 9,          // 回到 outer_loop
        
        // ===== end (addr = 37) =====
        OP_PRINT, R1,
        OP_HALT
    };

    //运行
    run(vm, program);

    return 0;
}

五、总结

本次vm解释器实现为极简写法,用于入门原理了解,对于现实的情况会复杂很多,例如笔者并未做区分标志寄存器,是直接用4字节的通用寄存器作为标志寄存器判断,这会导致空间的浪费

本小结作为vm练习的开端,后续还会继续完善栈区和宏指令实现 欢迎讨论 如有错漏 请留言


[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 3天前 被mb_binusgki编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回