« 《链接、装载与库》书评 | 返回首页 | 近日小结 »

tcc 的一个 bug

tcc 是个好东西,我们的粒子系统把它当作可选模块,用于动态生成粒子控制代码。较之 lua 的版本,性能可以提高一个数量级(另外一个 gcc 版本的可选模块,会失去动态性)。为了引入这个库,我还好好研究了一下 LGPL 。

前段时间我还在抱怨 tcc 不能跑在 64 bit 环境下,结果今天因为查 bug ,去关心了一下新版本,发现 0.9.25 已经开始支持 64bit 了。

表扬到此为止,现在开始抱怨。

最近两天,我们有两个人陷入了一个诡异的 bug 中,把手头许多工作都停了下来。由于问题过于诡异,引发了许多猜想都一直未能确认。bug 似乎不是特定某段代码引起的,又牵扯进一大段复杂的 3d 渲染模块,稍稍改变一点上下文状态,就会在另一个不相关的代码中出问题。

所以我常说,单元测试或是面向测试的开发绝不是万能药。甚至有时候就是鬼扯。前段看到公司某项目写的大坨的所谓单元测试代码,我就觉得好笑。纯粹是浪费人力。用 1000 行测试 100 行的模块,不如肉眼去看。真正出了问题还找不出来。能起的作用无非也就是找出点拼写错误而已。

有那个时间写这么多测试代码,还不如多写点实际程序,提高点水平,减少 bug 产出。

闲话到此。说说最后定位出来的 bug 。

tcc 动态编译出来的函数,如果使用了浮点寄存器,有时候会在浮点栈上留下一个数字。即占用了 st(0) 。

而 gcc/msvc 通常在编译代码时,认为每次函数调用前后,浮点栈都是干净的,即有 8 个栈空间可用。

所以,在某些边界情况下,编译器生成的代码会企图用满 8 个浮点寄存器。如果中途调用了 tcc 生成的函数,会造成浮点栈溢出。浮点运算的结果不正常。

结论如此简单,但是两三个人耗在里面的时间真是让人心痛阿。我们这几天做的诸多猜测,和各种实验就不详细写了。


末了,我研究了一晚上 tcc 的 source ,打算打个补丁,不过太晚了,暂时还没有头绪。本来想先写封 email 报告一下 bug 。打开了源码包里的 TODO 文件。感觉脑袋后面都是竖线,最前面赫然写着:

Bugs:

  • FPU st(0) is left unclean (kwisatz haderach). Incompatible with optimized gcc/msc code

在 mailling list 上的相关讨论帖

教训:用开源代码前,一定要熟读 bug list 。

Comments

bug是程序永远的话题,不过好的&足够详细的设计能消除大部分bug隐患,剩余部分就只能依靠开发人员的素质和运行环境的切合程度

测试只是一个方法,方法本身是没有错的,错只会在于用错地方

TDD 的概念甚好。
只是测试的例子要少而精。

该bug的全景分析尽在http://blog.csdn.net/soloist/archive/2009/07/12/4342331.aspx

现在好多游戏开发都在搞粒子系统,尤其是完美公司的《梦幻诛仙》,其游戏特色里就大肆吹嘘其采用了粒子系统,画面如何唯美逼真,隐隐在讽刺梦幻的画面。我挺喜欢梦幻,但是梦幻法术动画效果的修改,一直没得到好评!
《创世西游》我也有幸拿到内侧码,还是不太敢恭维!希望完善!

单元测试不是万能,但是如果没有单元测试的话,在很多地方是行不通的。单元测试在很多时候可以帮助避免不必要的成本浪费。

举个例子,我们现在的项目代码量在几千万行级别,几百个开发人员,项目下面分几个大组,几十个小组。虽然组和组之间的耦合已经被极小化,但是不可避免的会有互相调用的情况。这么复杂的系统已经超出了任何个人的理解力,所以在一个模块添加看起来完全无害的改变有可能会使另外一个模块无法工作。这样的改动如果被check in的话,严重时会使整个几百人的团队的checkin都瘫痪掉。这样一个团队耽误一天的成本是多少?20-30万美元。如果没有单元测试来检验的话,这么大的损失可以有任何人的任何checkin引起;也就是说,很多时候代码库都处在无序的状态之中,当坏的checkin积累到一定程度的时候,没有人知道到底是谁引进了坏代码。

另外单元测试也是质量评估的好帮手。完整的,设计得当的单元测试有助于评估整个系统的稳定性。我们项目现在有几万个单元测试用例,覆盖各个小组的模块。如果没有这些单元测试用例,没有人能够保证在产品推出,面对几千万用户的时候,没有难以原谅的重大的错误发生。

我挺想知道你们这个引擎到底完成多少了?还有多长时间可以面试

to asking & rich
有话直说:别鬼扯了你们,被搞应用的、尤其是还出书立传的“大牛”们折腾糊涂了吧。

没经验的时候,任何开发过程也逼不出良好的设计;有经验以后,随便一个开发过程也可以做出良好设计。

将功劳归于TDD或者其它什么,那是张冠李戴。关键是第一次尝试获得的反馈是什么。

至于正确性,测试比起其它手段那更是不值得一提。除了玩具级的程序(在应用领域倒是经常出现),还得是动态语言写的,纯当用这个东西当编译器做拼写检查了。

牛人啊,一个小小的BUG,让你写了这么长,呵呵

昨天梦幻的股票系统出现大bug,云风有没有什么评论?

并不是每一个人都能够按正常的流程写出正确的代码来。我个人的体会是,单元测试给我带来很大帮助,以我的糊涂劲头。

在这个问题上我只能觉得,不需要单元测试的人,多数是天才型的,比如云风。

