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绕过调试器