//---------------------------------------------------------------------------- // // Console input and output. // // Copyright (C) Microsoft Corporation, 1999-2000. // //---------------------------------------------------------------------------- #include "pch.cpp" #pragma hdrstop #include #include #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); } }