Click here to Skip to main content
11,413,961 members (73,062 online)
Click here to Skip to main content

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

, 4 Nov 2005 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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

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

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

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

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

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)

Share

About the Author

Duncan Edwards Jones
Software Developer (Senior)
Ireland Ireland
C# / SQL Server developer
Microsoft MVP 2006, 2007
Visual Basic .NET
Follow on   Twitter   LinkedIn

Comments and Discussions

 
QuestionBug Finder Pin
Antonio Petricca, 7-Jun-13 1:01
memberAntonio Petricca7-Jun-13 1:01 
Question相当不错:) Pin
jamesghq, 7-Nov-12 3:37
memberjamesghq7-Nov-12 3:37 
GeneralDebugger API OpenProcess Pin
Vitoto, 3-Jan-06 18:27
memberVitoto3-Jan-06 18:27 
GeneralLOG WriteProcessMemory Pin
Vitoto, 1-Jan-06 10:53
memberVitoto1-Jan-06 10:53 
GeneralRe: LOG WriteProcessMemory Pin
Duncan Edwards Jones, 9-Jan-06 6:54
memberDuncan Edwards Jones9-Jan-06 6:54 
GeneralRe: LOG WriteProcessMemory Pin
Vitoto, 10-Jan-06 6:38
memberVitoto10-Jan-06 6:38 
QuestionDoble Debug ? Pin
Vitoto, 20-Dec-05 6:59
memberVitoto20-Dec-05 6:59 
Generalwww.ebook5.com Find your IT ebooks for free! Pin
shihaibo, 16-Nov-05 23:22
membershihaibo16-Nov-05 23:22 
GeneralRe: www.ebook5.com Find your IT ebooks for free! Pin
Duncan Edwards Jones, 17-Nov-05 0:30
memberDuncan Edwards Jones17-Nov-05 0:30 
Generaldbgview Pin
Dave Cross, 8-Nov-05 6:47
memberDave Cross8-Nov-05 6:47 
GeneralRe: dbgview Pin
Duncan Edwards Jones, 8-Nov-05 7:04
memberDuncan Edwards Jones8-Nov-05 7:04 
GeneralRe: dbgview Pin
afisser, 9-Nov-05 2:43
memberafisser9-Nov-05 2:43 
GeneralGreat stuff Pin
Hal Angseesing, 8-Nov-05 5:03
memberHal Angseesing8-Nov-05 5:03 
GeneralStandard caveat when doing debugging Pin
Duncan Edwards Jones, 4-Nov-05 2:59
memberDuncan Edwards Jones4-Nov-05 2:59 

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 | Terms of Use | Mobile
Web01 | 2.8.150427.2 | Last Updated 4 Nov 2005
Article Copyright 2005 by Duncan Edwards Jones
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid