diff options
author | Zachary Turner <zturner@google.com> | 2019-03-07 21:23:21 +0000 |
---|---|---|
committer | Zachary Turner <zturner@google.com> | 2019-03-07 21:23:21 +0000 |
commit | 29e8754172031ed217d25e3de0bc4047192a3453 (patch) | |
tree | a937f8971212dcbd47fc516d9b3cb4fbf1255b58 /lldb | |
parent | d0c2dba644e27210ed13cd638aa8b8e677ed757d (diff) | |
download | bcm5719-llvm-29e8754172031ed217d25e3de0bc4047192a3453.tar.gz bcm5719-llvm-29e8754172031ed217d25e3de0bc4047192a3453.zip |
[lldb-vscode] Support running in server mode on Windows.
Windows can't use standard i/o system calls such as read and write
to work with sockets, it instead needs to use the specific send
and recv calls. This complicates matters for the debug adapter,
since it needs to be able to work in both server mode where it
communicates over a socket, as well as non-server mode where it
communicates via stdin and stdout. To abstract this out, I've
introduced a class IOStream which hides all these details and
exposes a read/write interface that does the right on each
platform.
Differential Revision: https://reviews.llvm.org/D59104
llvm-svn: 355637
Diffstat (limited to 'lldb')
-rw-r--r-- | lldb/tools/lldb-vscode/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lldb/tools/lldb-vscode/IOStream.cpp | 153 | ||||
-rw-r--r-- | lldb/tools/lldb-vscode/IOStream.h | 69 | ||||
-rw-r--r-- | lldb/tools/lldb-vscode/VSCode.cpp | 104 | ||||
-rw-r--r-- | lldb/tools/lldb-vscode/VSCode.h | 8 | ||||
-rw-r--r-- | lldb/tools/lldb-vscode/lldb-vscode.cpp | 26 | ||||
-rw-r--r-- | lldb/tools/lldb-vscode/package.json | 3 |
7 files changed, 278 insertions, 86 deletions
diff --git a/lldb/tools/lldb-vscode/CMakeLists.txt b/lldb/tools/lldb-vscode/CMakeLists.txt index 08511248d33..357a4a10b12 100644 --- a/lldb/tools/lldb-vscode/CMakeLists.txt +++ b/lldb/tools/lldb-vscode/CMakeLists.txt @@ -15,6 +15,7 @@ add_lldb_tool(lldb-vscode BreakpointBase.cpp ExceptionBreakpoint.cpp FunctionBreakpoint.cpp + IOStream.cpp JSONUtils.cpp LLDBUtils.cpp SourceBreakpoint.cpp diff --git a/lldb/tools/lldb-vscode/IOStream.cpp b/lldb/tools/lldb-vscode/IOStream.cpp new file mode 100644 index 00000000000..1b578bfa6d4 --- /dev/null +++ b/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 + 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; +} diff --git a/lldb/tools/lldb-vscode/IOStream.h b/lldb/tools/lldb-vscode/IOStream.h new file mode 100644 index 00000000000..8414c09e9fe --- /dev/null +++ b/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 +typedef int SOCKET; +#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 diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp index d689dc5f474..1f5515fd8a9 100644 --- a/lldb/tools/lldb-vscode/VSCode.cpp +++ b/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 @@ namespace lldb_vscode { 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() } 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 @@ VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) { // 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 @@ void VSCode::SendJSON(const llvm::json::Value &json) { // 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; } diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h index 9b041e943ab..bb05e78c3a3 100644 --- a/lldb/tools/lldb-vscode/VSCode.h +++ b/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 @@ typedef llvm::StringMap<FunctionBreakpoint> FunctionBreakpointMap; 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 @@ struct VSCode { ~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); diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp index 82637c6246a..4b17f71df87 100644 --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/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> @@ -53,7 +54,6 @@ typedef int socklen_t; 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 LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; 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 @@ int main(int argc, char *argv[]) { #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; diff --git a/lldb/tools/lldb-vscode/package.json b/lldb/tools/lldb-vscode/package.json index aa3de65c172..847a219fbb5 100644 --- a/lldb/tools/lldb-vscode/package.json +++ b/lldb/tools/lldb-vscode/package.json @@ -56,6 +56,9 @@ ] }, "program": "./bin/lldb-vscode", + "windows": { + "program": "./bin/lldb-vscode.exe" + }, "configurationAttributes": { "launch": { "required": [ |