Click here to Skip to main content
15,868,101 members
Articles / Programming Languages / Visual Basic
Article

Using the Debugger API to get trace messages from an executing application.

Rate me:
Please Sign up or sign in to vote.
4.76/5 (14 votes)
4 Nov 2005CPOL2 min read 158.7K   614   44   14
Trace.WriteLine outputs messages by OutputDebugString when no listeners are attached. This article shows how you can pick these messages up.

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:

VB
<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:

VB
<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:

VB
<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.

VB
<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:

VB
<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:

VB
Public 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
            'Allocate enough space to put the string into
            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
                'Make this buffer into a string...
                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...

License

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


Written By
Software Developer
Ireland Ireland
C# / SQL Server developer
Microsoft MVP (Azure) 2017
Microsoft MVP (Visual Basic) 2006, 2007

Comments and Discussions

 
QuestionBug Finder Pin
Antonio Petricca7-Jun-13 0:01
Antonio Petricca7-Jun-13 0:01 
Question相当不错:) Pin
jamesghq7-Nov-12 2:37
jamesghq7-Nov-12 2:37 
GeneralDebugger API OpenProcess Pin
Vitoto3-Jan-06 17:27
Vitoto3-Jan-06 17:27 
GeneralLOG WriteProcessMemory Pin
Vitoto1-Jan-06 9:53
Vitoto1-Jan-06 9:53 
GeneralRe: LOG WriteProcessMemory Pin
Duncan Edwards Jones9-Jan-06 5:54
professionalDuncan Edwards Jones9-Jan-06 5:54 
GeneralRe: LOG WriteProcessMemory Pin
Vitoto10-Jan-06 5:38
Vitoto10-Jan-06 5:38 
QuestionDoble Debug ? Pin
Vitoto20-Dec-05 5:59
Vitoto20-Dec-05 5:59 
Generalwww.ebook5.com Find your IT ebooks for free! Pin
testcooll16-Nov-05 22:22
testcooll16-Nov-05 22:22 
GeneralRe: www.ebook5.com Find your IT ebooks for free! Pin
Duncan Edwards Jones16-Nov-05 23:30
professionalDuncan Edwards Jones16-Nov-05 23:30 
Generaldbgview Pin
Dave Cross8-Nov-05 5:47
professionalDave Cross8-Nov-05 5:47 
GeneralRe: dbgview Pin
Duncan Edwards Jones8-Nov-05 6:04
professionalDuncan Edwards Jones8-Nov-05 6:04 
GeneralRe: dbgview Pin
afisser9-Nov-05 1:43
afisser9-Nov-05 1:43 
GeneralGreat stuff Pin
Hal Angseesing8-Nov-05 4:03
professionalHal Angseesing8-Nov-05 4:03 
GeneralStandard caveat when doing debugging Pin
Duncan Edwards Jones4-Nov-05 1:59
professionalDuncan Edwards Jones4-Nov-05 1:59 

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

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