Index: lldb/trunk/tools/lldb-vscode/CMakeLists.txt =================================================================== --- lldb/trunk/tools/lldb-vscode/CMakeLists.txt +++ lldb/trunk/tools/lldb-vscode/CMakeLists.txt @@ -15,6 +15,7 @@ BreakpointBase.cpp ExceptionBreakpoint.cpp FunctionBreakpoint.cpp + IOStream.cpp JSONUtils.cpp LLDBUtils.cpp SourceBreakpoint.cpp Index: lldb/trunk/tools/lldb-vscode/IOStream.h =================================================================== --- lldb/trunk/tools/lldb-vscode/IOStream.h +++ lldb/trunk/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 +#else +typedef int SOCKET; +#endif + +#include "llvm/ADT/StringRef.h" + +#include +#include + +// 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/trunk/tools/lldb-vscode/IOStream.cpp =================================================================== --- lldb/trunk/tools/lldb-vscode/IOStream.cpp +++ lldb/trunk/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 +#else +#include +#include +#include +#endif + +#include +#include +#include + +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 + 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/trunk/tools/lldb-vscode/VSCode.h =================================================================== --- lldb/trunk/tools/lldb-vscode/VSCode.h +++ lldb/trunk/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/trunk/tools/lldb-vscode/VSCode.cpp =================================================================== --- lldb/trunk/tools/lldb-vscode/VSCode.cpp +++ lldb/trunk/tools/lldb-vscode/VSCode.cpp @@ -10,12 +10,15 @@ #include #include -#include "VSCode.h" #include "LLDBUtils.h" +#include "VSCode.h" +#include "llvm/Support/FormatVariadic.h" #if defined(_WIN32) -#include +#define NOMINMAX +#include #include +#include #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]; + 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; - 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; - } - } return json_str; } Index: lldb/trunk/tools/lldb-vscode/lldb-vscode.cpp =================================================================== --- lldb/trunk/tools/lldb-vscode/lldb-vscode.cpp +++ lldb/trunk/tools/lldb-vscode/lldb-vscode.cpp @@ -23,6 +23,7 @@ #define NOMINMAX #include #undef GetObject +#include #else #include #include @@ -53,7 +54,6 @@ constexpr const char *dev_null_path = "nul"; #else -typedef int SOCKET; constexpr const char *dev_null_path = "/dev/null"; #endif @@ -68,9 +68,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 +2635,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/trunk/tools/lldb-vscode/package.json =================================================================== --- lldb/trunk/tools/lldb-vscode/package.json +++ lldb/trunk/tools/lldb-vscode/package.json @@ -56,6 +56,9 @@ ] }, "program": "./bin/lldb-vscode", + "windows": { + "program": "./bin/lldb-vscode.exe" + }, "configurationAttributes": { "launch": { "required": [