1. 程式人生 > >KLEE: Unassisted and Automatic Generation of High-Coverage Tests for Complex Systems Programs

KLEE: Unassisted and Automatic Generation of High-Coverage Tests for Complex Systems Programs

bug nas 基本 開發者 最快 The 外部 方法 繼續

題目:KLEE: Unassisted and Automatic Generation of High-Coverage Tests for Complex Systems Programs
作者:Cristian Cadar, Daniel Dunbar, Dawson Engler ?
單位:Stanford University
出版:USENIX Symposium on Operating Systems Design and Implementation (OSDI 2008)
December 8-10, 2008, San Diego, CA, USA

背景:
很多類型的錯誤(比如功能正確性錯誤),如果不執行部分代碼,很難發現它們。這些測試的重要性以及隨機、人工方法的困難和不好的表現使得最近關於使用符號執行自動生成測試用例的工作多了很多。

貢獻:

開發了新的符號執行工具——KLEE,可以更加深入地對一系列的應用進行測試,吸收了先前工具EXE的多年經驗。KLEE使用了很多的約束求解的優化,程序的狀態表示更加簡潔,使用啟發式搜索來獲得高的代碼覆蓋率。此外,KLEE使用了一個簡單且直接的方式來處理外部環境。這些特征使得KLEE的表現大幅提高,並且可以非常好地測試大量的系統敏感性程序。
KLEE在不同的真實、復雜和環境敏感的程序集合上自動生成的測試可以得到很高的覆蓋率。KLEE被用來測試GUN COREUTILS(version6.10)上的89個程序以及其他的UNIX工具套件:BUSYBOX和MINIX。最後對HISTAR操作系統內核進行測試,與應用代碼做對比。


架構
KLEE是對EXE的一個完全的重新設計。KLEE的功能可以看作是符號化過程操作系統和解釋器的混合體。每一個符號化過程有一個註冊文件、棧、堆、程序計數器和路徑條件。
為了避免和Unix進程的混淆,將KLEE的符號化過程表示稱為一個state。程序被編譯為LLVM匯編語言(一種類似RISC的虛擬指令集)。KLEE直接解釋這個指令集,將指令無近似地映射為約束(比如位精度)。

1.基本架構
在任何時間,KLEE可以執行大量的狀態。KLEE的核心是一個翻譯循環:選擇一個state去執行,然後符號化地執行指令的下一個狀態。這個循環的終止條件是沒有其他的狀態或者達到了用戶自定義的時間超時。
與正常進程不同的是,狀態的存儲位置(寄存器、堆棧和堆對象)存儲的是表達式(樹),而不是原始數據值。一個表達式的葉子是符號變量或常量,內部節點是來自LLVM匯編語言的操作(比如四則運算、位操作、比較和內存訪問)。常量表達式的存儲位置稱為具體的存儲位置。
條件分支取一個布爾表達式(分支條件)並根據狀態為真或假來改變狀態的指針。KLEE查詢約束求解器,來確定當前路徑的分支條件是可證明為真或可證明為假;如果可證明真假,指令指針更新到適當的位置。否則,兩分支都是可能的:KLEE克隆狀態,可以探索兩條路徑,更新每條路徑的指令指針和路徑條件。
潛在的危險操作隱式生成分支,檢查是否存在任何可能導致錯誤的輸入值。例如,一個除法指令生成一個分支來檢查一個零因子。這些分支與正常分支工作相同。因此,即使在檢查(check)成功(即檢查到錯誤)時,仍會繼續在false路徑上進行,這將檢查的否定作為約束(例如,使除數不是零)。如果檢測(detect)到錯誤,KLEE產生測試用例來觸發錯誤並終止狀態。
與其他危險操作一樣,Load和store指令生成檢查:在這種情況下,檢查地址是否在有效內存對象的邊界內。但是,加載和存儲操作會帶來額外的復雜性。檢查代碼使用的內存最直接的表示形式是平面字節數組。在這種情況下,load和store會簡單地映射到數組上,分別讀寫表達式。不幸的是,我們的約束求解器STP不可能求解所有的約束。因此,KLEE將被檢測代碼的每個內存對象映射到一個特定的STP數組中(從某種意義上說,將平面地址空間映射為分段地址空間。)這種表示方法很大程度地提高了性能,因為這使得STP忽略了所有的沒被給出的表達式引用的數組。
許多操作(如綁定檢查或寫對象級復制)需要特定於對象的信息。如果一個指針可以引用許多對象,這些操作就很難執行。為簡單起見,KLEE解決這個問題如下:如果解引用指針P引用了N個對象,KLEE將當前的狀態克隆N次。在每個狀態中,它將p限制在其各自對象的邊界內,然後執行適當的讀或寫操作。雖然這種方法對於指向大量指針集的指針來說花費很大,但是在我們已測試的程序中,大部分只使用指向一個對象的符號指針,對於這種情況KLEE做了很好的優化。

2.緊湊的狀態表示(Compact State Representation)
路徑爆炸問題:狀態的數目增加很快,即使是小程序也可能在解釋的前幾分鐘內生成成千上萬的並發狀態。
狀態表示的內部化極大地增加了可以同時探索的狀態的數量,這是通過降低每個狀態成本(在對象層級應用復制寫入,很大程度地減少了了每個狀態的內存需求)和允許在對象(而不是頁面)級別共享內存實現的。

3.查詢優化
約束求解的成本幾乎占主導地位的,由此KLEE生成了一個NP完全邏輯復雜查詢。
KLEE通過簡化表達式的技巧,在到達STP之前,盡量消除查詢(沒有查詢是最快的查詢)。簡化查詢可以更快地解決問題,減少內存消耗,並提高查詢緩存的命中率。主要的查詢優化是:
表達式重用
約束集簡化
隱含value的具體化
約束獨立
反例緩存

4.狀態調度
(1)KLEE通過在每個指令下選擇一個狀態,交互地運行兩種啟發式搜索——隨機路徑選擇和覆蓋優化搜索。
隨機路徑選擇維護一個二叉樹,記錄所有活動狀態之後的程序路徑,即樹的葉子是當前狀態,內部節點是執行分叉的地方。通過從根遍歷該樹並隨機選擇在分支點上追蹤的路徑來選擇狀態。因此,當一個分支點到達時,每個子樹的狀態集被選中的概率相等,不考慮其子樹的大小。
這個策略有兩個重要屬性:
傾向於樹的高層分支的狀態。這些狀態對其符號輸入的約束較少,所以有更大的自由去接觸未覆蓋的代碼。
避免饑餓。
覆蓋優化搜索試圖選擇可能覆蓋新代碼的狀態。它使用啟發式計算每個狀態的權重,然後根據這些權重隨機選擇一個狀態。目前,這些啟發式算法考慮到未覆蓋指令的最小距離、狀態的調用堆棧、以及該狀態最近是否覆蓋新代碼。
KLEE循環使用每個策略。雖然這會增加實現高覆蓋率策略的選擇時間,但它可以防止個別策略被卡住的情況。而且,由於策略從同一狀態池中選擇,交錯使用它們可以提高整體效能。

KLEE為避免經常執行耗費很大的狀態的指令占據大部分的執行時間,通過定義每個狀態的運行時間片,限制每個狀態的執行時間。


實驗:

1.分類:
1.進行密集運行,既發現錯誤,並獲得高覆蓋率。 (COREUTILS, HISTAR, and 75 BUSYBOX utilities)
2.快速運行許多應用程序來查找bug。 (an additional 207 BUSYBOX utilities and 77 MINIX utilities),
3.核查替代程序以發現更深層次的正確的bug。(67 BUSYBOX utilities vs. the equivalent 67 in COREUTILS)
(總的來說,在超過452個項目上運行了KLEE,包430k行代碼)
2.實驗結果:
1.KLEE在一系列復雜的程序上獲得了高覆蓋率。自動生成的測試覆蓋了84.5%的COREUTILS代碼行以及90.5%的BUSYBOX 代碼(忽略庫代碼)。平均來講,這些測試在每個工具上都達到了90%的命中(中位數:94%),在16個 Coreutils工具和31個busybox工具上實現完美的覆蓋率100%。
2.KLEE可以獲得比集中、持續的手工工作更大的代碼覆蓋率。KLEE只用了大約89小時,產生的Coreutils代碼行覆蓋率,就擊敗了開發者自己15年來建立的測試套件,並高出了16.8%!
3.KLEE在不變的應用上可以達到很高的覆蓋率結果。唯一的例外是Coreutils的sort,需要一個單一的編輯來收縮大的緩沖區,這帶來了約束求解器的問題。
4.KLEE在大量測試的代碼上發現了重要的錯誤。它發現了Coreutils的十個致命錯誤(其中3個歷時15年未被檢測出來),這比2006、 2007和2008三年發現的奔潰bug的總和還要多。進一步又在busybox找到了24個bug,在MINIX 中找到21個錯誤,並在HISTAR中發現一個安全漏洞。共計56個嚴重的錯誤。
5.測試用例可以運行在代碼的原始版本上(例如,用gcc編譯),大大簡化了調試和錯誤報告。例如,所有的Coreutils的bug都在兩天內被驗證和修復,KLEE生成的各個版本的測試被包含在標準的回歸套件中。
6.KLEE不局限於低層次的編程錯誤:當用於測試busybox和GNU Coreutils工具時,它會自動發現功能正確性bug。
7.KLEE也可以應用於非應用程序代碼。當運行在 HISTAR的內核,它在有磁盤情況下取得了76.4%的平均行覆蓋率,無磁盤時是67.1%。而且KLEE還發現了一個嚴重的安全漏洞
未來工作:
長期目標是對任意程序,常規地獲得最低90%的代碼覆蓋率。

KLEE: Unassisted and Automatic Generation of High-Coverage Tests for Complex Systems Programs