blob: e4f752057d539df2ed578a33af20d55520572b2d [file] [edit]
//===----------------------------------------------------------------------===//
//
// 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