//---------------------------------------------------------------------------- // // 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 : "", 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)