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