基于栈的虚拟机源码剖析

基于栈的虚拟机源码剖析

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

         本文我们对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\n", 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]; 用于存放待处理和已处理的数据

错误处理机制

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

读取指令

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

执行指令

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

测试虚拟机

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

 

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

 

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

你可能感兴趣的:(虚拟机)