諸如基于模式的靜態(tài)代碼分析、運行時內(nèi)存監(jiān)控、單元測試和流分析等自動化技術(shù)可以一起用于查找嵌入式 C 應(yīng)用程序中的錯誤。以下討論將使用 Parasoft C/C++test 演示這些技術(shù),Parasoft C/C++test 是一種集成解決方案,用于自動化廣泛的最佳實踐,以提高 C 和 C++ 軟件開發(fā)團隊的生產(chǎn)力和軟件質(zhì)量。
示例傳感器應(yīng)用
可以在 ARM Cortex-M3 板上運行的簡單傳感器應(yīng)用程序的上下文中探索推薦的錯誤發(fā)現(xiàn)策略。應(yīng)用程序已創(chuàng)建并上傳到開發(fā)板,但在運行時,它不會在 LCD 屏幕上呈現(xiàn)預(yù)期的輸出。
它不起作用,原因尚不清楚。在目標(biāo)板上進行調(diào)試既費時又乏味,因為需要手動分析調(diào)試器結(jié)果以嘗試確定真正的問題。或者,可以應(yīng)用某些工具或技術(shù)來自動查明錯誤。
此時,兩個選項是使用調(diào)試器調(diào)試應(yīng)用程序或應(yīng)用自動化測試策略從代碼中剝離錯誤。如果應(yīng)用自動化技術(shù)后應(yīng)用程序仍然無法工作,調(diào)試器可以作為最后的手段。
基于模式的靜態(tài)代碼分析
應(yīng)用了基于模式的靜態(tài)分析,而不是調(diào)試,它快速、易于使用并且?guī)缀蹩梢詰?yīng)用于每次代碼 更改。通過執(zhí)行靜態(tài)分析確定了一個問題(參見圖 1)。
圖 1:靜態(tài)代碼分析識別 MISRA 編碼標(biāo)準(zhǔn)違規(guī)。
這違反了 MISRA 規(guī)則,即在布爾表達(dá)式中使用賦值運算符可能會有風(fēng)險。目的不是使用賦值運算符,而是使用比較運算符。所以這個問題得到解決,程序重新運行。
由于一些輸出顯示在 LCD 上,因此有所改進。但是,應(yīng)用程序因訪問沖突而崩潰。再一次,有一個選擇:使用調(diào)試器或繼續(xù)應(yīng)用自動錯誤檢測技術(shù)。鑒于自動錯誤檢測在發(fā)現(xiàn)此類內(nèi)存損壞方面非常有效,因此執(zhí)行運行時內(nèi)存監(jiān)控是最佳選擇。
整個應(yīng)用程序的運行時內(nèi)存監(jiān)控
可以通過應(yīng)用適合在目標(biāo)板上運行的輕量級儀器來執(zhí)行運行時內(nèi)存監(jiān)控。上傳并運行檢測的應(yīng)用程序并下載結(jié)果后,會報告錯誤(參見圖 2)。
圖 2:運行時內(nèi)存監(jiān)控報告讀取超出范圍的數(shù)組。
這表明在第 48 行讀取了一個超出范圍的數(shù)組。顯然,msgIndex變量的值一定超出了數(shù)組的范圍。向上堆棧跟蹤顯示,這個具有超出范圍值的打印消息是由于在調(diào)用函數(shù)printMessage()之前為其設(shè)置了不正確的條件而導(dǎo)致的。這可以通過放松if語句中的值范圍控制并去掉不必要的條件(value 《= 20)來解決。
void handleSensorValue(int value)
{
initialize();
int index = -1;
if (value 》= 0 && value 《= 10) {
index = VALUE_LOW;
} else if ((value 》 10) && (value 《= 20)) {
index = VALUE_HIGH;
}
printMessage(index, value);
}
現(xiàn)在,當(dāng)重新運行應(yīng)用程序時,不會報告內(nèi)存錯誤。應(yīng)用程序上傳到板后,它似乎按預(yù)期工作。然而,一些擔(dān)憂仍然存在。
在執(zhí)行的代碼路徑中發(fā)現(xiàn)了一個內(nèi)存覆蓋實例,但這是否意味著未執(zhí)行的代碼中沒有內(nèi)存覆蓋?覆蓋分析表明,有些代碼根本沒有被執(zhí)行。reportSensorFailure() 函數(shù)沒有被覆蓋,并且調(diào)用reportSensorFailure的mainLoop函數(shù)內(nèi)部的一個分支根本沒有被執(zhí)行(再次參見圖 2)。測試此代碼的一種方法是創(chuàng)建一個單元測試(用于mainLoop函數(shù))和一個用戶存根(用于readSensor函數(shù)),以模擬在功能測試期間難以重現(xiàn)的條件。
帶有運行時內(nèi)存監(jiān)控的單元測試
創(chuàng)建一個測試用例骨架,然后用測試代碼填充。 此外,為readSensor函數(shù)添加了一個存根以模擬讀取錯誤。運行測試用例——只執(zhí)行這個以前未測試的功能——啟用運行時內(nèi)存監(jiān)控。結(jié)果顯示該函數(shù)現(xiàn)在已被覆蓋,但報告了新的錯誤(參見圖 3)。
圖 3:啟用運行時內(nèi)存監(jiān)控的單元測試會暴露內(nèi)存錯誤。
測試用例發(fā)現(xiàn)了更多與內(nèi)存相關(guān)的錯誤。調(diào)用失敗處理程序時,內(nèi)存初始化(空指針)存在明顯問題。進一步分析表明,reportSensorValue()中混合了調(diào)用順序,因此finalize()在printMessage()被調(diào)用之前被調(diào)用,但finalize()實際上釋放了printMessage()使用的內(nèi)存。
void finalize()
{
if (messages) {
free(messages[0]);
free(messages[1]);
free(messages[2]);
}
free(messages);
}
void printMessage(int msgIndex, int value)
{
const char* msg = messages[msgIndex];
printf(“Value: %d, State: %s\n”, value, msg);
fflush(stdout);
}
void reportSensorFailure()
{
finalize();
printMessage(ERROR_MSG, 0);
}
這個順序是固定的,測試用例會重新運行一次。
這解決了報告的錯誤之一。下一步是解決報告的第二個問題:打印消息中的 AccessViolationException。這是因為這些表消息未初始化。為了解決這個問題,在打印消息之前調(diào)用initialize()函數(shù)。修復(fù)后的功能如下:
void reportSensorFailure()
{
initialize();
printMessage(ERROR, 0);
finalize();
}
重新運行測試時,只報告一個任務(wù):一個無效的單元測試用例,這并不是真正的錯誤。必須驗證結(jié)果才能將此測試轉(zhuǎn)換為回歸測試(參見圖 4)。
圖 4:必須為回歸測試配置測試。
接下來,再次運行整個應(yīng)用程序。覆蓋率分析顯示幾乎整個應(yīng)用程序都被覆蓋了,結(jié)果表明沒有出現(xiàn)內(nèi)存錯誤問題。
即使運行了整個應(yīng)用程序并為未覆蓋的函數(shù)創(chuàng)建了單元測試,但仍有一些路徑未被覆蓋。可以繼續(xù)使用單元測試創(chuàng)建,但需要一些時間才能覆蓋應(yīng)用程序中的所有路徑。相反,可以使用流量分析來模擬這些路徑。
流量分析
運行流分析以模擬通過系統(tǒng)的不同路徑,并檢查這些路徑中是否存在潛在問題。報告了幾個問題(參見圖 5)。
圖 5:流分析發(fā)現(xiàn)路徑中的幾個問題。
有一條潛在的路徑——一個未被覆蓋的路徑——在finalize()函數(shù)中可以有一個雙重釋放。reportSensorValue()函數(shù)調(diào)用finalize(),然后finalize ()調(diào)用free()。此外,在mainLoop()中再次調(diào)用finalize () 。這可以通過使finalize()更智能來解決:
void finalize()
{
if (messages) {
free(messages[0]);
free(messages[1]);
free(messages[2]);
free(messages);
messages = 0;
}
}
然后再運行一次流量分析。僅報告了兩個問題(參見圖 6)。
圖 6:流量分析檢測到兩個剩余問題。
此處可能正在訪問索引為-1的表。這是因為積分索引最初設(shè)置為 -1,并且在調(diào)用printMessage()之前,可能存在一條通過if語句未將此積分設(shè)置為正確值的路徑。運行時分析并沒有導(dǎo)致這條路徑,并且這條路徑可能永遠(yuǎn)不會在現(xiàn)實生活中被采用。與實際運行時內(nèi)存監(jiān)控相比,這是流分析的主要弱點。流分析顯示潛在路徑,不一定是在實際應(yīng)用程序執(zhí)行期間將采用的路徑。通過刪除不必要的條件(value 》= 0)可以輕松修復(fù)此潛在錯誤。
void handleSensorValue(int value)
{
initialize();
int index = -1;
if (value 《= 10) {
index = VALUE_LOW;
} else {
index = VALUE_HIGH;
}
printMessage(index, value);
}
報告的最終錯誤以類似的方式修復(fù)。現(xiàn)在,重新運行流分析時,不會報告任何問題。
回歸測試
為確保一切正常,重新運行整個分析。首先,應(yīng)用程序在運行時內(nèi)存監(jiān)控下運行,一切似乎都很好。然后使用內(nèi)存監(jiān)控運行單元測試并報告一個任務(wù)(參見圖 7)。
圖 7:單元測試發(fā)現(xiàn)回歸失敗。
單元測試檢測到reportSensorFailure()函數(shù)的行為發(fā)生了變化。這是由finalize()中的修改引起的,這是為了糾正先前報告的問題之一而進行的更改。此任務(wù)引起對更改的注意,并指示必須審查測試用例。然后要么更正代碼,要么更新測試用例,以表明這個新行為實際上是預(yù)期的行為。看了下代碼,很明顯后者為真,并且更新了斷言的條件。
void sensor_tests_test_reportSensorFailure()
{
{
messages = 0 ;
}
{
reportSensorFailure();
CPPTEST_ASSERT(0 == ( messages ));
}
}
作為最后的健全性檢查,整個應(yīng)用程序自行運行,在集成開發(fā)環(huán)境中構(gòu)建它,無需任何運行時內(nèi)存監(jiān)控。結(jié)果證實它按預(yù)期工作。
補充工具
所有應(yīng)用的測試方法——基于模式的靜態(tài)代碼分析、內(nèi)存分析、單元測試、流分析和回歸測試——不相互競爭,而是相互補充。一起使用,它們提供了一個非常強大的工具,可以為嵌入式 C 軟件提供無與倫比的自動錯誤檢測水平。
作者:Marek Kucharski,Miros?aw Zieli nski
審核編輯:郭婷
評論
查看更多