[Lldb-commits] [lldb] [lldb-dap] Restore the override FD used by the output redirect on stop. (PR #129964)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Wed Mar 5 17:15:03 PST 2025
https://github.com/ashgti created https://github.com/llvm/llvm-project/pull/129964
While running lldb-dap over stdin/stdout the `stdout` and `stderr` FD's are replaced with a pipe that is reading the output to forward to the dap client. During shutdown we were not properly restoring those FDs, which means if any component attempted to write to stderr it would trigger a SIGPIPE due to the pipe being closed during the shutdown process. This can happen if we have an error reported from the `DAP::Loop` call that would then log to stderr, such as an error parsing a malformed DAP message or if lldb-dap crashed and it was trying to write the stack trace to stderr.
There is one place we were not handling an `llvm::Error` if there was no logging setup that could trigger this condition.
To address this, I updated the OutputRedirector to restore the FD to the prior state when `Stop` is called.
>From bcecf0c2aa0d398478734f24a9913cb398167e66 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 6 Mar 2025 02:05:46 +0100
Subject: [PATCH] [lldb-dap] Restore the override FD used by the output
redirect on stop.
While running lldb-dap over stdin/stdout the `stdout` and `stderr` FD's are replaced with a pipe that is reading the output to forward to the dap client. During shutdown we were not properly restoring those FDs, which means if any component attempted to write to stderr it would trigger a SIGPIPE due to the pipe being closed during the shutdown process. This can happen if we have an error reported from the `DAP::Loop` call that would then log to stderr, such as an error parsing a malformed DAP message or if lldb-dap crashed and it was trying to write the stack trace to stderr.
There is one place we were not handling an `llvm::Error` if there was no logging setup that could trigger this condition.
To address this, I updated the OutputRedirector to restore the FD to the prior state when `Stop` is called.
---
lldb/test/API/tools/lldb-dap/io/TestDAP_io.py | 25 ++++++------
lldb/tools/lldb-dap/DAP.cpp | 40 ++++---------------
lldb/tools/lldb-dap/OutputRedirector.cpp | 31 +++++++++++++-
lldb/tools/lldb-dap/OutputRedirector.h | 5 ++-
4 files changed, 53 insertions(+), 48 deletions(-)
diff --git a/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py b/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
index a39bd17ceb3b3..04414cd7a3cdf 100644
--- a/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
+++ b/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
@@ -18,18 +18,19 @@ def cleanup():
# If the process is still alive, terminate it.
if process.poll() is None:
process.terminate()
- stdout_data = process.stdout.read()
- stderr_data = process.stderr.read()
- print("========= STDOUT =========")
- print(stdout_data)
- print("========= END =========")
- print("========= STDERR =========")
- print(stderr_data)
- print("========= END =========")
- print("========= DEBUG ADAPTER PROTOCOL LOGS =========")
- with open(log_file_path, "r") as file:
- print(file.read())
- print("========= END =========")
+ process.wait()
+ stdout_data = process.stdout.read()
+ stderr_data = process.stderr.read()
+ print("========= STDOUT =========")
+ print(stdout_data)
+ print("========= END =========")
+ print("========= STDERR =========")
+ print(stderr_data)
+ print("========= END =========")
+ print("========= DEBUG ADAPTER PROTOCOL LOGS =========")
+ with open(log_file_path, "r") as file:
+ print(file.read())
+ print("========= END =========")
# Execute the cleanup function during test case tear down.
self.addTearDownHook(cleanup)
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index b21b83a79aec7..1f7b25e7c5bcc 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -195,34 +195,16 @@ ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) {
in = lldb::SBFile(std::fopen(DEV_NULL, "r"), /*transfer_ownership=*/true);
- if (auto Error = out.RedirectTo([this](llvm::StringRef output) {
+ if (auto Error = out.RedirectTo(overrideOut, [this](llvm::StringRef output) {
SendOutput(OutputType::Stdout, output);
}))
return Error;
- if (overrideOut) {
- auto fd = out.GetWriteFileDescriptor();
- if (auto Error = fd.takeError())
- return Error;
-
- if (dup2(*fd, fileno(overrideOut)) == -1)
- return llvm::errorCodeToError(llvm::errnoAsErrorCode());
- }
-
- if (auto Error = err.RedirectTo([this](llvm::StringRef output) {
+ if (auto Error = err.RedirectTo(overrideErr, [this](llvm::StringRef output) {
SendOutput(OutputType::Stderr, output);
}))
return Error;
- if (overrideErr) {
- auto fd = err.GetWriteFileDescriptor();
- if (auto Error = fd.takeError())
- return Error;
-
- if (dup2(*fd, fileno(overrideErr)) == -1)
- return llvm::errorCodeToError(llvm::errnoAsErrorCode());
- }
-
return llvm::Error::success();
}
@@ -729,15 +711,11 @@ PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
llvm::StringRef json_sref(json);
llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
- if (!json_value) {
- auto error = json_value.takeError();
- if (log) {
- std::string error_str;
- llvm::raw_string_ostream strm(error_str);
- strm << error;
+ if (auto error = json_value.takeError()) {
+ std::string error_str = llvm::toString(std::move(error));
+ if (log)
*log << "error: failed to parse JSON: " << error_str << std::endl
<< json << std::endl;
- }
return PacketStatus::JSONMalformed;
}
@@ -848,10 +826,6 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
SendTerminatedEvent();
- // Stop forwarding the debugger output and error handles.
- out.Stop();
- err.Stop();
-
disconnecting = true;
return error;
@@ -859,8 +833,8 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
llvm::Error DAP::Loop() {
auto cleanup = llvm::make_scope_exit([this]() {
- if (output.descriptor)
- output.descriptor->Close();
+ out.Stop();
+ err.Stop();
StopEventHandlers();
});
while (!disconnecting) {
diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp
index 79321aebe9aac..5da3839973706 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.cpp
+++ b/lldb/tools/lldb-dap/OutputRedirector.cpp
@@ -27,7 +27,9 @@ namespace lldb_dap {
int OutputRedirector::kInvalidDescriptor = -1;
-OutputRedirector::OutputRedirector() : m_fd(kInvalidDescriptor) {}
+OutputRedirector::OutputRedirector()
+ : m_fd(kInvalidDescriptor), m_original_fd(kInvalidDescriptor),
+ m_restore_fd(kInvalidDescriptor) {}
Expected<int> OutputRedirector::GetWriteFileDescriptor() {
if (m_fd == kInvalidDescriptor)
@@ -36,7 +38,8 @@ Expected<int> OutputRedirector::GetWriteFileDescriptor() {
return m_fd;
}
-Error OutputRedirector::RedirectTo(std::function<void(StringRef)> callback) {
+Error OutputRedirector::RedirectTo(std::FILE *override,
+ std::function<void(StringRef)> callback) {
assert(m_fd == kInvalidDescriptor && "Output readirector already started.");
int new_fd[2];
@@ -52,6 +55,19 @@ Error OutputRedirector::RedirectTo(std::function<void(StringRef)> callback) {
int read_fd = new_fd[0];
m_fd = new_fd[1];
+
+ if (override) {
+ int override_fd = fileno(override);
+
+ // Backup the FD to restore once redirection is complete.
+ m_original_fd = override_fd;
+ m_restore_fd = dup(override_fd);
+
+ // Override the existing fd the new write end of the pipe.
+ if (::dup2(m_fd, override_fd) == -1)
+ return llvm::errorCodeToError(llvm::errnoAsErrorCode());
+ }
+
m_forwarder = std::thread([this, callback, read_fd]() {
char buffer[OutputBufferSize];
while (!m_stopped) {
@@ -92,6 +108,17 @@ void OutputRedirector::Stop() {
(void)::write(fd, kCloseSentinel.data(), kCloseSentinel.size());
::close(fd);
m_forwarder.join();
+
+ // Restore the fd back to its original state since we stopped the
+ // redirection.
+ if (m_restore_fd != kInvalidDescriptor &&
+ m_original_fd != kInvalidDescriptor) {
+ int restore_fd = m_restore_fd;
+ m_restore_fd = kInvalidDescriptor;
+ int original_fd = m_original_fd;
+ m_original_fd = kInvalidDescriptor;
+ ::dup2(restore_fd, original_fd);
+ }
}
}
diff --git a/lldb/tools/lldb-dap/OutputRedirector.h b/lldb/tools/lldb-dap/OutputRedirector.h
index a47ea96f71f14..45571c0d5f344 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.h
+++ b/lldb/tools/lldb-dap/OutputRedirector.h
@@ -27,7 +27,8 @@ class OutputRedirector {
/// \return
/// \a Error::success if the redirection was set up correctly, or an error
/// otherwise.
- llvm::Error RedirectTo(std::function<void(llvm::StringRef)> callback);
+ llvm::Error RedirectTo(std::FILE *overrideFile,
+ std::function<void(llvm::StringRef)> callback);
llvm::Expected<int> GetWriteFileDescriptor();
void Stop();
@@ -41,6 +42,8 @@ class OutputRedirector {
private:
std::atomic<bool> m_stopped = false;
int m_fd;
+ int m_original_fd;
+ int m_restore_fd;
std::thread m_forwarder;
};
More information about the lldb-commits
mailing list