新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > 嵌入式實時應用的高級動態(tài)代碼分析(ADCA)

嵌入式實時應用的高級動態(tài)代碼分析(ADCA)

—— 一種自動檢測C/C++代碼中內(nèi)存訪問錯誤的方法
作者:Stephan Lauterbach(勞特巴赫技術公司CTO) 時間:2023-09-14 來源:電子產(chǎn)品世界 收藏

C 和C++ 程序語言功能強大,但也容易出現(xiàn)錯誤。其中一類容易出現(xiàn)的是內(nèi)存訪問錯誤,例如緩沖區(qū)溢出、內(nèi)存泄漏等,其后果也可能是災難性的。領先的調試器供應商Lauterbach 希望通過一項名為(Advanced Dynamic Code Analysis ,簡稱)的新技術來幫助嵌入式開發(fā)人員避免這類錯誤。

本文引用地址:http://butianyuan.cn/article/202309/450588.htm

1 引言

軟件錯誤的代價可能是巨大的,甚至具有災難性后果。例如,1996 年歐洲航天局(ESA)的阿麗亞娜5 號火箭在飛行39 秒后爆炸,造成超過3.7 億美元的損失[1]。事故原因是內(nèi)部慣性參考系統(tǒng)(SRI)軟件異常,由于執(zhí)行從64 位浮點數(shù)到16 位有符號整數(shù)值的數(shù)據(jù)轉換時,浮點數(shù)的值超出了16 位有符號整數(shù)所能表示的范圍,導致操作數(shù)錯誤。而數(shù)據(jù)轉換指令(在Ada 代碼中)也沒有保護機制以防止操作數(shù)錯誤。

錯誤發(fā)生在控制捆綁式慣性平臺對準的軟件部分。操作數(shù)錯誤是由于內(nèi)部對準函數(shù)結果(稱為BH(水平偏差))的值太高引起的,該值與平臺感知的水平速度有關。雖然阿麗亞娜5 號使用的SRI 設計與阿麗亞娜4號幾乎相同,但由于阿麗亞娜5 號早期軌跡與阿麗亞娜4 號不同,導致水平速度值顯著增加,使BH 值比預期高得多。這類錯誤不僅在火箭這一類的項目中可能造成災難性后果,在日常嵌入式系統(tǒng)中也可能是危險的主要來源。

在1985 年至1987 年間,醫(yī)療行業(yè)發(fā)生了一起嚴重事故。由于多任務系統(tǒng)中存在競爭條件(Race Conditions),Therac-25 放射治療機造成了大量輻射過量,導致三名患者死亡,至少三名其他患者受到嚴重傷害[2]。操作員原本打算使用低功率光束進行治療,但由于沒有放置擴散磁鐵,實際使用的卻是高功率光束,導致劑量遠遠超出預期。這一問題源自代碼庫中的競爭條件(Race Conditions),這種競爭條件在前一個模型Therac-20中就已經(jīng)存在,但被硬件安全控制所阻止。整個軟件系統(tǒng)由多個同時運行的進程組成,數(shù)據(jù)輸入和鍵盤處理程序共享一個變量來標識數(shù)據(jù)輸入是否完成。在數(shù)據(jù)輸入階段完成后,系統(tǒng)會進入磁鐵設置階段。但是,如果在這8 秒鐘的磁鐵設置階段內(nèi),用戶在數(shù)據(jù)輸入階段使用了特定的編輯序列,由于標識變量的值的影響,設置無法應用到機器硬件上。

在汽車行業(yè),自動駕駛汽車中的軟件錯誤也造成了多起死亡事故。例如,在2016 年,一輛汽車的傳感器系統(tǒng)未能識別出一輛大型白色18 輪卡車/ 拖車正在穿過高速公路,汽車以全速駛入了拖車的下方[3]。

為了盡可能避免這類錯誤,自動代碼分析工具可以幫助開發(fā)人員自動檢測日益復雜的軟件中的錯誤。

2 最危險的軟件缺陷

2.1 內(nèi)存訪問錯誤是軟件錯誤的主要來源之一

美國國家網(wǎng)絡安全卓越中心每年都會發(fā)布常見缺陷列表(CWE?),其中包括《Top 25 最危險軟件缺陷》(CWE? Top 25)[4]。該清單展示了目前最常見和影響最大的軟件缺陷。

為了創(chuàng)建這份清單,CWE 團隊利用了國家標準技術研究所(NIST)國家漏洞數(shù)據(jù)庫(NVD)中的常見漏洞和暴露(CVE?)數(shù)據(jù)和與每個CVE 記錄相關的常見漏洞評分系統(tǒng)(CVSS)的分數(shù),包括關注網(wǎng)絡安全和基礎設施安全局(CISA)已知被利用漏洞(KEV)目錄中的CVE 記錄。應用了一個公式來根據(jù)普遍性和嚴重性對每個軟件缺陷進行評分。

用于計算2022 年Top 25 的數(shù)據(jù)集包含了前兩年內(nèi)共37,899 條CVE 記錄。2022 年,前11 大缺陷中有4個是與內(nèi)存相關的錯誤( 圖1)。

image.png

圖1 2022 CWE? Top 25

2.2 C/C++語言中典型的內(nèi)存訪問錯誤

在2022 年,前11 種軟件缺陷中有四個與內(nèi)存訪問錯誤有關,如圖2 所示。越界錯誤位居2022 年CWE?Top 25 清單之首。該錯誤類型一般有三種變體,但基本問題的核心都相同:程序在分配的內(nèi)存區(qū)域外寫入或讀取了數(shù)據(jù)。

1694672244473437.png

圖2 三種常見越界示例

當數(shù)據(jù)大小超過所分配的內(nèi)存區(qū)域時,可能會發(fā)生緩沖區(qū)溢出。這種情況下,數(shù)據(jù)可能被寫入或讀取錯誤的內(nèi)存位置。此外,當程序計算數(shù)據(jù)大小或位置不正確時也可能發(fā)生緩沖區(qū)溢出。在我們的示例中,緩沖區(qū)溢出發(fā)生在程序試圖訪問數(shù)組中無效索引的情況下,即索引小于0 或等于或大于數(shù)組長度。

錯誤,每種錯誤發(fā)生的頻率都較低,但其影響也同樣嚴重,主要包括以下幾種。

●   未定義行為:程序的行為沒有被編程語言規(guī)范所定義。未定義行為的例子包括有符號整數(shù)溢出、空指針引用、在沒有序列點的表達式中多次修改同一個標量以及通過不同類型的指針訪問對象。

●   內(nèi)存泄漏:當程序員分配內(nèi)存但忘記使用delete()函數(shù)或delete[] 運算符釋放內(nèi)存時,就會發(fā)生內(nèi)存泄漏。C++ 中最常見的內(nèi)存泄漏是使用錯誤的delete 運算符。delete 運算符應用于釋放單個分配的內(nèi)存空間,而delete [] 運算符應用于釋放數(shù)據(jù)值數(shù)組。

●   使用后釋放:當在釋放內(nèi)存后又引用內(nèi)存時,會發(fā)生這種錯誤。使用先前釋放的內(nèi)存可能會產(chǎn)生各種不利后果,從有效數(shù)據(jù)損壞到執(zhí)行任意代碼,具體取決于缺陷的實例和時序。

●   未初始化內(nèi)存讀?。喝绻麘贸绦驈奈幢惶畛涑跏贾档目蓪ぶ穬?nèi)存中讀取,則會發(fā)生此錯誤。錯誤可能是由于初始化順序不正確或多線程應用程序中的競爭條件引起的。

●   返回后使用棧:如果在聲明函數(shù)返回后訪問棧變量內(nèi)存,則會發(fā)生此錯誤。

圖3 列舉了這些錯誤類型的簡短代碼示例。

1694672334732344.png

圖3 C/C++代碼中常見內(nèi)存錯誤

3 目前可用的自動代碼分析工具

為了避免C/C++ 代碼中出現(xiàn)內(nèi)存錯誤,已經(jīng)有一些動態(tài)代碼分析工具可以幫助檢查,如圖4 中顯示是最著名并長期被使用的Valgrind 和AddressSanitizer(簡稱ASan)兩個工具。兩者都支持多種CPU 架構,并以不同方式對代碼進行檢測。但是,由于這兩種工具都會導致性能降低和內(nèi)存損耗,因此它們在中的使用受到極大的限制。

1694672386513802.png

圖4 兩款常用工具比較

3.1 Valgrind

Valgrind本質上是一種使用即時編譯技術的虛擬機,包括動態(tài)重新編譯[5]。它首先將程序轉換為一種臨時、更簡單的形式,稱為中間表示(IR),然后工具可以自由地對IR 進行任何轉換,最后Valgrind 將IR 轉換成機器代碼并讓主處理器運行它。

Valgrind 附帶了多個工具, 默認且最常用的是Memcheck。Memcheck 在幾乎所有指令周圍插入額外的檢測代碼,用于跟蹤數(shù)據(jù)的有效性和可尋址性。此外,Memcheck 用自己的實現(xiàn)替換了標準C 內(nèi)存分配函數(shù),該實現(xiàn)還包括在所有分配塊周圍設置內(nèi)存保護。這個功能使Memcheck 能夠檢測到微量偏差錯誤。

Memcheck 能夠檢測并警告的問題包括以下幾點:

●   使用未初始化的內(nèi)存;

●   在內(nèi)存被釋放后讀寫;

●   越界訪問malloc 分配的內(nèi)存塊;

●   內(nèi)存泄漏。

使用Valgrind 工具的主要代價是性能的損失。在Memcheck 下運行的程序通常比在Valgrind 外運行慢20-30 倍,并且使用更多的內(nèi)存(每次分配都會有一定的內(nèi)存開銷)。

3.2 ASan

AddressSanitizer(或ASan)是谷歌安全研究人員創(chuàng)建的一種開源編程工具,用于識別C 和C++ 程序中的內(nèi)存訪問問題[6]。它可以檢測到內(nèi)存相關錯誤,例如緩沖區(qū)溢出或對懸空指針(釋放后使用)的訪問等。AddressSanitizer 的實現(xiàn)是基于編譯器插樁和直接映射影子內(nèi)存。

為了監(jiān)控內(nèi)存分配并識別內(nèi)存泄漏,malloc 和free系列函數(shù)被替換,因此每次內(nèi)存分配/ 釋放都會被工具監(jiān)控。然后,每次讀取或寫入內(nèi)存訪問都會被編譯為一段代碼,用來檢查該內(nèi)存地址是否被標記為有害。如果是有害的,它將報告一個錯誤。

通常情況下,應用程序的虛擬地址空間被劃分為應用程序代碼使用的程序內(nèi)存和存儲有害(不可尋址)內(nèi)存元數(shù)據(jù)內(nèi)存的影子內(nèi)存。 AddressSanitizer 將每8 字節(jié)的應用程序內(nèi)存映射到1 字節(jié)的影子內(nèi)存中。如果一個內(nèi)存地址未被標識有害(即可尋址),則影子內(nèi)存中的標志位為0。如果一個內(nèi)存地址為有害(即不可尋址),則影子內(nèi)存中的標志位為1。這樣,AddressSanitizer 就可以識別哪些內(nèi)存訪問是允許的,哪些不允許并報告錯誤。與Valgrind 一樣,您也必須為ASan付出高昂的代價,包括速度損失和內(nèi)存需求。在Asan 下運行的程序通常比在外部運行慢2 倍,并且平均使用240%更多的內(nèi)存。

4 適用于嵌入式實時系統(tǒng)的Lauterbach跟蹤技術

鑒于現(xiàn)有工具有時無法滿足實時系統(tǒng)的需求,那么使用Lauterbach 的跟蹤技術或者是一種選擇。圖5 顯示了“Lauterbach Trace Pyramid”的功能模塊,頂部是新的 技術。 基于Lauterbach 的上下文跟蹤系統(tǒng)(CTS),而CTS 又基于標準實時流跟蹤技術。

1694672565864937.png

圖5 “Lauterbach Trace Pyramid”架構

4.1 實時流數(shù)據(jù)跟蹤Real Time Flow Trace

不斷提高的集成密度和價格壓力導致許多處理器將CPU 內(nèi)核、緩存、外設、FLASH 和RAM 內(nèi)存集成在一個封裝中(SoC),在許多情況下甚至不再具有外部存儲器接口。除了調試接口外,很多芯片廠商還在芯片上實現(xiàn)了具有特殊功能的跟蹤接口。這使得程序和數(shù)據(jù)以壓縮形式可以輸出到芯片外部(圖6),這種方法稱為流跟蹤方法。

跟蹤總線鏈接到跟蹤接口,通過該總線以壓縮形式傳輸程序流和(或)數(shù)據(jù)訪問信息。地址總線/ 數(shù)據(jù)總線的信息在CPU 核心處也可以直接輸出。這意味著也可以記錄對芯片內(nèi)部FLASH或RAM內(nèi)存(特別是緩存)的訪問。

只有少數(shù)跟蹤接口支持特定的開/ 關切換或定義地址窗口,以控制生成跟蹤數(shù)據(jù)。因此唯一的解決方案是使用具有大型跟蹤內(nèi)存的跟蹤工具,其中所有跟蹤數(shù)據(jù)都未經(jīng)過濾地被記錄起來。通過幾秒鐘的錄制時間,很可能可以在錄制數(shù)據(jù)中找到所尋求的錯誤。此外,多任務程序運行的跟蹤數(shù)據(jù)也可用于運行時統(tǒng)計分析和/ 或代碼覆蓋率分析。

這種跟蹤技術唯一的缺點是帶寬問題,即如果芯片內(nèi)部生成的跟蹤數(shù)據(jù)比通過跟蹤接口傳輸?shù)母?,就會出?shù)據(jù)丟失的情況。芯片制造商一般用FIFO 緩沖區(qū)和減少跟蹤數(shù)據(jù)來解決這個問題。

實時流跟蹤雖然不是特別高尖端學科,但是,Lauterbach 的跟蹤工具提供了業(yè)界最高的數(shù)據(jù)帶寬和最豐富的數(shù)據(jù)分析功能。

1694672677290939.png

圖6 流跟蹤的通用配置

4.2 TRACE32上下文跟蹤系統(tǒng)(CTS)

僅依靠流跟蹤數(shù)據(jù)可能需要花費大量時間分析跟蹤數(shù)據(jù),以找出哪些指令、數(shù)據(jù)或系統(tǒng)狀態(tài)導致目標系統(tǒng)出現(xiàn)故障。

Lauterbach 的基于跟蹤的調試- 簡稱CTS- 允許用戶根據(jù)跟蹤緩沖區(qū)中采樣的跟蹤數(shù)據(jù)重構選定點的目標系統(tǒng)狀態(tài)(圖7)。并且從這個選定點開始,可以重復在TRACE32 PowerView GUI 中調試實時記錄在跟蹤存儲器中的程序步驟。執(zhí)行全功能跟蹤調試的前提條件是將程序和數(shù)據(jù)流完整記錄到跟蹤緩沖區(qū),直到程序執(zhí)行停止。

1694672736862482.png

圖7 Context Tracking System (CTS)功能

使能上下文跟蹤系統(tǒng)(CTS)功能后,可以選擇一個記錄點,在指令集模擬器(SIM) 中為其重構目標系統(tǒng)狀態(tài)。程序計數(shù)器(PC) 自動設置在源程序中對應的位置,即該記錄點所跟蹤數(shù)據(jù)的地址。

現(xiàn)在CTS 也已經(jīng)支持所有調試命令,例如Step、Step Over Call、Go、Return 等。 CPU 指令按照它們在跟蹤存儲器中記錄的順序進行處理。調試功能也實現(xiàn)了進一步擴展,甚至可支持程序向后步進。

由于指令集模擬器(SIM)支持單個指令的執(zhí)行,因此也能夠跟蹤變量、內(nèi)存和寄存器的變化等。甚至T32 SW 還可以自動修復由于跟蹤端口帶寬限制,所產(chǎn)生跟蹤數(shù)據(jù)遺漏而導致的代碼丟失。如果只采樣讀操作以防止跟蹤端口過載,則CTS 也可以重建所有寫操作。

此外,TRACE32? 也可以使用CTS 技術支持緩存(Cache)狀態(tài)/ 使用率分析等,該技術也是基于跟蹤記錄中捕獲的程序跟蹤數(shù)據(jù)。如果設置了MMU 架構,則緩存分析將考慮對緩存控制寄存器的所有操作:包括緩存刷新、緩存的開啟和關閉以及緩存鎖定等。

總之,Lauterbach 的CTS 可以大大簡化調試,特別是對于僅靠停止調試模式往往不夠的實時應用程序。

4.3 TRACE32 (Advanced Dynamic Code Analysis ,ADCA)

ADCA 是一種先進的CTS 模式,用于探索和修復由不同類型的錯誤觸發(fā)的內(nèi)存訪問錯誤。它在啟動代碼之后運行,捕獲有效的初始內(nèi)存和寄存器狀態(tài),堆棧和數(shù)據(jù)等。

ADCA 需要完整的程序跟蹤流和足夠的數(shù)據(jù)來重建所有指針以及從啟動代碼的完整跟蹤數(shù)據(jù)。該工具還依賴于編譯器提供的正確和完整的調試信息,并需要理解編譯器生成和優(yōu)化的結構。此外,它還需要理解特殊代碼,如中斷和RTOS 任務切換等。

ADCA 的核心功能是將靜態(tài)和動態(tài)標簽分配給所有數(shù)據(jù)和內(nèi)存地址。標簽是不可見的元信息,由調試器支持。根據(jù)鑰匙鎖原理檢測內(nèi)存違規(guī):具有某個標簽的數(shù)據(jù)只能訪問與相同標簽關聯(lián)的內(nèi)存地址(圖8)。

1694672848554055.png

圖8 一對一鑰匙鎖校驗機制

運行程序并記錄跟蹤數(shù)據(jù)后,您可以運行ADCA功能并結合TRACE32?PowerView 軟件的其他相關窗口的信息,識別相關錯誤(圖9)。

