郭思雨,王 磊
(中原工學院前沿信息技術研究院,河南 鄭州 450007)
目前,浮點計算被廣泛應用于各個領域,現(xiàn)有的計算機硬件設計及IEEE-754[1]標準,決定了浮點數(shù)是實數(shù)的有限精度編碼[2],不能精確表示出實數(shù),在進行浮點計算時,可能會導致不精確或者異常的結果。由于浮點數(shù)轉整數(shù)出現(xiàn)的整數(shù)溢出異常,歐洲Ariane 5火箭在1996年發(fā)射時出現(xiàn)了嚴重的升空自爆現(xiàn)象[3],造成了巨額的經(jīng)濟損失。因此,提前發(fā)現(xiàn)和規(guī)避,是目前解決浮點計算異常問題的關鍵。
能夠對異常處理起到指導作用的異常檢測方面的研究也在蓬勃發(fā)展。當前的測試研究可以分為2類:(1)對浮點異常的研究。文獻[2]提出了利用值-范圍分析來加速浮點異常檢測的符號執(zhí)行;文獻[4]提出的Ariadne,使用實數(shù)算法對變換后的程序進行符號執(zhí)行,以發(fā)現(xiàn)可能到達或觸發(fā)異常的候選實數(shù)輸入集;文獻[5]提出的FPChecker(Floating-Point Checker),使用Clang/LLVM(Low Level Virtual Machine)編譯器檢測GPU內核并在運行時檢測異常。這類方法多基于符號執(zhí)行方法,符號執(zhí)行是一種經(jīng)典的程序分析技術,它使用符號輸入來探索可行的程序路徑,但是使用符號執(zhí)行技術檢測浮點異常的代價是昂貴的。(2)對整數(shù)溢出的研究。文獻[6]基于動態(tài)檢測技術,通過檢測所有可能產(chǎn)生溢出的操作實現(xiàn)了RICH(Run-time Integer CHecking)工具[6];盧錫城等人[7]提出了一種二進制高危整數(shù)溢出錯誤的全自動測試方法DAIDT(Dynamic Automatic Integer-overflow Detection and Testing);Brick[8]能夠檢測和定位真實軟件中的大部分基于整數(shù)的漏洞,并具有較低的誤報率;文獻[9]基于靜態(tài)區(qū)間分析,提出了利用future bounds對變量進行處理的整數(shù)溢出分析算法。
綜上所述,現(xiàn)有的方法不是針對浮點數(shù)學函數(shù)而設計的,其研究重點集中在整數(shù)溢出錯誤,而浮點函數(shù)的運算降低了整數(shù)溢出存在的可能性。在申威1621平臺上也沒有專門針對浮點數(shù)學函數(shù)的異常檢測方法,并且目前的研究中對于檢測結果的全面性沒有進行相關的評估。鑒于此,面向基于匯編實現(xiàn)的浮點數(shù)學函數(shù)[10],本文提出了一種針對浮點數(shù)學函數(shù)的異常檢測方法。該檢測方法對浮點異常類型進行分類后,在編譯函數(shù)源碼時進行插樁。該方法能自動檢測異常、用生成的測試用例對函數(shù)進行全面檢測的同時記錄了代碼覆蓋率。
浮點環(huán)境由數(shù)據(jù)結構和運算組成,并通過硬件、系統(tǒng)軟件和軟件庫提供給程序員,實現(xiàn)了IEEE-754標準。浮點異常(即異常)是指在浮點算術運算中出現(xiàn)的異常,即IEEE-754標準中定義的5種類型的浮點異常:無效操作、被零除、上溢、下溢和不精確異常。浮點函數(shù)是指申威高性能基礎數(shù)學函數(shù)庫中的浮點數(shù)學函數(shù),包括三角函數(shù)、指數(shù)函數(shù)、對數(shù)函數(shù)和雙曲函數(shù)等初等函數(shù)。代碼覆蓋率描述了測試數(shù)據(jù)作為輸入時函數(shù)的運行情況,通過代碼覆蓋率可以對該檢測過程進行評價。
本文提出的浮點異常檢測方法是指在測試階段對函數(shù)程序進行必要的插樁修改,對其內部的運算指令進行異常檢測。
浮點控制寄存器FPCR(Floating-Point Control Register)包含浮點異常的狀態(tài)、浮點舍入模式、浮點異常自陷控制和特殊數(shù)據(jù)的控制等。浮點控制寄存器FPCR有64位,其中[59:58]位是動態(tài)舍入模式位;[57:52]位記錄浮點運算指令產(chǎn)生的6種算術異常,也記錄浮點SIMD(Single Instruction Multiple Data)運算指令中第0個元素進行運算產(chǎn)生的6種算術異常;[40:36]位、[24:20]位和[8:4]位分別表示浮點SIMD運算指令中第1、第2和第3元素進行運算產(chǎn)生的5種算術異常,由于本文所涉及到的函數(shù)為標量函數(shù),所以這些位暫時作為保留位;[63]位是算術異常的總標志位,指示是否存在異常。該寄存器通過浮點指令WFPCR和RFPCR進行訪問。
申威1621處理器支持IEEE-754標準定義的5種浮點算術異常,本文研究的浮點數(shù)學函數(shù)主要包括一系列初等函數(shù)[11],如三角函數(shù)、指數(shù)函數(shù)和對數(shù)函數(shù)等。本節(jié)對浮點算術異常進行相應分類檢測,由于產(chǎn)生上溢異常只可能是使浮點數(shù)增大的運算或操作,產(chǎn)生下溢異常只可能是使浮點數(shù)減小的運算或操作,因此,通過對faddd、fmuld等指令的檢測判斷是否產(chǎn)生上溢,通過對fsubd、fdivd等指令的檢測判斷是否產(chǎn)生下溢(其中還需對fdivd指令進行被零除異常的檢測),通過對輸入?yún)?shù)的檢測判斷是否觸發(fā)無效操作異常。如果舍入后的結果與舍入前的真值不一致,或舍入時產(chǎn)生了上溢而申威1621處理器實現(xiàn)中無上溢自陷,則產(chǎn)生非精確結果(Inexact Result)異常。由于不精確經(jīng)常發(fā)生[4,12],并且通常是有限精度不可避免的結果,例如,當1.0除以3.0時,會出現(xiàn)不精確的異常,因為比率1/3不能精確表示為浮點數(shù),因此暫不針對不精確異常進行分析。通過以上3種分類對浮點計算異常進行具體檢測。下面針對這3種分類進行詳細說明:
(1) 無效操作異常檢測。
若當前操作的一個操作數(shù)為非有限數(shù)或對要執(zhí)行的操作而言是非法的(浮點比較指令的操作數(shù)為無窮大時不產(chǎn)生自陷),或用戶輸入的參數(shù)不滿足參數(shù)域的范圍或輸入?yún)?shù)類型不匹配(如將雙精度數(shù)傳給單精度函數(shù))等操作,將會產(chǎn)生無效操作異常。由于無效操作異常產(chǎn)生在用戶輸入?yún)?shù)階段,因此可在進入函數(shù)正常運算前對用戶的輸入進行檢查,避免浮點數(shù)的特殊數(shù)參與到浮點運算中引發(fā)浮點算術異常,影響浮點函數(shù)的可靠性。浮點數(shù)的特殊數(shù)包括SNaN(Signaling Not a Number)、QNaN(Quiet Not a Number)、Infinity和Denormal等無法正常處理的數(shù)據(jù)。SNaN一般用于標記未初始化的值,QNaN一般表示未定義的算術運算結果。
在進入函數(shù)計算之前,對函數(shù)的定義域及輸入?yún)?shù)進行對比檢測,判斷參數(shù)是否符合函數(shù)定義域,判斷是否產(chǎn)生無效操作異常和非規(guī)格化數(shù)異常。檢測輸入?yún)?shù)是否為特殊數(shù)SNaN和QNaN的源碼為:
1. fcmpeq $f16,$f16,$f14
2. fbeq $f14,L$9
3.L$9:
4. faddd $f16,$f16,$f0
5. ret
非數(shù)NaN(Not a Number)是計算機科學中一類數(shù)值數(shù)據(jù)類型的值,表示未定義或不可表示的值,常在浮點數(shù)運算中使用。因為NaN是一個范圍,而不能代表一個確定的值,因此利用NaN!=NaN對輸入?yún)?shù)$f16進行判斷,如果$f16!=$f16,則該輸入?yún)?shù)為非數(shù)。其它特殊數(shù)可以通過參數(shù)定義域范圍檢測,在函數(shù)開始運算之前,把函數(shù)定義域放入數(shù)據(jù)表內。判斷輸入?yún)?shù)是否在函數(shù)定義域內,若在,則繼續(xù)參與運算;若不在,則直接返回,并報出無效操作異常。
(2)上溢異常檢測。
上溢異常是在舍入過程中產(chǎn)生的,當舍入結果的幅值(絕對值)超過目標格式的最大有限值時即產(chǎn)生上溢異常。標量浮點運算指令或SIMD浮點運算指令中的第0個元素進行浮點算術運算或轉換操作的結果產(chǎn)生上溢時,F(xiàn)PCR寄存器的第54位置1。只有可以使浮點數(shù)增大的浮點運算指令才可能會導致上溢異常的出現(xiàn),因此針對申威1621處理器中的相關運算指令進行檢測,如faddd、fmuld和fmad等浮點運算指令。
根據(jù)浮點控制寄存器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/*判斷對比上溢異常位是否發(fā)生變化*/
11. beq $3,L$1/*如果觸發(fā)上溢異常,則跳轉至L$1分支,記錄該異常*/
12.L$1:
13. call detection//調用該函數(shù)記錄異常具體信息
…
其中,faddd為浮點加運算;rfpcr為讀取當前浮點控制寄存器FPCR的值;fimovd為雙精度浮點數(shù)傳送到整數(shù);cmpeq是等于比較。
以上代碼,具體含義為:在源碼編譯時,檢測函數(shù)中是否有faddd或fmuld等匯編指令。若有,則將以上內容放置到該指令前后,對比判斷是否產(chǎn)生上溢異常。若產(chǎn)生異常,則調用detection函數(shù),記錄異常的具體信息,如輸入?yún)?shù)、異常類型等信息;若不產(chǎn)生異常,則繼續(xù)執(zhí)行其余代碼。
(3) 下溢及被零除異常檢測。
下溢異常是在舍入過程中產(chǎn)生的,當舍入結果的幅值(絕對值)小于目標格式的最小有限值時即產(chǎn)生下溢異常。標量浮點運算指令或SIMD浮點運算指令中的第0個元素進行浮點算術運算或轉換操作的結果產(chǎn)生下溢時,F(xiàn)PCR寄存器的第55位置1。只有可以使浮點數(shù)減小的浮點運算指令可能會導致下溢異常的出現(xiàn),因此針對申威1621處理器中的相關運算指令進行檢測,如fsubd、fdivd等浮點運算指令。
被零除異常為當除數(shù)為0 ,而被除數(shù)為有限數(shù)時觸發(fā)的異常,也泛指有限數(shù)運算導致無窮結果的異常,例如4.0/0.0,log(0.0)等。在浮點數(shù)學函數(shù)實際運算過程中,一般只被除法指令FDIVS/FDIVD觸發(fā)。被零除異常的檢測可以在檢測到除法指令時,若被除數(shù)是一個不會引起無效操作異常的數(shù)據(jù),則對除數(shù)進行判斷,若除數(shù)為0,則報出被零除異常。下溢異常的檢測與上溢異常的檢測方法基本一致,需要檢測是否產(chǎn)生下溢時,在使浮點數(shù)減小的浮點運算指令前后檢測FPCR寄存器第55位是否發(fā)生變化(由0變?yōu)?)。
檢測的主要流程為在源碼編譯時動態(tài)檢測相關指令,檢測到相關指令后,對其前后進行插樁,插樁內容為檢測指令運算前后FPCR的值,運行時對比FPCR對應異常位是否發(fā)生變化。如果發(fā)生改變,記錄并輸出當前對應的異常類型、相應的指令操作及輸入?yún)?shù)至文件內。具體檢測過程如圖1所示。此外,也可以將異常觸發(fā)的條件當作插樁的內容,放在相應的指令操作之后,判斷是否觸發(fā)異常,如通過對比結果是否大于相應浮點類型能表示的最大值來判斷是否產(chǎn)生上溢。除通過檢測浮點控制寄存器FPCR值的變化外,還可以通過IEEE-754標準定義的產(chǎn)生異常的條件進行檢測。
Figure 1 Detection process analysis圖1 檢測過程分析
采用插樁方法檢測異常的目的是快速檢測出使函數(shù)觸發(fā)異常的輸入?yún)?shù)范圍以及異常的具體信息,減少后期定位異常的工作量。本文使用的插樁方法是在源碼的編譯過程中,對生成的匯編指令進行插樁,保證任何函數(shù)都可以被檢測。插樁的位置在函數(shù)實現(xiàn)源碼中對浮點數(shù)的運算或者轉換指令中,對指令的所在行前后進行插樁,通過對比運算前后FPCR的值判斷函數(shù)在此處是否觸發(fā)異常。在檢測到異常發(fā)生時,可選擇終止檢測或繼續(xù)執(zhí)行其余代碼。選擇繼續(xù)執(zhí)行其余代碼前,需調用detection函數(shù)對此時檢測到的異常信息進行記錄,以便程序員檢查程序中出現(xiàn)的所有異常(不僅僅是第一個),即當發(fā)現(xiàn)異常時,輸出一份簡短的報告,程序繼續(xù)執(zhí)行,不會中止。
檢測浮點數(shù)學函數(shù)實現(xiàn)源碼的異常時,除了直接對浮點數(shù)學函數(shù)源碼的相應指令進行插樁檢測外,有些異??梢酝ㄟ^對具體異常的產(chǎn)生條件分析出來。如,被零除異常和整數(shù)溢出異常的可能觸發(fā)條件可以限定為FDIVS/FDIVD及FCVTDL/FCVTLW這4條指令的執(zhí)行,由于可能觸發(fā)這兩類異常的情況較少,可以先檢測函數(shù)中是否有以上4條指令,對檢測內容進行進一步細化。具體而言,可以通過對除法指令FDIVS/FDIVD的搜索,實現(xiàn)對被零除異常的檢測;通過對轉換指令FCVTDL/FCVTLW的搜索,實現(xiàn)對整數(shù)溢出異常的檢測。
測試用例是用于檢測函數(shù)異常的輸入數(shù)據(jù)集,通過輸入該數(shù)據(jù)集,對浮點數(shù)學函數(shù)進行大規(guī)模的檢測。IEEE-754標準規(guī)定,對于32位的浮點數(shù),最高的1位是符號位S,接著的8位是指數(shù)位E,剩下的23位為有效數(shù)字位M;對于64位的浮點數(shù),最高的1位是符號位S,接著的11位是指數(shù)位E,剩下的52位為有效數(shù)字位M。本文根據(jù)IEEE-754浮點數(shù)的規(guī)定及浮點數(shù)的理論分布生成測試用例。測試用例的生成規(guī)則:(1) 符號位[13],根據(jù)定義域區(qū)間,判斷測試用例的正負屬性;(2) 指數(shù)位,覆蓋輸入?yún)^(qū)間內所有浮點數(shù)的指數(shù);(3) 尾數(shù)位,針對每一個指數(shù)值,產(chǎn)生N個均勻分布的隨機數(shù)(相對于浮點數(shù)分布的基本特征均勻)。上述3部分構成完整的測試用例,保證了測試用例的完整性和有效性,為下一步檢測做好了充分的數(shù)據(jù)準備。
代碼覆蓋率是一種度量,它描述了對程序源代碼的測試程度[14]。這是白盒測試的一種手段,它可以發(fā)現(xiàn)測試用例無法覆蓋到的程序。測試人員可以創(chuàng)建代碼覆蓋缺失的測試用例,以提高覆蓋率并確定代碼覆蓋率的定量度量。在大多數(shù)情況下,代碼覆蓋系統(tǒng)會收集有關正在運行程序的信息。它還將其與源代碼信息相結合,以生成有關測試套件的代碼覆蓋率報告。代碼覆蓋率可以幫助評估測試的效率,提供定量測量手段,可以了解對代碼的測試程度。
統(tǒng)計代碼覆蓋率可以明確測試過程中軟件的運行情況,從而對測試結果進行評估。在本文中使用代碼覆蓋率的目的是為了在檢測過程中,通過查看代碼覆蓋率來檢驗對各個函數(shù)檢測的全面性,避免漏報??梢愿鶕?jù)代碼覆蓋率相應調整測試用例,以達到對浮點數(shù)學函數(shù)全面檢測的目的。
本文在AFL(American Fuzzy Lop)的基礎上對代碼覆蓋率部分進行移植修改,使其能夠在申威平臺上正常使用。統(tǒng)計代碼覆蓋率的主要流程如圖2所示。左上圖表示插樁前的一個函數(shù),b0 (block 0)表示一個基本塊,即程序順序執(zhí)行的語句序列。在每個基本塊的開始插入代碼覆蓋率計算指令。計算指令的內容為表示一個塊的隨機數(shù)和命中處理函數(shù)。當運行測試程序時,把隨機數(shù)運算的結果作為地址,該地址指向的內容加一完成記錄。用隨機數(shù)R0表示基本塊b0的標識,用*((R0?1)^R1)++表示從基本塊b0跳轉到基本塊b1執(zhí)行了一次。詳細解釋如圖2所示。
Figure 2 Code coverage process圖2 代碼覆蓋率流程
本文將實現(xiàn)的浮點異常檢測方法應用于申威高性能數(shù)學函數(shù)庫中的浮點數(shù)學函數(shù)。函數(shù)庫由基礎函數(shù)庫及SIMD擴展數(shù)學庫組成,本文主要研究基礎數(shù)學庫,基礎數(shù)學庫的函數(shù)分類主要包括三角函數(shù)、反三角函數(shù)、指數(shù)對數(shù)函數(shù)、貝塞爾函數(shù)及其他函數(shù)等初等函數(shù)。在下面的檢測中,對部分函數(shù)的檢測結果進行了分析,包括sin、cos、logf等函數(shù)。
根據(jù)申威1621處理器對浮點運算的定義及對浮點控制寄存器FPCR的設置,當輸入操作數(shù)沒有產(chǎn)生異常時,對正常浮點運算的中間結果,先進行舍入,后根據(jù)不同的舍入方式來判斷異常。結果產(chǎn)生異常時,會在FPCR中記錄相應異常標志位。以此對該基礎數(shù)學函數(shù)庫進行檢測。在用于檢測的測試用例生成后,開始對程序進行大規(guī)模檢測。
首先,在相應的浮點域內,生成均勻分布的浮點數(shù)數(shù)據(jù)。該均勻分布的數(shù)據(jù)集基于浮點數(shù)分布的基本特征[15],符合IEEE-754浮點數(shù)的理論分布:數(shù)字越接近零,數(shù)據(jù)分布越密集;數(shù)字離零越遠,數(shù)據(jù)分布越稀疏。其次,在完成對上一步生成的數(shù)據(jù)測試后,在測試結果文件內,查看函數(shù)在哪些數(shù)據(jù)或哪些位置產(chǎn)生異常的頻率較高,分析出異常熱點數(shù)據(jù)。以此熱點數(shù)據(jù)為數(shù)據(jù)中心,在該數(shù)據(jù)周圍生成大量數(shù)據(jù)集用于進一步測試。最后,在某一數(shù)據(jù)周圍生成了測試集后,對函數(shù)進行有針對性的大規(guī)模測試,得出最終結果。測試用例的完整性和有效性對測試的代碼覆蓋率有一定影響。檢測具體流程如圖3所示。
Figure 3 Test flow chart圖3 檢測流程圖
(1) 按照上述檢測流程,如表1所示為部分函數(shù)檢測過程中產(chǎn)生異常的參數(shù)及其異常類型。代碼覆蓋率的結果為數(shù)據(jù)集在測試過程中記錄函數(shù)中的代碼被測試到的比例。代碼覆蓋率越高,測試結果的可信度越高。對代碼覆蓋率的統(tǒng)計結果顯示,本文生成的測試集可以檢測到函數(shù)的所有代碼分支。開發(fā)人員可以根據(jù)該結果對函數(shù)進行進一步處理。
Table 1 Test results
(2) 152個函數(shù)中各異常(上溢、下溢、被零除和無效操作)出現(xiàn)的函數(shù)個數(shù),統(tǒng)計結果如表2所示。在檢測時,發(fā)現(xiàn)輸入特殊數(shù)NaN并沒有觸發(fā)無效操作異常,即函數(shù)未對特殊數(shù)NaN進行處理。在此檢測結果基礎上,對NaN進行了特殊數(shù)的處理,處理主要依據(jù)NaN是唯一一個與自身不相等的存在。檢測到異常進行報告是必須的,收到報告進行處理可以最大程度降低發(fā)生意外的可能。
Table 2 Exception functions
(3) 加入插樁檢測后的性能變化。
圖4對部分有上溢異常的函數(shù)插樁檢測前后的性能進行了對比,節(jié)拍數(shù)的變化如圖4所示。性能測試結果顯示:經(jīng)過插樁檢測后,函數(shù)的平均性能降低了38.43%((插樁后節(jié)拍數(shù)-插樁前節(jié)拍數(shù))/插樁前節(jié)拍數(shù));插樁后的運行速度雖然有下降,但對于大多數(shù)函數(shù)而言,進行插樁異常檢測帶來了幾拍到幾十拍的性能消耗,在可接受的范圍內。
Figure 4 Performance comparison before and after pile insertion testing圖4 插樁檢測前后的性能對比
需要特別說明的是,檢測出的異常是否會對系統(tǒng)造成重大不良影響,需要開發(fā)人員對檢測出的異常進行進一步的分析。浮點數(shù)的表示精度有限,不能精確表示出實數(shù),部分異常是由可表示精度有限導致的。因此,開發(fā)人員可以通過分析檢測出異常信息,對異常進行進一步的判斷。
上述測試結果表明,該浮點異常檢測方法科學有效,插樁后的函數(shù)具有檢測浮點異常的功能。既滿足了對浮點數(shù)學函數(shù)異常的檢測,也滿足了對函數(shù)性能干擾盡量小的要求。實驗數(shù)據(jù)表明,上溢出和下溢出異常在所有發(fā)生的異常中占有較大的比例,對特殊數(shù)的檢測中無效操作異常發(fā)生較多,被零除異常發(fā)生較少。當面臨大量復雜程序時,檢測的計算量變大,將會導致性能下降較多,這點后續(xù)需繼續(xù)優(yōu)化。本文提出了浮點異常分類檢測方法,以盡可能全面地在測試階段發(fā)現(xiàn)異常。
本文實現(xiàn)了一種檢測浮點數(shù)學函數(shù)異常并統(tǒng)計測試的代碼覆蓋率的方法。通過測試,該方法能夠有效地檢測出函數(shù)中出現(xiàn)的異常,同時對浮點數(shù)學函數(shù)的性能影響較小。本文提出的檢測浮點異常的編譯時插樁及異常分類方法也可以用于其他平臺檢測其他內容,具有一定的通用性。