郭思雨,王 磊
(中原工學院前沿信息技術研究院,河南 鄭州 450007)
目前,浮點計算被廣泛應用于各個領域,現有的計算機硬件設計及IEEE-754[1]標準,決定了浮點數是實數的有限精度編碼[2],不能精確表示出實數,在進行浮點計算時,可能會導致不精確或者異常的結果。由于浮點數轉整數出現的整數溢出異常,歐洲Ariane 5火箭在1996年發射時出現了嚴重的升空自爆現象[3],造成了巨額的經濟損失。因此,提前發現和規避,是目前解決浮點計算異常問題的關鍵。
能夠對異常處理起到指導作用的異常檢測方面的研究也在蓬勃發展。當前的測試研究可以分為2類:(1)對浮點異常的研究。文獻[2]提出了利用值-范圍分析來加速浮點異常檢測的符號執行;文獻[4]提出的Ariadne,使用實數算法對變換后的程序進行符號執行,以發現可能到達或觸發異常的候選實數輸入集;文獻[5]提出的FPChecker(Floating-Point Checker),使用Clang/LLVM(Low Level Virtual Machine)編譯器檢測GPU內核并在運行時檢測異常。這類方法多基于符號執行方法,符號執行是一種經典的程序分析技術,它使用符號輸入來探索可行的程序路徑,但是使用符號執行技術檢測浮點異常的代價是昂貴的。(2)對整數溢出的研究。文獻[6]基于動態檢測技術,通過檢測所有可能產生溢出的操作實現了RICH(Run-time Integer CHecking)工具[6];盧錫城等人[7]提出了一種二進制高危整數溢出錯誤的全自動測試方法DAIDT(Dynamic Automatic Integer-overflow Detection and Testing);Brick[8]能夠檢測和定位真實軟件中的大部分基于整數的漏洞,并具有較低的誤報率;文獻[9]基于靜態區間分析,提出了利用future bounds對變量進行處理的整數溢出分析算法。
綜上所述,現有的方法不是針對浮點數學函數而設計的,其研究重點集中在整數溢出錯誤,而浮點函數的運算降低了整數溢出存在的可能性。在申威1621平臺上也沒有專門針對浮點數學函數的異常檢測方法,并且目前的研究中對于檢測結果的全面性沒有進行相關的評估。鑒于此,面向基于匯編實現的浮點數學函數[10],本文提出了一種針對浮點數學函數的異常檢測方法。該檢測方法對浮點異常類型進行分類后,在編譯函數源碼時進行插樁。該方法能自動檢測異常、用生成的測試用例對函數進行全面檢測的同時記錄了代碼覆蓋率。
浮點環境由數據結構和運算組成,并通過硬件、系統軟件和軟件庫提供給程序員,實現了IEEE-754標準。浮點異常(即異常)是指在浮點算術運算中出現的異常,即IEEE-754標準中定義的5種類型的浮點異常:無效操作、被零除、上溢、下溢和不精確異常。浮點函數是指申威高性能基礎數學函數庫中的浮點數學函數,包括三角函數、指數函數、對數函數和雙曲函數等初等函數。代碼覆蓋率描述了測試數據作為輸入時函數的運行情況,通過代碼覆蓋率可以對該檢測過程進行評價。
本文提出的浮點異常檢測方法是指在測試階段對函數程序進行必要的插樁修改,對其內部的運算指令進行異常檢測。
浮點控制寄存器FPCR(Floating-Point Control Register)包含浮點異常的狀態、浮點舍入模式、浮點異常自陷控制和特殊數據的控制等。浮點控制寄存器FPCR有64位,其中[59:58]位是動態舍入模式位;[57:52]位記錄浮點運算指令產生的6種算術異常,也記錄浮點SIMD(Single Instruction Multiple Data)運算指令中第0個元素進行運算產生的6種算術異常;[40:36]位、[24:20]位和[8:4]位分別表示浮點SIMD運算指令中第1、第2和第3元素進行運算產生的5種算術異常,由于本文所涉及到的函數為標量函數,所以這些位暫時作為保留位;[63]位是算術異常的總標志位,指示是否存在異常。該寄存器通過浮點指令WFPCR和RFPCR進行訪問。
申威1621處理器支持IEEE-754標準定義的5種浮點算術異常,本文研究的浮點數學函數主要包括一系列初等函數[11],如三角函數、指數函數和對數函數等。本節對浮點算術異常進行相應分類檢測,由于產生上溢異常只可能是使浮點數增大的運算或操作,產生下溢異常只可能是使浮點數減小的運算或操作,因此,通過對faddd、fmuld等指令的檢測判斷是否產生上溢,通過對fsubd、fdivd等指令的檢測判斷是否產生下溢(其中還需對fdivd指令進行被零除異常的檢測),通過對輸入參數的檢測判斷是否觸發無效操作異常。如果舍入后的結果與舍入前的真值不一致,或舍入時產生了上溢而申威1621處理器實現中無上溢自陷,則產生非精確結果(Inexact Result)異常。由于不精確經常發生[4,12],并且通常是有限精度不可避免的結果,例如,當1.0除以3.0時,會出現不精確的異常,因為比率1/3不能精確表示為浮點數,因此暫不針對不精確異常進行分析。通過以上3種分類對浮點計算異常進行具體檢測。下面針對這3種分類進行詳細說明:
(1) 無效操作異常檢測。
若當前操作的一個操作數為非有限數或對要執行的操作而言是非法的(浮點比較指令的操作數為無窮大時不產生自陷),或用戶輸入的參數不滿足參數域的范圍或輸入參數類型不匹配(如將雙精度數傳給單精度函數)等操作,將會產生無效操作異常。由于無效操作異常產生在用戶輸入參數階段,因此可在進入函數正常運算前對用戶的輸入進行檢查,避免浮點數的特殊數參與到浮點運算中引發浮點算術異常,影響浮點函數的可靠性。浮點數的特殊數包括SNaN(Signaling Not a Number)、QNaN(Quiet Not a Number)、Infinity和Denormal等無法正常處理的數據。SNaN一般用于標記未初始化的值,QNaN一般表示未定義的算術運算結果。
在進入函數計算之前,對函數的定義域及輸入參數進行對比檢測,判斷參數是否符合函數定義域,判斷是否產生無效操作異常和非規格化數異常。檢測輸入參數是否為特殊數SNaN和QNaN的源碼為:
1. fcmpeq $f16,$f16,$f14
2. fbeq $f14,L$9
3.L$9:
4. faddd $f16,$f16,$f0
5. ret
非數NaN(Not a Number)是計算機科學中一類數值數據類型的值,表示未定義或不可表示的值,常在浮點數運算中使用。因為NaN是一個范圍,而不能代表一個確定的值,因此利用NaN!=NaN對輸入參數$f16進行判斷,如果$f16!=$f16,則該輸入參數為非數。其它特殊數可以通過參數定義域范圍檢測,在函數開始運算之前,把函數定義域放入數據表內。判斷輸入參數是否在函數定義域內,若在,則繼續參與運算;若不在,則直接返回,并報出無效操作異常。
(2)上溢異常檢測。
上溢異常是在舍入過程中產生的,當舍入結果的幅值(絕對值)超過目標格式的最大有限值時即產生上溢異常。標量浮點運算指令或SIMD浮點運算指令中的第0個元素進行浮點算術運算或轉換操作的結果產生上溢時,FPCR寄存器的第54位置1。只有可以使浮點數增大的浮點運算指令才可能會導致上溢異常的出現,因此針對申威1621處理器中的相關運算指令進行檢測,如faddd、fmuld和fmad等浮點運算指令。
根據浮點控制寄存器FPCR對應的上溢異常位進行檢測,具體內容如下所示:
①檢測前的源碼:
…
1. faddd $f16,$f1,$f10
…
②插入檢測代碼后的源碼:
…
1. rfpcr $f8
2. fimovd $f8,$1
3. faddd $f16,$f1,$f10//需要檢測的操作
4. rfpcr $f9
5. fimovd $f9,$2
6. sll $1,9,$1
7. srl $1,63,$1//取出FPCR對應上溢異常位
8. sll $2,9,$2
9. srl $2,63,$2
10. cmpeq $1,$2,$3/*判斷對比上溢異常位是否發生變化*/
11. beq $3,L$1/*如果觸發上溢異常,則跳轉至L$1分支,記錄該異常*/
12.L$1:
13. call detection//調用該函數記錄異常具體信息
…
其中,faddd為浮點加運算;rfpcr為讀取當前浮點控制寄存器FPCR的值;fimovd為雙精度浮點數傳送到整數;cmpeq是等于比較。
以上代碼,具體含義為:在源碼編譯時,檢測函數中是否有faddd或fmuld等匯編指令。若有,則將以上內容放置到該指令前后,對比判斷是否產生上溢異常。若產生異常,則調用detection函數,記錄異常的具體信息,如輸入參數、異常類型等信息;若不產生異常,則繼續執行其余代碼。
(3) 下溢及被零除異常檢測。
下溢異常是在舍入過程中產生的,當舍入結果的幅值(絕對值)小于目標格式的最小有限值時即產生下溢異常。標量浮點運算指令或SIMD浮點運算指令中的第0個元素進行浮點算術運算或轉換操作的結果產生下溢時,FPCR寄存器的第55位置1。只有可以使浮點數減小的浮點運算指令可能會導致下溢異常的出現,因此針對申威1621處理器中的相關運算指令進行檢測,如fsubd、fdivd等浮點運算指令。
被零除異常為當除數為0 ,而被除數為有限數時觸發的異常,也泛指有限數運算導致無窮結果的異常,例如4.0/0.0,log(0.0)等。在浮點數學函數實際運算過程中,一般只被除法指令FDIVS/FDIVD觸發。被零除異常的檢測可以在檢測到除法指令時,若被除數是一個不會引起無效操作異常的數據,則對除數進行判斷,若除數為0,則報出被零除異常。下溢異常的檢測與上溢異常的檢測方法基本一致,需要檢測是否產生下溢時,在使浮點數減小的浮點運算指令前后檢測FPCR寄存器第55位是否發生變化(由0變為1)。
檢測的主要流程為在源碼編譯時動態檢測相關指令,檢測到相關指令后,對其前后進行插樁,插樁內容為檢測指令運算前后FPCR的值,運行時對比FPCR對應異常位是否發生變化。如果發生改變,記錄并輸出當前對應的異常類型、相應的指令操作及輸入參數至文件內。具體檢測過程如圖1所示。此外,也可以將異常觸發的條件當作插樁的內容,放在相應的指令操作之后,判斷是否觸發異常,如通過對比結果是否大于相應浮點類型能表示的最大值來判斷是否產生上溢。除通過檢測浮點控制寄存器FPCR值的變化外,還可以通過IEEE-754標準定義的產生異常的條件進行檢測。

