【导读】:程序员工作中有两件事,一是写 Bug,另外就是调 Bug。 在 Quora 上有一个和 Bug 相关的热门问答帖:《What’s the hardest bug you’ve debugged? | 你调试过的最难 Bug 是?》,很多程序员参与分享自己经历。伯乐在线曾经从中摘编过的《一次因量子力学而 Debug 的痛苦经历》和《我遇过最难调的 Bug,最终发现是 CPU 的问题》,在今天这篇文章中,再次分享其他 4 位程序员的故事。
Jayesh Lalwani 的回复,5700+ 顶
在 2000 年的时候,项目组负责有关 JPEG 文件格式的工作,被称为 JPEG 小组,当时决定推出一个叫做 JPEG2000 的新版本。新版本有着很多很棒的想法,其中有一点是它支持流媒体图像。一张 JP2 图像可以包含多种分辨率,并且按照分辨率由低到高排列。所以,在你下载图片的时候,可以很快速的获取低分辨率的图片。这种特性的好处是,在网络状况很不好的情况下,浏览器可以迅速优先加载低分辨率的图像。同时,低分辨率的设备在匹配到适合它分辨率的图像后就会停止下载。
当时,JPEG2000 希望 JP2 图像标准可以被加入到浏览器中(剧透一下:很可惜,并没有)。我们想要用它来构建地图类的应用。JP2图像可以编码卫星图片,并且我们专门返回SVG地图的服务器。由于没有浏览器支持JP2,我创建了一个ActiveX控件,用来将JP2图像以流的形式转化为SVG地图。这真是太酷了,(在当时)我们的图像分辨率可是Google地图的十倍。
我用的是一个叫做 Kakadu 的第三方库。这是一个能够解析和将JP2 图像转化为流的开源库。Kakadu 的表现真的非常非常棒! 它非常快!除了在使用一段时间后它会无征兆地卡住。这引起了我的注意,并且我怀疑问题产生的原因是用于存在线程竞争。所以,我决定通过 debug 来解决这个问题。在当时,我很年轻,而且对多线程的理解还算不错,但我还没有真正解决过一个多线程问题,是的,对此我很兴奋。
所以我通过深入它们的源码来开始调试。我记得在一开始开启debug进行调试的时候,问题竟然消失了。该死!事实上,调试器本身作为一个同步机制,它改变了线程中指令的执行时间。
于是我开始添加log日志输出来观察一下。在我添加log之后,问题竟然又一次消失了,我的天!由于日志信息记录到文件系统,文件系统作为一个同步机制再一次改变了指令的执行时间。
调试不行,添加log日志也不行。我还没有告诉你,这是一个多么复杂的大工程,所以,在我解决这个问题之前,我需要弄清楚如何在多线程环境中解决问题,该死!
于是,我开始思考,这会是由于同步机制以外的代码而导致代码的执行时间被修改么?所以说,只要我保持内外部代码一致同步,那我就有可能阻止执行时间被修改。于是,我开始尽量的减少log日志输出。最后,我懂了,如果我输出一个字符的log日志就不会出错,也就是说,log日志最多不能超过一个字符。
所以,第一件我需要弄清楚的事是,问题的发生与否是不是由于执行了不同的代码所导致的。要记住,最多我只能输出单个字符的log日志。于是,我开始深入分析每一段代码,而不是仅仅理解它的作用。每当我到达一个条件判断语句时,我会在两条分支上分别输出日志信息。其中一条分支输出“”,另一条分支输出“/”。当我看到一个循环语句时,我在循环体内输出“|”。当我看到输出的日志时,它是这个样子的
1. |||//|||||||||//
我会在运行程序的时候,分别记录下代码执行成功与失败两种情况下的字符串信息。下一步,比较两个字符串,找出不同的地方。最后,我会根据字符串信息中不同的地方,对应到代码中去进一步分析。
因为我不能输出过多的日志信息,所以我不得不仔细分析。幸运的是,Kakadu的代码结构非常好。我真想亲一口Kakadu的开发者(虽然他们制造了这个bug)。所有的代码层次结构都很清晰。它们通过高级方法来调用底层的方法。所以,我选择在高级方法中添加单字符log信息。当我找到差异时,通过代码我可以知道产生差异的原因。在大多数情况下,差异都是由于底层调用的方法不同而产生的。于是,我删除掉原有的log日志,然后在底层的方法中添加相同的log日志来观察,就这样,一层又一层,直到我找到产生bug的原因所在。
这整个过程花了大约三个星期。最后通过修改了一个字符解决了问题。由于渲染线程在等待生产者线程加载的数据,而形成一个循环等待,造成了卡死的问题。计数器在判断数据时,使用的是<而不是<=。通常,计数器会从预期的情况中,得到想要的数据,并且能够正常执行。在极少数情况下,当=条件满足时,渲染线程会过早的解析数据,造成异常,并跳出执行语句。从而导致渲染线程中断执行。
三个星期,一个字符,我应该得到一件T恤上面写着:
感谢你为解决这个bug做出的重大贡献。
Fredrik Zettergren 的回复,5500+ 顶
虽然解决这个问题并没有花太长时间,但它绝对是我所调试过的最怪异的一个bug。
当时在学校学习电气工程,我们有一节关于嵌入式系统的课。我和其他两个同学真的很喜欢这门课,并且我们决定做一个自动化的遥控直升机。我们通过加速计为直升机连接了MCU(单片机),以此来控制伺服系统。在3个月的课程中,我们将全部的精力都投入到这个项目里,特别这也是我们第一次接触嵌入式系统。这个过程很有趣,我们也很努力,所以一切都朝着好的方向发展。
一天傍晚,我们差不多完成了直升机各个部分的工作,并且准备组装到一起开始做几次试飞。在试飞后,我们发现了一个问题,就是当系统启动的时候,伺服系统有时不会立即运转。我们检查了所有的代码,一层一层分析,移除了多余的组件,但还是无法解决这个问题。
经过一个漫长的夜晚的调试之后,能想到的可能出问题的地方都已经检查遍了,我们真的不知道该怎么办了。其中一个成员实在是太累了,他靠在椅背上,把鞋子放在桌子上,闭上眼睛准备休息一会。忽然,这个bug就不见了。由于太累了,并且对这个bug没有任何主意,我们开玩笑说,他的鞋子也许真是解决这个bug的神奇良方。奇怪的是,当把他的鞋子放在桌子上的时候,bug就消失了,鞋子放下去bug就出现了。过了一会儿,我们开始思考导致这个现象的原因。哭笑不得的是,我们大概做了10-20次把脚放到桌子上和放下来的动作,每当他把脚放下来的时候,bug就会出现。我想这可能是我人生中最令人困惑的时刻了吧,至少涉及到技术方面是。
我们突然想到了这之间的共同点!原来我们忘记了,直升机各个组件之间的连接是极其不稳定的。当我的组员把他的脚放到桌子上的时候,他的脚使地面与桌子上系统之间的组件建立了连接。虽然这方面的连接可能是极其微弱的,但这足以使组件间的通信变得更稳定。当我们意识到问题产生的原因所在,并且增加了缺少的电线,直升机终于可以完美运行了。(问题的关键就是在这个地方)
我们很幸运,偶然的发现了问题的关键所在,并很快解决了问题,所以,比起痛苦得到更多的是快乐的回忆。
Amir Memon 的回复,2500+ 顶
几年前,Mozilla 和 Microsoft 报告给我们 Flash Player 存在崩溃的问题。我们没有人能重现这个崩溃,我们只能从崩溃日志中知道哪里发生了崩溃,但是这没有任何意义。实际上,虽然崩溃日志中所指向不同行的代码,但这都是由于同一个bug引起的(我们后来才知道)。
最后,我们团队中有一个很棒的质量工程师,他找到了一个发生过崩溃的设备,并且制定出一个可靠的崩溃重现步骤。发现崩溃产生的原因是由于使用了低速硬盘。
当一个视频正在被释放的时候,在Flash Player的释放序列中会发生崩溃(比如说当你浏览另一个网页的时候)。视频流文件不会被马上清除,并且暴露了线程同步的问题。
这个bug解决如此之难的原因是,很难找到一个系统可以去重现这个bug,并且在崩溃发生的地方存在着另人讨厌的多线程问题。
最终,我解决了这个bug,它在之前是很常见的。在浏览器随着插件发生异常的时候,它可能阻止了数以万次的崩溃发生。
感觉我们就像英雄一样 :-)
Stan Hanks,1800+ 顶
80年代中期,我在一家医疗设备公司做顾问工作,公司的主要研究方向是新一代正电子发射断层扫描仪。悄悄地告诉你,这个项目可是要和市场上那个有着不为人知的新技术的大家伙正面对抗的。
我主要研究嵌入式系统下的UNIX实时处理。我曾经做过无数次关于其他类型的控制系统的开发项目,这是我所做过的第三个有关医疗设备的项目。
我是这样考虑的,先画草图,再做产品原型,然后在三天内快速完成代码部分的编写。点击“安装”,看着它编译,生成,构造一个下载包,然后下载到设备上,重新启动。
就这样实施,很完美!
但是我不能这样做…
事实上,第一次运行的时候你永远不会写代码,它只是一个存根启动调试,每个人都知道。
于是,我把它分成了几个部分,其中在bug调试和逻辑分析上,花费了我近一个月的时间。
但这可是我们必须要克服的事情
我和项目经理都知道这很难,甚至我们组内的一个同事提供了第二种方案来尝试。
是的,在6周后,我们克服了种种磨难。最终取得了成功,正如预期的那样,首次运行的时候,它能够正常工作,非常完美。
并且从那以后,这个项目一直由我负责。
想看更多精选文章吗?请扫描下方的二维码↓↓↓
阅读原文:http://mp.weixin.qq.com/s?__biz=MzI2MDQ5MjExOA==&mid=2247483722&idx=1&sn=78a2ce9715fcd5d92b9ce690ac2c3127&chksm=ea699f73dd1e16656d6ac0ee4ef8f1f0e495b7a3f9f654a4f83519b669d464dc5d57a6825c6b#rd