1694672879695858.png

圖9 TRACE32 PowerView相關窗口

Lauterbach 的ADCA 報告顯示了所有潛在內(nèi)存訪問錯誤的總結( 圖10)。下面將討論報告中的一個具體示例: 訪問名為“check_array1”的數(shù)組以及訪問沖突的內(nèi)存地址(0x418f5c) 時失敗。開發(fā)人員可以使用這些信息來分析代碼,并通過使用TRACE32?PowerView GUI中顯示的附加信息來修復錯誤。

1694672952387739.png

圖10 TRACE32? ACDA 自動檢測內(nèi)存錯誤

從圖9 所示的可能性中,第一個可能的TRACE32?PowerView視圖應該是存儲器顯示( 圖11)。在顯示器的左側,可以觀察到地址418F5C 的標簽變化。結果很明顯,與數(shù)組“check_array1”( 標簽28A) 相關的最新有效內(nèi)存地址是418F5B。

1694672985682842.png

圖11 TRACE32? ADCA 內(nèi)存視圖

在內(nèi)存顯示的中間部分可以觀察到,“check_array1”的最高有效索引( 與內(nèi)存地址418F58 到418F5B 和標簽28A 相關聯(lián)) 是“9”。

正如稍后將看到的,這些信息對于錯誤檢測很有價值,但當然還不足以修復錯誤。

接下來要探索的是寄存器顯示(如圖12)。顯然,寄存器R1 與標簽28A 相關聯(lián),該標簽屬于數(shù)組“check_array1”。不幸的是,R1 指向內(nèi)存地址418F5C,該地址位于“check_array1”的有效地址空間之外。此時,錯誤的原因已經(jīng)接近被發(fā)現(xiàn),只需要找出源代碼窗口即可(如圖13)。

1694673067603810.png

圖12 TRACE32? ADCA寄存器視圖

根據(jù)寄存器顯示中的信息(如圖12),可以輕松確定寄存器R1 包含Lauterbach 的ADCA 在開始時探索到的對“check_array1 [i]”進行寫訪問的(無效)內(nèi)存地址。根據(jù)內(nèi)存顯示中的信息,這種無效訪問的原因是顯而易見的:索引變量“i”從0 增加到10,這導致最后一個循環(huán)通過“check_array1 [10]”發(fā)生內(nèi)存訪問違規(guī)。要修復錯誤,您只需將代碼從“i<11”更改為“i <10”即可。

1694673100682024.png

圖13 TRACE32? ADCA源碼視圖

盡管目前已經(jīng)有一些成熟的代碼分析工具,但它們卻都不適用于實時的嵌入式軟件應用。而Lauterbach 恰恰滿足了嵌入式開發(fā)人員對于實時性的要求和檢測C / C++ 中相關的潛在內(nèi)存訪問錯誤的需求。ADCA 是識別這些難以發(fā)現(xiàn)和復現(xiàn)的錯誤的重大進步,也可支持多核架構。當然也有一些限制。比如某些特殊的結構和錯誤類型無法檢測。但是,這些類型的錯誤通常都會被編譯器檢測出來。特殊的代碼結構可能需要額外的設置來提供分析信息。

Lauterbach ADCA 技術將在2023 年的軟件版本中作為TRACE32?PowerView 軟件更新的一部分對勞特巴赫客戶免費提供。ADCA 也不會作為單獨產(chǎn)品出售,因此也無法用作第三方跟蹤工具的附加組件。希望這個功能為您的軟件開發(fā)提供更多幫助。

參考文獻:

[1] https://www.bugsnag.com/blog/bug-day-ariane-5-disaster, Retrieved 12 Feb 2023.

[2] https://pvs-studio.com/en/blog/posts/0438/, Retrieved12 Feb 2023.

[3] https://www.businessinsider.com/details-aboutthe-fatal-tesla-autopilot-accident-released-2017-6,Retrieved 12 Feb 2023.

[4] https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html, Retrieved 12 Feb 2023.

[5] https://valgrind.org/docs/manual/mc-manual.html,Retrieved 12 Feb 2023.

[6] https://clang.llvm.org/docs/AddressSanitizer.html,Retrieved 12 Feb 2023.

作者簡介:

1694673865321202.png

Lauterbach GmbH公司的所有者之一。自1982年加入勞特巴赫以來,一直在TRACE32調試器產(chǎn)品的開發(fā)中處于核心位置,他對復雜的軟硬件系統(tǒng)研發(fā)經(jīng)驗豐富。

(本文來源于EEPW 2023年9月期)



評論


技術專區(qū)

關閉