quake3
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
:在菜单阶段接收消息。使用系统调用绘制菜单。
- 服务器端:
QVM内部
在描述如何使用QVM之前,我们来看看如何生成字节码。像往常一样,我喜欢用一点补充文本绘制:
quake3.exe
它的字节码解释器是通过Visual Studio生成的,但是VM字节码需要一个非常不同的路径:
- 每个.c文件(翻译单元)通过LCC单独编译。
- LCC与特殊参数一起使用,因此不会输出PE(windows Portable Executable),而是它的基于文本的堆栈机器组装的中间表示。生产的每个文件都拥有
text
,data
并bss
用符号出口和进口部分。 - 来自id Software的一个特殊工具可以
q3asm.exe
将所有文本汇编文件复制到一个.qvm文件中。它还可以将所有内容从文本转换成二进制文件(为了速度,如果本机转换不能启动)。q3asm.exe
还可以识别哪些方法是系统调用,并给出那些负号码。 - 加载二进制字节码时,
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
但不管...) - 功能是通过定义
proc
,endproc
夹心(标记为蓝色)。 - LCC的中间表示是一个堆栈机器:所有操作都是在堆栈上完成的,没有关于cpu寄存器的假设。
- 在LCC短语结尾,我们有一堆文件导入/导出变量/函数。
- 每个语句与操作型开始(即:
ARGP4
,ADDRGP4
,CALLI4
...)。每个参数和结果将被传递给堆栈。 - 导入和导出在这里,所以汇编器可以将“翻译单元”链接在一起。请注意
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
推荐读数
文章最后发布于: 2017-07-30 08:35:38
相关阅读
在前面seo实验室小编给大家详细的介绍了关于防火墙的作用是什么的知识,相信在看了那篇教程之后,大家对于防火墙的知识应该有了一个
EditPlus3是小巧但功能强大的可处理文本、HTML和程序语言的Windows编辑器,甚至可以通过设置用户工具将其作为C,Java,Php等等语言的
A5创业网(公众号:iadmin5)10月29日报道,自媒体报道ofo退出日本市场之后,ofo又在国外被网友爆料退押金周期延长,看来确实是遭受到了很多
1. 数字类型及操作 1.1 整数类型 pow(x,y) 计算x的y次方4种进制表示形式:十进制、二进制(0b 0B)、八进制(0o 0O)、十六进制(0x 0X)1.2
互联网优秀的产品很多,笔者只从个人力所能及的认知范围去介绍所认可的作品,就如上次写的《从京东看商城产品设计》,可能有其他优秀几