實時嵌入式系統(tǒng)軟件調(diào)試問題分析_第1頁
實時嵌入式系統(tǒng)軟件調(diào)試問題分析_第2頁
實時嵌入式系統(tǒng)軟件調(diào)試問題分析_第3頁
免費預(yù)覽已結(jié)束,剩余6頁可下載查看

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、實時嵌入式系統(tǒng)軟件調(diào)試問題分析本文將討論常見的調(diào)試問題以及預(yù)防和檢查這些故障問題的一些方法。從歷史 角度上來看,嵌入式應(yīng)用代碼的調(diào)試流程可以分為兩類。第一類調(diào)試流程是回 答“我的代碼現(xiàn)在執(zhí)行到哪里 ?”的問題。當(dāng)開發(fā)商依靠打印語句或者 LED的閃 爍來指示應(yīng)用程序執(zhí)行到某個節(jié)點的調(diào)試方法時,往往就屬于這種情形。如果 開發(fā)工具支持這種調(diào)試方法,可以沿著應(yīng)用應(yīng)當(dāng)程序應(yīng)當(dāng)執(zhí)行的路徑插入斷 點。第二類調(diào)試流程是幫助回答“我看到的這一數(shù)值是從哪里來的 ?”本文將討論常見的調(diào)試問題以及預(yù)防和檢查這些故障問題的一些方法。從歷史角度上來看,嵌入式應(yīng)用代碼的調(diào)試流程可以分為兩類。第一類調(diào)試流 程是回答 “我的代

2、碼現(xiàn)在執(zhí)行到哪里 ?” 的問題。當(dāng)開發(fā)商依靠打印語句或者 LED的閃爍來指示應(yīng)用程序執(zhí)行到某個節(jié)點的調(diào)試方法時,往往就屬于這種情 形。如果開發(fā)工具支持這種調(diào)試方法,可以沿著應(yīng)用應(yīng)當(dāng)程序應(yīng)當(dāng)執(zhí)行的路徑 插入斷點。第二類調(diào)試流程是幫助回答“我看到的這一數(shù)值是從哪里來的?”這一問題。在這種情況下,人們往往依靠寄存器顯示窗口觀察變量信息、處理器 內(nèi)存的內(nèi)容。人們還可以嘗試單步執(zhí)行,并且觀察所有這些數(shù)據(jù)窗口以了解某 個寄存器狀態(tài)何時出現(xiàn)錯誤,內(nèi)存位置何時得到錯誤的數(shù)據(jù),抑或指針何時出 現(xiàn)了誤用。當(dāng)開發(fā)商寫完全部代碼后,如果無需了解網(wǎng)絡(luò)基礎(chǔ)設(shè)施,也沒有操作系統(tǒng)的任 務(wù)調(diào)度需要考慮,那么就可以利用這些調(diào)試方

3、法使一個應(yīng)用程序運行起來。然 而,現(xiàn)在的情況并非如此。嵌入式處理器以超過 600 MHz的速度運行,并且擁 有可支持 Ethernet 和 USB等協(xié)議的嵌入式外設(shè),它們支持功能齊備的操作系 統(tǒng),例如 uClinux ,而且這些操作系統(tǒng)所調(diào)度的各種應(yīng)用程序是由數(shù)千行代碼 構(gòu)成。使用打印語句和利用 LED來調(diào)試是不現(xiàn)實的,因為現(xiàn)在常常有如此之多 的功能在執(zhí)行是不可能的,或者它們會影響標(biāo)準(zhǔn) I/O 口,從而造成處理器性能 大幅度下降。也可能發(fā)生這樣的情況:處理器的工作速度是如此之快,以至于LED的亮滅速度會快到人眼無法察覺。另外現(xiàn)代的嵌入式系統(tǒng)通常支持?jǐn)帱c的設(shè)定,但是伴 隨這些處理器所運行的代碼數(shù)

4、量,使得這種類型的斷點調(diào)試難以駕馭。中斷和 多線程系統(tǒng)在代碼的任何一點上設(shè)置一個斷點,可能都無法指示系統(tǒng)的正確狀 態(tài)。由于斷點設(shè)置在物理內(nèi)存的某個地址上,索引不必了解線程的狀態(tài)。如果 使用寄存器顯示方法,那么局部變量窗口和內(nèi)存窗口都將有助于隔離出所載入 的不恰當(dāng)?shù)牧恐担?,由于這些是靜態(tài)化的工具,不能給出有意義的運行中 的調(diào)試信息,其適用性也常常很有限。實時嵌入式系統(tǒng)軟件最常見的調(diào)試問題可以大致劃分為如下幾類:1. 同步問題2. 內(nèi)存和寄存器訛誤 (corruption)3. 與中斷相關(guān)的問題4. 硬件配置問題5. 異常情況同步問題在任何系統(tǒng)中,只要有多串序線程或者進(jìn)程都在運行,而且是異步共

5、享數(shù)據(jù), 則系統(tǒng)必然存在同步問題。對于共享數(shù)據(jù)的全部操作必須是原子化的,也就是 說,只有在一個線程或者進(jìn)程完成對數(shù)據(jù)的操作后, 其它 的線程才能對數(shù)據(jù)進(jìn) 行操作。以圖 1 為例,線程 A和線程 B 對共享變量“ counter ”進(jìn)行操作, A讓 counter 增加,而 B則讓 counter 減少。下方示出了線程 A 的 counter+ 和線程 B counter 的匯編代碼。假設(shè)線程 B 的優(yōu)先級要高于線程 A,而線程 A目前正在 運行,則線程 B 將被阻止。舉例來說,假設(shè)初始的計數(shù)值是 2,而線程 A 是執(zhí)行線程。則線程 A 讀入計數(shù) 值,并送入一個寄存器,在使其增加一個增量后,再將

6、其寫回 計數(shù)器 變量上。在可搶先的多線程系統(tǒng)中,高優(yōu)先級的線程的執(zhí)行可以搶先于低優(yōu)先級的線 程。例如,假定線程 A執(zhí)行 Reg1 = Reg1+1指令后,一個事件喚醒線程 B。此 時, Reg1儲存量值 3?,F(xiàn)在線程 B被喚醒 ( 正如藍(lán)線所標(biāo)示的那樣 ) ,并讀入計 數(shù)器的量值 2(它尚未被線程 A刷新) 并將其量值減小到 1。正如棕色的線所顯示 的那樣,經(jīng)過一段時間,線程 A 恢復(fù)運行,將 Reg1寫入計數(shù)器中,而該計數(shù)器 的儲存量值為 3。 在這個過程中,線程 B 的減量操作結(jié)果被丟棄。計數(shù)器存儲 的量值變?yōu)?2,即線程 A 進(jìn)行一次增量后,線程 B又進(jìn)行了一次減量操作。被 竄改的鏈接表

7、則是另一個例子。如果數(shù)據(jù)被一個線程和中斷例程共享,則也會 出現(xiàn)上面的問題,因為中斷的執(zhí)行與線程的執(zhí)行之間是異步關(guān)系。同步化方面的問題常常是很難進(jìn)行調(diào)試的,因為它們?nèi)Q于時序,是隨著軟件對數(shù)據(jù)的操作而隨機(jī)出現(xiàn)的。幸運的是,這些問題可以通過恰當(dāng)?shù)乇Wo(hù)任何共 享數(shù)據(jù)來避免。大多數(shù)的實時操作系統(tǒng)可以提供同步化原語。開發(fā)商 可以使用 最適當(dāng)?shù)臋C(jī)制來保護(hù)共享數(shù)據(jù),而不至于影響系統(tǒng)的性能。如果數(shù)據(jù)在多個線 程之間共享,則開發(fā)商將有如下的選擇:a. 關(guān)閉調(diào)度器以便當(dāng)前的線程永遠(yuǎn)不會被其它線程搶先。 (無調(diào)度區(qū) )b. 使用信號兩 (Semaphore)或者互斥信號量 (Mutex) 來保護(hù)共享數(shù)據(jù)。c. 利用

8、關(guān)鍵區(qū)域來進(jìn)行保護(hù),即屏蔽所有的中斷開發(fā)商必須從性能出發(fā)來選擇恰當(dāng)?shù)募夹g(shù)選項。關(guān)閉調(diào)度器,將防止任何一種 環(huán)境的切換,從而使得現(xiàn)在的線程能繼續(xù)執(zhí)行,直到調(diào)度器重新打開為止。這 種方法有一個負(fù)面的影響:它將阻止任何準(zhǔn)備好運行的高優(yōu)先級的線程。這一 現(xiàn)象被稱為優(yōu)先級倒置。將中斷關(guān)閉是最安全的方法,對于執(zhí)行時間短的情形 來說是理想選擇。于是,最差情況的中斷延遲就是所有未發(fā)生中斷的持續(xù)時間 的總和。在硬實時系統(tǒng)中,一般來說,一個中斷功能可以被關(guān)閉的時間存在上 限。調(diào)試的一個小竅門就是,如果共享的數(shù)據(jù)被破壞,則編程者就應(yīng)當(dāng)首先檢查出 任何一種多個線程或者中斷對共享數(shù)據(jù)同時進(jìn)行的操作。如果線程和中斷共享

9、了數(shù)據(jù),那么在線程代碼中必須將中斷關(guān)閉。如果數(shù)據(jù)在多個中斷例程之間共 享的話,則中斷也應(yīng)當(dāng)被關(guān)閉,因為高優(yōu)先級的中斷可以搶先于低優(yōu)先級的中 斷。在多線程的系統(tǒng)中,高優(yōu)先級的線程可以搶在低優(yōu)先級的線程之前執(zhí)行。因 此,如果數(shù)據(jù)在多個線程間共享的話,則必須采用某種恰當(dāng)?shù)臋C(jī)制來保護(hù)被共 享的數(shù)據(jù)。另外一個同步化問題則與線程優(yōu)先級的不恰當(dāng)?shù)姆峙溆嘘P(guān)。應(yīng)當(dāng)確保系統(tǒng)的初 始化線程在引導(dǎo)時間內(nèi)就啟動,并在生成其它的優(yōu)先級更高的線程之前,完成 整個系統(tǒng)的初始化。例如,如果一個用于配置一個器件的低優(yōu)先級現(xiàn)場被一個 使用該設(shè)備的高優(yōu)先級的線程搶先后,配置可能會完成,并可能會造成設(shè)備的 故障。為了避免這種情形,開發(fā)

10、商應(yīng)當(dāng)使用操作系統(tǒng)所支持的信號量或者其它 同步化的原語。內(nèi)存和寄存器的數(shù)據(jù)訛誤大多數(shù)的嵌入式系統(tǒng)都采用了平面化的內(nèi)存模式,也并沒有內(nèi)存管理單元( MM)U,于是沒有硬件支持的內(nèi)存保護(hù)機(jī)制。即使采用能提供這種功能的處理 器,也需要由開發(fā)商來實現(xiàn)對某些內(nèi)存區(qū)域的保護(hù)。進(jìn)程和線程將對其它進(jìn)程 和線程的內(nèi)存空間有完全的訪問權(quán)限。這可能會造成下面所描述的、各種類型 的內(nèi)存訛誤問題。堆棧溢出 運行時堆棧是在函數(shù)調(diào)用進(jìn)程中所使用的一種暫存空間,用于存儲局部變量。 硬件寄存器指針 (SP) 將跟蹤堆棧指針的地址。如果你在高級的語言中編程,如 C語音,則編譯器所生成的代碼將使用與 C語言運行時間模型相一致的堆棧

11、。 運行時間模式定義了變量是如何存儲在堆棧中的以及編譯器將如何使用堆棧。 局部的變量被放置在當(dāng)前的堆棧中。下面給出的例子描述了在堆棧上采用的某 些關(guān)鍵性的內(nèi)存。當(dāng)堆棧指針超出了其所指定的邊界時,就會出現(xiàn)堆棧溢出。這將造成內(nèi)存的訛誤,并最終造成系統(tǒng)的失效。在上述的實例中,如果總的堆棧內(nèi)存區(qū)不足以容 納所有的局部變量,堆棧溢出就會發(fā)生。調(diào)試的一個技巧就是,如果你擔(dān)心溢出,一個好的做法,就是將堆棧安排在內(nèi) 存邊界上,這樣,如果在調(diào)試過程中出現(xiàn)了溢出,則 仿真器 將觸發(fā)一個硬件異 常提示。開發(fā)商可以采用的一個技巧是,如果你擔(dān)心堆棧的溢出,你就應(yīng)當(dāng)考慮把它放 在有效的內(nèi)存的邊界上。這樣,當(dāng)堆棧溢出時,設(shè)

12、備將報告硬件異常,而不是 造成其它內(nèi)存空間的訛誤。在獨立運行的應(yīng)用中,運行時間堆??赡芫鸵呀?jīng)夠用。然而,在使用任何一種 實時操作系統(tǒng)時,每個線程和過程都將有自己的堆棧??紤]到性能方面的原 因,大多數(shù)嵌入式實時操作系統(tǒng)的堆棧尺寸都是事先確定的,無法在運行中動 態(tài)擴(kuò)展。這意味著,如果針對特定的線程 / 進(jìn)程所選用的堆棧尺寸不恰當(dāng)?shù)脑挘?堆棧溢出就會發(fā)生。如果應(yīng)用大量使用局部變量 (如陣列和大的結(jié)構(gòu) ) ,則將不得不按比例為其分配 堆棧的空間。人們可以利用 malloc() 來分配內(nèi)存,或者將其設(shè)置為靜態(tài)的全 局變量,具體是何種方法,則取決于實際應(yīng)用。有些實時操作系統(tǒng)可能會提供調(diào)試功能,例如保護(hù)位,

13、以形成對堆棧溢出的防護(hù)。這些操作系統(tǒng)要么記錄關(guān)于堆棧溢出的錯誤信息,要么提交一個異常報 告,以便動態(tài)地增加堆棧。最起碼當(dāng)前的大多數(shù)實時操作系統(tǒng)都能報告堆棧以 及已經(jīng)被線程和進(jìn)程所采用的堆棧的情況。在任何中斷驅(qū)動的系統(tǒng)中,堆棧的分配方式都必須考慮到中斷服務(wù)例程所采用的空間。如果中斷例程的設(shè)計目標(biāo)是使用當(dāng)前的執(zhí)行對象棧,則在這種情況 下,每一個線程或進(jìn)程所擁有的最小的堆棧尺寸都應(yīng)大于或者等于執(zhí)行對象所 要求的堆棧尺寸加上所有中斷例程累積起來所需要的最大的堆棧尺寸。嵌入式系統(tǒng)開發(fā)商必須掌握各種應(yīng)用鏈接庫。例如,第三方的庫可能會認(rèn)定堆 棧上為其提供了空間。中斷服務(wù)例程代碼編寫時所出的問題:在嵌入式系統(tǒng)

14、中,一般情況下,出于性能方面的考慮,中斷服務(wù)例程是以匯編 形式編寫的。中斷本質(zhì)上是異步的,在應(yīng)用執(zhí)行中的任何時刻都有可能出現(xiàn)。 匯編層次上的中斷例程最常見的問題,是寄存器的訛誤。在中斷服務(wù)例程中所 采用的寄存器所存儲的數(shù)據(jù),在寄存器被使用之前都必須被保存,而在從中斷 服務(wù)例程返回之前,這些數(shù)據(jù)將被恢復(fù)。開發(fā)商必須了解狀態(tài)寄存器的情況, 而任何一種 ALU的操作都會改變其狀態(tài)。在這種情形中, ISR 應(yīng)該保存其狀態(tài) 并進(jìn)行恢復(fù),仿佛它是一個已被使用的寄存器一般。如果中斷例程是用 C 語言編寫 的,它們的開發(fā)也是為了使用當(dāng)前的堆棧,則開 發(fā)商就應(yīng)該針對堆棧溢出情況進(jìn)行防護(hù),即每個線程都應(yīng)該擁有足夠

15、多的堆 棧,來滿足中斷或者嵌套的中斷堆棧的要求。最好的做法,就是讓中斷例程的 規(guī)模盡可能小,推遲處理過程,交給一個線程或者優(yōu)先級較低的中斷。在開發(fā) 過程中,開發(fā)商可以在中斷的開始和結(jié)束部分添加診斷功能,對基礎(chǔ)的架構(gòu)中 的寄存器的狀態(tài)進(jìn)行比較。中斷嵌套可以讓一個高優(yōu)先級的中斷搶先于低優(yōu)先級的中斷例程執(zhí)行。開發(fā)商 應(yīng)該考慮到堆棧要求的峰值,并為其分配充足的空間 ( 考慮最差的情況,即你的 系統(tǒng)中的每一個中斷都被一個優(yōu)先級更高的中斷所搶先 ) 。而操作內(nèi)存映射寄存器 ( MM)R時,人們常常采用在線匯編以改善性能。例如,你 在屏蔽中斷時,可能希望直接設(shè)定中斷屏蔽寄存器 (IMASK)而不是執(zhí)行 RT

16、OS所 提供的應(yīng)用軟件編程接口 (API) 。例如原子增加或減少操作常常是用匯編語言編 寫的。在 C 函數(shù)中,這些宏匯編可能會被調(diào)用,在這種情況下,編譯器可能不 了解在宏匯編中所使用的寄存器。因此這會導(dǎo)致寄存器的訛誤。有些編譯器具 有匯編的擴(kuò)展版,可以將關(guān)于這些函數(shù)的更多的信息傳遞給編譯器,例如已被 使用的寄存器、代碼在內(nèi)存中的位置等等。這將使得編譯器可以生成恰當(dāng)?shù)拇?碼。有時,某些函數(shù)是以匯編語言編寫的,將被 C 函數(shù)所調(diào)用。如果匯編代碼并未 按照 C 函數(shù)運行時間調(diào)用規(guī)范來編寫,即按照編譯器所要求的那樣進(jìn)行,則會 導(dǎo)致參數(shù)傳遞 (argument passing) 無效和訛誤。例如, C

17、函數(shù)運行時間模型可 以規(guī)定前兩個參量必須通過寄存器 R0 和 R1來傳遞,則匯編的實現(xiàn)方式就必須 按照這種語法來編寫。在另一種情況下,運行時間模型可能需要存儲堆棧上的 函數(shù)的返回地址。如果匯編的實現(xiàn)方法并不符合運行時間模型,則它可能會攪 亂某些 寄存器,并帶來系統(tǒng)的故障。如果開發(fā)商使用混合模式的語言來避免這 種類型的問題的話,開發(fā)商就必須清楚運行時間模型。編譯器: 編譯器的優(yōu)化,即使實現(xiàn)了邏輯上的正確性,有時也仍然會造成故障。采用低 水平的設(shè)備驅(qū)動器時,這一問題特別關(guān)鍵。重排指令是實現(xiàn)更高性能的常用方 法,因為處理器常常支持單個周期內(nèi)執(zhí)行多條指令。因此,編譯器將試圖調(diào)度 指令,使得所有的指令時

