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

正好看见Gamesutra上也登了篇Dirty Coding Tricks ,发现老外也有这个癖好,原来天下程序员本一家。一路走来,程序员的成长,便是一路刀光剑影,与bug斗个你死我活。了解别人的Debug过程,或是回忆一下自己Debug的时候思路,也是很有意思的事情,值得定期总结。

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

靠不住的c:Memcpy的传说

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

第一反应就是那些地址被非法访问,可能是某个错误的指针指向那里,往里面写了不该写的值。于是我根据最常Crash的地址设下数据断点,试了好几天,从来没有断下过,Crash还是依旧。然后同事试图加上大量的保护代码,判断一个指针是不是空指针后才使用,很好的降低了Crash机率,但偶尔还是会有。想想问题根源没有找到,降低Crash概率只是让自己更难修bug,而且访问Octree也比较多,乱加保护会影响性能,我一狠心又把保护代码全去掉了。

Takeaway: 不到万不得已,不要用保护代码掩盖Bug,它知会让你的日子更难过。

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

为了保证多线程能同步并行执行,我们程序中有个很大的memcpy,把一块Octree从后台用memcpy复制到前台的工作buffer。当然这个做法的设计优劣不在此讨论,存在即合理,2007年,多线程引擎我们还不是那么擅长搞。

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

既然有了点线索,就可以试图简化bug重现条件了。我不能每次都花一个小时去运行游戏,寻找那一次crash。我在游戏load起来,开始走主循环后,加了一个死循环,不停用memcpy复制一块内存,然后比较源数据和目标数据。源数据里面没有0,都是1-255的值,可是运行几十秒以后目标数据居然有0。这样,我们成功地把重现一次bug的平均时间从一个小时降低到一分钟。

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

继续简化,我单独写一个小程序,里面只做死循环和memcpy,游戏账号交易平台来判断是不是OS的问题(实在是走投无路了)。试验结果是,Winxp 64没有问题,memcpy始终如一地正常工作着(本该如此^_^)。 可是某一次,当我们的游戏在后台运行的时候,再启动这个小程序,居然memcpy又出问题了...无语了,原来我们的游戏还能万里追杀,跨进程搞垮OS下面的其他进程。

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

  1. cmp DWORD PTR __sse2__available,0
  2. je Dword_align

复制代码

也就是说,memcpy上来看有没有设置__sse2_available,这个值估计是CRT库里面设的,如果有SSE2就执行sse优化的memcpy,没有就跳到Dword_align那里执行普通版本的流程。我开始怀疑是不是我们的游戏里面对系统做了点什么手脚,导致在__sse2_available允许的情况下,优化的代码会执行出错。游戏的代码规模实在太大,又用了n个中间件,我无力一一查看,且我也看不懂SSE代码(哎呀好羞射),就随手做了个试验,在那句判断的地方,通过Debugger把__sse2_available的值改成了0。从此memcpy再也不出错了。

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

  1. extern "C" extern DWORD __sse2_available;
  2. __sse2_available = 0;

复制代码

这样memcpy就永远使用不做sse2优化的代码了。

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

对游戏跨进程影响其他程序的memcpy,实在没能力解决了,Winxp 64是一个太小众的环境,用户要么用Winxp 32, 要么用Vista 32/64,市场占有率很低,我们也算仁至义尽了。

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

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

为了达成目的,我们要不择手段。

靠不住的SDK:OutputDebugString

话说当年开发Splinter Cell 4,使用的还是XBOX360的Alpha kit。微软早期的360 Kit,全不像后期的Kit,后期kit长得和主机差不多。而当时的KIt,是用一个很大的Power Mac G5,换上一块ATI显卡,再刷上MS的固件,连马甲都不穿一件就出来见人了。

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

扯远了,回头来说这个OutputDebugString的问题。

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

这个bug的表现现象就是加载失败,程序僵死。团队当时有几十个测试人员,每天打游戏八小时,从没碰到过游戏加载失败情况。但是开发人员这里就有很低概率会加载失败,表现情况为用VC启动游戏,然后过一会加载屏幕就僵死不动了。开发人员往往过了5分钟还没在电视上看见游戏画面才知道游戏又挂了,重现概率是每个程序员每一到两天碰到一次。我们担心这是线程管理系统内部的问题,就让每个开发人员碰到这种情况不要急着重启动,把现场给我看一下。每次僵死的时候都是在系统内核死锁,Callstack也没有有价值的信息,基本都是在创建每个线程的时候打印一句语句的时候就内核就死了。在接下来一周里,我试图在线程管理系统内部加一些日志输出,每次重现bug的时候查看日志,也没有找到更好的线索。

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

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

正好那些天有个微软360开发组的人员在我们组Onsite支持,于是他带着大量的360 Sdk的符号库(Symbol)来帮助调试,因为他不是做这一个模块的,最后也没有什么结果。最后他把我的小程序发回微软,找到内部开发人员处理,这比我们直接走正式support流程快很多。

Takeaway: 隔离问题,编写更小的测试案例,便于和别人沟通交流,寻求帮助

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

可得结论:微软靠不住。

那些年,我在游戏开发中改过的bug:靠不住的OS和SDK相关推荐

  1. 那些年,我在游戏开发中改过的bug:坑爹的Vista与中间件

    继续说那些奇葩的Bug. 靠不住的系统组件:Vista和Speech Recogition 我们游戏使用了语音识别,使用了DirectX里面的XAudio来采集声音.Windows Vista里面有一 ...

  2. 一些语法在游戏开发中的应用

    一些语法在游戏开发中的应用 一.减少不必要的全局变量 二.动态二维数组 三.整数转换为字符串输出 四.播放音乐模板 一.减少不必要的全局变量 游戏开发中有些变量只需要在少数函数中传递,修改数值.如果不 ...

  3. J2ME游戏开发中时钟的简单实现

    在java.util包中有一个TimerTask类,你可以扩展这个类并且实现他的run()方法,在run()方法中编写我们的逻辑代码.如果我们想制作一个游戏时钟,那么非常简单我们编写一个GameClo ...

  4. Java ME游戏开发中,碰撞检测算法在Java?ME中的实现(

    2019独角兽企业重金招聘Python工程师标准>>> 在Java ME游戏开发中,碰撞检测算法在Java?ME中的实现(百搜技术) 在Java ME游戏开发中,经常需要进行碰撞检测 ...

  5. 机器学习将在游戏开发中的6种应用

    机器学习正在改变几乎每个行业,从农业中的作物规划到医疗保健中的癌症诊断.这些主题通常会得到更广泛的讨论,因为它们已经产生了切实的,对人类有益的影响.对于游戏行业而言,不幸的是,游戏开发中的机器学习仍处 ...

  6. Android游戏开发中绘制游戏触摸轨迹的曲线图

    本篇文章主要来讲解怎样绘制游戏触摸轨迹的曲线图. 我们在onTouchEvent方法中,可以获取到触摸屏幕时手指触摸点的x.y坐标,如何用这些点形成一条无规则轨迹并把这条无规则轨迹曲线显示在屏幕上就是 ...

  7. [C++基金会]位计算 游戏开发中的应用

    定义的位操作:通俗点说,,位计算是计算机操作二进制整数. 无论整数可以用二的方式来表示进度,不同类型的其长度的整数位的是不一样的.INT8要么char靠8个月2 位表示,INT16或者short是由1 ...

  8. 游戏开发中的数据表示

    声明:本文内容源自腾讯游戏学院程序公开课_服务端 一.数据表示的基础 什么是数据表示? 数据是信息的载体. 数据表示是一组操作,可以描述.显示.操作信息. 数据表示的要素 IDL - 接口描述语言 I ...

  9. 手把手教你架构3d游戏引擎pdf_游戏开发中的算法

    游戏技术这条路,可深可浅.你可以满足于完成GamePlay玩法层面的东西,你也可以满足于架构和框架设计层面的东西,你也可以醉心于了解某一游戏引擎带来的掌控感.但是,我们不该止步于此,止步与目前所见或所 ...

最新文章

  1. 怎么设置ppt页面的长度和宽度_在PPT中将照片变裸眼3D效果怎样操作?分享技巧,帮你快速实现...
  2. JS模块化写法(转)
  3. Springboot+Maven在IDEA中配置好Maven后执行Reimport时提示:com.google.inject.internal.Errors.throwCreationExceptio
  4. 图着色问题贪心算法c语言,区间图着色问题(贪心算法)C++实现
  5. 七夕用腾讯最热门五大编程语言写三行情书
  6. php5.6non thread safe 区别,PHP版本Non Thread Safe和Thread Safe如何选择?区别是什么?
  7. group by(mysql oracle的区别) 的基本用法
  8. QT三种窗口、调试终端信息打印、新建菜单、设置窗口标题名称、界面初始化、打开文件对话框、保存文件对话框
  9. Linux 能否拿下苹果 M1 阵地?
  10. 线程之成员变量的线程共享
  11. python静态方法_python的静态方法和类方法
  12. 前端开发写代码哪个软件更好用?
  13. word和wps安装mendeley插件
  14. 2018年清华大学民商法学考研经验分享
  15. 13-4Happy Mid-Autumn Festival
  16. unity黄金矿工游戏
  17. 如何安装故障恢复控制台
  18. 腾讯2019秋招--小q爬塔(dp)
  19. 文本生成视频Make-A-Video,根据一句话就能一键生成视频 Meta新AI模型
  20. 强烈推荐!几款windows效率工具,文件查找、资源管理器标签化,效率控必备

热门文章

  1. 人生苦短我学python表情包_Python自动生成表情包
  2. android 日期相减得天数_Excel日期计算方法大全
  3. 从零开始数据科学与机器学习算法-KNN分类算法-07
  4. android 程序类图,Android模板设计模式之 - 构建整个应用的BaseActivity
  5. spark搭建和使用,处理massive文件
  6. mysql-in关键字,分组查询,分页查询
  7. flex布局学习笔记
  8. hdu 1286 找新朋友 欧拉函数模版题
  9. 什么是重构,什么不是重构
  10. 开始 Sencha Touch 2 之旅之三