Figure 1 Detection process analysis圖1 檢測過程分析
采用插樁方法檢測異常的目的是快速檢測出使函數觸發異常的輸入參數范圍以及異常的具體信息,減少后期定位異常的工作量。本文使用的插樁方法是在源碼的編譯過程中,對生成的匯編指令進行插樁,保證任何函數都可以被檢測。插樁的位置在函數實現源碼中對浮點數的運算或者轉換指令中,對指令的所在行前后進行插樁,通過對比運算前后FPCR的值判斷函數在此處是否觸發異常。在檢測到異常發生時,可選擇終止檢測或繼續執行其余代碼。選擇繼續執行其余代碼前,需調用detection函數對此時檢測到的異常信息進行記錄,以便程序員檢查程序中出現的所有異常(不僅僅是第一個),即當發現異常時,輸出一份簡短的報告,程序繼續執行,不會中止。
檢測浮點數學函數實現源碼的異常時,除了直接對浮點數學函數源碼的相應指令進行插樁檢測外,有些異常可以通過對具體異常的產生條件分析出來。如,被零除異常和整數溢出異常的可能觸發條件可以限定為FDIVS/FDIVD及FCVTDL/FCVTLW這4條指令的執行,由于可能觸發這兩類異常的情況較少,可以先檢測函數中是否有以上4條指令,對檢測內容進行進一步細化。具體而言,可以通過對除法指令FDIVS/FDIVD的搜索,實現對被零除異常的檢測;通過對轉換指令FCVTDL/FCVTLW的搜索,實現對整數溢出異常的檢測。
測試用例是用于檢測函數異常的輸入數據集,通過輸入該數據集,對浮點數學函數進行大規模的檢測。IEEE-754標準規定,對于32位的浮點數,最高的1位是符號位S,接著的8位是指數位E,剩下的23位為有效數字位M;對于64位的浮點數,最高的1位是符號位S,接著的11位是指數位E,剩下的52位為有效數字位M。本文根據IEEE-754浮點數的規定及浮點數的理論分布生成測試用例。測試用例的生成規則:(1) 符號位[13],根據定義域區間,判斷測試用例的正負屬性;(2) 指數位,覆蓋輸入區間內所有浮點數的指數;(3) 尾數位,針對每一個指數值,產生N個均勻分布的隨機數(相對于浮點數分布的基本特征均勻)。上述3部分構成完整的測試用例,保證了測試用例的完整性和有效性,為下一步檢測做好了充分的數據準備。
代碼覆蓋率是一種度量,它描述了對程序源代碼的測試程度[14]。這是白盒測試的一種手段,它可以發現測試用例無法覆蓋到的程序。測試人員可以創建代碼覆蓋缺失的測試用例,以提高覆蓋率并確定代碼覆蓋率的定量度量。在大多數情況下,代碼覆蓋系統會收集有關正在運行程序的信息。它還將其與源代碼信息相結合,以生成有關測試套件的代碼覆蓋率報告。代碼覆蓋率可以幫助評估測試的效率,提供定量測量手段,可以了解對代碼的測試程度。
統計代碼覆蓋率可以明確測試過程中軟件的運行情況,從而對測試結果進行評估。在本文中使用代碼覆蓋率的目的是為了在檢測過程中,通過查看代碼覆蓋率來檢驗對各個函數檢測的全面性,避免漏報。可以根據代碼覆蓋率相應調整測試用例,以達到對浮點數學函數全面檢測的目的。
本文在AFL(American Fuzzy Lop)的基礎上對代碼覆蓋率部分進行移植修改,使其能夠在申威平臺上正常使用。統計代碼覆蓋率的主要流程如圖2所示。左上圖表示插樁前的一個函數,b0 (block 0)表示一個基本塊,即程序順序執行的語句序列。在每個基本塊的開始插入代碼覆蓋率計算指令。計算指令的內容為表示一個塊的隨機數和命中處理函數。當運行測試程序時,把隨機數運算的結果作為地址,該地址指向的內容加一完成記錄。用隨機數R0表示基本塊b0的標識,用*((R0?1)^R1)++表示從基本塊b0跳轉到基本塊b1執行了一次。詳細解釋如圖2所示。

Figure 2 Code coverage process圖2 代碼覆蓋率流程
本文將實現的浮點異常檢測方法應用于申威高性能數學函數庫中的浮點數學函數。函數庫由基礎函數庫及SIMD擴展數學庫組成,本文主要研究基礎數學庫,基礎數學庫的函數分類主要包括三角函數、反三角函數、指數對數函數、貝塞爾函數及其他函數等初等函數。在下面的檢測中,對部分函數的檢測結果進行了分析,包括sin、cos、logf等函數。
根據申威1621處理器對浮點運算的定義及對浮點控制寄存器FPCR的設置,當輸入操作數沒有產生異常時,對正常浮點運算的中間結果,先進行舍入,后根據不同的舍入方式來判斷異常。結果產生異常時,會在FPCR中記錄相應異常標志位。以此對該基礎數學函數庫進行檢測。在用于檢測的測試用例生成后,開始對程序進行大規模檢測。
首先,在相應的浮點域內,生成均勻分布的浮點數數據。該均勻分布的數據集基于浮點數分布的基本特征[15],符合IEEE-754浮點數的理論分布:數字越接近零,數據分布越密集;數字離零越遠,數據分布越稀疏。其次,在完成對上一步生成的數據測試后,在測試結果文件內,查看函數在哪些數據或哪些位置產生異常的頻率較高,分析出異常熱點數據。以此熱點數據為數據中心,在該數據周圍生成大量數據集用于進一步測試。最后,在某一數據周圍生成了測試集后,對函數進行有針對性的大規模測試,得出最終結果。測試用例的完整性和有效性對測試的代碼覆蓋率有一定影響。檢測具體流程如圖3所示。

