//----------------------------------------------------------------------------
//
// Callback notification routines.
//
// Copyright (C) Microsoft Corporation, 2000-2001.
//
//----------------------------------------------------------------------------

#include "ntsdp.hpp"

//----------------------------------------------------------------------------
//
// APC support for dispatching event callbacks on the proper thread.
//
//----------------------------------------------------------------------------

struct AnyApcData
{
    AnyApcData(ULONG Mask, PCSTR Name)
    {
        m_Mask = Mask;
        m_Name = Name;
    }
    
    ULONG m_Mask;
    PCSTR m_Name;

    virtual ULONG Dispatch(DebugClient* Client) = 0;
};

struct BreakpointEventApcData : public AnyApcData
{
    BreakpointEventApcData()
        : AnyApcData(DEBUG_EVENT_BREAKPOINT,
                     "IDebugEventCallbacks::Breakpoint")
    {
    }
    
    Breakpoint* m_Bp;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        if ((m_Bp->m_Flags & DEBUG_BREAKPOINT_ADDER_ONLY) == 0 ||
            Client == m_Bp->m_Adder)
        {
            return Client->m_EventCb->
                Breakpoint(m_Bp);
        }
        else
        {
            return DEBUG_STATUS_NO_CHANGE;
        }
    }
};

struct ExceptionEventApcData : public AnyApcData
{
    ExceptionEventApcData()
        : AnyApcData(DEBUG_EVENT_EXCEPTION,
                     "IDebugEventCallbacks::Exception")
    {
    }
    
    PEXCEPTION_RECORD64 m_Record;
    ULONG m_FirstChance;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            Exception(m_Record, m_FirstChance);
    }
};

struct CreateThreadEventApcData : public AnyApcData
{
    CreateThreadEventApcData()
        : AnyApcData(DEBUG_EVENT_CREATE_THREAD,
                     "IDebugEventCallbacks::CreateThread")
    {
    }
    
    ULONG64 m_Handle;
    ULONG64 m_DataOffset;
    ULONG64 m_StartOffset;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            CreateThread(m_Handle, m_DataOffset, m_StartOffset);
    }
};

struct ExitThreadEventApcData : public AnyApcData
{
    ExitThreadEventApcData()
        : AnyApcData(DEBUG_EVENT_EXIT_THREAD,
                     "IDebugEventCallbacks::ExitThread")
    {
    }
    
    ULONG m_ExitCode;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            ExitThread(m_ExitCode);
    }
};

struct CreateProcessEventApcData : public AnyApcData
{
    CreateProcessEventApcData()
        : AnyApcData(DEBUG_EVENT_CREATE_PROCESS,
                     "IDebugEventCallbacks::CreateProcess")
    {
    }
    
    ULONG64 m_ImageFileHandle;
    ULONG64 m_Handle;
    ULONG64 m_BaseOffset;
    ULONG m_ModuleSize;
    PCSTR m_ModuleName;
    PCSTR m_ImageName;
    ULONG m_CheckSum;
    ULONG m_TimeDateStamp;
    ULONG64 m_InitialThreadHandle;
    ULONG64 m_ThreadDataOffset;
    ULONG64 m_StartOffset;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            CreateProcess(m_ImageFileHandle, m_Handle, m_BaseOffset,
                          m_ModuleSize, m_ModuleName, m_ImageName,
                          m_CheckSum, m_TimeDateStamp, m_InitialThreadHandle,
                          m_ThreadDataOffset, m_StartOffset);
    }
};

struct ExitProcessEventApcData : public AnyApcData
{
    ExitProcessEventApcData()
        : AnyApcData(DEBUG_EVENT_EXIT_PROCESS,
                     "IDebugEventCallbacks::ExitProcess")
    {
    }
    
    ULONG m_ExitCode;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            ExitProcess(m_ExitCode);
    }
};

struct LoadModuleEventApcData : public AnyApcData
{
    LoadModuleEventApcData()
        : AnyApcData(DEBUG_EVENT_LOAD_MODULE,
                     "IDebugEventCallbacks::LoadModule")
    {
    }
    
    ULONG64 m_ImageFileHandle;
    ULONG64 m_BaseOffset;
    ULONG m_ModuleSize;
    PCSTR m_ModuleName;
    PCSTR m_ImageName;
    ULONG m_CheckSum;
    ULONG m_TimeDateStamp;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            LoadModule(m_ImageFileHandle, m_BaseOffset, m_ModuleSize,
                       m_ModuleName, m_ImageName, m_CheckSum,
                       m_TimeDateStamp);
    }
};

struct UnloadModuleEventApcData : public AnyApcData
{
    UnloadModuleEventApcData()
        : AnyApcData(DEBUG_EVENT_UNLOAD_MODULE,
                     "IDebugEventCallbacks::UnloadModule")
    {
    }
    
    PCSTR m_ImageBaseName;
    ULONG64 m_BaseOffset;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            UnloadModule(m_ImageBaseName, m_BaseOffset);
    }
};

struct SystemErrorEventApcData : public AnyApcData
{
    SystemErrorEventApcData()
        : AnyApcData(DEBUG_EVENT_SYSTEM_ERROR,
                     "IDebugEventCallbacks::SystemError")
    {
    }
    
    ULONG m_Error;
    ULONG m_Level;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            SystemError(m_Error, m_Level);
    }
};

struct SessionStatusApcData : public AnyApcData
{
    SessionStatusApcData()
        : AnyApcData(DEBUG_EVENT_SESSION_STATUS,
                     "IDebugEventCallbacks::SessionStatus")
    {
    }
    
    ULONG m_Status;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            SessionStatus(m_Status);
    }
};

ULONG
ApcDispatch(DebugClient* Client, AnyApcData* ApcData, ULONG EventStatus)
{
    DBG_ASSERT(Client->m_EventCb != NULL);

    HRESULT Vote;

    __try
    {
        Vote = ApcData->Dispatch(Client);
    }
    __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                      NULL, ApcData->m_Name))
    {
        Vote = DEBUG_STATUS_NO_CHANGE;
    }
            
    return MergeVotes(EventStatus, Vote);
}

void APIENTRY
EventApc(ULONG_PTR Param)
{
    AnyApcData* ApcData = (AnyApcData*)Param;
    ULONG Tid = GetCurrentThreadId();
    DebugClient* Client;
    ULONG EventStatus = DEBUG_STATUS_NO_CHANGE;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_ThreadId == Tid &&
            (Client->m_EventInterest & ApcData->m_Mask))
        {
            EventStatus = ApcDispatch(Client, ApcData, EventStatus);
        }
    }

    if (WaitForSingleObject(g_EventStatusWaiting, INFINITE) !=
        WAIT_OBJECT_0)
    {
        ErrOut("Unable to wait for StatusWaiting, %d\n",
               GetLastError());
        EventStatus = WIN32_LAST_STATUS();
    }

    g_EventStatus = EventStatus;
    
    if (!SetEvent(g_EventStatusReady))
    {
        ErrOut("Unable to set StatusReady, %d\n",
               GetLastError());
        g_EventStatus = WIN32_LAST_STATUS();
    }
}

ULONG
SendEvent(AnyApcData* ApcData, ULONG EventStatus)
{
    DebugClient* Client;
    ULONG NumQueued = 0;
    ULONG TidDone = 0;
    static ULONG s_TidSending = 0;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        // Only queue one APC per thread regardless of how
        // many clients.  The APC function will deliver the
        // callback to all clients on that thread.
        if (Client->m_ThreadId != TidDone &&
            (Client->m_EventInterest & ApcData->m_Mask))
        {
            // SessionStatus callbacks are made at unusual
            // times so do not do full call preparation.
            if (TidDone == 0 &&
                ApcData->m_Mask != DEBUG_EVENT_SESSION_STATUS)
            {
                PrepareForCalls(DEBUG_STATUS_INSIDE_WAIT);
            }

            if (Client->m_ThreadId == GetCurrentThreadId())
            {
                // Don't hold the engine lock while the client
                // is called.
                SUSPEND_ENGINE();
                
                EventStatus = ApcDispatch(Client, ApcData, EventStatus);

                RESUME_ENGINE();
            }
            else if (QueueUserAPC(EventApc, Client->m_Thread,
                                  (ULONG_PTR)ApcData))
            {
                TidDone = Client->m_ThreadId;
                NumQueued++;
            }
            else
            {
                ErrOut("Unable to deliver callback, %d\n", GetLastError());
            }
        }
    }

    if (NumQueued == 0)
    {
        // No APCs queued.
        return EventStatus;
    }

    // This function's use of global data is only safe as
    // long as a single send is active at once.  Synchronous
    // sends are almost exclusively sent by the session thread
    // so competition to send is very rare, therefore we
    // don't really attempt to handle it.
    if (s_TidSending != 0)
    {
        return E_FAIL;
    }
    s_TidSending = GetCurrentThreadId();

    // Leave the lock while waiting.
    SUSPEND_ENGINE();
    
    while (NumQueued-- > 0)
    {
        if (!SetEvent(g_EventStatusWaiting))
        {
            // If the event can't be set everything is hosed
            // and threads may be stuck waiting so we
            // just panic.
            ErrOut("Unable to set StatusWaiting, %d\n",
                   GetLastError());
            EventStatus = WIN32_LAST_STATUS();
            break;
        }

        for (;;)
        {
            ULONG Wait;
            
            Wait = WaitForSingleObjectEx(g_EventStatusReady,
                                         INFINITE, TRUE);
            if (Wait == WAIT_OBJECT_0)
            {
                EventStatus = MergeVotes(EventStatus, g_EventStatus);
                break;
            }
            else if (Wait != WAIT_IO_COMPLETION)
            {
                ErrOut("Unable to wait for StatusReady, %d\n",
                       GetLastError());
                EventStatus = WIN32_LAST_STATUS();
                NumQueued = 0;
                break;
            }
        }
    }

    RESUME_ENGINE();
    s_TidSending = 0;
    return EventStatus;
}

