Index: tools/lldb-vscode/VSCode.h =================================================================== --- tools/lldb-vscode/VSCode.h +++ tools/lldb-vscode/VSCode.h @@ -76,6 +76,7 @@ int64_t num_locals; int64_t num_globals; std::thread event_thread; + std::thread command_interpreter_thread; std::unique_ptr log; llvm::DenseMap addr_to_source_ref; llvm::DenseMap source_map; Index: tools/lldb-vscode/lldb-vscode.cpp =================================================================== --- tools/lldb-vscode/lldb-vscode.cpp +++ tools/lldb-vscode/lldb-vscode.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #if defined(_WIN32) // We need to #define NOMINMAX in order to skip `min()` and `max()` macro // definitions that conflict with other system headers. @@ -309,7 +310,7 @@ char buffer[1024]; size_t count; while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0) - g_vsc.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count)); + g_vsc.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count)); while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0) g_vsc.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count)); } @@ -451,6 +452,34 @@ } } +static void reset_stdin_termios(); +static bool g_old_stdin_termios_is_valid = false; +static struct termios g_old_stdin_termios; + +// In the Driver::MainLoop, we change the terminal settings. This function is +// added as an atexit handler to make sure we clean them up. +static void reset_stdin_termios() { + if (g_old_stdin_termios_is_valid) { + g_old_stdin_termios_is_valid = false; + ::tcsetattr(STDIN_FILENO, TCSANOW, &g_old_stdin_termios); + } +} + +void CommandInterpreterThreadFunction() { + if (::tcgetattr(STDIN_FILENO, &g_old_stdin_termios) == 0) { + g_old_stdin_termios_is_valid = true; + atexit(reset_stdin_termios); + } +#ifndef _MSC_VER + // Disabling stdin buffering with MSVC's 2015 CRT exposes a bug in fgets + // which causes it to miss newlines depending on whether there have been an + // odd or even number of characters. Bug has been reported to MS via Connect. + ::setbuf(stdin, nullptr); +#endif + ::setbuf(stdout, nullptr); + g_vsc.debugger.RunCommandInterpreter(false, false); +} + // "AttachRequest": { // "allOf": [ { "$ref": "#/definitions/Request" }, { // "type": "object", @@ -541,8 +570,8 @@ auto attach_info_len = snprintf(attach_info, sizeof(attach_info), "Waiting to attach to \"%s\"...", program.data()); - g_vsc.SendOutput(OutputType::Console, llvm::StringRef(attach_info, - attach_info_len)); + g_vsc.SendOutput(OutputType::Console, + llvm::StringRef(attach_info, attach_info_len)); } if (attachCommands.empty()) { // No "attachCommands", just attach normally. @@ -777,6 +806,9 @@ g_vsc.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); g_vsc.event_thread.join(); } + if (g_vsc.command_interpreter_thread.joinable()) { + g_vsc.event_thread.join(); + } } void request_exceptionInfo(const llvm::json::Object &request) { @@ -1028,6 +1060,11 @@ // "supportsRunInTerminalRequest": { // "type": "boolean", // "description": "Client supports the runInTerminal request." +// }, +// "supportsCommandInterpreter": } +// "type": "boolean", +// "description": "Client launches lldb-vscode in a terminal and thus +// supports attaching the CommandInterpreter." // } // }, // "required": [ "adapterID" ] @@ -1050,15 +1087,6 @@ // before we are given an executable to launch in a "launch" request, or a // executable when attaching to a process by process ID in a "attach" // request. - FILE *out = llvm::sys::RetryAfterSignal(nullptr, fopen, dev_null_path, "w"); - if (out) { - // Set the output and error file handles to redirect into nothing otherwise - // if any code in LLDB prints to the debugger file handles, the output and - // error file handles are initialized to STDOUT and STDERR and any output - // will kill our debug session. - g_vsc.debugger.SetOutputFileHandle(out, true); - g_vsc.debugger.SetErrorFileHandle(out, false); - } g_vsc.target = g_vsc.debugger.CreateTarget(nullptr); lldb::SBListener listener = g_vsc.debugger.GetListener(); @@ -1071,6 +1099,33 @@ // process and more. g_vsc.event_thread = std::thread(EventThreadFunction); + // Set the output and error file handles to redirect into nothing iff the + // input and output are set to stdin and stdout. If we are receiving DAP + // packets over a socket then keep them up. Otherwise if any code in LLDB + // prints to the debugger file handles, the output and error file handles are + // initialized to STDOUT and STDERR and any output will kill our debug + // session. + if (!g_vsc.input.descriptor.m_is_socket) { + FILE *out = llvm::sys::RetryAfterSignal(nullptr, fopen, dev_null_path, "w"); + if (out) { + g_vsc.debugger.SetOutputFileHandle(out, true); + g_vsc.debugger.SetErrorFileHandle(out, false); + } + } + + // We can't attach the command interpreter if the DAP packets are being sent + // via stdin/stdout. We would require extra sockets. So only attach the CI iff + // we are communicating via a socket. + if (g_vsc.input.descriptor.m_is_socket) { + auto arguments = request.getObject("arguments"); + auto supports_ci = + GetBoolean(arguments, "supportsCommandInterpreter", false); + if (supports_ci) { + g_vsc.command_interpreter_thread = + std::thread(CommandInterpreterThreadFunction); + } + } + llvm::json::Object response; FillResponse(request, response); llvm::json::Object body;