Figure 1   VDMDBG and TOOLHELP Events

VDMDBG Event TOOLHELP Equivalent
DBG_SEGLOAD NFY_LOADSEG
DBG_SEGMOVE  
DBG_SEGFREE NFY_FREESEG
DBG_MODLOAD  
DBG_MODFREE NFY_DELMODULE
DBG_SINGLESTEP INT_1
DBG_BREAK INT_3
DBG_GPFAULT INT_GPFAULT
DBG_DIVOVERFLOW INT_DIV0
DBG_INSTRFAULT INT_UDINSTR
DBG_TASKSTART NFY_STARTTASK
DBG_TASKSTOP NFY_EXITTASK
DBG_DLLSTART NFY_STARTDLL
DBG_DLLSTOP  
DBG_ATTACH  
DBG_TOOLHELP  
DBG_STACKFAULT INT_STKFAULT
DBG_WOWINIT  
DBG_TEMPBP  
DBG_MODMOVE  
DBG_INIT  
DBG_GPFAULT2  


Figure 3   VDMDBGDemo.CPP


 //==================================================
 // VDMDBGDemo - Matt Pietrek 1998
 // Microsoft Systems Journal, August 1998
 // FILE: VDMDBGDemo.CPP
 //==================================================
 #include <windows.h>
 #include <COMMCTRL.H>
 #include <stdio.h>
 #include <process.h>
 #include <tchar.h>
 #include <vdmdbg.h>
 #pragma hdrstop
 #include "VDMDBGDemo.h"
 #include "VDMDBGDemoDbgLoop.h"
 
 // Helper function prototypes
 void    Handle_WM_INITDIALOG(HWND hDlg);
 void    Handle_WM_COMMAND(HWND hWndDlg, WPARAM wParam, LPARAM lParam );
 void    Handle_WM_CLOSE( HWND hDlg );
 void    Handle_WM_SIZE(HWND hWndDlg, WPARAM wParam, LPARAM lParam );
 void    Handle_WM_NOTIFY( HWND hDlg, WPARAM wParam, LPARAM lParam );
 BOOL    CALLBACK VDMDBGDemoDlgProc(HWND,UINT,WPARAM,LPARAM);
 void    GetSetPositionInfoFromRegistry( BOOL fSave, POINT *lppt );
 void    PopulateTree( HWND hWndTree );
 
 HTREEITEM AddTreeviewSubItem(   HWND hWndTree, HTREEITEM hTreeItem, 
                                 LPTSTR pszItemText, BOOL fItemData = FALSE,
                                 LPARAM itemData = 0 );
                                 
 TCHAR gszRegistryKey[] = _T("Software\\WheatyProductions\\VDMDBGDemo");
 
 TCHAR gszMBTitle[] = _T( "VDMDBGDemo, by Matt Pietrek - MSJ August 1998" );
 
 TCHAR gszAboutText[] =
     _T("VDMDBGDemo displays information and events in NTVDM processes\r\n\r\n")
     _T("To monitor events in a NTVDM session, highlight the desired session, ")
     _T("then click the Attach button");
     
 TCHAR gszCloseWarning[] =
     _T("There is at least one NTVDM session being monitored.\r\n\r\nIf you ")
     _T("exit, the Se sessions will be terminated\r\n\r\nExit anyway?");
             
 // ============================== Start of code ===============================
 HWND g_hWndTree = 0;
 HWND g_hDlg = 0;
 DWORD g_cAttachedProcesses = 0;
 
 BOOL
 WINAPI
 VDMTaskEnumProc(    DWORD dwThreadId, WORD hMod16, WORD hTask16,
                     PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined )
 {
     TCHAR szTaskInfo[512];
     
     wsprintf(szTaskInfo,
         _T("hMod:%04X  hTask:%04X  ThreadId:%u  ModName: %hs  FileName:%hs"),
         hMod16, hTask16, dwThreadId, pszModName, pszFileName );
 
     HTREEITEM hTreeITem = AddTreeviewSubItem(g_hWndTree, (HTREEITEM)lpUserDefined,
                                              szTaskInfo );
     
     return FALSE;
 }
 
 BOOL WINAPI
 VDMProcessEnumProc( DWORD dwProcessId, DWORD dwAttributes, LPARAM lpUserDefined )
 {
     TCHAR szVDMInfo[256];
     
     wsprintf(  szVDMInfo, _T("NTVDM session (process id: %u  attributes:%X %s)"),
                dwProcessId, dwAttributes,
                dwAttributes & WOW_SYSTEM ? _T("(default session)") : _T("") );
 
     HTREEITEM hTreeItem = AddTreeviewSubItem( g_hWndTree, NULL, szVDMInfo,
                                               TRUE, dwProcessId );
     
     VDMEnumTaskWOWEx( dwProcessId, VDMTaskEnumProc, (LPARAM)hTreeItem );
 
     TreeView_Expand( g_hWndTree, hTreeItem, TVE_EXPAND );
     
     // _beginthread( VDMDebugThreadFunc, 0, (void *)dwProcessId );
     
     return FALSE;
 }
 
 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpszCmdLine, int nCmdShow )
 {
     InitCommonControls();
     
     // Bring up the user interface
     DialogBox(  hInstance, MAKEINTRESOURCE(IDD_VDMDBGDEMO),
                 0, (DLGPROC)VDMDBGDemoDlgProc );
     GetLastError();
     return 0;
 }
 
 BOOL CALLBACK VDMDBGDemoDlgProc( HWND hDlg,UINT msg,WPARAM wParam, LPARAM lParam )
 {
     // The dialog procedure for the main window
     switch ( msg )
     {
         case WM_INITDIALOG:
             Handle_WM_INITDIALOG( hDlg ); return TRUE;
         case WM_CLOSE:
             Handle_WM_CLOSE( hDlg ); break;
         case WM_SIZE:
             Handle_WM_SIZE( hDlg, wParam, lParam ); break;
         case WM_COMMAND:
             Handle_WM_COMMAND( hDlg, wParam, lParam ); break;
         case WM_NOTIFY:
             Handle_WM_NOTIFY( hDlg, wParam, lParam ); break;
         case WM_TIMER:
             PopulateTree( g_hWndTree ); break;
         case WM_LB_ADDITEM:
             {
                 TCHAR * pwszItem = (TCHAR *)lParam;
                 SendDlgItemMessage(hDlg, IDC_LIST1, LB_ADDSTRING, 0,
                                   (LPARAM)pwszItem);
                 delete []pwszItem;
             }
             break;
         // let everything else fall through
     }
     return FALSE;
 }
 
 //============================================================================
 // Walk through the list of objects, adding each object name to the root of
 // the treeview
 
 void PopulateTree( HWND hWndTree )
 {
     TreeView_DeleteAllItems( hWndTree );
     
     VDMEnumProcessWOW( VDMProcessEnumProc, (LPARAM)hWndTree );  
 }
 
 void Handle_WM_INITDIALOG(HWND hDlg)
 {
     // Get the window coordinates where the program was last running,
     // and move the window to that spot.
     POINT pt;
     GetSetPositionInfoFromRegistry( FALSE, &pt );
 
     SetWindowPos(hDlg, 0, pt.x, pt.y, 0, 0,
                  SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER | SWP_NOACTIVATE);
 
     g_hDlg = hDlg;
     g_hWndTree = GetDlgItem(hDlg, IDC_TREE1);
     
     PopulateTree( g_hWndTree );
     
     SetTimer( hDlg, 0, 10000, 0 );  // Make a 2 second timer
 }
 
 void Handle_WM_COMMAND(HWND hWndDlg, WPARAM wParam, LPARAM lParam )
 {
     WORD wNotifyCode = HIWORD(wParam); // notification code 
     WORD wID = LOWORD(wParam);         // item, control, or accelerator id
     HWND hwndCtl = (HWND) lParam;      // handle of control
     
     if ( IDC_BUTTON_REFRESH == wID )
     {
         if ( BN_CLICKED == wNotifyCode )
             PopulateTree( g_hWndTree );         
     }
     else if ( IDC_BUTTON_ATTACH == wID )
     {   
         HTREEITEM hTreeItem = TreeView_GetSelection( g_hWndTree );
         
         TVITEM tvi;
         tvi.hItem = hTreeItem;
         tvi.mask = TVIF_PARAM;
         tvi.lParam = 0;
 
         // Get the process ID for the session that we stashed away earlier      
         if ( TreeView_GetItem( g_hWndTree, &tvi ) && tvi.lParam )
         {
             // start a new thread to act as the debug loop
             _beginthread( VDMDebugThreadFunc, 0, (void *)tvi.lParam );
         }
         else    // User didn't pick a valid line
         {
             MessageBox( hWndDlg, _T("Please select an NTVDM Session line"),
                         0, MB_OK );
         }
     }
     else if ( IDC_BUTTON_ABOUT == wID )
     {
         if ( BN_CLICKED == wNotifyCode )
             MessageBox( hWndDlg, gszAboutText, gszMBTitle, MB_OK );
     }
 }
 
 void Handle_WM_NOTIFY( HWND hDlg, WPARAM wParam, LPARAM lParam )
 {
     if ( wParam != IDC_TREE1 )
         return;
 
     LPNMHDR pnmh = (LPNMHDR)lParam;
             
     if ( NM_DBLCLK != pnmh->code )
         return;
         
     HTREEITEM hTreeItem = TreeView_GetSelection( g_hWndTree );
     
     TVITEM tvi;
     tvi.hItem = hTreeItem;
     tvi.mask = TVIF_PARAM;  
     if ( TreeView_GetItem( g_hWndTree, &tvi ) )
     {
         if ( tvi.lParam )
             _beginthread( VDMDebugThreadFunc, 0, (void *)tvi.lParam );
     }       
 }
 
 void Handle_WM_CLOSE( HWND hDlg )
 {
 
     // If g_cAttachedProcesses is non-zero at program exit time, we need to
     // warn the user that exiting will terminate any debug loops, and hence
     // make the 16 bit programs is the associated NTVDM sessions go away.
     if ( g_cAttachedProcesses )
     {
         if ( IDNO == MessageBox(hDlg, gszCloseWarning, gszMBTitle, MB_YESNO))       
             return;
     }
     
     // Save off the window's X,Y coordinates for next time
     RECT rect;
     if ( GetWindowRect( hDlg, &rect ) )
         GetSetPositionInfoFromRegistry( TRUE, (LPPOINT)&rect );
         
     KillTimer( hDlg, 0 );
     
     EndDialog(hDlg, 0);
 }
 
 void Handle_WM_SIZE(HWND hWndDlg, WPARAM wParam, LPARAM lParam )
 {
     RECT tvRect, dlgRect;
     POINT pt;
     
     WORD nClientWidth = LOWORD(lParam);
     WORD nClientHeight= HIWORD(lParam);
     
     GetClientRect( hWndDlg, &dlgRect );     // Get size of dialog
     GetWindowRect( g_hWndTree, &tvRect );   // Get screen position of child
     
     pt.x = tvRect.left;     // Get the X,Y coordinates for the top left
     pt.y = tvRect.top;      // and reuse the M in the resized client 
     ScreenToClient( hWndDlg, &pt ); // Calculate screen X,Y of child window
         
     WORD tvWidth = nClientWidth - ( pt.x * 2);  // Equal spacing on all borders
     WORD tvHeight= nClientHeight - (WORD)(pt.y + pt.x);
     MoveWindow( g_hWndTree, pt.x, pt.y, tvWidth, tvHeight, TRUE );  
 }
 
 void GetSetPositionInfoFromRegistry( BOOL fSave, POINT *lppt )
 {
     // Function that saves or restores the coordinates of a dialog box
     // in the system registry.  Handles the case where there's nothing there.
     //
     HKEY hKey;
     DWORD dataSize, err, disposition;
     TCHAR szKeyName[] = _T("DlgCoordinates");
     
     if ( !fSave )               // In case the key's not there yet, we'll
         lppt->x = lppt->y = 0;  // return 0,0 for the coordinates
 
     // Open the registry key (or create it if the first time being used)
     err = RegCreateKeyEx( HKEY_CURRENT_USER, gszRegistryKey, 0, 0,
                           REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
                           0, &hKey, &disposition );
     if ( ERROR_SUCCESS != err )
         return;
 
     if ( fSave )            // Save out coordinates
     {
         RegSetValueEx(hKey,szKeyName, 0, REG_BINARY,(PBYTE)lppt,sizeof(*lppt));
     }
     else                    // read in coordinates
     {
         dataSize = sizeof(*lppt);
         RegQueryValueEx( hKey, szKeyName, 0, 0, (PBYTE)lppt, &dataSize );
     }
 
     RegCloseKey( hKey );
 }
 
 HTREEITEM AddTreeviewSubItem(   HWND hWndTree, HTREEITEM hTreeItem,
                                 LPTSTR pszItemText, BOOL fItemData,
                                 LPARAM itemData )
 {
     TVINSERTSTRUCT tvi;
     
     tvi.hParent = hTreeItem;
     tvi.hInsertAfter = TVI_LAST;
     tvi.item.mask = TVIF_TEXT;
     tvi.item.pszText = pszItemText;
     tvi.item.cchTextMax = lstrlen( pszItemText );
     if ( fItemData )
     {
         tvi.item.mask |= TVIF_PARAM;
         tvi.item.lParam = itemData;
     }
 
     return TreeView_InsertItem( hWndTree, &tvi );
}

