windows-nt/Source/XPSP1/NT/sdktools/debuggers/ntsd64/dbgclt.cpp
2020-09-26 16:20:57 +08:00

2731 lines
56 KiB
C++

//----------------------------------------------------------------------------
//
// Debug client implementation.
//
// Copyright (C) Microsoft Corporation, 1999-2001.
//
//----------------------------------------------------------------------------
#include "ntsdp.hpp"
#include "dbgver.h"
#define VER_STRING(Specific) \
"\n" \
"Microsoft (R) Windows " Specific \
" Version " VER_PRODUCTVERSION_STR \
"\n" VER_LEGALCOPYRIGHT_STR \
"\n" \
"\n"
PCHAR g_Win9xVersionString = VER_STRING("9x User-Mode Debugger");
PCHAR g_WinKernelVersionString = VER_STRING("Kernel Debugger");
PCHAR g_WinUserVersionString = VER_STRING("User-Mode Debugger");
BOOL g_QuietMode;
ULONG g_OutputWidth = 80;
PCSTR g_OutputLinePrefix;
// The platform ID of the machine running the debugger. Note
// that this may be different from g_TargetPlatformId, which
// is the platform ID of the machine being debugged.
ULONG g_DebuggerPlatformId;
CRITICAL_SECTION g_QuickLock;
CRITICAL_SECTION g_EngineLock;
ULONG g_EngineNesting;
// Events and storage space for returning event callback
// status from an APC.
HANDLE g_EventStatusWaiting;
HANDLE g_EventStatusReady;
ULONG g_EventStatus;
// Named event to sleep on.
HANDLE g_SleepPidEvent;
//----------------------------------------------------------------------------
//
// DebugClient.
//
//----------------------------------------------------------------------------
// List of all clients.
DebugClient* g_Clients;
char g_InputBuffer[INPUT_BUFFER_SIZE];
ULONG g_InputSequence;
HANDLE g_InputEvent;
ULONG g_InputSizeRequested;
// The thread that created the current session.
ULONG g_SessionThread;
PPENDING_PROCESS g_ProcessPending;
ULONG g_EngOptions;
ULONG g_EngStatus;
ULONG g_EngDefer;
ULONG g_EngErr;
// Some options set through the process options apply to
// all processes and some are per-process. The global
// options are collected here.
ULONG g_GlobalProcOptions;
#if DBG
ULONG g_EnvOutMask;
#endif
DebugClient::DebugClient(void)
{
m_Next = NULL;
m_Prev = NULL;
m_Refs = 1;
m_Flags = 0;
m_ThreadId = ::GetCurrentThreadId();
m_Thread = NULL;
m_EventCb = NULL;
m_EventInterest = 0;
m_DispatchSema = NULL;
m_InputCb = NULL;
m_InputSequence = 0xffffffff;
m_OutputCb = NULL;
#if DBG
m_OutMask = DEFAULT_OUT_MASK | g_EnvOutMask;
#else
m_OutMask = DEFAULT_OUT_MASK;
#endif
m_OutputWidth = 80;
m_OutputLinePrefix = NULL;
}
DebugClient::~DebugClient(void)
{
// Most of the work is done in Destroy.
if (m_Flags & CLIENT_IN_LIST)
{
Unlink();
}
}
void
DebugClient::Destroy(void)
{
// Clients cannot arbitrarily be removed from the client list
// or their memory deleted due to the possibility of a callback
// loop occurring at the same time. Instead clients are left
// in the list and zeroed out to prevent further callbacks
// from occurring.
// XXX drewb - This memory needs to be reclaimed at some
// point, but there's no simple safe time to do so since
// callbacks can occur at any time. Clients are very small
// right now so the leakage is negligible.
m_Flags = (m_Flags & ~(CLIENT_REMOTE | CLIENT_PRIMARY)) |
CLIENT_DESTROYED;
// Remove any references from breakpoints this client
// added.
PPROCESS_INFO Process;
Breakpoint* Bp;
for (Process = g_ProcessHead; Process; Process = Process->Next)
{
for (Bp = Process->Breakpoints; Bp != NULL; Bp = Bp->m_Next)
{
if (Bp->m_Adder == this)
{
Bp->m_Adder = NULL;
}
}
}
if (m_Thread != NULL)
{
CloseHandle(m_Thread);
m_Thread = NULL;
}
m_EventInterest = 0;
RELEASE(m_EventCb);
if (m_DispatchSema != NULL)
{
CloseHandle(m_DispatchSema);
m_DispatchSema = NULL;
}
RELEASE(m_InputCb);
m_InputSequence = 0xffffffff;
RELEASE(m_OutputCb);
m_OutMask = 0;
CollectOutMasks();
}
STDMETHODIMP
DebugClient::QueryInterface(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface
)
{
HRESULT Status;
*Interface = NULL;
Status = S_OK;
// Interface specific casts are necessary in order to
// get the right vtable pointer in our multiple
// inheritance scheme.
if (DbgIsEqualIID(InterfaceId, IID_IUnknown) ||
DbgIsEqualIID(InterfaceId, IID_IDebugClient) ||
DbgIsEqualIID(InterfaceId, IID_IDebugClient2))
{
*Interface = (IDebugClientN *)this;
}
else if (DbgIsEqualIID(InterfaceId, IID_IDebugAdvanced))
{
*Interface = (IDebugAdvancedN *)this;
}
else if (DbgIsEqualIID(InterfaceId, IID_IDebugControl) ||
DbgIsEqualIID(InterfaceId, IID_IDebugControl2))
{
*Interface = (IDebugControlN *)this;
}
else if (DbgIsEqualIID(InterfaceId, IID_IDebugDataSpaces) ||
DbgIsEqualIID(InterfaceId, IID_IDebugDataSpaces2))
{
*Interface = (IDebugDataSpacesN *)this;
}
else if (DbgIsEqualIID(InterfaceId, IID_IDebugRegisters))
{
*Interface = (IDebugRegistersN *)this;
}
else if (DbgIsEqualIID(InterfaceId, IID_IDebugSymbols) ||
DbgIsEqualIID(InterfaceId, IID_IDebugSymbols2))
{
*Interface = (IDebugSymbolsN *)this;
}
else if (DbgIsEqualIID(InterfaceId, IID_IDebugSystemObjects) ||
DbgIsEqualIID(InterfaceId, IID_IDebugSystemObjects2))
{
*Interface = (IDebugSystemObjectsN *)this;
}
else if (DbgIsEqualIID(InterfaceId, IID_IDebugSymbolGroup))
{
*Interface = (IDebugSymbolGroupN *)this;
}
else
{
Status = E_NOINTERFACE;
}
if (Status == S_OK)
{
AddRef();
}
return Status;
}
STDMETHODIMP_(ULONG)
DebugClient::AddRef(
THIS
)
{
return InterlockedIncrement((PLONG)&m_Refs);
}
STDMETHODIMP_(ULONG)
DebugClient::Release(
THIS
)
{
LONG Refs = InterlockedDecrement((PLONG)&m_Refs);
if (Refs == 0)
{
Destroy();
}
return Refs;
}
STDMETHODIMP
DebugClient::AttachKernel(
THIS_
IN ULONG Flags,
IN OPTIONAL PCSTR ConnectOptions
)
{
ULONG Qual;
if (
#if DEBUG_ATTACH_KERNEL_CONNECTION > 0
Flags < DEBUG_ATTACH_KERNEL_CONNECTION ||
#endif
Flags > DEBUG_ATTACH_EXDI_DRIVER)
{
return E_INVALIDARG;
}
if (Flags == DEBUG_ATTACH_LOCAL_KERNEL)
{
if (ConnectOptions != NULL)
{
return E_INVALIDARG;
}
if (g_DebuggerPlatformId != VER_PLATFORM_WIN32_NT)
{
return E_UNEXPECTED;
}
Qual = DEBUG_KERNEL_LOCAL;
}
else if (Flags == DEBUG_ATTACH_EXDI_DRIVER)
{
Qual = DEBUG_KERNEL_EXDI_DRIVER;
}
else
{
Qual = DEBUG_KERNEL_CONNECTION;
}
ENTER_ENGINE();
HRESULT Status = LiveKernelInitialize(this, Qual, ConnectOptions);
if (Status == S_OK)
{
InitializePrimary();
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::GetKernelConnectionOptions(
THIS_
OUT OPTIONAL PSTR Buffer,
IN ULONG BufferSize,
OUT OPTIONAL PULONG OptionsSize
)
{
if (!IS_CONN_KERNEL_TARGET() ||
g_DbgKdTransport == NULL)
{
return E_UNEXPECTED;
}
if (BufferSize == 0)
{
return E_INVALIDARG;
}
ENTER_ENGINE();
#define MIN_BUFFER_SIZE (2 * (MAX_PARAM_NAME + MAX_PARAM_VALUE + 16))
char MinBuf[MIN_BUFFER_SIZE];
PSTR Buf;
ULONG BufSize;
if (Buffer == NULL || BufferSize < MIN_BUFFER_SIZE)
{
Buf = MinBuf;
BufSize = MIN_BUFFER_SIZE;
}
else
{
Buf = Buffer;
BufSize = BufferSize;
}
HRESULT Status;
if (g_DbgKdTransport->GetParameters(Buf, BufSize))
{
BufSize = strlen(Buf);
Status = S_OK;
}
else
{
// Just guess on the necessary size.
BufSize *= 2;
Status = S_FALSE;
}
if (Buffer != NULL && Buf != Buffer)
{
strcpy(Buffer, Buf);
}
if (OptionsSize != NULL)
{
*OptionsSize = BufSize;
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::SetKernelConnectionOptions(
THIS_
IN PCSTR Options
)
{
if (!IS_CONN_KERNEL_TARGET() ||
g_DbgKdTransport == NULL)
{
return E_UNEXPECTED;
}
// This method is reentrant.
if (!_strcmpi(Options, "resync"))
{
g_DbgKdTransport->m_Resync = TRUE;
}
else if (!_strcmpi(Options, "cycle_speed"))
{
g_DbgKdTransport->CycleSpeed();
}
else
{
return E_NOINTERFACE;
}
return S_OK;
}
DBGRPC_SIMPLE_FACTORY(LiveUserDebugServices, IID_IUserDebugServices, \
"Remote Process Server", (TRUE))
LiveUserDebugServicesFactory g_LiveUserDebugServicesFactory;
STDMETHODIMP
DebugClient::StartProcessServer(
THIS_
IN ULONG Flags,
IN PCSTR Options,
IN PVOID Reserved
)
{
if (Flags <= DEBUG_CLASS_KERNEL || Flags > DEBUG_CLASS_USER_WINDOWS)
{
return E_INVALIDARG;
}
// XXX drewb - Turn reserved into public IUserDebugServices
// parameter so that a server can be started over arbitrary services.
if (Reserved != NULL)
{
return E_NOTIMPL;
}
HRESULT Status;
ENTER_ENGINE();
DbgRpcClientObjectFactory* Factory;
switch(Flags)
{
case DEBUG_CLASS_USER_WINDOWS:
Factory = &g_LiveUserDebugServicesFactory;
break;
default:
DBG_ASSERT(FALSE);
break;
}
Status = DbgRpcCreateServer(Options, Factory);
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::ConnectProcessServer(
THIS_
IN PCSTR RemoteOptions,
OUT PULONG64 Server
)
{
HRESULT Status;
ENTER_ENGINE();
PUSER_DEBUG_SERVICES Services;
if ((Status = DbgRpcConnectServer(RemoteOptions, &IID_IUserDebugServices,
(IUnknown**)&Services)) == S_OK)
{
*Server = (ULONG64)(ULONG_PTR)Services;
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::DisconnectProcessServer(
THIS_
IN ULONG64 Server
)
{
ENTER_ENGINE();
((PUSER_DEBUG_SERVICES)Server)->Release();
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::GetRunningProcessSystemIds(
THIS_
IN ULONG64 Server,
OUT OPTIONAL /* size_is(Count) */ PULONG Ids,
IN ULONG Count,
OUT OPTIONAL PULONG ActualCount
)
{
HRESULT Status;
ENTER_ENGINE();
Status = SERVER_SERVICES(Server)->
GetProcessIds(Ids, Count, ActualCount);
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::GetRunningProcessSystemIdByExecutableName(
THIS_
IN ULONG64 Server,
IN PCSTR ExeName,
IN ULONG Flags,
OUT PULONG Id
)
{
if (Flags & ~(DEBUG_GET_PROC_DEFAULT |
DEBUG_GET_PROC_FULL_MATCH |
DEBUG_GET_PROC_ONLY_MATCH))
{
return E_INVALIDARG;
}
HRESULT Status;
ENTER_ENGINE();
Status = SERVER_SERVICES(Server)->
GetProcessIdByExecutableName(ExeName, Flags, Id);
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::GetRunningProcessDescription(
THIS_
IN ULONG64 Server,
IN ULONG SystemId,
IN ULONG Flags,
OUT OPTIONAL PSTR ExeName,
IN ULONG ExeNameSize,
OUT OPTIONAL PULONG ActualExeNameSize,
OUT OPTIONAL PSTR Description,
IN ULONG DescriptionSize,
OUT OPTIONAL PULONG ActualDescriptionSize
)
{
HRESULT Status;
if (Flags & ~(DEBUG_PROC_DESC_DEFAULT |
DEBUG_PROC_DESC_NO_PATHS))
{
return E_INVALIDARG;
}
ENTER_ENGINE();
Status = SERVER_SERVICES(Server)->
GetProcessDescription(SystemId, Flags, ExeName, ExeNameSize,
ActualExeNameSize, Description, DescriptionSize,
ActualDescriptionSize);
LEAVE_ENGINE();
return Status;
}
#define ALL_ATTACH_FLAGS \
(DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_EXISTING)
STDMETHODIMP
DebugClient::AttachProcess(
THIS_
IN ULONG64 Server,
IN ULONG ProcessId,
IN ULONG AttachFlags
)
{
HRESULT Status;
if ((AttachFlags & ~ALL_ATTACH_FLAGS) ||
(AttachFlags & (DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_EXISTING)) ==
(DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_EXISTING))
{
return E_INVALIDARG;
}
ENTER_ENGINE();
BOOL InitTarget = FALSE;
if (!IS_TARGET_SET())
{
Status = UserInitialize(this, Server);
InitTarget = TRUE;
}
if (IS_LIVE_USER_TARGET())
{
PPENDING_PROCESS Pending;
Status = StartAttachProcess(ProcessId, AttachFlags, &Pending);
if (Status == S_OK)
{
InitializePrimary();
}
else if (InitTarget)
{
DiscardTarget(DEBUG_SESSION_END_SESSION_PASSIVE);
}
}
else if (!InitTarget)
{
Status = E_UNEXPECTED;
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::CreateProcess(
THIS_
IN ULONG64 Server,
IN PSTR CommandLine,
IN ULONG CreateFlags
)
{
HRESULT Status;
ENTER_ENGINE();
BOOL InitTarget = FALSE;
if (!IS_TARGET_SET())
{
Status = UserInitialize(this, Server);
InitTarget = TRUE;
}
if (IS_LIVE_USER_TARGET())
{
PPENDING_PROCESS Pending;
Status = StartCreateProcess(CommandLine, CreateFlags, &Pending);
if (Status == S_OK)
{
InitializePrimary();
}
else if (InitTarget)
{
DiscardTarget(DEBUG_SESSION_END_SESSION_PASSIVE);
}
}
else if (!InitTarget)
{
Status = E_UNEXPECTED;
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::CreateProcessAndAttach(
THIS_
IN ULONG64 Server,
IN OPTIONAL PSTR CommandLine,
IN ULONG CreateFlags,
IN ULONG ProcessId,
IN ULONG AttachFlags
)
{
if ((CommandLine == NULL && ProcessId == 0) ||
(AttachFlags & ~ALL_ATTACH_FLAGS))
{
return E_INVALIDARG;
}
HRESULT Status;
ENTER_ENGINE();
BOOL InitTarget = FALSE;
if (!IS_TARGET_SET())
{
Status = UserInitialize(this, Server);
InitTarget = TRUE;
}
if (IS_LIVE_USER_TARGET())
{
PPENDING_PROCESS PendCreate, PendAttach;
if (CommandLine != NULL)
{
if (ProcessId != 0)
{
CreateFlags |= CREATE_SUSPENDED;
}
if ((Status = StartCreateProcess(CommandLine, CreateFlags,
&PendCreate)) != S_OK)
{
goto EH_Discard;
}
}
if (ProcessId != 0)
{
if ((Status = StartAttachProcess(ProcessId, AttachFlags,
&PendAttach)) != S_OK)
{
goto EH_Discard;
}
// If we previously created a process we need to wake
// it up when we attach since we created it suspended.
if (CommandLine != NULL)
{
g_ThreadToResume = PendCreate->InitialThreadHandle;
}
}
InitializePrimary();
}
else if (!InitTarget)
{
Status = E_UNEXPECTED;
}
LEAVE_ENGINE();
return Status;
EH_Discard:
DiscardTarget(DEBUG_SESSION_END_SESSION_PASSIVE);
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::GetProcessOptions(
THIS_
OUT PULONG Options
)
{
HRESULT Status;
ENTER_ENGINE();
if (!IS_LIVE_USER_TARGET())
{
Status = E_UNEXPECTED;
}
else
{
Status = S_OK;
*Options = g_GlobalProcOptions;
if (g_CurrentProcess != NULL)
{
*Options |= g_CurrentProcess->Options;
}
}
LEAVE_ENGINE();
return Status;
}
#define PROCESS_ALL \
(DEBUG_PROCESS_DETACH_ON_EXIT | DEBUG_PROCESS_ONLY_THIS_PROCESS)
#define PROCESS_GLOBAL \
(DEBUG_PROCESS_DETACH_ON_EXIT)
HRESULT
ChangeProcessOptions(ULONG Options, ULONG OptFn)
{
if (Options & ~PROCESS_ALL)
{
return E_INVALIDARG;
}
if (!IS_LIVE_USER_TARGET())
{
return E_UNEXPECTED;
}
HRESULT Status;
ENTER_ENGINE();
ULONG NewPer, OldPer;
ULONG NewGlobal;
switch(OptFn)
{
case OPTFN_ADD:
if (Options & ~PROCESS_GLOBAL)
{
if (g_CurrentProcess == NULL)
{
Status = E_UNEXPECTED;
goto Exit;
}
OldPer = g_CurrentProcess->Options;
NewPer = OldPer | (Options & ~PROCESS_GLOBAL);
}
else
{
NewPer = 0;
OldPer = 0;
}
NewGlobal = g_GlobalProcOptions | (Options & PROCESS_GLOBAL);
break;
case OPTFN_REMOVE:
if (Options & ~PROCESS_GLOBAL)
{
if (g_CurrentProcess == NULL)
{
Status = E_UNEXPECTED;
goto Exit;
}
OldPer = g_CurrentProcess->Options;
NewPer = OldPer & ~(Options & ~PROCESS_GLOBAL);
}
else
{
NewPer = 0;
OldPer = 0;
}
NewGlobal = g_GlobalProcOptions & ~(Options & PROCESS_GLOBAL);
break;
case OPTFN_SET:
// Always require a process in this case as otherwise
// there's no way to know whether a call to SetProcessOptions
// is actually necessary or not.
if (g_CurrentProcess == NULL)
{
Status = E_UNEXPECTED;
goto Exit;
}
OldPer = g_CurrentProcess->Options;
NewPer = Options & ~PROCESS_GLOBAL;
NewGlobal = Options & PROCESS_GLOBAL;
break;
}
PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services;
BOOL Notify = FALSE;
if (NewGlobal ^ g_GlobalProcOptions)
{
// Global options can only be changed by the session thread.
if (::GetCurrentThreadId() != g_SessionThread)
{
Status = E_UNEXPECTED;
goto Exit;
}
if ((Status = Services->SetDebugObjectOptions(0, NewGlobal)) != S_OK)
{
goto Exit;
}
Notify = TRUE;
g_GlobalProcOptions = NewGlobal;
}
if (NewPer ^ OldPer)
{
if ((Status = Services->
SetProcessOptions(g_CurrentProcess->FullHandle, NewPer)) != S_OK)
{
goto Exit;
}
g_CurrentProcess->Options = NewPer;
Notify = TRUE;
}
if (Notify)
{
NotifyChangeEngineState(DEBUG_CES_PROCESS_OPTIONS,
NewPer | NewGlobal, FALSE);
}
Exit:
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::AddProcessOptions(
THIS_
IN ULONG Options
)
{
return ChangeProcessOptions(Options, OPTFN_ADD);
}
STDMETHODIMP
DebugClient::RemoveProcessOptions(
THIS_
IN ULONG Options
)
{
return ChangeProcessOptions(Options, OPTFN_REMOVE);
}
STDMETHODIMP
DebugClient::SetProcessOptions(
THIS_
IN ULONG Options
)
{
return ChangeProcessOptions(Options, OPTFN_SET);
}
STDMETHODIMP
DebugClient::OpenDumpFile(
THIS_
IN PCSTR DumpFile
)
{
ENTER_ENGINE();
HRESULT Status;
if (g_SessionThread != 0)
{
// A session is already active.
Status = E_UNEXPECTED;
goto EH_Exit;
}
ULONG Class, Qual;
if ((Status = InitNtCmd(this)) != S_OK)
{
goto EH_Exit;
}
//
// Automatically expand CAB files.
//
PCSTR OpenFile = DumpFile;
char CabDumpFile[2 * MAX_PATH];
INT_PTR CabDumpFh = -1;
PSTR Ext;
Ext = strrchr(DumpFile, '.');
if (Ext != NULL && _stricmp(Ext, ".cab") == 0)
{
// Expand the first .dmp or .mdmp file in the CAB.
// Mark it as delete-on-close so it always gets
// cleaned up regardless of how the process exits.
if (ExpandDumpCab(DumpFile, _O_CREAT | _O_EXCL | _O_TEMPORARY,
CabDumpFile, &CabDumpFh) == S_OK)
{
OpenFile = CabDumpFile;
dprintf("Extracted %s\n", OpenFile);
}
}
Status = DmpInitialize(OpenFile);
if (CabDumpFh >= 0)
{
// We expanded a file from a CAB and can close it
// now because it was either reopened or we need
// to get rid of it.
_close((int)CabDumpFh);
}
if (Status != S_OK)
{
ErrOut("Could not initialize dump file [%s], %s\n \"%s\"\n",
DumpFile, FormatStatusCode(Status),
FormatStatusArgs(Status, &DumpFile));
goto EH_Exit;
}
g_Target = g_DumpTargets[g_DumpType];
Status = InitializeTarget();
if (Status != S_OK)
{
DmpUninitialize();
}
else
{
dprintf("%s", IS_KERNEL_TARGET() ? g_WinKernelVersionString :
g_WinUserVersionString);
InitializePrimary();
}
EH_Exit:
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::WriteDumpFile(
THIS_
IN PCSTR DumpFile,
IN ULONG Qualifier
)
{
return WriteDumpFile2(DumpFile, Qualifier, DEBUG_FORMAT_DEFAULT, NULL);
}
#define ALL_CONNECT_SESSION_FLAGS \
(DEBUG_CONNECT_SESSION_NO_VERSION | \
DEBUG_CONNECT_SESSION_NO_ANNOUNCE)
STDMETHODIMP
DebugClient::ConnectSession(
THIS_
IN ULONG Flags,
IN ULONG HistoryLimit
)
{
if (Flags & ~ALL_CONNECT_SESSION_FLAGS)
{
return E_INVALIDARG;
}
ENTER_ENGINE();
OutCtlSave OldCtl;
PushOutCtl(DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED,
this, &OldCtl);
if ((Flags & DEBUG_CONNECT_SESSION_NO_VERSION) == 0)
{
if (IS_KERNEL_TARGET())
{
dprintf("%s", g_WinKernelVersionString);
}
else if (g_TargetPlatformId != VER_PLATFORM_WIN32_NT)
{
dprintf("%s", g_Win9xVersionString);
}
else
{
dprintf("%s", g_WinUserVersionString);
}
}
SendOutputHistory(this, HistoryLimit);
// If we're in the middle of an input request and
// a new client has joined immediately start
// the input cycle for it.
ULONG InputRequest = g_InputSizeRequested;
if (InputRequest > 0)
{
m_InputSequence = 1;
if (m_InputCb != NULL)
{
m_InputCb->StartInput(InputRequest);
}
}
PopOutCtl(&OldCtl);
if ((Flags & DEBUG_CONNECT_SESSION_NO_ANNOUNCE) == 0)
{
InitializePrimary();
dprintf("%s connected at %s", m_Identity, ctime(&m_LastActivity));
}
LEAVE_ENGINE();
return S_OK;
}
DBGRPC_SIMPLE_FACTORY(DebugClient, IID_IDebugClient, \
"Debugger Server", ())
DebugClientFactory g_DebugClientFactory;
STDMETHODIMP
DebugClient::StartServer(
THIS_
IN PCSTR Options
)
{
HRESULT Status;
ENTER_ENGINE();
Status = DbgRpcCreateServer(Options, &g_DebugClientFactory);
if (Status == S_OK)
{
// Turn on output history collection.
g_OutHistoryMask = DEFAULT_OUT_HISTORY_MASK;
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::OutputServers(
THIS_
IN ULONG OutputControl,
IN PCSTR Machine,
IN ULONG Flags
)
{
if (Flags & ~DEBUG_SERVERS_ALL)
{
return E_INVALIDARG;
}
HRESULT Status;
ENTER_ENGINE();
OutCtlSave OldCtl;
if (!PushOutCtl(OutputControl, this, &OldCtl))
{
Status = E_INVALIDARG;
}
else
{
LONG RegStatus;
HKEY RegKey;
HKEY Key;
Status = S_OK;
if ((RegStatus = RegConnectRegistry(Machine, HKEY_LOCAL_MACHINE,
&RegKey)) != ERROR_SUCCESS)
{
Status = HRESULT_FROM_WIN32(RegStatus);
goto Pop;
}
if ((RegStatus = RegOpenKeyEx(RegKey, DEBUG_SERVER_KEY,
0, KEY_ALL_ACCESS,
&Key)) != ERROR_SUCCESS)
{
// Don't report not-found as an error since it just
// means there's nothing to enumerate.
if (RegStatus != ERROR_FILE_NOT_FOUND)
{
Status = HRESULT_FROM_WIN32(RegStatus);
}
goto RegClose;
}
ULONG Index;
char Name[32];
char Value[2 * MAX_PARAM_VALUE];
ULONG NameLen, ValueLen;
ULONG Type;
Index = 0;
for (;;)
{
NameLen = sizeof(Name);
ValueLen = sizeof(Value);
if ((RegStatus = RegEnumValue(Key, Index, Name, &NameLen,
NULL, &Type, (LPBYTE)Value,
&ValueLen)) != ERROR_SUCCESS)
{
// Done with the enumeration.
break;
}
if (Type != REG_SZ)
{
// Only string values should be present.
Status = E_FAIL;
break;
}
BOOL Output;
Output = FALSE;
if (!strncmp(Value, "Debugger Server", 15))
{
if (Flags & DEBUG_SERVERS_DEBUGGER)
{
Output = TRUE;
}
}
else if (Flags & DEBUG_SERVERS_PROCESS)
{
Output = TRUE;
}
if (Output)
{
dprintf("%s\n", Value);
}
Index++;
}
RegCloseKey(Key);
RegClose:
RegCloseKey(RegKey);
Pop:
PopOutCtl(&OldCtl);
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::TerminateProcesses(
THIS
)
{
if (!IS_LIVE_USER_TARGET())
{
return E_UNEXPECTED;
}
HRESULT Status;
ENTER_ENGINE();
Status = ::TerminateProcesses();
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::DetachProcesses(
THIS
)
{
if (!IS_LIVE_USER_TARGET())
{
return E_UNEXPECTED;
}
HRESULT Status;
ENTER_ENGINE();
Status = ::DetachProcesses();
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::EndSession(
THIS_
IN ULONG Flags
)
{
if (
#if DEBUG_END_PASSIVE > 0
Flags < DEBUG_END_PASSIVE ||
#endif
Flags > DEBUG_END_REENTRANT)
{
return E_INVALIDARG;
}
if (Flags == DEBUG_END_REENTRANT)
{
// If somebody's doing a reentrant end that means
// the process is going away so we can clean up
// any running server registration entries.
DbgRpcDeregisterServers();
}
if (!IS_TARGET_SET())
{
return E_UNEXPECTED;
}
HRESULT Status = S_OK;
if (Flags == DEBUG_END_REENTRANT)
{
goto Reenter;
}
ENTER_ENGINE();
if (IS_LIVE_USER_TARGET())
{
// If this is an active end, terminate or detach.
if (Flags == DEBUG_END_ACTIVE_TERMINATE)
{
Status = ::TerminateProcesses();
if (FAILED(Status))
{
goto Leave;
}
}
else if (Flags == DEBUG_END_ACTIVE_DETACH)
{
Status = ::DetachProcesses();
if (FAILED(Status))
{
goto Leave;
}
}
}
Reenter:
if (IS_LIVE_USER_TARGET() && SYSTEM_PROCESSES() &&
(g_GlobalProcOptions & DEBUG_PROCESS_DETACH_ON_EXIT) == 0)
{
//
// If we try to quit while debugging CSRSS, raise an
// error.
//
if (Flags != DEBUG_END_REENTRANT)
{
ErrOut("(%d): FATAL ERROR: Exiting Debugger while debugging CSR\n",
::GetCurrentProcessId());
}
g_NtDllCalls.DbgPrint("(%d): FATAL ERROR: "
"Exiting Debugger while debugging CSR\n",
::GetCurrentProcessId());
if (g_DebuggerPlatformId == VER_PLATFORM_WIN32_NT)
{
g_NtDllCalls.NtSystemDebugControl
(SysDbgBreakPoint, NULL, 0, NULL, 0, 0);
}
DebugBreak();
}
if (Flags != DEBUG_END_REENTRANT)
{
DiscardTarget(Flags == DEBUG_END_ACTIVE_TERMINATE ?
DEBUG_SESSION_END_SESSION_ACTIVE_TERMINATE :
(Flags == DEBUG_END_ACTIVE_DETACH ?
DEBUG_SESSION_END_SESSION_ACTIVE_DETACH :
DEBUG_SESSION_END_SESSION_PASSIVE));
}
Leave:
if (Flags != DEBUG_END_REENTRANT)
{
LEAVE_ENGINE();
}
return Status;
}
STDMETHODIMP
DebugClient::GetExitCode(
THIS_
OUT PULONG Code
)
{
ENTER_ENGINE();
HRESULT Status;
if (!IS_LIVE_USER_TARGET() || g_CurrentProcess == NULL)
{
Status = E_UNEXPECTED;
}
else
{
Status = ((UserTargetInfo*)g_Target)->m_Services->
GetProcessExitCode(g_CurrentProcess->FullHandle, Code);
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::DispatchCallbacks(
THIS_
IN ULONG Timeout
)
{
DWORD Wait;
// This constitutes interesting activity.
m_LastActivity = time(NULL);
// Do not hold the engine lock while waiting.
for (;;)
{
Wait = WaitForSingleObjectEx(m_DispatchSema, Timeout, TRUE);
if (Wait == WAIT_OBJECT_0)
{
return S_OK;
}
else if (Wait == WAIT_TIMEOUT)
{
return S_FALSE;
}
else if (Wait != WAIT_IO_COMPLETION)
{
return WIN32_LAST_STATUS();
}
}
}
STDMETHODIMP
DebugClient::ExitDispatch(
THIS_
IN PDEBUG_CLIENT Client
)
{
// This method is reentrant.
if (!ReleaseSemaphore(((DebugClient*)(IDebugClientN*)Client)->
m_DispatchSema, 1, NULL))
{
return WIN32_LAST_STATUS();
}
else
{
return S_OK;
}
}
STDMETHODIMP
DebugClient::CreateClient(
THIS_
OUT PDEBUG_CLIENT* Client
)
{
HRESULT Status;
ENTER_ENGINE();
DebugClient* DbgClient = new DebugClient;
if (DbgClient == NULL)
{
Status = E_OUTOFMEMORY;
}
else
{
if ((Status = DbgClient->Initialize()) == S_OK)
{
DbgClient->Link();
*Client = (PDEBUG_CLIENT)(IDebugClientN*)DbgClient;
}
else
{
DbgClient->Release();
}
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::GetInputCallbacks(
THIS_
OUT PDEBUG_INPUT_CALLBACKS* Callbacks
)
{
ENTER_ENGINE();
*Callbacks = m_InputCb;
m_InputCb->AddRef();
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::SetInputCallbacks(
THIS_
IN PDEBUG_INPUT_CALLBACKS Callbacks
)
{
ENTER_ENGINE();
TRANSFER(m_InputCb, Callbacks);
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::GetOutputCallbacks(
THIS_
OUT PDEBUG_OUTPUT_CALLBACKS* Callbacks
)
{
ENTER_ENGINE();
*Callbacks = m_OutputCb;
m_OutputCb->AddRef();
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::SetOutputCallbacks(
THIS_
IN PDEBUG_OUTPUT_CALLBACKS Callbacks
)
{
ENTER_ENGINE();
TRANSFER(m_OutputCb, Callbacks);
CollectOutMasks();
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::GetOutputMask(
THIS_
OUT PULONG Mask
)
{
// This method is reentrant.
*Mask = m_OutMask;
return S_OK;
}
STDMETHODIMP
DebugClient::SetOutputMask(
THIS_
IN ULONG Mask
)
{
// This method is reentrant.
m_OutMask = Mask;
CollectOutMasks();
return S_OK;
}
STDMETHODIMP
DebugClient::GetOtherOutputMask(
THIS_
IN PDEBUG_CLIENT Client,
OUT PULONG Mask
)
{
return Client->GetOutputMask(Mask);
}
STDMETHODIMP
DebugClient::SetOtherOutputMask(
THIS_
IN PDEBUG_CLIENT Client,
IN ULONG Mask
)
{
return Client->SetOutputMask(Mask);
}
STDMETHODIMP
DebugClient::GetOutputWidth(
THIS_
OUT PULONG Columns
)
{
ENTER_ENGINE();
*Columns = m_OutputWidth;
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::SetOutputWidth(
THIS_
IN ULONG Columns
)
{
if (Columns < 1)
{
return E_INVALIDARG;
}
ENTER_ENGINE();
m_OutputWidth = Columns;
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::GetOutputLinePrefix(
THIS_
OUT OPTIONAL PSTR Buffer,
IN ULONG BufferSize,
OUT OPTIONAL PULONG PrefixSize
)
{
HRESULT Status;
ENTER_ENGINE();
Status = FillStringBuffer(m_OutputLinePrefix, 0,
Buffer, BufferSize, PrefixSize);
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::SetOutputLinePrefix(
THIS_
IN OPTIONAL PCSTR Prefix
)
{
HRESULT Status;
ENTER_ENGINE();
ULONG Len;
Status = ChangeString((PSTR*)&m_OutputLinePrefix, &Len, Prefix);
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::GetIdentity(
THIS_
OUT OPTIONAL PSTR Buffer,
IN ULONG BufferSize,
OUT OPTIONAL PULONG IdentitySize
)
{
return FillStringBuffer(m_Identity, 0,
Buffer, BufferSize, IdentitySize);
}
STDMETHODIMP
DebugClient::OutputIdentity(
THIS_
IN ULONG OutputControl,
IN ULONG Flags,
IN PCSTR Format
)
{
HRESULT Status;
if (Flags != DEBUG_OUTPUT_IDENTITY_DEFAULT)
{
return E_INVALIDARG;
}
ENTER_ENGINE();
OutCtlSave OldCtl;
if (!PushOutCtl(OutputControl, this, &OldCtl))
{
Status = E_INVALIDARG;
}
else
{
dprintf(Format, m_Identity);
Status = S_OK;
PopOutCtl(&OldCtl);
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::GetEventCallbacks(
THIS_
OUT PDEBUG_EVENT_CALLBACKS* Callbacks
)
{
ENTER_ENGINE();
*Callbacks = m_EventCb;
m_EventCb->AddRef();
LEAVE_ENGINE();
return S_OK;
}
STDMETHODIMP
DebugClient::SetEventCallbacks(
THIS_
IN PDEBUG_EVENT_CALLBACKS Callbacks
)
{
ENTER_ENGINE();
HRESULT Status;
ULONG Interest;
if (Callbacks != NULL)
{
Status = Callbacks->GetInterestMask(&Interest);
}
else
{
Status = S_OK;
Interest = 0;
}
if (Status == S_OK)
{
TRANSFER(m_EventCb, Callbacks);
m_EventInterest = Interest;
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::FlushCallbacks(
THIS
)
{
::FlushCallbacks();
return S_OK;
}
STDMETHODIMP
DebugClient::WriteDumpFile2(
THIS_
IN PCSTR DumpFile,
IN ULONG Qualifier,
IN ULONG FormatFlags,
IN OPTIONAL PCSTR Comment
)
{
HRESULT Status;
if ((IS_KERNEL_TARGET() &&
(Qualifier < DEBUG_KERNEL_SMALL_DUMP ||
Qualifier > DEBUG_KERNEL_FULL_DUMP)) ||
(IS_USER_TARGET() &&
(Qualifier < DEBUG_USER_WINDOWS_SMALL_DUMP ||
Qualifier > DEBUG_USER_WINDOWS_DUMP)))
{
return E_INVALIDARG;
}
ENTER_ENGINE();
Status = ::WriteDumpFile(DumpFile, Qualifier, FormatFlags, Comment);
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::AddDumpInformationFile(
THIS_
IN PCSTR InfoFile,
IN ULONG Type
)
{
HRESULT Status;
if (Type != DEBUG_DUMP_FILE_PAGE_FILE_DUMP)
{
return E_INVALIDARG;
}
ENTER_ENGINE();
// This method must be called before OpenDumpFile.
if (IS_TARGET_SET())
{
Status = E_UNEXPECTED;
}
else
{
Status = AddDumpInfoFile(InfoFile, DUMP_INFO_PAGE_FILE,
64 * 1024);
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::EndProcessServer(
THIS_
IN ULONG64 Server
)
{
return ((IUserDebugServices*)Server)->
Uninitialize(TRUE);
}
STDMETHODIMP
DebugClient::WaitForProcessServerEnd(
THIS_
IN ULONG Timeout
)
{
HRESULT Status;
ENTER_ENGINE();
if (g_UserServicesUninitialized)
{
Status = S_OK;
}
else
{
//
// This could be done with an event to get true
// waiting but precision isn't that important.
//
HRESULT Status = S_FALSE;
while (Timeout)
{
ULONG UseTimeout;
UseTimeout = min(1000, Timeout);
Sleep(UseTimeout);
if (g_UserServicesUninitialized)
{
Status = S_OK;
break;
}
if (Timeout != INFINITE)
{
Timeout -= UseTimeout;
}
}
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::IsKernelDebuggerEnabled(
THIS
)
{
HRESULT Status;
ENTER_ENGINE();
if (g_DebuggerPlatformId != VER_PLATFORM_WIN32_NT)
{
Status = E_UNEXPECTED;
}
else
{
NTSTATUS NtStatus;
SYSTEM_KERNEL_DEBUGGER_INFORMATION KdInfo;
NtStatus = g_NtDllCalls.
NtQuerySystemInformation(SystemKernelDebuggerInformation,
&KdInfo, sizeof(KdInfo), NULL);
if (NT_SUCCESS(NtStatus))
{
Status = KdInfo.KernelDebuggerEnabled ? S_OK : S_FALSE;
}
else
{
Status = HRESULT_FROM_NT(NtStatus);
}
}
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::TerminateCurrentProcess(
THIS
)
{
HRESULT Status;
ENTER_ENGINE();
Status = SeparateCurrentProcess(SEP_TERMINATE, NULL);
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::DetachCurrentProcess(
THIS
)
{
HRESULT Status;
ENTER_ENGINE();
Status = SeparateCurrentProcess(SEP_DETACH, NULL);
LEAVE_ENGINE();
return Status;
}
STDMETHODIMP
DebugClient::AbandonCurrentProcess(
THIS
)
{
HRESULT Status;
ENTER_ENGINE();
Status = SeparateCurrentProcess(SEP_ABANDON, NULL);
LEAVE_ENGINE();
return Status;
}
HRESULT
DebugClient::Initialize(void)
{
m_DispatchSema = CreateSemaphore(NULL, 0, 0x7fffffff, NULL);
if (m_DispatchSema == NULL)
{
return WIN32_LAST_STATUS();
}
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &m_Thread,
0, FALSE, DUPLICATE_SAME_ACCESS))
{
return WIN32_LAST_STATUS();
}
// If we're requesting input allow this client
// to return input immediately.
if (g_InputSizeRequested > 0)
{
m_InputSequence = 1;
}
return S_OK;
}
void
DebugClient::InitializePrimary(void)
{
m_Flags |= CLIENT_PRIMARY;
if ((m_Flags & CLIENT_REMOTE) == 0)
{
// Can't call GetClientIdentity here as it uses
// many system APIs and therefore can cause trouble
// when debugging system processes such as LSA.
strcpy(m_Identity, "HostMachine\\HostUser");
}
m_LastActivity = time(NULL);
}
void
DebugClient::Link(void)
{
EnterCriticalSection(&g_QuickLock);
// Keep list grouped by thread ID.
DebugClient* Cur;
for (Cur = g_Clients; Cur != NULL; Cur = Cur->m_Next)
{
if (Cur->m_ThreadId == m_ThreadId)
{
break;
}
}
m_Prev = Cur;
if (Cur != NULL)
{
m_Next = Cur->m_Next;
Cur->m_Next = this;
}
else
{
// No ID match so just put it in the front.
m_Next = g_Clients;
g_Clients = this;
}
if (m_Next != NULL)
{
m_Next->m_Prev = this;
}
m_Flags |= CLIENT_IN_LIST;
LeaveCriticalSection(&g_QuickLock);
}
void
DebugClient::Unlink(void)
{
EnterCriticalSection(&g_QuickLock);
m_Flags &= ~CLIENT_IN_LIST;
if (m_Next != NULL)
{
m_Next->m_Prev = m_Prev;
}
if (m_Prev != NULL)
{
m_Prev->m_Next = m_Next;
}
else
{
g_Clients = m_Next;
}
LeaveCriticalSection(&g_QuickLock);
}
//----------------------------------------------------------------------------
//
// Initialize/uninitalize functions.
//
//----------------------------------------------------------------------------
ULONG NTAPI
Win9xDbgPrompt( char *Prompt, char *buffer, ULONG cb)
{
return gets(buffer) ? strlen(buffer) : 0;
}
ULONG __cdecl
Win9xDbgPrint( char *Text, ... )
{
char Temp[OUT_BUFFER_SIZE];
va_list valist;
va_start(valist, Text);
wvsprintf(Temp, Text, valist);
OutputDebugString(Temp);
va_end(valist);
return 0;
}
HRESULT
OneTimeInitialization(void)
{
static BOOL Init = FALSE;
if (Init)
{
return S_OK;
}
// This function is called exactly once at the first
// DebugCreate for a process. It should perform any
// global one-time initialization necessary.
// Nothing initialized here will be explicitly cleaned
// up, instead it should all be the kind of thing
// that can wait for process cleanup.
HRESULT Status = S_OK;
// These sizes are hard-coded into the remoting script
// so verify them to ensure no mismatch.
C_ASSERT(sizeof(DEBUG_BREAKPOINT_PARAMETERS) == 56);
C_ASSERT(sizeof(DEBUG_STACK_FRAME) == 128);
C_ASSERT(sizeof(DEBUG_VALUE) == 32);
C_ASSERT(sizeof(DEBUG_REGISTER_DESCRIPTION) == 32);
C_ASSERT(sizeof(DEBUG_SYMBOL_PARAMETERS) == 32);
C_ASSERT(sizeof(DEBUG_MODULE_PARAMETERS) == 64);
C_ASSERT(sizeof(DEBUG_SPECIFIC_FILTER_PARAMETERS) == 20);
C_ASSERT(sizeof(DEBUG_EXCEPTION_FILTER_PARAMETERS) == 24);
C_ASSERT(sizeof(EXCEPTION_RECORD64) == 152);
C_ASSERT(sizeof(MEMORY_BASIC_INFORMATION64) == 48);
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
g_DumpCacheGranularity = SystemInfo.dwAllocationGranularity;
// Get the debugger host system's OS type. Note that
// this may be different from g_TargetPlatformId, which
// is the OS type of the debug target.
OSVERSIONINFO OsVersionInfo;
OsVersionInfo.dwOSVersionInfoSize = sizeof(OsVersionInfo);
if (!GetVersionEx(&OsVersionInfo))
{
Status = WIN32_LAST_STATUS();
goto EH_Fail;
}
g_DebuggerPlatformId = OsVersionInfo.dwPlatformId;
if (g_DebuggerPlatformId == VER_PLATFORM_WIN32_NT)
{
if ((Status = InitDynamicCalls(&g_NtDllCallsDesc)) != S_OK)
{
goto EH_Fail;
}
}
else
{
g_NtDllCalls.DbgPrint = Win9xDbgPrint;
g_NtDllCalls.DbgPrompt = Win9xDbgPrompt;
}
if ((Status = InitDynamicCalls(&g_Kernel32CallsDesc)) != S_OK)
{
goto EH_Fail;
}
if ((Status = InitDynamicCalls(&g_Advapi32CallsDesc)) != S_OK)
{
goto EH_Fail;
}
ULONG SvcFlags;
if ((Status = g_LiveUserDebugServices.Initialize(&SvcFlags)) != S_OK)
{
goto EH_Fail;
}
g_InputEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_InputEvent == NULL)
{
Status = WIN32_LAST_STATUS();
goto EH_Fail;
}
g_EventStatusWaiting = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_EventStatusWaiting == NULL)
{
Status = WIN32_LAST_STATUS();
goto EH_InputEvent;
}
g_EventStatusReady = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_EventStatusReady == NULL)
{
Status = WIN32_LAST_STATUS();
goto EH_EventStatusWaiting;
}
g_SleepPidEvent = CreatePidEvent(GetCurrentProcessId(), CREATE_NEW);
if (g_SleepPidEvent == NULL)
{
Status = E_FAIL;
goto EH_EventStatusReady;
}
if ((Status = InitializeAllAccessSecObj()) != S_OK)
{
goto EH_SleepPidEvent;
}
__try
{
InitializeCriticalSection(&g_QuickLock);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Status = HRESULT_FROM_NT(GetExceptionCode());
goto EH_AllAccessObj;
}
__try
{
InitializeCriticalSection(&g_EngineLock);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Status = HRESULT_FROM_NT(GetExceptionCode());
goto EH_QuickLock;
}
g_SrcPath = getenv("_NT_SOURCE_PATH");
if (g_SrcPath != NULL)
{
// This path must be in allocated space.
// If this fails it's not catastrophic.
g_SrcPath = _strdup(g_SrcPath);
}
// Initialize default machines. This is to make machine
// information available early for querying. Things
// will get reinitialized every time the true target
// machine type is discovered.
InitializeMachines(IMAGE_FILE_MACHINE_UNKNOWN);
// Set default symbol options.
SymSetOptions(g_SymOptions);
if (getenv("KDQUIET"))
{
g_QuietMode = TRUE;
}
else
{
g_QuietMode = FALSE;
}
ReadDebugOptions(TRUE, NULL);
PCSTR Env;
#if DBG
// Get default out mask from environment variables.
Env = getenv("DBGENG_OUT_MASK");
if (Env != NULL)
{
ULONG Mask = strtoul(Env, NULL, 0);
g_EnvOutMask |= Mask;
g_LogMask |= Mask;
}
#endif
Env = getenv("_NT_DEBUG_HISTORY_SIZE");
if (Env != NULL)
{
g_OutHistoryRequestedSize = atoi(Env) * 1024;
}
InitKdFileAssoc();
Init = TRUE;
return S_OK;
EH_QuickLock:
DeleteCriticalSection(&g_QuickLock);
EH_AllAccessObj:
DeleteAllAccessSecObj();
EH_SleepPidEvent:
CloseHandle(g_SleepPidEvent);
g_SleepPidEvent = NULL;
EH_EventStatusReady:
CloseHandle(g_EventStatusReady);
g_EventStatusReady = NULL;
EH_EventStatusWaiting:
CloseHandle(g_EventStatusWaiting);
g_EventStatusWaiting = NULL;
EH_InputEvent:
CloseHandle(g_InputEvent);
g_InputEvent = NULL;
EH_Fail:
return Status;
}
STDAPI
DebugConnect(
IN PCSTR RemoteOptions,
IN REFIID InterfaceId,
OUT PVOID* Interface
)
{
HRESULT Status;
if ((Status = OneTimeInitialization()) != S_OK)
{
return Status;
}
IUnknown* Client;
if ((Status = DbgRpcConnectServer(RemoteOptions, &IID_IDebugClient,
&Client)) != S_OK)
{
return Status;
}
Status = Client->QueryInterface(InterfaceId, Interface);
Client->Release();
return Status;
}
STDAPI
DebugCreate(
IN REFIID InterfaceId,
OUT PVOID* Interface
)
{
HRESULT Status;
if ((Status = OneTimeInitialization()) != S_OK)
{
return Status;
}
DebugClient* Client = new DebugClient;
if (Client == NULL)
{
Status = E_OUTOFMEMORY;
}
else
{
if ((Status = Client->Initialize()) == S_OK)
{
Status = Client->QueryInterface(InterfaceId, Interface);
if (Status == S_OK)
{
Client->Link();
}
}
Client->Release();
}
return Status;
}
HRESULT
LiveKernelInitialize(DebugClient* Client, ULONG Qual, PCSTR Options)
{
HRESULT Status;
if (g_SessionThread != 0)
{
// A session is already active.
return E_UNEXPECTED;
}
if ((Status = InitNtCmd(Client)) != S_OK)
{
return Status;
}
g_TargetClass = DEBUG_CLASS_KERNEL;
g_TargetClassQualifier = Qual;
if (Qual == DEBUG_KERNEL_CONNECTION)
{
g_Target = &g_ConnLiveKernelTarget;
}
else if (Qual == DEBUG_KERNEL_LOCAL)
{
//
// We need to get the debug privilege to enable local kernel debugging
//
if ((Status = EnableDebugPrivilege()) != S_OK)
{
ErrOut("Unable to enable debug privilege, %s\n \"%s\"\n",
FormatStatusCode(Status), FormatStatus(Status));
return Status;
}
g_Target = &g_LocalLiveKernelTarget;
}
else
{
g_Target = &g_ExdiLiveKernelTarget;
}
// These options only need to stay valid until Initialize.
((LiveKernelTargetInfo*)g_Target)->m_ConnectOptions = Options;
Status = InitializeTarget();
if (Status != S_OK)
{
return Status;
}
if (IS_REMOTE_KERNEL_TARGET())
{
//
// Check environment variables for configuration settings
//
PCHAR CacheEnv = getenv("_NT_DEBUG_CACHE_SIZE");
if (CacheEnv != NULL)
{
g_VirtualCache.m_MaxSize = atol(CacheEnv);
g_PhysicalCache.m_MaxSize = g_VirtualCache.m_MaxSize;
}
g_VirtualCache.m_DecodePTEs = TRUE;
}
// Other target configuration information is retrieved in various
// places during KD init.
dprintf("%s", g_WinKernelVersionString);
if (IS_CONN_KERNEL_TARGET())
{
dprintf("Waiting to reconnect...\n");
}
return S_OK;
}
HRESULT
UserInitialize(DebugClient* Client, ULONG64 Server)
{
HRESULT Status;
PUSER_DEBUG_SERVICES Services;
ULONG Qual;
if ((Status = InitNtCmd(Client)) != S_OK)
{
return Status;
}
if (Server == 0)
{
Services = new LiveUserDebugServices(FALSE);
if (Services == NULL)
{
return E_OUTOFMEMORY;
}
Qual = DEBUG_USER_WINDOWS_PROCESS;
g_Target = &g_LocalUserTarget;
}
else
{
Services = (PUSER_DEBUG_SERVICES)Server;
Services->AddRef();
Qual = DEBUG_USER_WINDOWS_PROCESS_SERVER;
g_Target = &g_RemoteUserTarget;
}
if ((Status = Services->
Initialize(&((UserTargetInfo*)g_Target)->m_ServiceFlags)) == S_OK)
{
g_TargetClass = DEBUG_CLASS_USER_WINDOWS;
g_TargetClassQualifier = Qual;
((UserTargetInfo*)g_Target)->m_Services = Services;
Status = InitializeTarget();
if (Status == S_OK)
{
g_VirtualCache.m_DecodePTEs = FALSE;
dprintf("%s", g_WinUserVersionString);
return S_OK;
}
}
// Error path
if (Qual == DEBUG_USER_WINDOWS_PROCESS)
{
delete(Services);
}
return Status;
}
HRESULT
InitializeTarget(void)
{
HRESULT Status;
DBG_ASSERT(g_SessionThread == 0);
g_SessionThread = GetCurrentThreadId();
if ((Status = g_Target->Initialize()) != S_OK)
{
DiscardTarget(DEBUG_SESSION_END_SESSION_PASSIVE);
}
return Status;
}
HRESULT
InitializeMachine(ULONG Machine)
{
HRESULT Status;
// Dump initialization initializes machines so
// don't reinitialize them.
if (g_TargetMachineType == IMAGE_FILE_MACHINE_UNKNOWN)
{
InitializeMachines(Machine);
}
SetEffMachine(Machine, TRUE);
// Executing machine is not set as code execution
// status is unknown. The executing machine will
// be updated when a wait completes.
Status = BreakpointInit();
if (Status != S_OK)
{
InitializeMachines(IMAGE_FILE_MACHINE_UNKNOWN);
SetEffMachine(IMAGE_FILE_MACHINE_UNKNOWN, TRUE);
return Status;
}
// X86 prefers registers to be displayed at the prompt unless
// we're on a kernel connection where it would force a context
// load all the time.
if (Machine == IMAGE_FILE_MACHINE_I386 &&
(IS_DUMP_TARGET() || IS_USER_TARGET()))
{
g_OciOutputRegs = TRUE;
}
g_MachineInitialized = TRUE;
//
// Load extensions after this is set so Extensions can query information
// during machine initialization
//
LoadMachineExtensions();
// Now that all initialization is done, send initial
// notification that a debuggee exists.
NotifySessionStatus(DEBUG_SESSION_ACTIVE);
NotifyChangeDebuggeeState(DEBUG_CDS_ALL, 0);
NotifyExtensions(DEBUG_NOTIFY_SESSION_ACTIVE, 0);
return S_OK;
}
void
DiscardTarget(ULONG Reason)
{
if (g_MachineInitialized)
{
DiscardMachine(Reason);
}
g_Target->Uninitialize();
g_SessionThread = 0;
g_TargetClass = DEBUG_CLASS_UNINITIALIZED;
g_Target = &g_UnexpectedTarget;
g_TargetClassQualifier = 0;
g_ThreadToResume = NULL;
g_GlobalProcOptions = 0;
g_NextProcessUserId = 0;
g_EngStatus = 0;
g_EngDefer = 0;
g_EngErr = 0;
g_OutHistRead = NULL;
g_OutHistWrite = NULL;
g_OutHistoryMask = 0;
g_OutHistoryUsed = 0;
}
void
DiscardMachine(ULONG Reason)
{
g_MachineInitialized = FALSE;
g_CmdState = 'i';
g_ExecutionStatusRequest = DEBUG_STATUS_NO_CHANGE;
PPROCESS_INFO Process;
for (Process = g_ProcessHead;
Process != NULL;
Process = Process->Next)
{
Process->Exited = TRUE;
}
// Breakpoint removal must wait until all processes are marked as
// exited to avoid asserts on breakpoints that are inserted.
RemoveAllBreakpoints(Reason);
DeleteExitedInfos();
DiscardPendingProcesses();
g_NumUnloadedModules = 0;
g_VirtualCache.SetForceDecodePtes(FALSE);
DiscardLastEvent();
ClearEventLog();
ZeroMemory(&g_LastEventInfo, sizeof(g_LastEventInfo));
g_EventProcess = NULL;
g_EventThread = NULL;
g_CurrentProcess = NULL;
ResetImplicitData();
g_OciOutputRegs = FALSE;
DbgKdApi64 = FALSE;
ZeroMemory(&KdDebuggerData, sizeof(KdDebuggerData));
g_KdMaxPacketType = 0;
g_KdMaxStateChange = 0;
g_KdMaxManipulate = 0;
g_SystemVersion = SVER_INVALID;
g_ActualSystemVersion = SVER_INVALID;
g_TargetCheckedBuild = 0;
g_TargetBuildNumber = 0;
g_TargetServicePackString[0] = 0;
g_TargetServicePackNumber = 0;
g_TargetPlatformId = 0;
g_TargetBuildLabName[0] = 0;
InitializeMachines(IMAGE_FILE_MACHINE_UNKNOWN);
g_TargetExecMachine = IMAGE_FILE_MACHINE_UNKNOWN;
SetEffMachine(IMAGE_FILE_MACHINE_UNKNOWN, FALSE);
g_TargetNumberProcessors = 0;
EXTDLL* Ext = g_ExtDlls;
EXTDLL* ExtNext;
while (Ext != NULL)
{
ExtNext = Ext->Next;
if (!Ext->UserLoaded)
{
UnloadExtensionDll(Ext);
}
else
{
DeferExtensionDll(Ext);
}
Ext = ExtNext;
}
free(g_ExtensionSearchPath);
g_ExtensionSearchPath = NULL;
g_WatchBeginCurFunc = 1;
g_WatchEndCurFunc = 0;
g_WatchTrace = FALSE;
g_WatchInitialSP = 0;
g_StepTraceInRangeStart = (ULONG64)-1;
g_StepTraceInRangeEnd = 0;
g_EngStatus &= ~(ENG_STATUS_SUSPENDED |
ENG_STATUS_BREAKPOINTS_INSERTED |
ENG_STATUS_PROCESSES_ADDED |
ENG_STATUS_STATE_CHANGED |
ENG_STATUS_MODULES_LOADED |
ENG_STATUS_PREPARED_FOR_CALLS |
ENG_STATUS_NO_AUTO_WAIT |
ENG_STATUS_PENDING_BREAK_IN |
ENG_STATUS_AT_INITIAL_BREAK |
ENG_STATUS_AT_INITIAL_MODULE_LOAD |
ENG_STATUS_EXIT_CURRENT_WAIT |
ENG_STATUS_USER_INTERRUPT);
g_EngDefer &= ~(ENG_DEFER_EXCEPTION_HANDLING |
ENG_DEFER_UPDATE_CONTROL_SET |
ENG_DEFER_HARDWARE_TRACING |
ENG_DEFER_OUTPUT_CURRENT_INFO |
ENG_DEFER_CONTINUE_EVENT);
g_EngErr &= ~(ENG_ERR_DEBUGGER_DATA);
g_SwitchProcessor = 0;
g_LastSelector = -1;
g_RegContextThread = NULL;
g_RegContextProcessor = -1;
ULONG i;
for (i = 0; i < MACHIDX_COUNT; i++)
{
if (g_AllMachines[i] != NULL)
{
g_AllMachines[i]->InvalidateContext();
}
}
if (IS_CONN_KERNEL_TARGET())
{
g_DbgKdTransport->Restart();
}
::FlushCallbacks();
// Send final notification that debuggee is gone.
// This must be done after all the work as the lock
// will be suspended during the callbacks, allowing
// other threads in, so the state must be consistent.
NotifyChangeEngineState(DEBUG_CES_EXECUTION_STATUS,
DEBUG_STATUS_NO_DEBUGGEE, TRUE);
NotifySessionStatus(Reason);
NotifyExtensions(DEBUG_NOTIFY_SESSION_INACTIVE, 0);
}
//----------------------------------------------------------------------------
//
// DbgRpcClientObject implementation.
//
//----------------------------------------------------------------------------
HRESULT
DebugClient::Initialize(PSTR Identity, PVOID* Interface)
{
HRESULT Status;
m_Flags |= CLIENT_REMOTE;
if ((Status = Initialize()) != S_OK)
{
return Status;
}
strcpy(m_Identity, Identity);
*Interface = (IDebugClientN*)this;
return S_OK;
}
void
DebugClient::Finalize(void)
{
Link();
// Take a reference on this object for the RPC client
// thread to hold.
AddRef();
}
void
DebugClient::Uninitialize(void)
{
// Directly destroy the client object rather than releasing
// as the remote client may have exited without politely
// cleaning up references.
Destroy();
}