//----------------------------------------------------------------------------
//
// Event callbacks.
//
//----------------------------------------------------------------------------

ULONG g_EngNotify;

ULONG
ExecuteEventCommand(ULONG EventStatus, DebugClient* Client, PCSTR Command)
{
    if (Command == NULL)
    {
        return EventStatus;
    }
    
    // Don't output any noise while processing event
    // command strings.
    BOOL OldOutReg = g_OciOutputRegs;
    g_OciOutputRegs = FALSE;

    PrepareForCalls(DEBUG_STATUS_INSIDE_WAIT);
    // Stop execution as soon as the execution status
    // changes.
    g_EngStatus |= ENG_STATUS_NO_AUTO_WAIT;
        
    Execute(Client, Command, DEBUG_EXECUTE_NOT_LOGGED);
        
    g_EngStatus &= ~ENG_STATUS_NO_AUTO_WAIT;
    g_OciOutputRegs = OldOutReg;

    // Translate the continuation status from
    // the state the engine was left in by the command.
    if (IS_RUNNING(g_CmdState))
    {
        // If the command left the engine running override
        // the incoming event status.  This allows a user
        // to create conditional commands that can resume
        // execution even when the basic setting may be to break.
        return g_ExecutionStatusRequest;
    }
    else
    {
        return EventStatus;
    }
}

HRESULT
NotifyBreakpointEvent(ULONG Vote, Breakpoint* Bp)
{
    ULONG EventStatus;

    g_LastEventType = DEBUG_EVENT_BREAKPOINT;
    g_LastEventInfo.Breakpoint.Id = Bp->m_Id;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.Breakpoint);
    sprintf(g_LastEventDesc, "Hit breakpoint %d", Bp->m_Id);
    
    // Execute breakpoint command first if one exists.
    if (Bp->m_Command != NULL)
    {
        EventStatus = ExecuteEventCommand(DEBUG_STATUS_NO_CHANGE,
                                          Bp->m_Adder, Bp->m_Command);
    }
    else
    {
        if ((Bp->m_Flags & (BREAKPOINT_HIDDEN |
                            DEBUG_BREAKPOINT_ADDER_ONLY)) == 0)
        {
            StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
            dprintf("Breakpoint %u hit\n", Bp->m_Id);
        }
        
        EventStatus = DEBUG_STATUS_NO_CHANGE;
    }

    BreakpointEventApcData ApcData;
    ApcData.m_Bp = Bp;
    EventStatus = SendEvent(&ApcData, EventStatus);

    // If there weren't any votes default to breaking in.
    if (EventStatus == DEBUG_STATUS_NO_CHANGE)
    {
        EventStatus = DEBUG_STATUS_BREAK;
    }

    // Fold command status into votes from previous breakpoints.
    return MergeVotes(Vote, EventStatus);
}

void
ProcessVcppException(PEXCEPTION_RECORD64 Record)
{
    EXCEPTION_VISUALCPP_DEBUG_INFO64 Info64;
    EXCEPTION_VISUALCPP_DEBUG_INFO64* Info;

    // If this is a 32-bit system we need to convert
    // back to a 32-bit exception record so that
    // we can properly reconstruct the info from
    // the arguments.
    if (!g_Machine->m_Ptr64)
    {
        EXCEPTION_RECORD32 Record32;
        EXCEPTION_VISUALCPP_DEBUG_INFO32* Info32;
        
        ExceptionRecord64To32(Record, &Record32);
        Info32 = (EXCEPTION_VISUALCPP_DEBUG_INFO32*)
            Record32.ExceptionInformation;
        Info = &Info64;
        Info->dwType = Info32->dwType;
        switch(Info->dwType)
        {
        case VCPP_DEBUG_SET_NAME:
            Info->SetName.szName = EXTEND64(Info32->SetName.szName);
            Info->SetName.dwThreadID = Info32->SetName.dwThreadID;
            Info->SetName.dwFlags = Info32->SetName.dwFlags;
            break;
        }
    }
    else
    {
        Info = (EXCEPTION_VISUALCPP_DEBUG_INFO64*)
            Record->ExceptionInformation;
    }

    PTHREAD_INFO Thread;
    
    switch(Info->dwType)
    {
    case VCPP_DEBUG_SET_NAME:
        if (Info->SetName.dwThreadID == -1)
        {
            Thread = g_EventThread;
        }
        else
        {
            Thread = FindThreadBySystemId(NULL, Info->SetName.dwThreadID);
        }
        if (Thread != NULL)
        {
            DWORD Read;
            
            if (g_Target->ReadVirtual(Info->SetName.szName, Thread->Name,
                                      MAX_THREAD_NAME - 1, &Read) != S_OK)
            {
                Thread->Name[0] = 0;
            }
            else
            {
                Thread->Name[Read] = 0;
            }
        }
        break;
    }
}

