基于栈的虚拟机源码剖析 - unixfy

时间:2024-03-10 22:44:54

基于栈的虚拟机源码剖析

基于栈的虚拟机源码剖析

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

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

错误处理机制

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

读取指令

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

执行指令

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

测试虚拟机

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

 

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

 

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