,,,,
(1.齊魯工業(yè)大學(山東省科學院),濟南 250353;2.山東省科學院自動化研究所;3.山東省汽車電子技術重點實驗室)
面向特定應用的嵌入式系統(tǒng)一般會根據(jù)實際需求選擇規(guī)格適中的MCU,采用C語言進行軟件開發(fā)。在MCU的地址空間中,RAM是一段連續(xù)分配的線性空間,全局變量、堆(Heap)、堆棧(Stack)都分配在這段有限的線性空間內,根據(jù)實際需要,還可能把FLASH中一段代碼重定位到一段RAM空間內運行,以加快程序運行速度[1],提高系統(tǒng)實時性。
由于RAM資源有限,不可能為堆棧分配太大的尺寸,而且,作為一種靈活性很強的高級編程語言,C語言采用線性尋址方式訪問RAM空間,堆棧溢出時會繼續(xù)訪問臨近堆棧的RAM空間。堆棧尺寸設置過小、局部變量尺寸定義過大、中斷優(yōu)先級設置不合理、中斷服務程序過長導致中斷嵌套、遞歸調用、函數(shù)調用層次過深等程序設計不當之處都可能導致堆棧溢出,改變臨近堆棧的RAM空間中的內容,從而造成程序運行異常[2],發(fā)生故障甚至導致重大事故。
通過靜態(tài)分析方式確定堆??臻g的尺寸時,需要根據(jù)源程序中每個函數(shù)的局部變量大小確定每個函數(shù)的堆棧使用量[3],然后根據(jù)編譯器生成的函數(shù)調用列表為每個函數(shù)建立調用樹,檢查每棵調用樹,確定從樹根到樹葉的調用路徑的堆棧使用量,從中選出最大堆棧使用量,同時,還要仔細分析系統(tǒng)用到的所有中斷,確定中斷服務程序的堆棧使用量。但是,無法得知C標準庫函數(shù)以及大值整數(shù)的乘除、浮點運算等對應的運行庫函數(shù)的堆棧使用量,這種靜態(tài)分析方式對開發(fā)者的技術水平、對產品代碼的理解程度要求非常高,得到的數(shù)據(jù)并不完善,而且這種方式依賴于具體的應用和源程序實現(xiàn)方式,缺乏通用性。
本文設計了一種檢測嵌入式軟件堆棧溢出及使用量的方案[4],在不影響系統(tǒng)正常運行的情況下,在不受堆棧溢出影響的定時器中斷服務程序中,周期檢測堆棧使用量,通過控制LED提示堆棧溢出情況。設置了堆棧溢出緩沖區(qū),隔離堆棧和全局變量分區(qū),堆棧溢出后部分上下文信息被存放在堆棧溢出緩沖區(qū)中,將最大堆棧使用量和系統(tǒng)發(fā)生堆棧溢出后的堆棧溢出緩沖區(qū)數(shù)據(jù)存入非易失性存儲器。系統(tǒng)在實際環(huán)境中運行一段時間后,通過查看LED狀態(tài)以及讀取非易失性存儲器中的數(shù)據(jù),便可以判斷堆棧使用情況,通過保存的溢出上下文數(shù)據(jù)可以分析程序異常位置,從而調整堆棧尺寸或者調整程序設計,以提高系統(tǒng)運行的穩(wěn)定性。
堆棧的生長方向為自上而下,即向著RAM地址減小的方向增長。如果把堆??臻g設置在RAM的底部,堆棧溢出時會訪問不存在的RAM空間,造成代碼跑飛,這時無法得到溢出時的上下文數(shù)據(jù),也無法對后續(xù)的程序修改提供有用信息,所以,在劃分RAM空間時,需要將堆??臻g設置在RAM空間的頂部。在鏈接文件中,按照從頂部到底部的順序,將RAM空間劃分為堆棧區(qū)、堆棧溢出緩沖區(qū)和全局變量區(qū),根據(jù)需要和MCU RAM空間尺寸,還可能設置有代碼重定位區(qū)[5]。具體劃分如圖1所示。
圖1 RAM空間劃分圖
在RAM頂部設置大小為STACK_SIZE的堆棧區(qū),STACK_SIZE根據(jù)實際應用設置,留有一定的余量,同時受限于RAM資源,STACK_SIZE不能設置過大,棧底為堆棧區(qū)的最大地址,記為STACK_BOTTOM,設置為MCU RAM空間的最大地址;緊鄰堆棧區(qū),設置尺寸為100字節(jié)大小的堆棧溢出緩沖區(qū);緊鄰堆棧溢出緩沖區(qū),設置尺寸為APP_RAM_SIZE的全局變量區(qū),MCU的RAM尺寸記為RAM_SIZE。STACK_SIZE、APP_RAM_SIZE和RAM_SIZE的關系為:RAM_SIZE≥STACK_SIZE+100+APP_RAM_SIZE。
堆棧溢出緩沖區(qū)位于堆棧和全局變量區(qū)之間,可以起到隔離堆棧和全局變量區(qū)的作用,當堆棧溢出時,如果溢出深度小于堆棧溢出緩沖區(qū)的尺寸,則不會影響全局變量,全局變量的變化也不會改變堆棧內容。
在堆棧溢出緩沖區(qū)中定義一個包含100個單字節(jié)元素的數(shù)組,記為Stack_overflow_buf[100],堆棧溢出時,該數(shù)組會存放一部分上下文數(shù)據(jù),為后續(xù)的程序分析提供關鍵信息。
系統(tǒng)運行期間,堆棧操作會改變堆棧區(qū)數(shù)據(jù),堆棧溢出會改變Stack_overflow_buf數(shù)據(jù),通過檢查堆棧區(qū)數(shù)據(jù)和Stack_overflow_buf,便可以得知系統(tǒng)對堆棧的實際消耗。堆棧溢出可能會改變程序計數(shù)器的數(shù)值,如果把堆棧檢測函數(shù)放在中斷服務程序之外的其它位置,程序可能無法運行堆棧檢測函數(shù),而即使發(fā)生了堆棧溢出,中斷也會觸發(fā)MCU進入中斷服務程序,因此,將堆棧檢測函數(shù)放在定時器中斷服務程序中。
MCU上電初始化時,將堆棧指針SP初始化為STACK_BOTTOM,堆棧區(qū)數(shù)據(jù)和數(shù)組Stack_overflow_buf全部初始化為0x55,最大堆棧使用量記為Stack_size_max,初始化為0,然后開啟一個周期為50 ms的定時器。在定時器中斷服務程序中,讀取堆棧溢出緩沖區(qū)和堆棧區(qū)的數(shù)據(jù),判斷堆棧使用情況。
具體方法為:
讀取次數(shù)記為Read_times,初始化為0,以堆棧溢出緩沖區(qū)初始地址為首地址,以堆棧棧底為末地址,循環(huán)讀取各個RAM地址上的數(shù)據(jù),如果讀取到的數(shù)據(jù)等于0x55,讀取地址加1,讀取次數(shù)加1,如果讀取到的數(shù)據(jù)不等于0x55,跳出RAM讀取循環(huán)。
根據(jù)當前最大堆棧使用量=STACK_SIZE+100-Read_times,如果當前最大堆棧使用量大于Stack_size_max,將當前最大堆棧使用量賦值給Stack_size_max,存入非易失性存儲器,如果Stack_size_max小于或等于STACK_SIZE,不再進行處理,等待下一次定時中斷。否則,判斷為堆棧溢出,將數(shù)組Stack_overflow_buf的數(shù)據(jù)作為溢出上下文,存入非易失性存儲器;進一步根據(jù)Stack_size_max判斷是深度溢出還是淺度溢出,如果Stack_size_max小于(STACK_ZIZE + 100),此時的堆棧溢出不會影響全局變量區(qū),不會造成系統(tǒng)運行異常,從而點亮LED進行提示。如果Stack_size_max等于(STACK_ZIZE + 100),此時的堆棧溢出會影響全局變量區(qū),造成系統(tǒng)運行異常,閃爍LED燈進行提示。此時,通過專用設備可以讀取存儲在非易失性存儲器中的最大堆棧使用量和Stack_overflow_buf。根據(jù)Stack_overflow_buf數(shù)據(jù)判斷堆棧溢出位置,修改程序設計或者增加堆棧空間的大小。 堆棧溢出檢測算法流程圖如圖2所示。(說明:定時器中斷判斷分支的“N”分支與本文內容無關,所以未列出。)
圖2 堆棧溢出檢測算法流程圖
[1] 高源,羅秋鳳.基于DSP28335程序移植方法的研究與實現(xiàn)[J].電子測量技術,2013,36(3):84-88.
[2] 北京空間飛行器總體設計部.一種適用于多任務軟件進程堆棧使用深度檢測的方法:中國,201610080939.2 [P].2016-2-14.
[3] 張西超,郭向英.一種用于分析MCS-51目標碼堆棧深度的方法[J].空間控制技術與應用,2010,36(2):47-50
[4] 山東省科學院自動化研究所.嵌入式軟件堆棧溢出檢測方法和裝置:中國,201710997905.5[P].2017-10-11.
[5] 山東省科學院自動化研究所.CAN報文濾波解析方法、系統(tǒng)及電子控制單元:中國,201710743658.5 [P].2017-8-25.
馬建輝(工程師),研究方向為嵌入式與汽車電子。