基于栈的语言
想必大家都已经了解GOF的解释器模式,他的优点很明显-安全,因为语法行为是我们自己定义的,不直接接触底层,但缺点也很明显——效率低,内存开销大。
再来看下我们的C++,直接编译成机器码,机器码是一组密集的,线性的,底层的指令,效率飞快。但又有谁愿意直接编辑机器码呢。
那有没有兼得熊掌和鱼的方法呢,有——定义自己的虚拟机器码,然后再在需要的地方写一个小的模拟器。
我们将这个模拟器叫做虚拟机(简称"VM"),虚拟机器码叫字节码。
下面我们从简单的例子起步
游戏中我们需要创建英雄,设置英雄的血量,简单起见,可以像下面这样
void creatHero(int health = 10); //创建英雄
void setHealth(int index,int health); //改变指定英雄生命值
当然还可以是底层API的调用,比如
void playSound(int soundID); //播放音乐
void printDebug(); //打印状态
来让我们看下我们是如何一步步将API分离,让我们的字节码解析执行的
首先是定义字节码,我们使用枚举
enum Instruction
{
INST_SET_HEALTH = 0x00,
INST_NEW_HERO = 0x01,
INST_PLAY_SOUND = 0x02,
INST_PRINT_DEBUG = 0x03
};
每一个字节码对应一个行为,我们使用switch来完成
switch(instruction)
{
case INST_SET_HEALTH:
setHealth(0,10);
break;
case INST_NEW_HERO:
creatHero();
break;
case INST_PLAY_SOUND:
playSound(DROP_SOUND)
break;
case INST_PRINT_DEBUG:
printDebug();
break;
}
于是我们完成我们的第一个虚拟机
class VM
{
public:
void interpret(string bytecode)
{
int instruction;
for(int i = 0;i < bytecode.size();i++)
{
instruction = (int)(bytecode[i] - '0')
switch(instruction)
{
case INST_SET_HEALTH:
setHealth(0,10);
break;
case INST_NEW_HERO:
creatHero();
break;
case INST_PLAY_SOUND:
playSound(DROP_SOUND)
break;
case INST_PRINT_DEBUG:
printDebug();
break;
}
}
}
}
但这还不够灵活,我们不能修改指定英雄血量。对,我们需要传入参数
于是我们引入堆栈
calss VM
{
public:
VM()
:stackSize_(0)
{}
private:
static const int MAX_STACK = 128;
int stackSize_;
int stack_[MAX_STACK];
}
然后我们定义加入(push)和取出(pop)功能
class VM
{
public:
.......//something else
void push(int value){
stack_[stackSize++] = value;
}
int pop(){
return stack[--stackSize];
}
}
现在我们可以像这样传入参数
switch(instruction)
{
case INST_SET_HEALTH:
{
int health = pop();
int index = pop();
setHealth(index,health);
}
break;
//do something
}
然后我们需要一个机器码指定push指令,于是我们定义
enum Instruction
{
//else
INST_LITERAL = 0x04
};
然后加入行为
switch(instruction)
{
case INST_LITERRAL:
{
int _value = bytecode[i++];
push(_value);
}
break;
}
举个例子,设置index为1的英雄生命值为10为以下字节码
INST_LITERAL 1 INST_LITERAL 10 INST_SET_HEALTH
分析:
INST_LITERAL 1 INST_LITERAL 10:将1压入堆栈,将10压入堆栈
INST_SET_HEALTH:出站10作为health,出站1为index,将index为1的英雄的生命值设置为10
我们还需要让它能够实现行为
比如我们需要将编号为1的生命值设置为编号为3的生命值和编号4的生命值的和,我们就得需要用到运算,我们需要获取指定英雄的血量
于是我们像这样给运算定义字节码
//else
int getHealth(int index);
enum Instruction
{
INST_ADD = 0x05,
INST_RIDE = 0x06,
INST_GET_HEALTH = 0x07
};
然后像下面
switch(instruction)
{
//else
case INST_ADD:
{
int _lvalue = pop();
int _rvalue = pop();
push(_lvalue+_rvalue);
}
break;
case INST_RIDE:
{
int _lvalue = pop();
int _rvalue = pop();
push(_lvalue+_rvalue);
}
break;
case INST_GET_HEALTH:
{
int index = pop();
push(getHealth(index));
}
break;
}
现在我们就可以这样实现我们的例子
(我们需要将编号为1的生命值设置为编号为3的生命值和编号4的生命值的和)
INST_LITERAL 1 INST_LITERAL 3 INST_GET_HEALTH INST_LITERAL 4 INST_GET_HEALTH INST_ADD INST_SET_HEALTH
分析:
INST_LITERAL 1:将1压入堆栈,作为index
NST_LITERAL 3 INST_GET_HEALTH:将3压入堆栈,最为index,出站3作为index,获取编号为3的血量并压入堆栈
INST_LITERAL 4 INST_GET_HEALTH:将4压入堆栈,最为index,出站4作为index,获取编号为4的血量并压入堆栈
INST_ADD:出站4的血量,出站3的血量,将他们相加并压入堆栈
INST_SET_HEALTH:出站血量和,作为参数health,出站1,作为index,设置1的血量为health
到目前为止,我们已经可以可以使用我们的语言完成一些工作了,但这还不够我们需要逻辑的支持,代码重用,条件语句详情请期待下期更新
我打算在最近更新渗透测试骚操作,(其实鸽了很久了)
参考:https://blog.csdn.net/u010223072/article/details/59483093