實(shí)時(shí)嵌入式系統(tǒng)軟件調(diào)試常見問題分析
內(nèi)存和寄存器的數(shù)據(jù)訛誤本文引用地址:http://m.butianyuan.cn/article/148706.htm
大多數(shù)的嵌入式系統(tǒng)都采用了平面化的內(nèi)存模式,也并沒有內(nèi)存管理單元(MMU),于是沒有硬件支持的內(nèi)存保護(hù)機(jī)制。即使采用能提供這種功能的處理器,也需要由開發(fā)商來實(shí)現(xiàn)對(duì)某些內(nèi)存區(qū)域的保護(hù)。進(jìn)程和線程將對(duì)其它進(jìn)程和線程的內(nèi)存空間有完全的訪問權(quán)限。這可能會(huì)造成下面所描述的、各種類型的內(nèi)存訛誤問題。
堆棧溢出
運(yùn)行時(shí)堆棧是在函數(shù)調(diào)用進(jìn)程中所使用的一種暫存空間,用于存儲(chǔ)局部變量。硬件寄存器指針(SP)將跟蹤堆棧指針的地址。如果你在高級(jí)的語言中編程,如C語音,則編譯器所生成的代碼將使用與C語言運(yùn)行時(shí)間模型相一致的堆棧。運(yùn)行時(shí)間模式定義了變量是如何存儲(chǔ)在堆棧中的以及編譯器將如何使用堆棧。局部的變量被放置在當(dāng)前的堆棧中。下面給出的例子描述了在堆棧上采用的某些關(guān)鍵性的內(nèi)存。
當(dāng)堆棧指針超出了其所指定的邊界時(shí),就會(huì)出現(xiàn)堆棧溢出。這將造成內(nèi)存的訛誤,并最終造成系統(tǒng)的失效。在上述的實(shí)例中,如果總的堆棧內(nèi)存區(qū)不足以容納所有的局部變量,堆棧溢出就會(huì)發(fā)生。
調(diào)試的一個(gè)技巧就是,如果你擔(dān)心溢出,一個(gè)好的做法,就是將堆棧安排在內(nèi)存邊界上,這樣,如果在調(diào)試過程中出現(xiàn)了溢出,則仿真器將觸發(fā)一個(gè)硬件異常提示。
開發(fā)商可以采用的一個(gè)技巧是,如果你擔(dān)心堆棧的溢出,你就應(yīng)當(dāng)考慮把它放在有效的內(nèi)存的邊界上。這樣,當(dāng)堆棧溢出時(shí),設(shè)備將報(bào)告硬件異常,而不是造成其它內(nèi)存空間的訛誤。
在獨(dú)立運(yùn)行的應(yīng)用中,運(yùn)行時(shí)間堆??赡芫鸵呀?jīng)夠用。然而,在使用任何一種實(shí)時(shí)操作系統(tǒng)時(shí),每個(gè)線程和過程都將有自己的堆棧??紤]到性能方面的原因,大多數(shù)嵌入式實(shí)時(shí)操作系統(tǒng)的堆棧尺寸都是事先確定的,無法在運(yùn)行中動(dòng)態(tài)擴(kuò)展。這意味著,如果針對(duì)特定的線程/進(jìn)程所選用的堆棧尺寸不恰當(dāng)?shù)脑?,堆棧溢出就?huì)發(fā)生。
如果應(yīng)用大量使用局部變量(如陣列和大的結(jié)構(gòu)),則將不得不按比例為其分配堆棧的空間。人們可以利用malloc() 來分配內(nèi)存,或者將其設(shè)置為靜態(tài)的全局變量,具體是何種方法,則取決于實(shí)際應(yīng)用。
有些實(shí)時(shí)操作系統(tǒng)可能會(huì)提供調(diào)試功能,例如保護(hù)位,以形成對(duì)堆棧溢出的防護(hù)。這些操作系統(tǒng)要么記錄關(guān)于堆棧溢出的錯(cuò)誤信息,要么提交一個(gè)異常報(bào)告,以便動(dòng)態(tài)地增加堆棧。最起碼當(dāng)前的大多數(shù)實(shí)時(shí)操作系統(tǒng)都能報(bào)告堆棧以及已經(jīng)被線程和進(jìn)程所采用的堆棧的情況。
在任何中斷驅(qū)動(dòng)的系統(tǒng)中,堆棧的分配方式都必須考慮到中斷服務(wù)例程所采用的空間。如果中斷例程的設(shè)計(jì)目標(biāo)是使用當(dāng)前的執(zhí)行對(duì)象棧,則在這種情況下,每一個(gè)線程或進(jìn)程所擁有的最小的堆棧尺寸都應(yīng)大于或者等于執(zhí)行對(duì)象所要求的堆棧尺寸加上所有中斷例程累積起來所需要的最大的堆棧尺寸。
嵌入式系統(tǒng)開發(fā)商必須掌握各種應(yīng)用鏈接庫。例如,第三方的庫可能會(huì)認(rèn)定堆棧上為其提供了空間。
中斷服務(wù)例程代碼編寫時(shí)所出的問題:
在嵌入式系統(tǒng)中,一般情況下,出于性能方面的考慮,中斷服務(wù)例程是以匯編形式編寫的。中斷本質(zhì)上是異步的,在應(yīng)用執(zhí)行中的任何時(shí)刻都有可能出現(xiàn)。匯編層次上的中斷例程最常見的問題,是寄存器的訛誤。在中斷服務(wù)例程中所采用的寄存器所存儲(chǔ)的數(shù)據(jù),在寄存器被使用之前都必須被保存,而在從中斷服務(wù)例程返回之前,這些數(shù)據(jù)將被恢復(fù)。開發(fā)商必須了解狀態(tài)寄存器的情況,而任何一種ALU的操作都會(huì)改變其狀態(tài)。在這種情形中,ISR應(yīng)該保存其狀態(tài)并進(jìn)行恢復(fù),仿佛它是一個(gè)已被使用的寄存器一般。
如果中斷例程是用C語言編寫 的,它們的開發(fā)也是為了使用當(dāng)前的堆棧,則開發(fā)商就應(yīng)該針對(duì)堆棧溢出情況進(jìn)行防護(hù),即每個(gè)線程都應(yīng)該擁有足夠多的堆棧,來滿足中斷或者嵌套的中斷堆棧的要求。最好的做法,就是讓中斷例程的規(guī)模盡可能小,推遲處理過程,交給一個(gè)線程或者優(yōu)先級(jí)較低的中斷。在開發(fā)過程中,開發(fā)商可以在中斷的開始和結(jié)束部分添加診斷功能,對(duì)基礎(chǔ)的架構(gòu)中的寄存器的狀態(tài)進(jìn)行比較。
中斷嵌套可以讓一個(gè)高優(yōu)先級(jí)的中斷搶先于低優(yōu)先級(jí)的中斷例程執(zhí)行。開發(fā)商應(yīng)該考慮到堆棧要求的峰值,并為其分配充足的空間(考慮最差的情況,即你的系統(tǒng)中的每一個(gè)中斷都被一個(gè)優(yōu)先級(jí)更高的中斷所搶先)。
而操作內(nèi)存映射寄存器(MMR)時(shí),人們常常采用在線匯編以改善性能。例如,你在屏蔽中斷時(shí),可能希望直接設(shè)定中斷屏蔽寄存器(IMASK)而不是執(zhí)行RTOS所提供的應(yīng)用軟件編程接口(API)。例如原子增加或減少操作常常是用匯編語言編寫的。在C函數(shù)中,這些宏匯編可能會(huì)被調(diào)用,在這種情況下,編譯器可能不了解在宏匯編中所使用的寄存器。因此這會(huì)導(dǎo)致寄存器的訛誤。有些編譯器具有匯編的擴(kuò)展版,可以將關(guān)于這些函數(shù)的更多的信息傳遞給編譯器,例如已被使用的寄存器、代碼在內(nèi)存中的位置等等。這將使得編譯器可以生成恰當(dāng)?shù)拇a。
有時(shí),某些函數(shù)是以匯編語言編寫的,將被C函數(shù)所調(diào)用。如果匯編代碼并未按照C函數(shù)運(yùn)行時(shí)間調(diào)用規(guī)范來編寫,即按照編譯器所要求的那樣進(jìn)行,則會(huì)導(dǎo)致參數(shù)傳遞(argument passing)無效和訛誤。例如,C函數(shù)運(yùn)行時(shí)間模型可以規(guī)定前兩個(gè)參量必須通過寄存器R0和R1來傳遞,則匯編的實(shí)現(xiàn)方式就必須按照這種語法來編寫。在另一種情況下,運(yùn)行時(shí)間模型可能需要存儲(chǔ)堆棧上的函數(shù)的返回地址。如果匯編的實(shí)現(xiàn)方法并不符合運(yùn)行時(shí)間模型,則它可能會(huì)攪亂某些 寄存器,并帶來系統(tǒng)的故障。如果開發(fā)商使用混合模式的語言來避免這種類型的問題的話,開發(fā)商就必須清楚運(yùn)行時(shí)間模型。
編譯器:
編譯器的優(yōu)化,即使實(shí)現(xiàn)了邏輯上的正確性,有時(shí)也仍然會(huì)造成故障。采用低水平的設(shè)備驅(qū)動(dòng)器時(shí),這一問題特別關(guān)鍵。重排指令是實(shí)現(xiàn)更高性能的常用方法,因?yàn)樘幚砥鞒3VС謫蝹€(gè)周期內(nèi)執(zhí)行多條指令。因此,編譯器將試圖調(diào)度指令,使得所有的指令時(shí)間片都得到充分的利用,即使這意味著在寄存器使用前很久就載入數(shù)據(jù),或者在數(shù)值被計(jì)算完畢后很久,也讓內(nèi)存保持載入的數(shù)據(jù)。請(qǐng)看附圖,其中描述了這種內(nèi)存的移動(dòng)是如何發(fā)生的。
評(píng)論