程序匠人 - 程序调试(除错)过程中的一些雕虫小技
轉(zhuǎn)自:http://bbs.21ic.com/icview-128517-1-1.html
一、前言
調(diào)試程序,是軟件開發(fā)過程中的一個必不可少的環(huán)節(jié)。這篇帖子,匠人試著來整理一下一些調(diào)試的技巧。
說到“技巧”,這個詞自從被所長批臭之后,匠人就嚇得不敢再提,生怕一不小心就暴露了思想的淺薄和眼光的局限,呵呵。所以咱們不叫“技巧”,干脆低調(diào)點,就叫“雕蟲小技”吧。
這里所討論的“調(diào)試”技巧,有些是必須結(jié)合開發(fā)工具本身的功能來實現(xiàn),而有些可以通過燒錄芯片來驗證。
各種開發(fā)工具,提供的功能多少強弱也不盡相同,這些方法也未必都能套用。僅供參考吧。
最后說明一下,這是沒有草稿的帖子,匠人仍然以不定期連載的方式,邊寫邊發(fā)邊改。可能結(jié)構(gòu)會比較混亂。歡迎大家一起參與討論。
二、磨刀不誤砍柴功
在調(diào)試之前,需要掌握以下一些基本功:
1、熟悉當前的開發(fā)(調(diào)試)環(huán)境,比如:設(shè)置斷點、單步運行、全速運行、終止運行,查看RAM、查看堆棧、查看IO口狀態(tài)……總之,要熟練掌握基本操作的方法,并深刻了解其中意義。
2、了解芯片本身的資源和特性。
3、了解一點匯編語言的知識。(本來匠人是準備寫“精通”的,但考慮到現(xiàn)狀,還是“放低”這方面的要求罷了)。
4、掌握基本的電路知識和排錯能力。(軟件調(diào)試有時也會牽涉到硬件原因。總不能連三極管的好壞都不能識別吧?)
5、萬用表、示波器、信號發(fā)生器……這些工具總該會用吧?
6、搜索、鑒別資料的能力。(內(nèi)事問百度、外事問古狗、有事沒事上21ic網(wǎng))
7、與人溝通,描述問題的能力。(調(diào)試36計的最后一計——就是向他人討教。當然,你得把話說明白才行)
差不多了,如果上述7把砍柴刀磨好了,就可以開始調(diào)試了。接下來,請調(diào)入你的程序……
——什么?你說你程序還沒寫?
——匠人倒塌……
三、優(yōu)先調(diào)試人機界面
面對程序中的一大堆模塊,無從下手是嗎?好吧,匠人告訴你,先調(diào)顯示模塊,然后是鍵盤。
為什么要先調(diào)顯示模塊?道理很簡單,我們說“眼睛是心靈的窗戶”,同樣,“顯示是程序的窗戶”。一旦把顯示模塊調(diào)試好了,就可以通過這個窗口,偷 窺 (天吶,這兩個居然是敏感字!) 程序內(nèi)部的數(shù)據(jù)和狀態(tài)了。
然后緊接著,就是調(diào)試鍵盤模塊。有了這個按鍵,我們就可以人工干預(yù)程序的運行了。
——什么,你的程序沒有顯示和按鍵?
——這位童鞋,你真不幸,請去檢查一下自己的人品和星座運程先。謝謝。
實在是沒顯示?再看看系統(tǒng)有蜂鳴器嗎?如果僥幸有的話,也能湊合著發(fā)發(fā)提示聲音吧?
或者,有串口嗎?可以考慮借助PC 端的串口調(diào)試軟件來收發(fā)數(shù)據(jù),這也是一個間接的人機交流方法。
總而言之,要盡快建立人機交流界面。
四、慢鏡頭的威力
2009年春晚捧紅了魔術(shù)師劉謙(這位老兄名“謙”,其實一點都不謙虛——長的帥不是錯,出來拽就是罪過了!),也勾起了大家對魔術(shù)的濃厚興趣,如何識破那些快速的眼花繚亂的魔術(shù)手法呢?很簡單,用慢鏡頭回放即可。據(jù)說劉謙那個橡皮筋魔術(shù)的手法就是被人如此識破的。
回到我們單片機上來。我們知道,單片機的運行速度,一般都是在幾M到幾十M(當然,也有為了節(jié)能而采用幾十K的低速)。不管怎么樣,這個速度都遠遠超出了我們?nèi)搜勰軌蚍直娴乃俣取Q劬σ徽?#xff0c;也許幾M條指令已經(jīng)執(zhí)行過去了。
比如說數(shù)碼管顯示(假設(shè)有4位數(shù)碼管)。平時我們看到數(shù)碼管同時點亮著,但是實際上,這4個數(shù)碼管是逐個掃描的。在任意一個時刻,只有一位數(shù)碼管被點亮。在微觀上,我們可以進一步把每位數(shù)碼管的掃描動作細分為以下幾個步驟:
1、關(guān)閉上一位數(shù)碼管的位選信號;
2、輸出當前位數(shù)碼管的段選信號;
3、開啟當前位數(shù)碼管的位選信號;
4、啟動1ms延時;
5、延時結(jié)束后,指針移動到下一位數(shù)碼管,并重復(fù)上述4個步驟,如此周而復(fù)始。
你看,這樣是不是就像用一個慢鏡頭在分解顯示掃描的動作了?
那么如何實現(xiàn)這個慢鏡頭呢?方法很多:
1、單步運行(需要仿真器支持);
2、在每一步分動作之后設(shè)立斷點(需要仿真器支持);
3、在每一步分動作之后插入足夠的延時,讓我們?nèi)庋劭梢钥辞宄@些分動作(不需要仿真器,適合燒片測試);
通過慢鏡頭的反復(fù)回放,我們就可以發(fā)現(xiàn),到底是哪一個分動作出現(xiàn)了問題。
這個技巧,不僅僅適用于調(diào)試顯示程序,也適用于按鍵掃描或其它模塊。只要一個功能可以被細分為若干的動作,那么這一招“慢鏡頭分解法”都是可以使用的。
五、給程序安裝個黑匣子
某年某月的某一天,一架飛機以優(yōu)美的拋物線形狀,一頭栽到海里去了……幾天后,人們找到了飛機的黑匣子,里面記錄了飛行員的最后一句話:“天吶,我看到火星人了!……”
以上空難情節(jié)我們經(jīng)常會通過新聞看到吧(當然,最后一句是匠人版的科幻情節(jié))。看看,飛機的黑匣子可以記錄并再現(xiàn)現(xiàn)場,多么神奇!歐耶!
我們在調(diào)試程序時,也可以借鑒這個方法,給程序按裝一個黑匣子。程序中的黑匣子其實就是一個在內(nèi)存中開辟的隊列。隊列的原理我們很清楚,先進先出,后進后出(與飛機黑匣子的特性相同)。
比如說吧,假設(shè)我們的系統(tǒng)在工作中,某個輸入量的采樣值經(jīng)常受到不明原因的擾動。我們要摸清這種擾動的規(guī)律,以便對癥下藥。但是這種擾動稍縱即逝。
我們的困擾是:程序正常運行時看不出規(guī)律,單步走又難以捕捉擾動。怎么辦?
有沒有辦法,把擾動記錄下來?
當然可以。
我們可以利用系統(tǒng)里剩余的RAM,開辟一塊單元,做成隊列。并寫段測試程序,定時把新采樣值壓入隊列。
然后我們讓程序運行,在需要的(任意)時刻,讓程序停下來。這時,隊列里記錄的就是最新一批采樣數(shù)據(jù)。
只要隊列的深度足夠大,我們就可以找出擾動的規(guī)律來。
——什么,你問我什么叫隊列?
——匠人曰“天吶,我看到火星人了!……”
六、在程序中設(shè)卡伏擊,攔截流竄犯
警察抓流竄犯的場面我們都很熟悉了。一般的方法,就是以案發(fā)現(xiàn)場為中心,在犯罪分子逃竄的必經(jīng)路口,設(shè)卡盤查。有道是天網(wǎng)恢恢疏而不漏,叫你插翅也飛不過去。
有時,程序中也會出現(xiàn)這樣一個“流竄犯”,它就是PC指針。
對于一個未經(jīng)調(diào)試的不成熟的程序來說,導致PC指針跑飛的因素很多,我們逐條列舉并分析之:
1、電磁干擾(如果不是在現(xiàn)場,那么這一條可以暫時不考慮。因為在調(diào)試環(huán)境下一般不會有干擾);
2、程序結(jié)構(gòu)錯亂(喜歡用jmp或goto類指令的尤其要注意這點);
3、堆棧溢出或錯亂,導致PC指針出錯;
4、PC指針被錯誤改寫(有些芯片PC指針存儲單元和其它RAM單元的訪問方法是一樣的,很容易被誤寫);
5、數(shù)據(jù)錯誤,導致程序沒有按照預(yù)期路徑運行;
6、看門狗溢出(原因一般是因為看門狗設(shè)置不當、喂狗不及時、程序堵塞或者程序死循環(huán));
7、中斷被意外觸發(fā);
8、外部電路問題,比如電源不穩(wěn)等等;
9、其它……
當我們開始懷疑PC指針時,我們首先要做的是確認PC指針是否跑飛了,其次要找到PC指針跑飛的證據(jù)。
我們可以在不同的分支路口,或者在我們懷疑的地方,設(shè)立斷點,看程序是否走了不該經(jīng)過的路徑。
舉個例子,比如我們懷疑程序運行中看門狗發(fā)生了溢出復(fù)位,那么很簡單,我們只需要在初始化入口設(shè)立一個斷點,讓程序運行。正常情況下,程序只會經(jīng)過一次該斷點。如果再次經(jīng)過該斷點被攔截,那么我們就可以初步確診“看門狗發(fā)生了溢出復(fù)位”。
再舉個例子,比如程序中某個環(huán)節(jié)有A、B兩個分支,正常時只走A分支,不正常時才走B分支。那么我們可以在B分支設(shè)立斷點,程序一旦異常,走入B分支,就可以被攔截下來。
程序被攔截下來后,我們可以勘察現(xiàn)場,查看RAM區(qū)內(nèi)容和程序剛走過的路徑,從中分析導致程序PC指針錯亂的原因。
當然,并不是每一次伏擊守候都能一舉擒獲流竄犯(敵人是“狡猾”的,呵呵)。這就需要我們多一份耐心和技巧。通過不斷調(diào)整斷點位置來改變攔截地點。逐漸逼近并找到根源(流竄犯的老巢),然后一舉拿下。
七、向獵人學習挖坑設(shè)陷阱的技術(shù)
上一回說到,在程序中設(shè)卡(斷點),可以攔截流竄犯(程序流程錯誤)。實際上,斷點的功能可強大了,不但可以攔截程序流程錯誤,也可以攔截數(shù)據(jù)錯誤。當然,這需要一些輔助手段。
還是以前面提到的一個例子來說。比如某個采樣值(當然,也不一定是采樣值,在這里也可以是RAM中任意單元中的值)受到未明因素影響,經(jīng)常“亂跳”。這種數(shù)據(jù)出錯的原因,可能如下:
1、計算錯誤(比如溢出),導致結(jié)果出錯;
2、被其它程序段誤改寫;
3、其它原因……
當數(shù)據(jù)出錯后,我們希望能夠在最快時間內(nèi),讓程序停下來,這樣才能有效查出是哪一段程序出了問題。
有些調(diào)試環(huán)境本身可以捕捉數(shù)據(jù)錯誤,并產(chǎn)生斷點中斷。這當然最好不過。但是如果調(diào)試環(huán)境本身不提供這種捕捉功能,那么就需要我們自己來制造機關(guān)了。
看看獵人是是如何做的:他們會在獵物經(jīng)過的地方,挖個坑,上面蓋上浮土。當小型動物經(jīng)過時,浮土不會塌陷。而當體重較大的動物經(jīng)過時,它們的體重就會壓垮浮土,掉進獵人的陷阱。
獵人的這個陷阱機關(guān),妙就妙在是它“智能”的,會根據(jù)動物的體重進行篩選。
輕巧的小白兔來了——放過,笨重的大狗熊來了——捕獲!歐耶!
好了,回到程序中來,假設(shè)我們要監(jiān)控的那個RAM單元,正常值域為0~9;那么我們可以寫一段測試代碼,判斷數(shù)值是否>9,根據(jù)判斷結(jié)果執(zhí)行兩個分支,并在那條錯誤的分支路徑上設(shè)置斷點。
如果數(shù)據(jù)沒有出錯,程序會一直運行(小白兔請放心過去);直到數(shù)據(jù)錯誤發(fā)生,斷點會自動停下來(大狗熊給我拿下)。
我們可以把這段測試程序,插入在“狗熊出沒”的地方,“守株待兔”(其實“守坑待熊”)。
接下來的事情,就跟上回說的抓流竄犯原理差不多了。
——什么,你喜歡吃兔肉?不喜歡吃熊掌?
——你也太沒有愛心了,唉。。。。。
八、在程序中設(shè)置竊 聽 器
1、你的定時中斷頻率是否等于設(shè)想的那個值?
2、你的主程序循環(huán)一次花了多少時間?
3、你的程序中某一次復(fù)雜計算需要耗費多少時間?
4、你的程序里某個動作發(fā)生的具體時刻是什么時候?
5、……
——也許你不關(guān)心這些時間,那么你就不必看這一回了。
但是——
1、當我們的計時時鐘發(fā)生偏差時,我們希望知道定時中斷是否正常發(fā)生了;
2、當我們的程序任務(wù)較多,并已經(jīng)導致任務(wù)堵塞時,我們需要知道主程序運行一圈的時間是多少,以便我們合理分割任務(wù),避免堵塞;
3、同樣,為了避免任務(wù)堵塞,我們要了解那些復(fù)雜計算所消耗的時間,并采取必要的措施(優(yōu)化算法、分時間片執(zhí)行、調(diào)整執(zhí)行頻率)來保證系統(tǒng)的實時性;
4、當程序中某些動作與其它動作或狀態(tài)存在時間上的關(guān)聯(lián)時,我們必須嚴格控制它的執(zhí)行時機,確保它在正確的時刻被執(zhí)行到;
5、……
我們?nèi)绾尾拍軓耐獠?#xff0c;對這些這些發(fā)生在程序內(nèi)部的時間(時刻)進行精準的測量?
我們當然不能鉆到芯片里面去監(jiān)視每一條指令的運行情況。但是,我們可以學習一下克格勃,給程序安裝個竊? 聽 器。
具體方法:
1、首先,你需要一臺示波器。沒有的話,可以去偷、去搶、去騙。總之,最終你搞定了這臺示波器,歐耶。
2、其次,你的芯片上要有一個空余的輸出口用作測試口。沒有的話,就拆東墻補西墻吧,先把不相關(guān)功能的IO口挪用一下啦。總之,最終你搞定了這個測試口,歐耶。
3、接下來,你可以在你要“監(jiān)聽”的程序段中,寫一小段程序,對那個測試口取反(或者輸出一個脈沖)。
4、最后讓程序全速運行起來,你就可以用示波器來監(jiān)聽程序的運行狀況了。
以本回開始舉的幾個例子來分析:
1、如果要測試定時中斷頻率,只要在中斷中對這個測試口取反,即可通過示波器觀測中斷頻率;
2、如果要測試主程序運行周期,只要把取反指令放在主程序循環(huán)圈中,即可;
3、如果要測試一次復(fù)雜計算(或其它動作)需要消耗多少時間,我們只需在計算之前把測試口變?yōu)楦唠娖?#xff0c;等到計算結(jié)束后立即把輸出口恢復(fù)到低電平,這段高電平的時間長度,即為計算消耗時間;
4、如果想知道兩個動作之間的延時時間,我們也可以按照上一條方法一樣,在兩個動作發(fā)生前把測試口分別取一次反。就可以通過示波器輕松測試出來。
5、根據(jù)實際案例的具體情況,我們可以把這種竊聽技術(shù)變換出更多花樣。比如我們可以用兩個IO口做測試口,同步檢測兩個事件的發(fā)生時刻,并測量其相互時間關(guān)系。等等……
6、引申開去,這個測試口不僅僅可以檢測時間,也可以用來檢測內(nèi)部數(shù)據(jù)的變化。比如當某個數(shù)據(jù)的值發(fā)生“越界”時,輸出一個高電平(平時為低電平)。
等到我們?nèi)〉梦覀兿胍臏y試數(shù)據(jù),我們可以把這個臨時的測試口功能撤銷。同時,那些測試代碼也可一并刪除或屏蔽。
總結(jié):把程序內(nèi)在的、不直觀的、快速的一些狀態(tài)變化,通過IO口傳遞出來,以便我們觀測。——這就是我們這一回所講的“竊 聽 器”調(diào)試技巧的精髓。
——警告,請勿把“竊? 聽 器”安裝在女生宿舍哦!
——那樣的話,匠人豈不就成為教唆犯了。罪過,罪過。。。。。
九、快鏡頭加速
前面已經(jīng)講過慢鏡頭,這回再講快鏡頭。
慢鏡頭的作用的把程序的運行節(jié)奏降低,以便我們能夠“一幀一幀”地觀測程序的運行狀態(tài)。而快鏡頭的作用,則相反,就是讓程序的運行節(jié)奏變快,讓我們驗證一些原本需要消耗較多等待時間的功能。
比如說,一個定時功能,定時范圍是可調(diào)的,為1~24小時。如果我們要去驗證,總不能傻等1~24小時吧?
怎么辦呢?快鏡頭來了。
我們知道程序中的時間,是靠一級一級的計時器累計上來的。比如一個程序中分別有“時、分、秒”三個計時器單元。依次計數(shù),逢60進一。“秒”計滿60次了,則“分”+1;“分”計滿60次了,則“時”+1;“時”計數(shù)超過設(shè)定值了,我們就可以判定定時結(jié)束。
那么我們只要修改一下“分”到“時”的進位關(guān)系。比如改成:“分”+1;“分”計滿1次(原本是60次)了,則“時”+1。這樣一來,整個定時系統(tǒng)速度就比原來提高60倍。測試起來就很省時間了。
當然,測試完成后,記得要把剛才做的測試代碼改回原樣哦。
舉一反三,“快鏡頭”技巧,不僅僅用在定時方面,也可以用在計數(shù)方面。通過對數(shù)據(jù)的變化“加速”,來加快我們的測試速度。
——什么,你喜歡磨洋工,愿意花24小時去測試那個定時功能?
——哈哈,放心,我不會告訴你的老板的——除非他使出美人計來對付我。歐耶!
十、拉閘睡覺!統(tǒng)一管理調(diào)試代碼
前面介紹的幾種方法,需要在程序中增加一些臨時性的調(diào)試代碼。
有些調(diào)試代碼是無害的,比如只是一些延時指令,或者是在不使用的IO口上有一些輸出而已。
但另一些調(diào)試代碼,與正式要求的程序功能是相沖突的。那么這些代碼在完成調(diào)試之后就應(yīng)該被刪除或屏蔽掉。
那么會不會出現(xiàn)意外,把本該被刪除的代碼漏刪了?結(jié)果埋下禍害?——如果調(diào)試代碼少,出錯的概率比較低,只要認真仔細點還好辦;但是如果程序中的調(diào)試代碼寫得比較多,那么確實很擔心會發(fā)生這種問題。
或者另一種情況,就是前腳把調(diào)試代碼刪除或屏蔽掉,后腳發(fā)現(xiàn)還需要再調(diào)試,又要重新輸入或打開那些代碼?
如何管理這些代碼呢?這個我們要向宿舍管理員學習了。他們是這么做的,給所有房間安裝一個總電閘。到了晚上11點就把總閘一拉,看書的、打牌的、喝酒的、胡侃的、泡妞的、夜游的、Y們都給我老老實實睡覺去吧!
程序中,這樣的總閘也是可以通過條件編譯的方式來實現(xiàn)的。就像這樣:
| 1 2 3 4 5 6 7 8 9 | //#define TEST_MD??????? //調(diào)試狀態(tài)標志(在調(diào)試時打開,正式燒錄芯片時屏蔽) ????//在編寫調(diào)試代碼時,采用下面的形式: ????#ifdef TEST_MD??????????????? //如果是調(diào)試狀態(tài),則編譯這段代碼 ????????????…… ????????????…… ????#else??????????????????????? //如果不是調(diào)試狀態(tài),則編譯這段代碼 ????????????…… ????????????…… ????#endif |
一個總閘,把管理簡單化了。歐耶!
十一、刪繁就簡,從最小系統(tǒng)開始!
這篇手記寫到上一節(jié),原本已經(jīng)結(jié)束了,直到今天看到網(wǎng)友問的一個問題:“我的程序調(diào)試都通過啦,為什么燒片后沒有反應(yīng)?”
匠人突然發(fā)現(xiàn),這篇手記的一個缺陷,就是過于集中討論了調(diào)試中的軟件技巧,卻疏忽了硬件方面的問題。所以特意補充這個小節(jié)的內(nèi)容:
當你辛辛苦苦在仿真上完成了所有調(diào)試工作,卻發(fā)現(xiàn)燒片后系統(tǒng)不工作,該怎么辦?
到 百腦匯去看看電腦修理工是怎么干活的:面對一臺故障不明的電腦,修理工會把先不相關(guān)的部件拆掉,只留下電源、主板、CPU三樣基本核心部件,看能否啟動; 如果這一步通過了,他們會繼續(xù)加上內(nèi)存、顯卡、顯示器,看能否點亮;如果點亮了,接下來再加上:硬盤、鍵盤;最后才是鼠標、光驅(qū)、網(wǎng)卡、打印機、攝像頭之 類。
從最小系統(tǒng)開始,有條不紊地排查。這就是有經(jīng)驗的修理工們慣用的“最小系統(tǒng)法”!
所謂的最小系統(tǒng)法,是指構(gòu)建一個可運行的系統(tǒng),必不可少的、最基本的硬件和軟件環(huán)境。而在這里,我們特指硬件方面。
如果要讓一個單片機系統(tǒng)正常工作起來,需要哪些硬件條件,我們羅列一下:
1、電源
2、復(fù)位信號
3、晶振信號
Ok!無需多說了,這就是我們要優(yōu)先排查的目標(也許你需要一個示波器!)。暫時忽視那些不相關(guān)的硬件。等單片機能夠正常運行了,再去檢查其它外圍功能電路吧。
如果上述3個方面都排查無誤,系統(tǒng)還不能工作,那就是人品問題啦。趕緊找個牧師去懺悔,或者到百腦匯去幫老板干幾天活。完了再回來繼續(xù)查自己的板子上有沒有短路、開路等弱智問題。
最后再引申一下:在軟件調(diào)試時,最小系統(tǒng)法也同樣可以使用。先寫一個只有最少的代碼的系統(tǒng),讓程序跑起來,然后把模塊一個個加入調(diào)試,不失為一種明智的方法。
總結(jié)
以上是生活随笔為你收集整理的程序匠人 - 程序调试(除错)过程中的一些雕虫小技的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA毕设项目宠物店管理系统设计与实现
- 下一篇: Android建快捷方式app,创建快捷