打印
[匠人手记]

程序调试(除错)过程中的一些雕虫小技(更新:2010-04-09)

[复制链接]
楼主: 程序匠人
手机看帖
扫描二维码
随时随地手机跟帖
21
箫笑| | 2009-8-18 14:49 | 只看该作者 回帖奖励 |倒序浏览
正在学习中....
有请问匠人,对于功能性的,有规律的,或者比较容易找到规律的,或者不是很难找到规律的Bug,都不是很怕,就怕有些个莫名其妙的问题,自己坐桌旁试了两百次,一点问题都没有,兴高采烈地拿给客户,人家一开机就指着你奚落,看吧,这么大问题,怎么用???这可咋整呢?

使用特权

评论回复
22
程序匠人|  楼主 | 2009-8-18 15:27 | 只看该作者
正在学习中....
有请问匠人,对于功能性的,有规律的,或者比较容易找到规律的,或者不是很难找到规律的Bug,都不是很怕,就怕有些个莫名其妙的问题,自己坐桌旁试了两百次,一点问题都没有,兴高采烈地拿给客户,人家一开机就 ...
箫笑 发表于 2009-8-18 14:49


首先要纠正一个观念:调试程序的目的,是DEBUG,而不是擦屁股。

如果程序里老是有一堆莫明其妙的问题,解决一个又冒出一个。这就是程序的的规划出问题啦。如何加强程序的鲁棒性,这又是一个可以大书特书的专题了。

使用特权

评论回复
23
風神| | 2009-8-18 16:39 | 只看该作者
新人,没办法评分。只能帮顶了。酱人~~~顶

使用特权

评论回复
评分
参与人数 2威望 +4 收起 理由
[惊涛骇浪] + 3 很给力!
赵海利 + 1
24
程序匠人|  楼主 | 2009-8-18 16:57 | 只看该作者
新人,没办法评分?不会吧?
你看不到贴子边上的“评分”二字吗?

使用特权

评论回复
评论
16777216 2013-5-1 20:44 回复TA
赞一个 
评分
参与人数 7威望 +7 收起 理由
darcyju + 1 哦,看到了
海里的波涛 + 1
sun520up + 1
xingaoao + 1
wx15019203084 + 1

查看全部评分

25
蓝色的云| | 2009-8-18 21:59 | 只看该作者
新人  我也没看到“评分”。。
呼。。 刚写程序把for(i=248;i>0;i--)写成for(i-248;i>0;i--)了 搞得结果莫名其妙,都是显示器不够清晰惹的祸

使用特权

评论回复
26
蓝色的云| | 2009-8-18 22:02 | 只看该作者
哇 现在看到“评分”了

发表于 2009-8-13 23:08 | 只看该作者 回复 引用 订阅 报告 收藏 分享 评分

使用特权

评论回复
27
qwe70007| | 2009-8-18 22:06 | 只看该作者
只能看到“评分”二字,而不能真正评分~~

使用特权

评论回复
28
xwj| | 2009-8-18 22:14 | 只看该作者
哈哈~~~~~~~~~~~~~~~~~~~~~~~~~~~~
对不起,您不能对发表于 24 小时前的帖子进行评分。

使用特权

评论回复
29
蓝色的云| | 2009-8-18 22:15 | 只看该作者
哦 我的是~~~~~~~~~~~~~~~~~~~~
您所在的用户组(本站新人)无法进行此操作。

使用特权

评论回复
评分
参与人数 1威望 +3 收起 理由
[惊涛骇浪] + 3 赞一个!
30
程序匠人|  楼主 | 2009-8-18 22:34 | 只看该作者
六、在程序中设卡伏击,拦截流窜犯

警察抓流窜犯的场面我们都很熟悉了。一般的方法,就是以案发现场为中心,在犯罪分子逃窜的必经路口,设卡盘查。有道是天网恢恢疏而不漏,叫你插翅也飞不过去。

有时,程序中也会出现这样一个“流窜犯”,它就是PC指针。

对于一个未经调试的不成熟的程序来说,导致PC指针跑飞的因素很多,我们逐条列举并分析之:
1、电磁干扰(如果不是在现场,那么这一条可以暂时不考虑。因为在调试环境下一般不会有干扰);
2、程序结构错乱(喜欢用jmp或goto类指令的尤其要注意这点);
3、堆栈溢出或错乱,导致PC指针出错;
4、PC指针被错误改写(有些芯片PC指针存储单元和其它RAM单元的访问方法是一样的,很容易被误写);
5、数据错误,导致程序没有按照预期路径运行;
6、看门狗溢出(原因一般是因为看门狗设置不当、喂狗不及时、程序堵塞或者程序死循环);
7、中断被意外触发;
8、外部电路问题,比如电源不稳等等;
9、其它……

当我们开始怀疑PC指针时,我们首先要做的是确认PC指针是否跑飞了,其次要找到PC指针跑飞的证据。

我们可以在不同的分支路口,或者在我们怀疑的地方,设立断点,看程序是否走了不该经过的路径。

举个例子,比如我们怀疑程序运行中看门狗发生了溢出复位,那么很简单,我们只需要在初始化入口设立一个断点,让程序运行。正常情况下,程序只会经过一次该断点。如果再次经过该断点被拦截,那么我们就可以初步确诊“看门狗发生了溢出复位”。

再举个例子,比如程序中某个环节有A、B两个分支,正常时只走A分支,不正常时才走B分支。那么我们可以在B分支设立断点,程序一旦异常,走入B分支,就可以被拦截下来。

程序被拦截下来后,我们可以勘察现场,查看RAM区内容和程序刚走过的路径,从中分析导致程序PC指针错乱的原因。

当然,并不是每一次伏击守候都能一举擒获流窜犯(敌人是“狡猾”的,呵呵)。这就需要我们多一份耐心和技巧。通过不断调整断点位置来改变拦截地点。逐渐逼近并找到根源(流窜犯的老巢),然后一举拿下。

(未完待续,喜欢就顶)

使用特权

评论回复
评分
参与人数 12威望 +12 收起 理由
jk2010 + 1 赞一个!
nabody + 1 赞一个!
wyb668 + 1 厉害
forget121 + 1
XBJD + 1

查看全部评分

31
bear15888| | 2009-8-19 10:16 | 只看该作者
现在正在捣腾程序,好好学习,天天向上!

使用特权

评论回复
32
gyjdk0601| | 2009-8-19 12:41 | 只看该作者
顶下先,留个位置,再慢慢看!

使用特权

评论回复
33
lhj200304| | 2009-8-20 09:25 | 只看该作者
一般喜欢先把串口弄好了,然后加assert,在可能出问题的地方放一些,比如模块的入口和出口部分,在写程序的时候就写好。调试的时候很方便,调试完了加个#define NDEBUG 就行了

使用特权

评论回复
评分
参与人数 1威望 +6 收起 理由
程序匠人 + 6
34
程序匠人|  楼主 | 2009-8-20 13:27 | 只看该作者
鼓励33楼参与讨论,加分。

使用特权

评论回复
35
程序匠人|  楼主 | 2009-8-20 22:13 | 只看该作者
七、向猎人学习挖坑设陷阱的技术

上一回说到,在程序中设卡(断点),可以拦截流窜犯(程序流程错误)。实际上,断点的功能可强大了,不但可以拦截程序流程错误,也可以拦截数据错误。当然,这需要一些辅助手段。

还是以前面提到的一个例子来说。比如某个采样值(当然,也不一定是采样值,在这里也可以是RAM中任意单元中的值)受到未明因素影响,经常“乱跳”。这种数据出错的原因,可能如下:
1、计算错误(比如溢出),导致结果出错;
2、被其它程序段误改写;
3、其它原因……

当数据出错后,我们希望能够在最快时间内,让程序停下来,这样才能有效查出是哪一段程序出了问题。

有些调试环境本身可以捕捉数据错误,并产生断点中断。这当然最好不过。但是如果调试环境本身不提供这种捕捉功能,那么就需要我们自己来制造机关了。

看看猎人是是如何做的:他们会在猎物经过的地方,挖个坑,上面盖上浮土。当小型动物经过时,浮土不会塌陷。而当体重较大的动物经过时,它们的体重就会压垮浮土,掉进猎人的陷阱。

猎人的这个陷阱机关,妙就妙在是它“智能”的,会根据动物的体重进行筛选。

轻巧的小白兔来了——放过,笨重的大狗熊来了——捕获!欧耶!

好了,回到程序中来,假设我们要监控的那个RAM单元,正常值域为0~9;那么我们可以写一段测试代码,判断数值是否>9,根据判断结果执行两个分支,并在那条错误的分支路径上设置断点。

如果数据没有出错,程序会一直运行(小白兔请放心过去);直到数据错误发生,断点会自动停下来(大狗熊给我拿下)。