对于一般编程人员,还是做做验算的好。实际上并花不了太多时间。当然如果有人非要全路径覆盖,那就没什么好说。

看完最后一段,悲剧啊

我很认同云风说的与其提升自己debug的能力,还不如提升自己代码质量。

不过目前这个理论似乎还缺乏足够的依据让大多数人接受。

大可不必因为这个编译器的bug,而否定1自动测试、2单元测试、3自动单元测试以及4TDD。

这个bug是因为TCC没有做自动单元测试引起的,非粒子系统之罪,无论例子系统怎么做单元测试,都不可能发现这个bug,因果关系不要调乱。

对于云风提到的测试代码比实际代码更多,公司里大沱的单元测试代码,必然是设计得不好,耦合太高导致。真正亲手实践过自动单元测试的朋友,应该能发现,容易进行自动单元测试的代码,必然是设计良好的代码。

所以自动单元测试的最大意义,正是倒逼得出云风追求的,“我们需要的是减低模块耦合度、提高内聚性”。

对于神这个级别的设计大师,方式方法已经不重要,单不单元、测不测试一点所谓都没有。但对于一般的设计人员来说,非常有必要掌握自动单元测试,掌握之后使不使用是另外一个问题。

总的一句,自动单元测试,设计意义大于测试意义!

我的理解,前些日子流行的敏捷开发XP,其中的测试驱动开发和反复重构从人力资源上面是矛盾的!测试驱动开发并不适合反复重构的系统(测试代码量太大),适合于功能和实现缓慢变化的系统,可以积累BUG,避免在后续改进中重新引入该错误。

不能指望用单元测试去覆盖集成时出现的问题,单元测试只能检查代码本身的问题。

单元测试的目的在于有证据证明代码的逻辑正确了。找拼写错误是编译器的事情。

写代码前写测试,是为了确认需求;
写代码后写测试,是为了修改代码时更容易确保一致性。

“我们这几天做的诸多猜测,和各种实验就不详细写了。”
八卦一下,能不能介绍一下这个过程?我想这个问题的定位过程应该是蛮有启发性的。和编译器相关的这一类问题在跨平台的软件中经常会遇到。

企图用测试用例覆盖所有的BUG显然是不可能的。TDD是一个说起来很好听做起来很难贯彻的东西,根本原因还是在于测试用例的开发量太大,要完整的正确的测试一个模块,要比开发这个模块本身还要难的多,就算勉强为之总还是会有一些隐藏的bug没有被覆盖到,所以最终不得不让人产生放弃的念头。

不过,要想提高软件的质量和生产效率,自动化测试还是必不可少的环节,但是测试用例不能依赖开发人员自己去编写,而是应该有独立的测试组团队来针对软件编写自动化测试用例。

@cloud

我的意思并不是说测试可以解决一切问题,不过自动化的测试确实可以避免很多无谓的时间浪费,可以让程序员集中精力于有价值的事情上。在测试上花费过多的时间确实也是过犹不及的。单凭测试不会带来好的设计

我之所以回帖就是担心以你的影响力会对许多小朋友造成误解,呵呵

你后面的关于设计的观点我都赞成。

@asking,

我不反对构造一段程序可以最容易的重现 bug 。但,

以这个例子,构造这段程序容易吗?即使在已知 bug 成因的情况下,我的同事花了一晚上时间也没有构造出这样的程序。

因为这跟编译器行为有关,换个 gcc 版本,就不一样了。

“找到了bug,如何保证该bug随着程序的演化再次出现“

如果这个 bug 是人为失误,比如在 C 语言中把 == 写成 = 。那么重新出现在原来位置的可能性很小。(比之更复杂的情况也是。) 通常,一个好的编译器(对于静态语言)或一个好的代码静态分析器(对于动态语言) 比自动化测试更有效。

如果 bug 存在于软件复杂的上下文,由于整体设计失误,或是繁杂的模块依赖关系造成。对于单个模块的测试也就无效,而问题被彻底弄清然后 fix ,在软件的生命期内也就几乎不会再出现了。

归根到底,我们需要的是减低模块耦合度、提高内聚性。比如写更小的程序,用进程而不是线程解决问题,制定简洁稳定的 IPC 通讯协议,而不是定一堆的 interface ,等等来提高软件的可维护性。

在许多时候:企图写出覆盖所有分支的测试案例是在浪费人的时间,而
频繁、自动化运行测试套件,则是在浪费机器的时间,为地球多制造一些温室气体罢了。

找到了bug,如何保证该bug随着程序的演化再次出现时,不用再大海捞针一样的去查找?

频繁、自动化运行的测试套件的好处就是可以避免这一点。当然toy程序除外。

BS一下自己,写了这么长,只有两个句号。逗号无敌。!--

写测试用例没有一点好处,我觉得一切bug还得靠一行一行代码人为的来找才是最实际有效的,而且是唯一的办法,写测试用例的人最初找bug也应该是这么做的。既然你已经找到了BUG,那么写个说明这个Bug的文档就行了,还得在那里做“体力活”,非得用代码来实现,这不是没事找事吗,说到“验证你认识是否正确的过程”,更是可笑了,把Bug改了,编译程序,如果没有出错,那就是最好的难证了,当然,如果你的人力资源充足到大家都“几乎没事可做”,这时你去写写测试用例也是可以让你打发打发无聊的时间的。:)

如果有一个针对该bug的测试用例,你还用为花这么多时间而痛苦吗?

写测试的过程就是帮助你搞清楚问题的过程,也是验证你认识是否正确的过程。

好汗啊

"用开源代码前,一定要熟读 bug list 。"
长期潜水,今天一定要说一句,"哈。。同感。。"我也遇过相同的情况,当时看到bug list后差点昏倒,呵呵。

Post a comment

非这个主题相关的留言请到:留言本