This patch merges `IOHandlerProcessSTDIO` and `IOHandlerProcessSTDIOWindows` in one file: `ProcessIOHandler.h`. This is an NFC change which reduces the size of `Process.cpp` and allows to reuse `IOHandlerProcessSTDIOWindows` in GDBRemote, ahead of https://github.com/llvm/llvm-project/pull/201638.
303 lines
9.4 KiB
C++
303 lines
9.4 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|