武器装备
outputdebugstring(那些年,我在游戏开发中改过的bug:靠不住的OS和SDK)

记忆中有很多次了,几个程序员朋友聊天,聊着聊着,就聊到自己遇到过的bug。然后大家开始口沫横飞交流那些或诡异或神奇的bug,谈论自己当年是如何搞定bug或是被bug搞定。

下面分享一下自己遇到过印象深刻的bug:

做一个PC项目的时候,凶猛的测试兄弟把Winxp 64单独列出来,作为一个测试平台,然后我们的噩梦就开始了。游戏在Winxp 64上面频繁Crash,经常在更新Octree的时候访问到空指针,但逻辑上来看那个指针不可能是空的。Crash位置很随机,到处都有,通常都是玩了一个小时在一个莫名其妙的地方Crash。

来回几轮搞下来,根据某次比较靠谱的Crash Callstack,怀疑到了memcpy。memcpy是个老同志了,兢兢业业地忙碌在各个程序里,负责搬运数据很多年,工作绩效有口皆碑。它有什么问题呢?它还能有什么问题呢?

既然有怀疑,就要捉奸。我做了试验,在memcpy后面直接加一个循环,逐字节比较源数据和目标数据,有时候居然会不相等... 这个可颠覆了我的世界观。我试图写了一个函数,里面就一个循环,逐字节复制数据,然后把所有的memcpy全替换成这个函数,果然不Crash了。但显然这是不行的,速度太慢了。

我们的怀疑从3d代码转移到多线程,在进入那个死循环之前,我们设下断点,把其他无关的线程全部都Freeze,只有那个线程会运行。这样,任何多线程的干扰全部排除,memcpy在一个理想的环境中欢快的运行,但memcpy还是会出错。

山穷水尽疑无路,我无奈下单步跟踪了一下memcpy的汇编代码,上来有两句

  1. je Dword_align

所以最终的解决方案是,Win64下,我在游戏一开始初始化的地方,加上谜一样的初始化代码:

  1. __sse2_available = 0;

memcpy不使用sse2后会不会有性能问题?经过测试,发现问题不大,对于频繁调用的少量数据复制,memcpy不太能从sse2里面得到多少好处。对于大量数据的复制,我们用得也不多,profile了一下,没有发现明显的瓶颈,无视了。这事情也可以从反向理解,由于游戏规模太大,各种多线程和GPU/CPU同步,导致任何一点的效率损失,可能不会扩散到整个游戏,被其他同步和等待吸收掉了…我真是一个好程序员,能想到这么好的理由说服自己。

可得结论:Winxp 64靠不住(其实问题还是应该出在我们内部,不过其他OS都没问题,就赖在它身上了)

Takeaway: 首先,你要有一个有耐心的老板,才能给你时间去查这么奇怪的Bug

靠不住的SDK:OutputDebugString

Xbox 360开发SDK,早期bug一大堆,比如预编译头文件太大了,编译器抱怨说预编译头文件预留内存不够,这个好办,加上/Zm512编译选项即可。加上,编译,没用?!只好写信去MS问,他们说,哦,原来如此啊,今天天气真好,哈哈哈哈,请等待下一次更新,谢谢您汇报云云...虽是MS的bug,可是我也不能等着他修复才工作,只好手动拆分预编译头文件,把很多内容放在预编译头文件外面,预编译头文件就会变小,编译就可以顺利通过了。

360有3个cores,每个core有2个Hyperthreads,总计6个线程,我们的游戏在逻辑线程、声音线程和渲染线程外,还开了3个辅助线程,用自己写的Thread Pool管理系统来管理这些线程,初始化的时候就是一个循环把每个线程创建出来。

重现概率实在太低,不好调试,于是我试图用简单的程序片段来重现这个bug。因为都是创建新线程时候死锁,第一个想到的就是写一个小程序,直接一个死循环,创建线程,打印日志,然后杀掉线程,重复再做。果然能够重现bug,程序运行1分钟以后,就死锁了。同样的现场,还是在OutputDebugString内部死锁了。难道bug不是在线程库,而是在OutputDebugString内?

Takeaway: 不要轻易相信你看见的表层问题,问题可能在别处

一天后,微软发回Email,说这是内部的Bug,请无视,不会影响Release版本,是Debug协议上的问题。

可得结论:微软靠不住。(开玩笑,请微软公司不要追杀)


顶一下()     踩一下()

热门推荐

发表评论
0评