Click here to Skip to main content
Click here to Skip to main content

Mechanism of OutputDebugString

, 21 Feb 2008
Rate this:
Please Sign up or sign in to vote.
Write a monitoring application to capture debug messages

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:

    1. DBWinMutex: A Mutex object. Creates exclusive execution of OutputDebugString.
    2. DBWIN_BUFFER_READY: It is the name of an Event object. Monitor or Debugger posts signal to OutputDebugString when the shared memory is ready.
    3. DBWIN_DATA_READY: It is the name of an Event object. This event is signaled when OutputDebugString routine finishes writing to shared memory.
    4. 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.

//////////////////////////////////////////////////////////////
//
//         File: WinDebugMonitor.h
//  Description: Interface of class CWinDebugMonitor
//      Created: 2007-12-6
//       Author: Ken Zhang
//       E-Mail: cpp.china@hotmail.com
//
//////////////////////////////////////////////////////////////

#ifndef __WIN_DEBUG_BUFFER_H__
#define __WIN_DEBUG_BUFFER_H__

#include <span class="code-keyword"><windows.h></span>
#include <span class="code-keyword"><atlbase.h></span>
#include <span class="code-keyword"><atlstr.h></span>

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);

    // Mutex: DBWin
    // ---------------------------------------------------------
    CComBSTR DBWinMutex = L"DBWinMutex";
    m_hDBWinMutex = ::OpenMutex(
        MUTEX_ALL_ACCESS,
        FALSE,
        DBWinMutex
        );

    if (m_hDBWinMutex == NULL) {
        errorCode = GetLastError();
        return errorCode;
    }

    // Event: buffer ready
    // ---------------------------------------------------------
    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,    // auto-reset
            TRUE,    // initial state: signaled
            DBWIN_BUFFER_READY
            );

        if (m_hEventBufferReady == NULL) {
            errorCode = GetLastError();
            return errorCode;
        }
    }

    // Event: data ready
    // ---------------------------------------------------------
    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,    // auto-reset
            FALSE,    // initial state: nonsignaled
            DBWIN_DATA_READY
            );

        if (m_hEventDataReady == NULL) {
            errorCode = GetLastError();
            return errorCode;
        }
    }

    // Shared memory
    // ---------------------------------------------------------
    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;
    }

    // Monitoring thread
    // ---------------------------------------------------------
    m_bWinDebugMonStopped = FALSE;

    m_hWinDebugMonitorThread = ::CreateThread(
        NULL,
        0,
        WinDebugMonitorThread,
        this,
        0,
        NULL
        );

    if (m_hWinDebugMonitorThread == NULL) {
        m_bWinDebugMonStopped = TRUE;
        errorCode = GetLastError();
        return errorCode;
    }

    // set monitor thread's priority to highest
    // ---------------------------------------------------------
    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;

    // wait for data ready
    ret = ::WaitForSingleObject(m_hEventDataReady, TIMEOUT_WIN_DEBUG);

    if (ret == WAIT_OBJECT_0) {
        OutputWinDebugString(m_pDBBuffer->data);

        // signal buffer ready
        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 <span class="code-string">"WinDebugMonitor.h"</span>
#include <span class="code-keyword"><conio.h></span>

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 <span class="code-string">"stdafx.h"</span>
#include <span class="code-keyword"><conio.h></span>
#include <span class="code-keyword"><windows.h></span>

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

ken.zhang
Software Developer (Senior)
China China
Programming since 1997
Favorite language: C++, C#
Interest: OS, algorithm, embedded-system, UI design

Comments and Discussions

 
GeneralGood Stuff Pinmembersheds26-Jan-09 1:50 
GeneralNew! Wheel 3.0! PinmemberWoody1461926-Feb-08 13:38 
I've seen this done more ways than one.. the problem I have with most of them is that introducing the debug mechanism can cause other issues. Most of the time I need something like this is when working on a multi-threaded program that has an odd timing issue. Using a mutex or other locking component to push the data out can change the execution enough that when you turn on this "debugging" tool, the thing you're trying to debug doesn't happen.
 
What I'd love to see is a non-intrusive way to log whats going on. Maybe have a free-running collection thread that can be externally triggered to collect/view the logs AFTER the program hits a lock point.
GeneralInteresting... PinmvpStephen Hewitt21-Feb-08 13:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 21 Feb 2008
Article Copyright 2008 by ken.zhang
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid