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