DOOM3 源码分析笔记(1)

11 篇文章 0 订阅
1 篇文章 0 订阅

原文:点击打开链接

DOOM3代码放出来也好久了,过年的时候抽空看了看,先更新第一篇,主要是编译的一些问题笔记,以及用自动生成的TypeInfo表进行未初始化变量检查的机制分析。

编译的问题

Can’t load default.cfg

需要手动在FileSystem.cpp中把下面这行fs_basepath初始化改成自己的目录:

idCVar idFileSystemLocal::fs_basepath( "fs_basepath", "Z:\\Docs\\Project_X\\Ref_Engines\\Doom3", CVAR_SYSTEM | CVAR_INIT, "" );

这时因为在编译gamex86.dll时,会先执行一次TypeInfo的生成,这时会用默认路径去加载default.cfg。

win_input.cpp里面包含了一些Unicode的字符串,中文系统下的VS2010编译有这些字符的文件时,会把当前系统字符集(比如gb2312)无法表示的Unicode字符自动替换成“?”。导致编译失败,手动修改一下就好了。不过VS这种行为还是很奇怪的

最新代码把移除的阴影渲染代码又加回去了,不过要做一些小修改,才能在VS中编译

1.在glext.h中把__STDC_VERSION__定义为0(非GCC编译器)

2.在TypeInfo.cpp里面会在预编译头前面强行把private和protected重新定义成public,以方便进行变量初始化和检查工作。不过最新代码显然把一些文件漏了,所以会出现无法访问private变量的编译错误,手动把这些类全部改成public即可。。。

// This is real evil but allows the code to inspect arbitrary class variables.
#define private public
#define protected public
#include "../../idlib/precompiled.h"

3.从VS启动游戏的设置

在Debugging面板中

设置Command为:$(OutDir)DOOM3.exe,不能用默认的$(TargetPath)

否则gamex86.dll不能正常加载.这个问题很是奇怪,宏展开两者的结果应该是一样的。

设置Command Line Arguments:

+set fs_basepath "Z:\Docs\Ref_Engines\Doom3" +set com_allowConsole 1 +set si_pure 0

Doom3的内存调试机制分析

C/C++引擎的特点就是内存越界,泄漏,未初始化变量等Bug非常头疼,特别是在游戏逻辑非常复杂之后,加上一些多线程行为,所以一旦出这种bug,是非常难调试的,所以一般会在引擎一开始就引入一套完善的内存统计和调试机制。这样也能精确的控制游戏各个模块的内存使用。对Console来说这个也非常重要。Doom3引入了一套比较复杂的内存调试机制。除了常规的重载内存分配机制。还加入了一套TypeInfo生成机制。下面主要分析一下这套机制。在编译gamex86.dll的过程中,会在game/gamesys下生成一个GameTypeInfo.h文件。这个文件只有在ID_DEBUG_MEMORY宏定义的情况下才会包含进工程。

#ifdef ID_DEBUG_MEMORY
#include "GameTypeInfo.h" // Make sure this is up to date!
#else
#include "NoGameTypeInfo.h"
#endif
//在Debug With Memory的工程设置里面有这个编译条件的定义:
//ID_REDIRECT_NEWDELETE;ID_DEBUG_MEMORY;ID_DEBUG_UNINITIALIZED_MEMORY;%(PreprocessorDefinitions)
//GameTypeInfo.h里面生成的表结构是这样的:
typedef struct {
const char * name;
const char * type;
const char * value;
} constantInfo_t;
 
typedef struct {
const char * name;
int value;
} enumValueInfo_t;
 
typedef struct {
const char * typeName;
const enumValueInfo_t * values;
} enumTypeInfo_t;
 
typedef struct {
const char * type;
const char * name;
int offset;
int size;
} classVariableInfo_t;
 
typedef struct {
const char * typeName;
const char * superType;
int size;
const classVariableInfo_t * variables;
} classTypeInfo_t;
 
 
static constantInfo_t constantInfo[] = {
{ NULL, NULL, NULL }
};
 
static enumTypeInfo_t enumTypeInfo[] = {
{ NULL, NULL }
};
 
static classTypeInfo_t classTypeInfo[] = {
{ NULL, NULL, 0, NULL }
};

也就是所有的变量的类型信息,名字,相对对象起始地址的内存偏移量。需要注意的是:所有的模板类是不生成类型信息的,因为模板会在编译期生成多种实例代码。Bit Fields也不生成,因为无法获得其地址偏移,不支持多重继承,只建立单一继承的链表关系。所有的变量类型信息都是带词法信息的,比如类里面包含的constant是这样的:

{ “int”, “idMapPrimitive::TYPE_PATCH”, “1″ },第一个是类型,第二个是名字,第三个是常量值

对于类变量则是这样的:

{ “idList < const function_t * >“, “functions”, (int)(&((idTypeDef *)0)->functions), sizeof( ((idTypeDef *)0)->functions ) },

最复杂的是第三项,这个表达式其实是这个变量在类实例中的内存偏移量。这样,在运行时,只要有类实例的指针,加上这个offset,就是这个变量的内存区域起始地址了。

TypInfo信息是这样使用的:

1.在CreateInstance的时候检查是否有构造函数未初始化的变量存在。类实例创建之后,立即调用FindUninitializedMemory函数,这个函数会检查是否有内存特殊标记值的存在,如果发现,则查询TypeInfo信息表,获得类名和变量名。输出警告。

void idClass::FindUninitializedMemory( void ) {
#ifdef ID_DEBUG_UNINITIALIZED_MEMORY
unsigned long *ptr = ( ( unsigned long * )this ) - 1;
int size = *ptr;
assert( ( size & 3 ) == 0 );
size >>= 2;
for ( int i = 0; i < size; i++ ) {
if ( ptr[i] == 0xcdcdcdcd ) {
const char *varName = GetTypeVariableName( GetClassname(), i << 2 );
gameLocal.Warning( "type '%s' has uninitialized variable %s (offset %d)", GetClassname(), varName, i << 2 );
}
}
#endif
}
 
const char *GetTypeVariableName( const char *typeName, int offset ) {
static char varName[1024];
int i;
 
for ( i = 0; classTypeInfo[i].typeName != NULL; i++ ) {
if ( idStr::Cmp( typeName, classTypeInfo[i].typeName ) == 0 ) {
if ( classTypeInfo[i].variables[0].name != NULL && offset >= classTypeInfo[i].variables[0].offset ) {
break;
}
typeName = classTypeInfo[i].superType;
if ( *typeName == '\0' ) {
return "<unknown>";
}
i = -1;
}
}
 
const classTypeInfo_t &classInfo = classTypeInfo[i];
 
for ( i = 0; classInfo.variables[i].name != NULL; i++ ) {
if ( offset <= classInfo.variables[i].offset ) {
break;
}
}
if ( i == 0 ) {
idStr::snPrintf( varName, sizeof( varName ), "%s::<unknown>", classInfo.typeName );
} else {
idStr::snPrintf( varName, sizeof( varName ), "%s::%s", classInfo.typeName, classInfo.variables[i-1].name );
}
return varName;
}

2.在创建时的检查没有递归遍历指针对象,所以可能会有一定的遗漏,所以在idRestoreGame::CreateObjects的时候,会对每一个创建出来的对象,执行idTypeInfoTools::InitTypeVariables。将所有变量初始化为一个特殊值。同时检查指针指向的对象里面是否有没有初始化的变量。

void idTypeInfoTools::InitTypeVariables( const void *typePtr, const char *typeName, int value ) {
idTypeInfoTools::fp = NULL;
idTypeInfoTools::initValue = value;
idTypeInfoTools::Write = InitVariable;
WriteClass_r( typePtr, "", typeName, "", "", 0 );
}

递归的查询TypeInfo表,把每一个变量的内存值都填成一个特殊值(比如0xCE)。同时在这个过程中,检查是否有变量还是没有初始化过的值(在分配内存时,会把内存值全部填充成0xCDCDCDCD)。如果有,则报警。

void idTypeInfoTools::WriteClass_r( const void *classPtr, const char *className, const char *classType, const char *scope, const char *prefix, const int pointerDepth ) {
int i;
 
const classTypeInfo_t *classInfo = FindClassInfo( classType );
if ( !classInfo ) {
return;
}
if ( *classInfo->superType != '\0' ) {
WriteClass_r( classPtr, className, classInfo->superType, scope, prefix, pointerDepth );
}
 
for ( i = 0; classInfo->variables[i].name != NULL; i++ ) {
const classVariableInfo_t &classVar = classInfo->variables[i];
 
void *varPtr = (void *) (((byte *)classPtr) + classVar.offset);
 
WriteVariable_r( varPtr, classVar.name, classVar.type, classType, prefix, pointerDepth );
}
}
//设置对象变量的初始值
void idTypeInfoTools::InitVariable( const char *varName, const char *varType, const char *scope, const char *prefix, const char *postfix, const char *value, const void *varPtr, int varSize ) {
if ( varPtr != NULL && varSize > 0 ) {
// NOTE: skip renderer handles
if ( IsRenderHandleVariable( varName, varType, scope, prefix, postfix, value ) ) {
return;
}
memset( const_cast<void   *>(varPtr), initValue, varSize );
}
}
//检查是否有变量是否初始化的代码如下,如果没有初始化,就打印出详细的变量信息
do {
if ( *((unsigned long *)varPtr) == 0xcdcdcdcd ) {
common->Warning( "%s%s::%s%s uses uninitialized memory", prefix, scope, varName, "" );
break;
}
} while( ++i < typeSize );

基本原理就是这样的,遇到指针和数组的时候,情况会更复杂一些,总的来说是提供了一套在运行时动态检查所有变量的机制。在看这个代码的是否我就在想,ID的静态分析工具是怎么用的?后来看到一篇卡马克的博客,发现他们也是静态分析的狂热支持者,不断的寻求更好的静态分析工具,这篇文章非常值得一看。PC-Lint我们也试用过一段时间,但是最后还是没有用起来。其他的几个工具准备好好评估下。毕竟引擎代码库的质量对项目来说非常关键。

No related posts.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值