程序调试除错过程中的一些雕虫小技 1.docx

上传人:b****7 文档编号:10440403 上传时间:2023-02-11 格式:DOCX 页数:13 大小:24.27KB
下载 相关 举报
程序调试除错过程中的一些雕虫小技 1.docx_第1页
第1页 / 共13页
程序调试除错过程中的一些雕虫小技 1.docx_第2页
第2页 / 共13页
程序调试除错过程中的一些雕虫小技 1.docx_第3页
第3页 / 共13页
程序调试除错过程中的一些雕虫小技 1.docx_第4页
第4页 / 共13页
程序调试除错过程中的一些雕虫小技 1.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

程序调试除错过程中的一些雕虫小技 1.docx

《程序调试除错过程中的一些雕虫小技 1.docx》由会员分享,可在线阅读,更多相关《程序调试除错过程中的一些雕虫小技 1.docx(13页珍藏版)》请在冰豆网上搜索。

程序调试除错过程中的一些雕虫小技 1.docx

程序调试除错过程中的一些雕虫小技1

程序调试(除错)过程中的一些雕虫小技

一、前言

调试程序,是软件开发过程中的一个必不可少的环节。

这篇帖子,匠人试着来整理一下一些调试的技巧。

说到“技巧”,这个词自从被所长批臭之后,匠人就吓得不敢再提,生怕一不小心就暴露了思想的浅薄和眼光的局限,呵呵。

所以咱们不叫“技巧”,干脆低调点,就叫“雕虫小技”吧。

这里所讨论的“调试”技巧,有些是必须结合开发工具本身的功能来实现,而有些可以通过烧录芯片来验证。

各种开发工具,提供的功能多少强弱也不尽相同,这些方法也未必都能套用。

仅供参考吧。

最后说明一下,这是没有草稿的帖子,匠人仍然以不定期连载的方式,边写边发边改。

可能结构会比较混乱。

欢迎大家一起参与讨论。

二、磨刀不误砍柴功

在调试之前,需要掌握以下一些基本功:

1、熟悉当前的开发(调试)环境,比如:

设置断点、单步运行、全速运行、终止运行,查看RAM、查看堆栈、查看IO口状态……总之,要熟练掌握基本操作的方法,并深刻了解其中意义。

2、了解芯片本身的资源和特性。

3、了解一点汇编语言的知识。

(本来匠人是准备写“精通”的,但考虑到现状,还是“放低”这方面的要求罢了)。

4、掌握基本的电路知识和排错能力。