Figure 4   VDMDBGDemoDbgLoop

VDMDBGDemoDbgLoop.H


void VDMDebugThreadFunc( void * args );
VDMDBGDemoDbgLoop.CPP

//==================================================
// VDMDBGDemo - Matt Pietrek 1998
// Microsoft Systems Journal, August 1998
// FILE: VDMDBGDemoDbgLoop.CPP
//==================================================
#include <windows.h>
#include <stdio.h>
#include <vdmdbg.h>
#include <tchar.h>
#include <stdarg.h>
#include "VDMDBGDemo.h"
#include "VDMDBGDemoDbgLoop.h"

//============================ Prototypes ===================================
int _lbPrintf( LPTSTR format, ... );
void HookupToPSAPI( void );

DWORD WINAPI GetModuleFileNameExW(  // Stolen from PSAPI.H, which not everybody
    HANDLE hProcess,                // may have...
    HMODULE hModule,
    LPWSTR lpFilename,
    DWORD nSize  );
typedef DWORD (WINAPI *PFNGETMODULEFILENAMEEXW)(HANDLE,HMODULE,LPWSTR,DWORD);

//============================= Variables ===================================
PFNGETMODULEFILENAMEEXW pfnGetModuleFileNameExW = 0;

__declspec(thread) HANDLE tls_hProcess;     // A per-thread variable

// Defined in VDMDBGDemo.CPP
extern HWND g_hDlg;
extern DWORD g_cAttachedProcesses;

//============================= Code ========================================
void HandleExceptionDebugEvent( DEBUG_EVENT &de )
{
    EXCEPTION_RECORD & exrec = de.u.Exception.ExceptionRecord;
    
    DWORD dwExceptionCode = exrec.ExceptionCode;
    
    if ( STATUS_VDM_EVENT == dwExceptionCode )
    {
        VDMProcessException( &de );
        
        LPTSTR pszEventName = 0;
        
        switch ( W1( exrec ) )
        {
            case DBG_SEGLOAD:
                {
                    pszEventName = _T("DBG_SEGLOAD");
                    SEGMENT_NOTE segNote;
                    DWORD cbRead = 0;
                    ReadProcessMemory(  tls_hProcess, (PVOID)DW3(exrec),
                                        &segNote, sizeof(segNote), &cbRead );
                    if ( sizeof(SEGMENT_NOTE) != cbRead )
                        break;
                        
                    _lbPrintf(  _T("%s(%u): %hs(%u)"), pszEventName,
                                de.dwThreadId, segNote.Module, segNote.Segment);
                    return;
                }

            case DBG_TASKSTART:     // the Se events can all be handled
            case DBG_DLLSTART:      // in the same manner
            case DBG_DLLSTOP:
            case DBG_TASKSTOP:
                {
                    switch( W1(exrec) )
                    {
                        case DBG_TASKSTART:
                            pszEventName = _T("DBG_TASKSTART"); break;
                        case DBG_DLLSTART:
                            pszEventName = _T("DBG_DLLSTART"); break;
                        case DBG_DLLSTOP:
                            pszEventName = _T("DBG_DLLSTOP"); break;
                        case DBG_TASKSTOP:
                            pszEventName = _T("DBG_TASKSTOP"); break;
                    }
                                                                    
                    IMAGE_NOTE imgNote;
                    DWORD cbRead;
                    ReadProcessMemory(  tls_hProcess, (PVOID)DW3(exrec),
                                        &imgNote, sizeof(imgNote), &cbRead );
                    if ( sizeof(IMAGE_NOTE) != cbRead )
                        break;

                    _lbPrintf( _T("%s(%u): %hs %hs"), pszEventName,
                               de.dwThreadId, imgNote.Module, imgNote.FileName);
                    return;
                }

            case DBG_MODFREE:
                {
                    pszEventName = _T("DBG_MODFREE");
                    SEGMENT_NOTE segNote;
                    DWORD cbRead;
                    ReadProcessMemory(  tls_hProcess, (PVOID)DW3(exrec),
                                        &segNote, sizeof(segNote), &cbRead );
                    if ( sizeof(SEGMENT_NOTE) == cbRead )
                    {
                        _lbPrintf(_T("%s(%u): %hs"),
                                  pszEventName, de.dwThreadId, segNote.Module);
                        return;
                    }
                    // else, fall through...
                    break;
                }
                
            case DBG_SEGMOVE: pszEventName = _T("DBG_SEGMOVE"); break;
            case DBG_SEGFREE: pszEventName = _T("DBG_SEGFREE"); break;
            case DBG_MODLOAD: pszEventName = _T("DBG_MODLOAD"); break;
            case DBG_SINGLESTEP: pszEventName = _T("DBG_SINGLESTEP"); break;
            case DBG_BREAK: pszEventName = _T("DBG_BREAK"); break;
            case DBG_GPFAULT: pszEventName = _T("DBG_GPFAULT"); break;
            case DBG_DIVOVERFLOW: pszEventName = _T("DBG_DIVOVERFLOW"); break;
            case DBG_INSTRFAULT: pszEventName = _T("DBG_INSTRFAULT"); break;
            case DBG_ATTACH: pszEventName = _T("DBG_ATTACH"); break;
        }

        _lbPrintf( _T("%s(%u)"), pszEventName, de.dwThreadId );
                        
        return;
    }

    _lbPrintf(
        _T("EXCEPTION_DEBUG_EVENT(%u): Code:%08X  Address:%08X  %s chance"),
        de.dwThreadId, dwExceptionCode, exrec.ExceptionAddress,
        de.u.Exception.dwFirstChance ? _T("first") : _T("second") );
                
}

void HandleLoadDllDebugEvent( DEBUG_EVENT &de )
{
    LOAD_DLL_DEBUG_INFO & loadInfo = de.u.LoadDll;
    
    if ( pfnGetModuleFileNameExW )
    {
        TCHAR szModName[MAX_PATH];

        szModName[0] = 0;
                
        pfnGetModuleFileNameExW(tls_hProcess, (HINSTANCE)loadInfo.lpBaseOfDll,
                                szModName, sizeof(szModName) );
                                
        _lbPrintf(  _T("LOAD_DLL_DEBUG_EVENT(%u): %08X %s"),
                    de.dwThreadId, loadInfo.lpBaseOfDll, szModName );
    }
    else
        _lbPrintf( _T("LOAD_DLL_DEBUG_EVENT(%u): %08X"),
                   de.dwThreadId, loadInfo.lpBaseOfDll );
    
    CloseHandle( loadInfo.hFile );  // Don't need this, so close it
}

void HandleCreateThreadDebugEvent( DEBUG_EVENT &de )
{
    CloseHandle( de.u.CreateThread.hThread ); // Don't need this, so close it
    
    _lbPrintf( _T("CREATE_THREAD_DEBUG_EVENT(%u)"), de.dwThreadId );
}

void HandleCreateProcessDebugEvent( DEBUG_EVENT &de )
{
    CREATE_PROCESS_DEBUG_INFO & cpdi = de.u.CreateProcessInfo;
    
    tls_hProcess = cpdi.hProcess;
    
    CloseHandle( cpdi.hFile );      // Don't need the Se, so
    CloseHandle( cpdi.hThread );    // close it!
    
    if ( pfnGetModuleFileNameExW )
    {
        TCHAR szModName[MAX_PATH];

        szModName[0] = 0;
                
        pfnGetModuleFileNameExW(tls_hProcess, (HINSTANCE)cpdi.lpBaseOfImage,
                                szModName, sizeof(szModName) );
                                
        _lbPrintf(  _T("CREATE_PROCESS_DEBUG_EVENT(%u): %08X %s"),
                    de.dwThreadId, cpdi.lpBaseOfImage, szModName );
    }
    else
        _lbPrintf( _T("CREATE_PROCESS_DEBUG_EVENT: %08X"), cpdi.lpBaseOfImage );
}

