| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lldb/Target/ProcessIOHandler.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Host/Terminal.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/SelectHelper.h" | |
| #include "lldb/Utility/State.h" | |
| #include "lldb/Utility/Status.h" | |
| using namespace lldb_private; | |
| IOHandlerProcessSTDIO::IOHandlerProcessSTDIO(Process *process, int write_fd) | |
| : IOHandler(process->GetTarget().GetDebugger(), IOHandler::Type::ProcessIO), | |
| m_process(process), | |
| m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false), | |
| m_write_file(write_fd, File::eOpenOptionWriteOnly, false) { | |
| m_pipe.CreateNew(); | |
| } | |
| void IOHandlerProcessSTDIO::SetIsRunning(bool running) { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| SetIsDone(!running); | |
| m_is_running = running; | |
| } | |
| // Each IOHandler gets to run until it is done. It should read data from the | |
| // "in" and place output into "out" and "err and return when done. | |
| void IOHandlerProcessSTDIO::Run() { | |
| if (!m_read_file.IsValid() || !m_write_file.IsValid() || !m_pipe.CanRead() || | |
| !m_pipe.CanWrite()) { | |
| SetIsDone(true); | |
| return; | |
| } | |
| SetIsDone(false); | |
| const int read_fd = m_read_file.GetDescriptor(); | |
| Terminal terminal(read_fd); | |
| TerminalState terminal_state(terminal, false); | |
| // FIXME: error handling? | |
| llvm::consumeError(terminal.SetCanonical(false)); | |
| llvm::consumeError(terminal.SetEcho(false)); | |
| // FD_ZERO, FD_SET are not supported on windows | |
| #ifndef _WIN32 | |
| const int pipe_read_fd = m_pipe.GetReadFileDescriptor(); | |
| SetIsRunning(true); | |
| while (true) { | |
| { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| if (GetIsDone()) | |
| break; | |
| } | |
| SelectHelper select_helper; | |
| select_helper.FDSetRead(read_fd); | |
| select_helper.FDSetRead(pipe_read_fd); | |
| Status error = select_helper.Select(); | |
| if (error.Fail()) | |
| break; | |
| char ch = 0; | |
| size_t n; | |
| if (select_helper.FDIsSetRead(read_fd)) { | |
| n = 1; | |
| if (m_read_file.Read(&ch, n).Success() && n == 1) { | |
| if (m_write_file.Write(&ch, n).Fail() || n != 1) | |
| break; | |
| } else | |
| break; | |
| } | |
| if (select_helper.FDIsSetRead(pipe_read_fd)) { | |
| // Consume the interrupt byte | |
| if (llvm::Expected<size_t> bytes_read = m_pipe.Read(&ch, 1)) { | |
| if (ch == 'q') | |
| break; | |
| if (ch == 'i') | |
| if (StateIsRunningState(m_process->GetState())) | |
| m_process->SendAsyncInterrupt(); | |
| } else { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Process), bytes_read.takeError(), | |
| "Pipe read failed: {0}"); | |
| } | |
| } | |
| } | |
| SetIsRunning(false); | |
| #endif | |
| } | |
| void IOHandlerProcessSTDIO::Cancel() { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| SetIsDone(true); | |
| // Only write to our pipe to cancel if we are in | |
| // IOHandlerProcessSTDIO::Run(). We can end up with a python command that | |
| // is being run from the command interpreter: | |
| // | |
| // (lldb) step_process_thousands_of_times | |
| // | |
| // In this case the command interpreter will be in the middle of handling | |
| // the command and if the process pushes and pops the IOHandler thousands | |
| // of times, we can end up writing to m_pipe without ever consuming the | |
| // bytes from the pipe in IOHandlerProcessSTDIO::Run() and end up | |
| // deadlocking when the pipe gets fed up and blocks until data is consumed. | |
| if (m_is_running) { | |
| char ch = 'q'; // Send 'q' for quit | |
| if (llvm::Error err = m_pipe.Write(&ch, 1).takeError()) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Process), std::move(err), | |
| "Pipe write failed: {0}"); | |
| } | |
| } | |
| } | |
| bool IOHandlerProcessSTDIO::Interrupt() { | |
| // Do only things that are safe to do in an interrupt context (like in a | |
| // SIGINT handler), like write 1 byte to a file descriptor. This will | |
| // interrupt the IOHandlerProcessSTDIO::Run() and we can look at the byte | |
| // that was written to the pipe and then call | |
| // m_process->SendAsyncInterrupt() from a much safer location in code. | |
| if (m_active) { | |
| char ch = 'i'; // Send 'i' for interrupt | |
| return !errorToBool(m_pipe.Write(&ch, 1).takeError()); | |
| } else { | |
| // This IOHandler might be pushed on the stack, but not being run | |
| // currently so do the right thing if we aren't actively watching for | |
| // STDIN by sending the interrupt to the process. Otherwise the write to | |
| // the pipe above would do nothing. This can happen when the command | |
| // interpreter is running and gets a "expression ...". It will be on the | |
| // IOHandler thread and sending the input is complete to the delegate | |
| // which will cause the expression to run, which will push the process IO | |
| // handler, but not run it. | |
| if (StateIsRunningState(m_process->GetState())) { | |
| m_process->SendAsyncInterrupt(); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| #ifdef _WIN32 | |
| #include "lldb/Host/windows/windows.h" | |
| IOHandlerProcessSTDIOWindows::IOHandlerProcessSTDIOWindows(Process *process) | |
| : IOHandler(process->GetTarget().GetDebugger(), IOHandler::Type::ProcessIO), | |
| m_process(process), | |
| m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false), | |
| m_interrupt_event( | |
| CreateEvent(/*lpEventAttributes=*/nullptr, /*bManualReset=*/FALSE, | |
| /*bInitialState=*/FALSE, /*lpName=*/nullptr)) {} | |
| IOHandlerProcessSTDIOWindows::~IOHandlerProcessSTDIOWindows() { | |
| if (m_interrupt_event != INVALID_HANDLE_VALUE) | |
| ::CloseHandle(m_interrupt_event); | |
| } | |
| void IOHandlerProcessSTDIOWindows::SetIsRunning(bool running) { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| SetIsDone(!running); | |
| m_is_running = running; | |
| } | |
| llvm::Expected<bool> | |
| IOHandlerProcessSTDIOWindows::ConsoleHasTextInput(const HANDLE hStdin) { | |
| // Check if there are already characters buffered. Pressing enter counts as | |
| // 2 characters '\r\n' and only one of them is a keyDown event. | |
| DWORD bytesAvailable = 0; | |
| if (PeekNamedPipe(hStdin, nullptr, 0, nullptr, &bytesAvailable, nullptr)) { | |
| if (bytesAvailable > 0) | |
| return true; | |
| } | |
| while (true) { | |
| INPUT_RECORD inputRecord; | |
| DWORD numRead = 0; | |
| if (!PeekConsoleInput(hStdin, &inputRecord, 1, &numRead)) | |
| return llvm::createStringError("failed to peek standard input"); | |
| if (numRead == 0) | |
| return false; | |
| if (inputRecord.EventType == KEY_EVENT && | |
| inputRecord.Event.KeyEvent.bKeyDown && | |
| inputRecord.Event.KeyEvent.uChar.AsciiChar != 0) | |
| return true; | |
| if (!ReadConsoleInput(hStdin, &inputRecord, 1, &numRead)) | |
| return llvm::createStringError("failed to read standard input"); | |
| } | |
| } | |
| void IOHandlerProcessSTDIOWindows::Run() { | |
| if (!m_read_file.IsValid()) { | |
| SetIsDone(true); | |
| return; | |
| } | |
| SetIsDone(false); | |
| SetIsRunning(true); | |
| HANDLE hStdin = m_read_file.GetWaitableHandle(); | |
| HANDLE waitHandles[2] = {hStdin, m_interrupt_event}; | |
| DWORD consoleMode; | |
| bool isConsole = GetConsoleMode(hStdin, &consoleMode) != 0; | |
| // With ENABLE_LINE_INPUT, ReadFile returns only when a carriage return is | |
| // read. This will block lldb in ReadFile until the user hits enter. Save | |
| // the previous console mode to restore it later and remove | |
| // ENABLE_LINE_INPUT. | |
| DWORD oldConsoleMode = consoleMode; | |
| SetConsoleMode(hStdin, consoleMode & ~ENABLE_LINE_INPUT & ~ENABLE_ECHO_INPUT); | |
| while (true) { | |
| { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| if (GetIsDone()) | |
| goto exit_loop; | |
| } | |
| DWORD result = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); | |
| switch (result) { | |
| case WAIT_FAILED: | |
| goto exit_loop; | |
| case WAIT_OBJECT_0: { | |
| if (isConsole) { | |
| auto hasInputOrErr = ConsoleHasTextInput(hStdin); | |
| if (!hasInputOrErr) { | |
| Log *log = GetLog(LLDBLog::Process); | |
| LLDB_LOG_ERROR(log, hasInputOrErr.takeError(), | |
| "failed to process debuggee's IO: {0}"); | |
| goto exit_loop; | |
| } | |
| // If no text input is ready, go back to waiting. | |
| if (!*hasInputOrErr) | |
| continue; | |
| } | |
| char ch = 0; | |
| DWORD read = 0; | |
| if (!ReadFile(hStdin, &ch, 1, &read, nullptr) || read != 1) | |
| goto exit_loop; | |
| Status err; | |
| m_process->PutSTDIN(&ch, 1, err); | |
| if (err.Fail()) | |
| goto exit_loop; | |
| break; | |
| } | |
| case WAIT_OBJECT_0 + 1: { | |
| ControlOp op = m_pending_op.exchange(eControlOpNone); | |
| if (op == eControlOpQuit) | |
| goto exit_loop; | |
| if (op == eControlOpInterrupt && | |
| StateIsRunningState(m_process->GetState())) | |
| m_process->SendAsyncInterrupt(); | |
| break; | |
| } | |
| default: | |
| goto exit_loop; | |
| } | |
| } | |
| exit_loop:; | |
| SetIsRunning(false); | |
| SetIsDone(true); | |
| SetConsoleMode(hStdin, oldConsoleMode); | |
| } | |
| void IOHandlerProcessSTDIOWindows::Cancel() { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| SetIsDone(true); | |
| if (m_is_running) { | |
| m_pending_op.store(eControlOpQuit); | |
| ::SetEvent(m_interrupt_event); | |
| } | |
| } | |
| bool IOHandlerProcessSTDIOWindows::Interrupt() { | |
| if (m_active) { | |
| m_pending_op.store(eControlOpInterrupt); | |
| ::SetEvent(m_interrupt_event); | |
| return true; | |
| } | |
| if (StateIsRunningState(m_process->GetState())) { | |
| m_process->SendAsyncInterrupt(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| #endif // _WIN32 |