24小時(shí)聯(lián)系電話:18217114652、13661815404
中文
技術(shù)專題
單片機(jī)開發(fā)功能安全中的編譯器
在各個(gè)領(lǐng)域,功能安全領(lǐng)域?qū)﹂_發(fā)人員提出了新要求。功能上安全的代碼必須包括防御性代碼,以防御各種原因引起的意外事件。例如,由于編碼錯(cuò)誤或宇宙射線事件而導(dǎo)致的內(nèi)存損壞可能導(dǎo)致執(zhí)行根據(jù)代碼邏輯“不可能”的代碼路徑。高級(jí)語言,特別是C和C ++,包含數(shù)量眾多的功能,這些功能的行為不是代碼所遵循的語言規(guī)范所規(guī)定的。這種不確定的行為可能導(dǎo)致意外的結(jié)果和潛在的災(zāi)難性后果,而這在功能安全的應(yīng)用程序中是無法接受的。出于這些原因,標(biāo)準(zhǔn)要求應(yīng)用防御性編碼,可測試的編碼,有可能整理足夠的編碼覆蓋率,
代碼還必須實(shí)現(xiàn)高級(jí)別的代碼覆蓋率,在某些領(lǐng)域(尤其是汽車領(lǐng)域),設(shè)計(jì)通常需要復(fù)雜的外部診斷,校準(zhǔn)和開發(fā)工具。出現(xiàn)的問題是,防御性編碼和外部數(shù)據(jù)訪問等實(shí)踐并不屬于編譯器認(rèn)可的領(lǐng)域。例如,C和C ++都沒有為內(nèi)存損壞留出任何余地,因此,除非在沒有這種損壞的情況下可以訪問旨在防止內(nèi)存損壞的代碼,否則在對(duì)代碼進(jìn)行優(yōu)化時(shí)可以將其忽略。因此,如果不“優(yōu)化”防御性代碼,則必須在語法和語義上都可以實(shí)現(xiàn)。
未定義行為的實(shí)例也會(huì)引起意外。很容易建議應(yīng)避免使用它們,但通常很難識(shí)別它們。如果存在它們,就不能保證已編譯的可執(zhí)行代碼的行為將符合開發(fā)人員的意圖。對(duì)調(diào)試工具使用的數(shù)據(jù)的“后門”訪問代表了該語言不允許的另一種情況,因此可能會(huì)帶來意想不到的后果。
編譯器優(yōu)化可能對(duì)所有這些領(lǐng)域產(chǎn)生重大影響,因?yàn)樗鼈兌疾粚儆诰幾g器供應(yīng)商的職責(zé)范圍。優(yōu)化可能會(huì)導(dǎo)致在與“不可行”相關(guān)聯(lián)時(shí),即在存在于無法通過任何可能的輸入值進(jìn)行測試和驗(yàn)證的路徑上存在的情況下,顯然消除了防御性代碼。更令人震驚的是,在構(gòu)建系統(tǒng)可執(zhí)行文件時(shí),很可能會(huì)消除在單元測試期間顯示的防御代碼。僅僅因?yàn)樵趩卧獪y試期間已經(jīng)實(shí)現(xiàn)了防御性代碼的覆蓋范圍,因此并不能保證其已存在于完整的系統(tǒng)中。
在功能安全這個(gè)陌生的領(lǐng)域,編譯器可能超出了其要素。這就是為什么目標(biāo)代碼驗(yàn)證(OCV)代表了對(duì)任何與故障相關(guān)的后果都有嚴(yán)重后果的系統(tǒng)的最佳實(shí)踐,甚至對(duì)于只有最佳實(shí)踐就足夠好的任何系統(tǒng)都代表了最佳實(shí)踐。
編譯前后
功能安全性,安全性和編碼標(biāo)準(zhǔn)(例如IEC 61508,ISO 26262,IEC 62304,MISRA C和C ++)提倡的驗(yàn)證和確認(rèn)做法非常強(qiáng)調(diào)顯示在基于需求的測試中使用了多少應(yīng)用程序源代碼。
經(jīng)驗(yàn)向我們表明,如果已證明代碼可以正確執(zhí)行,則現(xiàn)場失敗的可能性會(huì)大大降低。但是,由于這種值得稱贊的努力的重點(diǎn)是高級(jí)源代碼(無論使用哪種語言),所以這種方法使編譯器具有創(chuàng)建目標(biāo)代碼的能力,這些目標(biāo)代碼可以準(zhǔn)確地再現(xiàn)開發(fā)人員的能力,這使人們深信不疑預(yù)期的。在最關(guān)鍵的應(yīng)用程序中,該隱含假設(shè)無法成立。
不可避免的是,目標(biāo)代碼的控制和數(shù)據(jù)流不會(huì)完全是源代碼的鏡像,因此證明所有源代碼路徑都可以可靠地行使并不能證明目標(biāo)代碼是同一件事。 。鑒于目標(biāo)代碼和匯編器之間存在1:1的關(guān)系,因此可以比較源代碼和匯編代碼??紤]一下圖1所示的示例,其中右邊的匯編代碼是從左邊的源代碼生成的(使用禁用了優(yōu)化的TI編譯器)。
圖1:右邊的匯編代碼是從左邊的源代碼生成的,顯示了源代碼和匯編代碼之間的明顯對(duì)比
如下所述,當(dāng)編譯此源代碼時(shí),生成的匯編代碼的流程圖與源代碼的流程圖完全不同,因?yàn)?/span>C或C ++編譯器遵循的規(guī)則允許它們以自己喜歡的任何方式修改代碼,前提是二進(jìn)制表現(xiàn)為“好像是一樣的。”
在大多數(shù)情況下,該原則是完全可以接受的-但存在異常情況。編譯器優(yōu)化基本上是數(shù)學(xué)上的變換,可應(yīng)用于代碼的內(nèi)部表示。如果假設(shè)不成立,這些轉(zhuǎn)換就會(huì)“出錯(cuò)”-例如,在代碼庫包含未定義行為的實(shí)例的情況下,這種情況經(jīng)常發(fā)生。
只有航空航天業(yè)中使用的DO-178C才將重點(diǎn)放在開發(fā)人員意圖與可執(zhí)行行為之間潛在的危險(xiǎn)不一致的可能性上,即使如此,仍不難找到具有明顯潛能的解決方法的倡導(dǎo)者,以免發(fā)現(xiàn)那些不一致之處。但是,可以原諒此類方法,但事實(shí)是,源代碼和目標(biāo)代碼之間的差異可能在任何關(guān)鍵應(yīng)用程序中造成毀滅性后果。
開發(fā)人員意圖與可執(zhí)行行為
盡管源代碼流和目標(biāo)代碼流之間存在明顯差異,但它們并不是主要問題。編譯器通常是高度可靠的應(yīng)用程序,盡管可能會(huì)像其他任何軟件一樣存在錯(cuò)誤,但編譯器的實(shí)現(xiàn)通常會(huì)滿足其設(shè)計(jì)要求。問題在于這些設(shè)計(jì)要求并不總是反映功能安全系統(tǒng)的需求。
簡而言之,可以假定編譯器在功能上符合其創(chuàng)建者的目標(biāo)。但這可能并不完全是期望或期望的結(jié)果,如下面的圖2所示,其中包括一個(gè)使用CLANG編譯器進(jìn)行編譯的示例。
圖2顯示了使用CLANG編譯器進(jìn)行的編譯
顯然,在匯編代碼中并未表達(dá)對(duì)“錯(cuò)誤”功能的防御性呼吁。
僅在初始化“ state”對(duì)象時(shí)以及在“ S0”和“ S1”情況下修改“ state”對(duì)象,因此編譯器可以推斷出賦予“ state”的唯一值是“ S0”和“ S1”。編譯器得出結(jié)論,不需要“默認(rèn)值”,因?yàn)榧僭O(shè)沒有損壞,“狀態(tài)”將永遠(yuǎn)不包含任何其他值-實(shí)際上,編譯器所做的正是這一假設(shè)。
編譯器還決定,由于實(shí)際對(duì)象(13和23)的值未在數(shù)字上下文中使用,因此它將僅使用0和1的值在狀態(tài)之間切換,然后使用異或“或”更新狀態(tài)值。二進(jìn)制文件遵循“好像”義務(wù),并且代碼快速緊湊。在其職權(quán)范圍內(nèi),編譯器做得很好。
此行為對(duì)使用鏈接器內(nèi)存映射文件間接訪問對(duì)象的“校準(zhǔn)”工具以及通過調(diào)試器直接訪問內(nèi)存有影響。同樣,這些考慮因素也不屬于編譯器的職責(zé)范圍,因此在優(yōu)化和/或代碼生成期間不會(huì)考慮。
現(xiàn)在假設(shè)代碼保持不變,但是在呈現(xiàn)給編譯器的代碼中其上下文發(fā)生了微小的變化,如圖3所示。
圖3:代碼保持不變,但是提供給編譯器的代碼中的上下文略有變化
現(xiàn)在有一個(gè)附加函數(shù),該函數(shù)以整數(shù)形式返回狀態(tài)變量的值。這次,絕對(duì)值13和23在提交給編譯器的代碼中很重要。即使這樣,這些值也不會(huì)在更新函數(shù)中進(jìn)行操作(保持不變),并且僅在新的“ f”函數(shù)中可見。
簡而言之,編譯器繼續(xù)(正確地)對(duì)應(yīng)該使用13和23的值進(jìn)行價(jià)值判斷,并且絕不會(huì)將它們應(yīng)用于可能的所有情況。
如果更改了新功能以返回指向我們狀態(tài)變量的指針,則匯編代碼將發(fā)生重大變化。由于現(xiàn)在存在通過指針進(jìn)行別名訪問的可能性,因此編譯器無法再推斷出狀態(tài)對(duì)象正在發(fā)生的情況。如下圖4所示,它不能得出13和23的值不重要的結(jié)論,因此現(xiàn)在可以在匯編器中明確表示它們。
圖4:如果將新函數(shù)更改為返回指向我們的狀態(tài)變量的指針,則匯編代碼將發(fā)生重大變化。它不能得出結(jié)論13和23的值并不重要,因此它們現(xiàn)在已在匯編程序中明確表示
對(duì)源代碼單元測試的影響
現(xiàn)在,在虛構(gòu)的單元測試工具的上下文中考慮示例。由于需要一種工具來訪問被測代碼,因此會(huì)操縱狀態(tài)變量的值,因此默認(rèn)值不會(huì)“被優(yōu)化”。這種方法在沒有與源代碼其余部分相關(guān)的上下文并且需要使所有內(nèi)容都可訪問的測試工具中是完全合理的,但是,其副作用是,它可以掩蓋編譯器對(duì)防御性代碼的合法遺漏。
編譯器認(rèn)識(shí)到已通過指針將任意值寫入狀態(tài)變量,并且不能再次得出13和23的值不重要的結(jié)論。因此,它們現(xiàn)在在匯編器中明確表示。在這種情況下,不能得出結(jié)論:S0和S1代表狀態(tài)變量的唯一可能值,這意味著默認(rèn)路徑可能可行。如圖5所示,狀態(tài)變量的操作達(dá)到了目的,并且在匯編器中現(xiàn)在可以明顯看到對(duì)錯(cuò)誤函數(shù)的調(diào)用。
圖5:狀態(tài)變量的操作已達(dá)到其目的,并且錯(cuò)誤函數(shù)的調(diào)用現(xiàn)在在匯編程序中顯而易見
但是,這種操作不會(huì)出現(xiàn)在產(chǎn)品內(nèi)隨附的代碼中,因此對(duì)error()的調(diào)用實(shí)際上不在整個(gè)系統(tǒng)中。
目標(biāo)代碼驗(yàn)證的重要性
為了說明目標(biāo)代碼驗(yàn)證如何幫助解決這個(gè)難題,請(qǐng)?jiān)俅慰紤]第一個(gè)示例代碼片段,如圖6所示:
圖6:這說明了目標(biāo)代碼驗(yàn)證如何幫助解決錯(cuò)誤提示在整個(gè)系統(tǒng)中的作用
通過一次調(diào)用,可以證明此C代碼實(shí)現(xiàn)了100%的源代碼覆蓋率,因此:
f_while4(0,3);
可以將代碼重新格式化為每行單個(gè)操作,并在流程圖上表示為“基本塊”節(jié)點(diǎn)的集合,每個(gè)節(jié)點(diǎn)都是一系列直線代碼。基本塊之間的關(guān)系在圖7中使用節(jié)點(diǎn)之間的有向邊表示。
圖7:使用節(jié)點(diǎn)之間的有向邊顯示基本塊之間的關(guān)系
編譯代碼后,結(jié)果如下所示(圖8)。流程圖的藍(lán)色元素表示調(diào)用f_while4(0,3)尚未執(zhí)行的代碼。
通過利用目標(biāo)代碼與匯編代碼之間的一對(duì)一關(guān)系,此機(jī)制可以揭示目標(biāo)代碼的哪些部分未被執(zhí)行,從而促使測試人員設(shè)計(jì)其他測試并實(shí)現(xiàn)完整的匯編代碼覆蓋范圍,從而實(shí)現(xiàn)目標(biāo)代碼驗(yàn)證。
圖8:顯示了編譯代碼后的結(jié)果。流程圖的藍(lán)色元素表示調(diào)用f_while4(0,3)尚未執(zhí)行的代碼
顯然,目標(biāo)代碼驗(yàn)證無權(quán)阻止編譯器遵循其設(shè)計(jì)規(guī)則,并無意中繞開了開發(fā)人員的最佳意圖。但這確實(shí)可以并且確實(shí)會(huì)引起任何此類失配,引起粗心的人的注意。
現(xiàn)在,在前面的“錯(cuò)誤提示”示例的上下文中考慮該原理。當(dāng)然,完整系統(tǒng)中的源代碼將與在單元測試級(jí)別上證明的源代碼相同,因此,將其進(jìn)行比較不會(huì)發(fā)現(xiàn)任何問題。但是,將目標(biāo)代碼驗(yàn)證應(yīng)用于完整的系統(tǒng)對(duì)于確保基本行為按照開發(fā)人員的意圖進(jìn)行表達(dá)將具有極大的價(jià)值。