http://www.codeproject.com/KB/vb/VisualTraceListener.aspx
Introduction
I was tooling around in the documentation pertaining to the TraceListener
class and I found that "..where no trace listeners are attached, trace messages are output using the OutputDebugString
API..."
This got me thinking - an application that can receive these OutputDebugString
messages can allow you to view the trace messages of an application without having to stop and restart it to add a trace listener.
To receive this output you need to attach to the running process as a debugger and then whenever the process needs a trace message your debugger will receive an OUTPUT_DEBUG_STRING_EVENT
event and you can get the string from that.
Writing a rudimentary debugger
There are a couple of API calls used in Windows NT for creating a debugger application:
DebugActiveProcess
which attaches to the process to start debugging it:
Collapse | Copy Code<DllImport("kernel32", CallingConvention:=CallingConvention.Winapi, _
EntryPoint:="DebugActiveProcess", _
ExactSpelling:=True, SetLastError:=True)> _
Public Shared Function DebugActiveProcess( <InAttribute()> _
ByVal ProcessHandle As Int32) As Boolean
End Function
WaitForDebugEvent
which pauses the caller until the process being debugged reaches a debug event:
Collapse | Copy Code<DllImport("kernel32", CallingConvention:=CallingConvention.Winapi, _
EntryPoint:="WaitForDebugEvent", _
ExactSpelling:=True, SetLastError:=True)> _
Public Shared Function WaitForDebugEvent( _
<OutAttribute(), MarshalAs(UnmanagedType.LPStruct)> _
ByVal DebugEvent As DebugApi.Structures.DEBUG_EVENT, _
<InAttribute()> ByVal dwMilliseconds As Int32) As Boolean
End Function
and ContinueDebugEvent
which resumes the debugee:
Collapse | Copy Code<DllImport("kernel32", CallingConvention:=CallingConvention.Winapi, _
EntryPoint:="ContinueDebugEvent", _
ExactSpelling:=True, SetLastError:=True)> _
Public Shared Function ContinueDebugEvent( _
<InAttribute()> ByVal dwProcessId As Int32, _
<InAttribute()> ByVal dwThreadId As Int32, _
<InAttribute(), MarshalAs(UnmanagedType.U4)> _
ByVal dwContinueStatus As DebugAPIConstatnts.DebugStates) As Boolean
End Function
To debug an application you need to attach to the process and then run a loop that waits for a debug event, handles the debug event and then resumes the debuggee.
Attaching to the process
The .NET Process
class has a member Id
which you pass to the DebugActiveProcess
API to start debugging that process.
Waiting for a debug event
Calling the WaitForDebug
event will block until a debug event occurs. When it does occur the details needed to handle it will be held in the structure DEBUG_EVENT
passed back.
Collapse | Copy Code<StructLayout(LayoutKind.Sequential)> _
Public Class DEBUG_EVENT
<MarshalAs(UnmanagedType.U4)> Public DebugEventCode _
As DebugApi.DebugAPIConstatnts.DebugEventTypes
Public ProcessId As Int32
Public ThreadId As Int32
Public lpDebugStringData As UInt32
Public IsUnicode As Int16
Public DebugStringLength As Int16
End Class
Handling the OUTPUT_DEBUG_STRING event
When we receive an OUTPUT_DEBUG_STRING
event there are three properties that we need to retrieve the actual string: lpDebugStringDate
is a pointer to the memory address of the string, IsUnicode
is true if the string is in Unicode format and DebugStringLength
tells you how long the string is...but there is a slight hitch: the address in lpDebugStringData
is a memory address in the debuggee application's address space.
Getting a string from another application's memory
To get at the data in another application's memory we need to use the ReadProcessmemory
API call:
Collapse | Copy Code<DllImport("kernel32", CallingConvention:=CallingConvention.Winapi, _
EntryPoint:="ReadProcessMemory", _
ExactSpelling:=True, SetLastError:=True)> _
Public Shared Function ReadProcessMemory( _
<InAttribute()> ByVal hProcess As Int32, _
<InAttribute()> ByVal lpBaseAddress As UInt32, _
<OutAttribute()> ByVal lpBuffer As IntPtr, _
<InAttribute()> ByVal nSize As Int32, _
<OutAttribute()> ByRef lpNumberOfBytesRead As UInt32 _
) As Boolean
End Function
For a bit of OO design I have implemented this as a function in the DEBIG_EVENT
class:
Collapse | Copy CodePublic Function GetString(ByVal ProcessHandle As Int32) As String
Dim sRet As String
Dim hProcess As Int32
If DebugStringLength.Equals(0) OrElse lpDebugStringData.Equals(0) Then
sRet = ""
Else
hProcess = _
DebugApiDeclarations.OpenProcess(_
DebugAPIConstatnts.ProcessAccessPriviledges.PROCESS_VM_READ,_
False, Me.ProcessId)
If hProcess <> 0 Then
Dim bytesReturned As UInt32
Dim lpBuffer As IntPtr
If Not IsUnicode Then
lpBuffer = Marshal.AllocHGlobal(DebugStringLength)
Else
lpBuffer = Marshal.AllocHGlobal(DebugStringLength * 2)
End If
If DebugApiDeclarations.ReadProcessMemory(hProcess, _
lpDebugStringData, lpBuffer, _
DebugStringLength, bytesReturned) Then
If IsUnicode Then
sRet = Marshal.PtrToStringUni(lpBuffer, DebugStringLength)
Else
sRet = Marshal.PtrToStringAnsi(lpBuffer, DebugStringLength)
End If
Marshal.FreeHGlobal(lpBuffer)
Call DebugApiDeclarations.CloseHandle(hProcess)
Else
If Not lpBuffer.Equals(IntPtr.Zero) Then
Marshal.FreeHGlobal(lpBuffer)
End If
Throw New Win32Exception
End If
Else
Throw New Win32Exception
End If
End If
Return sRet
End Function
And with that you have a basic debugger that can watch an application and take note of its trace messages...
<!-- Main Page Contents End -->
License
About the Author