zoukankan      html  css  js  c++  java
  • 基于栈的虚拟机源码剖析

    基于栈的虚拟机源码剖析

             之前我们曾剖析过一个栈虚拟机《栈虚拟机源码剖析》,并实现了一个栈虚拟机《实现一个栈虚拟机》。

             本文我们对Kevin Lynx的《基于栈的虚拟机的实现》进行学习,学习其源码实现原理和技巧,其源码地址为:source code

             有关该基于栈的虚拟机说明,可以直接参考原文,我们不在此赘述。这里,我们主要是对源码进行分析学习。

             该虚拟机对应两个文件:头文件sm.h和源文件sm.c。

             其中,sm.h中定义了虚拟机的指令集(二进制指令集),该指令集为枚举类型:

    enum op_type
    
    {
    
        opHalt, opIn, opOut, opAdd, opSub, opMul, opDiv,
    
        opDup,
    
        opLd, opSt, opLdc, opJlt, opJle, opJgt, opJge, opJeq, opJne, opJmp,
    
        opInvalid
    
    };

             有关这些指令的说明,可以参考stack_machine.txt文件。

             另外,还定义了指令结构体:

    typedef struct Instruction
    
    {
    
        int op;
    
        int arg;
    
    } Instruction;

             Instruction结构体,包含两部分:指令码和操作数。指令码最多有一个操作数。

             此外,还包含了两个宏定义:

    #define CODE_SIZE (1024)
    
    #define DATA_SIZE (1024)

             sm.c文件中定义了堆栈:

    /* the operation stack */
    
    int op_stack[STACK_SIZE];
    
    int op_pos = 0;

             该堆栈op_stack是虚拟机处理数据的场所,op_pos是一个栈索引。堆栈元素类型为int型。

             紧接着定义了指令内存:

    Instruction i_mem[CODE_SIZE];
    
    int pc;

             pc相当于是指令指针寄存器。

    int d_mem[DATA_SIZE];

             d_men为数据内存。

    enum err_code
    
    {
    
        Halt, Okay, errDivByZero, errDMem, errIMem, errStackOverflow, errStackEmpty,
    
        errUnknownOp
    
    };

             err_code为枚举类型的错误码。这里不多作介绍。

    void error( const char *err )
    
    {
    
        fprintf( stderr, err );
    
    }

             error用于将错误信息输出。

    /* get the op code arg(operand) count */
    
    int get_operand_count( int op )
    
    {
    
        int ret;
    
        switch( op )
    
        {
    
        case opLdc:
    
        case opJlt:
    
        case opJle:
    
        case opJgt:
    
        case opJge:
    
        case opJeq:
    
        case opJne:
    
        case opJmp:
    
            ret = 1;
    
            break;
    
        default:
    
            ret = 0;
    
        }
    
        return ret;
    
    }

             get_operand_count函数用于返回指令int型指令op所对应的操作数个数,op最多需要一个操作数。

    int push_op_stack( int i )
    
    {
    
        if( op_pos >= STACK_SIZE )
    
        {
    
            error( "stack overflow" );
    
            return -1;
    
        }
    
        op_stack[op_pos++] = i;
    
        return 0;
    
    }

             push_op_stack用于压栈操作。

    int pop_op_stack()
    
    {
    
        if( op_pos == 0 )
    
        {
    
            error( "stack empty" );
    
            return POP_ERR;
    
        }
    
        return op_stack[--op_pos];
    
    }

             pop_op_stack用于弹栈操作。

    int top_op_stack()
    
    {
    
        if( op_pos == 0 )
    
        {
    
            error( "stack empty" );
    
            return POP_ERR;
    
        }
    
        return op_stack[op_pos-1];
    
    }

             top_op_stack用于返回栈顶元素值,不弹栈。

    #define INC_P( t ) codes+=sizeof(t); size-=sizeof(t)
    
    /* read codes into the i_mem */
    
    int read_instruction( const char *codes, int size )
    
    {
    
        int op_count, loc = 0;
    
        Instruction inst;
    
        while( size > 0 && loc < CODE_SIZE )
    
        {
    
            /* op is 1 byte in the code file */
    
            inst.op = *codes;  
    
            INC_P( char );
    
            op_count = get_operand_count( inst.op );
    
            if( op_count > 0 ) /* has arg */
    
            {
    
                inst.arg = *(int*) codes;
    
                INC_P( int );
    
            }
    
            else
    
            {
    
                inst.arg = 0;
    
            }
    
            i_mem[loc++] = inst;
    
        }
    
        return 1;
    
    }

             read_instruction函数用于从codes读取指令,其中INC_P(t)的定义用于修改读取指令时codes和size的值。将读取到的指令码和对应的操作数存入到i_mem Instruction数组中。

    int step_run()
    
    {
    
        Instruction *inst = &i_mem[pc++];
    
        int ret = Okay;;
    
        switch( inst->op )
    
        {
    
        case opHalt:
    
            {
    
                ret = Halt;
    
            }
    
            break;
    
        case opIn:
    
            {
    
                int i;
    
                printf( "input:" );
    
                scanf( "%d", &i );
    
                push_op_stack( i );
    
            }
    
            break;
    
        case opOut:
    
            {
    
                int i = pop_op_stack();
    
                printf( "output:%d
    ", i );
    
            }
    
            break;
    
        case opAdd:
    
            {
    
                int a = pop_op_stack();
    
                int b = pop_op_stack();
    
                push_op_stack( b + a );
    
            }
    
            break;
    
        case opSub:
    
            {
    
                int a = pop_op_stack();
    
                int b = pop_op_stack();
    
                push_op_stack( b - a );
    
            }
    
            break;
    
        case opMul:
    
            {
    
                int a = pop_op_stack();
    
                int b = pop_op_stack();
    
                push_op_stack( b * a );
    
            }
    
            break;
    
        case opDiv:
    
            {
    
                int a = pop_op_stack();
    
                int b = pop_op_stack();
    
                if( a == 0 )
    
                {
    
                    return errDivByZero;
    
                }
    
                push_op_stack( b / a );
    
            }
    
            break;
    
        case opDup:
    
            {
    
                push_op_stack( top_op_stack() );
    
            }
    
            break;
    
        case opLd:
    
            {
    
                int addr = pop_op_stack();
    
                if( addr < 0 || addr >= DATA_SIZE )
    
                {
    
                    error( "data memory access error" );
    
                    return errDMem;
    
                }
    
                else
    
                {
    
                    push_op_stack( d_mem[addr] );
    
                }
    
            }
    
            break;
    
        case opSt:
    
            {
    
                int val = pop_op_stack();
    
                int addr = pop_op_stack();
    
                if( addr < 0 || addr >= DATA_SIZE )
    
                {
    
                    error( "data memory access error" );
    
                    return errDMem;
    
                }
    
                else
    
                {
    
                    d_mem[addr] = val;
    
                }  
    
            }
    
            break;
    
        case opLdc:
    
            {
    
                push_op_stack( inst->arg );
    
            }
    
            break;
    
        case opJlt:
    
            {
    
                int i = pop_op_stack();
    
                if( i < 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJle:
    
            {
    
                int i = pop_op_stack();
    
                if( i <= 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJgt:
    
            {
    
                int i = pop_op_stack();
    
                if( i > 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJge:
    
            {
    
                int i = pop_op_stack();
    
                if( i >= 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJeq:
    
            {
    
                int i = pop_op_stack();
    
                if( i == 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJne:
    
            {
    
                int i = pop_op_stack();
    
                if( i != 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJmp:
    
            {
    
                pc = inst->arg;
    
            }
    
            break;
    
        default:
    
            ret = errUnknownOp;
    
        }
    
        return ret;
    
    }

             step_run函数用于执行当前指令。switch-case中虚拟机的二进制指令不作详细介绍,有关指令的定义,属于另一个专题讨论。

    void run()
    
    {
    
        int ret = Okay;
    
        while( ret == Okay )
    
        {
    
            ret = step_run();
    
        }
    
    }

             run函数用于根据step_run的执行结果,逐步执行i_mem中的指令。

    int file_size( FILE *fp )
    
    {
    
        int size;
    
        fseek( fp, 0, SEEK_SET );
    
        fseek( fp, 0, SEEK_END );
    
        size = ftell( fp );
    
        fseek( fp, 0, SEEK_SET );
    
        return size;
    
    }

             两个文件函数:fseek和ftell。

    函数

    原型

    功能

    参考

    fseek

    int fseek(FILE* stream, long offset, int fromwhere);

    设置文件指针stream的位置

    百度百科

    CPLUSPLUS

    ftell

    long ftell(FILE* stream);

    返回当前文件位置,返回FILE指针当前位置

    百度百科

    CPLUSPLUS

             file_size函数用于返回文件的大小。

    extern void dasm_output( const char *file, const Instruction *insts, int size );
    
     
    
    int main( int argc, char **argv )
    
    {
    
        FILE *fp;
    
        char *buf;
    
        int size;
    
        if( argc < 2 )
    
        {
    
            error( "Usage: SM <filename>" );
    
            exit( -1 );
    
        }
    
        fp = fopen( argv[1], "rb" );
    
        if( fp == 0 )
    
        {
    
            error( "Open file failed" );
    
            exit( -1 );
    
        }
    
        size = file_size( fp );
    
        buf = (char*) malloc( size );
    
        fread( buf, size, 1, fp );
    
     
    
        read_instruction( buf, size );
    
        if( argc > 2 )
    
        {
    
            int dflag = atoi( argv[2] );
    
            if( dflag )
    
            {
    
                dasm_output( argv[1], i_mem, CODE_SIZE );
    
            }
    
        }
    
        run();
    
        free( buf );
    
        fclose( fp );
    
        return 0;
    
    }

             main函数:如果参数个数argc小于2,则失败;否则读取argv[1]文件中的指令,如果argc大于2,则检测argv[2],如果为真,则将从argv[1]文件中读取出来的指令进行反汇编dasm_output,并将反汇编后的结果输出到argv[1]对应的反汇编文件中。

             进而,执行run函数,执行从argv[1]中读取出来的虚拟机指令。最后将buf释放,并且将文件指针fp关闭。

             总结

             综上所述,实现一个基于堆栈的虚拟机主要包含以下几个重要模块:

    模块

    说明

    虚拟机二进制指令集

    枚举类型。有关指令集如何定义,属于另一个话题

    指令结构体的定义

    指令码+操作数

    堆栈的实现

    是虚拟机执行指令时,数据处理的场所;op_pos为堆栈的栈顶索引

    指令内存

    Instruction i_mem[CODE_SIZE]; 用于存放待执行的指令;pc为指令指针寄存器

    数据内存

    int d_mem[DATA_SIZE]; 用于存放待处理和已处理的数据

    错误处理机制

    虚拟机各个环节的错误处理,错误类型可以定义为枚举类型

    读取指令

    从文件或终端读取虚拟机的二进制指令

    执行指令

    根据不同指令,进行相应的操作,这些操作发生在堆栈、指令内存、数据内存之间

    测试虚拟机

    从读取指令,到执行执行,测试

             以上是对实现一个基于堆栈虚拟机几个重要的模块说明,另外可以参考《实现一个堆栈虚拟机》中关于虚拟机原理的解析图

             以上是对基于栈的虚拟机的源码剖析,通过对虚拟机源码的学习,我们更进一步了解了虚拟机的实现原理,以及实现中几个重要的模块细节。下一步,我们将实现另一个版本的基于堆栈的虚拟机;另外,学习基于寄存器的虚拟机实现原理和实现一个基于寄存器的虚拟机。

  • 相关阅读:
    Intel 编译器 线程安全检查 真心的很详细 转
    当前软件设计分析
    当代并行机系统
    多人游戏服务器
    ACE源代码目录结构
    (转!)Z buffer和W buffer简介
    数据库-视图(View)详解
    推荐一个vs自带工具分析代码的复杂度
    SCOPE_IDENTITY的用法
    vs2013开发调试cocos2d-x-Lua工程项目
  • 原文地址:https://www.cnblogs.com/unixfy/p/3358144.html
Copyright © 2011-2022 走看看