我们可以把这段测试程序,插入在“狗熊出没”的地方,“守株待兔”(其实“守坑待熊”)。

接下来的事情,就跟上回说的抓流窜犯原理差不多了。

——什么,你喜欢吃兔肉?不喜欢吃熊掌?
——你也太没有爱心了,唉。。。。。

(未完待续)

使用特权

评论回复
评分
参与人数 3威望 +3 收起 理由
forget121 + 1
digiway + 1 我很赞同
chen611b + 1
36
cauhorse| | 2009-8-21 01:07 | 只看该作者
狗熊是国家二级保护动物丫~
抓来吃是要进提篮桥的。。。

使用特权

评论回复
37
一只小蜗牛| | 2009-8-21 11:36 | 只看该作者
写得好,我家不了分,顶一下

使用特权

评论回复
38
h_x_zhong| | 2009-8-21 17:49 | 只看该作者
写得很好,特别对初学者,顶顶!谢谢!

使用特权

评论回复
39
gavin_li| | 2009-8-24 14:17 | 只看该作者
学习,都是使用的经验。

使用特权

评论回复
40
程序匠人|  楼主 | 2009-8-24 23:36 | 只看该作者
八、在程序中设置**

1、你的定时中断频率是否等于设想的那个值?
2、你的主程序循环一次花了多少时间?
3、你的程序中某一次复杂计算需要耗费多少时间?
4、你的程序里某个动作发生的具体时刻是什么时候?
5、……
——也许你不关心这些时间,那么你就不必看这一回了。

但是——
1、当我们的计时时钟发生偏差时,我们希望知道定时中断是否正常发生了;
2、当我们的程序任务较多,并已经导致任务堵塞时,我们需要知道主程序运行一圈的时间是多少,以便我们合理分割任务,避免堵塞;
3、同样,为了避免任务堵塞,我们要了解那些复杂计算所消耗的时间,并采取必要的措施(优化算法、分时间片执行、调整执行频率)来保证系统的实时性;
4、当程序中某些动作与其它动作或状态存在时间上的关联时,我们必须严格控制它的执行时机,确保它在正确的时刻被执行到;
5、……

我们如何才能从外部,对这些这些发生在程序内部的时间(时刻)进行精准的测量?

我们当然不能钻到芯片里面去监视每一条指令的运行情况。但是,我们可以学习一下克格勃,给程序安装个**。

具体方法:
1、首先,你需要一台示波器。没有的话,可以去偷、去抢、去骗。总之,最终你搞定了这台示波器,欧耶。
2、其次,你的芯片上要有一个空余的输出口用作测试口。没有的话,就拆东墙补西墙吧,先把不相关功能的IO口挪用一下啦。总之,最终你搞定了这个测试口,欧耶。
3、接下来,你可以在你要“监听”的程序段中,写一小段程序,对那个测试口取反(或者输出一个脉冲)。
4、最后让程序全速运行起来,你就可以用示波器来监听程序的运行状况了。

以本回开始举的几个例子来分析:
1、如果要测试定时中断频率,只要在中断中对这个测试口取反,即可通过示波器观测中断频率;
2、如果要测试主程序运行周期,只要把取反指令放在主程序循环圈中,即可;
3、如果要测试一次复杂计算(或其它动作)需要消耗多少时间,我们只需在计算之前把测试口变为高电平,等到计算结束后立即把输出口恢复到低电平,这段高电平的时间长度,即为计算消耗时间;
4、如果想知道两个动作之间的延时时间,我们也可以按照上一条方法一样,在两个动作发生前把测试口分别取一次反。就可以通过示波器轻松测试出来。
5、根据实际案例的具体情况,我们可以把这种窃听技术变换出更多花样。比如我们可以用两个IO口做测试口,同步检测两个事件的发生时刻,并测量其相互时间关系。等等……
6、引申开去,这个测试口不仅仅可以检测时间,也可以用来检测内部数据的变化。比如当某个数据的值发生“越界”时,输出一个高电平(平时为低电平)。

等到我们取得我们想要的测试数据,我们可以把这个临时的测试口功能撤销。同时,那些测试代码也可一并删除或屏蔽。

总结:把程序内在的、不直观的、快速的一些状态变化,通过IO口传递出来,以便我们观测。——这就是我们这一回所讲的“**”调试技巧的精髓。

——警告,请勿把“**”安装在女生宿舍哦!
——那样的话,匠人岂不就成为教唆犯了。罪过,罪过。。。。。

(想看下回吗?请顶贴!)

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则