Skip to content

Instantly share code, notes, and snippets.

@mfcollins3
Created June 2, 2013 21:31
Show Gist options
  • Save mfcollins3/5695056 to your computer and use it in GitHub Desktop.
Save mfcollins3/5695056 to your computer and use it in GitHub Desktop.
Windows console mode program that will capture debug trace messages written by a process using OutputDebugString and will write the messages to standard output

Understanding OutputDebugString

This Gist contains the sample code for my blog post titled Understanding OutputDebugString. This Gist contains two programs. The first program will simply capture the debug output written using OutputDebugString from any process and will write the debug messages to standard output. The second program will modify the first by running a specific program as a child process and outputting the debug messages from that process only.

License

Copyright © 2013 Michael F. Collins, III

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// Copyright 2013 Michael F. Collins, III
// This program will enhance the previous sample by running
// a program as a child process and only outputting the debug
// messages from the child process to standard output.
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Text;
using System.Threading;
internal class Program
{
private static void Main(string[] args)
{
// To capture the output from OutputDebugString, we need a
// shared memory buffer and two events.
MemoryMappedFile memoryMappedFile = null;
EventWaitHandle bufferReadyEvent = null;
EventWaitHandle dataReadyEvent = null;
try
{
memoryMappedFile = MemoryMappedFile.CreateNew("DBWIN_BUFFER", 4096L);
// We try to create the events. If the events exist, we
// will report an error and abort.
bool created;
bufferReadyEvent = new EventWaitHandle(
false,
EventResetMode.AutoReset,
"DBWIN_BUFFER_READY",
out created);
if (!created)
{
Console.Error.WriteLine("The DBWIN_BUFFER_READY event exists.");
return;
}
dataReadyEvent = new EventWaitHandle(
false,
EventResetMode.AutoReset,
"DBWIN_DATA_READY",
out created);
if (!created)
{
Console.Error.WriteLine("The DBWIN_DATA_READY event exists.");
return;
}
// I am using a cancellation token to control the lifetime of
// the program. You can terminate the program by pressing
// CTRL+C and the handler that I defined will set the cancellation
// token.
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
Console.CancelKeyPress += (sender, e) =>
{
cancellationTokenSource.Cancel();
e.Cancel = true;
};
// Run the child process with the arguments specified on the
// command line.
var processPath = args[0];
var arguments = 1 == args.Length
? string.Empty
: string.Join(" ", args, 1, args.Length - 1);
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo(processPath, arguments)
};
process.Exited += (sender, e) =>
{
cancellationTokenSource.Cancel();
};
process.Start();
// During the message processing loop, I want to check every
// second to see if the user has pressed the CTRL+C key to end
// the program.
var timeout = TimeSpan.FromSeconds(1.0);
bufferReadyEvent.Set();
while (!cancellationToken.IsCancellationRequested)
{
if (!dataReadyEvent.WaitOne(timeout))
{
continue;
}
using (var stream = memoryMappedFile.CreateViewStream())
{
using (var reader = new BinaryReader(stream, Encoding.Default))
{
var processId = reader.ReadUInt32();
if (processId == process.Id)
{
var chars = reader.ReadChars(4092);
index = Array.IndexOf(chars, '\0');
var message = new string(chars, 0, index);
Console.Out.Write("{0}: {1}", processId, message);
}
}
}
// The message has been processed, so trigger the
// DBWIN_BUFFER_READY event in order to receive the next message
// from the process being debugged.
bufferReadyEvent.Set();
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
Console.ReadLine();
}
finally
{
if (null != memoryMappedFile)
{
memoryMappedFile.Dispose();
}
if (null != bufferReadyEvent)
{
bufferReadyEvent.Dispose();
}
if (null != dataReadyEvent)
{
dataReadyEvent.Dispose();
}
}
}
}
// Copyright 2013 Michael F. Collins, III
// This program will demonstrate how to capture the output from
// OutputDebugString and will write the output to standard output.
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Text;
using System.Threading;
internal class Program
{
private static void Main()
{
// To capture the output from OutputDebugString, we need a
// shared memory buffer and two events.
MemoryMappedFile memoryMappedFile = null;
EventWaitHandle bufferReadyEvent = null;
EventWaitHandle dataReadyEvent = null;
try
{
memoryMappedFile = MemoryMappedFile.CreateNew("DBWIN_BUFFER", 4096L);
// We try to create the events. If the events exist, we
// will report an error and abort.
bool created;
bufferReadyEvent = new EventWaitHandle(
false,
EventResetMode.AutoReset,
"DBWIN_BUFFER_READY",
out created);
if (!created)
{
Console.Error.WriteLine("The DBWIN_BUFFER_READY event exists.");
return;
}
dataReadyEvent = new EventWaitHandle(
false,
EventResetMode.AutoReset,
"DBWIN_DATA_READY",
out created);
if (!created)
{
Console.Error.WriteLine("The DBWIN_DATA_READY event exists.");
return;
}
// I am using a cancellation token to control the lifetime of
// the program. You can terminate the program by pressing
// CTRL+C and the handler that I defined will set the cancellation
// token.
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
Console.CancelKeyPress += (sender, e) =>
{
cancellationTokenSource.Cancel();
e.Cancel = true;
};
// During the message processing loop, I want to check every
// second to see if the user has pressed the CTRL+C key to end
// the program.
var timeout = TimeSpan.FromSeconds(1.0);
bufferReadyEvent.Set();
while (!cancellationToken.IsCancellationRequested)
{
if (!dataReadyEvent.WaitOne(timeout))
{
continue;
}
using (var stream = memoryMappedFile.CreateViewStream())
{
using (var reader = new BinaryReader(stream, Encoding.Default))
{
var processId = reader.ReadUInt32();
// Because I specified the Encoding.Default object as the
// encoding for the BinaryReader object, the characters
// will be read out of memory as 8-bit octets and converted
// to Unicode characters by .NET.
var chars = reader.ReadChars(4092);
// The debug message will be null-terminated in memory,
// so I am looking for the null character in the character
// array to determine the bounds of the message to output.
index = Array.IndexOf(chars, '\0');
var message = new string(chars, 0, index);
Console.Out.Write("{0}: {1}", processId, message);
}
}
// The message has been processed, so trigger the
// DBWIN_BUFFER_READY event in order to receive the next message
// from the process being debugged.
bufferReadyEvent.Set();
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
Console.ReadLine();
}
finally
{
if (null != memoryMappedFile)
{
memoryMappedFile.Dispose();
}
if (null != bufferReadyEvent)
{
bufferReadyEvent.Dispose();
}
if (null != dataReadyEvent)
{
dataReadyEvent.Dispose();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment