董 燕 黃 晨 左萬娟 于 倩
北京控制工程研究所,北京100190
?
基于編譯器優化的嵌入式軟件缺陷分析方法*
董 燕 黃 晨 左萬娟 于 倩
北京控制工程研究所,北京100190

嵌入式編譯器會根據設定的編譯選項和級別,對源代碼進行優化處理,生成可執行目標碼。針對嵌入式編譯器的3種典型優化技術:數據預取技術、指令重排序技術和覆蓋技術,結合具體實例分析問題引入機理,為有效避免編譯器優化引入的軟件缺陷,給出解決方案和建議。在程序開發和測試階段應熟悉編譯器優化準則,嚴格遵守編程約定,進一步提升嵌入式軟件的質量。 關鍵詞 編譯器優化;數據預??;指令重排序;覆蓋技術
隨著計算機技術的發展,嵌入式軟硬件系統已被廣泛應用在軍事、航空、航天、醫療、交通、金融和通訊等關鍵行業領域[1]。由于嵌入式資源有限,且對實時性要求較高,對編譯器也有特定的要求,不同于一般計算機中的編譯器,為提高編譯效率和占用空間,嵌入式編譯器會根據編譯選項的不同對源代碼進行相應級別的優化處理。較好的嵌入式編譯器在編譯和執行高級語言代碼時具有和匯編語言代碼同等的處理效率和執行結果[2]。
嵌入式編譯器優化的目的是為了從空間和時間上提高目標代碼的存儲和執行效率,對程序的功能進行等價轉換,以換取目標代碼的執行效率和性能[2]。也正是由于這點,受限于開發和測試人員對嵌入式編譯器的理解和認識程度,某些問題的引入僅從高級語言進行分析是無法發現的,如原子操作反匯編成多條代碼后引入的中斷沖突問題,編譯器優化引入的指令重排序問題等,據研究統計,星載嵌入式軟件中有相當程度的軟件質量問題都與編譯器相關。
本文介紹了GCC和Keil C51等常見的嵌入式編譯器的翻譯過程,包括優化級別和方法,重點分析了數據預取技術,指令重排序技術,覆蓋技術的編譯器優化方法和原理,以及針對這3種優化技術在編程時的忽視引入的軟件問題給出實例分析和規避方法。
嵌入式編譯器能將高級語言源程序轉換為低級語言目標程序,在保證外部功能不變的情況下,減少目標代碼的執行時間和存儲空間[2]。
1.1 編譯器翻譯過程
編譯器將初始源程序翻譯成最終加載到目標機上的目標執行程序,要經過4個階段:預處理(Pre
processing)、編譯(Compilation)、匯編(Assembly)和鏈接(Linking)[3]。
1) 預處理器:將初始的源程序代碼經過宏處理、文件包含及擴展語言等處理,生成源程序供編譯器處理;
2) 編譯器:將預處理器生成的源程序翻譯成機器可識別的匯編程序;
3) 匯編器:將匯編程序翻譯成機器語言,生成可重定位的機器程序;
4) 鏈接器/加載器:將不同的機器程序目標文件收集起來,構造成1個可執行文件,再將庫函數綁定到目標程序中,由于程序在存儲器中的位置不確定,再通過加載器對其進行重定位,將程序中的相對地址轉換成存儲器的真實地址,并加載運行。

圖1 嵌入式編譯器編譯過程
1.2 典型嵌入式編譯器
1.2.1 GCC編譯器[2]
GCC(GNU C Compiler)是一種可移植的優化編譯器,具有高度可移植性和代碼優化質量。GCC編譯程序采用優化的底層模型,由語言相關的前端、語言無關的后端和用于描述目標機特性信息的目標機描述組成[2]。它能夠支持多處理器平臺的可移植編譯系統,將目標機相關部分從編譯器中分離出來。
GCC編譯器優化選項有5個級別,典型的優化內容如:RTL中間代碼表示機制;目標機描述與定義機制;由目標機的機器描述引導中間代碼生成及優化策略;控制流分析;數據流分析等[4]。
1.2.2 Keil C51編譯器[5]
Keil C51是一種標準C編譯器,它為8051微控制器的軟件開發提供了C語言環境,同時保留了匯編代碼高效、快速的特點。遵循ANSI標準,C51編譯器可以實現對8051系列所有資源的操作,可以支持應用程序的調試。
Keil C51編譯器優化級別有10級,默認為8級優化。典型的優化內容如:對8051系統的內部數據和位地址進行訪問優化;數據覆蓋;寄存器變量優化;CASE/SWITCH語句優化等。
1.3 編譯器優化技術
所謂編譯器優化就是對程序代碼進行等價變換,生成更有效的目標代碼。嵌入式編譯器優化可以在不同階段進行,可以分為局部優化和全局優化,也可以分為與機器有關和無關的優化等[6-8]。
1.3.1 數據預取技術[9]
為解決處理器訪問存儲器速度慢于內部寄存器的問題,引入編譯器數據預取的方式提升處理器讀取存儲器的速度。
采用數據預取的基本思想是通過編譯器插入預取指令,在處理器讀取存儲器之前先把數據放入緩存cache中,通過設置預取更新寄存器、寄存器沖突緩沖區和預取緩沖區等專用的硬件來訪問緩存cache實現預取數據。這種方式既不會增加系統開銷,也不會影響處理器訪問緩存cache的速度[9]。
1.3.2 指令重排序[8]
為充分發揮處理器指令流水線的能力,提高執行過程中的效率與性能,減少內存操作速度慢于處理器運行速度帶來的空置影響,編譯器會按照某種規則將指令執行順序打亂,多條指令根據設定的時間并行執行,寫在后面的代碼可能會比寫在前面的代碼先執行,從而使生成的目標代碼具有更大的并行性。分為靜態和動態的方法:靜態方法是在程序編譯優化或鏈接時實現,通過統計程序中分支的轉移頻率,計算指令的調用頻率,規劃指令的排序方式;動態方法是通過統計程序運行時的動態信息實現,需要借助其他輔助工具,根據動態信息實現指令重排[8]。
圖2 指令重排序執行示意圖
1.3.3 覆蓋技術
覆蓋技術是指一個程序的若干程序段或幾個程序的某些部分共享同一個存儲空間,相互獨立的程序段在內存空間相互覆蓋,實現小容量內存運行較大程序的功能。覆蓋技術通常用于系統程序的內存管理,尤其是在嵌入式系統內存比較小時,能有效提高內存的使用率。
覆蓋技術是C51單片機特有的一種編譯器優化技術。受C51單片機內部堆??臻g限制,局部變量存儲在RAM空間;在編譯鏈接時就完成局部變量地址的分配定位;如果各函數之間沒有直接或間接的調用關系,則其局部變量空間便可覆蓋。
2.1 volatile避免數據預取
volatile關鍵字表示避免進行默認的優化,將1個變量定義為volatile,表示該變量可能會意想不到的被改變,告訴編譯器在每次使用這個變量時都需要重新讀取,而不是使用保存在寄存器里的備份。
實例1:在進行數據采集時,從硬件端口地址連續讀2次數據,第1次讀取高字節,第2次讀取低字節,2次讀取的高低字節拼接成1個數據字。使用的編譯器為Keil C51,優化級別為8級。
在無volatile修飾外部硬件端口地址時,C語言程序經Keil C51優化編譯后,第1次從端口0x2000讀取高字節數據放入累加器A中,第2次直接從累加器A中讀取低字節數據,實際上硬件端口0x2000的2次數據已經發生改變,但是軟件中獲取的高低字節數據都是第1次讀取到的高字節數據,導致獲取的數據錯誤。如果在端口定義時增加volatile修飾符,從對應的反匯編代碼可以清楚看到第2次讀取0x2000端口數據時會從端口重新獲取新的數據放入累加器A中,未經優化的處理保證了每次讀取外部端口數據時都是從所在的端口地址去讀取。使用volatile修飾能夠保證編譯器在讀取外部端口地址時不進行優化,每次讀取時訪問它所在的硬件端口地址,而不是使用寄存器中的備份數據。
基于數據預取的編譯器優化方法,為提高存取速度,訪問寄存器的速度遠快于外部存儲器,一般會做減少訪問外部存儲器的優化處理,將外部存儲器變量緩存到寄存器,下次讀取時直接從寄存器取數。不使用volatile修飾,對外部端口或者寄存器進行訪問操作時,編譯器優化處理后會將其中的內容放到寄存器中,當外部端口的數據發生改變時,讀取到的還是之前寄存器的數據值,并不是最新硬件端口的數據。對于多線程的情況,當變量值或端口數據在本線程里改變時,會同時把新值拷貝到寄存器中,以保持一致,但是如果變量在別的線程被改變或者端口數據值因外部硬件改變時,寄存器的值不會相應改變,導致本線程讀取的值和實際的值不一致。對于外部端口或者寄存器,多線程共享全局變量,定義時使用volatile修飾能夠避免編譯器數據預取。
2.2 volatile避免指令重排序
使用volatile關鍵字可以禁用指令重排序,避免進行默認的優化處理。
實例2:某個多任務多線程代碼,在其中一個線程中向多個端口地址連續輸出一串數據指令。使用的編譯器為GCC編譯器,優化級別為O2。

表2 GCC編譯器-硬件端口缺少volatile
在無volatile修飾時,輸出數據的結果與實際向硬件端口地址寫數據的順序不一致,順序紊亂,規律不易發現。在增加volatile修飾后,實際數據的順序與向硬件地址寫數據的順序一致。
編譯器為提高CPU運行效率進行優化,將寫端口地址的操作順序進行重排序優化操作,可能會使后面的代碼先開始執行并先于前面的代碼執行結束,導致實際端口輸出結果與預期不符。存儲器映射的外部地址和硬件寄存器在定義時需要添加volatile修飾符,禁用編譯器的指令重排序優化。
2.3 重入函數避免變量地址覆蓋
重入函數可被遞歸調用,也可以同時被2個或更多個任務調用,而不會出現數據錯誤。非重入函數不能由超過1個任務所共享,除非能確保函數的互斥(使用信號量,或者在代碼的關鍵部分禁用中斷)。
實例3:在主程序和中斷程序均調用同一個函數模塊,主程序在調用該模塊時進行訪問沖突保護。使用的編譯器為Keil C51,優化級別為8級。
Main函數及中斷函數調用函數關系及局部變量地址分配是在靜態編譯時就已經分配好的,可通過m51文件查看各函數和變量的地址分配情況進行分析。

圖3 Keil C51編譯器-局部變量地址沖突
圖3(a)中Amodal函數為普通函數,主程序和中斷程序中均調用函數Amodal,其中的局部變量A1在靜態編譯鏈接后分配的地址為D:0x000F。為了防止主程序被中斷意外打斷而對地址D:0x000F存在訪問沖突,在調用Amodal前關中斷,完成后開中斷。函數Bmodal中的局部變量B1和Amodal中的A1在靜態編譯時分配了相同的地址,且主程序調用函數Bmodal時未進行中斷保護,對2個局部變量的共用地址D:0x000F仍然存在訪問沖突。
圖3(b)中Amodal函數定義為重入函數,在函數定義的末尾添加關鍵字reentrant,形如void Amodal reentrant。靜態編譯鏈接后重入函數的局部變量A1放入模擬堆棧區,所在地址為i:0x0002,每次調用時會新建一塊模擬堆棧區,將重入函數需要傳遞的參數和局部變量保存在該堆棧中,與之前使用的模擬堆棧區分開,這樣既避免了主函數和中斷中同時調用Amodal的沖突問題,也避免了2個模塊中對于不同的局部變量可能分配同一個地址空間的問題。
Keil C51編譯器在靜態編譯鏈接時為函數的局部變量分配好地址空間,特有的覆蓋技術可能將不存在調用關系的函數內局部變量分配到同一個地址空間。針對Keil C51這種編譯優化技術,主程序和中斷應盡量避免調用同一個函數,如果必須要調用,需要進行保護或者將公共函數定義為重入函數。重入函數可以在任意時刻被打斷,且不會丟失數據,對于每個重入函數,根據存儲器模型在內部或外部存儲器模擬重入堆棧區,每次被調用時使用一塊獨立的堆棧區進行參數傳遞和存放局部變量,如果再次被調用,會另外再開辟一段堆棧區進行參數傳遞和存放局部變量,因此2個堆棧區的局部變量不會出現之前訪問沖突的問題。需要注意的是,單片機的資源非常有效,如果調用次數過多有可能導致堆棧溢出。
嵌入式編譯器為了從空間和時間上提高目標代碼的存儲和執行效率,對程序的功能進行等價轉換。在設定編譯器優化級別時需要合理選擇,若降低優化級別,會導致代碼的執行效率降低;若提高優化級別,可能因為編程處理不當引入程序問題。
高級C語言中的volatile修飾符和重入函數reentrant等都是為了避免編譯器優化(數據預取,指令重排序,變量地址覆蓋等)導致程序缺陷而引入的:
1) volatile的使用準則:外部硬件端口或寄存器;中斷服務程序中的全局變量;多任務或多線程中的全局變量;
2) Keil C51編譯器的局部變量地址在編譯時已經分配完成,可通過查看m51文件或者反匯編幫助分析是否存在變量地址沖突的問題;定義reentrant重入函數能夠避免內存地址訪問沖突,但是需要考慮堆棧溢出的問題。
為避免因編譯器優化引入代碼缺陷,在研制過程中,需要熟悉編譯器優化準則,嚴格遵守編程規范,有效保證嵌入式軟件的質量。
[1] 張惠臻,王超,陳雁. 嵌入式軟件性能分析方法研究與工具設計[J].計算機應用與軟件, 2013,30(10):284-287,321.(Zhang Huizhen, Wang Chao, Chen Yan. On Embedded Software Performance Analysis Methods and Analysis Tool Design[J]. Computer Applications and Software, 2013,30(10):284-287,321.)
[2] 馮鋼. 基于GCC的嵌入式系統編譯器研究與開發[D]. 浙江大學碩士論文,2004.(Feng Gang. Research and Development of the Cross Compiler Based on GCC for Embedded System[D]. Zhejiang University for the Degree of Master, 2004.)
[3] 任坤. DSP編譯器關鍵技術研究[D].浙江大學博士論文,2007.(Ren Kun. Research on Key Techniques for DSP Compiler[D]. Zhejiang University for the Degree of Doctor, 2007.)
[4] 汪小飛,趙克佳,田祖偉. 數據流分析的關鍵技術研究[J].計算機科學, 2005,32(12):91-93.(Wang Xiaofei, Zhao Kejia, Tian Zuwei. Research of Key Techniques in Data Flow Analysis[J]. Computer Science, 2005,32(12):91-93.)
[5] 晁陽. 單片機MCS-51原理及應用開發教程[M].清華大學出版社, 2007.(Chao Yang. MCS-51 Microcontroller Principle and Application Development Guide[M]. Tsinghua University Press, 2007.)
[6] 艾菊梅, 陸鋼, 王強. 嵌入式系統中基于寄存器的編譯器優化技術[J].華東理工學院學報,2007,30(1): 78-80.(Ai Jumei, Lu Gang, Wang Qiang. A Compiler Optimization Technology with Embedded System Based on Register[J]. Journal of East China Institute of Technology, 2007, 30(1): 78-80.)
[7] 張振宇,胡兆權. 嵌入式操作系統編譯器優化技術分析[J].信息與電子工程,2003,1(4):269-271,280.(Zhang Zhenyu,Hu Zhaoquan. The Analysis of Embedded Operating System Compiler Optimizing Technique[J]. Information and Electronic Engineering, 2003,1(4):269-271, 280.)
[8] 張定飛, 趙克佳, 黃春. 指令Cache優化中代碼重排技術研究[J].計算機工程與應用,2006,(7):28-30,68.(Zhang Dingfei, Zhao Kejia, Huang Chun. Research on Code Reordering Technology of I-Cache Optimization[J]. Computer Engineering and Applications, 2006,(7):28-30,68.)
[9] 連瑞琦,張兆慶,喬如良. 指令級并行編譯器的數據預取及優化方法[J].計算機學報,2000,23(6):576-583.(Lian Ruiqi, Zhang Zhaoqing, Qiao Ruliang . A Data Prefetching Method Used in ILP Compilers and Its Optimization[J].Chinese Journal of Computers, 2000,23(6):576-583.)
[10] Todd C M, Monica S L, Anoop G. Design and Evaluation of a Compiler Algorithm for Refetching[C]. Proceedings of the 5th International Conference on Architectural Support for Programming Languages and Operation System, MA, 1992, 62-73.
Analysis of Embedded Software Defection Based on Compiler Optimization
Dong Yan, Huang Chen, Zuo Wanjuan, Yu Qian
Beijing Institute of Control Engineering, Beijing 100190, China
Accordingtocompileroptionandlevel,thesourcecodeisoptimizedtogenerateexecutableobjectcodebyapplyingembeddedcompiler.Thethreetypicalembeddedcompileroptimizationtechniquesknownasdataprefetching,instructionreorderingandcoveringtechniqueareanalyzed.Themechanismisintroducedbycombiningwithspecificexamplestoavoidinvolvingbugsandthenthesolutionandsuggestionareprovided.Compileroptimizationcriteriashouldbeknowncompletelyinthedevelopmentandtestingphase,andstrictcompliancewiththeagreedprogramisobeyed,tofurtherenhancethequalityofembeddedsoftware.
Compileroptimization;Dataprefetching;Instructionreordering;Coveringtechnique
*國家自然科學基金資助項目(91118007)
2016-05-09
董 燕(1972-),女,河南人,碩士,高級工程師,主要研究方向為軟件測試;黃 晨(1983-),女,湖北人,博士,工程師,主要研究方向為軟件測試;左萬娟(1971-),女,四川人,碩士,高級工程師,主要研究方向為軟件測試;于 倩(1982-),女,河北人,碩士,高級工程師,主要研究方向為軟件測試。
TP311.5
A
1006-3242(2016)05-0064-06