董 燕 黃 晨 左萬娟 于 倩
北京控制工程研究所,北京100190
?
基于編譯器優(yōu)化的嵌入式軟件缺陷分析方法*
董 燕 黃 晨 左萬娟 于 倩
北京控制工程研究所,北京100190
嵌入式編譯器會根據(jù)設定的編譯選項和級別,對源代碼進行優(yōu)化處理,生成可執(zhí)行目標碼。針對嵌入式編譯器的3種典型優(yōu)化技術(shù):數(shù)據(jù)預取技術(shù)、指令重排序技術(shù)和覆蓋技術(shù),結(jié)合具體實例分析問題引入機理,為有效避免編譯器優(yōu)化引入的軟件缺陷,給出解決方案和建議。在程序開發(fā)和測試階段應熟悉編譯器優(yōu)化準則,嚴格遵守編程約定,進一步提升嵌入式軟件的質(zhì)量。 關(guān)鍵詞 編譯器優(yōu)化;數(shù)據(jù)預?。恢噶钪嘏判?;覆蓋技術(shù)
隨著計算機技術(shù)的發(fā)展,嵌入式軟硬件系統(tǒng)已被廣泛應用在軍事、航空、航天、醫(yī)療、交通、金融和通訊等關(guān)鍵行業(yè)領(lǐng)域[1]。由于嵌入式資源有限,且對實時性要求較高,對編譯器也有特定的要求,不同于一般計算機中的編譯器,為提高編譯效率和占用空間,嵌入式編譯器會根據(jù)編譯選項的不同對源代碼進行相應級別的優(yōu)化處理。較好的嵌入式編譯器在編譯和執(zhí)行高級語言代碼時具有和匯編語言代碼同等的處理效率和執(zhí)行結(jié)果[2]。
嵌入式編譯器優(yōu)化的目的是為了從空間和時間上提高目標代碼的存儲和執(zhí)行效率,對程序的功能進行等價轉(zhuǎn)換,以換取目標代碼的執(zhí)行效率和性能[2]。也正是由于這點,受限于開發(fā)和測試人員對嵌入式編譯器的理解和認識程度,某些問題的引入僅從高級語言進行分析是無法發(fā)現(xiàn)的,如原子操作反匯編成多條代碼后引入的中斷沖突問題,編譯器優(yōu)化引入的指令重排序問題等,據(jù)研究統(tǒng)計,星載嵌入式軟件中有相當程度的軟件質(zhì)量問題都與編譯器相關(guān)。
本文介紹了GCC和Keil C51等常見的嵌入式編譯器的翻譯過程,包括優(yōu)化級別和方法,重點分析了數(shù)據(jù)預取技術(shù),指令重排序技術(shù),覆蓋技術(shù)的編譯器優(yōu)化方法和原理,以及針對這3種優(yōu)化技術(shù)在編程時的忽視引入的軟件問題給出實例分析和規(guī)避方法。
嵌入式編譯器能將高級語言源程序轉(zhuǎn)換為低級語言目標程序,在保證外部功能不變的情況下,減少目標代碼的執(zhí)行時間和存儲空間[2]。
1.1 編譯器翻譯過程
編譯器將初始源程序翻譯成最終加載到目標機上的目標執(zhí)行程序,要經(jīng)過4個階段:預處理(Pre
processing)、編譯(Compilation)、匯編(Assembly)和鏈接(Linking)[3]。
1) 預處理器:將初始的源程序代碼經(jīng)過宏處理、文件包含及擴展語言等處理,生成源程序供編譯器處理;
2) 編譯器:將預處理器生成的源程序翻譯成機器可識別的匯編程序;
3) 匯編器:將匯編程序翻譯成機器語言,生成可重定位的機器程序;
4) 鏈接器/加載器:將不同的機器程序目標文件收集起來,構(gòu)造成1個可執(zhí)行文件,再將庫函數(shù)綁定到目標程序中,由于程序在存儲器中的位置不確定,再通過加載器對其進行重定位,將程序中的相對地址轉(zhuǎn)換成存儲器的真實地址,并加載運行。
圖1 嵌入式編譯器編譯過程
1.2 典型嵌入式編譯器
1.2.1 GCC編譯器[2]
GCC(GNU C Compiler)是一種可移植的優(yōu)化編譯器,具有高度可移植性和代碼優(yōu)化質(zhì)量。GCC編譯程序采用優(yōu)化的底層模型,由語言相關(guān)的前端、語言無關(guān)的后端和用于描述目標機特性信息的目標機描述組成[2]。它能夠支持多處理器平臺的可移植編譯系統(tǒng),將目標機相關(guān)部分從編譯器中分離出來。
GCC編譯器優(yōu)化選項有5個級別,典型的優(yōu)化內(nèi)容如:RTL中間代碼表示機制;目標機描述與定義機制;由目標機的機器描述引導中間代碼生成及優(yōu)化策略;控制流分析;數(shù)據(jù)流分析等[4]。
1.2.2 Keil C51編譯器[5]
Keil C51是一種標準C編譯器,它為8051微控制器的軟件開發(fā)提供了C語言環(huán)境,同時保留了匯編代碼高效、快速的特點。遵循ANSI標準,C51編譯器可以實現(xiàn)對8051系列所有資源的操作,可以支持應用程序的調(diào)試。
Keil C51編譯器優(yōu)化級別有10級,默認為8級優(yōu)化。典型的優(yōu)化內(nèi)容如:對8051系統(tǒng)的內(nèi)部數(shù)據(jù)和位地址進行訪問優(yōu)化;數(shù)據(jù)覆蓋;寄存器變量優(yōu)化;CASE/SWITCH語句優(yōu)化等。
1.3 編譯器優(yōu)化技術(shù)
所謂編譯器優(yōu)化就是對程序代碼進行等價變換,生成更有效的目標代碼。嵌入式編譯器優(yōu)化可以在不同階段進行,可以分為局部優(yōu)化和全局優(yōu)化,也可以分為與機器有關(guān)和無關(guān)的優(yōu)化等[6-8]。
1.3.1 數(shù)據(jù)預取技術(shù)[9]
為解決處理器訪問存儲器速度慢于內(nèi)部寄存器的問題,引入編譯器數(shù)據(jù)預取的方式提升處理器讀取存儲器的速度。
采用數(shù)據(jù)預取的基本思想是通過編譯器插入預取指令,在處理器讀取存儲器之前先把數(shù)據(jù)放入緩存cache中,通過設置預取更新寄存器、寄存器沖突緩沖區(qū)和預取緩沖區(qū)等專用的硬件來訪問緩存cache實現(xiàn)預取數(shù)據(jù)。這種方式既不會增加系統(tǒng)開銷,也不會影響處理器訪問緩存cache的速度[9]。
1.3.2 指令重排序[8]
為充分發(fā)揮處理器指令流水線的能力,提高執(zhí)行過程中的效率與性能,減少內(nèi)存操作速度慢于處理器運行速度帶來的空置影響,編譯器會按照某種規(guī)則將指令執(zhí)行順序打亂,多條指令根據(jù)設定的時間并行執(zhí)行,寫在后面的代碼可能會比寫在前面的代碼先執(zhí)行,從而使生成的目標代碼具有更大的并行性。分為靜態(tài)和動態(tài)的方法:靜態(tài)方法是在程序編譯優(yōu)化或鏈接時實現(xiàn),通過統(tǒng)計程序中分支的轉(zhuǎn)移頻率,計算指令的調(diào)用頻率,規(guī)劃指令的排序方式;動態(tài)方法是通過統(tǒng)計程序運行時的動態(tài)信息實現(xiàn),需要借助其他輔助工具,根據(jù)動態(tài)信息實現(xiàn)指令重排[8]。
圖2 指令重排序執(zhí)行示意圖
1.3.3 覆蓋技術(shù)
覆蓋技術(shù)是指一個程序的若干程序段或幾個程序的某些部分共享同一個存儲空間,相互獨立的程序段在內(nèi)存空間相互覆蓋,實現(xiàn)小容量內(nèi)存運行較大程序的功能。覆蓋技術(shù)通常用于系統(tǒng)程序的內(nèi)存管理,尤其是在嵌入式系統(tǒng)內(nèi)存比較小時,能有效提高內(nèi)存的使用率。
覆蓋技術(shù)是C51單片機特有的一種編譯器優(yōu)化技術(shù)。受C51單片機內(nèi)部堆??臻g限制,局部變量存儲在RAM空間;在編譯鏈接時就完成局部變量地址的分配定位;如果各函數(shù)之間沒有直接或間接的調(diào)用關(guān)系,則其局部變量空間便可覆蓋。
2.1 volatile避免數(shù)據(jù)預取
volatile關(guān)鍵字表示避免進行默認的優(yōu)化,將1個變量定義為volatile,表示該變量可能會意想不到的被改變,告訴編譯器在每次使用這個變量時都需要重新讀取,而不是使用保存在寄存器里的備份。
實例1:在進行數(shù)據(jù)采集時,從硬件端口地址連續(xù)讀2次數(shù)據(jù),第1次讀取高字節(jié),第2次讀取低字節(jié),2次讀取的高低字節(jié)拼接成1個數(shù)據(jù)字。使用的編譯器為Keil C51,優(yōu)化級別為8級。
在無volatile修飾外部硬件端口地址時,C語言程序經(jīng)Keil C51優(yōu)化編譯后,第1次從端口0x2000讀取高字節(jié)數(shù)據(jù)放入累加器A中,第2次直接從累加器A中讀取低字節(jié)數(shù)據(jù),實際上硬件端口0x2000的2次數(shù)據(jù)已經(jīng)發(fā)生改變,但是軟件中獲取的高低字節(jié)數(shù)據(jù)都是第1次讀取到的高字節(jié)數(shù)據(jù),導致獲取的數(shù)據(jù)錯誤。如果在端口定義時增加volatile修飾符,從對應的反匯編代碼可以清楚看到第2次讀取0x2000端口數(shù)據(jù)時會從端口重新獲取新的數(shù)據(jù)放入累加器A中,未經(jīng)優(yōu)化的處理保證了每次讀取外部端口數(shù)據(jù)時都是從所在的端口地址去讀取。使用volatile修飾能夠保證編譯器在讀取外部端口地址時不進行優(yōu)化,每次讀取時訪問它所在的硬件端口地址,而不是使用寄存器中的備份數(shù)據(jù)。
基于數(shù)據(jù)預取的編譯器優(yōu)化方法,為提高存取速度,訪問寄存器的速度遠快于外部存儲器,一般會做減少訪問外部存儲器的優(yōu)化處理,將外部存儲器變量緩存到寄存器,下次讀取時直接從寄存器取數(shù)。不使用volatile修飾,對外部端口或者寄存器進行訪問操作時,編譯器優(yōu)化處理后會將其中的內(nèi)容放到寄存器中,當外部端口的數(shù)據(jù)發(fā)生改變時,讀取到的還是之前寄存器的數(shù)據(jù)值,并不是最新硬件端口的數(shù)據(jù)。對于多線程的情況,當變量值或端口數(shù)據(jù)在本線程里改變時,會同時把新值拷貝到寄存器中,以保持一致,但是如果變量在別的線程被改變或者端口數(shù)據(jù)值因外部硬件改變時,寄存器的值不會相應改變,導致本線程讀取的值和實際的值不一致。對于外部端口或者寄存器,多線程共享全局變量,定義時使用volatile修飾能夠避免編譯器數(shù)據(jù)預取。
2.2 volatile避免指令重排序
使用volatile關(guān)鍵字可以禁用指令重排序,避免進行默認的優(yōu)化處理。
實例2:某個多任務多線程代碼,在其中一個線程中向多個端口地址連續(xù)輸出一串數(shù)據(jù)指令。使用的編譯器為GCC編譯器,優(yōu)化級別為O2。
表2 GCC編譯器-硬件端口缺少volatile
在無volatile修飾時,輸出數(shù)據(jù)的結(jié)果與實際向硬件端口地址寫數(shù)據(jù)的順序不一致,順序紊亂,規(guī)律不易發(fā)現(xiàn)。在增加volatile修飾后,實際數(shù)據(jù)的順序與向硬件地址寫數(shù)據(jù)的順序一致。
編譯器為提高CPU運行效率進行優(yōu)化,將寫端口地址的操作順序進行重排序優(yōu)化操作,可能會使后面的代碼先開始執(zhí)行并先于前面的代碼執(zhí)行結(jié)束,導致實際端口輸出結(jié)果與預期不符。存儲器映射的外部地址和硬件寄存器在定義時需要添加volatile修飾符,禁用編譯器的指令重排序優(yōu)化。
2.3 重入函數(shù)避免變量地址覆蓋
重入函數(shù)可被遞歸調(diào)用,也可以同時被2個或更多個任務調(diào)用,而不會出現(xiàn)數(shù)據(jù)錯誤。非重入函數(shù)不能由超過1個任務所共享,除非能確保函數(shù)的互斥(使用信號量,或者在代碼的關(guān)鍵部分禁用中斷)。
實例3:在主程序和中斷程序均調(diào)用同一個函數(shù)模塊,主程序在調(diào)用該模塊時進行訪問沖突保護。使用的編譯器為Keil C51,優(yōu)化級別為8級。
Main函數(shù)及中斷函數(shù)調(diào)用函數(shù)關(guān)系及局部變量地址分配是在靜態(tài)編譯時就已經(jīng)分配好的,可通過m51文件查看各函數(shù)和變量的地址分配情況進行分析。
圖3 Keil C51編譯器-局部變量地址沖突
圖3(a)中Amodal函數(shù)為普通函數(shù),主程序和中斷程序中均調(diào)用函數(shù)Amodal,其中的局部變量A1在靜態(tài)編譯鏈接后分配的地址為D:0x000F。為了防止主程序被中斷意外打斷而對地址D:0x000F存在訪問沖突,在調(diào)用Amodal前關(guān)中斷,完成后開中斷。函數(shù)Bmodal中的局部變量B1和Amodal中的A1在靜態(tài)編譯時分配了相同的地址,且主程序調(diào)用函數(shù)Bmodal時未進行中斷保護,對2個局部變量的共用地址D:0x000F仍然存在訪問沖突。
圖3(b)中Amodal函數(shù)定義為重入函數(shù),在函數(shù)定義的末尾添加關(guān)鍵字reentrant,形如void Amodal reentrant。靜態(tài)編譯鏈接后重入函數(shù)的局部變量A1放入模擬堆棧區(qū),所在地址為i:0x0002,每次調(diào)用時會新建一塊模擬堆棧區(qū),將重入函數(shù)需要傳遞的參數(shù)和局部變量保存在該堆棧中,與之前使用的模擬堆棧區(qū)分開,這樣既避免了主函數(shù)和中斷中同時調(diào)用Amodal的沖突問題,也避免了2個模塊中對于不同的局部變量可能分配同一個地址空間的問題。
Keil C51編譯器在靜態(tài)編譯鏈接時為函數(shù)的局部變量分配好地址空間,特有的覆蓋技術(shù)可能將不存在調(diào)用關(guān)系的函數(shù)內(nèi)局部變量分配到同一個地址空間。針對Keil C51這種編譯優(yōu)化技術(shù),主程序和中斷應盡量避免調(diào)用同一個函數(shù),如果必須要調(diào)用,需要進行保護或者將公共函數(shù)定義為重入函數(shù)。重入函數(shù)可以在任意時刻被打斷,且不會丟失數(shù)據(jù),對于每個重入函數(shù),根據(jù)存儲器模型在內(nèi)部或外部存儲器模擬重入堆棧區(qū),每次被調(diào)用時使用一塊獨立的堆棧區(qū)進行參數(shù)傳遞和存放局部變量,如果再次被調(diào)用,會另外再開辟一段堆棧區(qū)進行參數(shù)傳遞和存放局部變量,因此2個堆棧區(qū)的局部變量不會出現(xiàn)之前訪問沖突的問題。需要注意的是,單片機的資源非常有效,如果調(diào)用次數(shù)過多有可能導致堆棧溢出。
嵌入式編譯器為了從空間和時間上提高目標代碼的存儲和執(zhí)行效率,對程序的功能進行等價轉(zhuǎn)換。在設定編譯器優(yōu)化級別時需要合理選擇,若降低優(yōu)化級別,會導致代碼的執(zhí)行效率降低;若提高優(yōu)化級別,可能因為編程處理不當引入程序問題。
高級C語言中的volatile修飾符和重入函數(shù)reentrant等都是為了避免編譯器優(yōu)化(數(shù)據(jù)預取,指令重排序,變量地址覆蓋等)導致程序缺陷而引入的:
1) volatile的使用準則:外部硬件端口或寄存器;中斷服務程序中的全局變量;多任務或多線程中的全局變量;
2) Keil C51編譯器的局部變量地址在編譯時已經(jīng)分配完成,可通過查看m51文件或者反匯編幫助分析是否存在變量地址沖突的問題;定義reentrant重入函數(shù)能夠避免內(nèi)存地址訪問沖突,但是需要考慮堆棧溢出的問題。
為避免因編譯器優(yōu)化引入代碼缺陷,在研制過程中,需要熟悉編譯器優(yōu)化準則,嚴格遵守編程規(guī)范,有效保證嵌入式軟件的質(zhì)量。
[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的嵌入式系統(tǒng)編譯器研究與開發(fā)[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編譯器關(guān)鍵技術(shù)研究[D].浙江大學博士論文,2007.(Ren Kun. Research on Key Techniques for DSP Compiler[D]. Zhejiang University for the Degree of Doctor, 2007.)
[4] 汪小飛,趙克佳,田祖?zhèn)? 數(shù)據(jù)流分析的關(guān)鍵技術(shù)研究[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原理及應用開發(fā)教程[M].清華大學出版社, 2007.(Chao Yang. MCS-51 Microcontroller Principle and Application Development Guide[M]. Tsinghua University Press, 2007.)
[6] 艾菊梅, 陸鋼, 王強. 嵌入式系統(tǒng)中基于寄存器的編譯器優(yōu)化技術(shù)[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] 張振宇,胡兆權(quán). 嵌入式操作系統(tǒng)編譯器優(yōu)化技術(shù)分析[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優(yōu)化中代碼重排技術(shù)研究[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] 連瑞琦,張兆慶,喬如良. 指令級并行編譯器的數(shù)據(jù)預取及優(yōu)化方法[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