HRESULT
NotifyExceptionEvent(PEXCEPTION_RECORD64 Record,
                     ULONG FirstChance, BOOL OutputDone)
{
    ULONG EventStatus;
    EVENT_FILTER* Filter;
    EVENT_COMMAND* Command;
    PDEBUG_EXCEPTION_FILTER_PARAMETERS Params;

    g_LastEventType = DEBUG_EVENT_EXCEPTION;
    g_LastEventInfo.Exception.ExceptionRecord = *Record;
    g_LastEventInfo.Exception.FirstChance = FirstChance;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.Exception);
    sprintf(g_LastEventDesc, "Exception %X, %s chance",
            Record->ExceptionCode, FirstChance ? "first" : "second");

    if (Record->ExceptionCode == STATUS_VCPP_EXCEPTION)
    {
        // Handle special VC++ exceptions as they
        // pass information from the debuggee to the debugger.
        ProcessVcppException(Record);
    }
    
    Filter = GetSpecificExceptionFilter(Record->ExceptionCode);
    if (Filter == NULL)
    {
        // Use the default filter for name and handling.
        Filter = &g_EventFilters[FILTER_DEFAULT_EXCEPTION];
        GetOtherExceptionParameters(Record->ExceptionCode,
                                    &Params, &Command);
    }
    else
    {
        Params = &Filter->Params;
        Command = &Filter->Command;
    }

    g_EngDefer |= ENG_DEFER_EXCEPTION_HANDLING;
    g_EventExceptionFilter = Params;
    g_ExceptionFirstChance = FirstChance;
    
    if (Params->ExecutionOption != DEBUG_FILTER_IGNORE)
    {
        if (!OutputDone)
        {
            StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
            dprintf("%s", Filter->Name);
            if (Filter->OutArgFormat != NULL)
            {
                dprintf(Filter->OutArgFormat,
                        Record->ExceptionInformation[Filter->OutArgIndex]);
            }

            dprintf(" - code %08lx (%s)\n",
                    Record->ExceptionCode,
                    FirstChance ? "first chance" : "!!! second chance !!!");
        }

        if (Params->ExecutionOption == DEBUG_FILTER_BREAK ||
            (Params->ExecutionOption == DEBUG_FILTER_SECOND_CHANCE_BREAK &&
             !FirstChance))
        {
            EventStatus = DEBUG_STATUS_BREAK;
        }
        else
        {
            EventStatus = DEBUG_STATUS_IGNORE_EVENT;
        }
    }
    else
    {
        EventStatus = DEBUG_STATUS_IGNORE_EVENT;
    }

    // If this is the initial breakpoint execute the
    // initial breakpoint command.
    if ((g_EngStatus & ENG_STATUS_AT_INITIAL_BREAK) &&
        IS_EFEXECUTION_BREAK(g_EventFilters[DEBUG_FILTER_INITIAL_BREAKPOINT].
                             Params.ExecutionOption))
    {
        EventStatus = ExecuteEventCommand
            (EventStatus,
             g_EventFilters[DEBUG_FILTER_INITIAL_BREAKPOINT].Command.Client,
             g_EventFilters[DEBUG_FILTER_INITIAL_BREAKPOINT].
             Command.Command[0]);
    }
    
    EventStatus = ExecuteEventCommand(EventStatus,
                                      Command->Client,
                                      Command->Command[FirstChance ? 0 : 1]);
    
    ExceptionEventApcData ApcData;
    ApcData.m_Record = Record;
    ApcData.m_FirstChance = FirstChance;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyCreateThreadEvent(ULONG64 Handle,
                        ULONG64 DataOffset,
                        ULONG64 StartOffset,
                        ULONG Flags)
{
    PPROCESS_INFO Process;
    
    StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
    VerbOut("*** Create thread %x:%x\n",
            g_EventProcessSysId, g_EventThreadSysId);

    if ((Process = FindProcessBySystemId(g_EventProcessSysId)) == NULL)
    {
        ErrOut("Unable to find system process %x\n", g_EventProcessSysId);

        if (g_EngNotify == 0)
        {
            // Put in a placeholder description to make it easy
            // to identify this case.
            g_LastEventType = DEBUG_EVENT_CREATE_THREAD;
            sprintf(g_LastEventDesc, "Create unowned thread %x for %x",
                    g_EventThreadSysId, g_EventProcessSysId);
        }
        
        // Can't really continue the notification.
        return DEBUG_STATUS_BREAK;
    }

    PTHREAD_INFO Thread;
    
    // There's a small window when attaching during process creation where
    // it's possible to get two create thread events for the
    // same thread.  Check and see if this process already has
    // a thread with the given ID and handle.
    // If a process attach times out and the process is examined,
    // there's a possibility that the attach may succeed later,
    // yielding events for processes and threads already created
    // by examination.  In that case just check for an ID match
    // as the handles will be different.

    for (Thread = Process->ThreadHead;
         Thread != NULL;
         Thread = Thread->Next)
    {
        if (((Process->Flags & ENG_PROC_EXAMINED) ||
             Thread->Handle == Handle) &&
            Thread->SystemId == g_EventThreadSysId)
        {
            // We already know about this thread, just
            // ignore the event.
            if ((Process->Flags & ENG_PROC_EXAMINED) == 0)
            {
                WarnOut("WARNING: Duplicate thread create event for %x:%x\n",
                        g_EventProcessSysId, g_EventThreadSysId);
            }
            return DEBUG_STATUS_IGNORE_EVENT;
        }
    }
    
    if (AddThread(Process, g_EventThreadSysId, Handle,
                  DataOffset, StartOffset, Flags) == NULL)
    {
        ErrOut("Unable to allocate thread record for create thread event\n");
        ErrOut("Thread %x:%x will be lost\n",
               g_EventProcessSysId, g_EventThreadSysId);

        if (g_EngNotify == 0)
        {
            // Put in a placeholder description to make it easy
            // to identify this case.
            g_LastEventType = DEBUG_EVENT_CREATE_THREAD;
            sprintf(g_LastEventDesc, "Can't create thread %x for %x",
                    g_EventThreadSysId, g_EventProcessSysId);
        }
        
        // Can't really continue the notification.
        return DEBUG_STATUS_BREAK;
    }
    
    // Look up infos now that they've been added.
    FindEventProcessThread();
    if (g_EventProcess == NULL || g_EventThread == NULL)
    {
        // This should never happen with the above failure
        // checks but handle it just in case.
        ErrOut("Create thread unable to locate process or thread %x:%x\n",
               g_EventProcessSysId, g_EventThreadSysId);
        return DEBUG_STATUS_BREAK;
    }

    VerbOut("Thread created: %lx.%lx\n",
            g_EventProcessSysId, g_EventThreadSysId);

    if (g_EngNotify > 0)
    {
        // This call is just to update internal thread state.
        // Do not make real callbacks.
        return DEBUG_STATUS_NO_CHANGE;
    }

    OutputProcessInfo("*** Create thread ***");

    g_LastEventType = DEBUG_EVENT_CREATE_THREAD;
    sprintf(g_LastEventDesc, "Create thread %d:%x",
            g_EventThread->UserId, g_EventThreadSysId);
    
    // Always update breakpoints to account for the new thread.
    SuspendExecution();
    RemoveBreakpoints();
    g_UpdateDataBreakpoints = TRUE;
    g_DataBreakpointsChanged = TRUE;
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_CREATE_THREAD];

    EventStatus =
        IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) ?
        DEBUG_STATUS_BREAK : DEBUG_STATUS_IGNORE_EVENT;
    
    EventStatus = ExecuteEventCommand(EventStatus,
                                      Filter->Command.Client,
                                      Filter->Command.Command[0]);
    
    CreateThreadEventApcData ApcData;
    ApcData.m_Handle = Handle;
    ApcData.m_DataOffset = DataOffset;
    ApcData.m_StartOffset = StartOffset;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyExitThreadEvent(ULONG ExitCode)
{
    StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
    VerbOut("*** Exit thread\n");

    g_EngDefer |= ENG_DEFER_DELETE_EXITED;
    // There's a small possibility that exit events can
    // be delivered when the engine is not expecting them.
    // When attaching to a process that's exiting it's possible
    // to get an exit but no create.  When restarting it's
    // possible that not all events were successfully drained.
    // Protect this code from faulting in that case.
    if (g_EventThread == NULL)
    {
        WarnOut("WARNING: Unknown thread exit: %lx.%lx\n",
                g_EventProcessSysId, g_EventThreadSysId);
    }
    else
    {
        g_EventThread->Exited = TRUE;
    }
    VerbOut("Thread exited: %lx.%lx, code %X\n",
            g_EventProcessSysId, g_EventThreadSysId, ExitCode);

    g_LastEventType = DEBUG_EVENT_EXIT_THREAD;
    g_LastEventInfo.ExitThread.ExitCode = ExitCode;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.ExitThread);
    if (g_EventThread == NULL)
    {
        sprintf(g_LastEventDesc, "Exit thread ???:%x, code %X",
                g_EventThreadSysId, ExitCode);
    }
    else
    {
        sprintf(g_LastEventDesc, "Exit thread %d:%x, code %X",
                g_EventThread->UserId, g_EventThreadSysId, ExitCode);
    }
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_EXIT_THREAD];

    EventStatus =
        IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) ?
        DEBUG_STATUS_BREAK : DEBUG_STATUS_IGNORE_EVENT;

    // If we were stepping on this thread then force a breakin
    // so it's clear to the user that the thread exited.
    if (g_EventThread != NULL &&
        (g_StepTraceBp->m_Flags & DEBUG_BREAKPOINT_ENABLED) &&
        (g_StepTraceBp->m_MatchThread == g_EventThread ||
         g_DeferBp->m_MatchThread == g_EventThread))
    {
        WarnOut("WARNING: Step/trace thread exited\n");
        g_WatchFunctions.End(NULL);
        EventStatus = DEBUG_STATUS_BREAK;
        // Ensure that p/t isn't repeated.
        g_LastCommand[0] = 0;
    }
    
    EventStatus = ExecuteEventCommand(EventStatus,
                                      Filter->Command.Client,
                                      Filter->Command.Command[0]);
    
    ExitThreadEventApcData ApcData;
    ApcData.m_ExitCode = ExitCode;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyCreateProcessEvent(ULONG64 ImageFileHandle,
                         ULONG64 Handle,
                         ULONG64 BaseOffset,
                         ULONG ModuleSize,
                         PSTR ModuleName,
                         PSTR ImageName,
                         ULONG CheckSum,
                         ULONG TimeDateStamp,
                         ULONG64 InitialThreadHandle,
                         ULONG64 ThreadDataOffset,
                         ULONG64 StartOffset,
                         ULONG Flags,
                         ULONG Options,
                         ULONG InitialThreadFlags)
{
    StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
    VerbOut("*** Create process %x\n", g_EventProcessSysId);

    PPROCESS_INFO Process;
    
    // If a process attach times out and the process is examined,
    // there's a possibility that the attach may succeed later,
    // yielding events for processes and threads already created
    // by examination.  In that case just check for an ID match
    // as the handles will be different.

    for (Process = g_ProcessHead;
         Process != NULL;
         Process = Process->Next)
    {
        if (((Process->Flags & ENG_PROC_EXAMINED) ||
             Process->FullHandle == Handle) &&
            Process->SystemId == g_EventProcessSysId)
        {
            // We already know about this process, just
            // ignore the event.
            if ((Process->Flags & ENG_PROC_EXAMINED) == 0)
            {
                WarnOut("WARNING: Duplicate process create event for %x\n",
                        g_EventProcessSysId);
            }
            return DEBUG_STATUS_IGNORE_EVENT;
        }
    }
    
    if (AddProcess(g_EventProcessSysId, Handle, g_EventThreadSysId,
                   InitialThreadHandle, ThreadDataOffset, StartOffset,
                   Flags, Options, InitialThreadFlags) == NULL)
    {
        ErrOut("Unable to allocate process record for create process event\n");
        ErrOut("Process %x will be lost\n", g_EventProcessSysId);

        if (g_EngNotify == 0)
        {
            // Put in a placeholder description to make it easy
            // to identify this case.
            g_LastEventType = DEBUG_EVENT_CREATE_PROCESS;
            sprintf(g_LastEventDesc, "Can't create process %x",
                    g_EventProcessSysId);
        }
        
        // Can't really continue the notification.
        return DEBUG_STATUS_BREAK;
    }
    
    // Look up infos now that they've been added.
    FindEventProcessThread();
    if (g_EventProcess == NULL || g_EventThread == NULL)
    {
        // This should never happen with the above failure
        // checks but handle it just in case.
        ErrOut("Create process unable to locate process or thread %x:%x\n",
               g_EventProcessSysId, g_EventThreadSysId);
        return DEBUG_STATUS_BREAK;
    }
    
    VerbOut("Process created: %lx.%lx\n",
            g_EventProcessSysId, g_EventThreadSysId);

    if (g_EngNotify > 0)
    {
        // This call is just to update internal process state.
        // Do not make real callbacks.
        g_EngStatus |= ENG_STATUS_PROCESSES_ADDED;
        return DEBUG_STATUS_NO_CHANGE;
    }
    
    OutputProcessInfo("*** Create process ***");

    g_LastEventType = DEBUG_EVENT_CREATE_PROCESS;
    sprintf(g_LastEventDesc, "Create process %d:%x",
            g_EventProcess->UserId, g_EventProcessSysId);
    
    // Simulate a load module event for the process but do
    // not send it to the client.
    g_EngNotify++;
    
    NotifyLoadModuleEvent(ImageFileHandle, BaseOffset, ModuleSize,
                          ModuleName, ImageName, CheckSum, TimeDateStamp);
    
    g_EngNotify--;

    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_CREATE_PROCESS];
    BOOL MatchesEvent;

    MatchesEvent = BreakOnThisImageTail(ImageName, Filter->Argument);

    EventStatus =
        (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
         MatchesEvent) ?
        DEBUG_STATUS_BREAK : DEBUG_STATUS_IGNORE_EVENT;

    if (MatchesEvent)
    {
        EventStatus = ExecuteEventCommand(EventStatus,
                                          Filter->Command.Client,
                                          Filter->Command.Command[0]);
    }
    
    g_EngStatus |= ENG_STATUS_PROCESSES_ADDED;
    
    CreateProcessEventApcData ApcData;
    ApcData.m_ImageFileHandle = ImageFileHandle;
    ApcData.m_Handle = Handle;
    ApcData.m_BaseOffset = BaseOffset;
    ApcData.m_ModuleSize = ModuleSize;
    ApcData.m_ModuleName = ModuleName;
    ApcData.m_ImageName = ImageName;
    ApcData.m_CheckSum = CheckSum;
    ApcData.m_TimeDateStamp = TimeDateStamp;
    ApcData.m_InitialThreadHandle = InitialThreadHandle;
    ApcData.m_ThreadDataOffset = ThreadDataOffset;
    ApcData.m_StartOffset = StartOffset;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyExitProcessEvent(ULONG ExitCode)
{
    StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
    VerbOut("*** Exit process\n");
    
    g_EngDefer |= ENG_DEFER_DELETE_EXITED;
    // There's a small possibility that exit events can
    // be delivered when the engine is not expecting them.
    // When attaching to a process that's exiting it's possible
    // to get an exit but no create.  When restarting it's
    // possible that not all events were successfully drained.
    // Protect this code from faulting in that case.
    if (g_EventProcess == NULL)
    {
        WarnOut("WARNING: Unknown process exit: %lx.%lx\n",
                g_EventProcessSysId, g_EventThreadSysId);
    }
    else
    {
        g_EventProcess->Exited = TRUE;
    }
    VerbOut("Process exited: %lx.%lx, code %X\n",
            g_EventProcessSysId, g_EventThreadSysId, ExitCode);

    g_LastEventType = DEBUG_EVENT_EXIT_PROCESS;
    g_LastEventInfo.ExitProcess.ExitCode = ExitCode;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.ExitProcess);
    if (g_EventProcess == NULL)
    {
        sprintf(g_LastEventDesc, "Exit process ???:%x, code %X",
                g_EventProcessSysId, ExitCode);
    }
    else
    {
        sprintf(g_LastEventDesc, "Exit process %d:%x, code %X",
                g_EventProcess->UserId, g_EventProcessSysId, ExitCode);
    }
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_EXIT_PROCESS];
    BOOL MatchesEvent;

    if (g_EventProcess && g_EventProcess->ExecutableImage)
    {
        MatchesEvent =
            BreakOnThisImageTail(g_EventProcess->ExecutableImage->ImagePath,
                                 Filter->Argument);
    }
    else
    {
        // If this process doesn't have a specific name always break.
        MatchesEvent = TRUE;
    }
    
    EventStatus =
        ((g_EngOptions & DEBUG_ENGOPT_FINAL_BREAK) ||
         (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
          MatchesEvent)) ?
        DEBUG_STATUS_BREAK : DEBUG_STATUS_IGNORE_EVENT;

    if (MatchesEvent)
    {
        EventStatus = ExecuteEventCommand(EventStatus,
                                          Filter->Command.Client,
                                          Filter->Command.Command[0]);
    }
    
    ExitProcessEventApcData ApcData;
    ApcData.m_ExitCode = ExitCode;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyLoadModuleEvent(ULONG64 ImageFileHandle,
                      ULONG64 BaseOffset,
                      ULONG ModuleSize,
                      PSTR  ModuleName,
                      PSTR  ImagePathName,
                      ULONG CheckSum,
                      ULONG TimeDateStamp)
{
    MODULE_INFO_ENTRY ModEntry = {0};

    ModEntry.NamePtr       = ImagePathName;
    ModEntry.File          = (HANDLE)ImageFileHandle;
    ModEntry.Base          = BaseOffset;
    ModEntry.Size          = ModuleSize;
    ModEntry.CheckSum      = CheckSum;
    ModEntry.ModuleName    = ModuleName;
    ModEntry.TimeDateStamp = TimeDateStamp;

    AddImage(&ModEntry, FALSE);

    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_LOAD_MODULE];

    //
    // ntsd has always shown mod loads by default.
    //

    if (IS_USER_TARGET())
    {
        //if (Filter->Params.ExecutionOption == DEBUG_FILTER_OUTPUT)
        {
            StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
            dprintf("ModLoad: %s %s   %-8s\n",
                    FormatAddr64(BaseOffset),
                    FormatAddr64(BaseOffset + ModuleSize),
                    ImagePathName);
        }
    }

    OutputProcessInfo("*** Load dll ***");

    if (g_EngNotify > 0)
    {
        g_EngStatus |= ENG_STATUS_MODULES_LOADED;
        return DEBUG_STATUS_IGNORE_EVENT;
    }
    
    g_LastEventType = DEBUG_EVENT_LOAD_MODULE;
    g_LastEventInfo.LoadModule.Base = BaseOffset;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.LoadModule);
    sprintf(g_LastEventDesc, "Load module %.*s at %s",
            MAX_IMAGE_PATH - 32, ImagePathName,
            FormatAddr64(BaseOffset));
    
    ULONG EventStatus;
    BOOL MatchesEvent;

    if ((g_EngStatus & ENG_STATUS_MODULES_LOADED) == 0)
    {
        g_EngStatus |= ENG_STATUS_AT_INITIAL_MODULE_LOAD;
    }
    
    MatchesEvent = BreakOnThisImageTail(ImagePathName, Filter->Argument);
    
    if ((IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
         MatchesEvent) ||
        ((g_EngOptions & DEBUG_ENGOPT_INITIAL_MODULE_BREAK) &&
         (g_EngStatus & ENG_STATUS_MODULES_LOADED) == 0))
    {
        EventStatus = DEBUG_STATUS_BREAK;
    }
    else
    {
        EventStatus = DEBUG_STATUS_IGNORE_EVENT;
    }

    // If this is the very first module load give breakpoints
    // a chance to get established.  Execute the initial
    // module command if there is one.
    if (g_EngStatus & ENG_STATUS_AT_INITIAL_MODULE_LOAD)
    {
        // On NT4 boot the breakpoint update and context management caused
        // by this seems to hit the system at a fragile time and
        // usually causes a bugcheck 50, so don't do it.  Win2K seems
        // to be able to handle it, so allow it there.
        if (IS_USER_TARGET() || g_ActualSystemVersion != NT_SVER_NT4)
        {
            SuspendExecution();
            RemoveBreakpoints();
            
            if (IS_EFEXECUTION_BREAK(g_EventFilters
                                     [DEBUG_FILTER_INITIAL_MODULE_LOAD].
                                     Params.ExecutionOption))
            {
                EventStatus = ExecuteEventCommand
                    (EventStatus,
                     g_EventFilters[DEBUG_FILTER_INITIAL_MODULE_LOAD].
                     Command.Client,
                     g_EventFilters[DEBUG_FILTER_INITIAL_MODULE_LOAD].
                     Command.Command[0]);
            }
        }
    }

    if (MatchesEvent)
    {
        EventStatus = ExecuteEventCommand(EventStatus,
                                          Filter->Command.Client,
                                          Filter->Command.Command[0]);
    }
    
    g_EngStatus |= ENG_STATUS_MODULES_LOADED;

    LoadModuleEventApcData ApcData;
    ApcData.m_ImageFileHandle = ImageFileHandle;
    ApcData.m_BaseOffset = BaseOffset;
    ApcData.m_ModuleSize = ModuleSize;
    ApcData.m_ModuleName = ModuleName;
    ApcData.m_ImageName = ImagePathName;
    ApcData.m_CheckSum = CheckSum;
    ApcData.m_TimeDateStamp = TimeDateStamp;
    EventStatus = SendEvent(&ApcData, EventStatus);

    if (EventStatus > DEBUG_STATUS_GO_NOT_HANDLED &&
        IS_KERNEL_TARGET() && g_ActualSystemVersion == NT_SVER_NT4)
    {
        WarnOut("WARNING: Any modification to state may cause bugchecks.\n");
        WarnOut("         The debugger will not write "
                "any register changes.\n");
    }
    
    return EventStatus;
}

HRESULT
NotifyUnloadModuleEvent(PCSTR ImageBaseName,
                        ULONG64 BaseOffset)
{
    PDEBUG_IMAGE_INFO Image = NULL;
    
    // First try to look up the image by the base offset
    // as that's the most reliable identifier.
    if (BaseOffset)
    {
        Image = GetImageByOffset(g_EventProcess, BaseOffset);
    }

    // Next try to look up the image by the full name given.
    if (!Image && ImageBaseName)
    {
        Image = GetImageByName(g_EventProcess, ImageBaseName,
                               INAME_IMAGE_PATH);

        // Finally try to look up the image by the tail of the name given.
        if (!Image)
        {
            Image = GetImageByName(g_EventProcess, PathTail(ImageBaseName),
                                   INAME_IMAGE_PATH_TAIL);
        }
    }

    if (Image)
    {
        ImageBaseName = Image->ImagePath;
        BaseOffset = Image->BaseOfImage;
        Image->Unloaded = TRUE;
        g_EngDefer |= ENG_DEFER_DELETE_EXITED;
    }
    
    g_LastEventType = DEBUG_EVENT_UNLOAD_MODULE;
    g_LastEventInfo.UnloadModule.Base = BaseOffset;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.UnloadModule);
    sprintf(g_LastEventDesc, "Unload module %.*s at %s",
            MAX_IMAGE_PATH - 32, ImageBaseName ? ImageBaseName : "<not found>",
            FormatAddr64(BaseOffset));
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_UNLOAD_MODULE];
    BOOL MatchesEvent;

    if (Filter->Params.ExecutionOption == DEBUG_FILTER_OUTPUT)
    {
        StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
        dprintf("%s\n", g_LastEventDesc);
    }

    MatchesEvent = BreakOnThisDllUnload(BaseOffset);
    
    if (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
        MatchesEvent)
    {
        EventStatus = DEBUG_STATUS_BREAK;
    }
    else
    {
        EventStatus = DEBUG_STATUS_IGNORE_EVENT;
    }

    if (MatchesEvent)
    {
        EventStatus = ExecuteEventCommand(EventStatus,
                                          Filter->Command.Client,
                                          Filter->Command.Command[0]);
    }
    
    UnloadModuleEventApcData ApcData;
    ApcData.m_ImageBaseName = ImageBaseName;
    ApcData.m_BaseOffset = BaseOffset;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifySystemErrorEvent(ULONG Error,
                       ULONG Level)
{
    g_LastEventType = DEBUG_EVENT_SYSTEM_ERROR;
    g_LastEventInfo.SystemError.Error = Error;
    g_LastEventInfo.SystemError.Level = Level;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.SystemError);
    sprintf(g_LastEventDesc, "System error %d.%d",
            Error, Level);
    
    if (Level <= g_SystemErrorOutput)
    {
        char ErrorString[_MAX_PATH];
        va_list Args;

        StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
        dprintf("%s - %s: ", Level == SLE_WARNING ?
                "WARNING" : "ERROR", g_EventProcess->ImageHead->ImagePath);

        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL,
                      Error,
                      0,
                      ErrorString,
                      sizeof(ErrorString),
                      &Args);

        dprintf("%s", ErrorString);
    }
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_SYSTEM_ERROR];

    if (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) ||
        Level <= g_SystemErrorBreak)
    {
        EventStatus = DEBUG_STATUS_BREAK;
    }
    else
    {
        EventStatus = DEBUG_STATUS_IGNORE_EVENT;
    }

    EventStatus = ExecuteEventCommand(EventStatus,
                                      Filter->Command.Client,
                                      Filter->Command.Command[0]);
    
    SystemErrorEventApcData ApcData;
    ApcData.m_Error = Error;
    ApcData.m_Level = Level;
    return SendEvent(&ApcData, EventStatus);
}
    
HRESULT
NotifySessionStatus(ULONG Status)
{
    SessionStatusApcData ApcData;
    ApcData.m_Status = Status;
    return SendEvent(&ApcData, DEBUG_STATUS_NO_CHANGE);
}

void
NotifyChangeDebuggeeState(ULONG Flags, ULONG64 Argument)
{
    if (g_EngNotify > 0)
    {
        // Notifications are being suppressed.
        return;
    }
    
    DebugClient* Client;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_EventInterest & DEBUG_EVENT_CHANGE_DEBUGGEE_STATE)
        {
            HRESULT Status;
            
            DBG_ASSERT(Client->m_EventCb != NULL);

            __try
            {
                Status = Client->m_EventCb->
                    ChangeDebuggeeState(Flags, Argument);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugEventCallbacks::"
                                              "ChangeDebuggeeState"))
            {
                Status = E_FAIL;
            }

            if (HRESULT_FACILITY(Status) == FACILITY_RPC)
            {
                Client->Destroy();
            }
        }
    }
}

void
NotifyChangeEngineState(ULONG Flags, ULONG64 Argument, BOOL HaveEngineLock)
{
    if (g_EngNotify > 0)
    {
        // Notifications are being suppressed.
        return;
    }

    DebugClient* Client;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_EventInterest & DEBUG_EVENT_CHANGE_ENGINE_STATE)
        {
            HRESULT Status;
            
            DBG_ASSERT(Client->m_EventCb != NULL);

            __try
            {
                Status = Client->m_EventCb->
                    ChangeEngineState(Flags, Argument);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugEventCallbacks::"
                                              "ChangeEngineState"))
            {
                Status = E_FAIL;
            }

            if (HRESULT_FACILITY(Status) == FACILITY_RPC)
            {
                Client->Destroy();
            }
        }
    }
}

void
NotifyChangeSymbolState(ULONG Flags, ULONG64 Argument, PPROCESS_INFO Process)
{
    if (g_EngNotify > 0)
    {
        // Notifications are being suppressed.
        return;
    }

    if ((Flags & (DEBUG_CSS_LOADS | DEBUG_CSS_UNLOADS)) &&
        Process)
    {
        // Reevaluate any offset expressions to account
        // for the change in symbols.
        EvaluateOffsetExpressions(Process, Flags);
    }
    
    DebugClient* Client;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_EventInterest & DEBUG_EVENT_CHANGE_SYMBOL_STATE)
        {
            HRESULT Status;
            
            DBG_ASSERT(Client->m_EventCb != NULL);

            __try
            {
                Status = Client->m_EventCb->
                    ChangeSymbolState(Flags, Argument);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugEventCallbacks::"
                                              "ChangeSymbolState"))
            {
                Status = E_FAIL;
            }

            if (HRESULT_FACILITY(Status) == FACILITY_RPC)
            {
                Client->Destroy();
            }
        }
    }
}

//----------------------------------------------------------------------------
//
// Input callbacks.
//
//----------------------------------------------------------------------------

ULONG
GetInput(PCSTR Prompt,
         PSTR Buffer,
         ULONG BufferSize)
{
    DebugClient* Client;
    ULONG Len;
    HRESULT Status;

    // Do not suspend the engine lock as this may be called
    // in the middle of an operation.
    
    // Start a new sequence for this input.
    g_InputSequence = 0;
    g_InputSizeRequested = BufferSize;
    
    if (Prompt != NULL && Prompt[0] != 0)
    {
        dprintf("%s", Prompt);
    }

    // Don't hold the engine locked while waiting.
    SUSPEND_ENGINE();
    
    // Begin the input process by notifying all
    // clients with input callbacks that input
    // is needed.
    
    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        // Update the input sequence for all clients so that
        // clients that don't have input callbacks can still
        // return input.  This is necessary in some threading cases.
        Client->m_InputSequence = 1;
        if (Client->m_InputCb != NULL)
        {
            __try
            {
                Status = Client->m_InputCb->StartInput(BufferSize);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugInputCallbacks::"
                                              "StartInput"))
            {
                Status = E_FAIL;
            }

            if (Status != S_OK)
            {
                if (HRESULT_FACILITY(Status) == FACILITY_RPC)
                {
                    Client->Destroy();
                }
                else
                {
                    Len = 0;
                    ErrOut("Client %N refused StartInput, 0x%X\n",
                           Client, Status);
                    goto End;
                }
            }
        }
    }

    // Wait for input to be returned.
    if (WaitForSingleObject(g_InputEvent, INFINITE) != WAIT_OBJECT_0)
    {
        Len = 0;
        Status = WIN32_LAST_STATUS();
        ErrOut("Input event wait failed, 0x%X\n", Status);
    }
    else
    {
        ULONG CopyLen;
        
        Len = strlen(g_InputBuffer) + 1;
        CopyLen = min(Len, BufferSize);
        memcpy(Buffer, g_InputBuffer, CopyLen);
        Buffer[BufferSize - 1] = 0;
    }
    
 End:
    RESUME_ENGINE();
    
    g_InputSizeRequested = 0;
    
    // Notify all clients that input process is done.
    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        Client->m_InputSequence = 0xffffffff;
        if (Client->m_InputCb != NULL)
        {
            __try
            {
                Client->m_InputCb->EndInput();
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugInputCallbacks::"
                                              "EndInput"))
            {
            }
        }
    }

    return Len;
}

//----------------------------------------------------------------------------
//
// Output callbacks.
//
//----------------------------------------------------------------------------

char g_OutBuffer[OUT_BUFFER_SIZE], g_FormatBuffer[OUT_BUFFER_SIZE];

char g_OutFilterPattern[MAX_IMAGE_PATH];
BOOL g_OutFilterResult = TRUE;

ULONG g_AllOutMask;

// Don't split up entries if they'll result in data so
// small that the extra callbacks are worse than the wasted space.
#define MIN_HISTORY_ENTRY_SIZE (256 + sizeof(OutHistoryEntryHeader))

PSTR g_OutHistory;
ULONG g_OutHistoryActualSize;
ULONG g_OutHistoryRequestedSize = 512 * 1024;
ULONG g_OutHistWriteMask;
OutHistoryEntry g_OutHistRead, g_OutHistWrite;
ULONG g_OutHistoryMask;
ULONG g_OutHistoryUsed;

ULONG g_OutputControl = DEBUG_OUTCTL_ALL_CLIENTS;
DebugClient* g_OutputClient;
BOOL g_BufferOutput;

// The kernel silently truncates DbgPrints longer than
// 512 characters so don't buffer any more than that.
#define BUFFERED_OUTPUT_SIZE 512

// Largest delay allowed in TimedFlushCallbacks, in ticks.
#define MAX_FLUSH_DELAY 250

ULONG g_BufferedOutputMask;
char g_BufferedOutput[BUFFERED_OUTPUT_SIZE];
ULONG g_BufferedOutputUsed;
ULONG g_LastFlushTicks;

void
CollectOutMasks(void)
{
    DebugClient* Client;

    g_AllOutMask = 0;
    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_OutputCb != NULL)
        {
            g_AllOutMask |= Client->m_OutMask;
        }
    }
}

BOOL
PushOutCtl(ULONG OutputControl, DebugClient* Client,
           OutCtlSave* Save)
{
    BOOL Status;

    FlushCallbacks();
    
    Save->OutputControl = g_OutputControl;
    Save->Client = g_OutputClient;
    Save->BufferOutput = g_BufferOutput;
    Save->OutputWidth = g_OutputWidth;
    Save->OutputLinePrefix = g_OutputLinePrefix;

    if (OutputControl == DEBUG_OUTCTL_AMBIENT)
    {
        // Leave settings unchanged.
        Status = TRUE;
    }
    else
    {
        ULONG SendMask = OutputControl & DEBUG_OUTCTL_SEND_MASK;
    
        if (
#if DEBUG_OUTCTL_THIS_CLIENT > 0
            SendMask < DEBUG_OUTCTL_THIS_CLIENT ||
#endif
            SendMask > DEBUG_OUTCTL_LOG_ONLY ||
            (OutputControl & ~(DEBUG_OUTCTL_SEND_MASK |
                               DEBUG_OUTCTL_NOT_LOGGED |
                               DEBUG_OUTCTL_OVERRIDE_MASK)))
        {
            Status = FALSE;
        }
        else
        {
            g_OutputControl = OutputControl;
            g_OutputClient = Client;
            g_BufferOutput = TRUE;
            if (Client != NULL)
            {
                g_OutputWidth = Client->m_OutputWidth;
                g_OutputLinePrefix = Client->m_OutputLinePrefix;
            }
            Status = TRUE;
        }
    }

    return Status;
}

void
PopOutCtl(OutCtlSave* Save)
{
    FlushCallbacks();
    g_OutputControl = Save->OutputControl;
    g_OutputClient = Save->Client;
    g_BufferOutput = Save->BufferOutput;
    g_OutputWidth = Save->OutputWidth;
    g_OutputLinePrefix = Save->OutputLinePrefix;
}

void
SendOutput(ULONG Mask, PCSTR Text)
{
    ULONG OutTo = g_OutputControl & DEBUG_OUTCTL_SEND_MASK;
    HRESULT Status;
    
    if (OutTo == DEBUG_OUTCTL_THIS_CLIENT)
    {
        if (g_OutputClient->m_OutputCb != NULL &&
            ((g_OutputControl & DEBUG_OUTCTL_OVERRIDE_MASK) ||
             (Mask & g_OutputClient->m_OutMask)))
        {
            __try
            {
                Status = g_OutputClient->m_OutputCb->Output(Mask, Text);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugOutputCallbacks::"
                                              "Output"))
            {
                Status = E_FAIL;
            }

            if (HRESULT_FACILITY(Status) == FACILITY_RPC)
            {
                g_OutputClient->Destroy();
            }
        }
    }
    else
    {
        DebugClient* Client;

        for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
        {
            if ((OutTo == DEBUG_OUTCTL_ALL_CLIENTS ||
                 Client != g_OutputClient) &&
                Client->m_OutputCb != NULL &&
                ((g_OutputControl & DEBUG_OUTCTL_OVERRIDE_MASK) ||
                 (Client->m_OutMask & Mask)))
            {
                __try
                {
                    Status = Client->m_OutputCb->Output(Mask, Text);
                }
                __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                                  NULL,
                                                  "IDebugOutputCallbacks::"
                                                  "Output"))
                {
                    Status = E_FAIL;
                }

                if (HRESULT_FACILITY(Status) == FACILITY_RPC)
                {
                    Client->Destroy();
                }
            }
        }
    }
}

void
BufferOutput(ULONG Mask, PCSTR Text, ULONG Len)
{
    EnterCriticalSection(&g_QuickLock);

    if (Mask != g_BufferedOutputMask ||
        g_BufferedOutputUsed + Len >= BUFFERED_OUTPUT_SIZE)
    {
        FlushCallbacks();

        if (Len >= BUFFERED_OUTPUT_SIZE)
        {
            SendOutput(Mask, Text);
            LeaveCriticalSection(&g_QuickLock);
            return;
        }

        g_BufferedOutputMask = Mask;
    }

    memcpy(g_BufferedOutput + g_BufferedOutputUsed, Text, Len + 1);
    g_BufferedOutputUsed += Len;
    
    LeaveCriticalSection(&g_QuickLock);
}

void
FlushCallbacks(void)
{
    EnterCriticalSection(&g_QuickLock);
    
    if (g_BufferedOutputUsed > 0)
    {
        SendOutput(g_BufferedOutputMask, g_BufferedOutput);
        g_BufferedOutputMask = 0;
        g_BufferedOutputUsed = 0;
        g_LastFlushTicks = GetTickCount();
    }

    LeaveCriticalSection(&g_QuickLock);
}

void
TimedFlushCallbacks(void)
{
    EnterCriticalSection(&g_QuickLock);

    if (g_BufferedOutputUsed > 0)
    {
        ULONG Ticks = GetTickCount();
    
        // Flush if the last flush was a "long" time ago.
        if (g_LastFlushTicks == 0 ||
            g_LastFlushTicks > Ticks ||
            (Ticks - g_LastFlushTicks) > MAX_FLUSH_DELAY)
        {
            FlushCallbacks();
        }
    }

    LeaveCriticalSection(&g_QuickLock);
}

#if 0
#define DBGHIST(Args) g_NtDllCalls.DbgPrint Args
#else
#define DBGHIST(Args)
#endif

void
WriteHistoryEntry(ULONG Mask, PCSTR Text, ULONG Len)
{
    PSTR Buf;

    DBG_ASSERT((PSTR)g_OutHistWrite + sizeof(OutHistoryEntryHeader) +
               Len + 1 <= g_OutHistory + g_OutHistoryActualSize);
    
    if (Mask != g_OutHistWriteMask)
    {
        // Start new entry.
        g_OutHistWrite->Mask = Mask;
        g_OutHistWriteMask = Mask;
        Buf = (PSTR)(g_OutHistWrite + 1);
        g_OutHistoryUsed += sizeof(OutHistoryEntryHeader);
        
        DBGHIST(("  Write new "));
    }
    else
    {
        // Merge with previous entry.
        Buf = (PSTR)g_OutHistWrite - 1;
        g_OutHistoryUsed--;

        DBGHIST(("  Merge old "));
    }

    DBGHIST(("entry %p:%X, %d\n", g_OutHistWrite, Mask, Len));
    
    // Len does not include the terminator here so
    // always append a terminator.
    memcpy(Buf, Text, Len);
    Buf += Len;
    *Buf++ = 0;

    g_OutHistWrite = (OutHistoryEntry)Buf;
    g_OutHistoryUsed += Len + 1;
    DBG_ASSERT(g_OutHistoryUsed <= g_OutHistoryActualSize);
}

void
AddToOutputHistory(ULONG Mask, PCSTR Text, ULONG Len)
{
    if (Len == 0 || g_OutHistoryRequestedSize == 0)
    {
        return;
    }

    if (g_OutHistory == NULL)
    {
        // Output history buffer hasn't been allocated yet,
        // so go ahead and do it now.
        g_OutHistory = (PSTR)malloc(g_OutHistoryRequestedSize);
        if (g_OutHistory == NULL)
        {
            return;
        }
        
        // Reserve space for a trailing header as terminator.
        g_OutHistoryActualSize = g_OutHistoryRequestedSize -
            sizeof(OutHistoryEntryHeader);
    }
 
    ULONG TotalLen = Len + sizeof(OutHistoryEntryHeader) + 1;

    DBGHIST(("Add %X, %d\n", Mask, Len));
    
    if (TotalLen > g_OutHistoryActualSize)
    {
        Text += TotalLen - g_OutHistoryActualSize;
        TotalLen = g_OutHistoryActualSize;
        Len = TotalLen - sizeof(OutHistoryEntryHeader) - 1;
    }
    
    if (g_OutHistWrite == NULL)
    {
        g_OutHistRead = (OutHistoryEntry)g_OutHistory;
        g_OutHistWrite = (OutHistoryEntry)g_OutHistory;
        g_OutHistWriteMask = 0;
    }

    while (Len > 0)
    {
        ULONG Left;

        if (g_OutHistoryUsed == 0 || g_OutHistWrite > g_OutHistRead)
        {
            Left = g_OutHistoryActualSize -
                (ULONG)((PSTR)g_OutHistWrite - g_OutHistory);

            if (TotalLen > Left)
            {
                // See if it's worth splitting this request to
                // fill the space at the end of the buffer.
                if (Left >= MIN_HISTORY_ENTRY_SIZE &&
                    (TotalLen - Left) >= MIN_HISTORY_ENTRY_SIZE)
                {
                    ULONG Used = Left - sizeof(OutHistoryEntryHeader) - 1;
                
                    // Pack as much data as possible into the
                    // end of the buffer.
                    WriteHistoryEntry(Mask, Text, Used);
                    Text += Used;
                    Len -= Used;
                    TotalLen -= Used;
                }

                // Terminate the buffer and wrap around.  A header's
                // worth of space is reserved at the buffer end so
                // there should always be enough space for this.
                DBG_ASSERT((ULONG)((PSTR)g_OutHistWrite - g_OutHistory) <=
                           g_OutHistoryActualSize);
                g_OutHistWrite->Mask = 0;
                g_OutHistWriteMask = 0;
                g_OutHistWrite = (OutHistoryEntry)g_OutHistory;
                Left = (ULONG)((PUCHAR)g_OutHistRead - (PUCHAR)g_OutHistWrite);
            }
        }
        else
        {
            Left = (ULONG)((PUCHAR)g_OutHistRead - (PUCHAR)g_OutHistWrite);
        }

        if (TotalLen > Left)
        {
            ULONG Need = TotalLen - Left;
        
            // Advance the read pointer to make room.
            while (Need > 0)
            {
                PSTR EntText = (PSTR)(g_OutHistRead + 1);
                ULONG EntTextLen = strlen(EntText);
                ULONG EntTotal =
                    sizeof(OutHistoryEntryHeader) + EntTextLen + 1;

                if (EntTotal <= Need ||
                    EntTotal - Need < MIN_HISTORY_ENTRY_SIZE)
                {
                    DBGHIST(("  Remove %p:%X, %d\n", g_OutHistRead,
                             g_OutHistRead->Mask, EntTextLen));
                    
                    // Remove the whole entry.
                    g_OutHistRead = (OutHistoryEntry)
                        ((PUCHAR)g_OutHistRead + EntTotal);
                    DBG_ASSERT((ULONG)((PSTR)g_OutHistRead - g_OutHistory) <=
                               g_OutHistoryActualSize);
                    if (g_OutHistRead->Mask == 0)
                    {
                        g_OutHistRead = (OutHistoryEntry)g_OutHistory;
                    }
                    
                    Need -= EntTotal <= Need ? EntTotal : Need;
                    DBG_ASSERT(g_OutHistoryUsed >= EntTotal);
                    g_OutHistoryUsed -= EntTotal;
                }
                else
                {
                    OutHistoryEntryHeader EntHdr = *g_OutHistRead;

                    DBGHIST(("  Trim %p:%X, %d\n", g_OutHistRead,
                             g_OutHistRead->Mask, EntTextLen));
                    
                    // Remove part of the head of the entry.
                    g_OutHistRead = (OutHistoryEntry)
                        ((PUCHAR)g_OutHistRead + Need);
                    DBG_ASSERT((ULONG)
                               ((PSTR)g_OutHistRead + (EntTotal - Need) -
                                g_OutHistory) <= g_OutHistoryActualSize);
                    *g_OutHistRead = EntHdr;
                    DBG_ASSERT(g_OutHistoryUsed >= Need);
                    g_OutHistoryUsed -= Need;
                    Need = 0;
                }

                DBGHIST(("  Advance read to %p:%X\n",
                         g_OutHistRead, g_OutHistRead->Mask));
            }
        }
        else
        {
            WriteHistoryEntry(Mask, Text, Len);
            break;
        }
    }
    
    DBGHIST(("History read %p, write %p, used %d\n",
             g_OutHistRead, g_OutHistWrite, g_OutHistoryUsed));
}

void
SendOutputHistory(DebugClient* Client, ULONG HistoryLimit)
{
    if (g_OutHistRead == NULL ||
        Client->m_OutputCb == NULL ||
        (Client->m_OutMask & g_OutHistoryMask) == 0 ||
        HistoryLimit == 0)
    {
        return;
    }

    FlushCallbacks();
    
    OutHistoryEntry Ent;
    ULONG Total;
    ULONG Len;

    Ent = g_OutHistRead;
    Total = 0;
    while (Ent != g_OutHistWrite)
    {
        if (Ent->Mask == 0)
        {
            Ent = (OutHistoryEntry)g_OutHistory;
        }

        PSTR Text = (PSTR)(Ent + 1);
        Len = strlen(Text);
        Total += Len;

        Ent = (OutHistoryEntry)(Text + Len + 1);
    }

    DBGHIST(("Total history %X\n", Total));
    
    Ent = g_OutHistRead;
    while (Ent != g_OutHistWrite)
    {
        if (Ent->Mask == 0)
        {
            Ent = (OutHistoryEntry)g_OutHistory;
        }

        PSTR Text = (PSTR)(Ent + 1);
        Len = strlen(Text);

        if (Total - Len <= HistoryLimit)
        {
            PSTR Part = Text;
            if (Total > HistoryLimit)
            {
                Part += Total - HistoryLimit;
            }
            
            DBGHIST(("Send %p:%X, %d\n",
                     Ent, Ent->Mask, strlen(Part)));

            Client->m_OutputCb->Output(Ent->Mask, Part);
        }

        Total -= Len;
        Ent = (OutHistoryEntry)(Text + Len + 1);
    }
}

