最近特别忙,经常连续工作超过 10 小时。所以情绪也有点不稳定,有的应该在半小时处理完的问题,会拖上几个小时。比如昨天就碰到一个。
由于底层代码由我一个人做了大规模重构(在不改变中间层的基础上做的),大约修改了一万多行代码,上百个 C 文件,其中重写了数千行。这样的变动实在太大,以至于出了罕见的问题后,很让人头痛。新的代码在 Windows 和 Ubuntu 上都一切正常,惟独在 Freebsd 上,程序退出的时候会引起一个 core dump 。
我对 gdb 调试器的高阶运用显然经验不足,加上这几年写代码也不怎么调试。(IMHO ,与其增加调试能力,不如提高设计能力和编写高质量代码的能力;让 bug 不容易出现,或是把 bug 限制在很小的范围,比解决 bug 更重要)在尝试过初步的调试后,我还是使用大脑做静态分析。确定问题出在 so 文件的显式关闭 (主动调用 dlclose)的环节。
根据常规推断,如果在 dlclose 后,程序异常,最可能是其它模块引用了其中的代码段,或静态数据段。实际现象中,进程崩溃在 C 的 main 函数返回之后。而我的系统整个是用 C 构建的,不会有 C++ 里那么多的跟这些有关的相关问题。所以可以推断是进程隐式加载的 so 最后卸载时发生的问题。
这个合乎逻辑的推理最终也基本得到证实,看起来很简单。不知道昨天我怎么就吃错药了,足足浪费了 4 小时来得到结论。
实际调试过程是这样的。我用二分法去掉某些代码,想看看把测试程序缩减到什么程度,问题会消失。这显然是个不肯动脑子的傻方案。鉴于精神不佳,姑且原谅一下。凡是愚蠢的方法,在有效的同时,一定是件体力活。干的时间久点就可以理解了。最终我发现,在 X Window 创建之后,为了隐藏鼠标光标,我调用了一个 X 的 API :XCreateBitmapFromData 。如果不调这个 API ,系统结束后就没有问题。但这个 API 本身调用是成功的。
在没有经过大脑的状态下,我拼命的往这个小洞里钻。又是怀疑 API 传入的参数,又是怀疑 XLib 的工作方式。接着,我又发现了另外几个 X 调用会引起问题。全部的现象都是,调用时成功,程序结束时崩溃。当然具体情况也分两类,一类是毫无朕兆,另一类是,调整一些次序,会提前得到 X Server 发过来的错误。这后一类近一步的加深了我的错觉。
最终,我发现,即使不改变代码,修改最终编译的链接参数也会引起问题。
如果程序只链接 GL 库,就一切正常。但是多链接一个 GLU 或是别的跟 openGL 有关的库,例如 GLEW ,就会在程序退出的时候崩溃。即使,我缩减后的测试代码里没有用到任何 GLU 或 GLEW 里的函数也一样。
当然,测试代码里使用了 glx 的一个 API ,必须链接 GL 库。
暂时还不能确定引起进程崩溃的 bug 直接的罪魁祸首在哪个位置。还是多休息一下,精力充沛时再回头来看比较好。好在 freeBSD 上有全部相关源码,我的桌面也是 buildworld 出来的,等周一再仔细看看吧。
虽然程序世界里,因果关系往往都非常明显,远没有现实世界那么复杂。但偶而也会遇到一些棘手的问题。当然是否棘手取决于你对系统的犄角旮旯的了解程度。或许你难以理解的问题,换个具有相关知识的人就变的一览无遗。就好象 Windows 上的木马病毒泛滥,许多用户在中招后毫无察觉;但 Windows Geek 们不装所谓杀毒软件,一有异向就会立刻察觉到。
链接,其实是个很复杂的过程。有经验的程序员一不小心也会中招。我曾经帮同事查过一个跟 lua 有关的问题,最终就是因为错误的链接导致进程内同时存在了两份 lua core 的代码。导致某些静态变量有了两份。这种问题往往会被隐藏很久,最终会以奇怪的形式暴露出来,而超出一般程序员的调试能力。
btw, 我认为,这次遇到的问题以前就存在,只是这次重构暴露出来而已。因为早先的代码,我自己完成了链接和二进制模块加载的过程。这次我想简化这个环节,更依赖系统提供的相关机制。
3 月 23 日补充:
经查证,这个 bug 是由于我的 freeBSD 以前某次升级出了问题导致的(某些 so 的新老版本共存,相互链接混乱)。重新更新一下系统就好了。
三月 2009 | ||||||
一 | 二 | 三 | 四 | 五 | 六 | 日 |
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |