NO_DEBUG_INFO_TESTCASE = True
- def create_debug_adaptor(self):
+ def create_debug_adaptor(self, lldbVSCodeEnv=None):
'''Create the Visual Studio Code debug adaptor'''
self.assertTrue(os.path.exists(self.lldbVSCodeExec),
'lldb-vscode must exist')
log_file_path = self.getBuildArtifact('vscode.txt')
self.vscode = vscode.DebugAdaptor(
executable=self.lldbVSCodeExec, init_commands=self.setUpCommands(),
- log_file=log_file_path)
+ log_file=log_file_path, env=lldbVSCodeEnv)
- def build_and_create_debug_adaptor(self):
+ def build_and_create_debug_adaptor(self, lldbVSCodeEnv=None):
self.build()
- self.create_debug_adaptor()
+ self.create_debug_adaptor(lldbVSCodeEnv)
def set_source_breakpoints(self, source_path, lines, condition=None,
hitCondition=None):
stopCommands=None, exitCommands=None,
terminateCommands=None, sourcePath=None,
debuggerRoot=None, runInTerminal=False,
- disconnectAutomatically=True, postRunCommands=None):
+ disconnectAutomatically=True, postRunCommands=None,
+ lldbVSCodeEnv=None):
'''Build the default Makefile target, create the VSCode debug adaptor,
and launch the process.
'''
- self.build_and_create_debug_adaptor()
+ self.build_and_create_debug_adaptor(lldbVSCodeEnv)
self.assertTrue(os.path.exists(program), 'executable must exist')
return self.launch(program, args, cwd, env, stopOnEntry, disableASLR,
# Decode the JSON bytes into a python dictionary
return json.loads(json_str)
- return None
+ raise Exception("unexpected malformed message from lldb-vscode: " + line)
def packet_type_is(packet, packet_type):
class DebugAdaptor(DebugCommunication):
- def __init__(self, executable=None, port=None, init_commands=[], log_file=None):
+ def __init__(self, executable=None, port=None, init_commands=[], log_file=None, env=None):
self.process = None
if executable is not None:
adaptor_env = os.environ.copy()
+ if env is not None:
+ adaptor_env.update(env)
+
if log_file:
adaptor_env['LLDBVSCODE_LOG'] = log_file
self.process = subprocess.Popen([executable],
--- /dev/null
+import unittest2
+import vscode
+import json
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import lldbvscode_testcase
+
+
+class TestVSCode_redirection_to_console(lldbvscode_testcase.VSCodeTestCaseBase):
+
+ mydir = TestBase.compute_mydir(__file__)
+
+ @skipIfWindows
+ @skipIfRemote
+ def test(self):
+ """
+ Without proper stderr and stdout redirection, the following code would throw an
+ exception, like the following:
+
+ Exception: unexpected malformed message from lldb-vscode
+ """
+ program = self.getBuildArtifact("a.out")
+ self.build_and_launch(
+ program,
+ lldbVSCodeEnv={"LLDB_VSCODE_TEST_STDOUT_STDERR_REDIRECTION": ""})
+
+ source = 'main.cpp'
+
+ breakpoint1_line = line_number(source, '// breakpoint 1')
+ breakpoint_ids = self.set_source_breakpoints(source, [breakpoint1_line])
+
+ self.assertEqual(len(breakpoint_ids), 1,
+ "expect correct number of breakpoints")
+ self.continue_to_breakpoints(breakpoint_ids)
+
+ self.assertIn('argc', json.dumps(self.vscode.get_local_variables(frameIndex=1)))
IOStream.cpp
JSONUtils.cpp
LLDBUtils.cpp
+ OutputRedirector.cpp
ProgressEvent.cpp
RunInTerminal.cpp
SourceBreakpoint.cpp
#include "IOStream.h"
-#if defined(_WIN32)
+#if defined(_WIN32)
#include <io.h>
#else
#include <netinet/in.h>
--- /dev/null
+//===-- OutputRedirector.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
+//
+//===----------------------------------------------------------------------===/
+
+#if !defined(_WIN32)
+#include <unistd.h>
+#endif
+
+#include "OutputRedirector.h"
+
+using namespace llvm;
+
+namespace lldb_vscode {
+
+Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) {
+#if !defined(_WIN32)
+ int new_fd[2];
+ if (pipe(new_fd) == -1) {
+ int error = errno;
+ return createStringError(inconvertibleErrorCode(),
+ "Couldn't create new pipe for fd %d. %s", fd,
+ strerror(error));
+ }
+
+ if (dup2(new_fd[1], fd) == -1) {
+ int error = errno;
+ return createStringError(inconvertibleErrorCode(),
+ "Couldn't override the fd %d. %s", fd,
+ strerror(error));
+ }
+
+ int read_fd = new_fd[0];
+ std::thread t([read_fd, callback]() {
+ char buffer[4096];
+ while (true) {
+ ssize_t bytes_count = read(read_fd, &buffer, sizeof(buffer));
+ if (bytes_count == 0)
+ return;
+ if (bytes_count == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ break;
+ }
+ callback(StringRef(buffer, bytes_count).str());
+ }
+ });
+ t.detach();
+#endif
+ return Error::success();
+}
+
+} // namespace lldb_vscode
--- /dev/null
+//===-- OutputRedirector.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 LLDB_TOOLS_LLDB_VSCODE_OUTPUT_REDIRECTOR_H
+#define LLDB_TOOLS_LLDB_VSCODE_OUTPUT_REDIRECTOR_H
+
+#include <thread>
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+namespace lldb_vscode {
+
+/// Redirects the output of a given file descriptor to a callback.
+///
+/// \return
+/// \a Error::success if the redirection was set up correctly, or an error
+/// otherwise.
+llvm::Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback);
+
+} // namespace lldb_vscode
+
+#endif // LLDB_TOOLS_LLDB_VSCODE_OUTPUT_REDIRECTOR_H
#include "JSONUtils.h"
#include "LLDBUtils.h"
+#include "OutputRedirector.h"
#if defined(_WIN32)
#ifndef PATH_MAX
#endif
}
+/// used only by TestVSCode_redirection_to_console.py
+void redirection_test() {
+ printf("stdout message\n");
+ fprintf(stderr, "stderr message\n");
+ fflush(stdout);
+ fflush(stderr);
+}
+
+/// Redirect stdout and stderr fo the IDE's console output.
+///
+/// Errors in this operation will be printed to the log file and the IDE's
+/// console output as well.
+///
+/// \return
+/// A fd pointing to the original stdout.
+int SetupStdoutStderrRedirection() {
+ int new_stdout_fd = dup(fileno(stdout));
+ auto stdout_err_redirector_callback = [&](llvm::StringRef data) {
+ g_vsc.SendOutput(OutputType::Console, data);
+ };
+
+ for (int fd : {fileno(stdout), fileno(stderr)}) {
+ if (llvm::Error err = RedirectFd(fd, stdout_err_redirector_callback)) {
+ std::string error_message = llvm::toString(std::move(err));
+ if (g_vsc.log)
+ *g_vsc.log << error_message << std::endl;
+ stdout_err_redirector_callback(error_message);
+ }
+ }
+
+ /// used only by TestVSCode_redirection_to_console.py
+ if (getenv("LLDB_VSCODE_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
+ redirection_test();
+ return new_stdout_fd;
+}
+
int main(int argc, char *argv[]) {
llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
llvm::PrettyStackTraceProgram X(argc, argv);
+ // stdout/stderr redirection to the IDE's console
+ int new_stdout_fd = SetupStdoutStderrRedirection();
+
llvm::SmallString<256> program_path(argv[0]);
llvm::sys::fs::make_absolute(program_path);
g_vsc.debug_adaptor_path = program_path.str().str();
}
} else {
g_vsc.input.descriptor = StreamDescriptor::from_file(fileno(stdin), false);
- g_vsc.output.descriptor =
- StreamDescriptor::from_file(fileno(stdout), false);
+ g_vsc.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false);
}
+
uint32_t packet_idx = 0;
while (!g_vsc.sent_terminated_event) {
llvm::json::Object object;