,,,,
(1.齊魯工業(yè)大學(xué)(山東省科學(xué)院),濟(jì)南 250353;2.山東省科學(xué)院自動(dòng)化研究所;3.山東省汽車電子技術(shù)重點(diǎn)實(shí)驗(yàn)室)
面向特定應(yīng)用的嵌入式系統(tǒng)一般會(huì)根據(jù)實(shí)際需求選擇規(guī)格適中的MCU,采用C語言進(jìn)行軟件開發(fā)。在MCU的地址空間中,RAM是一段連續(xù)分配的線性空間,全局變量、堆(Heap)、堆棧(Stack)都分配在這段有限的線性空間內(nèi),根據(jù)實(shí)際需要,還可能把FLASH中一段代碼重定位到一段RAM空間內(nèi)運(yùn)行,以加快程序運(yùn)行速度[1],提高系統(tǒng)實(shí)時(shí)性。
由于RAM資源有限,不可能為堆棧分配太大的尺寸,而且,作為一種靈活性很強(qiáng)的高級(jí)編程語言,C語言采用線性尋址方式訪問RAM空間,堆棧溢出時(shí)會(huì)繼續(xù)訪問臨近堆棧的RAM空間。堆棧尺寸設(shè)置過小、局部變量尺寸定義過大、中斷優(yōu)先級(jí)設(shè)置不合理、中斷服務(wù)程序過長導(dǎo)致中斷嵌套、遞歸調(diào)用、函數(shù)調(diào)用層次過深等程序設(shè)計(jì)不當(dāng)之處都可能導(dǎo)致堆棧溢出,改變臨近堆棧的RAM空間中的內(nèi)容,從而造成程序運(yùn)行異常[2],發(fā)生故障甚至導(dǎo)致重大事故。
通過靜態(tài)分析方式確定堆??臻g的尺寸時(shí),需要根據(jù)源程序中每個(gè)函數(shù)的局部變量大小確定每個(gè)函數(shù)的堆棧使用量[3],然后根據(jù)編譯器生成的函數(shù)調(diào)用列表為每個(gè)函數(shù)建立調(diào)用樹,檢查每棵調(diào)用樹,確定從樹根到樹葉的調(diào)用路徑的堆棧使用量,從中選出最大堆棧使用量,同時(shí),還要仔細(xì)分析系統(tǒng)用到的所有中斷,確定中斷服務(wù)程序的堆棧使用量。但是,無法得知C標(biāo)準(zhǔn)庫函數(shù)以及大值整數(shù)的乘除、浮點(diǎn)運(yùn)算等對(duì)應(yīng)的運(yùn)行庫函數(shù)的堆棧使用量,這種靜態(tài)分析方式對(duì)開發(fā)者的技術(shù)水平、對(duì)產(chǎn)品代碼的理解程度要求非常高,得到的數(shù)據(jù)并不完善,而且這種方式依賴于具體的應(yīng)用和源程序?qū)崿F(xiàn)方式,缺乏通用性。
本文設(shè)計(jì)了一種檢測(cè)嵌入式軟件堆棧溢出及使用量的方案[4],在不影響系統(tǒng)正常運(yùn)行的情況下,在不受堆棧溢出影響的定時(shí)器中斷服務(wù)程序中,周期檢測(cè)堆棧使用量,通過控制LED提示堆棧溢出情況。設(shè)置了堆棧溢出緩沖區(qū),隔離堆棧和全局變量分區(qū),堆棧溢出后部分上下文信息被存放在堆棧溢出緩沖區(qū)中,將最大堆棧使用量和系統(tǒng)發(fā)生堆棧溢出后的堆棧溢出緩沖區(qū)數(shù)據(jù)存入非易失性存儲(chǔ)器。系統(tǒng)在實(shí)際環(huán)境中運(yùn)行一段時(shí)間后,通過查看LED狀態(tài)以及讀取非易失性存儲(chǔ)器中的數(shù)據(jù),便可以判斷堆棧使用情況,通過保存的溢出上下文數(shù)據(jù)可以分析程序異常位置,從而調(diào)整堆棧尺寸或者調(diào)整程序設(shè)計(jì),以提高系統(tǒng)運(yùn)行的穩(wěn)定性。
堆棧的生長方向?yàn)樽陨隙拢聪蛑鳵AM地址減小的方向增長。如果把堆??臻g設(shè)置在RAM的底部,堆棧溢出時(shí)會(huì)訪問不存在的RAM空間,造成代碼跑飛,這時(shí)無法得到溢出時(shí)的上下文數(shù)據(jù),也無法對(duì)后續(xù)的程序修改提供有用信息,所以,在劃分RAM空間時(shí),需要將堆??臻g設(shè)置在RAM空間的頂部。在鏈接文件中,按照從頂部到底部的順序,將RAM空間劃分為堆棧區(qū)、堆棧溢出緩沖區(qū)和全局變量區(qū),根據(jù)需要和MCU RAM空間尺寸,還可能設(shè)置有代碼重定位區(qū)[5]。具體劃分如圖1所示。

圖1 RAM空間劃分圖
在RAM頂部設(shè)置大小為STACK_SIZE的堆棧區(qū),STACK_SIZE根據(jù)實(shí)際應(yīng)用設(shè)置,留有一定的余量,同時(shí)受限于RAM資源,STACK_SIZE不能設(shè)置過大,棧底為堆棧區(qū)的最大地址,記為STACK_BOTTOM,設(shè)置為MCU RAM空間的最大地址;緊鄰堆棧區(qū),設(shè)置尺寸為100字節(jié)大小的堆棧溢出緩沖區(qū);緊鄰堆棧溢出緩沖區(qū),設(shè)置尺寸為APP_RAM_SIZE的全局變量區(qū),MCU的RAM尺寸記為RAM_SIZE。STACK_SIZE、APP_RAM_SIZE和RAM_SIZE的關(guān)系為:RAM_SIZE≥STACK_SIZE+100+APP_RAM_SIZE。
堆棧溢出緩沖區(qū)位于堆棧和全局變量區(qū)之間,可以起到隔離堆棧和全局變量區(qū)的作用,當(dāng)堆棧溢出時(shí),如果溢出深度小于堆棧溢出緩沖區(qū)的尺寸,則不會(huì)影響全局變量,全局變量的變化也不會(huì)改變堆棧內(nèi)容。
在堆棧溢出緩沖區(qū)中定義一個(gè)包含100個(gè)單字節(jié)元素的數(shù)組,記為Stack_overflow_buf[100],堆棧溢出時(shí),該數(shù)組會(huì)存放一部分上下文數(shù)據(jù),為后續(xù)的程序分析提供關(guān)鍵信息。
系統(tǒng)運(yùn)行期間,堆棧操作會(huì)改變堆棧區(qū)數(shù)據(jù),堆棧溢出會(huì)改變Stack_overflow_buf數(shù)據(jù),通過檢查堆棧區(qū)數(shù)據(jù)和Stack_overflow_buf,便可以得知系統(tǒng)對(duì)堆棧的實(shí)際消耗。堆棧溢出可能會(huì)改變程序計(jì)數(shù)器的數(shù)值,如果把堆棧檢測(cè)函數(shù)放在中斷服務(wù)程序之外的其它位置,程序可能無法運(yùn)行堆棧檢測(cè)函數(shù),而即使發(fā)生了堆棧溢出,中斷也會(huì)觸發(fā)MCU進(jìn)入中斷服務(wù)程序,因此,將堆棧檢測(cè)函數(shù)放在定時(shí)器中斷服務(wù)程序中。
MCU上電初始化時(shí),將堆棧指針SP初始化為STACK_BOTTOM,堆棧區(qū)數(shù)據(jù)和數(shù)組Stack_overflow_buf全部初始化為0x55,最大堆棧使用量記為Stack_size_max,初始化為0,然后開啟一個(gè)周期為50 ms的定時(shí)器。在定時(shí)器中斷服務(wù)程序中,讀取堆棧溢出緩沖區(qū)和堆棧區(qū)的數(shù)據(jù),判斷堆棧使用情況。
具體方法為:
讀取次數(shù)記為Read_times,初始化為0,以堆棧溢出緩沖區(qū)初始地址為首地址,以堆棧棧底為末地址,循環(huán)讀取各個(gè)RAM地址上的數(shù)據(jù),如果讀取到的數(shù)據(jù)等于0x55,讀取地址加1,讀取次數(shù)加1,如果讀取到的數(shù)據(jù)不等于0x55,跳出RAM讀取循環(huán)。
根據(jù)當(dāng)前最大堆棧使用量=STACK_SIZE+100-Read_times,如果當(dāng)前最大堆棧使用量大于Stack_size_max,將當(dāng)前最大堆棧使用量賦值給Stack_size_max,存入非易失性存儲(chǔ)器,如果Stack_size_max小于或等于STACK_SIZE,不再進(jìn)行處理,等待下一次定時(shí)中斷。否則,判斷為堆棧溢出,將數(shù)組Stack_overflow_buf的數(shù)據(jù)作為溢出上下文,存入非易失性存儲(chǔ)器;進(jìn)一步根據(jù)Stack_size_max判斷是深度溢出還是淺度溢出,如果Stack_size_max小于(STACK_ZIZE + 100),此時(shí)的堆棧溢出不會(huì)影響全局變量區(qū),不會(huì)造成系統(tǒng)運(yùn)行異常,從而點(diǎn)亮LED進(jìn)行提示。如果Stack_size_max等于(STACK_ZIZE + 100),此時(shí)的堆棧溢出會(huì)影響全局變量區(qū),造成系統(tǒng)運(yùn)行異常,閃爍LED燈進(jìn)行提示。此時(shí),通過專用設(shè)備可以讀取存儲(chǔ)在非易失性存儲(chǔ)器中的最大堆棧使用量和Stack_overflow_buf。根據(jù)Stack_overflow_buf數(shù)據(jù)判斷堆棧溢出位置,修改程序設(shè)計(jì)或者增加堆??臻g的大小。 堆棧溢出檢測(cè)算法流程圖如圖2所示。(說明:定時(shí)器中斷判斷分支的“N”分支與本文內(nèi)容無關(guān),所以未列出。)

圖2 堆棧溢出檢測(cè)算法流程圖

[1] 高源,羅秋鳳.基于DSP28335程序移植方法的研究與實(shí)現(xiàn)[J].電子測(cè)量技術(shù),2013,36(3):84-88.
[2] 北京空間飛行器總體設(shè)計(jì)部.一種適用于多任務(wù)軟件進(jìn)程堆棧使用深度檢測(cè)的方法:中國,201610080939.2 [P].2016-2-14.
[3] 張西超,郭向英.一種用于分析MCS-51目標(biāo)碼堆棧深度的方法[J].空間控制技術(shù)與應(yīng)用,2010,36(2):47-50
[4] 山東省科學(xué)院自動(dòng)化研究所.嵌入式軟件堆棧溢出檢測(cè)方法和裝置:中國,201710997905.5[P].2017-10-11.
[5] 山東省科學(xué)院自動(dòng)化研究所.CAN報(bào)文濾波解析方法、系統(tǒng)及電子控制單元:中國,201710743658.5 [P].2017-8-25.
馬建輝(工程師),研究方向?yàn)榍度胧脚c汽車電子。