void HandleExitThreadDebugEvent( DEBUG_EVENT &de )
{
    _lbPrintf( _T("EXIT_THREAD_DEBUG_EVENT(%u): exit code:%u"),
               de.dwThreadId, de.u.ExitThread.dwExitCode );    
}

void HandleDebugEvent( DEBUG_EVENT &de )
{
    LPTSTR s;

    switch ( de.dwDebugEventCode )
    {
        case EXCEPTION_DEBUG_EVENT:
            HandleExceptionDebugEvent( de ); return;
        case LOAD_DLL_DEBUG_EVENT:
            HandleLoadDllDebugEvent( de ); return;
        case CREATE_THREAD_DEBUG_EVENT:
            HandleCreateThreadDebugEvent( de ); return;
        case CREATE_PROCESS_DEBUG_EVENT:
            HandleCreateProcessDebugEvent( de ); return;
        case EXIT_THREAD_DEBUG_EVENT:
            HandleExitThreadDebugEvent( de ); return;       
        case EXIT_PROCESS_DEBUG_EVENT: s = _T("EXIT_PROCESS_DEBUG_EVENT"); break;
        case UNLOAD_DLL_DEBUG_EVENT: s = _T("UNLOAD_DLL_DEBUG_EVENT"); break;
        case OUTPUT_DEBUG_STRING_EVENT: s = _T("OUTPUT_DEBUG_STRING_EVENT");
            break;
        case RIP_EVENT: s = _T("RIP_EVENT"); break;
    }

    _lbPrintf( _T("%s(%u)"), s, de.dwThreadId );        
}

