Click here to Skip to main content
Licence CPOL
First Posted 7 Mar 2006
Views 66,864
Bookmarked 68 times

DbMon.NET - A simple .NET OutputDebugString capturer

By | 7 Mar 2006 | Article
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)

About the Author

Christian Birkl

Web Developer

Germany Germany

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionThank you! This is great! Pinmemberryowu15:07 18 May '12  
GeneralMessage filtering based on TraceLevel PinmemberMember 240217122:32 13 Sep '10  
Generaloriginal DBMON source code Pinmemberumeca7422:14 4 Mar '10  
GeneralRe: original DBMON source code PinmemberGarth J Lancaster12:42 4 Apr '10  
QuestionWhat license is your code under...? PinmemberWing Flanagan12:21 18 Dec '08  
AnswerRe: What license is your code under...? PinmemberChristian Birkl11:46 22 Dec '08  
GeneralRe: What license is your code under...? Pinmemberchrisbhmg9:58 8 Aug '09  
GeneralAnother alternative Pinmemberchaiguy13375:35 18 Nov '08  
QuestionHow to capture under windows 2003 terminal service PinmemberSilent Night3:05 28 Jul '07  
AnswerRe: How to capture under windows Vista Ultimate PinmemberOliman13:31 25 Jun '08  
GeneralA bug PinmemberIgorp715:21 13 Jul '07  
QuestionCapture Global Win32 PinmemberIgorp7113:08 11 Jul '07  
AnswerRe: Capture Global Win32 PinmemberIgorp7116:06 11 Jul '07  
AnswerRe: Capture Global Win32 PinmemberSiva Chandran P6:07 7 Jan '12  
QuestionUsing in a windows service? Pinmembermelchizedek11:15 19 Jun '07  
AnswerRe: Using in a windows service? Pinmembermelchizedek5:30 20 Jun '07  
AnswerRe: Using in a windows service? PinmemberSiva Chandran P6:09 7 Jan '12  
GeneralNice! Pinmembermelchizedek10:58 12 Jun '07  
GeneralNot capturing from ASP.NET Pinmemberjfdoubell8:24 19 Apr '07  
GeneralNice article Pinmemberjamni12322:32 27 Jul '06  
AnswerRe: Nice article PinmemberChristian Birkl8:25 29 Jul '06  
GeneralVery nice work PinmemberAlois Kraus2:27 11 Apr '06  
This article has helped me a lot to write unit tests which do pass their output via OutputDebugString. Keep up the good work.
 
Yours,
Alois Kraus

QuestionTraceMonitor??? Pinmemberbaloban15:03 7 Mar '06  
AnswerRe: TraceMonitor??? PinmemberChristian Birkl10:44 16 Mar '06  
GeneralRe: TraceMonitor??? Pinmemberguy kolb7:11 18 Apr '06  

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.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120529.1 | Last Updated 7 Mar 2006
Article Copyright 2006 by Christian Birkl
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid