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

960 lines
24 KiB
C++

//----------------------------------------------------------------------------
//
// Console input and output.
//
// Copyright (C) Microsoft Corporation, 1999-2000.
//
//----------------------------------------------------------------------------
#include "pch.cpp"
#pragma hdrstop
#include <stdarg.h>
#include <process.h>
#include "conio.hpp"
#include "engine.hpp"
#include "main.hpp"
#define CONTROL_A 1
#define CONTROL_B 2
#define CONTROL_D 4
#define CONTROL_E 5
#define CONTROL_F 6
#define CONTROL_K 11
#define CONTROL_P 16
#define CONTROL_R 18
#define CONTROL_V 22
#define CONTROL_W 23
#define CONTROL_X 24
HANDLE g_ConInput, g_ConOutput;
HANDLE g_PromptInput;
HANDLE g_AllowInput;
ConInputCallbacks g_ConInputCb;
ConOutputCallbacks g_ConOutputCb;
BOOL g_ConInitialized;
char g_Buffer[MAX_COMMAND];
LONG g_Lines;
HANDLE g_PipeWrite;
OVERLAPPED g_PipeWriteOverlapped;
CRITICAL_SECTION g_InputLock;
BOOL g_InputStarted;
// Input thread interfaces for direct input thread calls.
IDebugClient* g_ConClient;
IDebugControl* g_ConControl;
//----------------------------------------------------------------------------
//
// Default input callbacks implementation, provides IUnknown.
//
//----------------------------------------------------------------------------
STDMETHODIMP
DefInputCallbacks::QueryInterface(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface
)
{
*Interface = NULL;
if (IsEqualIID(InterfaceId, IID_IUnknown) ||
IsEqualIID(InterfaceId, IID_IDebugInputCallbacks))
{
*Interface = (IDebugInputCallbacks *)this;
AddRef();
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG)
DefInputCallbacks::AddRef(
THIS
)
{
// This class is designed to be static so
// there's no true refcount.
return 1;
}
STDMETHODIMP_(ULONG)
DefInputCallbacks::Release(
THIS
)
{
// This class is designed to be static so
// there's no true refcount.
return 0;
}
//----------------------------------------------------------------------------
//
// Console input callbacks.
//
//----------------------------------------------------------------------------
STDMETHODIMP
ConInputCallbacks::StartInput(
THIS_
IN ULONG BufferSize
)
{
if (g_IoMode == IO_NONE)
{
// Ignore input requests.
return S_OK;
}
EnterCriticalSection(&g_InputLock);
if (g_ConControl == NULL)
{
// If we're not remoted we aren't running a separate input
// thread so we need to block here until we get some input.
while (!ConIn(g_Buffer, sizeof(g_Buffer), TRUE))
{
; // Wait.
}
g_DbgControl->ReturnInput(g_Buffer);
}
else if (ConIn(g_Buffer, sizeof(g_Buffer), FALSE))
{
g_ConControl->ReturnInput(g_Buffer);
}
else
{
g_InputStarted = TRUE;
#ifndef KERNEL
// Wake up the input thread if necessary.
SetEvent(g_AllowInput);
#endif
}
LeaveCriticalSection(&g_InputLock);
return S_OK;
}
STDMETHODIMP
ConInputCallbacks::EndInput(
THIS
)
{
g_InputStarted = FALSE;
return S_OK;
}
//----------------------------------------------------------------------------
//
// Default output callbacks implementation, provides IUnknown.
//
//----------------------------------------------------------------------------
STDMETHODIMP
DefOutputCallbacks::QueryInterface(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface
)
{
*Interface = NULL;
if (IsEqualIID(InterfaceId, IID_IUnknown) ||
IsEqualIID(InterfaceId, IID_IDebugOutputCallbacks))
{
*Interface = (IDebugOutputCallbacks *)this;
AddRef();
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG)
DefOutputCallbacks::AddRef(
THIS
)
{
// This class is designed to be static so
// there's no true refcount.
return 1;
}
STDMETHODIMP_(ULONG)
DefOutputCallbacks::Release(
THIS
)
{
// This class is designed to be static so
// there's no true refcount.
return 0;
}
//----------------------------------------------------------------------------
//
// Console output callbacks.
//
//----------------------------------------------------------------------------
STDMETHODIMP
ConOutputCallbacks::Output(
THIS_
IN ULONG Mask,
IN PCSTR Text
)
{
ConOutStr(Text);
return S_OK;
}
//----------------------------------------------------------------------------
//
// Functions
//
//----------------------------------------------------------------------------
void
InitializeIo(PCSTR InputFile)
{
__try
{
InitializeCriticalSection(&g_InputLock);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
ErrorExit("Unable to initialize lock\n");
}
// The input file may not exist so there's no
// check for failure.
g_InputFile = fopen(InputFile, "r");
}
void
CreateConsole(void)
{
if (g_ConInitialized)
{
return;
}
// Set this early to prevent an init call from Exit in
// case an Exit call is made inside this routine.
g_ConInitialized = TRUE;
#ifdef INHERIT_CONSOLE
g_ConInput = GetStdHandle(STD_INPUT_HANDLE);
g_ConOutput = GetStdHandle(STD_OUTPUT_HANDLE);
#else
SECURITY_ATTRIBUTES Security;
if (!AllocConsole())
{
ErrorExit("AllocConsole failed, %d\n", GetLastError());
}
ZeroMemory(&Security, sizeof(Security));
Security.nLength = sizeof(Security);
Security.bInheritHandle = TRUE;
g_ConInput = CreateFile( "CONIN$",
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&Security,
OPEN_EXISTING,
0,
NULL
);
if (g_ConInput == INVALID_HANDLE_VALUE)
{
ErrorExit("Create CONIN$ failed, %d\n", GetLastError());
}
g_ConOutput = CreateFile( "CONOUT$",
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&Security,
OPEN_EXISTING,
0,
NULL
);
if (g_ConOutput == INVALID_HANDLE_VALUE)
{
ErrorExit("Create CONOUT$ failed, %d\n", GetLastError());
}
#endif
g_PromptInput = g_ConInput;
}
void
ReadPromptInputChars(PSTR Buffer, ULONG BufferSize)
{
ULONG Len;
ULONG Read;
// Reading from another source. Read character by
// character until a line is read.
Len = 0;
while (Len < BufferSize)
{
if (!ReadFile(g_PromptInput, &Buffer[Len], sizeof(Buffer[0]),
&Read, NULL) ||
Read != sizeof(Buffer[0]))
{
OutputDebugString("Unable to read input\n");
ExitDebugger(E_FAIL);
}
if (Buffer[Len] == '\n')
{
InterlockedDecrement(&g_Lines);
break;
}
// Ignore carriage returns.
if (Buffer[Len] != '\r')
{
// Prevent buffer overflow.
if (Len == BufferSize - 1)
{
break;
}
Len++;
}
}
Buffer[Len] = '\0';
}
BOOL
CheckForControlCommands(PDEBUG_CLIENT Client, PDEBUG_CONTROL Control,
char Char)
{
HRESULT Hr;
ULONG OutMask;
PCHAR DebugAction;
ULONG EngOptions;
switch(Char)
{
case CONTROL_B:
case CONTROL_X:
if (!g_RemoteClient && Client != NULL)
{
// Force servers to get cleaned up.
Client->EndSession(DEBUG_END_REENTRANT);
}
ExitProcess(S_OK);
case CONTROL_F:
//
// Force a breakin like Ctrl-C would do.
// The advantage is this will work when kd is being debugged.
//
Control->SetInterrupt(DEBUG_INTERRUPT_ACTIVE);
return TRUE;
case CONTROL_P:
// Launch cdb on this debugger.
char PidStr[32];
sprintf(PidStr, "\"cdb -p %d\"", GetCurrentProcessId());
_spawnlp(_P_NOWAIT,
"cmd.exe", "/c", "start",
"remote", "/s", PidStr, "cdb_pipe",
NULL);
return TRUE;
case CONTROL_V:
Client->GetOtherOutputMask(g_DbgClient, &OutMask);
OutMask ^= DEBUG_OUTPUT_VERBOSE;
Client->SetOtherOutputMask(g_DbgClient, OutMask);
Control->SetLogMask(OutMask);
ConOut("Verbose mode %s.\n",
(OutMask & DEBUG_OUTPUT_VERBOSE) ? "ON" : "OFF");
return TRUE;
case CONTROL_W:
Hr = Control->OutputVersionInformation(DEBUG_OUTCTL_AMBIENT);
if (Hr == HRESULT_FROM_WIN32(ERROR_BUSY))
{
ConOut("Engine is busy, try again\n");
}
else if (Hr != S_OK)
{
ConOut("Unable to show version information, 0x%X\n", Hr);
}
return TRUE;
#ifdef KERNEL
case CONTROL_A:
Client->SetKernelConnectionOptions("cycle_speed");
return TRUE;
case CONTROL_D:
Client->GetOtherOutputMask(g_DbgClient, &OutMask);
OutMask ^= DEBUG_IOUTPUT_KD_PROTOCOL;
Client->SetOtherOutputMask(g_DbgClient, OutMask);
Control->SetLogMask(OutMask);
return TRUE;
case CONTROL_K:
//
// Toggle between the following possibilities-
//
// (0) no breakin
// (1) -b style (same as Control-C up the wire)
// (2) -d style (stop on first dll load).
//
// NB -b and -d could both be on the command line
// but become mutually exclusive via this method.
// (Maybe should be a single enum type).
//
Control->GetEngineOptions(&EngOptions);
if (EngOptions & DEBUG_ENGOPT_INITIAL_BREAK)
{
//
// Was type 1, go to type 2.
//
EngOptions |= DEBUG_ENGOPT_INITIAL_MODULE_BREAK;
EngOptions &= ~DEBUG_ENGOPT_INITIAL_BREAK;
DebugAction = "breakin on first symbol load";
}
else if (EngOptions & DEBUG_ENGOPT_INITIAL_MODULE_BREAK)
{
//
// Was type 2, go to type 0.
//
EngOptions &= ~DEBUG_ENGOPT_INITIAL_MODULE_BREAK;
DebugAction = "NOT breakin";
}
else
{
//
// Was type 0, go to type 1.
//
EngOptions |= DEBUG_ENGOPT_INITIAL_BREAK;
DebugAction = "request initial breakpoint";
}
Control->SetEngineOptions(EngOptions);
ConOut("Will %s at next boot.\n", DebugAction);
return TRUE;
case CONTROL_R:
Client->SetKernelConnectionOptions("resync");
return TRUE;
#endif // #ifdef KERNEL
}
return FALSE;
}
BOOL
ConIn(PSTR Buffer, ULONG BufferSize, BOOL Wait)
{
if (g_InitialCommand != NULL)
{
ConOut("%s: Reading initial command '%s'\n",
g_DebuggerName, g_InitialCommand);
strncpy(Buffer, g_InitialCommand, BufferSize);
Buffer[BufferSize - 1] = 0;
g_InitialCommand = NULL;
return TRUE;
}
while (g_InputFile && g_InputFile != stdin)
{
if (fgets(Buffer, BufferSize, g_InputFile))
{
ConOut("%s", Buffer);
Buffer[strlen(Buffer) - 1] = 0;
return TRUE;
}
else
{
fclose(g_InputFile);
if (g_NextOldInputFile > 0)
{
g_InputFile = g_OldInputFiles[--g_NextOldInputFile];
}
else
{
g_InputFile = stdin;
}
}
}
if (g_InputFile == NULL)
{
g_InputFile = stdin;
}
switch(g_IoMode)
{
case IO_NONE:
return FALSE;
case IO_DEBUG:
if (!Wait)
{
return FALSE;
}
g_NtDllCalls.DbgPrompt("", Buffer,
min(BufferSize, MAX_DBG_PROMPT_COMMAND));
break;
case IO_CONSOLE:
ULONG Len;
if (g_PromptInput == g_ConInput)
{
if (!Wait)
{
return FALSE;
}
// Reading from the console so we can assume we'll
// read a line.
for (;;)
{
if (!ReadFile(g_PromptInput, Buffer, BufferSize, &Len, NULL))
{
OutputDebugString("Unable to read input\n");
ExitDebugger(E_FAIL);
}
// At a minimum a read should have CRLF. If it
// doesn't assume that something weird happened
// and ignore the read.
if (Len >= 2)
{
break;
}
Sleep(50);
}
// Remove CR LF.
Len -= 2;
Buffer[Len] = '\0';
// Edit out any special characters.
for (ULONG i = 0; i < Len; i++)
{
if (CheckForControlCommands(g_DbgClient, g_DbgControl,
Buffer[i]))
{
Buffer[i] = ' ';
}
}
}
else
{
#ifndef KERNEL
if (g_Lines == 0)
{
// Allow the input thread to read the console.
SetEvent(g_AllowInput);
}
#endif
while (g_Lines == 0)
{
if (!Wait)
{
return FALSE;
}
// Wait for the input thread to notify us that
// a line of input is available. While we're waiting,
// let the engine process callbacks provoked by
// other clients.
HRESULT Hr = g_DbgClient->DispatchCallbacks(INFINITE);
if (Hr != S_OK)
{
OutputDebugString("Unable to dispatch callbacks\n");
ExitDebugger(Hr);
}
// Some other client may have started execution in
// which case we want to stop waiting for input.
if (g_ExecStatus != DEBUG_STATUS_BREAK)
{
#ifndef KERNEL
// XXX drewb - Need a way to turn input off.
#endif
return FALSE;
}
}
ReadPromptInputChars(Buffer, BufferSize);
}
break;
}
return TRUE;
}
void
ConOutStr(PCSTR Str)
{
switch(g_IoMode)
{
case IO_NONE:
// Throw it away.
break;
case IO_DEBUG:
//
// Send the output to the kernel debugger but note that we
// want any control C processing to be done locally rather
// than in the kernel.
//
if (g_NtDllCalls.DbgPrint("%s", Str) == STATUS_BREAKPOINT &&
g_DbgControl != NULL)
{
g_DbgControl->SetInterrupt(DEBUG_INTERRUPT_PASSIVE);
}
break;
case IO_CONSOLE:
if (g_ConOutput != NULL)
{
ULONG Written;
WriteFile(g_ConOutput, Str, strlen(Str), &Written, NULL);
}
else
{
OutputDebugString(Str);
}
break;
}
}
void
ConOut(PCSTR Format, ...)
{
DWORD Len;
va_list Args;
// If no attempt has been made to create a console
// go ahead and try now.
if (g_IoMode == IO_CONSOLE && !g_ConInitialized)
{
CreateConsole();
}
va_start(Args, Format);
Len = _vsnprintf(g_Buffer, sizeof(g_Buffer), Format, Args);
if (Len == -1)
{
Len = sizeof(g_Buffer);
g_Buffer[sizeof(g_Buffer) - 1] = 0;
}
va_end(Args);
ConOutStr(g_Buffer);
}
void
ExitDebugger(ULONG Code)
{
if (g_DbgClient != NULL && !g_RemoteClient)
{
g_DbgClient->EndSession(DEBUG_END_PASSIVE);
// Force servers to get cleaned up.
g_DbgClient->EndSession(DEBUG_END_REENTRANT);
}
ExitProcess(Code);
}
void
ErrorExit(PCSTR Format, ...)
{
if (Format != NULL)
{
DWORD Len;
va_list Args;
// If no attempt has been made to create a console
// go ahead and try now.
if (g_IoRequested == IO_CONSOLE && !g_ConInitialized)
{
CreateConsole();
}
va_start(Args, Format);
Len = _vsnprintf(g_Buffer, sizeof(g_Buffer), Format, Args);
va_end(Args);
if (g_ConOutput != NULL)
{
WriteFile(g_ConOutput, g_Buffer, Len, &Len, NULL);
}
else
{
OutputDebugString(g_Buffer);
}
}
#ifndef INHERIT_CONSOLE
if (g_IoRequested == IO_CONSOLE)
{
ConOut("%s: exiting - press enter ---", g_DebuggerName);
g_InitialCommand = NULL;
g_InputFile = NULL;
ConIn(g_Buffer, sizeof(g_Buffer), TRUE);
}
#endif
ExitDebugger(E_FAIL);
}
DWORD WINAPI
InputThreadLoop(PVOID Param)
{
DWORD Read;
BOOL Status;
UCHAR Char;
BOOL NewLine = TRUE;
BOOL SpecialChar = FALSE;
HANDLE ConIn = g_ConInput;
HRESULT Hr;
BOOL ShowInputError = TRUE;
// Create interfaces usable on this thread.
if ((Hr = g_DbgClient->CreateClient(&g_ConClient) != S_OK) ||
(Hr = g_ConClient->QueryInterface(IID_IDebugControl,
(void **)&g_ConControl)) != S_OK)
{
ConOut("%s: Unable to create input thread interfaces, 0x%X\n",
g_DebuggerName, Hr);
if (!g_RemoteClient)
{
// Force servers to get cleaned up.
g_DbgClient->EndSession(DEBUG_END_REENTRANT);
}
ExitProcess(E_FAIL);
}
//
// Capture all typed input immediately.
// Stuff the characters into an anonymous pipe, from which
// ConIn will read them.
//
for (;;)
{
#ifndef KERNEL
// The debugger should only read the console when the
// debuggee isn't running to avoid eating up input
// intended for the debuggee.
if (!g_RemoteClient && NewLine)
{
if (WaitForSingleObject(g_AllowInput,
INFINITE) != WAIT_OBJECT_0)
{
ConOut("%s: Failed to wait for input window, %d\n",
GetLastError());
}
NewLine = FALSE;
}
#endif
Status = ReadFile(ConIn, &Char, sizeof(Char), &Read, NULL);
if (!Status || Read != sizeof(Char))
{
if (ShowInputError &&
GetLastError() != ERROR_OPERATION_ABORTED &&
GetLastError() != ERROR_IO_PENDING)
{
ConOut("%s: Could not read from console, %d\n",
g_DebuggerName, GetLastError());
ShowInputError = FALSE;
}
// The most common cause of a console read failure
// is killing remote with @K. Give things some
// time to kill this process.
// If this is a remote server it's possible that
// the debugger was run without a valid console
// and is just being accessed via remoting.
// Sleep longer in that case since errors will
// probably always occur.
Sleep(!g_RemoteClient && g_RemoteOptions != NULL ?
1000 : 50);
continue;
}
// We successfully got some input so if it
// fails later we should show a fresh error.
ShowInputError = TRUE;
if (CheckForControlCommands(g_ConClient, g_ConControl, Char))
{
SpecialChar = TRUE;
continue;
}
if (SpecialChar && Char == '\r')
{
// If we get a CR immediately after a special
// char turn it into a space so that it doesn't cause
// a command repeat.
Char = ' ';
}
SpecialChar = FALSE;
ULONG Len;
Status = WriteFile(g_PipeWrite, &Char, sizeof(Char), &Len,
&g_PipeWriteOverlapped);
if (!Status && GetLastError() != ERROR_IO_PENDING)
{
ConOut("%s: Could not write to pipe, %d\n",
g_DebuggerName, GetLastError());
}
else if (Char == '\n')
{
EnterCriticalSection(&g_InputLock);
InterlockedIncrement(&g_Lines);
// If input is needed send it directly
// to the engine.
if (g_InputStarted)
{
ReadPromptInputChars(g_Buffer, sizeof(g_Buffer));
g_ConControl->ReturnInput(g_Buffer);
g_InputStarted = FALSE;
}
else
{
// Wake up the engine thread when a line of
// input is present.
g_ConClient->ExitDispatch(g_DbgClient);
}
LeaveCriticalSection(&g_InputLock);
NewLine = TRUE;
}
}
return 0;
}
void
CreateInputThread(void)
{
HANDLE Thread;
DWORD ThreadId;
CHAR PipeName[256];
if (g_PipeWrite != NULL)
{
// Input thread already exists.
return;
}
#ifndef KERNEL
g_AllowInput = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_AllowInput == NULL)
{
ErrorExit("Unable to create input event, %d\n", GetLastError());
}
#endif
_snprintf(PipeName,
sizeof(PipeName),
"\\\\.\\pipe\\Dbg%d",
GetCurrentProcessId());
g_PipeWrite = CreateNamedPipe(PipeName,
PIPE_ACCESS_DUPLEX |
FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE |
PIPE_WAIT,
2,
2000,
2000,
NMPWAIT_WAIT_FOREVER,
NULL);
if (g_PipeWrite == INVALID_HANDLE_VALUE)
{
ErrorExit("Failed to create input pipe, %d\n",
GetLastError());
}
g_PromptInput = CreateFile(PipeName,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (g_PromptInput == INVALID_HANDLE_VALUE)
{
ErrorExit("Failed to create read pipe, %d\n",
GetLastError());
}
Thread = CreateThread(NULL,
16000, // THREAD_STACK_SIZE
InputThreadLoop,
NULL,
THREAD_SET_INFORMATION,
&ThreadId);
if (Thread == NULL)
{
ErrorExit("Failed to create input thread, %d\n",
GetLastError());
}
else
{
if (!SetThreadPriority(Thread, THREAD_PRIORITY_ABOVE_NORMAL))
{
ErrorExit("Failed to raise the input thread priority, %d\n",
GetLastError());
}
}
CloseHandle(Thread);
// Wait for thread initialization. Callbacks are
// already registered so we need to dispatch them
// while waiting.
while (g_ConControl == NULL)
{
g_DbgClient->DispatchCallbacks(50);
}
}