數組越界問題大家在軟件開發過程中應該都司空見慣了。如果你沒見過,大概率是一個新手,工作經驗不足,倒不是說你自己會生產這種 BUG,但有些同事卻可能是 BUG 搬運工。
在魚鷹五年的工作開發過程中,除了在北京剛畢業那會沒遇到這種隱藏問題(碰到的都是自己生產的 BUG,不過自產自銷,也還行),在深圳的這幾家公司都遇到了數組越界的問題。
問題一
第一個問題是關于串口驅動導致的越界(最終結果是 hardfault),這個魚鷹在以前的筆記中也反復強調了,因為這個問題差點導致自己熬了一個通宵,也是醉了(老代碼的一個 bug)。
當然這個問題的解決和當時沒有在線調試環境(當時的 PCB 板子通過串口燒錄代碼,沒有調試接口,大坑)有很大關系,否則解決起來會快不少。
當然當時魚鷹也沒掌握這個方法《BUG 終結者,現場抓獲!|顛覆認知》,否則出現問題時,這種小問題分分鐘定位它。
所以當時解決這個問題,全靠玄學:運氣。
否則這個問題不知道要蹂躪魚鷹多少天。
問題二
這個問題在前東家遇到。當時的環境是 boot + app 形式。boot 代碼也是跑了多年的老代碼,從來沒有出現過問題。
直到有一次版本升級,發現程序不能跳轉到 app 正常運行(具體細節不記得了)。
當時有同事懷疑是我當時更新的 printf 打印函數有關系,因為當時的版本更新有這個改動。但魚鷹對自己寫的代碼還是比較有自信的,并且我的 printf 改動和 app 跳轉能有什么關系。
但懷疑到你頭上了,同時魚鷹也經常負責定位這類疑難雜癥,剛好空閑,那就去瞧瞧看了,證明一下這不是你的問題。
因為問題 100% 復現,又掌握了那個現場抓獲的技巧,很快就定位到是 boot 的一段代碼申請的棧數組空間不足,導致被調用的函數使用這塊空間時越界了。
類似下面這種:
func2(uint8_t*buff) { i=5; buff[i]= 0; } fun1() { uint8_t buff[4]; func2(buff); }
當然實際代碼肯定不可能這么簡單,i 的值是變化的,不可能一眼看出。
這個問題也是導致 hardfault(退出 func2 時,破壞了返回地址)。
看到沒有,有時候二分法(二分查找有問題的代碼提交)查找問題也不是那么可靠,因為問題可能根本不在新提交的的代碼中。
而下面的問題三也證明了這一點(當然不是說二分法沒用,只是不能全靠它作為你的結果判斷)。
問題三
這個問題是現東家遇到的問題。
自己開發的一個新模塊,當合并到主分支時,發現開機必定 hardfault,這讓我百思不得其解。自己新加入的代碼,都沒用到數組,怎么會hardfault。
我的第一反應就是,不是我的鍋。
但問題出現在我合并的過程,也只能由我定位了。還好經驗豐富,一天時間+加班幾個小時,總算是定位到了。
這個問題定位有幾個難點:
1、使用 C++
2、使用O2 優化,而使用 O0 的方式問題不復現了(最蛋疼)
3、使用了 map 庫函數
因此在復現率很高的情況下,還是花了這么多時間。
但好在順利解決了(這么高的復現率,定位root case只是時間問題,信心也是 100%)。
簡單來說,是以前的一段代碼在使用 sprintf 時(這里強烈建議用 snprintf),導致棧緩存空間越界,然后導致上一層函數的局部變量被篡改,而這個局部變量會導致 map 傳入的參數有問題,最終導致了 hardfault 。
可以看到,雖然根因在一個函數中,但最終出現問題卻可能在另一個函數中。
就像犯罪現場,作案現場只有一個(root case),但可能案發現場并不是作案現場。
因此解決 bug 過程其實就是警察破案,通過蛛絲馬跡找到第一作案現場,如此才能正確破案。
而這種代碼在工程里面有好幾處.....并且在合入我的代碼之前,運行良好。所以,數組越界也不一定會 hardfault,就看你破壞的是啥了。
為什么?
大家很奇怪,為毛數據越界大部分情況下會 hardfault,有時卻不會產生問題。只有思考到更深層的原因,你才能在 BUG 環繞中有所成長。
這個時候,就看你的基礎扎實不扎實了。
這里來個簡單示意函數(優化O0)
void func2() { inti= 0; intbuff[4]; buff[4] = 0; } voidfunc1() { intj=0;//假設該局部變量使用r4 func2(); }
棧空間如下(因為只有 4 個字,編譯器可能 buff[4] 直接使用寄存器了,但為了簡單說明,這里假設 buff 都使用了棧):
從上圖我們可以知道,進入 func2 函數時,先 push,離開時 pop。
局部變量 i 使用 r4 寄存器,但是棧空間 r4 保存的是 func1 使用的j的值。
因此,當我們數組越界時(一般越界是往高地址,因為數組索引一般是自加),很容易破壞上一個函數的棧空間,在這里破壞的是 j 的值。如果 j 很重要,那么很可能會導致 hardfault 或者其它問題(能引起 hardfault 反而是好事)。
并且這里面還有重要的返回地址 lr,如果這個值被越界破壞,那么大概率都是hardfault,因為你企圖跳轉到一個不存在的地址執行。
數組越界是一個很危險的 BUG,能觀察到現象還好,萬一是默默破壞而不能很快被察覺,成為一個隱藏 BUG,那才是最危險的。
那為啥問題三增加別的代碼會觸發這個 BUG ,修改優化等級又會消失呢?
這和編譯器有關系,有可能你的代碼導致有問題的代碼使用了不同的內存布局,從而越界篡改的位置變成了重要的內存,因此出現了現象,而優化等級對棧內存布局更是有很大影響。
另外本篇筆記介紹的局部緩存數組的越界,實際上還有全局數組的越界,那種問題相對簡單許多,看 map 文件即可。
因此,操作數組時,一定要時時刻刻檢測數組的索引的大小,以防越界。
審核編輯:劉清
-
寄存器
+關注
關注
31文章
5318瀏覽量
120015 -
C++語言
+關注
關注
0文章
147瀏覽量
6971 -
數組越界
+關注
關注
0文章
2瀏覽量
5521 -
printf函數
+關注
關注
0文章
31瀏覽量
5880
原文標題:數組越界是一顆隱形炸彈
文章出處:【微信號:emOsprey,微信公眾號:魚鷹談單片機】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論