趙正旭,梅成芳,張 強(qiáng)
(石家莊鐵道大學(xué) 復(fù)雜網(wǎng)絡(luò)與可視化研究所,河北 石家莊 050043)
嵌入式軟件廣泛應(yīng)用于航天、通信、軌道交通等領(lǐng)域,其自身有著實(shí)時(shí)性、專用性、與硬件緊密關(guān)聯(lián)等特點(diǎn),因此嵌入式軟件對(duì)軟件可靠性、安全性要求極高。靜態(tài)測(cè)試是指不運(yùn)行程序源代碼,通過(guò)人工或者借助工具來(lái)發(fā)現(xiàn)源代碼中隱藏的空指針、變量未初始化、數(shù)組越界、內(nèi)存泄漏、嵌套錯(cuò)誤等問題[1]。人工檢測(cè)代碼缺陷受測(cè)試人員經(jīng)驗(yàn)的影響,且難度高、工作強(qiáng)度大,一般采取工具檢測(cè)和人工干預(yù)的方式來(lái)相互配合提高工作效率。針對(duì)嵌入式軟件自身特點(diǎn),文中提出一種在嵌入式軟件初期編碼階段進(jìn)行靜態(tài)測(cè)試的方法,以保障高質(zhì)量代碼編寫以及嵌入式軟件的安全性、可靠性。
國(guó)內(nèi)普遍認(rèn)同的嵌入式系統(tǒng)定義為:以應(yīng)用為中心,以計(jì)算機(jī)技術(shù)為基礎(chǔ),軟硬件可裁剪,適應(yīng)應(yīng)用系統(tǒng)對(duì)功能、可靠性、成本、體積、功耗等嚴(yán)格要求的專用計(jì)算機(jī)系統(tǒng)[2]。
嵌入式軟件具有實(shí)時(shí)性。例如航天系統(tǒng)中的嵌入式軟件要求軟件能夠?qū)崿F(xiàn)實(shí)時(shí)運(yùn)行,在特定時(shí)間內(nèi)需完成接收、處理、發(fā)送信息等一系列任務(wù),對(duì)處理時(shí)間、處理任務(wù)的時(shí)序性,軟件運(yùn)行速度以及避免運(yùn)行時(shí)內(nèi)存遺漏都有嚴(yán)格要求[3]。
嵌入式軟件要求高可靠性。嵌入式軟件一般有別于通用軟件適用環(huán)境,其工作環(huán)境常常伴隨著強(qiáng)輻射、強(qiáng)磁場(chǎng)、真空、遠(yuǎn)距離操控等復(fù)雜問題。
嵌入式軟件具有專用性。嵌入式軟件的開發(fā)一般是針對(duì)特定用途、特定場(chǎng)景,具有很強(qiáng)的專用性。
嵌入式軟件與硬件關(guān)系密切。嵌入式系統(tǒng)由軟件和硬件組成,其中軟件部分正是為硬件部分設(shè)計(jì)的。嵌入式軟件的編寫還涉及到嵌入式系統(tǒng)的外圍設(shè)備,例如通信接口、輸入/輸出設(shè)備、外設(shè)接口等。
由于嵌入式系統(tǒng)自身具有軟硬件結(jié)合的特點(diǎn),其軟件的代碼編寫過(guò)程中會(huì)涉及到對(duì)硬件的操作。由于C語(yǔ)言本身可以對(duì)硬件操作,又具有易學(xué)、靈活、可移植、高效率等特點(diǎn),因此日漸成為嵌入式軟件的主要編程語(yǔ)言。C++是C語(yǔ)言的增強(qiáng)版,也在一定程度上繼承了C語(yǔ)言的缺陷,C/C++語(yǔ)言其本身都屬于弱類型語(yǔ)言,允許變量類型隱式轉(zhuǎn)換,在開發(fā)效率高的同時(shí)可靠性較弱。而且C/C++編譯器不進(jìn)行強(qiáng)制類型檢查,也不做任何邊界檢查,很容易存在代碼安全隱患[4]。
大多數(shù)靜態(tài)測(cè)試工具都支持靜態(tài)測(cè)試規(guī)則檢查。對(duì)于嵌入式軟件測(cè)試規(guī)則的制定,國(guó)內(nèi)外已經(jīng)有很多成熟的案例。汽車工業(yè)軟件可靠性聯(lián)會(huì)(The Motor Industry Software Reliability Association,MISRA)在1998年發(fā)布了汽車行業(yè)著名的安全性C語(yǔ)言編程規(guī)范MISRA-C:1998,旨在協(xié)助開發(fā)安全、高可靠性的嵌入式軟件。隨后又出版了MISRA-C:2004,該版本把適用領(lǐng)域從汽車領(lǐng)域擴(kuò)展到所有高安全性系統(tǒng)。MISRA系列編碼規(guī)則通過(guò)不斷完善和更新,之后相繼出版了MISRA-C++:2008、MISRA-C:2012[5]。類似的規(guī)則還有由航天科工集團(tuán)出版的《航天型號(hào)軟件C語(yǔ)言安全子集》(簡(jiǎn)稱GJB5369-2005)[6],由總裝電子信息基礎(chǔ)部出版的《C/C++語(yǔ)言編程安全子集》(簡(jiǎn)稱GJB8114-2013)和《軍用軟件安全性設(shè)計(jì)指南》(簡(jiǎn)稱GJB/Z 102A-2012)等。這些規(guī)則大多分為強(qiáng)制執(zhí)行和推薦執(zhí)行兩類,需要根據(jù)測(cè)試的嵌入式軟件靈活選擇。
嵌入式軟件靜態(tài)測(cè)試和通用軟件靜態(tài)測(cè)試的方法大致相同,區(qū)別之處在于針對(duì)其軟件自身特點(diǎn)有針對(duì)性地進(jìn)行檢測(cè)[7]。例如:關(guān)注運(yùn)行時(shí)檢測(cè)、內(nèi)存遺漏問題;針對(duì)嵌入式軟件的專用性有側(cè)重點(diǎn)地去篩選檢測(cè)項(xiàng);關(guān)注嵌入式軟件主流編程語(yǔ)言C/C++的自身缺陷;適當(dāng)采用成熟的靜態(tài)測(cè)試規(guī)則。盡可能地在代碼編寫過(guò)程中解決軟件中隱藏的安全隱患,進(jìn)而確保嵌入式軟件在特定硬件環(huán)境下安全可靠的運(yùn)行。
由于C/C++語(yǔ)言本身風(fēng)格自由易出錯(cuò),程序員在編寫過(guò)程中要格外注意防止安全漏洞的產(chǎn)生。通過(guò)靜態(tài)測(cè)試工具輔助程序員排除緩沖區(qū)溢出、數(shù)組越界、指針引用錯(cuò)誤、內(nèi)存泄漏等安全隱患,消除代碼缺陷的同時(shí)保障程序的安全。
緩沖區(qū)溢出指程序接收的數(shù)據(jù)長(zhǎng)度超出了緩沖區(qū)的容量,而程序中沒有對(duì)數(shù)據(jù)長(zhǎng)度的限制操作,導(dǎo)致部分?jǐn)?shù)據(jù)覆蓋了臨近內(nèi)存段的數(shù)據(jù)。而被覆蓋的數(shù)據(jù)可能是與程序運(yùn)行相關(guān)的重要數(shù)據(jù),也有可能是某一個(gè)操作指令的指針,這些都可能導(dǎo)致程序運(yùn)行失敗、拒絕服務(wù)、系統(tǒng)異常等安全隱患。最為嚴(yán)重的是導(dǎo)致堆棧溢出,攻擊者可以實(shí)施攻擊,改變程序返回地址。這個(gè)被改變的地址有可能就指向任意惡意代碼,使攻擊者可以對(duì)系統(tǒng)進(jìn)行非法操作[8]。因?yàn)榫彌_區(qū)溢出漏洞普遍存在以及緩沖區(qū)攻擊易于實(shí)現(xiàn),緩沖區(qū)溢出攻擊已成為遠(yuǎn)程網(wǎng)絡(luò)攻擊的主要方式。在C/C++語(yǔ)言中可能導(dǎo)致緩沖區(qū)溢出的語(yǔ)句有:sprintf()、vsprintf()、scanf()、gets()、strcpy()、strcat()、strcmp()等[9]。
數(shù)組占用一段連續(xù)的內(nèi)存空間,通過(guò)數(shù)組的下標(biāo)來(lái)訪問某一個(gè)數(shù)組成員。例如數(shù)組int a[len]的合法訪問范圍是0到len-1,當(dāng)數(shù)組索引不在其正常范圍內(nèi)時(shí),程序運(yùn)行時(shí)就很可能出錯(cuò)[10]。因?yàn)镃/C++的編譯器在編譯時(shí)遇到數(shù)組訪問越界代碼是不會(huì)提示錯(cuò)誤的,但是當(dāng)運(yùn)行時(shí)可能會(huì)改變其他內(nèi)存的數(shù)據(jù),此種錯(cuò)誤極有可能是災(zāi)難性的,而且調(diào)試時(shí)不易查找。
當(dāng)聲明一個(gè)指針變量并沒有對(duì)它實(shí)例化時(shí),是不指向任何合法內(nèi)存的。發(fā)生空指針引用異常導(dǎo)致程序運(yùn)行時(shí)拋出Null Point Exception的情況可能是:調(diào)用空指針對(duì)象的本身、屬性或者方法;獲取空指針對(duì)象的長(zhǎng)度;試圖修改空指針對(duì)象的數(shù)據(jù)成員。
內(nèi)存泄漏指程序沒有釋放已經(jīng)不再使用的內(nèi)存,使程序?qū)@些未釋放的內(nèi)存無(wú)法控制,導(dǎo)致程序可用內(nèi)存空間減少,直接導(dǎo)致性能不良。更為嚴(yán)重的是程序運(yùn)行時(shí)過(guò)多的內(nèi)存泄漏可能會(huì)導(dǎo)致程序運(yùn)行停止。短暫運(yùn)行過(guò)程中的內(nèi)存泄漏對(duì)計(jì)算機(jī)影響不大,因?yàn)閷?dǎo)致內(nèi)存泄漏的進(jìn)程終止時(shí),這個(gè)進(jìn)程使用的內(nèi)存會(huì)被釋放。前提是系統(tǒng)有足夠的內(nèi)存空間,比如PC機(jī)。但是嵌入式系統(tǒng)硬件不同于計(jì)算機(jī)系統(tǒng)硬件擁有大容量的硬件存儲(chǔ)器,嵌入式系統(tǒng)使用像EPROM和EEPROM之類的閃存。嵌入式軟件設(shè)計(jì)中存在的內(nèi)存泄漏有可能會(huì)導(dǎo)致系統(tǒng)崩潰等嚴(yán)重后果。所以對(duì)嵌入式軟件中的內(nèi)存泄漏的檢測(cè)是必不可少的。但是由于內(nèi)存泄漏難以定位查找,此時(shí)靜態(tài)測(cè)試工具就提供了很大的便利。導(dǎo)致內(nèi)存泄漏的情況有:堆里用new創(chuàng)建了對(duì)象,但是沒有手動(dòng)delete釋放(編譯器不會(huì)自動(dòng)釋放堆內(nèi)new分配的內(nèi)存);new和delete沒有一一對(duì)應(yīng),有遺漏的內(nèi)存空間未得到釋放或者多次釋放同一個(gè)內(nèi)存;不正確釋放二維數(shù)組;不正確釋放指向?qū)ο蟮闹羔様?shù)組,應(yīng)該是先釋放對(duì)象再釋放指針;釋放和分配函數(shù)不對(duì)應(yīng),例如malloc/new/new[]分別對(duì)應(yīng)free/delete/delete[];釋放未被分配的內(nèi)存等。
成熟的測(cè)試工具有很多,例如Fortify SCA、Coverity、C++Test、PC-Lint、CppCheck等。這些靜態(tài)測(cè)試工具都有其測(cè)試的側(cè)重點(diǎn),它們的主要功能特點(diǎn)如表1所示。
表1 五種工具的主要功能特點(diǎn)
C++Test是Parasoft公司的基于C/C++的測(cè)試工具,涵蓋靜態(tài)測(cè)試、單元測(cè)試、回歸測(cè)試。C++Test靜態(tài)測(cè)試部分包含了很多典型行業(yè)規(guī)范,有BugDetective、Effective C++、GJB5369、MISRA C 2004、MISRA C++ 2008等。C++Test還提供RuleWizard用戶自定義規(guī)則功能。C++Test的靜態(tài)測(cè)試具有基于數(shù)據(jù)流和基于模式的兩種分析技術(shù)。基于數(shù)據(jù)流的靜態(tài)測(cè)試分析技術(shù)也被稱為BugDetective。BugDetective模擬和識(shí)別代碼中的復(fù)雜路徑,搜索并定義可疑點(diǎn),進(jìn)而暴露可能觸發(fā)運(yùn)行時(shí)缺陷的路徑。特別是針對(duì)遺留代碼庫(kù)和嵌入式代碼的運(yùn)行時(shí)檢測(cè)效果較差的情況,BugDetective可以無(wú)需測(cè)試用例和執(zhí)行代碼就發(fā)現(xiàn)除數(shù)為零、內(nèi)存和資源泄漏、空指針引用等運(yùn)行時(shí)缺陷,而這些軟件錯(cuò)誤很多是基于模式的靜態(tài)分析技術(shù)不易檢測(cè)到的[11]。C++Test提供的可視化、易操作的靜態(tài)測(cè)試完全可以勝任嵌入式軟件的靜態(tài)測(cè)試工作。
Fortify SCA是Fortify 360系列產(chǎn)品中的靜態(tài)代碼分析器,旨在檢測(cè)源代碼中存在的安全漏洞。Fortify SCA有全面的安全編碼規(guī)則,提供最新的安全編碼規(guī)則包下載來(lái)應(yīng)對(duì)新的安全漏洞,用戶還可以自定義安全規(guī)則。具有數(shù)據(jù)流、語(yǔ)義、結(jié)構(gòu)、控制流、配置流等分析引擎[12]。支持C/C++、Java、JavaScript、.NET、PHP等20多種語(yǔ)言,支持Windows、Linux、Unix等操作平臺(tái)和多種編譯器,以及提供Eclipse、Visual Studio等IDE的插件。該工具分析源代碼分為三個(gè)步驟:轉(zhuǎn)換、掃描與分析、校驗(yàn)。轉(zhuǎn)換:源代碼關(guān)聯(lián)一個(gè)Build ID,創(chuàng)建中間文件。掃描與分析:掃描中間文件,分析源代碼,將檢測(cè)結(jié)果寫入FPR文件。校驗(yàn):驗(yàn)證所有源文件依據(jù)正確的規(guī)則包被掃描。但是其本身是商業(yè)軟件,價(jià)格昂貴。
Fortify SCA提供掃描方式:IDE插件掃描、命令行、Audit Workbench(Fortify SCA圖形用戶界面)掃描。例如想要掃描一個(gè)Visual Studio項(xiàng)目FreeBird,此時(shí)就可以用下列語(yǔ)句:
sourceanalyzer -b FreeBird devenv FreeBird.sln /REBUILD
sourceanalyzer -b FreeBird -scan -f FreeBird.fpr
FreeBird是項(xiàng)目名,devenv表示visual studio的可執(zhí)行程序,/REBUILD是devenv命令參數(shù)。
PC-Lint是GIMPEL SOFTWARE公司開發(fā)的C/C++軟件代碼靜態(tài)分析工具,側(cè)重于代碼的邏輯分析[13]。PC-Lint的歷史可以追溯到1979年貝爾實(shí)驗(yàn)室開發(fā)的靜態(tài)代碼分析工具Lint(早期最著名的C語(yǔ)言工具之一)。而Lint起初是UNIX系統(tǒng)工具,后來(lái)衍生了Linux系統(tǒng)發(fā)行版本Splint和Windows系統(tǒng)發(fā)行版本PC-Lint。PC-Lint支持大多數(shù)流行編譯環(huán)境和編譯器。PC-Lint采用命令行和開發(fā)環(huán)境插件集成兩種方式。PC-Lint的基本命令行的形式為:
c:lintlint-nt.exe -i"c:lint" -u std.lnt "d:FreeBird *.cpp"
lint-nt.exe是PC-Lint在Windows下的可執(zhí)行程序,-i"c:lint"表示查找到配置文件*.lnt的路徑,-u std.lnt指明使用的那些配置文件,"d:FreeBird *.cpp "表示要測(cè)試的目標(biāo)文件。
PC-Lint檢測(cè)出來(lái)的錯(cuò)誤列表如表2所示。
表2 PC-Lint警告列表
每個(gè)編號(hào)對(duì)應(yīng)信息可以通過(guò)PC-Lint安裝包中msg.txt文檔進(jìn)行查閱。語(yǔ)句錯(cuò)誤:普通編譯器也能檢測(cè)出的語(yǔ)法錯(cuò)誤,必須改正;內(nèi)部錯(cuò)誤:程序內(nèi)部不應(yīng)該出現(xiàn)的錯(cuò)誤,必須改正;致命錯(cuò)誤:超過(guò)某個(gè)限制而導(dǎo)致的系統(tǒng)致命錯(cuò)誤,必須改正;警告:指出程序中很可能存在的錯(cuò)誤;消息:可能存在的錯(cuò)誤,也有可能是個(gè)人編碼風(fēng)格導(dǎo)致被檢測(cè)(其本身屬于合法編程);可選部分:不會(huì)被默認(rèn)檢查,需要在選項(xiàng)中指定。
CppCheck是針對(duì)C/C++的檢測(cè)非語(yǔ)法錯(cuò)誤的開源靜態(tài)檢測(cè)工具。一般作為編譯器或者其他靜態(tài)檢測(cè)工具的補(bǔ)充。支持命令行、插件版、獨(dú)立版三種檢測(cè)代碼缺陷方式。其中獨(dú)立版操作較其他工具簡(jiǎn)單。報(bào)告類別為:錯(cuò)誤、警告、風(fēng)格警告、可移植性警告、性能警告、信息消息。CppCheck的基本命令行的形式為:
cppcheck --enable=all "d: FreeBird *.cpp"
--enable=all表示全部啟用以上6種報(bào)告類型,"d: FreeBird *.cpp"表示要測(cè)試的目標(biāo)文件。
Coverity擅長(zhǎng)精確查找源代碼中最嚴(yán)重以及最難檢測(cè)到的缺陷,例如在檢測(cè)C/C++源代碼時(shí)能發(fā)現(xiàn)并發(fā)、性能低下、導(dǎo)致崩潰的缺陷、不正確的程序行為、安全編碼缺陷、隱含的缺陷等問題。支持Windows、Linux等操作系統(tǒng),支持C/C++、C#、java、JavaScript、PHP、Python在內(nèi)的大多數(shù)編程語(yǔ)言。Coverity擁有一套基礎(chǔ)技術(shù):檢測(cè)質(zhì)量缺陷、潛在安全漏洞、測(cè)試沖突以及java運(yùn)行時(shí)缺陷。分析源代碼可以使用基于GUI的Coverity Wizard,也可以使用插件集成和命令行。Coverity Wizard分析源代碼的標(biāo)準(zhǔn)工作流為:簡(jiǎn)介、編譯器配置、捕獲、分析、提交缺陷、查看結(jié)果。簡(jiǎn)介:填寫項(xiàng)目名稱,勾選分析選項(xiàng)(一般缺陷、安全缺陷、報(bào)告不充分的測(cè)試、確定測(cè)試優(yōu)先級(jí)別);編譯器配置:Coverity內(nèi)置的配置文件coverity_config.xml自動(dòng)配置通用編譯器,包含gcc、javac、clangcc、javascript、python、php、ruby、swiftc、cl、csc,可添加其他編譯器;捕獲:設(shè)置包含命令行構(gòu)建(推薦)和IDE構(gòu)建。分析:設(shè)置分析選項(xiàng),運(yùn)行分析;提交缺陷:將包含源代碼和缺陷在內(nèi)的分析結(jié)果提交給Coverity Connect服務(wù)器。查看結(jié)果:使用網(wǎng)頁(yè)瀏覽器查看已提交的缺陷,并進(jìn)行管理和分類[14]。Coverity作為靜態(tài)源代碼分析領(lǐng)域的領(lǐng)導(dǎo)者,在全球擁有很多用戶。早在2010年底,華為公司就部署了Coverity作為靜態(tài)分析工具,并且在一年內(nèi)實(shí)現(xiàn)從小范圍使用到全公司覆蓋。
文中靜態(tài)測(cè)試實(shí)驗(yàn)是依據(jù)嵌入式開源的Visual Studio項(xiàng)目FreeBird展開的,選用靜態(tài)測(cè)試工具C++Test、PC-Lint進(jìn)行檢測(cè)。
C++Test靜態(tài)測(cè)試實(shí)驗(yàn)選擇C++Test的Visual Studio 2008插件集成方式??紤]到嵌入式軟件對(duì)運(yùn)行時(shí)檢測(cè)的要求,選擇C++Test內(nèi)置的BugDetective測(cè)試配置,來(lái)識(shí)別運(yùn)行時(shí)錯(cuò)誤。測(cè)試結(jié)果如圖1所示,共顯示17個(gè)測(cè)試結(jié)果,可展開節(jié)點(diǎn)查看嚴(yán)重度、位置等具體信息(由于圖中信息比較清晰,在這里不進(jìn)行列舉)。
圖1 BugDetective測(cè)試結(jié)果
選擇Parasoft’s Recommended Rules測(cè)試配置,來(lái)識(shí)別最容易造成嚴(yán)重結(jié)構(gòu)錯(cuò)誤的代碼缺陷,測(cè)試結(jié)果如圖2所示。
圖2 Parasoft’s Recommended Rules測(cè)試結(jié)果
PC-Lint的靜態(tài)測(cè)試同樣選擇了Visual Studio 2008插件集成方式。PC-Lint作為一個(gè)命令行工具,其檢查結(jié)果的可視化體驗(yàn)比C++Test略差,但代碼缺陷信息描述很清晰,可作為C++Test檢測(cè)結(jié)果的核實(shí)與補(bǔ)充,這里只展示一部分結(jié)果,如圖3所示。
圖3 PC-Lint部分檢測(cè)結(jié)果
error1927:構(gòu)造函數(shù)應(yīng)該初始化所有成員變量。
error1732:沒有定義賦值運(yùn)算符。
error1733:沒有聲明復(fù)制構(gòu)造函數(shù)。
error1551:這樣的異常需要在try塊中被捕獲,析構(gòu)函數(shù)不應(yīng)該拋出異常。
考慮到C++Test和PC-Lint軟件的測(cè)試重點(diǎn)和嵌入式軟件的測(cè)試重點(diǎn),在這里只對(duì)部分測(cè)試項(xiàng)進(jìn)行統(tǒng)計(jì)對(duì)比,如表3所示。
表3 實(shí)驗(yàn)結(jié)果
C++Test選擇運(yùn)行時(shí)檢測(cè)以及推薦規(guī)則兩個(gè)測(cè)試項(xiàng),PC-Lint選擇警告級(jí)別為0、1、2的測(cè)試項(xiàng)。從這里可以看出C++Test側(cè)重運(yùn)行時(shí)錯(cuò)誤、資源泄漏等缺陷,PC-Lint側(cè)重非法異常拋出、無(wú)法到達(dá)的函數(shù)等代碼邏輯問題。兩者測(cè)試結(jié)果既形成對(duì)比又相互補(bǔ)充,減少漏報(bào)、誤報(bào)的可能性,提高了測(cè)試效率,完善了嵌入式項(xiàng)目FreeBird的測(cè)試結(jié)果。
針對(duì)嵌入式軟件的特點(diǎn),得出嵌入式軟件靜態(tài)測(cè)
試的測(cè)試重點(diǎn)。對(duì)常出現(xiàn)的代碼缺陷進(jìn)行分析,總結(jié)能有效檢測(cè)出這些代碼缺陷的五種靜態(tài)測(cè)試工具的功能側(cè)重點(diǎn)和使用方法。通過(guò)實(shí)驗(yàn)分析得出適用于嵌入式軟件靜態(tài)測(cè)試的測(cè)試方案:使用C++Test和PC-Lint兩種靜態(tài)測(cè)試工具測(cè)試嵌入式軟件,兩者側(cè)重點(diǎn)不同形成互補(bǔ),能更全面、有效地檢測(cè)出嵌入式軟件中隱藏的代碼缺陷。