zturner created this revision.
zturner added a reviewer: clayborg.
Herald added a subscriber: mgorny.

For historical reasons, Windows unfortunately doesn't support using sockets in 
standard system calls like read/write, which means that they also can't be 
buffered with a `FILE*`.  This violates some fundamental assumptions of how 
lldb-vscode was written, so fixing it requires some replumbing of the guts.  I 
introduced an abstraction called `IOStream` which basically knows whether the 
descriptor is a socket or a non-socket, and then delegates to the appropriate 
underlying APIs accordingly.  This means calling `read/write` on `stdin/stdout` 
and calling `recv/send` on sockets.  Luckily this same strategy will also work 
on non-Windows platforms, so the abstractions needed aren't too egregious.


https://reviews.llvm.org/D59104

Files:
  lldb/tools/lldb-vscode/CMakeLists.txt
  lldb/tools/lldb-vscode/IOStream.cpp
  lldb/tools/lldb-vscode/IOStream.h
  lldb/tools/lldb-vscode/VSCode.cpp
  lldb/tools/lldb-vscode/VSCode.h
  lldb/tools/lldb-vscode/lldb-vscode.cpp
  lldb/tools/lldb-vscode/package.json

Index: lldb/tools/lldb-vscode/package.json
===================================================================
--- lldb/tools/lldb-vscode/package.json
+++ lldb/tools/lldb-vscode/package.json
@@ -56,6 +56,9 @@
 					]
 				},
 				"program": "./bin/lldb-vscode",
+				"windows": {
+					"program": "./bin/lldb-vscode.exe"
+				},
 				"configurationAttributes": {
 					"launch": {
 						"required": [
Index: lldb/tools/lldb-vscode/lldb-vscode.cpp
===================================================================
--- lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -23,6 +23,7 @@
 #define NOMINMAX
 #include <windows.h>
 #undef GetObject
+#include <io.h>
 #else
 #include <netinet/in.h>
 #include <sys/socket.h>
@@ -68,9 +69,9 @@
 
 enum VSCodeBroadcasterBits { eBroadcastBitStopEventThread = 1u << 0 };
 
-int AcceptConnection(int portno) {
+SOCKET AcceptConnection(int portno) {
   // Accept a socket connection from any host on "portno".
-  int newsockfd = -1;
+  SOCKET newsockfd = -1;
   struct sockaddr_in serv_addr, cli_addr;
   SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (sockfd < 0) {
@@ -2635,23 +2636,19 @@
 #endif
       int portno = atoi(arg);
       printf("Listening on port %i...\n", portno);
-      int socket_fd = AcceptConnection(portno);
+      SOCKET socket_fd = AcceptConnection(portno);
       if (socket_fd >= 0) {
-        // We must open two FILE objects, one for reading and one for writing
-        // the FILE objects have a mutex in them that won't allow reading and
-        // writing to the socket stream.
-        g_vsc.in = fdopen(socket_fd, "r");
-        g_vsc.out = fdopen(socket_fd, "w");
-        if (g_vsc.in == nullptr || g_vsc.out == nullptr) {
-          if (g_vsc.log)
-            *g_vsc.log << "fdopen failed (" << strerror(errno) << ")"
-                       << std::endl;
-          exit(1);
-        }
+        g_vsc.input.descriptor = StreamDescriptor::from_socket(socket_fd, true);
+        g_vsc.output.descriptor =
+            StreamDescriptor::from_socket(socket_fd, false);
       } else {
         exit(1);
       }
     }
+  } else {
+    g_vsc.input.descriptor = StreamDescriptor::from_file(fileno(stdin), false);
+    g_vsc.output.descriptor =
+        StreamDescriptor::from_file(fileno(stdout), false);
   }
   auto request_handlers = GetRequestHandlers();
   uint32_t packet_idx = 0;
Index: lldb/tools/lldb-vscode/VSCode.h
===================================================================
--- lldb/tools/lldb-vscode/VSCode.h
+++ lldb/tools/lldb-vscode/VSCode.h
@@ -19,6 +19,7 @@
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
 
 #include "lldb/API/SBAttachInfo.h"
 #include "lldb/API/SBBreakpoint.h"
@@ -43,6 +44,7 @@
 
 #include "ExceptionBreakpoint.h"
 #include "FunctionBreakpoint.h"
+#include "IOStream.h"
 #include "SourceBreakpoint.h"
 #include "SourceReference.h"
 
@@ -62,8 +64,8 @@
 enum class OutputType { Console, Stdout, Stderr, Telemetry };
 
 struct VSCode {
-  FILE *in;
-  FILE *out;
+  InputStream input;
+  OutputStream output;
   lldb::SBDebugger debugger;
   lldb::SBTarget target;
   lldb::SBAttachInfo attach_info;
@@ -94,8 +96,6 @@
   ~VSCode();
   VSCode(const VSCode &rhs) = delete;
   void operator=(const VSCode &rhs) = delete;
-  void CloseInputStream();
-  void CloseOutputStream();
   int64_t GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const;
   ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter);
   ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id);
Index: lldb/tools/lldb-vscode/VSCode.cpp
===================================================================
--- lldb/tools/lldb-vscode/VSCode.cpp
+++ lldb/tools/lldb-vscode/VSCode.cpp
@@ -10,12 +10,15 @@
 #include <fstream>
 #include <mutex>
 
-#include "VSCode.h"
 #include "LLDBUtils.h"
+#include "VSCode.h"
+#include "llvm/Support/FormatVariadic.h"
 
 #if defined(_WIN32)
-#include <io.h>
+#define NOMINMAX
+#include <Windows.h>
 #include <fcntl.h>
+#include <io.h>
 #endif
 
 using namespace lldb_vscode;
@@ -31,15 +34,15 @@
 VSCode g_vsc;
 
 VSCode::VSCode()
-    : in(stdin), out(stdout), launch_info(nullptr), variables(),
-      broadcaster("lldb-vscode"), num_regs(0), num_locals(0), num_globals(0),
-      log(), exception_breakpoints(
-                 {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus},
-                  {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus},
-                  {"objc_catch", "Objective C Catch", lldb::eLanguageTypeObjC},
-                  {"objc_throw", "Objective C Throw", lldb::eLanguageTypeObjC},
-                  {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift},
-                  {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}),
+    : launch_info(nullptr), variables(), broadcaster("lldb-vscode"),
+      num_regs(0), num_locals(0), num_globals(0), log(),
+      exception_breakpoints(
+          {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus},
+           {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus},
+           {"objc_catch", "Objective C Catch", lldb::eLanguageTypeObjC},
+           {"objc_throw", "Objective C Throw", lldb::eLanguageTypeObjC},
+           {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift},
+           {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}),
       focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
       stop_at_entry(false) {
   const char *log_file_path = getenv("LLDBVSCODE_LOG");
@@ -55,22 +58,6 @@
 }
 
 VSCode::~VSCode() {
-  CloseInputStream();
-  CloseOutputStream();
-}
-
-void VSCode::CloseInputStream() {
-  if (in != stdin) {
-    fclose(in);
-    in = nullptr;
-  }
-}
-
-void VSCode::CloseOutputStream() {
-  if (out != stdout) {
-    fclose(out);
-    out = nullptr;
-  }
 }
 
 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const {
@@ -103,9 +90,11 @@
 // JSON bytes.
 //----------------------------------------------------------------------
 void VSCode::SendJSON(const std::string &json_str) {
-  fprintf(out, "Content-Length: %u\r\n\r\n%s", (uint32_t)json_str.size(),
-          json_str.c_str());
-  fflush(out);
+  output.write_full("Content-Length: ");
+  output.write_full(llvm::utostr(json_str.size()));
+  output.write_full("\r\n\r\n");
+  output.write_full(json_str);
+
   if (log) {
     *log << "<-- " << std::endl
          << "Content-Length: " << json_str.size() << "\r\n\r\n"
@@ -130,44 +119,25 @@
 // Read a JSON packet from the "in" stream.
 //----------------------------------------------------------------------
 std::string VSCode::ReadJSON() {
-  static std::string header("Content-Length: ");
-
-  uint32_t packet_len = 0;
+  std::string length_str;
   std::string json_str;
-  char line[1024];
-
-  while (fgets(line, sizeof(line), in)) {
-    if (strncmp(line, header.data(), header.size()) == 0) {
-      packet_len = atoi(line + header.size());
-      if (fgets(line, sizeof(line), in)) {
-        if (!IsEmptyLine(line))
-          if (log)
-            *log << "warning: expected empty line but got: \"" << line << "\""
-                 << std::endl;
-        break;
-      }
-    } else {
-      if (log)
-        *log << "warning: expected \"" << header << "\" but got: \"" << line
-             << "\"" << std::endl;
-    }
-  }
-  // This is followed by two windows newline sequences ("\r\n\r\n") so eat
-  // two the newline sequences
-  if (packet_len > 0) {
-    json_str.resize(packet_len);
-    auto bytes_read = fread(&json_str[0], 1, packet_len, in);
-    if (bytes_read < packet_len) {
-      if (log)
-        *log << "error: read fewer bytes (" << bytes_read
-             << ") than requested (" << packet_len << ")" << std::endl;
-      json_str.erase(bytes_read);
-    }
-    if (log) {
-      *log << "--> " << std::endl;
-      *log << header << packet_len << "\r\n\r\n" << json_str << std::endl;
-    }
-  }
+  int length;
+
+  if (!input.read_expected(log.get(), "Content-Length: "))
+    return json_str;
+
+  if (!input.read_line(log.get(), length_str))
+    return json_str;
+
+  if (!llvm::to_integer(length_str, length))
+    return json_str;
+
+  if (!input.read_expected(log.get(), "\r\n"))
+    return json_str;
+
+  if (!input.read_full(log.get(), length, json_str))
+    return json_str;
+
   return json_str;
 }
 
Index: lldb/tools/lldb-vscode/IOStream.h
===================================================================
--- /dev/null
+++ lldb/tools/lldb-vscode/IOStream.h
@@ -0,0 +1,69 @@
+//===-- IOStream.h ----------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDBVSCODE_IOSTREAM_H_
+#define LLDBVSCODE_IOSTREAM_H_
+
+#if defined(_WIN32)
+// We need to #define NOMINMAX in order to skip `min()` and `max()` macro
+// definitions that conflict with other system headers.
+// We also need to #undef GetObject (which is defined to GetObjectW) because
+// the JSON code we use also has methods named `GetObject()` and we conflict
+// against these.
+#define NOMINMAX
+#include <windows.h>
+#else
+#include <sys/socket.h>
+#endif
+
+#include "llvm/ADT/StringRef.h"
+
+#include <fstream>
+#include <string>
+
+// Windows requires different system calls for dealing with sockets and other
+// types of files, so we can't simply have one code path that just uses read
+// and write everywhere.  So we need an abstraction in order to allow us to
+// treat them identically.
+namespace lldb_vscode {
+struct StreamDescriptor {
+  StreamDescriptor();
+  ~StreamDescriptor();
+  StreamDescriptor(StreamDescriptor &&other);
+
+  StreamDescriptor &operator=(StreamDescriptor &&other);
+
+  static StreamDescriptor from_socket(SOCKET s, bool close);
+  static StreamDescriptor from_file(int fd, bool close);
+
+  bool m_is_socket = false;
+  bool m_close = false;
+  union {
+    int m_fd;
+    SOCKET m_socket;
+  };
+};
+
+struct InputStream {
+  StreamDescriptor descriptor;
+
+  bool read_full(std::ofstream *log, size_t length, std::string &text);
+
+  bool read_line(std::ofstream *log, std::string &line);
+
+  bool read_expected(std::ofstream *log, llvm::StringRef expected);
+};
+
+struct OutputStream {
+  StreamDescriptor descriptor;
+
+  bool write_full(llvm::StringRef str);
+};
+} // namespace lldb_vscode
+
+#endif
Index: lldb/tools/lldb-vscode/IOStream.cpp
===================================================================
--- /dev/null
+++ lldb/tools/lldb-vscode/IOStream.cpp
@@ -0,0 +1,153 @@
+//===-- 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"
+
+#if defined(_WIN32)
+#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)
+#if defined(_WIN32)
+    ::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) {
+    int bytes_read = 0;
+    if (descriptor.m_is_socket)
+      bytes_read = ::recv(descriptor.m_socket, ptr, length, 0);
+    else
+      bytes_read = ::read(descriptor.m_fd, ptr, length);
+
+    if (bytes_read < 0) {
+      int reason = 0;
+#if defined(_WIN32)
+      if (descriptor.m_is_socket)
+        reason = WSAGetLastError();
+      else
+        reason = errno;
+#else
+      int reason = errno;
+      if (reason == EINTR || reason == EAGAIN)
+        continue;
+#endif
+
+      if (log)
+        *log << "Error " << reason << " reading from input file.\n";
+      return false;
+    }
+
+    assert(bytes_read <= length);
+    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;
+}
Index: lldb/tools/lldb-vscode/CMakeLists.txt
===================================================================
--- lldb/tools/lldb-vscode/CMakeLists.txt
+++ lldb/tools/lldb-vscode/CMakeLists.txt
@@ -15,6 +15,7 @@
   BreakpointBase.cpp
   ExceptionBreakpoint.cpp
   FunctionBreakpoint.cpp
+  IOStream.cpp
   JSONUtils.cpp
   LLDBUtils.cpp
   SourceBreakpoint.cpp
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
  • [Lldb-commits] [PATCH] D59... Zachary Turner via Phabricator via lldb-commits

Reply via email to