分析概觀
分析工具是監視另一個應用程式執行的工具。 Common Language Runtime (CLR) 分析工具是動態連結程式庫 (DLL) 由數個函式所組成,可使用分析 API,從 CLR 接收訊息,以及傳送訊息至 CLR。 CLR 會在執行階段載入分析工具 DLL。
傳統的分析工具著重在測量應用程式的執行。 也就是說,它們會測量花費在每個函式的時間,或是應用程式經過一段時間的記憶體使用量。 分析 API 的目標是更廣泛的診斷工具類別,例如程式碼涵蓋範圍公用程式,甚至進階偵錯輔助工具。 這些用途在本質上都是診斷。 分析 API 不僅會測量,也會監視應用程式的執行。 基於這個理由,應用程式本身應該永遠都不要使用分析 API,而且應用程式的執行不應該依賴分析工具 (或受其影響)。
分析 CLR 應用程式所需的支援,比依照慣例分析已編譯的機器碼更多。 這是因為 CLR 引進了應用程式域、垃圾收集、Managed 例外狀況處理、程式代碼 Just-In-Time (JIT) 編譯程式代碼(將通用中繼語言或 CIL、程式代碼轉換成原生機器碼)等概念,以及類似的功能。 傳統分析機制無法識別或提供關於這些功能的有用資訊。 分析 API 有效地提供這項遺漏的資訊,而幾乎不會影響 CLR 和已分析之應用程式的效能。
執行階段的 JIT 編譯為分析提供很好的機會。 程式代碼剖析 API 可讓分析工具在 JIT 編譯之前變更例程的記憶體內部 CIL 程式代碼資料流。 以這種方式,分析工具可以將檢測程式碼動態加入需要更深入調查的特定常式。 雖然這種方法在傳統案例中是可行的,但是使用分析 API 來為 CLR 實作會更加容易。
程式碼剖析 API
一般而言,分析 API 是用來撰寫 程式代碼分析工具,這是監視受控應用程式執行的程式。
分析工具 DLL 會使用分析 API,它會載入與所分析之應用程式相同的程序中。 分析工具 DLL 會實作回呼介面(.NET Framework 1.0 版和 1.1 版中的 ICorProfilerCallback2,版本 2.0 和更新版本中的 ICorProfilerCallback2)。 CLR 會在該介面中呼叫方法,以通知分析工具所分析之程序中的事件。 分析工具可以使用 ICorProfilerInfo 和 ICorProfilerInfo2 介面中的方法來取得已分析應用程式狀態的相關信息,以回呼運行時間。
注意
只有分析工具解決方案的資料蒐集部分,應該要在與所分析之應用程式相同的程序中執行。 所有使用者介面和資料分析都應該在分開的處理序中執行。
下圖顯示分析工具 DLL 如何與正在分析的應用程式和 CLR 互動。
通知介面
ICorProfilerCallback 和 ICorProfilerCallback2 可視為通知介面。 這些介面是由 ClassLoadStarted、ClassLoadFinished 和 JITCompilationStarted 等方法所組成。 每當 CLR 載入或卸載類別、編譯函式等等,都會在分析工具的 ICorProfilerCallback
或 ICorProfilerCallback2
介面上呼叫對應的方法。
例如,分析工具可以透過兩個通知函式來測量程式代碼效能: FunctionEnter2 和 FunctionLeave2。 它會即時戳記每個通知、彙總結果,並輸出一個清單,指出在應用程式執行期間,哪些函式耗用最多 CPU 或時鐘時間。
資訊擷取介面
程序代碼剖析所涉及的其他主要介面是 ICorProfilerInfo 和 ICorProfilerInfo2。 分析工具會視需要呼叫這些介面,以取得更多資訊來協助進行分析。 例如,每當 CLR 呼叫 FunctionEnter2 函式時,就會提供函式識別碼。 分析工具可以呼叫 ICorProfilerInfo2::GetFunctionInfo2 方法來探索函式的父類別、其名稱等等,以取得該函式的詳細資訊。
支援的功能
分析 API 會提供 Common Language Runtime 中發生之各種事件和動作的相關資訊。 您可以使用這項資訊來監視處理序的內部運作方式,以及分析 .NET Framework 應用程式的效能。
分析 API 會擷取 CLR 中發生之下列動作和事件的相關資訊:
CLR 啟動和關閉事件。
應用程式定義域建立和關閉事件。
組件載入與卸載事件。
模組載入與卸載事件。
COM vtable 建立和解構事件。
Just-In-Time (JIT) 編譯和 code-pitching 事件。
類別載入和卸載事件。
執行緒建立和解構事件。
函式進入和結束事件。
例外狀況。
Managed 和 Unmanaged 程式碼執行之間的轉換。
不同執行階段內容之間的轉換。
執行階段暫止的相關資訊。
執行階段記憶體堆積和記憶體回收活動的相關資訊。
您可以從任何 (非 Managed) COM 相容語言呼叫分析 API 。
在 CPU 和記憶體耗用量方面,此 API 很有效率。 分析並不會對所分析的應用程式造成足以誤導結果的變更。
分析 API 對取樣和非取樣分析工具都非常有用。 取樣 分析工具 會以一般時鐘刻度檢查配置檔,例如,相距 5 毫秒。 非 取樣分析工具 會以同步方式通知事件,且線程會導致事件。
不支援的功能
分析 API 不支援下列功能:
Unmanaged 程式碼,這必須使用傳統 Win32 方法來分析。 不過,CLR 分析工具包含轉換事件,可判斷 Managed 與 Unmanaged 程式碼之間的界限。
自我修改的應用程式,它會針對外觀導向程式設計之類的目的,修改自己的程式碼。
繫結檢查,因為分析 API 並未提供這項資訊。 CLR 為所有 Managed 程式碼的繫結檢查提供內建支援。
遠端分析,不支援的原因如下:
遠端分析會延長執行時間。 當您使用分析介面時,您必須盡量減少執行時間,使分析結果不要受到過度影響。 當執行效能受到監視時,特別是這樣。 不過,使用分析介面來監視記憶體使用量,或取得有關堆疊框架、物件等等的執行階段資訊時,遠端分析並不是個限制。
CLR 程式碼分析工具必須向執行所分析應用程式之本機電腦上的執行階段,註冊一個或多個回呼介面。 這會限制建立遠端程式碼分析工具的能力。
通知執行緒
在大部分情況下,產生事件的執行緒也會執行通知。 這類通知(例如 FunctionEnter 和 FunctionLeave)不需要提供明確的 ThreadID
。 此外,根據受影響執行緒的 ThreadID
,分析工具可能會決定使用執行緒區域儲存區來儲存和更新其分析區塊,而不是在全域儲存體中將分析區塊編製索引。
請注意,這些回呼不會序列化。 使用者必須藉由下列方式保護他們的程式碼:建立執行緒安全資料結構,並且鎖定分析工具程式碼,進而防止從多個執行緒進行平行存取。 因此,在某些情況下,您會收到不尋常的回呼序列。 例如,假設 Managed 應用程式正在繁衍兩個正在執行相同程式碼的執行緒。 在此情況下,從一個線程接收某些函式的 ICorProfilerCallback::JITCompilationStarted 事件,以及接收 ICorProfilerCallback::JITCompilationFinished 回呼之前,可以接收另一個FunctionEnter
線程的回呼。 在此情況下,使用者將會因為可能尚未完全 Just-In-Time (JIT) 編譯的函式,而收到 FunctionEnter
回呼。
安全性
分析工具 DLL 是 Unmanaged 的 DLL,它會當做 Common Language Runtime 執行引擎的一部分來執行。 因此,分析工具 DLL 中的程式碼不受 Managed 程式碼存取安全性的限制。 分析工具 DLL 的唯一限制,只有作業系統針對執行所分析應用程式的使用者施加的那些限制。
分析工具作者應該採取適當的預防措施,以避免安全性相關問題。 例如,在安裝期間,應該將分析工具 DLL 加入存取控制清單 (ACL),如此惡意使用者就無法加以修改。
將 Managed 和 Unmanaged 程式碼合併在程式碼分析工具中
撰寫不正確的分析工具可能會造成對本身循環參考,導致無法預期的行為。
檢閱 CLR 分析 API 可能會造成一個印象,認為您可以撰寫一個分析工具,其中包含 Managed 和 Unmanaged 元件,而它們可以透過 COM Interop 或間接呼叫來彼此呼叫。
雖然從設計的觀點來看,這是可行的,但是分析 API 並不支援 Managed 元件。 CLR 分析工具必須是完全 Unmanaged。 嘗試將 Managed 和 Unmanaged 程式碼結合在 CLR 分析工具中,可能會造成存取違規、程式失敗或死結。 分析工具的 Managed 元件會引發事件回到其 Unmanaged 元件,Unmanaged 元件接著又會呼叫 Managed 的元件,而導致循環參考。
CLR 分析工具可以安全地呼叫 Managed 程式代碼的唯一位置是方法的通用中繼語言 (CIL) 主體。 修改 CIL 主體的建議做法是在 ICorProfilerCallback4 介面中使用 JIT 重新編譯方法。
您也可以使用較舊的檢測方法來修改 CIL。 在函式的 Just-In-Time (JIT) 編譯完成之前,分析工具可以在方法的 CIL 主體中插入 Managed 呼叫,然後 JIT 編譯它(請參閱 ICorProfilerInfo::GetILFunctionBody 方法)。 這項技術可以成功用於選擇性的 Managed 程式碼檢測,或是用來收集 JIT 的相關統計資料和效能資料。
或者,程式代碼分析工具可以在呼叫 Unmanaged 程式代碼的每個 Managed 函式的 CIL 主體中插入原生攔截。 這項技術可以用於檢測和涵蓋範圍。 例如,程式代碼分析工具可以在每個 CIL 區塊之後插入檢測攔截,以確保已執行區塊。 方法 CIL 主體的修改是一個非常微妙的作業,而且有許多因素應該考慮。
分析 Unmanaged 程式碼
Common Language Runtime (CLR) 分析 API 為分析 Unmanaged 程式碼提供最小支援。 提供下列功能:
堆疊鏈結的列舉。 這項功能可讓程式碼分析工具判斷 Managed 程式碼與 Unmanaged 程式碼之間的界限。
判斷堆疊鏈結是對應至 Managed 程式碼或原生程式碼。
在 .NET Framework 1.0 和 1.1 版中,這些方法可用於 CLR 偵錯 API 的整個同處理序子集。 其定義在 CorDebug.idl 檔案中。
在 .NET Framework 2.0 和更新版本中,您可以針對這項功能使用 ICorProfilerInfo2::D oStackSnapshot 方法。
使用 COM
雖然分析介面被定義為 COM 介面,但是 Common Language Runtime (CLR) 並不會實際初始化 COM 來使用這些介面。 原因是在 Managed 應用程式有機會指定所需的線程模型之前,必須先使用 CoInitialize 函式來設定線程模型。 同樣地,分析工具本身也不應該呼叫 CoInitialize
,因為它所選擇的執行緒模型可能會與正在分析的應用程式不相容,而導致應用程式失敗。
呼叫堆疊
分析 API 提供兩種取得呼叫堆疊的方式:堆疊快照方法,可啟用呼叫堆疊的疏鬆收集;以及陰影堆疊方法,可追蹤每個時刻的呼叫堆疊。
堆疊快照
堆疊快照是執行緒堆疊的即時追蹤。 分析 API 支援追蹤堆疊上的 Managed 函式,但會將 Unmanaged 函式的追蹤交由分析工具本身的堆疊查核器處理。
如需如何對分析工具進行程序設計以逐步執行 Managed 堆疊的詳細資訊,請參閱本檔集的 ICorProfilerInfo2::D oStackSnapshot 方法,以及 .NET Framework 2.0:Basics 和 Beyond 中的 Profiler Stack Walk。
陰影堆疊
過於頻繁使用快照方法,很快就會產生效能問題。 如果您想要經常進行堆棧追蹤,您的分析工具應該改用 FunctionEnter2、FunctionLeave2、FunctionTailcall2 和 ICorProfilerCallback2 例外狀況回呼來建置陰影堆棧。 每當需要堆疊快照時,陰影堆疊一定都是當前的,而且可以快速複製到儲存體。
陰影堆疊可能會取得函式引數、傳回值,以及泛型具現化的相關資訊。 此資訊只能透過陰影堆疊來使用,並且可以在將控制權交給函式時取得。 不過,之後在函式執行期間,可能無法使用這項資訊。
回呼和堆疊深度
分析工具回呼可能會在堆疊極為受限的情況下發出,而分析工具回呼中的堆疊溢位將會導致處理序立即結束。 分析工具在回應回呼,應該要確保儘可能少用堆疊。 如果分析工具是為了用於防止堆疊溢位的穩固處理序,則分析工具本身也應該避免觸發堆疊溢位。
[相關主題]
標題 | 描述 |
---|---|
設定分析環境 | 說明如何初始化分析工具、設定事件通知,以及為 Windows 服務進行分析。 |
分析介面 | 說明分析 API 所使用的 Unmanaged 介面。 |
分析全域靜態函式 | 說明分析 API 所使用的 Unmanaged 全域靜態函式。 |
分析列舉 | 說明分析 API 所使用的 Unmanaged 列舉。 |
分析結構 | 說明分析 API 所使用的 Unmanaged 結構。 |