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

DbMon.NET - A simple .NET OutputDebugString capturer

, 7 Mar 2006
Rate this:
Please Sign up or sign in to vote.
A .NET port of the VC++ 6.0 sample, 'dbmon'.

Introduction

The .NET class System.Diagnostics.Debug provides a set of methods and properties that help debug your code. An example for this might be System.Diagnostics.Debug.WriteLine("Program loaded."). You can capture this output by attaching a debugger to your program, or starting it in 'Debug' mode. But what if you launch your program without a debugger attached? Where do these messages go?

To answer this question, one needs to know that all method calls prefixed with Debug.Write* are redirected to the kernel32.dll function OutputDebugString. Therefore, tools like the famous and all beloved DebugView from SysInternals, which are capturing the output of this kernel call, can display debugging texts even if they are not .NET programs.

Still no answer to what happens to Debug.WriteLine calls if no debugger is attached. Perhaps DebugView's homepage can bring some light into our darkness, but the only information revealed is:

  • you don’t need a debugger to catch the debug output of your applications
  • nor do you need to modify your applications [...] to use non-standard debug output APIs.

According to this, it is possible to capture those calls without modifying any function tables or doing some "illegal" process memory modifications.

Let's go back to what we know for sure. Debug.Write* method calls are redirected to OutputDebugString. The MSDN API documentation for this function doesn't help us, the only information you will get is "If the application has no debugger [...], OutputDebugString does nothing.", which must be somehow wrong since it does at least something as we can see when using DebugView.

A few Google searches later, I stumbled upon a Visual C++ 6.0 sample called "DbMon - Implements a Debug Monitor" which explained everything needed to build a .NET OutputDebugString capturer. With the help of this sample, one can explain where those messages go when no debugger is attached.

OutputDebugString Internals

The kernel32.dll function OutputDebugString uses two techniques that help us capture debug messages:

Interprocess memory sharing via CreateFileMapping

The data passed to OutputDebugString is stored in a shared memory segment which can be accessed by every process running on the same machine. The name of this shared memory segment is DBWIN_BUFFER. To read this memory, you just have to create a new file mapping to and a view this segment. Then you are prepared to read from this memory although it is outside your process scope.

Interprocess synchronization via CreateEvent/SetEvent

To get notified when a new message is available in our DBWIN_BUFFER, Microsoft uses two interprocess events called "DBWIN_BUFFER_READY" and "DBWIN_DATA_READY". The first event is used to let application(s) know that someone is listening to the shared buffer segment. The second event is used to notify the capturing application that data is available.

The shared memory segment used in OutputDebugString is rather trivial. The first DWORD (4 bytes) is the process ID of the client application which called OutputDebugString, the rest of the buffer is a null (\0) terminated string contain the debugging text.

PID
(4 bytes)
text
(n bytes terminated with \0)
                         

Defining the .NET interface

Now that we know the internals of Debug.Write*, we can start building a .NET framework around it. It should be usable via delegates and one shall turn it on or off. That's all we need to make it publicly visible, the rest is not relevant for building a capturing application.

An example console application might look like this:

public static void Main(string[] args) {
    DebugMonitor.Start();
    DebugMonitor.OnOutputDebugString += new 
          OnOutputDebugStringHandler(OnOutputDebugString);
    Console.WriteLine("Press 'Enter' to exit.");
    Console.ReadLine();
    DebugMonitor.Stop();
}

private static void OnOutputDebugString(int pid, string text) {
    Console.WriteLine(DateTime.Now + ": [" + pid + "] " + text);
}

Implementing the .NET monitor

void DbMon.NET.DebugMonitor.Start()

To make this debug monitor listen on OutputDebugString calls, we need to setup our two events mentioned above and create a file mapping to the shared buffer.

public static void Start() {
    // ...
    
    // Create the event for slot 'DBWIN_BUFFER_READY'
    m_AckEvent = CreateEvent(ref sa, false, 
                   false, "DBWIN_BUFFER_READY");
    if (m_AckEvent == IntPtr.Zero) {
        throw CreateApplicationException("Failed to create" + 
                                         " event 'DBWIN_BUFFER_READY'");
    }    
    
    // Create the event for slot 'DBWIN_DATA_READY'
    m_ReadyEvent = CreateEvent(ref sa, false, false, "DBWIN_DATA_READY");
    if (m_ReadyEvent == IntPtr.Zero) {
        throw CreateApplicationException("Failed to create" + 
                                         " event 'DBWIN_DATA_READY'");
    }    
    
    // Get a handle to the readable shared memory at slot 'DBWIN_BUFFER'.
    m_SharedFile = CreateFileMapping(new IntPtr(-1), ref sa, 
                   PageProtection.ReadWrite, 0, 4096, "DBWIN_BUFFER");
    if (m_SharedFile == IntPtr.Zero) {
        throw CreateApplicationException("Failed to create" + 
                   " a file mapping to slot 'DBWIN_BUFFER'");
    }
        
    //...

Also, create a new thread where we can listen to the event DBWIN_DATA_READY for not blocking the executing thread when calling Start.

    // ...

    // Start a new thread where we can capture the output
    // of OutputDebugString calls so we don't block here.
    m_Capturer = new Thread(new ThreadStart(Capture));
    m_Capturer.Start();                
}

void DbMon.NET.DebugMonitor.Capture()

The Capture method is an endless loop which waits for a signal to the event DBWIN_DATA_READY. If it gets notified, it starts reading the shared memory segment DBWIN_BUFFER, extracts the information, and calls the .NET event DebugMonitor.OnOutputDebugString.

private static void Capture() {                    
    // ...
    
    while (true) {        
        SetEvent(m_AckEvent);    
        // ...
        WaitForSingleObject(m_ReadyEvent, INFINITE);                
        // ...        
        if (OnOutputDebugString != null)
            OnOutputDebugString(pid, text);        
    }    
                    
    // ...
}

Control flow of the method Capture().

Reading the shared memory segment

The Visual Studio C++ sample is simple to port to .NET. You just need to P/Invoke all external function calls. But two lines of code are a bit trickier since they use C's ability of manipulating pointers:

LPSTR String = (LPSTR)SharedMem + sizeof(DWORD);
DWORD pThisPid = SharedMem;

The first line assigns to the variable String a new pointer of SharedMem starting at the offset sizeof(DWORD) (4 bytes). Since we know how this buffer is structured, we can use the helper class System.Runtime.InteropServices.Marshal to extract the PID, which can be done by reading the first 4 bytes as an Int32:

int pid = Marshal.ReadInt32(SharedMem);

To extract the text, we need to skip the first 4 bytes.

IntPtr sharedMemAfterPid = new IntPtr(SharedMem.ToInt32() + 
                           Marshal.SizeOf(typeof(Int32)));

Now we can use Marshal.PtrToStringAnsi to copy the content of this pointer to a .NET string:

string text = Marshal.PtrToStringAnsi(sharedMemAfterPid);

Conclusion

This article is not meant to be a replacement to SysInternals' DebugView. It is the best tool for debugging programs when attaching to a debugger is not an option. The intent is to show how OutputDebugString works, and I hope I could help in bringing some light into this darkness.

References

  • DebugView: An (the) OutputDebugString capturer.
  • PInvoke: Porting Win32 API calls to .NET.
  • DbMon: Implements a Debug Monitor.

API References

License

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

Share

About the Author

Christian Birkl
Web Developer
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionNice article, help create a new 'debugviewer' ? Pinmemberjan wilmans16-Oct-13 9:19 
GeneralThanks! Pinmembershuizuan20-Oct-12 3:04 
GeneralRe: Thanks! Pinprofessionalryowu27-Nov-13 19:54 
QuestionHow to catch KdPrint() output from kernel? PinmemberGeorge1189-Oct-12 8:08 
QuestionThank you! This is great! Pinmemberryowu18-May-12 15:07 
GeneralMessage filtering based on TraceLevel PinmemberMember 240217113-Sep-10 22:32 
Generaloriginal DBMON source code Pinmemberumeca744-Mar-10 22:14 
GeneralRe: original DBMON source code PinmemberGarth J Lancaster4-Apr-10 12:42 
QuestionWhat license is your code under...? PinmemberWing Flanagan18-Dec-08 12:21 
AnswerRe: What license is your code under...? PinmemberChristian Birkl22-Dec-08 11:46 
GeneralRe: What license is your code under...? Pinmemberchrisbhmg8-Aug-09 9:58 
GeneralAnother alternative Pinmemberchaiguy133718-Nov-08 5:35 
If you're only needing to receive trace/debug messages from the current process, you can create a TraceListener (either by instantiating an existing one or inheriting your own--the interface is simple). Much simpler for in-process debug messages.
 
“It behooves every man to remember that the work of the critic, is of altogether secondary importance, and that, in the end, progress is accomplished by the man who does things.”
–Theodore Roosevelt
 
{o,o}.oO( Check out my blog! )
|)””’)          http://pihole.org/
-”-”-

QuestionHow to capture under windows 2003 terminal service PinmemberSilent Night28-Jul-07 3:05 
AnswerRe: How to capture under windows Vista Ultimate PinmemberOliman25-Jun-08 13:31 
GeneralA bug PinmemberIgorp7113-Jul-07 5:21 
QuestionCapture Global Win32 PinmemberIgorp7111-Jul-07 13:08 
AnswerRe: Capture Global Win32 PinmemberIgorp7111-Jul-07 16:06 
AnswerRe: Capture Global Win32 PinmemberSiva Chandran P7-Jan-12 6:07 
QuestionUsing in a windows service? Pinmembermelchizedek19-Jun-07 11:15 
AnswerRe: Using in a windows service? Pinmembermelchizedek20-Jun-07 5:30 
AnswerRe: Using in a windows service? PinmemberSiva Chandran P7-Jan-12 6:09 
GeneralNice! Pinmembermelchizedek12-Jun-07 10:58 
GeneralNot capturing from ASP.NET Pinmemberjfdoubell19-Apr-07 8:24 
GeneralNice article Pinmemberjamni12327-Jul-06 22:32 
AnswerRe: Nice article PinmemberChristian Birkl29-Jul-06 8:25 
GeneralVery nice work PinmemberAlois Kraus11-Apr-06 2:27 
QuestionTraceMonitor??? Pinmemberbaloban7-Mar-06 15:03 
AnswerRe: TraceMonitor??? PinmemberChristian Birkl16-Mar-06 10:44 
GeneralRe: TraceMonitor??? Pinmemberguy kolb18-Apr-06 7:11 

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.140827.1 | Last Updated 7 Mar 2006
Article Copyright 2006 by Christian Birkl
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid