You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2688 lines
80 KiB
2688 lines
80 KiB
//----------------------------------------------------------------------------
|
|
//
|
|
// Callback notification routines.
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 2000-2002.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "ntsdp.hpp"
|
|
|
|
// Event APCs end up calling out to external code so it's difficult
|
|
// to pick a reasonable timeout. However, we need to ensure that
|
|
// a server won't hang indefinitely if something goes wrong with
|
|
// event notifications.
|
|
#define EVENT_APC_TIMEOUT 300000
|
|
|
|
PSTR
|
|
EventIdStr(void)
|
|
{
|
|
static char s_Buf[128];
|
|
|
|
if (IS_USER_TARGET(g_EventTarget))
|
|
{
|
|
PrintString(s_Buf, DIMA(s_Buf), "(%x.%x): ",
|
|
g_EventProcessSysId, g_EventThreadSysId);
|
|
}
|
|
else
|
|
{
|
|
s_Buf[0] = 0;
|
|
}
|
|
return s_Buf;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// 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, EVENT_APC_TIMEOUT) !=
|
|
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,
|
|
EVENT_APC_TIMEOUT, 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;
|
|
|
|
// Processing of commands can delete breakpoints.
|
|
// For example, 'g' will delete any go breakpoints.
|
|
// Preserve this breakpoint as long as we need it.
|
|
Bp->Preserve();
|
|
|
|
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);
|
|
|
|
Bp->Relinquish();
|
|
|
|
// 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;
|
|
|
|
SuspendExecution();
|
|
if (!g_EventMachine ||
|
|
!g_EventProcess)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 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_EventMachine->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;
|
|
}
|
|
|
|
ThreadInfo* Thread;
|
|
|
|
switch(Info->dwType)
|
|
{
|
|
case VCPP_DEBUG_SET_NAME:
|
|
if (Info->SetName.dwThreadID == -1)
|
|
{
|
|
Thread = g_EventThread;
|
|
}
|
|
else
|
|
{
|
|
Thread = g_EventProcess->
|
|
FindThreadBySystemId(Info->SetName.dwThreadID);
|
|
}
|
|
if (Thread != NULL)
|
|
{
|
|
DWORD Read;
|
|
|
|
if (g_EventTarget->ReadVirtual(g_EventProcess,
|
|
Info->SetName.szName,
|
|
Thread->m_Name,
|
|
MAX_THREAD_NAME - 1,
|
|
&Read) != S_OK)
|
|
{
|
|
Thread->m_Name[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
Thread->m_Name[Read] = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
ULONG
|
|
ProcessCommandException(ULONG EventStatus, PEXCEPTION_RECORD64 Record)
|
|
{
|
|
if (Record->NumberParameters != 4 ||
|
|
(ULONG)Record->ExceptionInformation[0] != DEBUG_COMMAND_EXCEPTION_ID ||
|
|
Record->ExceptionInformation[1] < DEBUG_CMDEX_ADD_EVENT_STRING ||
|
|
Record->ExceptionInformation[1] > DEBUG_CMDEX_RESET_EVENT_STRINGS)
|
|
{
|
|
// Malformed exception.
|
|
return EventStatus;
|
|
}
|
|
|
|
switch(Record->ExceptionInformation[1])
|
|
{
|
|
case DEBUG_CMDEX_ADD_EVENT_STRING:
|
|
if (Record->ExceptionInformation[2] > 0 &&
|
|
Record->ExceptionInformation[2] < 0x100000)
|
|
{
|
|
EventString* Str = g_EventThread->
|
|
AllocEventString((ULONG)Record->ExceptionInformation[2]);
|
|
if (Str == NULL)
|
|
{
|
|
ErrOut("Unable to allocate event string\n");
|
|
return DEBUG_STATUS_BREAK;
|
|
}
|
|
|
|
if (g_Target->
|
|
ReadAllVirtual(g_EventProcess,
|
|
Record->ExceptionInformation[3],
|
|
Str->String,
|
|
(ULONG)Record->ExceptionInformation[2]) != S_OK)
|
|
{
|
|
ErrOut("Unable to read event string\n");
|
|
return DEBUG_STATUS_BREAK;
|
|
}
|
|
|
|
Str->String[(ULONG)Record->ExceptionInformation[2]] = 0;
|
|
g_EventThread->AppendEventString(Str);
|
|
}
|
|
break;
|
|
|
|
case DEBUG_CMDEX_RESET_EVENT_STRINGS:
|
|
if (Record->ExceptionInformation[2] == 0 &&
|
|
Record->ExceptionInformation[3] == 0)
|
|
{
|
|
g_EventThread->ClearEventStrings();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
void
|
|
ProcessAssertException(PEXCEPTION_RECORD64 Record)
|
|
{
|
|
char FileName[MAX_PATH];
|
|
ULONG Line;
|
|
char Text[2 * MAX_PATH];
|
|
ImageInfo* Image;
|
|
|
|
Image = g_EventProcess->FindImageByOffset(Record->ExceptionAddress, FALSE);
|
|
if (!Image ||
|
|
FAILED(Image->FindSysAssert(Record->ExceptionAddress,
|
|
FileName, DIMA(FileName),
|
|
&Line,
|
|
Text, DIMA(Text))))
|
|
{
|
|
return;
|
|
}
|
|
|
|
dprintf("Assertion %s(%d): %s\n", FileName, Line, Text);
|
|
}
|
|
|
|
HRESULT
|
|
NotifyExceptionEvent(PEXCEPTION_RECORD64 Record,
|
|
ULONG FirstChance, BOOL OutputDone)
|
|
{
|
|
ULONG EventStatus;
|
|
EVENT_FILTER* Filter;
|
|
EVENT_COMMAND* Command;
|
|
PDEBUG_EXCEPTION_FILTER_PARAMETERS Params;
|
|
BOOL FirstChanceMessage = FALSE;
|
|
|
|
if (g_EventThread == NULL)
|
|
{
|
|
ErrOut("ERROR: Exception %X occurred on unknown thread %X\n",
|
|
Record->ExceptionCode, g_EventThreadSysId);
|
|
}
|
|
|
|
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);
|
|
|
|
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, TRUE,
|
|
&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%s", EventIdStr(), 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 !!!");
|
|
|
|
FirstChanceMessage = FirstChance != 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
strcpy(g_LastEventDesc, Filter->Name);
|
|
if (Filter->OutArgFormat != NULL)
|
|
{
|
|
sprintf(g_LastEventDesc + strlen(g_LastEventDesc),
|
|
Filter->OutArgFormat,
|
|
Record->ExceptionInformation[Filter->OutArgIndex]);
|
|
}
|
|
sprintf(g_LastEventDesc + strlen(g_LastEventDesc),
|
|
" - code %08lx (%s)",
|
|
Record->ExceptionCode,
|
|
FirstChance ? "first chance" : "!!! second chance !!!");
|
|
|
|
if (g_EventThread && Record->ExceptionCode == DBG_COMMAND_EXCEPTION)
|
|
{
|
|
EventStatus = ProcessCommandException(EventStatus, Record);
|
|
}
|
|
|
|
// 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;
|
|
EventStatus = SendEvent(&ApcData, EventStatus);
|
|
|
|
if (FirstChanceMessage && EventStatus == DEBUG_STATUS_BREAK)
|
|
{
|
|
// Show a verbose message for first-chance exceptions
|
|
// to try and explain to users that they may not
|
|
// necessarily be a problem. Don't show the message
|
|
// for hard-coded break instructions as those are
|
|
// common and very rarely handled.
|
|
if (Record->ExceptionCode != STATUS_BREAKPOINT &&
|
|
Record->ExceptionCode != STATUS_WAKE_SYSTEM_DEBUGGER)
|
|
{
|
|
dprintf("First chance exceptions are reported "
|
|
"before any exception handling.\n");
|
|
dprintf("This exception may be expected and handled.\n");
|
|
}
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
HRESULT
|
|
NotifyCreateThreadEvent(ULONG64 Handle,
|
|
ULONG64 DataOffset,
|
|
ULONG64 StartOffset,
|
|
ULONG Flags)
|
|
{
|
|
ProcessInfo* Process;
|
|
|
|
StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
|
|
VerbOut("*** Create thread %x:%x\n",
|
|
g_EventProcessSysId, g_EventThreadSysId);
|
|
|
|
if ((Process = g_EventTarget->
|
|
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;
|
|
}
|
|
|
|
ThreadInfo* 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.
|
|
|
|
ForProcessThreads(Process)
|
|
{
|
|
if (((Process->m_Flags & ENG_PROC_EXAMINED) ||
|
|
Thread->m_Handle == Handle) &&
|
|
Thread->m_SystemId == g_EventThreadSysId)
|
|
{
|
|
// We already know about this thread, just
|
|
// ignore the event.
|
|
if ((Process->m_Flags & ENG_PROC_EXAMINED) == 0)
|
|
{
|
|
WarnOut("WARNING: Duplicate thread create event for %x:%x\n",
|
|
g_EventProcessSysId, g_EventThreadSysId);
|
|
}
|
|
return DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
}
|
|
|
|
Thread = new ThreadInfo(Process, g_EventThreadSysId,
|
|
DataOffset, Handle, Flags, StartOffset);
|
|
if (Thread == 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;
|
|
}
|
|
|
|
g_EventTarget->OutputProcessesAndThreads("*** Create thread ***");
|
|
|
|
g_LastEventType = DEBUG_EVENT_CREATE_THREAD;
|
|
sprintf(g_LastEventDesc, "Create thread %d:%x",
|
|
g_EventThread->m_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->m_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->m_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,
|
|
HANDLE SymHandle,
|
|
ULONG64 SysHandle,
|
|
ULONG64 BaseOffset,
|
|
ULONG ModuleSize,
|
|
PSTR ModuleName,
|
|
PSTR ImageName,
|
|
ULONG CheckSum,
|
|
ULONG TimeDateStamp,
|
|
ULONG64 InitialThreadHandle,
|
|
ULONG64 ThreadDataOffset,
|
|
ULONG64 StartOffset,
|
|
ULONG Flags,
|
|
ULONG Options,
|
|
ULONG InitialThreadFlags,
|
|
BOOL QueryImageInfo,
|
|
ULONG64 ImageNameOffset,
|
|
BOOL ImageNameUnicode)
|
|
{
|
|
CHAR NameBuffer[MAX_IMAGE_PATH];
|
|
char ModuleNameBuffer[MAX_MODULE];
|
|
|
|
StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
|
|
VerbOut("*** Create process %x\n", g_EventProcessSysId);
|
|
|
|
ProcessInfo* 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.
|
|
|
|
ForTargetProcesses(g_EventTarget)
|
|
{
|
|
if (((Process->m_Flags & ENG_PROC_EXAMINED) ||
|
|
Process->m_SysHandle == SysHandle) &&
|
|
Process->m_SystemId == g_EventProcessSysId)
|
|
{
|
|
// We already know about this process, just
|
|
// ignore the event.
|
|
if ((Process->m_Flags & ENG_PROC_EXAMINED) == 0)
|
|
{
|
|
WarnOut("WARNING: Duplicate process create event for %x\n",
|
|
g_EventProcessSysId);
|
|
}
|
|
return DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
}
|
|
|
|
ThreadInfo* Thread;
|
|
|
|
Process = new ProcessInfo(g_EventTarget, g_EventProcessSysId,
|
|
SymHandle, SysHandle, Flags, Options);
|
|
if (Process)
|
|
{
|
|
Thread = new ThreadInfo(Process, g_EventThreadSysId,
|
|
ThreadDataOffset, InitialThreadHandle,
|
|
InitialThreadFlags, StartOffset);
|
|
}
|
|
else
|
|
{
|
|
Thread = NULL;
|
|
}
|
|
|
|
if (!Process || !Thread)
|
|
{
|
|
// Clean up the process in case one was created.
|
|
delete Process;
|
|
|
|
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_EventTarget->m_ProcessesAdded = TRUE;
|
|
return DEBUG_STATUS_NO_CHANGE;
|
|
}
|
|
|
|
g_EventTarget->OutputProcessesAndThreads("*** Create process ***");
|
|
|
|
g_LastEventType = DEBUG_EVENT_CREATE_PROCESS;
|
|
sprintf(g_LastEventDesc, "Create process %d:%x",
|
|
g_EventProcess->m_UserId, g_EventProcessSysId);
|
|
|
|
// Simulate a load module event for the process but do
|
|
// not send it to the client.
|
|
g_EngNotify++;
|
|
|
|
if (QueryImageInfo)
|
|
{
|
|
GetEventName(ImageFileHandle, BaseOffset,
|
|
ImageNameOffset, (WORD)ImageNameUnicode,
|
|
NameBuffer, sizeof(NameBuffer));
|
|
GetHeaderInfo(g_EventProcess, BaseOffset,
|
|
&CheckSum, &TimeDateStamp, &ModuleSize);
|
|
CreateModuleNameFromPath(NameBuffer, ModuleNameBuffer);
|
|
ImageName = NameBuffer;
|
|
ModuleName = ModuleNameBuffer;
|
|
}
|
|
|
|
NotifyLoadModuleEvent(ImageFileHandle, BaseOffset, ModuleSize,
|
|
ModuleName, ImageName, CheckSum, TimeDateStamp,
|
|
IS_USER_TARGET(g_EventTarget));
|
|
|
|
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_EventTarget->m_ProcessesAdded = TRUE;
|
|
|
|
CreateProcessEventApcData ApcData;
|
|
ApcData.m_ImageFileHandle = ImageFileHandle;
|
|
ApcData.m_Handle = SysHandle;
|
|
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->m_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->m_UserId, g_EventProcessSysId, ExitCode);
|
|
}
|
|
|
|
ULONG EventStatus;
|
|
EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_EXIT_PROCESS];
|
|
BOOL MatchesEvent;
|
|
|
|
if (g_EventProcess && g_EventProcess->m_ExecutableImage)
|
|
{
|
|
MatchesEvent =
|
|
BreakOnThisImageTail(g_EventProcess->m_ExecutableImage->
|
|
m_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,
|
|
BOOL UserMode)
|
|
{
|
|
if (!g_EventProcess)
|
|
{
|
|
ErrOut("ERROR: Module load event for unknown process\n");
|
|
|
|
if (g_EngNotify == 0)
|
|
{
|
|
// Put in a placeholder description to make it easy
|
|
// to identify this case.
|
|
g_LastEventType = DEBUG_EVENT_LOAD_MODULE;
|
|
sprintf(g_LastEventDesc, "Ignored load module at %s",
|
|
FormatAddr64(BaseOffset));
|
|
}
|
|
|
|
return DEBUG_STATUS_BREAK;
|
|
}
|
|
|
|
MODULE_INFO_ENTRY ModEntry = {0};
|
|
ImageInfo* ImageEntry;
|
|
|
|
ModEntry.NamePtr = ImagePathName;
|
|
ModEntry.File = (HANDLE)ImageFileHandle;
|
|
ModEntry.Base = BaseOffset;
|
|
ModEntry.Size = ModuleSize;
|
|
ModEntry.CheckSum = CheckSum;
|
|
ModEntry.ModuleName = ModuleName;
|
|
ModEntry.TimeDateStamp = TimeDateStamp;
|
|
ModEntry.UserMode = UserMode ? TRUE : FALSE;
|
|
|
|
if (g_EventProcess->AddImage(&ModEntry, FALSE, &ImageEntry) != S_OK)
|
|
{
|
|
ImageEntry = NULL;
|
|
}
|
|
|
|
EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_LOAD_MODULE];
|
|
|
|
//
|
|
// ntsd has always shown mod loads by default.
|
|
//
|
|
|
|
if (IS_USER_TARGET(g_EventTarget))
|
|
{
|
|
//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);
|
|
}
|
|
}
|
|
|
|
g_EventTarget->OutputProcessesAndThreads("*** Load dll ***");
|
|
|
|
if (g_EngNotify > 0)
|
|
{
|
|
g_EventProcess->m_ModulesLoaded = TRUE;
|
|
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);
|
|
PrintString(g_LastEventDesc, DIMA(g_LastEventDesc),
|
|
"Load module %.*s at %s",
|
|
MAX_IMAGE_PATH - 32, ImagePathName, FormatAddr64(BaseOffset));
|
|
|
|
ULONG EventStatus;
|
|
BOOL MatchesEvent;
|
|
|
|
if (!g_EventProcess->m_ModulesLoaded)
|
|
{
|
|
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_EventProcess->m_ModulesLoaded))
|
|
{
|
|
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_EventTarget) ||
|
|
g_EventTarget->m_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_EventProcess->m_ModulesLoaded = TRUE;
|
|
|
|
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 &&
|
|
EventStatus < DEBUG_STATUS_IGNORE_EVENT &&
|
|
IS_KERNEL_TARGET(g_EventTarget) &&
|
|
g_EventTarget->m_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)
|
|
{
|
|
ImageInfo* Image = NULL;
|
|
|
|
if (!g_EventProcess)
|
|
{
|
|
ErrOut("ERROR: Module unload event for unknown process\n");
|
|
|
|
if (g_EngNotify == 0)
|
|
{
|
|
// Put in a placeholder description to make it easy
|
|
// to identify this case.
|
|
g_LastEventType = DEBUG_EVENT_UNLOAD_MODULE;
|
|
sprintf(g_LastEventDesc, "Ignored unload module at %s",
|
|
FormatAddr64(BaseOffset));
|
|
}
|
|
|
|
return DEBUG_STATUS_BREAK;
|
|
}
|
|
|
|
// First try to look up the image by the base offset
|
|
// as that's the most reliable identifier.
|
|
if (BaseOffset)
|
|
{
|
|
Image = g_EventProcess->FindImageByOffset(BaseOffset, FALSE);
|
|
}
|
|
|
|
// Next try to look up the image by the full name given.
|
|
if (!Image && ImageBaseName)
|
|
{
|
|
Image = g_EventProcess->FindImageByName(ImageBaseName, 0,
|
|
INAME_IMAGE_PATH, FALSE);
|
|
|
|
// Finally try to look up the image by the tail of the name given.
|
|
if (!Image)
|
|
{
|
|
Image = g_EventProcess->FindImageByName(PathTail(ImageBaseName), 0,
|
|
INAME_IMAGE_PATH_TAIL,
|
|
FALSE);
|
|
}
|
|
}
|
|
|
|
if (Image)
|
|
{
|
|
ImageBaseName = Image->m_ImagePath;
|
|
BaseOffset = Image->m_BaseOfImage;
|
|
Image->m_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);
|
|
PrintString(g_LastEventDesc, DIMA(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 - %s: ",
|
|
EventIdStr(),
|
|
Level == SLE_WARNING ?
|
|
"WARNING" : "ERROR", g_EventProcess->m_ImageHead->m_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, ProcessInfo* 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.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
//
|
|
// IMPORTANT: GetInput may be called in the middle of an operation.
|
|
// For example, an extension command may request input, so the engine
|
|
// is still in the middle of processing that command
|
|
// text. Nothing in the engine should change at this point but
|
|
// it's necessary to suspend the engine lock to allow new
|
|
// connections and processing while waiting. In theory
|
|
// all write operations should be prevented if g_InputNesting >= 1,
|
|
// indicating an input wait. Right now key
|
|
// methods like WaitForEvent and Execute have such checks,
|
|
// but all writes should have them. If you see problems
|
|
// with GetInput it might indicate a need for more checks.
|
|
//
|
|
ULONG g_InputNesting;
|
|
|
|
ULONG
|
|
GetInput(PCSTR Prompt,
|
|
PSTR Buffer,
|
|
ULONG BufferSize,
|
|
ULONG Flags)
|
|
{
|
|
DebugClient* Client;
|
|
ULONG Len;
|
|
HRESULT Status;
|
|
|
|
// Start a new sequence for this input.
|
|
g_InputSequence = 0;
|
|
g_InputSizeRequested = BufferSize;
|
|
g_InputNesting++;
|
|
|
|
if (Prompt != NULL && Prompt[0] != 0)
|
|
{
|
|
dprintf("%s", Prompt);
|
|
}
|
|
|
|
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.
|
|
// This must occur before any callbacks are called as
|
|
// the callbacks may use a different client for
|
|
// ReturnInput.
|
|
Client->m_InputSequence = 1;
|
|
}
|
|
for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
|
|
{
|
|
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;
|
|
g_InputNesting--;
|
|
|
|
// Notify all clients that input process is done.
|
|
for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
|
|
{
|
|
if (Client->m_InputCb != NULL)
|
|
{
|
|
__try
|
|
{
|
|
Client->m_InputCb->EndInput();
|
|
}
|
|
__except(ExtensionExceptionFilter(GetExceptionInformation(),
|
|
NULL, "IDebugInputCallbacks::"
|
|
"EndInput"))
|
|
{
|
|
}
|
|
}
|
|
}
|
|
for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
|
|
{
|
|
Client->m_InputSequence = 0xffffffff;
|
|
}
|
|
|
|
if (Len &&
|
|
(Flags & GETIN_LOG_INPUT) &&
|
|
g_LogFile >= 0)
|
|
{
|
|
ULONG BufLen = strlen(Buffer);
|
|
if (BufLen)
|
|
{
|
|
_write(g_LogFile, Buffer, BufLen);
|
|
}
|
|
if (Flags & GETIN_LOG_INPUT_ADD_NEW_LINE)
|
|
{
|
|
_write(g_LogFile, "\n", 1);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
#define BUFFERED_OUTPUT_SIZE 1024
|
|
|
|
// 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;
|
|
|
|
BOOL g_PartialOutputLine;
|
|
ULONG g_LastOutputMask;
|
|
|
|
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
|
|
CompletePartialLine(ULONG MatchMask)
|
|
{
|
|
// There are often cases where special output, such
|
|
// as !sym noisy output, will come in the middle
|
|
// of some other output. If the last output was
|
|
// a partial line and not special output, insert
|
|
// a newline so that the special output isn't stuck
|
|
// out on the end of a line somewhere.
|
|
if (g_PartialOutputLine && g_LastOutputMask != MatchMask)
|
|
{
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
|
|
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,
|
|
BOOL Ptr64
|
|
)
|
|
{
|
|
#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);
|
|
(void)va_arg(args, int);
|
|
TypeFormat = FALSE;
|
|
break;
|
|
case 'e': case 'E': case 'f': case 'g':
|
|
case 'G':
|
|
Duplicate(j,i);
|
|
(void)va_arg(args, double);
|
|
TypeFormat = FALSE;
|
|
break;
|
|
case 'n':
|
|
Duplicate(j,i);
|
|
(void)va_arg(args, int*);
|
|
TypeFormat = FALSE;
|
|
break;
|
|
case 'N':
|
|
// Native pointer, turns into %p.
|
|
formatOut[j++] = 'p';
|
|
FormatChanged = TRUE;
|
|
i++;
|
|
(void)va_arg(args, void*);
|
|
TypeFormat = FALSE;
|
|
break;
|
|
case 's': case 'S':
|
|
Duplicate(j,i);
|
|
(void)va_arg(args, char*);
|
|
TypeFormat = FALSE;
|
|
break;
|
|
|
|
case 'I':
|
|
if ((format[i+1] == '6') && (format[i+2] == '4'))
|
|
{
|
|
Duplicate(j,i);
|
|
Duplicate(j,i);
|
|
(void)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);
|
|
(void)va_arg(args, void*);
|
|
TypeFormat = FALSE;
|
|
break;
|
|
|
|
case 'p':
|
|
case 'P':
|
|
minSize +=3;
|
|
if (format[i-1] == '%')
|
|
{
|
|
minSize++;
|
|
if (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 (!Ptr64)
|
|
{
|
|
PULONG64 Arg;
|
|
|
|
Arg = (PULONG64) (args);
|
|
|
|
//
|
|
// Truncate signextended addresses
|
|
//
|
|
*Arg = (ULONG64) (ULONG) *Arg;
|
|
}
|
|
|
|
(void)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;
|
|
|
|
// 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,
|
|
g_Machine ? g_Machine->m_Ptr64 : TRUE))
|
|
{
|
|
Len = _vsnprintf(g_OutBuffer, OUT_BUFFER_SIZE - 1,
|
|
g_FormatBuffer, Args);
|
|
}
|
|
else
|
|
{
|
|
Len = _vsnprintf(g_OutBuffer, OUT_BUFFER_SIZE - 1, Format, Args);
|
|
}
|
|
if (Len == 0)
|
|
{
|
|
__leave;
|
|
}
|
|
else if (Len < 0)
|
|
{
|
|
Len = OUT_BUFFER_SIZE - 1;
|
|
g_OutBuffer[Len] = 0;
|
|
}
|
|
|
|
// 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_OutBuffer[Len - 1] != '\n' &&
|
|
g_OutBuffer[Len - 1] != '\r')
|
|
{
|
|
// The current output is not a complete line.
|
|
g_PartialOutputLine = TRUE;
|
|
}
|
|
else
|
|
{
|
|
g_PartialOutputLine = FALSE;
|
|
}
|
|
g_LastOutputMask = Mask;
|
|
|
|
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)
|