程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> CLR完全介紹: .NET的內部診斷工具

CLR完全介紹: .NET的內部診斷工具

編輯:關於.NET

很多診斷工具都需要使用 CLR 分析 API,甚至包括那些在嚴格意義上講不是探查器的工具。因此,如 果您曾經想知道這些工具是如何工作的,那麼,了解 API 的分析會是個良好的開端。在本專欄中,您將 看到它們是如何工作的,並了解一些有用的提示和技巧。您還將在“其他分析資源”側欄內找 到一些基本的資源。

若要使用 CLR 分析 API,需要使用非托管語言(通常是 C++)來創建 DLL,然後設置一些環境變量, 用於指導公共語言運行庫 (CLR) 加載 DLL 並允許它使用分析 API。此 DLL 加載後,實際上會變成 CLR 自身的擴展,以接收回調、請求信息並對 CLR 的執行進行深層次的更改。分析 API 可以就 CLR 和托管 代碼中發生的很多活動發出通知,這些活動包括 Appdomains 的創建和析構、程序集的加載和卸載、JIT 編譯函數、執行函數、引發和捕獲異常以及進行垃圾回收。使用分析 API,可以獲得有關應用程序各部分 的信息,例如,程序集的名稱和位置、類型和函數的說明以及對象在內存中的位置和分布。最後,可以使 用分析 API 來修改設置、說明和類似信息,包括禁用 JIT 編譯器中的優化、更改函數的中間語言 (IL) 、甚至創建新的類型和函數。

您將看到,這些功能結合在一起可以為許多診斷工具提供動力。

性能探查器

性能探查器是顯示應用程序如何利用時間的診斷工具。如果應用程序中的某些 操作運行得太緩慢,則探查器可以識別出問題出在哪裡,以便將優化工作集中在相應的地方。

性 能探查器有兩個主要類別:跟蹤和采樣。跟蹤探查器的基本任務是在日志中記錄應用程序運行時每個函數 的調用或返回所用的時間。從此日志中,探查器可以知道控制流如何在整個應用程序中移動,以及時間的 花費情況。在 Visual Studio® 2005 性能工具中,從性能向導中選擇“檢測”可指定跟 蹤探查器(參見圖 1)。

圖 1 Visual Studio 2005 中的性能分析

在實踐中,尤其是在托管代碼應用程序中,有很多函數調用。即使探查器可以非常快速地在日志中記 錄每個函數的進入和退出,但開銷仍會增加,並且會導致在分析期間應用程序的運行速度非常緩慢。這不 僅會使開發人員的分析工作變得很困難,還會徹底改變應用程序的定時功能,而這會暴露或掩蓋細微的爭 用條件,甚至會導致超時。因此,跟蹤探查器通常允許選擇要對哪些模塊或函數進行分析。

至少 ,跟蹤探查器會在日志中記錄下正在進入或退出的函數的名稱,以及某些時間度量值。它記錄的可能是時 鐘時間(即“實際的時間”),也可能是實際花費的 CPU 周期數。探查器還可以在日志中記 錄其他信息(例如,傳遞給函數的參數),以幫助您更好地了解應用程序。當然,它在日志中記錄的信息 越多,記錄日志所花費的時間就會越長,並且開銷就越大。

編寫用於 .NET 的跟蹤探查器的方法 有很多種。某些方法根本不使用分析 API,而是打開磁盤上的程序集,並在函數的開頭和結尾插入日志寫 入代碼。此方法類似於為非托管代碼編寫跟蹤探查器的方式。

甚至是使用分析 API 的方法也有選 項。存在多個函數調用時,探查器可以在每次調用或返回函數時,以及在每次通過函數傳遞異常時,請求 從 CLR 回調。探查器甚至可以選擇僅接收某些函數的這些回調,或者獲取函數的參數及返回值的相關信 息。然後,探查器從這些回調的內部執行其日志記錄操作。

如果內置的進入/離開回調的功能不夠 強大,則在對函數代碼進行 JIT 編譯之前,探查器還可以使用分析 API 來修改這些代碼。此方式類似於 修改磁盤上的程序集,只不過它發生於運行時的內存中,並且在執行分析之前不需要執行額外的創建被檢 測程序集的步驟。

采樣探查器使用簡單的統計數據來啟用對整個應用程序的分析,而不會產生像 跟蹤探查器那麼高的開銷。在應用程序運行時,采樣探查器會定期進行檢查(即采樣),以查看應用程序 在做什麼。最簡單的采樣只記錄當時正在執行哪個函數。在結束時,將積累數百或數千個采樣,然後探查 器會分析每個函數中有多少個采樣,並由此進行推算,以估計出這些函數花費了多少時間。例如,如果 25 % 的采樣發生在函數 Foo 中,則很可能應用程序花費了其 25 % 時間來運行 Foo。有了足夠多的采樣 ,這種估計結果可以相當准確,而如果采樣操作的速度足夠快,則這種分析並不會使應用程序大幅減慢。 很多高級采樣探查器還會在采樣時記錄對堆棧的調用,以便讓您更好地了解上下文。通過在 Visual Studio 2005 性能向導中選擇“采樣”,可以使用采樣探查器。采樣探查器可以使用各種技術 進行實際采樣。有的方法會新建處於循環休眠狀態的線程,定期蘇醒以查看其他線程在做什麼。有的方法 會通過操作系統內核來定期接收中斷。

不管探查器如何獲得控制,它都會查找當前的本機代碼指令指針,並使用 CLR 分析 API 來確定該指 令是否確實位於 JIT 編譯的托管代碼中,如果是,則確定它在哪個函數中。那些對調用堆棧進行采樣的 探查器還會使用分析 API 來查找堆棧中的托管代碼函數。

內存探查器

內存探查器用於檢 查應用程序的內存使用情況。在非托管代碼中,大多數內存探查器側重於查找在哪些實例中有開發人員忘 記釋放的內存或他們已寫入數據的尚未分配的內存。雖然 CLR 的內置邊界檢查和垃圾收集器可以消除這 樣的問題,但應用程序的內存使用仍然可能導致性能問題,尤其當它強制垃圾收集器工作在非最佳方式下 時。實際上,調查托管應用程序的性能問題時,最佳方式通常是首先查看它的內存使用情況,而不是它如 何花費它的時間。

通常,托管內存探查器會在應用程序的整個生存期內跟蹤內存使用情況。從創 建對象開始,到對象被各種垃圾回收操作來回移動,直到最終釋放對象,探查器都會進行全面跟蹤。開發 人員可以使用此數據來發現有問題的分配模式,並清理應用程序使用的內存。Visual Studio 2005 性能 工具可以在執行采樣或跟蹤分析的同時分析內存使用情況;在所執行項目的屬性頁中,有用於啟用內存分 析的復選框。CLR 探查器是專門的內存探查器工具,它與源代碼一起提供,因此它也是很棒的探查器示例 ,如圖 2 所示

圖 2 Visual Studio 2005 中的內存分析

托管內存探查器很大程度上依靠 CLR 分析 API,CLR 分析 API 可以將對象分配情況通知托管內存探 查器,並使它們能夠探查對象的大小和分布,還可以告訴它們在垃圾回收期間對象如何被移動和刪除。

專用探查器

偶爾,您會需要獲取很難通過常用的探查器獲取的小部分信息。有時,可以直 接將一些跟蹤語句放在代碼中,但對於大型應用程序來說,這樣做代價太大。但是,如果您熟悉 CLR 分 析 API,您可以經常構建自定義的探查器來解答具體的問題。下面是自定義探查器可以解答的一些問題:

是否正在終結已釋放的對象?是否正在終結可釋放而未釋放的對象?

我的程序的調用關系 圖是怎樣的?

所有被處理的異常的引發者是誰?

診斷工具

分析 API 的功能(尤其 是它更改函數代碼或引入全新類型的能力)是包括探查器在內的很多工具的主要基礎。實際上,這些 API 可能已有很多更具一般性的名稱。我知道一些基於分析 API 的新型工具;以下是我喜歡的幾款這種工具 。

錯誤注入程序:應用程序的許多代碼都專門處理錯誤和隱蔽的情況。對比來說,其大部分測試 都是針對錯誤處理代碼,而這些代碼很難測試,因為通常很難按需要產生錯誤條件。錯誤注入器可以通過 強制發生失敗來幫助演練應用程序中的失敗經過。

有很多種方式可以構建錯誤注入器。一種方式 是利用分析 API 的能力,在函數代碼被 JIT 編譯之前更改這些代碼。例如,錯誤注入器可以通過更改函 數來引發 OutOfMemoryException 而不是分配對象來模擬內存不足的情況,或者,可以通過引發 TimeoutException 來代替調用網絡函數的方法來模擬網絡中斷故障。圖 3 顯示了一個示例。

Figure3 典型的錯誤注入修改

原始代碼

void Foo()
{
  StringBuilder sb;
  sb = new StringBuilder();
  ...
}  

 

錯誤注入之後

void Foo()
{
  StringBuilder sb = null;
  throw new OutOfMemoryException();
  ...
}

代碼覆蓋工具:代碼覆蓋工具類似於跟蹤探查器,但它不度量時間,而只記錄哪些代碼塊被執 行。由於很多測試演練的都是隱蔽的情況,因此對應用程序的測試套件的代碼覆蓋率的度量是一種評估測 試完整性的方式。如果在完全測試運行期間某個函數始終沒有執行,則該函數便無法被測試到。 CoverageEye.Net(可從 GotDotNet Workspace 獲得)是基於 CLR 分析 API 的代碼覆蓋工具(參見圖 4 ),並且它附帶的源代碼是使用分析 API 的另一個很好的示例。

有些代碼覆蓋工具只需知道哪些 函數被執行即可,這樣便可以使用分析 API 的內置的進入/離開回調功能。有些代碼覆蓋工具則更深入, 它們通過在函數的代碼中插入日志記錄語句來記錄函數中的執行經過。

圖 4 CoverageEye 中的代碼覆蓋數據

Flight Recorders(黑匣子):Flight Recorders 類似於跟蹤探查器和覆蓋工具,但其目標更遠大: 記錄盡可能多的有關應用程序執行的信息,並生成活動日志。這樣,在發生失敗後,您可以細讀此日志, 以查找與原因有關的線索。

Flight Recorders 通常會從可以獲得信息的任何地方獲取信息,如, 類似 Windows® 事件跟蹤 (ETW) 的跟蹤基礎結構、系統事件日志、窗口消息等等。分析 API 提供了 有關應用程序執行情況的豐富信息。

Aspect Weaver:面向方面的編程 (AOP) 可以減少代碼中需 要重復相同的日志記錄任務的位置個數。例如,如果應用程序中的很多函數需要寫入日志條目,則在采用 傳統編程語言編寫程序時,您會在每個這些函數中復制並粘貼日志記錄代碼。即使日志記錄代碼利用日志 記錄實用程序類,某些類型的更新仍然需要您用日志記錄代碼來改動每個函數。

在面向方面的編 程方法中,可以將日志記錄方面放在需要日志記錄的任意函數上。在托管語言中,日志記錄方面可能是新 的屬性類型,比如 LogAttribute。然後,只需編寫一次代碼來實現這個方面。此方面的實現代碼通常與 稱為 Aspect Weaver 的工具配合使用,來將日志記錄代碼插入具有 LogAttribute 的所有函數中。

構建 Aspect Weaver 的一種方式是在 JIT 編譯之前使用分析 API 的相應功能來添加新類型並修 改函數的代碼。我不推薦此方法,因為每次只能有一個 DLL 可以使用分析 API。如果這一個 DLL 是 Aspect Weaver,則無法同時使用性能探查器來度量面向方面的應用程序。

有關分析 API 的提示 和技巧

1. 讓生成的程序注冊探查器 在可以運行探查器之前,必須在注冊表中放入一些有關它的 信息。這一點很容易忘記,因此最好對 Visual C++® 生成過程進行配置,讓它為您執行該操作。在 項目的屬性頁中,將 Post Build Event(生成後事件)設置為執行“regsvr32 /s $(TargetPath) ”。

2. 在 Visual Studio 2005 中按 F5 啟動探查器 若要在探查器下啟動應用程序,請設 置兩個環境變量來指定要使用的探查器,然後在該環境中運行應用程序。如果您使用 Visual C++ 2005 來開發探查器,則可以在 IDE 中啟動應用程序時讓 Visual Studio 設置這些環境變量,以此來提高 “生成/運行/調試”循環操作的速度。在 C++ 項目的屬性頁中,在“調試”窗格 內,輸入有關目標應用程序和探查器環境變量的信息。確保已將“調試器類型”設置為 “僅限本機”。

3. 奇怪的行為? 發生 Win32 異常時停止執行。當探查器 DLL 被加 載到進程中時,它實際上會成為 CLR 的擴展。雖然在轉換為托管代碼之前 CLR 總是會樹立屏障,但它很 少會在調用探查器 DLL 中的函數之前執行任何特殊操作,這樣的函數實際上已被視為 CLR 中的函數!

當探查器函數產生異常(比如訪問沖突 (AV))時,異常會出現在 CLR 內的函數中,這一位置本 不該出現異常。這可以導致多種奇怪的行為,包括正在分析的應用程序中出現劇烈的行為變化。

因此,如果在探查器下運行應用程序時偶遇奇怪的行為,請對調試器進行相應設置,使它在引發本機異常 時停止運行,方法是:打開“異常”對話框,單擊“Win32 異常”旁邊的“ 引發”復選框。

這樣,當您正 Visual Studio 調試器下運行探查器時,一旦它發現 AV 或 任何其他 Win32® 異常,便會停止運行。當您看到一個在探查器內部(或在探查器所調用的函數內部 )產生的這種異常時,您可能已經發現了奇怪行為的原因。

特別是在托管代碼中,出現 Win32 AV 並不總表示存在重大問題。例如,在很多情況下 NullReferenceExceptions 一開始就表現為 Win32 AV。 由於您是僅在本機模式下進行調試,因而不會看到托管異常,並且可能很難區分 AV 是否由探查器中的 Bug 引起。因此,只有當偶遇可能是由探查器代碼中的異常所導致的奇怪行為時,才可以考慮使用此技巧 。

4. 實現 ICorProfilerCallback2 如果您要為 .NET Framework 2.0 或 .NET Framework 3.0( 包含前者)編寫探查器,請確保探查器類繼承了 ICorProfilerCallback2(即使所有方法只返回 E_NOTIMPL),並且 QueryInterface 方法可以響應對 IID_ICorProfilerCallback2 的請求。CLR 2.0 有 幾個 CLR 1.x 中不存在的概念(例如泛型),並且探查器通知 CLR 它可以處理這些概念的方式是對新接 口的 QueryInterface 作出響應。

將您想詢問的問題和提出的意見發送至 [email protected].

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved