QUAKE 3源代码审查:虚拟机

QUAKE 3源代码审查:虚拟机(第4部分,共5部分)>>

如果以前的引擎仅将游戏委托给虚拟机,idtech3会严重依赖它们进行基本任务。除其他事项外:

  • Rendition由Client VM触发。
  • 滞后补偿机制完全在客户端VM中。

此外,他们的设计更加详细:它们将Quake1虚拟机的安全性/可移植性与Quake2的本机DLL的高性能相结合。这是通过将字节码编译为x86指令来实现的。

琐事:虚拟机最初应该是一个简单的字节码解释器,但表现令人失望,所以开发团队写了一个运行时x86编译器。根据1999年8月16日的计划, 这是在一天之内完成的。

建筑

在Quake III中,虚拟机被称为QVM:其中三个可随时加载:

  • 客户端:加载了两台虚拟机。消息发送到一个或另一个取决于gamestate:
    • cgame:在战斗阶段接收消息。执行实体剔除,预测和触发renderer.lib
    • q3_ui:在菜单阶段接收消息。使用系统调用绘制菜单。
  • 服务器端:
    • game:总是收到消息:执行游戏,然后点击bot.lib执行AI。

QVM内部

在描述如何使用QVM之前,我们来看看如何生成字节码。像往常一样,我喜欢用一点补充文本绘制:

quake3.exe它的字节码解释器是通过Visual Studio生成的,但是VM字节码需要一个非常不同的路径:

  1. 每个.c文件(翻译单元)通过LCC单独编译。
  2. LCC与特殊参数一起使用,因此不会输出PE(Windows Portable Executable),而是它的基于文本的堆栈机器组装的中间表示。生产的每个文件都拥有textdatabss用符号出口和进口部分。
  3. 来自id Software的一个特殊工具可以q3asm.exe将所有文本汇编文件复制到一个.qvm文件中。它还可以将所有内容从文本转换成二进制文件(为了速度,如果本机转换不能启动)。q3asm.exe还可以识别哪些方法是系统调用,并给出那些负号码。
  4. 加载二进制字节码时,quake3.exe将其转换为x86指令(不是强制性的)。

LCC内部

这是一个具体的例子,从我们想在虚拟机中运行的函数开始:

   
    extern int variableA;
    
    int variableB;
    
    int variableC = 0;
    
    int fooFunction(char * string){
	    
        return variableA + strlen(string);
        
    }
    
    

保存在module.c翻译单元中,lcc.exe用特殊标志调用,以避免生成Windows PE对象,而是输出中间表示。这是LCC .obj输出匹配上面的C函数:

   
    数据
    导出变量C
    对齐4
    LABELV变量C
    字节4 0
    导出fooFunction 
    代码
    proc fooFunction 4 4
    ADDRFP4 0
    INDIRP4
    ARGP4
    ADDRLP4 0
    ADDRGP4 strlen
    CALLI4
    ASGNI4
    ARGP4变量A.
    INDIRI4
    ADDRLP4 0
    INDIRI4
    ADDI4
    RETI4
    LABELV $ 1
    endproc fooFunction 4 4 
    import strlen 
    bss 
    export variableB
    对齐4
    LABELV变量B
    跳过4
    导入变量A

    

几点意见:

  • 字节码以部分(标记为红色)组织:我们可以清楚地看到bss(未初始化的变量),data(已初始化的变量)和code(通常称为text但不管...)
  • 功能是通过定义procendproc夹心(标记为蓝色)。
  • LCC的中间表示是一个堆栈机器:所有操作都是在堆栈上完成的,没有关于CPU寄存器的假设。
  • 在LCC短语结尾,我们有一堆文件导入/导出变量/函数。
  • 每个语句与操作型开始(即:ARGP4ADDRGP4CALLI4...)。每个参数和结果将被传递给堆栈。
  • 导入和导出在这里,所以汇编器可以将“翻译单元”链接在一起。请注意import strlen,由于q3asm.exe和VM Interpreter都不喜欢C标准库,strlen所以被认为是系统调用,必须由虚拟机提供。

为VM模块中的每个.c生成此类文本文件。

q3asm.exe内部

q3asm.exe使用LCC中间表示文本文件并将它们组合在一起.qvm文件:


需要注意的几件事情:

  • q3asm对每个文本文件的导入/导出符号都有意义。
  • 一些方法是通过系统调用文本文件预定义的。您可以看到客户端虚拟机和服务器虚拟机的系统调用。系统调用符号被归为负整数值,因此可以由解释器识别。
  • q3asm将文本更改为二进制,以获得空间和速度,但这是几乎没有优化。
  • 要组装的第一种方法必须是vmMain因为它是输入消息分派器。此外,它必须位于0x2D字节码的文本段。


QVM:它如何工作

再次绘制一幅图,说明独特的入口点和独特的退出点作为调度:


一些细节:

消息(Quake3 - > VM)将发送到虚拟机如下:

  • Quake3的任何部分都可以打电话VM_Call( vm_t *vm, int callnum, ... )
  • VMCall最多可以使用11个参数,并将VM bytecode(vm_t *vm)中的每个4字节值从0x00 写入0x26。
  • VMCall 将消息ID写入0x2A。
  • 解释器开始将操作码解释为0x2D(vmMain放置在哪里q3asm.exe)。
  • vmMain 作为调度并将消息路由到适当的字节码方法。

您可以找到可以发送到客户端VM和服务器VM(位于每个文件底部)的消息列表。

系统调用(VM - > Quake3)以这种方式出来:

  • 解释器一个接一个执行VM操作码(VM_CallInterpreted)。
  • 当遇到CALLI4操作码时,它会检查int方法索引。
  • 如果值为负,那么它是一个系统调用。
  • 使用参数调用“系统调用函数指针”(int (*systemCall)( int *parms ))。
  • 指定的功能systemCall是作为一个调度,并将系统调用路由到quake3.exe的正确部分

您可以找到由客户端VM和服务器虚拟机(每个文件顶部)提供的系统调用列表。

参数:参数总是非常简单的类型:基元类型(char,int,float)或指向基本类型的指针(char *,int [])。我怀疑这是为了最小化Visual Studio和LCC之间的结构对齐问题。

琐事: Quake3 VM不执行动态链接,所以QVM mod的开发人员无法访问任何库,甚至没有C标准库(strlen,memset函数都在这里,但实际上是系统调用)。有些人仍然设法用预先分配的缓冲区来伪造它: Malloc在QVM中!

前所未有的自由

随着对虚拟机的任务偏移,修改社区的能力要比修改更多。Neil“匆忙”的多伦多,Unlagged的“反向和解” 改写了预测系统。

生产力问题和解决方案

使用这么长的工具链,开发VM代码是困难的:

  • 工具很慢
  • 工具链未集成到Visual Studio中。
  • 使用命令行工具构建QVM。这很麻烦,中断了工作流程。
  • 在工具链中有很多元素,很难确定哪些部分在出现错误的情况下是错误的。

因此,idTech3还能够为VM部件加载本机DLL,并解决了所有问题:

整体而言,VM系统非常灵活,因为虚拟机能够运行:

  • 解释的字节码
  • 字节码编译为x86指令
  • 代码编译为Windows DLL

推荐读数

  

你可能感兴趣的:(quake3)