(软件调试有时也会牵涉到硬件原因。

总不能连三极管的好坏都不能识别吧?

5、万用表、示波器、信号发生器……这些工具总该会用吧?

6、搜索、鉴别资料的能力。

(内事问XX、外事问古狗、有事没事上21ic网)

7、与人沟通,描述问题的能力。

(调试36计的最后一计——就是向他人讨教。

当然,你得把话说明白才行)

差不多了,如果上述7把砍柴刀磨好了,就可以开始调试了。

接下来,请调入你的程序……

——什么?

你说你程序还没写?

——匠人倒塌……

三、优先调试人机界面

面对程序中的一大堆模块,无从下手是吗?

好吧,匠人告诉你,先调显示模块,然后是键盘。

为什么要先调显示模块?

道理很简单,我们说“眼睛是心灵的窗户”,同样,“显示是程序的窗户”。

一旦把显示模块调试好了,就可以通过这个窗口,偷窥(天呐,这两个居然是敏感字!

)程序内部的数据和状态了。

然后紧接着,就是调试键盘模块。

有了这个按键,我们就可以人工干预程序的运行了。

——什么,你的程序没有显示和按键?

——这位童鞋,你真不幸,请去检查一下自己的人品和星座运程先。

谢谢。

实在是没显示?

再看看系统有蜂鸣器吗?

如果侥幸有的话,也能凑合着发发提示声音吧?

或者,有串口吗?

可以考虑借助PC端的串口调试软件来收发数据,这也是一个间接的人机交流方法。

总而言之,要尽快建立人机交流界面。

(未完待续)

五、给程序安装个黑匣子

某年某月的某一天,一架飞机以优美的抛物线形状,一头栽到海里去了……几天后,人们找到了飞机的黑匣子,里面记录了飞行员的最后一句话:

“天呐,我看到火星人了!

……”

以上空难情节我们经常会通过新闻看到吧(当然,最后一句是匠人版的科幻情节)。

看看,飞机的黑匣子可以记录并再现现场,多么神奇!

欧耶!

我们在调试程序时,也可以借鉴这个方法,给程序按装一个黑匣子。

程序中的黑匣子其实就是一个在内存中开辟的队列。

队列的原理我们很清楚,先进先出,后进后出(与飞机黑匣子的特性相同)。

比如说吧,假设我们的系统在工作中,某个输入量的采样值经常受到不明原因的扰动。

我们要摸清这种扰动的规律,以便对症下药。

但是这种扰动稍纵即逝。

我们的困扰是:

程序正常运行时看不出规律,单步走又难以捕捉扰动。

怎么办?

有没有办法,把扰动记录下来?

当然可以。

我们可以利用系统里剩余的RAM,开辟一块单元,做成队列。

并写段测试程序,定时把新采样值压入队列。

然后我们让程序运行,在需要的(任意)时刻,让程序停下来。

这时,队列里记录的就是最新一批采样数据。

只要队列的深度足够大,我们就可以找出扰动的规律来。

——什么,你问我什么叫队列?

——匠人曰“天呐,我看到火星人了!

……”

(未完待续……)

也谈程序“鲁棒性”

magicchip发表于2008-2-11:

24:

000

推荐N年前,匠人曾经在“侃单片机”论坛里发起过一次关于软件抗干扰的讨论。

其实,当时的讨论基本上已经达到了软件所能做的一切范畴。

但是随后,讨论的方向逐渐转向了“软件抗干扰是否有实际意义”上去了。

虽然匠人坚持认为软件在抗干扰方面可以有所作为。

但是,来自反面的意见,也让匠人深思了许久。

世纪轮回。

这次,由emailli网友发起的“建议做为2008年1月的专题----软件抗干扰的方法研究”,又把当年的讨论场景再现。

别具意味的是,对软件抗干扰本身的置疑也被再次提出。

从某种意义上来说,随着单片机硬件抗干扰性能的越来越完善。

软件在此方面的用武之地,似乎确实在萎缩。

试问又有几个单片机程序中应用到了软件陷阱呢?

比例恐怕很小吧。

然而,匠人最近有事没事,经常喜欢在同事面前卖弄这个词——“鲁棒性”。

鲁棒性

robust

[rEJ5bQst]

adj.

强壮的;健壮的

Hisrobuststrengthwasacounterpoisetothedisease.

他身体强壮抵住了这疾病。

粗野的,不文雅的(玩笑)

什么叫“鲁棒性”呢?

按匠人的理解,就是,你的程序是否把所有的因素(包括异常因素)都考虑进去了,并且对可能的异常因素采取的规避、补救措施。

比如:

1、我们要让一个变量做递增运算,每次+1,达到某一个阀值时清零。

那么你在做阀值判断时,是判“等于”,还是判“大于等于”?

(正确答案:

判“大于等于”)

2、我们要根据一个变量去查表,或散转,假设这个变量正常范围=0~7。

那么你有没有考虑过,如果该值大于7后,程序该怎么办?

(答案:

先屏蔽(剔除)无效值,再去查表,或散转)

3、我们要让某个IO口输出“高电平”去驱动外部电路(比如说,继电器)。

那么你是否只输出一次“1”就认为完事了?

(答案:

开辟输出缓存,定期刷新输出口)

4、串口接收数据,假设收到“0X00”时执行动作A,收到“0X01”时执行动作B。

那么,你有没有考虑过,如果收到的是其他数据,该怎么办?

(答案:

参考第2例)

这样的例子不胜枚举,每一个细节中都存在陷阱。

如果在程序设计中予以考虑,则可以规避;否则,很难说你的程序运行过程中会发生什么事情。

因此,一个好的程序,定义应该如此:

“在正常情况下,可以得到正常的结果;在异常情况下,可以得到意料中的结果。

而不是:

“在正常情况下,可以得到正常的结果;在异常情况下,得到不可意料的结果。

匠人的一些同事(新手)往往会跟匠人来犯犟。

强调曰:

“我的程序没有BUG啊,是输入不正常导致的。

”,云云。

确实,这些细节上的疏忽,不能称为BUG。

我们只能称之为“鲁棒性”差!

再扩展开来看,在整个系统中,不光是软件需要考虑“鲁棒性”,硬件也同样需要考虑。

举个例子:

假设系统工作电压为5V,那么当电压低于5V时,会发生什么事情?

考虑过吗?

OK,你说你有复位电路,电压跌落时会复位。

那么匠人再问:

电压快速跌落时可以复位,但如果电压缓慢下降,你的复位电路还能正常工作吗?

或者,电压波动时,又会如何?

这样的细节还有很多,贯穿在整个设计过程中。

对于有准备的人来说,只要事先预想到了并采取规避措施,都不是问题。

对于没有准备的人来说,调试将是一场艰苦的跋涉。

因为前进的道路上,“坑”太多了,指不定在哪里跌倒。

以上,为匠人信口开河。

欢迎探讨。

六、在程序中设卡伏击,拦截流窜犯

警察抓流窜犯的场面我们都很熟悉了。

一般的方法,就是以案发现场为中心,在犯罪分子逃窜的必经路口,设卡盘查。

有道是天网恢恢疏而不漏,叫你插翅也飞不过去。

有时,程序中也会出现这样一个“流窜犯”,它就是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指针错乱的原因。

当然,并不是每一次伏击守候都能一举擒获流窜犯(敌人是“狡猾”的,呵呵)。

这就需要我们多一份耐心和技巧。

通过不断调整断点位置来改变拦截地点。

逐渐逼近并找到根源(流窜犯的老巢),然后一举拿下。

(未完待续,喜欢就顶)

七、向猎人学习挖坑设陷阱的技术

上一回说到,在程序中设卡(断点),可以拦截流窜犯(程序流程错误)。

实际上,断点的功能可强大了,不但可以拦截程序流程错误,也可以拦截数据错误。

当然,这需要一些辅助手段。

还是以前面提到的一个例子来说。

比如某个采样值(当然,也不一定是采样值,在这里也可以是RAM中任意单元中的值)受到未明因素影响,经常“乱跳”。

这种数据出错的原因,可能如下:

1、计算错误(比如溢出),导致结果出错;

2、被其它程序段误改写;

3、其它原因……

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

有些调试环境本身可以捕捉数据错误,并产生断点中断。

这当然最好不过。

但是如果调试环境本身不提供这种捕捉功能,那么就需要我们自己来制造机关了。

看看猎人是是如何做的:

他们会在猎物经过的地方,挖个坑,上面盖上浮土。

当小型动物经过时,浮土不会塌陷。

而当体重较大的动物经过时,它们的体重就会压垮浮土,掉进猎人的陷阱。

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

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

欧耶!

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

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

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

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

——什么,你喜欢吃兔肉?

不喜欢吃熊掌?

——你也太没有爱心了,唉。

八、在程序中设置窃听器

1、你的定时中断频率是否等于设想的那个值?

2、你的主程序循环一次花了多少时间?

3、你的程序中某一次复杂计算需要耗费多少时间?

4、你的程序里某个动作发生的具体时刻是什么时候?

5、……

——也许你不关心这些时间,那么你就不必看这一回了。

但是——

1、当我们的计时时钟发生偏差时,我们希望知道定时中断是否正常发生了;

2、当我们的程序任务较多,并已经导致任务堵塞时,我们需要知道主程序运行一圈的时间是多少,以便我们合理分割任务,避免堵塞;

3、同样,为了避免任务堵塞,我们要了解那些复杂计算所消耗的时间,并采取必要的措施(优化算法、分时间片执行、调整执行频率)来保证系统的实时性;

4、当程序中某些动作与其它动作或状态存在时间上的关联时,我们必须严格控制它的执行时机,确保它在正确的时刻被执行到;

5、……

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

我们当然不能钻到芯片里面去监视每一条指令的运行情况。

但是,我们可以学习一下克格勃,给程序安装个窃听器。

具体方法:

1、首先,你需要一台示波器。

没有的话,可以去偷、去抢、去骗。

总之,最终你搞定了这台示波器,欧耶。

2、其次,你的芯片上要有一个空余的输出口用作测试口。

没有的话,就拆东墙补西墙吧,先把不相关功能的IO口挪用一下啦。

总之,最终你搞定了这个测试口,欧耶。

3、接下来,你可以在你要“监听”的程序段中,写一小段程序,对那个测试口取反(或者输出一个脉冲)。

4、最后让程序全速运行起来,你就可以用示波器来监听程序的运行状况了。

以本回开始举的几个例子来分析:

1、如果要测试定时中断频率,只要在中断中对这个测试口取反,即可通过示波器观测中断频率;

2、如果要测试主程序运行周期,只要把取反指令放在主程序循环圈中,即可;

3、如果要测试一次复杂计算(或其它动作)需要消耗多少时间,我们只需在计算之前把测试口变为高电平,等到计算结束后立即把输出口恢复到低电平,这段高电平的时间长度,即为计算消耗时间;

4、如果想知道两个动作之间的延时时间,我们也可以按照上一条方法一样,在两个动作发生前把测试口分别取一次反。

就可以通过示波器轻松测试出来。

5、根据实际案例的具体情况,我们可以把这种窃听技术变换出更多花样。

比如我们可以用两个IO口做测试口,同步检测两个事件的发生时刻,并测量其相互时间关系。

等等……

6、引申开去,这个测试口不仅仅可以检测时间,也可以用来检测内部数据的变化。

比如当某个数据的值发生“越界”时,输出一个高电平(平时为低电平)。

等到我们取得我们想要的测试数据,我们可以把这个临时的测试口功能撤销。

同时,那些测试代码也可一并删除或屏蔽。

总结:

把程序内在的、不直观的、快速的一些状态变化,通过IO口传递出来,以便我们观测。

——这就是我们这一回所讲的“窃听器”调试技巧的精髓。

——警告,请勿把“窃听器”安装在女生宿舍哦!

——那样的话,匠人岂不就成为教唆犯了。

罪过,罪过。

 

九、快镜头加速

前面已经讲过慢镜头,这回再讲快镜头。

慢镜头的作用的把程序的运行节奏降低,以便我们能够“一帧一帧”地观测程序的运行状态。

而快镜头的作用,则相反,就是让程序的运行节奏变快,让我们验证一些原本需要消耗较多等待时间的功能。

比如说,一个定时功能,定时范围是可调的,为1~24小时。

如果我们要去验证,总不能傻等1~24小时吧?

XX文库-让每个人平等地提升自我

怎么办呢?

快镜头来了。

我们知道程序中的时间,是靠一级一级的计时器累计上来的。

比如一个程序中分别有“时、分、秒”三个计时器单元。

依次计数,逢60进一。

“秒”计满60次了,则“分”+1;“分”计满60次了,则“时”+1;“时”计数超过设定值了,我们就可以判定定时结束。

那么我们只要修改一下“分”到“时”的进位关系。

比如改成:

“分”+1;“分”计满1次(原本是60次)了,则“时”+1。

这样一来,整个定时系统速度就比原来提高60倍。

测试起来就很省时间了。

当然,测试完成后,记得要把刚才做的测试代码改回原样哦。

举一反三,“快镜头”技巧,不仅仅用在定时方面,也可以用在计数方面。

通过对数据的变化“加速”,来加快我们的测试速度。

——什么,你喜欢磨洋工,愿意花24小时去测试那个定时功能?

——哈哈,放心,我不会告诉你的老板的——除非他使出美人计来对付我。

欧耶!

(明天开始连载暂停5天,喜欢看的继续顶着……)

人按:

本连载已经停顿好久了,但看到这么多人顶帖,实在感动,要么,咱再挤点牙膏出来?

十、拉闸睡觉!

统一管理调试代码

前面介绍的几种方法,需要在程序中增加一些临时性的调试代码。

有些调试代码是无害的,比如只是一些延时指令,或者是在不使用的IO口上有一些输出而已。

但另一些调试代码,与正式要求的程序功能是相冲突的。

那么这些代码在完成调试之后就应该被删除或屏蔽掉。

那么会不会出现意外,把本该被删除的代码漏删了?

结果埋下祸害?

——如果调试代码少,出错的概率比较低,只要认真仔细点还好办;但是如果程序中的调试代码写得比较多,那么确实很担心会发生这种问题。

或者另一种情况,就是前脚把调试代码删除或屏蔽掉,后脚发现还需要再调试,又要重新输入或打开那些代码?

如何管理这些代码呢?

这个我们要向宿舍管理员学习了。

他们是这么做的,给所有房间安装一个总电闸。

到了晚上11点就把总闸一拉,看书的、打牌的、喝酒的、胡侃的、泡妞的、夜游的、Y们都给我老老实实睡觉去吧!

程序中,这样的总闸也是可以通过条件编译的方式来实现的。

就像这样:

//#defineTEST_MD//调试状态标志(在调试时打开,正式烧录芯片时屏蔽)

//在编写调试代码时,采用下面的形式:

#ifdefTEST_MD//如果是调试状态,则编译这段代码

……

……

#else//如果不是调试状态,则编译这段代码

……

……

#endif

一个总闸,把管理简单化了。

欧耶!

十一、删繁就简,从最小系统开始!

这篇手记写到上一节,原本已经结束了,直到今天看到网友问的一个问题:

“我的程序调试都通过啦,为什么烧片后没有反应?

匠人突然发现,这篇手记的一个缺陷,就是过于集中讨论了调试中的软件技巧,却疏忽了硬件方面的问题。

所以特意补充这个小节的内容:

当你辛辛苦苦在仿真上完成了所有调试工作,却发现烧片后系统不工作,该怎么办?

到百脑汇去看看电脑修理工是怎么干活的:

面对一台故障不明的电脑,修理工会把先不相关的部件拆掉,只留下电源、主板、CPU三样基本核心部件,看能否启动;如果这一步通过了,他们会继续加上内存、显卡、显示器,看能否点亮;如果点亮了,接下来再加上:

硬盘、键盘;最后才是鼠标、光驱、网卡、打印机、摄像头之类。

从最小系统开始,有条不紊地排查。

这就是有经验的修理工们惯用的“最小系统法”!

所谓的最小系统法,是指构建一个可运行的系统,必不可少的、最基本的硬件和软件环境。

而在这里,我们特指硬件方面。

如果要让一个单片机系统正常工作起来,需要哪些硬件条件,我们罗列一下:

1、电源

2、复位信号

3、晶振信号

Ok!

无需多说了,这就是我们要优先排查的目标(也许你需要一个示波器!

)。

暂时忽视那些不相关的硬件。

等单片机能够正常运行了,再去检查其它外围功能电路吧。

如果上述3个方面都排查无误,系统还不能工作,那就是人品问题啦。

赶紧找个牧师去忏悔,或者到百脑汇去帮老板干几天活。

完了再回来继续查自己的板子上有没有短路、开路等弱智问题。

最后再引申一下:

在软件调试时,最小系统法也同样可以使用。

先写一个只有最少的代码的系统,让程序跑起来,然后把模块一个个加入调试,不失为一种明智的方法。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 军事

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1