void
StartOutLine(ULONG Mask, ULONG Flags)
{
    if ((Flags & OUT_LINE_NO_TIMESTAMP) == 0 &&
        g_EchoEventTimestamps)
    {
        MaskOut(Mask, "%s: ", TimeToStr((ULONG)time(NULL)));
    }
    
    if ((Flags & OUT_LINE_NO_PREFIX) == 0 &&
        g_OutputLinePrefix)
    {
        MaskOut(Mask, "%s", g_OutputLinePrefix);
    }
}

//
// Translates various printf formats to account for the target platform.
//
// This looks for %p type format and truncates the top 4 bytes of the ULONG64
// address argument if the debugee is a 32 bit machine.
// The %p is replaced by %I64x in format string.
//
BOOL
TranslateFormat(
    LPSTR formatOut,
    LPCSTR format,
    va_list args,
    ULONG formatOutSize
    )
{
#define Duplicate(j,i) (formatOut[j++] = format[i++])
    ULONG minSize = strlen(format), i = 0, j = 0;
    CHAR c;
    BOOL TypeFormat = FALSE;
    BOOL FormatChanged = FALSE;

    do
    {
        c = format[i];

        if (c=='%')
        {
            TypeFormat = !TypeFormat;
        }
        if (TypeFormat)
        {
            switch (c)
            {
            case 'c': case 'C': case 'i': case 'd':
            case 'o': case 'u': case 'x': case 'X':
                Duplicate(j,i);
                va_arg(args, int);
                TypeFormat = FALSE;
                break;
            case 'e': case 'E': case 'f': case 'g':
            case 'G':
                Duplicate(j,i);
                va_arg(args, double);
                TypeFormat = FALSE;
                break;
            case 'n':
                Duplicate(j,i);
                va_arg(args, int*);
                TypeFormat = FALSE;
                break;
            case 'N':
                // Native pointer, turns into %p.
                formatOut[j++] = 'p';
                FormatChanged = TRUE;
                i++;
                va_arg(args, void*);
                TypeFormat = FALSE;
                break;
            case 's': case 'S':
                Duplicate(j,i);
                va_arg(args, char*);
                TypeFormat = FALSE;
                break;

            case 'I':
                if ((format[i+1] == '6') && (format[i+2] == '4'))
                {
                    Duplicate(j,i);
                    Duplicate(j,i);
                    va_arg(args, ULONG64);
                    TypeFormat = FALSE;
                }
                // dprintf("I64 a0 %lx, off %lx\n", args.a0, args.offset);
                Duplicate(j,i);
                break;
            
            case 'z': case 'Z':
                // unicode string
                Duplicate(j,i);
                va_arg(args, void*);
                TypeFormat = FALSE;
                break;

            case 'p':
            case 'P':
                minSize +=3;
                if (format[i-1] == '%')
                {
                    minSize++;
                    if (g_Machine->m_Ptr64)
                    {
                        minSize += 2;
                        if (minSize > formatOutSize)
                        {
                            return FALSE;
                        }
                        formatOut[j++] = '0';
                        formatOut[j++] = '1';
                        formatOut[j++] = '6';
                    }
                    else
                    {
                        if (minSize > formatOutSize)
                        {
                            return FALSE;
                        }
                        formatOut[j++] = '0';
                        formatOut[j++] = '8';
                    }
                }

                if (minSize > formatOutSize)
                {
                    return FALSE;
                }
                formatOut[j++] = 'I';
                formatOut[j++] = '6';
                formatOut[j++] = '4';
                formatOut[j++] = (c == 'p') ? 'x' : 'X'; ++i;
                FormatChanged = TRUE;

                if (!g_Machine->m_Ptr64)
                {
                    PULONG64 Arg;

#ifdef  _M_ALPHA
                    Arg = (PULONG64) ((args.a0)+args.offset);
                    //dprintf("a0 %lx, off %lx\n", args.a0, args.offset);
#else
                    Arg = (PULONG64) (args);
#endif

                    //
                    // Truncate signextended addresses
                    //
                    *Arg = (ULONG64) (ULONG) *Arg;
                }

                va_arg(args, ULONG64);
                TypeFormat = FALSE;
                break;

            default:
                Duplicate(j,i);
            } /* switch */
        }
        else
        {
            Duplicate(j,i);
        }
    }
    while (format[i] != '\0');

    formatOut[j] = '\0';
    return FormatChanged;
#undef Duplicate
}

void
MaskOutVa(ULONG Mask, PCSTR Format, va_list Args, BOOL Translate)
{
    int Len;
    ULONG OutTo = g_OutputControl & DEBUG_OUTCTL_SEND_MASK;
    HRESULT Status;

    // Reject output as quickly as possible to avoid
    // doing the format translation and sprintf.
    if (OutTo == DEBUG_OUTCTL_IGNORE ||
        (((g_OutputControl & DEBUG_OUTCTL_NOT_LOGGED) ||
          (Mask & g_OutHistoryMask) == 0) &&
         ((g_OutputControl & DEBUG_OUTCTL_NOT_LOGGED) ||
          (Mask & g_LogMask) == 0 ||
          g_LogFile == -1) &&
         (OutTo == DEBUG_OUTCTL_LOG_ONLY ||
          ((g_OutputControl & DEBUG_OUTCTL_OVERRIDE_MASK) == 0 &&
           (OutTo == DEBUG_OUTCTL_THIS_CLIENT &&
            ((Mask & g_OutputClient->m_OutMask) == 0 ||
             g_OutputClient->m_OutputCb == NULL)) ||
           (Mask & g_AllOutMask) == 0))))
    {
        return;
    }

    // Do not suspend the engine lock as this may be called
    // in the middle of an operation.

    EnterCriticalSection(&g_QuickLock);
    
    __try
    {
        if (Translate &&
            TranslateFormat(g_FormatBuffer, Format, Args, OUT_BUFFER_SIZE - 1))
        {
            Len = _vsnprintf(g_OutBuffer, OUT_BUFFER_SIZE - 1,
                             g_FormatBuffer, Args);
        }
        else
        {
            Len = _vsnprintf(g_OutBuffer, OUT_BUFFER_SIZE - 1, Format, Args);
        }

        // Check and see if this output is filtered away.
        if ((Mask & DEBUG_OUTPUT_DEBUGGEE) &&
            g_OutFilterPattern[0] &&
            !(MatchPattern(g_OutBuffer, g_OutFilterPattern) ==
              g_OutFilterResult))
        {
            __leave;
        }
        
        // If the caller doesn't think this output should
        // be logged it probably also shouldn't go in the
        // history.
        if ((g_OutputControl & DEBUG_OUTCTL_NOT_LOGGED) == 0 &&
            (Mask & g_OutHistoryMask))
        {
            AddToOutputHistory(Mask, g_OutBuffer, Len);
        }
        
        if ((g_OutputControl & DEBUG_OUTCTL_NOT_LOGGED) == 0 &&
            (Mask & g_LogMask) &&
            g_LogFile != -1)
        {
            _write(g_LogFile, g_OutBuffer, Len);
        }

        if (OutTo == DEBUG_OUTCTL_LOG_ONLY)
        {
            __leave;
        }

        if (g_BufferOutput)
        {
            BufferOutput(Mask, g_OutBuffer, Len);
        }
        else
        {
            SendOutput(Mask, g_OutBuffer);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        OutputDebugStringA("Exception in MaskOutVa\n");
    }

    LeaveCriticalSection(&g_QuickLock);
}

void __cdecl
MaskOut(ULONG Mask, PCSTR Format, ...)
{
    va_list Args;
    va_start(Args, Format);
    MaskOutVa(Mask, Format, Args, TRUE);
    va_end(Args);
}

void __cdecl
dprintf(PCSTR Format, ...)
{
    va_list Args;
    va_start(Args, Format);
    MaskOutVa(DEBUG_OUTPUT_NORMAL, Format, Args, FALSE);
    va_end(Args);
}

#define OUT_FN(Name, Mask)                      \
void __cdecl                                    \
Name(PCSTR Format, ...)                         \
{                                               \
    va_list Args;                               \
    va_start(Args, Format);                     \
    MaskOutVa(Mask, Format, Args, TRUE);        \
    va_end(Args);                               \
}

OUT_FN(dprintf64, DEBUG_OUTPUT_NORMAL)
OUT_FN(ErrOut,    DEBUG_OUTPUT_ERROR)
OUT_FN(WarnOut,   DEBUG_OUTPUT_WARNING)
OUT_FN(VerbOut,   DEBUG_OUTPUT_VERBOSE)
OUT_FN(BpOut,     DEBUG_IOUTPUT_BREAKPOINT)
OUT_FN(EventOut,  DEBUG_IOUTPUT_EVENT)
OUT_FN(KdOut,     DEBUG_IOUTPUT_KD_PROTOCOL)