2019-03-08 05:23:21 +08:00
|
|
|
//===-- IOStream.cpp --------------------------------------------*- 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 "IOStream.h"
|
|
|
|
|
[lldb-vscode] redirect stderr/stdout to the IDE's console
In certain occasions times, like when LLDB is initializing and
evaluating the .lldbinit files, it tries to print to stderr and stdout
directly. This confuses the IDE with malformed data, as it talks to
lldb-vscode using stdin and stdout following the JSON RPC protocol. This
ends up terminating the debug session with the user unaware of what's
going on. There might be other situations in which this can happen, and
they will be harder to debug than the .lldbinit case.
After several discussions with @clayborg, @yinghuitan and @aadsm, we
realized that the best course of action is to simply redirect stdout and
stderr to the console, without modifying LLDB itself. This will prove to
be resilient to future bugs or features.
I made the simplest possible redirection logic I could come up with. It
only works for POSIX, and to make it work with Windows should be merely
changing pipe and dup2 for the windows equivalents like _pipe and _dup2.
Sadly I don't have a Windows machine, so I'll do it later once my office
reopens, or maybe someone else can do it.
I'm intentionally not adding a stop-redirecting logic, as I don't see it
useful for the lldb-vscode case (why would we want to do that, really?).
I added a test.
Note: this is a simpler version of D80659. I first tried to implement a
RIIA version of it, but it was problematic to manage the state of the
thread and reverting the redirection came with some non trivial
complexities, like what to do with unflushed data after the debug
session has finished on the IDE's side.
2021-04-22 05:20:17 +08:00
|
|
|
#if defined(_WIN32)
|
2019-03-08 05:23:21 +08:00
|
|
|
#include <io.h>
|
|
|
|
#else
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <fstream>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
using namespace lldb_vscode;
|
|
|
|
|
|
|
|
StreamDescriptor::StreamDescriptor() {}
|
|
|
|
|
|
|
|
StreamDescriptor::StreamDescriptor(StreamDescriptor &&other) {
|
|
|
|
*this = std::move(other);
|
|
|
|
}
|
|
|
|
|
|
|
|
StreamDescriptor::~StreamDescriptor() {
|
|
|
|
if (!m_close)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_is_socket)
|
2021-02-05 02:07:07 +08:00
|
|
|
#if defined(_WIN32)
|
2019-03-08 05:23:21 +08:00
|
|
|
::closesocket(m_socket);
|
|
|
|
#else
|
|
|
|
::close(m_socket);
|
|
|
|
#endif
|
|
|
|
else
|
|
|
|
::close(m_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
StreamDescriptor &StreamDescriptor::operator=(StreamDescriptor &&other) {
|
|
|
|
m_close = other.m_close;
|
|
|
|
other.m_close = false;
|
|
|
|
m_is_socket = other.m_is_socket;
|
|
|
|
if (m_is_socket)
|
|
|
|
m_socket = other.m_socket;
|
|
|
|
else
|
|
|
|
m_fd = other.m_fd;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
StreamDescriptor StreamDescriptor::from_socket(SOCKET s, bool close) {
|
|
|
|
StreamDescriptor sd;
|
|
|
|
sd.m_is_socket = true;
|
|
|
|
sd.m_socket = s;
|
|
|
|
sd.m_close = close;
|
|
|
|
return sd;
|
|
|
|
}
|
|
|
|
|
|
|
|
StreamDescriptor StreamDescriptor::from_file(int fd, bool close) {
|
|
|
|
StreamDescriptor sd;
|
|
|
|
sd.m_is_socket = false;
|
|
|
|
sd.m_fd = fd;
|
|
|
|
sd.m_close = close;
|
|
|
|
return sd;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool OutputStream::write_full(llvm::StringRef str) {
|
|
|
|
while (!str.empty()) {
|
|
|
|
int bytes_written = 0;
|
|
|
|
if (descriptor.m_is_socket)
|
|
|
|
bytes_written = ::send(descriptor.m_socket, str.data(), str.size(), 0);
|
|
|
|
else
|
|
|
|
bytes_written = ::write(descriptor.m_fd, str.data(), str.size());
|
|
|
|
|
|
|
|
if (bytes_written < 0) {
|
|
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
|
|
continue;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
str = str.drop_front(bytes_written);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool InputStream::read_full(std::ofstream *log, size_t length,
|
|
|
|
std::string &text) {
|
|
|
|
std::string data;
|
|
|
|
data.resize(length);
|
|
|
|
|
|
|
|
char *ptr = &data[0];
|
|
|
|
while (length != 0) {
|
2019-03-09 01:36:54 +08:00
|
|
|
int bytes_read = 0;
|
2019-03-08 05:23:21 +08:00
|
|
|
if (descriptor.m_is_socket)
|
|
|
|
bytes_read = ::recv(descriptor.m_socket, ptr, length, 0);
|
|
|
|
else
|
|
|
|
bytes_read = ::read(descriptor.m_fd, ptr, length);
|
|
|
|
|
[lldb] Handle EOF from `lldb-vscode`
Sometimes (when running lldb-vscode under strace) I get:
read(0, "", 16) = 0
read(0, "", 16) = 0
read(0, "", 16) = 0
...
With this patch testcases finish properly even with strace:
read(0, "", 16) = 0
futex(0x1346508, FUTEX_WAKE_PRIVATE, 2147483647) = 0
stat("", 0x7ffe8f2634c8) = -1 ENOENT (No such file or directory)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=9124, si_uid=1001, si_status=SIGINT, si_utime=1, si_stime=0} ---
close(4) = 0
exit_group(0) = ?
+++ exited with 0 +++
Differential Revision: https://reviews.llvm.org/D64698
llvm-svn: 366187
2019-07-16 14:34:44 +08:00
|
|
|
if (bytes_read == 0) {
|
|
|
|
if (log)
|
|
|
|
*log << "End of file (EOF) reading from input file.\n";
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-08 05:23:21 +08:00
|
|
|
if (bytes_read < 0) {
|
|
|
|
int reason = 0;
|
2021-02-05 02:07:07 +08:00
|
|
|
#if defined(_WIN32)
|
2019-03-08 05:23:21 +08:00
|
|
|
if (descriptor.m_is_socket)
|
|
|
|
reason = WSAGetLastError();
|
|
|
|
else
|
|
|
|
reason = errno;
|
|
|
|
#else
|
|
|
|
reason = errno;
|
|
|
|
if (reason == EINTR || reason == EAGAIN)
|
|
|
|
continue;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (log)
|
|
|
|
*log << "Error " << reason << " reading from input file.\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-09 01:36:54 +08:00
|
|
|
assert(bytes_read >= 0 && (size_t)bytes_read <= length);
|
2019-03-08 05:23:21 +08:00
|
|
|
ptr += bytes_read;
|
|
|
|
length -= bytes_read;
|
|
|
|
}
|
|
|
|
text += data;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool InputStream::read_line(std::ofstream *log, std::string &line) {
|
|
|
|
line.clear();
|
|
|
|
while (true) {
|
|
|
|
if (!read_full(log, 1, line))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (llvm::StringRef(line).endswith("\r\n"))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
line.erase(line.size() - 2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool InputStream::read_expected(std::ofstream *log, llvm::StringRef expected) {
|
|
|
|
std::string result;
|
|
|
|
if (!read_full(log, expected.size(), result))
|
|
|
|
return false;
|
|
|
|
if (expected != result) {
|
|
|
|
if (log)
|
|
|
|
*log << "Warning: Expected '" << expected.str() << "', got '" << result
|
|
|
|
<< "\n";
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|