指針就是飛刀,隨時(shí)都會(huì)中招
天氣已然入秋了,迷人的夜色帶著陣陣涼意降臨到喧鬧了一天的大地上,皎潔的月光裹挾著點(diǎn)點(diǎn)星光灑滿窗臺(tái),是時(shí)中夜,寂然無(wú)聲,周圍的一切顯得安詳而又寧?kù)o,我仿佛聽得見(jiàn)一旁熟睡的妻兒那綿長(zhǎng)的呼吸聲和帶著歡快節(jié)奏的心跳聲,是的,我又睡不著覺(jué)了,每當(dāng)工作中遇到一個(gè)一時(shí)找不出來(lái)的bug時(shí),我總會(huì)“廢寢不忘食”一番。
本文引用地址:http://cafeforensic.com/article/201810/392924.htm佛陀在金剛經(jīng)上說(shuō),菩薩應(yīng)無(wú)所住而生其心,就是說(shuō)處理任何事情都不要執(zhí)著,不要“住”在這個(gè)事情上,執(zhí)著于事無(wú)補(bǔ),就拿我來(lái)說(shuō)吧,倘若不糾結(jié)在這個(gè)bug上,好好睡覺(jué)養(yǎng)足了精神,第二天休息充分的大腦或許就突然開了竅很順利地把bug找出來(lái)了,像現(xiàn)在這樣躺在床上胡思亂想,看不了代碼,思緒還不時(shí)地跑飛,分明就是浪費(fèi)時(shí)間嘛,休息不好,第二天狀態(tài)肯定也不好,解決問(wèn)題的能力肯定大不如常。從理性上來(lái)說(shuō),應(yīng)該看破這種執(zhí)著,放下bug,而生起睡覺(jué)的心,可是,感性的我還是執(zhí)著地“住”在bug上,哎,看破和放下哪有那么容易!
1
這次遇到的bug現(xiàn)象再清楚不過(guò),筆者做的一款BCM產(chǎn)品,上電后代碼跑著跑著就飛了,飛得我心驚肉跳,飛得我不知所措。
其實(shí),Bug的表現(xiàn)非常有規(guī)律:產(chǎn)品上電后大約八分多鐘的樣子就重啟復(fù)位了,一分不多,一分不少,照常理,規(guī)律性重現(xiàn)的bug最好捉了,但是也不盡然。因?yàn)楫a(chǎn)品代碼被劃分成那么多模塊,每個(gè)模塊都有可能出問(wèn)題,這種不知道怎么操作來(lái)著就跑飛了的bug其實(shí)并不好解決。
2
據(jù)說(shuō)高人們捉Bug最青睞直接分析代碼,悟性高的人直搗黃龍,三下兩下就能找到問(wèn)題源頭,悟性低的人在代碼的迷霧中上下求索,最終也能撥云見(jiàn)霧找到真兇。而小白們捉Bug的主要手段就是調(diào)試和測(cè)試了。
筆者對(duì)著代碼恍惚半天,終于意識(shí)到所謂“軟件一哥”之語(yǔ)只是自欺欺人,于是老老實(shí)實(shí)回歸測(cè)試一途。
筆者之前都是上電后就開始做各種測(cè)試,做哪些測(cè)試也只管隨心,并無(wú)一定之規(guī)。按照“寧可錯(cuò)殺一千,不可放過(guò)一人”的原則,這些測(cè)試涉及的操作都有導(dǎo)致跑飛的重大嫌疑,倘若真真地都細(xì)究起來(lái),難免有魯迅先生《狂人日記》中覺(jué)得事事可疑,人人可怖的狂生的愚笨之嫌。于是,為了逐步縮小對(duì)bug的包圍圈,筆者采用了毛主席著名的游擊戰(zhàn)策略:“敵動(dòng)我不動(dòng)、敵不動(dòng)我動(dòng)”,既然不知道究竟是什么操作造成的跑飛,那么現(xiàn)在什么操作也不做,我不動(dòng),看敵動(dòng)否?
筆者在代碼中加了一段測(cè)試程序,一旦跑飛復(fù)位,一個(gè)led燈就會(huì)閃爍一下。在測(cè)試臺(tái)上撥拉幾下開關(guān),設(shè)置了產(chǎn)品的運(yùn)行環(huán)境之后,灑家就靜心屏氣,正襟危坐,伴隨那潺潺不息的時(shí)間之水神游天外起來(lái)。
筆者一邊品茶消磨時(shí)間,一邊觀察著led燈的現(xiàn)象,時(shí)不時(shí)抬起手腕看看手機(jī)上的時(shí)間。八分鐘過(guò)去了,‘不急,時(shí)間未到’,十分鐘過(guò)去了,’不急,也許每次跑飛的時(shí)刻都不大一樣’,十五分鐘過(guò)去了,‘咦?見(jiàn)鬼了,就加了那么一點(diǎn)測(cè)試程序,代碼就不跑飛了?’二十分鐘過(guò)去了,灑家開始著急起來(lái),’之前每次都跑飛的,現(xiàn)在怎么回事?’
“哎呦,寫B(tài)ug吶!”一位相熟的同事從我身邊飄過(guò),輕松的調(diào)侃就像飛刀一樣,直入我心。
筆者怔怔地看著測(cè)試臺(tái)和慵懶地躺在一邊的電路板,剛加上的那個(gè)測(cè)試led燈直愣愣地杵在電路板上,欲言又止?!翱隙ㄊ菧y(cè)試條件發(fā)生了變化!”我下意識(shí)地想到,“發(fā)生了什么變化呢?”串接在電路板上的萬(wàn)用表默默顯示著工作電流-1.6毫安,絲毫沒(méi)有招搖之意,這是低功耗狀態(tài)下的休眠電流,整車廠要求低于3mA,我從4mA一路做下來(lái),減到1.6mA,‘先給自己點(diǎn)個(gè)贊?!掖蛑e茬,隨手打開了測(cè)試臺(tái)的ACC開關(guān),讓BCM離開了低功耗狀態(tài)。
“我這次什么也沒(méi)有操作,難道是之前進(jìn)行的某個(gè)操作導(dǎo)致了跑飛?那就麻煩大了,鬼知道當(dāng)時(shí)進(jìn)行過(guò)哪些操作?”我繼續(xù)分析,“又或者是操作順序,不是單個(gè)操作引發(fā)跑飛,而是某幾個(gè)操作連起來(lái)導(dǎo)致的?那就更麻煩了!”我痛苦地思索道。
滴答,滴答,時(shí)間無(wú)情地流逝著,我半躺在轉(zhuǎn)椅上,任由思緒紛飛。電路和程序是從來(lái)都不會(huì)說(shuō)謊的,我懷疑過(guò)天,懷疑過(guò)地,唯獨(dú)沒(méi)有懷疑過(guò)你,我盯著那個(gè)測(cè)試led燈,小聲地自言自語(yǔ)。
Led燈像一個(gè)靜靜的美男子,全然不顧我的胡言亂語(yǔ),默然靜立。突然,就如電光火石一般,Led閃爍了一下,當(dāng)它那炫美的藍(lán)色余暉還在我眼前流連,我已經(jīng)石破天驚地意識(shí)到,肯定是非休眠狀態(tài)下才會(huì)跑飛的,我抬了抬手腕,看了看手機(jī)上的時(shí)間,算上上電后進(jìn)入休眠狀態(tài)的一分鐘,加上剛才離開低功耗后運(yùn)行的七分多鐘,正好八分鐘多!
3
按照這個(gè)思路,我重新設(shè)置了測(cè)試臺(tái),使得產(chǎn)品不會(huì)進(jìn)入休眠狀態(tài),果不其然,每隔八分多鐘led燈都會(huì)規(guī)律地閃上那么一下,守時(shí)守信,從不爽約。
那么,既然沒(méi)有做任何操作,首先就可以把所有控制模塊的嫌疑排除掉,剩下的就是通信模塊了。這款產(chǎn)品有兩路CAN總線通信,一路LIN總線通信。繼續(xù)調(diào)試程序,先把CAN總線通信屏蔽掉,測(cè)試發(fā)現(xiàn)led繼續(xù)雷打不動(dòng)地規(guī)律閃爍,排除CAN通信嫌疑。然后試著把LIN通信屏蔽掉,Led終于不再鬧騰了,它選擇了在機(jī)器的世界中如如不動(dòng),直入三昧之境。
那么,LIN通信程序錯(cuò)在哪兒了呢?這塊程序大致包括時(shí)間槽調(diào)度程序、報(bào)文接收中斷服務(wù)程序、報(bào)文解析程序三大塊,為了進(jìn)一步縮小嫌疑,我把canoe撤掉,這時(shí)LIN總線上沒(méi)有了實(shí)際的LIN通信,便不會(huì)執(zhí)行報(bào)文接收ISR和解析程序,如此這般,一通測(cè)試下來(lái),這個(gè)Bug依然故我,固執(zhí)而堅(jiān)強(qiáng),就是不肯往生極樂(lè)。問(wèn)題了然了,肯定是時(shí)間槽調(diào)度程序出了問(wèn)題。
這段程序說(shuō)來(lái)也很簡(jiǎn)單,以20ms為時(shí)間槽長(zhǎng)度,在每個(gè)時(shí)槽到達(dá)時(shí)發(fā)送報(bào)文頭,然后根據(jù)是發(fā)送數(shù)據(jù)場(chǎng)還是接收數(shù)據(jù)場(chǎng)設(shè)置相關(guān)寄存器。聽說(shuō)分析程序不貼代碼的都是耍流氓,我就先上為敬,把代碼貼上來(lái)。
void l_ifc_tx(l_sch_table_item *sch_item)
{
l_u32buffer_data;
buffer_data= *(sch_item->data);
BLIN.linflex->BDRM.R= buffer_data;
buffer_data= *(++sch_item->data);
BLIN.linflex->BDRL.R= buffer_data;
sch_item->data--;
BLIN.linflex->BIDR.B.DFL= sch_item->datalen - 1;
BLIN.linflex->BIDR.B.CCS= 0;
BLIN.linflex->BIDR.B.ID= sch_item->id;
if(l_SEND== sch_item->mode){
BLIN.linflex->BIDR.B.DIR= 1;
}else{
BLIN.linflex->BIDR.B.DIR= 0;
}
BLIN.linflex->LINCR2.B.HTRQ= 1;
}
其中,l_sch_table_item這個(gè)結(jié)構(gòu)體的定義如下:
typedef struct
{
l_ifc_handle handle;
l_u8 id;
l_Resp_mode mode;
l_u8 datalen;
l_u32 *data;
l_u8 timelen;
}l_sch_table_item;
就路還家,知道問(wèn)題出在哪段程序里就算找到Bug的家門了,膽大心細(xì)眼又尖的高手可能已經(jīng)發(fā)現(xiàn)了Bug所在,沒(méi)錯(cuò),就是這句:
buffer_data =*(++sch_item->data);
按照C語(yǔ)言運(yùn)算符的優(yōu)先級(jí),上述語(yǔ)句的執(zhí)行次序是,先找到sch_item的成員變量data,然后對(duì)data執(zhí)行++,最后執(zhí)行*(新地址)的操作。
竟然那么隨意地對(duì)指針進(jìn)行了算術(shù)運(yùn)算!!!這種行云流水的灑脫用在日常生活上倒是很美,用在編程上就是自討苦吃了。看看這條語(yǔ)句,sch_item->data是個(gè)指針,在這里的++運(yùn)算使得它每隔20ms都會(huì)累加一次,意味著1秒鐘累加50次,一分鐘累加3000次,八分鐘累加24K次,這款產(chǎn)品的RAM為24KB,八分鐘就累加到RAM空間之外,就是無(wú)效的地址空間了,加上程序上電開始執(zhí)行第一次通信調(diào)度的時(shí)間,正好是8分多鐘!
后記
嵌入式軟件領(lǐng)域的編程寶典MISRA C的規(guī)則101明確指出,不能對(duì)指針進(jìn)行算術(shù)運(yùn)算,目的是為了防止指針指向無(wú)效的內(nèi)存空間,言之鑿鑿,非常熟悉MISRA C的筆者卻充耳不聞,拿豆包不當(dāng)干糧,哎,不聽老人言,吃虧在眼前,早干嘛去了?!
評(píng)論