有趣的调试过程
?? 有人说,程序员写代码的能力一定要强,如果写代码不行,错误百出,那他调试能力一定要强。总之,两个优势中至少要占有一个,否则写代码又烂,自己又改正不了,岂不是糟糕至极。本人不才,写代码经常丢东拉西,每次检查自己的程序,几乎都能发现新的bug。在惊喜不断的同时,也锻炼了我临危不惧的神经。开始我很害怕发现程序的错误,写好程序都不怎么用,发现bug后也是尽快修复,很少去系统地检查和测试。后来我发现这样做其实效率很低,bug不会因为不检查不运行就不存在,如果不主动去检查和查找原因,等待自己的会是无休无止的bug发现与修复的循环。而且发现bug之后,不应该急于修复,因为很多bug是隐藏极深的,通过现有的调试手段很难直观地定位。如果不小心改错了,会使事情更糟。 ? 如果我遇到一个bug,我会尽量让这个bug的出现变得规律、固定,并会尝试添加、删除或者调整一些代码的顺序,来实验bug出现的情况。这样调试后,肯定有些情况,bug是不会出现的,而另一些情况,bug则会出现。这是一个简单的二分法,错误的原因将运行结果分为两半,一边是正确的,一边是错误的。我的目标就是找到这条分割线。很多时候,分割线并非那么直观好找,这是就要靠程序员的直觉,或者说靠他的经验以及对程序和运行环境的了解情况。多次的实验往往会触发人的灵感,有时会觉得是这里的问题,有时又觉得是那里的问题,裁判它们的方法很简单,就是实际去验证。有意思的事,问题症结所在,往往在开始的很长时间内是意识不到的(如果能意识到该问题,那它能成为一种bug的概率就很小)。我把这个过程画成了一张流程表: ? ????? 我写这篇文章也是因为我发现了一个很有意思的bug。不是因为它有多难,而是因为它的原因让我很意外。这是一个嵌入式的任务,有一块板卡,是用uboot(一种bootloader)启动的,之后会加载板卡flash上存储的操作系统linux 2.4。我的任务是将ucos移植上去。因为uboot的bootm命令基本上是专为linux的zImage设计的,我就用cp.b和go两个命令组合,让uboot来加载我存在flash上映像。而且在真实移植ucos之前,我需要先验证几个设备的驱动,比如led灯、数码管、串口、定时器之类的。问题就在我编写裸机程序验证数码管时出现了。数码管操作其实非常简单,只需要在特定的地址写数据即可。但就在我写完数码管后几秒钟,板卡就没有了输出(因为缺乏调试手段,我能看到的现象就是板卡没有了输出,连怎么异常的都不知道)。因为数码管所在的这个地址离其它地址都非常远,我以为是内存控制器或者MMU的问题。但因为时间关系,我对它们无法详细了解。后来验证的其它设备的裸机程序都很正常,ucos也成功移植了。但即便是在ucos中,也仍然是无法对数码管读写,读写后两三秒内必然当机。 ???? 直到一个多月后,我才再有时间来再次验证这块板卡上的程序。之前的调试数码管的裸机程序,早就多次的修改和调试中无法回溯了,只记得可能是数码管所在的内存控制器或MMU的问题。我这次详细地阅读了板卡控制器的文档,并分析了uboot对内存控制器和MMU的操作代码,发现它们存在对这块地址的映射,这里查不出问题。使用uboot的mw指令访问数码管,也是正常的。然后我在ucos bsp的启动代码里,添加了对数码管的操作,并让它死循环,发现它还是在两三秒后挂掉。再去掉对数码管的操作,让它死循环,发现它竟然仍然挂掉。这时我才觉得,挂掉并非因为对数码管的访问,而是另有原因。奇怪的是,如果我不在开始处死循环,而是让它尽快地开启ucos的任务调度,就一切运行良好。我想不出这是什么原因,好在我对uboot的异常处理机制代码刚进行过分析,我修改了ucos bsp的异常接收代码,让它把所有的异常全都打印出来。然后继续在开始处死循环,好吧,我发现在死循环时,有异常被响应,并导致当机。检查uboot的go命令实现,原来其中并没有关中断,而ucos bsp启动开始处,也没有关中断,恰好uboot中启动了一些定时器的中断,导致死循环中中断也会到来。再检查ucos的中断响应和恢复代码,发现中断响应时会保存任务上下文,退出时会恢复任务上下文,并支持任务抢占。等等,这时ucos的数据结构根本没有初始化,而且也没有开启任务调度,这时中断当然会紊乱掉。最后,我在ucos bsp的开始处,添加了关中断的代码,直到第一个任务开始运行,才开启中断,错误就不再发送了。 ??? 这个错误其实很简单,但我开始并没有解决,有很多原因:时间很紧、对板卡控制器了解过少,验证缺乏良好的思路等等。但最直接的原因,是我在发现这个数码管错误时,有了错误的猜想,我认为是内存控制器或者MMU的问题,没有想到是因为异常没有处理。所以说,我们习惯怀疑那些自己不确定不了解的因素,而对自己了解的熟悉的因素视而不见。很多奇奇怪怪的现象,其实只是因为很简单的原因;很多你认为不可救药的程序,其实只需要修改很微小的地方就可以全线畅通。 ?? 这些我在调试过程中发现的人性的缺点,其实并非无解。因为无论在调试过程中走过多少弯路,只要肯花时间,实验、猜想、验证,都总能找到一条出路。这大概就是勤能补拙吧。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |