llvm-project/lldb/source/Host/windows/ConnectionConPTYWindows.cpp
Charles Zablit 796a1ea79f
[lldb][windows] fix a race condition when closing the ConPTY (#182302)
This patch fixes a race condition when closing the ConPTY of a process
on Windows.

The read operation in `ConnectionGenericFile::Read` is asynchronous
because the ConPTY does not work with synchronous reads. However, this
is prone to a race condition if the ConPTY is closed between the
`ReadFile` and the `WaitForMultipleObjects`. This eventually leads to
the data being truncated, i.e some of the STDOUT is missing.

The fix is to introduce a mutex which prevents the ConPTY from closing
while a Read loop is in progress.

At the end of the pipe, `WaitForMultipleObjects` hangs until it receives
a `ERROR_BROKEN_PIPE` event, which is signaled when closing the ConPTY.
It's important to close the Read thread before closing the ConPTY,
otherwise the thread deadlocks waiting for the `ERROR_BROKEN_PIPE` which
is never sent because the ConPTY is waiting for the thread to exit
before closing.

All of this logic is encapsulated in the `ConnectionConPTY` class which
is a subclass of `ConnectionGenericFile`. `ConnectionConPTY` only
supports reading from the ConPTY. To write to it,
`IOHandlerProcessSTDIOWindows` should be used instead.

This patch depends on:
- https://github.com/llvm/llvm-project/pull/182536

This patch replaces https://github.com/llvm/llvm-project/pull/182109.

---------

Co-authored-by: Nerixyz <nero.9@hotmail.de>
2026-02-23 17:21:06 +00:00

88 lines
3.3 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/Host/windows/ConnectionConPTYWindows.h"
#include "lldb/Utility/Status.h"
using namespace lldb;
using namespace lldb_private;
/// Strips the ConPTY initialization sequences that Windows unconditionally
/// emits when a process is first attached to a pseudo console.
///
/// These are emitted by ConPTY's host process (conhost.exe) at process attach
/// time, not by the debuggee. They are always the first bytes on the output
/// pipe and are always present as a contiguous prefix.
///
/// \param dst Buffer containing the data read from the ConPTY output pipe.
/// Modified in place: if the initialization sequences are present
/// as a prefix, they are removed by shifting the remaining bytes
/// to the front of the buffer.
/// \param dst_len The size of \p dst.
/// \param len On input, the number of valid bytes in \p dst. On output,
/// reduced by the number of bytes stripped.
/// \return
/// \p true if the sequence was found and stripped.
static bool StripConPTYInitSequences(void *dst, size_t dst_len, size_t &len) {
static const char sequences[] = "\x1b[?9001l\x1b[?1004l";
static const size_t sequences_len = sizeof(sequences) - 1;
char *buf = static_cast<char *>(dst);
if (len >= sequences_len) {
assert(dst_len >= len - sequences_len);
if (memcmp(buf, sequences, sequences_len) == 0) {
memmove(buf, buf + sequences_len, len - sequences_len);
len -= sequences_len;
return true;
}
}
return false;
}
ConnectionConPTY::ConnectionConPTY(std::shared_ptr<PseudoConsole> pty)
: m_pty(pty), ConnectionGenericFile(pty->GetSTDOUTHandle(), false) {};
ConnectionConPTY::~ConnectionConPTY() {}
lldb::ConnectionStatus ConnectionConPTY::Connect(llvm::StringRef s,
Status *error_ptr) {
if (m_pty->IsConnected())
return eConnectionStatusSuccess;
return eConnectionStatusNoConnection;
}
lldb::ConnectionStatus ConnectionConPTY::Disconnect(Status *error_ptr) {
m_pty->Close();
return eConnectionStatusSuccess;
}
size_t ConnectionConPTY::Read(void *dst, size_t dst_len,
const Timeout<std::micro> &timeout,
lldb::ConnectionStatus &status,
Status *error_ptr) {
std::unique_lock<std::mutex> guard(m_pty->GetMutex());
if (m_pty->IsStopping()) {
m_pty->GetCV().wait(guard, [this] { return !m_pty->IsStopping(); });
}
size_t bytes_read =
ConnectionGenericFile::Read(dst, dst_len, timeout, status, error_ptr);
if (bytes_read > 0 && !m_pty_vt_sequence_was_stripped) {
if (StripConPTYInitSequences(dst, dst_len, bytes_read))
m_pty_vt_sequence_was_stripped = true;
}
return bytes_read;
}
size_t ConnectionConPTY::Write(const void *src, size_t src_len,
lldb::ConnectionStatus &status,
Status *error_ptr) {
llvm_unreachable("not implemented");
}