Figure 3 Test flow chart圖3 檢測流程圖
(1) 按照上述檢測流程,如表1所示為部分函數檢測過程中產生異常的參數及其異常類型。代碼覆蓋率的結果為數據集在測試過程中記錄函數中的代碼被測試到的比例。代碼覆蓋率越高,測試結果的可信度越高。對代碼覆蓋率的統計結果顯示,本文生成的測試集可以檢測到函數的所有代碼分支。開發人員可以根據該結果對函數進行進一步處理。

Table 1 Test results
(2) 152個函數中各異常(上溢、下溢、被零除和無效操作)出現的函數個數,統計結果如表2所示。在檢測時,發現輸入特殊數NaN并沒有觸發無效操作異常,即函數未對特殊數NaN進行處理。在此檢測結果基礎上,對NaN進行了特殊數的處理,處理主要依據NaN是唯一一個與自身不相等的存在。檢測到異常進行報告是必須的,收到報告進行處理可以最大程度降低發生意外的可能。

Table 2 Exception functions
(3) 加入插樁檢測后的性能變化。
圖4對部分有上溢異常的函數插樁檢測前后的性能進行了對比,節拍數的變化如圖4所示。性能測試結果顯示:經過插樁檢測后,函數的平均性能降低了38.43%((插樁后節拍數-插樁前節拍數)/插樁前節拍數);插樁后的運行速度雖然有下降,但對于大多數函數而言,進行插樁異常檢測帶來了幾拍到幾十拍的性能消耗,在可接受的范圍內。

Figure 4 Performance comparison before and after pile insertion testing圖4 插樁檢測前后的性能對比
需要特別說明的是,檢測出的異常是否會對系統造成重大不良影響,需要開發人員對檢測出的異常進行進一步的分析。浮點數的表示精度有限,不能精確表示出實數,部分異常是由可表示精度有限導致的。因此,開發人員可以通過分析檢測出異常信息,對異常進行進一步的判斷。
上述測試結果表明,該浮點異常檢測方法科學有效,插樁后的函數具有檢測浮點異常的功能。既滿足了對浮點數學函數異常的檢測,也滿足了對函數性能干擾盡量小的要求。實驗數據表明,上溢出和下溢出異常在所有發生的異常中占有較大的比例,對特殊數的檢測中無效操作異常發生較多,被零除異常發生較少。當面臨大量復雜程序時,檢測的計算量變大,將會導致性能下降較多,這點后續需繼續優化。本文提出了浮點異常分類檢測方法,以盡可能全面地在測試階段發現異常。
本文實現了一種檢測浮點數學函數異常并統計測試的代碼覆蓋率的方法。通過測試,該方法能夠有效地檢測出函數中出現的異常,同時對浮點數學函數的性能影響較小。本文提出的檢測浮點異常的編譯時插樁及異常分類方法也可以用于其他平臺檢測其他內容,具有一定的通用性。