18、間片都得到充分的利用,即使這意味著在寄存器使用 前很久就載入數(shù)據(jù),或者在數(shù)值被計算完畢后很久,也讓內(nèi)存保持載入的數(shù) 據(jù)。請看附圖,其中描述了這種內(nèi)存的移動是如何發(fā)生的。例如,假設(shè)一個設(shè)備必須在向其發(fā)任何指令前就完成初始化。編譯器可能會移動指令位置,以便改善性能。這可能會造成設(shè)備的故障。如果你的設(shè)備驅(qū)動器 調(diào)試后的版本是可行的,而采用經(jīng)過優(yōu)化的版本時會出現(xiàn)故障,那么你會想查 看設(shè)備的初始化中是否有被移動的指令。你可能不得不采用恰當(dāng)?shù)木幾g器指南 以便指導(dǎo)編譯器不去對每條基本函數(shù)執(zhí)行這樣的優(yōu)化,而不至于損失性能。有時,將代碼從一個架構(gòu)移植到另一種架構(gòu)上,也會帶來某種數(shù)據(jù)類型上的問 題。例如,一種架構(gòu)

19、內(nèi)的整數(shù)可能是 32 bit 的,而其它的架構(gòu)中可能是 48bit 或者 64 bit 的。這可能會導(dǎo)致數(shù)據(jù)的失效或者被截斷。異常所帶來的問題如果異常是與程序的執(zhí)行相同步的,則這往往是一種不當(dāng)?shù)牟僮鞯慕Y(jié)果,例如 零作為除數(shù)所造成的異常。某些異常則是架構(gòu)所特有的。處理異常的最佳方法 是采用缺省的異常處理器,并在出現(xiàn)異常時檢查異常出現(xiàn)的環(huán)境。異常所處的 環(huán)境背景是寄存器量值的集合,包括狀態(tài)寄存器。大多數(shù)架構(gòu)將擁有一個指令 地址寄存器,用來保存造成問題的指令地址。在多數(shù)情況下,要知道一個異常 是如何發(fā)生的并不難,但是,是何種指令路徑可以隔離出這一失效,則是調(diào)試 時棘手的地方。有些架構(gòu)支持跟蹤,即讓你

20、可以看到程序順序執(zhí)行的指令的歷 史。這將給出造成異常的指令順序的某些細(xì)節(jié)信息。內(nèi)存和寄存器訛誤則是造 成異常及程序邏輯錯誤的主要原因。通過細(xì)致檢查造成異常的內(nèi)存指向或者寄 存器,將可以縮小問題的范圍。不能執(zhí)行錯誤檢驗的代碼會造成內(nèi)存的訛誤由于性能方面的原因,開發(fā)商可能會放棄對錯誤的檢查。跳過錯誤檢查將讓內(nèi) 存泄漏等事件無法為人所知,而最終導(dǎo)致內(nèi)存訛誤。例如,如果 malloc() 出現(xiàn) 故障,而由于返回的值并未得到檢驗,則開發(fā)商將開始覆蓋在內(nèi)存的地址 0x0 地址所寫入的量值,在很多嵌入式系統(tǒng)中,這則是一個有效的內(nèi)存區(qū)域。一個 技巧是,讓某些地址 0x0 處的內(nèi)存控制,以便排查出任何一種潛在的訛誤。某 些處理器架構(gòu)就容許應(yīng)用監(jiān)測數(shù)據(jù)總線的活動,從而能抓住相應(yīng)事件。探尋架構(gòu)特有的功能 :大多數(shù)嵌入式處理器都支持某種層次上的調(diào)試功能。內(nèi)置的跟蹤單元就是一種 得到硬件支持的跟蹤機(jī)制。例如, ADI公司的 Blackfin 處理器 系列就具有硬件 跟蹤單元,它可以跟蹤至少 16 路的時序控制器的訪問。當(dāng)硬件跟蹤緩沖器充滿 后,就會產(chǎn)生跟蹤異常。使用這種跟蹤單元后,人們可以構(gòu)建出完整的執(zhí)行路 徑。所提供的跟蹤輸出來自于一種可以免費提供的工具(http

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論