2013-02-08 88 views
3

我已經編寫了一個小代碼覆蓋實用程序來記錄在x86可執行文件中命中哪些基本塊。它運行時沒有源代碼或調試目標的符號,只是丟失了它監視的基本塊。如何加快我的代碼覆蓋率工具?

但是,它正在成爲我的應用程序中的瓶頸,它涉及單個可執行映像的重複覆蓋快照。

它已經經歷了幾個階段,因爲我試圖加快速度。我開始時只是在每個基本塊的開始處放置一個INT3,作爲調試器附加,並記錄命中。然後,我嘗試通過在計數器中修補任何大於5個字節的塊(JMP REL32的大小)來提高性能。我在進程內存空間中寫了一個小存根('mov [blah],1/jmp backToTheBasicBlockWeCameFrom')並將JMP打補丁。這大大加快了速度,因爲沒有例外,也沒有調試器中斷,但我想加快速度。

我想以下之一:

1)預儀器,目標二進我的修補計數器(目前我做這在運行時)。我可以在PE中創建一個新的部分,將我的計數器放入其中,修補所需的所有鉤子,然後在每次執行後使用調試器從同一部分中讀取數據。這會讓我獲得一些速度(根據我的估計,這個速度大約爲16%),但是仍然有那些令人討厭的INT3,我需要在更小的區塊中使用,這實際上會削弱性能。

2)將該二進制文件包含其自己的UnhandledExceptionFilter,並處理它自己的int3與上述內容一起使用。這意味着每個in​​t3都沒有從調試對象到我的覆蓋工具的過程切換,但是仍然會出現斷點異常和隨後的內核轉換 - 我認爲這實際上不會獲得太多的性能嗎?

3)嘗試使用英特爾的硬件分支分析說明來做一些巧妙的事情。這聽起來很棒,但我不清楚我會怎麼做 - 它甚至可以在Windows用戶模式應用程序?如果它非常簡單,我可以儘可能編寫一個內核模式驅動程序,但我不是內核編碼器(我討論了一點),可能會讓我感到頭疼。有沒有其他項目使用這種方法?我看到Linux內核有它來監視內核本身,這讓我認爲監視特定的用戶模式應用程序將會很困難。

4)使用現成的應用程序。它需要在沒有任何源代碼或調試符號的情況下工作,可以編寫腳本(所以我可以批量運行),並且最好是免費的(我很吝嗇)。然而,付費工具並未脫穎而出(如果我可以在工具上花費更少,並且增加足夠的性能以避免購買新硬件,那就是很好的理由)。

5)別的東西。我在Windows XP上運行VMWare,在相當陳舊的硬件上運行(Pentium 4-ish) - 是否有任何我錯過的或者我應該閱讀的任何線索?我可以讓我的JMP REL32下降到少於5個字節(並且在不需要int3的情況下捕獲更小的塊)?

謝謝。

+0

投票結束爲「非建設性」?令我驚訝的是,有多少SO人投票結束完全合法的問題,並給出了完全合理的答案。你們給了這個糟糕的味道。 – 2013-02-08 20:41:45

+1

我很好奇。如果你沒有來源或符號,你如何檢查結果?你只是計算一個百分比覆蓋值?如果百分比很低,你如何確定你的測試缺失了哪些部分? – 2013-02-08 22:53:09

+0

我實際上在做黑盒fuzzing找安全漏洞。我的想法是,我稍微改變一個輸入文件,觀察任何打開的新塊,然後'下降'以更多地突變該輸入,如果它打開我們以前沒有看到的塊。(如果你有一個小時左右的空餘時間,我發現Travis Ormandy的演講「製作軟件笨拙」令人着迷 - http://www.youtube.com/watch?v=YqZRuvdbR64) – randomdude 2013-02-09 07:28:07

回答

1

如果你堅持使用二進制文件,幾乎你最快的覆蓋範圍是5個字節的跳出跳回技巧。 (您正在使用二進制儀表工具的標準基準。)

INT 3解決方案將始終涉及陷阱。是的,你可以在你的空間中處理陷阱而不是調試器空間,這可以加快它的速度,但它永遠不會與跳出/退回補丁競爭。無論如何,如果您正在測試的功能恰好短於5個字節(例如,您可能需要它作爲備份)。,「inc eax/ret」),因爲那時你沒有5個字節可以修補。

你可能會做些什麼來優化一些東西,檢查修補後的代碼。如果沒有這樣的考試,與原來的代碼:

  instrn 1 
     instrn 2 
     instrn N 
    next: 

修補,一般看起來像這樣:

  jmp patch 
     xxx 
    next: 

具有一般都有補丁:

patch: pushf 
      inc count 
      popf 
      instrn1 
      instrn2 
      instrnN 
      jmp back 

如果你想要的是覆蓋範圍,您無需增加,也就是說您不需要保存標誌:

patch: mov byte ptr covered,1 
      instrn1 
      instrn2 
      instrnN 
      jmp back 

您應該使用字節而不是一個字來減少補丁大小。您應該將緩衝區中的修補程序對齊,以便處理器不具有獲取2緩存行來執行修補程序。

如果你堅持計數,你可以分析instrn1/2/N,看看他們是關心「inc」的標誌,如果需要的話只有pushf/popf,或者你可以在兩個之間插入增量不需要關心的補丁說明。無論如何,您必須在某種程度上分析這些情況,以便處理諸如本身爲ret的併發症;你可以生成一個更好的補丁(例如,不要「jmp back」)。

您可能會發現使用加計數,1INC快算,因爲這避免了部分條件代碼更新和隨之而來的管道互鎖。這會影響你的cc影響分析,因爲inc沒有設置進位位,而呢。

另一種可能性是PC採樣。根本不要測試代碼;只需定期中斷線程並獲取示例PC值。如果你知道基本塊在哪裏,那麼在基本塊中的任何一個PC樣本都可以證明整個塊被執行。這不一定會提供精確的覆蓋數據(您可能會錯過關鍵的PC值),但開銷很低。

如果你願意補丁源碼你可以做得更好:只需插入「covered [i] = true;」在第一個基本塊的開始,讓編譯器負責所有各種優化。不需要補丁。這真的很酷的部分是,如果你在嵌套循環中有基本塊,並且你插入這樣的源探針,編譯器會注意到探針分配對於循環是冪等的,並將探針從循環中提出。中提琴,循環內零探針開銷。你還想要什麼?

+0

感謝您的指點(heeh)。目前,我在我的存根中使用'mov byte ptr,1',但我沒有想到在緩存行上對齊。 – randomdude 2013-02-08 21:28:26

+0

看起來這是我將要得到的最佳答案。我接受它作爲答案主要是因爲它充滿了有用的信息,即使其中一些不適用於我的場景(例如,我不需要計數,沒有來源等)。 – randomdude 2013-02-15 12:06:25