Introduction
This article focuses on how to capture debug strings outputted by an application that calls Win32 API OutputDebugString
. If you have ever used DebugView
produced by Sysinternals, you will be familiar with the usage of this tool.
Background
- Objects
There are 4 kernel objects to create the mechanism of interactive communication between an application and monitor
or debugger:
DBWinMutex
: A Mutex
object. Creates exclusive execution of OutputDebugString
. DBWIN_BUFFER_READY
: It is the name of an Event
object. Monitor
or Debugger posts signal to OutputDebugString
when the shared memory is ready. DBWIN_DATA_READY
: It is the name of an Event
object. This event is signaled when OutputDebugString
routine finishes writing to shared memory. DBWIN_BUFFER
: It is the name of shared memory. Its size is 4K bytes, the first 4 bytes indicate the process id and the following is the content of debug string.
- Workflow
Here is the workflow of the mechanism of communication between the monitor
and OutputDebugString
.
- Capture the Debug Messages
If you want to capture debug messages in your application, you need to create or open these 4 kernel objects and create a task to monitor the shared memory. When the data is ready, the task sends it out. The picture below shows the debug messages captured by the application itself:
- Be Careful in the UI Application
Be careful when you are writing a UI application like the above, if you call OutputDebugString
twice continuously in the UI thread, your application will be blocked for 10 seconds and then the first debug string will be outputted. The reason for that is: after the first call to OutputDebugString
, the monitor
thread captures the debug string, and sends it to the UI's Editbox
and waits for a response, but currently the UI thread goes to the second OutputDebugString
and is waiting for the BufferReady
event, while this event need to be signaled by the monitor
thread after its data processing. That means these two threads, UI
thread and Monitor
Thread, are in deadlock.
How to Resolve this Situation: When you have lots of continuous debug strings to be outputted, you need to keep your UI thread in one simple separated thread and run OutputDebugString
in other threads.
Using the Code
Interface Declaration
The class CWinDebugMonitor
has only one public
virtual method: OutputWinDebugString
, a developer can declare a class derived from CWinDebugMonitor
and implement the virtual method to process all of the captured debug strings.
#ifndef __WIN_DEBUG_BUFFER_H__
#define __WIN_DEBUG_BUFFER_H__
#include <windows.h>
#include <atlbase.h>
#include <atlstr.h>
class CWinDebugMonitor
{
private:
enum {
TIMEOUT_WIN_DEBUG = 100,
};
struct dbwin_buffer
{
DWORD dwProcessId;
char data[4096-sizeof(DWORD)];
};
private:
HANDLE m_hDBWinMutex;
HANDLE m_hDBMonBuffer;
HANDLE m_hEventBufferReady;
HANDLE m_hEventDataReady;
HANDLE m_hWinDebugMonitorThread;
BOOL m_bWinDebugMonStopped;
struct dbwin_buffer *m_pDBBuffer;
private:
DWORD Initialize();
void Unintialize();
DWORD WinDebugMonitorProcess();
static DWORD WINAPI WinDebugMonitorThread(void *pData);
public:
CWinDebugMonitor();
~CWinDebugMonitor();
public:
virtual void OutputWinDebugString(const char *str) {};
};
#endif
Initialization of CWinDebugMonitor
Initialize
method opens all the kernel objects described previously, and creates a thread monitoring and processing the debug messages.
DWORD CWinDebugMonitor::Initialize()
{
DWORD errorCode = 0;
BOOL bSuccessful = FALSE;
SetLastError(0);
CComBSTR DBWinMutex = L"DBWinMutex";
m_hDBWinMutex = ::OpenMutex(
MUTEX_ALL_ACCESS,
FALSE,
DBWinMutex
);
if (m_hDBWinMutex == NULL) {
errorCode = GetLastError();
return errorCode;
}
CComBSTR DBWIN_BUFFER_READY = L"DBWIN_BUFFER_READY";
m_hEventBufferReady = ::OpenEvent(
EVENT_ALL_ACCESS,
FALSE,
DBWIN_BUFFER_READY
);
if (m_hEventBufferReady == NULL) {
m_hEventBufferReady = ::CreateEvent(
NULL,
FALSE, TRUE, DBWIN_BUFFER_READY
);
if (m_hEventBufferReady == NULL) {
errorCode = GetLastError();
return errorCode;
}
}
CComBSTR DBWIN_DATA_READY = L"DBWIN_DATA_READY";
m_hEventDataReady = ::OpenEvent(
SYNCHRONIZE,
FALSE,
DBWIN_DATA_READY
);
if (m_hEventDataReady == NULL) {
m_hEventDataReady = ::CreateEvent(
NULL,
FALSE, FALSE, DBWIN_DATA_READY
);
if (m_hEventDataReady == NULL) {
errorCode = GetLastError();
return errorCode;
}
}
CComBSTR DBWIN_BUFFER = L"DBWIN_BUFFER";
m_hDBMonBuffer = ::OpenFileMapping(
FILE_MAP_READ,
FALSE,
DBWIN_BUFFER
);
if (m_hDBMonBuffer == NULL) {
m_hDBMonBuffer = ::CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
sizeof(struct dbwin_buffer),
DBWIN_BUFFER
);
if (m_hDBMonBuffer == NULL) {
errorCode = GetLastError();
return errorCode;
}
}
m_pDBBuffer = (struct dbwin_buffer *)::MapViewOfFile(
m_hDBMonBuffer,
SECTION_MAP_READ,
0,
0,
0
);
if (m_pDBBuffer == NULL) {
errorCode = GetLastError();
return errorCode;
}
m_bWinDebugMonStopped = FALSE;
m_hWinDebugMonitorThread = ::CreateThread(
NULL,
0,
WinDebugMonitorThread,
this,
0,
NULL
);
if (m_hWinDebugMonitorThread == NULL) {
m_bWinDebugMonStopped = TRUE;
errorCode = GetLastError();
return errorCode;
}
bSuccessful = ::SetPriorityClass(
::GetCurrentProcess(),
REALTIME_PRIORITY_CLASS
);
bSuccessful = ::SetThreadPriority(
m_hWinDebugMonitorThread,
THREAD_PRIORITY_TIME_CRITICAL
);
return errorCode;
}
Uninitialize
Uninitialize
is a private
method, it is automatically called in the destructor. It stops the monitor
thread and releases all the opened kernel objects.
void CWinDebugMonitor::Unintialize()
{
if (m_hWinDebugMonitorThread != NULL) {
m_bWinDebugMonStopped = TRUE;
::WaitForSingleObject(m_hWinDebugMonitorThread, INFINITE);
}
if (m_hDBWinMutex != NULL) {
CloseHandle(m_hDBWinMutex);
m_hDBWinMutex = NULL;
}
if (m_hDBMonBuffer != NULL) {
::UnmapViewOfFile(m_pDBBuffer);
CloseHandle(m_hDBMonBuffer);
m_hDBMonBuffer = NULL;
}
if (m_hEventBufferReady != NULL) {
CloseHandle(m_hEventBufferReady);
m_hEventBufferReady = NULL;
}
if (m_hEventDataReady != NULL) {
CloseHandle(m_hEventDataReady);
m_hEventDataReady = NULL;
}
m_pDBBuffer = NULL;
}
Process Captured Debug Strings
This method is called by the monitor
thread, when debug strings are available, the user implemented method OutputWinDebugString
is called here.
DWORD CWinDebugMonitor::WinDebugMonitorProcess()
{
DWORD ret = 0;
ret = ::WaitForSingleObject(m_hEventDataReady, TIMEOUT_WIN_DEBUG);
if (ret == WAIT_OBJECT_0) {
OutputWinDebugString(m_pDBBuffer->data);
SetEvent(m_hEventBufferReady);
}
return ret;
}
Monitor Thread
The monitor
thread is started automatically in Initialize
if all the initialization work is done successfully, and it is stopped by Uninitialize
.
DWORD WINAPI CWinDebugMonitor::WinDebugMonitorThread(void *pData)
{
CWinDebugMonitor *_this = (CWinDebugMonitor *)pData;
if (_this != NULL) {
while (!_this->m_bWinDebugMonStopped) {
_this->WinDebugMonitorProcess();
}
}
return 0;
}
Sample Code
It's very simple to use the source code, we can create two projects, one is monitor
for capturing debug messages, and one is responsible for sending continuous debug messages by calling OutputDebugString
. Firstly, run monitor.exe, then run output.exe, you will see all the messages sent out by output.exe will be captured by monitor.exe.
Notice: Do not test this in Visual Studio Debug Mode, because Visual Studio will capture all the debug messages before they come to WinDebugMonitor
.
Monitor Application
The monitor
captures all of the debug messages and prints them. If any key is pressed, it quits.
#include "WinDebugMonitor.h"
#include <conio.h>
class Monitor : public CWinDebugMonitor
{
public:
virtual void OutputWinDebugString(const char *str)
{
printf("%s", str);
};
};
void main()
{
printf("Win Debug Monitor Tool\n");
printf("----------------------\n");
Monitor mon;
getch();
}
Output Application
This application keeps sending a lot of debug messages, if any key is pressed, it quits the loop.
#include "stdafx.h"
#include <conio.h>
#include <windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
int i = 0;
printf("Press any key to stop calling OutputDebugString......\n");
while (!kbhit()) {
TCHAR buf[64];
_stprintf(buf, _T("Message from process %d, msg id: %d\n"),
::GetCurrentProcessId(), ++i);
OutputDebugString(buf);
}
printf("Total %d messages sent.\n", i);
return 0;
}
Run these Two Applications
Start two command consoles, run monitor.exe in one console, and run output.exe in the other console, all the debug messages sent out by output.exe will be captured and displayed by monitor.exe.
Points of Interest
The source code is a good sample for beginners who are learning Win32 multi-threading and kernel objects, such as mutex, events and shared memory. When I was looking for the source code of OutputDebugString
, I found that ReactOS has it. After researching on it, I wrote this article.
Notes: ReactOS is an open source OS which implements another Windows XP.
References
History
- 2008-2-21: Article created
- 2008-2-21: Article updated - removed process id compared with current process id
- 2008-2-21: Article updated - sample section and demo project download added