5. 虚拟机
在生成可执行代码之前, 必须设计虚拟机.
因为有了虚拟机才有指令系统, 你才知道生成什么代码.
1. 虚拟机架构的指令系统
虚拟机的设计完全模仿X86.
AX, BX, CX, DX是通用寄存器.
其他有一堆特殊用途的寄存器, 用来存放内存的段地址, 系统变量(比如IP, PSW), 等等.
见下图.
接下来是设计指令系统. 也就是有哪些指令, 指令的格式, 以及寻址方式.
指令表(部分)
指令名称 |
操作码 |
格式 |
说明 |
__mov |
0x1010 |
mov op1, op2 |
传送指令 |
__add |
0x1021 |
add op1, op2 |
加法 |
__sub |
0x1050 |
sub op1, op2 |
减法 |
__mul |
0x1070 |
mul op1, op2 |
乘法 |
__div |
0x1090 |
div op1, op2 |
除法 |
__jmp |
0x10a0 |
jmp op1 |
跳转 |
__jz |
0x10a1 |
jz op1 |
PSW的第0位为1时跳转 |
__jnz |
0x10a2 |
jnz op1 |
PSW的第0位为0时跳转 |
__callpub |
0x10a3 |
callpub op1 |
准备调用基本函数 |
__parampub |
0x10a4 |
parampub op1 |
向基本函数传送参数 |
__endcallpub |
0x10a5 |
endcallpub op1 |
调用基本函数 |
__not |
0x10d0 |
not op1 |
非 |
__test |
0x10f2 |
test op1, op2, op3 |
条件测试 |
__ret |
0x10f3 |
ret |
返回 |
__ea |
0x10f4 |
ea op1, op2 |
取地址 |
__mod |
0x10f0 |
mod op1, op2 |
取模 |
__cast |
|
cast op1, op2, op3 |
类型转换 |
基本寻址方式有5种:
立即数 直接寻址 寄存器寻址 数组寻址 常量段寻址
14~15bit |
12~13bit |
8~b11bit |
6~7bit |
4~5bit |
0~3bit |
字操作还是, 字节操作,双字操作 |
间接寻址的深度 |
基本寻址方式 |
字操作还是, 字节操作,双字操作 |
间接寻址的深度 |
基本寻址方式 |
2. 实现每一个指令.
我为每个指令写了一个函数, 来运行这个指令, 参数是指令本身.
第一步, 指令预处理, 根据寻址方式把操作转成物理地址, check地址是否有效
第二步, 执行指令的任务
第三步, 做退出前的动作, 比如IP++等等
指令函数的流程图:

1. 指令预处理
预处理的作用是根据指令的寻址方式和操所数, 得到操作数的实际地址, 使指令的实现了解如何得到地址, 统一处理实际地址, 就像在当前的系统中一样处理。
函数声明: BOOL CVirtualMachine::Preprocess1(PCOMMAND cmd, int &op1mode, int &op1reflvl, unsigned char* &dest, BOOL bValidate)
函数功能: 指令预处理(单操作数指令)
参数说明:
[IN]PCOMMAND cmd - 指令内容
[OUT]int &op1mode - 操作数1的寻址方式字
[OUT]int &op1reflvl - 操作数1的间接级别
[OUT]unsigned char* &dest - 实际地址
[IN]BOOL bValidate - 是否进行地址验证
返 回 值: BOOL - 成功或失败

2. 地址验证
地址验证是用来验证操作数的实际地址是否合法, 防止不正确的内存操作导致系统崩溃。
函数声明: BOOL CVirtualMachine::ValidateAddress(unsigned char* address)
函数功能: 验证地址是否合法
参数说明: [IN]unsigned char* address - 地址
返 回 值: BOOL - 成功或失败

3. 加载脚本函数
当虚拟机执行脚本时:
第一步, 载入脚本, 也就是载入代码到代码段, 载入静态数据到数据段. 没有栈, 原因前面面已经解释
第二步, 从第一条指令开始执行, 直到结束. 也就是遇到ret指令.
第三步, 释放资源.
函数声明: BOOL CVirtualMachine::LoadFunction(CFunction *pFunc)
函数功能: 加载脚本函数
参数说明:
[IN]CFunction *pFunc - 函数指针
返 回 值: BOOL - 成功或失败

By Jackie