我最近花了一些時間分析OutputDebugString方法。在我的另一個實驗中,我需要一個僅依賴於本機API的OutputDebugString版本。在實現它的過程中,我發現了一些關於OutputDebugString的有趣的事實,也許您也會感興趣。
OutputDebugString的工作原理
簡而言之,OutputDebugString嘗試將消息發送到附加到給定進程的調試器,如果沒有調試器偵聽,則嘗試將全局節映射到進程內存中並將調試消息保存在其中。我使用本機API實現OutputDebugStringA(ANSI版本)的示例如下:
void NTAPI RtlOutputDebugStringA(_In_opt_ LPCSTR OutputString) { if (OutputString) { EXCEPTION_RECORD exceptionRecord{ 0 }; exceptionRecord.ExceptionCode = DBG_PRINTEXCEPTION_C; exceptionRecord.NumberParameters = 2; exceptionRecord.ExceptionInformation[0] = strlen(OutputString) + 1; exceptionRecord.ExceptionInformation[1] = reinterpret_cast<ULONG_PTR>(OutputString); __try { RtlRaiseException(&exceptionRecord); } __except (EXCEPTION_EXECUTE_HANDLER) { NotifyGlobalDebugOutputMonitor(OutputString); } } }
RtlOutputDebugStringA(以及OutputDebugString)檢查調試器存在的方式非常有趣:它引發了一個異常。如果有一個調試器正在偵聽,它將吞了異常(異常代碼:0x40010006L表示ANSI消息,0x4001000A表示UNICODE消息),並且處理程序將永遠不會執行。我們都知道異常是昂貴的,我們應該只在特殊情況下使用它們。因此,對跟蹤的每個寫操作都拋出異常似乎不正確。稍后我將向您展示一些基准測試結果和解決此問題的簡單方法(我想您已經知道了)。但是關於NotifyGlobalDebugOutputMonitor方法的前幾句話。它使用一個全局映射部分、兩個事件對象和一個互斥對象來編寫調試消息。事件對象和互斥鎖保護該節防止並發使用。我不會過多地討論這個問題;如果你感興趣,可以看看張玉武關於代碼項目的優秀文章。您還可以在github上查看我的實現的源代碼(它只是一個POC,所以請不要在接近生產的地方使用它)。可以說,在系統中運行調試輸出監視器(如DebugView)也會對OutputDebugString性能產生負面影響,特別是當多個進程同時向調試輸出寫入數據時。