void VDMDebugThreadFunc( void * args )
{
    DWORD dwProcessId = (DWORD)args;    // "args" is just the NTVDM process Id
            
    if ( !DebugActiveProcess( dwProcessId ) )
        return;

    // Increment the count describing how many debug loops we have.
    InterlockedIncrement( (LPLONG)&g_cAttachedProcesses );
    
    HookupToPSAPI();    // In case the user has PSAPI.DLL, we can use it to
                        // get a better description of debug events
    
    DEBUG_EVENT de;
    
    while ( WaitForDebugEvent( &de, INFINITE ) )
    {   
        HandleDebugEvent( de );
                        
        if ( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
            break;

        ContinueDebugEvent( de.dwProcessId, de.dwThreadId, DBG_CONTINUE );
    }       

    // Decrement the count describing how many debug loops we have.
    InterlockedDecrement( (LPLONG)&g_cAttachedProcesses );
}

void HookupToPSAPI( void )
{
    HMODULE hModPSAPI;
    
    if ( !pfnGetModuleFileNameExW )
    {
        hModPSAPI = LoadLibrary( _T("PSAPI.DLL") );
        if ( hModPSAPI )
            pfnGetModuleFileNameExW = (PFNGETMODULEFILENAMEEXW)
                        GetProcAddress( hModPSAPI, "GetModuleFileNameExW" );
    }
}

int _lbPrintf( LPTSTR format, ... )
{
    TCHAR szBuffer[1024];
    va_list argptr;
          
    va_start(argptr, format);
    int retValue = wvsprintf(szBuffer, format, argptr);
    va_end(argptr);

    LPTSTR pwszDbgEvent = _wcsdup( szBuffer );
    
    PostMessage( g_hDlg, WM_LB_ADDITEM, 0, (LPARAM)pwszDbgEvent );
    
    return retValue;
}