DebugView简介 DebugView是一个监视本地系统或者通过tcp/ip连接的网络系统的OutputDebugString输出的应用程序。DebugView不仅能够监视Win32应用的debug输出,还可以监视内核模型的debug输出。因此,如果使用OutputDebugString来打印调试信息的话,就可以在程序运行时候通过DebugView来实时显示程序的调试信息。 这种方式在某种意义上,比将Log打印到文件中,关闭程序后再查看Log输出的方式更加方便。而且可以将这两种调试程序的方式结合起来,既使用DebugView来实时显示调试信息,又将调试信息输出到Log文件中,方便以后分析。
安装DebugView 下载地址:DebugView 下载后面后解压压缩包,发现里面有三个文件:Dbgview.exe、dbgview.chm、Eula.txt。 Dbgview.exe就是我们要使用的实时显示Log工具。dbgview.chm是自带的文档,有不懂的地方可以查阅该文档。 现在可以将DebugView.exe放到任何你喜欢的目录,比如桌面。
配置DebugView 配置Capture 如下图所示,要记得勾选Capture Win32和Capture Global Win32。Capture Global Win32用于网络模式下捕获网络主机的Debug输出的时候。如果需要捕获内核模式的调试输出,记得勾选Capture Kernel Win32。 如果点击Capture Global Win32菜单出现提示: 重新以管理员的身份启动DebugView。
配置Filter 如下图所示,打开Filter对话框, 然后在Include中输入要包含的字符串,比如”hankpcxiao”,多个字符串用;分隔,比如”hankpcxiao;xpc”。 这样就只会捕获包括过滤字符串hankpcxiao或者xpc的OutputDebugString输出。 如果我们在每个OutputDebugString输出前自动加上过滤字符串,那么DebugView就只会输出我们的Log信息了。
开启捕获 最后确保开启了捕获,如下图所示:
如何在程序中输出Log信息? 默认情况下,DebugView会捕获函数OutputDebugString的输出,但是这个函数的参数是个字符串指针,不太方便。下面我们通过一些列步骤来创建一个方便使用的Log类。
格式化输出 我们习惯使用printf这样的函数来格式化输出信息,因此这次我们也把OutputDebugString包装成可变参数形式的格式化输出函数。
1 2 3 4 5 6 7 8 9 10 11 void DebugViewOutput (const char * fm, ...) { static char szMsg[MAX_PATH]; va_list argList; va_start (argList, fm); vsprintf_s (szMsg, fm, argList); va_end (argList); OutputDebugString (szMsg); }
让DebugView在VS调试程序时候也能够捕获Log 网上有不少介绍DebugView使用的文章,但是都忽略了一个事实,那就是默认情况下,使用VS运行程序时候,OutputDebugString的输出是到VS的输出窗口中,DebugView中并没有任何信息。只有单独运行程序的时候,DebugView才能够捕捉到信息。 但是这样就不能结合打断点调试和DebugView两个强大的调试方法了。不过,还是有解决办法的。通过一个叫做DBWin通信机制可以实现调试程序时候,把OutputDebugString的输出信息显示到DebugView窗口中。这套机制的本质是通过内存映射文件来跨进程交换数据。 具体参考以下的类代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 class XpcDebugView { public : XpcDebugView () {} ~XpcDebugView () {} void XpcDebugViewOutput (const char * fm, ...) ; private : struct DBWinBuffer { DWORD ProcessId; char Data[4096 - sizeof (DWORD)]; }; bool Initialize () ; void UnInitialize () ; HANDLE m_mutex; HANDLE m_fileMapping; HANDLE m_bufferReadyEvent; HANDLE m_dataReadyEvent; DBWinBuffer* m_buffer; }; bool XpcDebugView::Initialize () { m_mutex = OpenMutex (SYNCHRONIZE, FALSE, TEXT ("DBWinMutex" )); m_fileMapping = OpenFileMapping (FILE_MAP_WRITE, FALSE, TEXT ("DBWIN_BUFFER" )); if (m_fileMapping == NULL ) return false ; m_bufferReadyEvent = OpenEvent (SYNCHRONIZE, FALSE, TEXT ("DBWIN_BUFFER_READY" )); m_dataReadyEvent = OpenEvent (EVENT_MODIFY_STATE, FALSE, TEXT ("DBWIN_DATA_READY" )); WaitForSingleObject (m_bufferReadyEvent, INFINITE); m_buffer = (DBWinBuffer*)MapViewOfFile (m_fileMapping, FILE_MAP_WRITE, 0 , 0 , 0 ); m_buffer->ProcessId = GetCurrentProcessId (); } void XpcDebugView::UnInitialize () { FlushViewOfFile (m_buffer, 0 ); UnmapViewOfFile (m_buffer); SetEvent (m_dataReadyEvent); CloseHandle (m_fileMapping); CloseHandle (m_dataReadyEvent); CloseHandle (m_bufferReadyEvent); ReleaseMutex (m_mutex); CloseHandle (m_mutex); } void XpcDebugView::XpcDebugViewOutput (const char * fm, ...) { if (Initialize () == false ) return ; static char szMsg[MAX_PATH]; va_list argList; va_start (argList, fm); vsprintf_s (szMsg, fm, argList); va_end (argList); #pragma warning ( push ) #pragma warning ( disable: 4996 ) strcpy (m_buffer->Data, szMsg); printf (szMsg); #pragma warning ( pop ) UnInitialize (); }
使用的时候直接调用类成员函数XpcDebugViewOutput即可。 关于这部分的内容更具体的可以参考文章,如何让OutputDebugString绕过调试器
输出到DebugView的同时输出到Log文件 我将上面的类改造成下面的样子,在初始化时候创建一个Log文件,在反初始化时候关闭Log文件,每次调用XpcDebugViewOutput使用调用fprintf将格式化字符串输出到文件中。这样就能达到输出Log信息到DebugView中的同时,又能够将Log信息持久化保存了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 class XpcDebugView { public : XpcDebugView () { m_pszLogName = "DefaultLog.txt" ; Initialize (); } XpcDebugView (const char * pszLogName) { m_pszLogName = pszLogName; Initialize ();} ~XpcDebugView () { UnInitialize (); } void XpcDebugViewOutput (const char * fm, ...) ; private : struct DBWinBuffer { DWORD ProcessId; char Data[4096 - sizeof (DWORD)]; }; bool Initialize () ; void UnInitialize () ; bool InitializeDBWin () ; void UnInitializeDBWin () ; HANDLE m_mutex; HANDLE m_fileMapping; HANDLE m_bufferReadyEvent; HANDLE m_dataReadyEvent; DBWinBuffer* m_buffer; const char * m_pszLogName; FILE* m_pFileLog; }; #pragma warning ( push ) #pragma warning ( disable: 4996 ) bool XpcDebugView::Initialize () { m_pFileLog = fopen (m_pszLogName, "w" ); return m_pFileLog != NULL ; } void XpcDebugView::UnInitialize () { if (m_pFileLog) { fclose (m_pFileLog); } } bool XpcDebugView::InitializeDBWin () { m_mutex = OpenMutex (SYNCHRONIZE, FALSE, TEXT ("DBWinMutex" )); m_fileMapping = OpenFileMapping (FILE_MAP_WRITE, FALSE, TEXT ("DBWIN_BUFFER" )); if (m_fileMapping == NULL ) return false ; m_bufferReadyEvent = OpenEvent (SYNCHRONIZE, FALSE, TEXT ("DBWIN_BUFFER_READY" )); m_dataReadyEvent = OpenEvent (EVENT_MODIFY_STATE, FALSE, TEXT ("DBWIN_DATA_READY" )); WaitForSingleObject (m_bufferReadyEvent, INFINITE); m_buffer = (DBWinBuffer*)MapViewOfFile (m_fileMapping, FILE_MAP_WRITE, 0 , 0 , 0 ); m_buffer->ProcessId = GetCurrentProcessId (); } void XpcDebugView::UnInitializeDBWin () { FlushViewOfFile (m_buffer, 0 ); UnmapViewOfFile (m_buffer); SetEvent (m_dataReadyEvent); CloseHandle (m_fileMapping); CloseHandle (m_dataReadyEvent); CloseHandle (m_bufferReadyEvent); ReleaseMutex (m_mutex); CloseHandle (m_mutex); } void XpcDebugView::XpcDebugViewOutput (const char * fm, ...) { if (InitializeDBWin () == false ) return ; static char szMsg[MAX_PATH]; va_list argList; va_start (argList, fm); vsprintf_s (szMsg, fm, argList); va_end (argList); strcpy (m_buffer->Data, szMsg); if (m_pFileLog) { fprintf (m_pFileLog, szMsg); } UnInitializeDBWin (); } #pragma warning ( pop )
如何使用XpcDebugView类 最简单的方式是定义一个XpcDebugView的全局变量,比如: XpcDebugView myDebugview(“myLog.txt”); 输出Log信息的时候调用函数myDebugview.XpcDebugViewOutput(“%d %d %s\n”, 1, 2, “log”): 为了方便使用,可以在XpcDebugViewOutput的输出后面添加换行符,这样每次调用后就会自动换行了。 并且加上过滤字符串前缀,这样DebugView就只会捕获我们的输出了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void XpcDebugView::XpcDebugViewOutput (const char * fm, ...) { static char szMsg[MAX_PATH]; static char szOutput[MAX_PATH]; va_list argList; va_start (argList, fm); vsprintf_s (szMsg, fm, argList); va_end (argList); strcpy (szOutput, "[hankpcxiao] " ); strcat (szOutput, szMsg); strcat (szOutput, "\n" ); if (m_pFileLog) { fprintf (m_pFileLog, szOutput); } if (InitializeDBWin ()) { strcpy (m_buffer->Data, szOutput); UnInitializeDBWin (); } }
总结 首先需要下载好DebugView程序,然后配置capture选项,另外是Filter字符串。最后为了保证在VS中调试程序时候,能够将调试信息输出到DebugView,需要使用DBWin通信进制。为此,我封装了一个Log类,在将Log输出到DebugView的同时也将Log输出到日志文件中。
参考资料: [1] Frequently asked questions on the Microsoft application DebugView.exe [2] 如何让OutputDebugString绕过调试器