星期二, 10月 14, 2014

如何避免 Stack Overflow



上次我們談到基本的觀念 嵌入式系統(Embedded System)的記憶體淺介
還沒有看過的朋友, 不妨複習一下


現在我們可以來談談一些記憶體的問題
首先是 Stack Overflow
這類的問題, 在我們的研發團隊認定的七大讓工程師黯然消魂的問題中是排名第四
外號 -- “乾坤大挪移"


為什麼會取這個外號呢 ?
主要的原因是這類的問題通常沒有很難解(發現真正的問題之後都解得很快),
但是通常是錯誤的人去解決這個問題, 所以一直解不出來


舉個簡單的例子
負責 Task A (姑且稱之 Mr. A)的工程師, 寫程式的時候, 寫錯了一個地方, 造成 stack overflow,
但是系統不一定會馬上crash , 或是發生問題 (否則也不會這麼黯然銷魂了)
等到發生 context switch 切換到另外一個 Task B 的時候
系統八九成會發生問題(根據莫非定律),
此時依造一般公司的慣例,
是由負責 Task B 的的工程師(姑且稱之 Mr. B) 來解決這個問題.


這就好玩了, Mr. B 要解一個不存在的問題,
結果就是焚膏繼晷, 日以繼夜地想盡辦法證明自己有錯
如果一直證明不出來, 就疑神疑鬼地亂改 code ,
把本來沒問題的 code 都改成有問題了


最慘的是, 哪天出錯的工程師 Mr. A 突然佛心來著,
修正了 stack overflow 的問題
Task B 又完全正常,
Mr. B 還是不知道為什麼沒問題了(說不定會開始投入宗教, 認為這是被上天懲罰)

接下來看一個簡單的範例, 看看 stack overflow 是多麼容易發生
void find_name(int id, int number) {  
   char name[5];


   strcpy(name, "abcde");  //1
}


void foo(void) {
   find_name(123, 456);
}


下面是一個stack 使用的簡單示意圖
//1 的程式乍看之下好像沒有問題,
事實上卻會破壞 stack 的內容.
因為 name 的空間只有5個bytes,
但是卻 copy 了6個字元 (含 Null Terminated)進去
如下圖 Return Address 的位置已經被改掉了,
到時候find_name 這個 function call 結束之後,
不會回到 foo, 而是不知道會跳到哪裡去了


Stack Overflow 示意圖 1
因為各個 task 的 stack 空間通常是連在一起的,
有的時候甚至會破壞到別的 task 的 stack,
造成別的 task 執行時當機,
例如 Task A 裡面有一段程式不小心 做了 memset(buffer, 0, MAX_BUF_SIZE),
buffer 是Task A 的local 變數,
MAX_BUF_SIZE 是一個極大值, 就很有可能將所有的 stack 一路破壞下去


Stack Overflow 示意圖 2


要避免 stack overflow 就要時時注意程式寫入某些記憶體的邊界,
尤其是字串會有 Null Terminated ('\0')


有一種比較難找的 stack overflow 就是宣告極大 local variable
或是傳入的 parameters size 極大, 例如


typedef T_ALL_NAME char[1024*1024];


void find_name(T_ALL_NAME p1) {   //1


   T_ALL_NAME all;   //2


   memcpy(&all, &p1, sizeof(T_ALL_NAME));
   /* do something */
}


在//1 或是 //2 時, task 的 SP 會有可能超出邊界, 指到別的 task 裡面,
比較正確的寫法如下


void find_name(T_ALL_NAME *p1) {   //1
   T_ALL_NAME *all;   //2


   all = malloc(sizeof(T_ALL_NAME));
   memcpy(all, p1, sizeof(T_ALL_NAME));
   /* do something */
}
利用 pointer 來傳遞size大的參數,
並且利用 heap memory 來處理需要的內容.






沒有留言: