ashgti created this revision.
Herald added a project: All.
ashgti updated this revision to Diff 533349.
ashgti added a comment.
ashgti published this revision for review.
ashgti added reviewers: wallace, ivanhernandez13.
ashgti added a project: LLDB.
Herald added subscribers: lldb-commits, JDevlieghere.

Corrected a makefile reference.


Adds support for a reverse DAP request to startDebugging. The new request can 
be used to launch child processes from lldb scripts, for example it would be 
start forward to configure a debug configuration for a server and a client 
allowing you to launch both processes with a single debug configuraiton.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D153447

Files:
  lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
  lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
  lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
  lldb/test/API/tools/lldb-vscode/startDebugging/main.c
  lldb/tools/lldb-vscode/JSONUtils.cpp
  lldb/tools/lldb-vscode/VSCode.cpp
  lldb/tools/lldb-vscode/VSCode.h
  lldb/tools/lldb-vscode/lldb-vscode.cpp

Index: lldb/tools/lldb-vscode/lldb-vscode.cpp
===================================================================
--- lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -1471,6 +1471,13 @@
 
   g_vsc.debugger =
       lldb::SBDebugger::Create(source_init_file, log_cb, nullptr);
+  auto cmd = g_vsc.debugger.GetCommandInterpreter().AddMultiwordCommand(
+      "dap", nullptr);
+  cmd.AddCommand(
+      "startDebugging", &g_vsc.start_debugging_request_handler,
+      "Sends a startDebugging from the debug adapter to the client to start "
+      "child debug session of the same type as the caller.");
+
   g_vsc.progress_event_thread = std::thread(ProgressEventThreadFunction);
 
   // Start our event thread so we can receive events from the debugger, target,
@@ -1564,7 +1571,8 @@
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 }
 
-llvm::Error request_runInTerminal(const llvm::json::Object &launch_request) {
+llvm::Error request_runInTerminal(const llvm::json::Object &launch_request,
+                                  const uint64_t timeout_seconds) {
   g_vsc.is_attach = true;
   lldb::SBAttachInfo attach_info;
 
@@ -1582,10 +1590,10 @@
 #endif
   llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
       launch_request, g_vsc.debug_adaptor_path, comm_file.m_path, debugger_pid);
-  llvm::json::Object reverse_response;
-  lldb_vscode::PacketStatus status =
-      g_vsc.SendReverseRequest(reverse_request, reverse_response);
-  if (status != lldb_vscode::PacketStatus::Success)
+  auto sent =
+      g_vsc.SendReverseRequest("runInTerminal", std::move(reverse_request));
+  if (std::future_status::ready !=
+      sent.wait_for(std::chrono::seconds(timeout_seconds)))
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "Process cannot be launched by the IDE. %s",
                                    comm_channel.GetLauncherError().c_str());
@@ -1676,7 +1684,7 @@
   const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
 
   if (GetBoolean(arguments, "runInTerminal", false)) {
-    if (llvm::Error err = request_runInTerminal(request))
+    if (llvm::Error err = request_runInTerminal(request, timeout_seconds))
       error.SetErrorString(llvm::toString(std::move(err)).c_str());
   } else if (launchCommands.empty()) {
     // Disable async events so the launch will be successful when we return from
@@ -3464,17 +3472,13 @@
     g_vsc.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false);
   }
 
-  while (!g_vsc.sent_terminated_event) {
-    llvm::json::Object object;
-    lldb_vscode::PacketStatus status = g_vsc.GetNextObject(object);
-    if (status == lldb_vscode::PacketStatus::EndOfFile)
-      break;
-    if (status != lldb_vscode::PacketStatus::Success)
-      return 1; // Fatal error
-
-    if (!g_vsc.HandleObject(object))
-      return 1;
+  bool CleanExit = true;
+  if (auto Err = g_vsc.Loop()) {
+    if (g_vsc.log)
+      *g_vsc.log << "Transport Error: " << llvm::toString(std::move(Err))
+                 << "\n";
+    CleanExit = false;
   }
 
-  return EXIT_SUCCESS;
+  return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
 }
Index: lldb/tools/lldb-vscode/VSCode.h
===================================================================
--- lldb/tools/lldb-vscode/VSCode.h
+++ lldb/tools/lldb-vscode/VSCode.h
@@ -11,8 +11,10 @@
 
 #include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
 
+#include <atomic>
 #include <condition_variable>
 #include <cstdio>
+#include <future>
 #include <iosfwd>
 #include <map>
 #include <optional>
@@ -121,6 +123,11 @@
   void Clear();
 };
 
+struct StartDebuggingRequestHandler : public lldb::SBCommandPluginInterface {
+  bool DoExecute(lldb::SBDebugger debugger, char **command,
+                 lldb::SBCommandReturnObject &result) override;
+};
+
 struct VSCode {
   std::string debug_adaptor_path;
   InputStream input;
@@ -146,7 +153,7 @@
   // arguments if we get a RestartRequest.
   std::optional<llvm::json::Object> last_launch_or_attach_request;
   lldb::tid_t focus_tid;
-  bool sent_terminated_event;
+  std::atomic<bool> sent_terminated_event;
   bool stop_at_entry;
   bool is_attach;
   // The process event thread normally responds to process exited events by
@@ -154,13 +161,20 @@
   // the old process here so we can detect this case and keep running.
   lldb::pid_t restarting_process_id;
   bool configuration_done_sent;
-  uint32_t reverse_request_seq;
   std::map<std::string, RequestCallback> request_handlers;
   bool waiting_for_run_in_terminal;
   ProgressEventReporter progress_event_reporter;
   // Keep track of the last stop thread index IDs as threads won't go away
   // unless we send a "thread" event to indicate the thread exited.
   llvm::DenseSet<lldb::tid_t> thread_ids;
+  uint32_t reverse_request_seq;
+  std::mutex call_mutex;
+  std::map<
+      /* request_seq */ int,
+      /* response promise */ std::promise<llvm::Expected<llvm::json::Value>>>
+      inflight_reverse_requests;
+  StartDebuggingRequestHandler start_debugging_request_handler;
+
   VSCode();
   ~VSCode();
   VSCode(const VSCode &rhs) = delete;
@@ -224,19 +238,21 @@
   PacketStatus GetNextObject(llvm::json::Object &object);
   bool HandleObject(const llvm::json::Object &object);
 
-  /// Send a Debug Adapter Protocol reverse request to the IDE
+  llvm::Error Loop();
+
+  /// Send a Debug Adapter Protocol reverse request to the IDE.
   ///
-  /// \param[in] request
-  ///   The payload of the request to send.
+  /// \param[in] command
+  ///   The reverse request command.
   ///
-  /// \param[out] response
-  ///   The response of the IDE. It might be undefined if there was an error.
+  /// \param[in] arguments
+  ///   The reverse request arguements.
   ///
   /// \return
-  ///   A \a PacketStatus object indicating the sucess or failure of the
-  ///   request.
-  PacketStatus SendReverseRequest(llvm::json::Object request,
-                                  llvm::json::Object &response);
+  ///   A future that resolves to the response object indicating the sucess or
+  ///   failure of the request.
+  std::future<llvm::Expected<llvm::json::Value>>
+  SendReverseRequest(llvm::StringRef command, llvm::json::Value arguments);
 
   /// Registers a callback handler for a Debug Adapter Protocol request
   ///
Index: lldb/tools/lldb-vscode/VSCode.cpp
===================================================================
--- lldb/tools/lldb-vscode/VSCode.cpp
+++ lldb/tools/lldb-vscode/VSCode.cpp
@@ -41,10 +41,10 @@
       focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
       stop_at_entry(false), is_attach(false),
       restarting_process_id(LLDB_INVALID_PROCESS_ID),
-      configuration_done_sent(false), reverse_request_seq(0),
-      waiting_for_run_in_terminal(false),
+      configuration_done_sent(false), waiting_for_run_in_terminal(false),
       progress_event_reporter(
-          [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }) {
+          [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
+      reverse_request_seq(0) {
   const char *log_file_path = getenv("LLDBVSCODE_LOG");
 #if defined(_WIN32)
   // Windows opens stdout and stdin in text mode which converts \n to 13,10
@@ -505,24 +505,91 @@
       return false; // Fail
     }
   }
+
+  if (packet_type == "response") {
+    auto id = GetSigned(object, "request_seq", 0);
+    std::promise<llvm::Expected<llvm::json::Value>> response_promise;
+
+    {
+      std::lock_guard<std::mutex> locker(call_mutex);
+      if (inflight_reverse_requests.find(id) !=
+          inflight_reverse_requests.end()) {
+        response_promise =
+            std::move(inflight_reverse_requests.find(id)->second);
+      } else {
+        if (log) {
+          *log << "Unhandled reply " << id << std::endl;
+        }
+        return true;
+      }
+    }
+
+    // Result should be given, use null if not.
+    if (GetBoolean(object, "success", false)) {
+      llvm::json::Value Result = nullptr;
+      if (auto *B = object.get("body")) {
+        Result = std::move(*B);
+      }
+      response_promise.set_value(std::move(Result));
+    } else {
+      llvm::StringRef message = GetString(object, "message");
+      if (message.empty()) {
+        message = "Unknown error, response failed";
+      }
+      response_promise.set_value(llvm::createStringError(
+          std::error_code(-1, std::generic_category()), message));
+    }
+
+    return true;
+  }
+
   return false;
 }
 
-PacketStatus VSCode::SendReverseRequest(llvm::json::Object request,
-                                        llvm::json::Object &response) {
-  request.try_emplace("seq", ++reverse_request_seq);
-  SendJSON(llvm::json::Value(std::move(request)));
-  while (true) {
-    PacketStatus status = GetNextObject(response);
-    const auto packet_type = GetString(response, "type");
-    if (packet_type == "response")
-      return status;
-    else {
-      // Not our response, we got another packet
-      HandleObject(response);
+llvm::Error VSCode::Loop() {
+  while (!sent_terminated_event) {
+    llvm::json::Object object;
+    lldb_vscode::PacketStatus status = GetNextObject(object);
+
+    if (status == lldb_vscode::PacketStatus::EndOfFile) {
+      break;
+    }
+
+    if (status != lldb_vscode::PacketStatus::Success) {
+      return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                     "failed to send packet");
+    }
+
+    if (!HandleObject(object)) {
+      return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                     "unhandled packet");
     }
   }
-  return PacketStatus::EndOfFile;
+
+  return llvm::Error::success();
+}
+
+std::future<llvm::Expected<llvm::json::Value>>
+VSCode::SendReverseRequest(llvm::StringRef command,
+                           llvm::json::Value arguments) {
+  int64_t id;
+  std::future<llvm::Expected<llvm::json::Value>> response_future;
+  {
+    std::lock_guard<std::mutex> locker(call_mutex);
+    id = ++reverse_request_seq;
+    std::promise<llvm::Expected<llvm::json::Value>> response_promise;
+    response_future = response_promise.get_future();
+    inflight_reverse_requests.emplace(id, std::move(response_promise));
+  }
+
+  SendJSON(llvm::json::Object{
+      {"type", "request"},
+      {"seq", id},
+      {"command", command},
+      {"arguments", std::move(arguments)},
+  });
+
+  return response_future;
 }
 
 void VSCode::RegisterRequestCallback(std::string request,
@@ -610,4 +677,60 @@
   return var_ref;
 }
 
+bool StartDebuggingRequestHandler::DoExecute(
+    lldb::SBDebugger debugger, char **command,
+    lldb::SBCommandReturnObject &result) {
+  // Command format like: `startDebugging <launch|attach> <configuration>`
+  if (!command) {
+    result.SetError("Invalid use of startDebugging");
+    result.SetStatus(lldb::eReturnStatusFailed);
+    return false;
+  }
+
+  if (!command[0] || llvm::StringRef(command[0]).empty()) {
+    result.SetError("startDebugging request type missing.");
+    result.SetStatus(lldb::eReturnStatusFailed);
+    return false;
+  }
+
+  if (!command[1] || llvm::StringRef(command[1]).empty()) {
+    result.SetError("configuration missing.");
+    result.SetStatus(lldb::eReturnStatusFailed);
+    return false;
+  }
+
+  llvm::StringRef request{command[0]};
+  llvm::StringRef raw_configuration{command[1]};
+
+  llvm::Expected<llvm::json::Value> configuration =
+      llvm::json::parse(raw_configuration);
+
+  if (!configuration) {
+    llvm::Error err = configuration.takeError();
+    std::string msg =
+        "Failed to parse json configuration: " + llvm::toString(std::move(err));
+    result.SetError(msg.c_str());
+    result.SetStatus(lldb::eReturnStatusFailed);
+    return false;
+  }
+
+  auto future = g_vsc.SendReverseRequest(
+      "startDebugging",
+      llvm::json::Object{{"request", request},
+                         {"configuration", std::move(*configuration)}});
+
+  future.wait();
+  auto sent = future.get();
+  if (!sent) {
+    llvm::Error err = configuration.takeError();
+    std::string msg = "reverse start debugging request failed: " +
+                      llvm::toString(std::move(err));
+    result.SetError(msg.c_str());
+    result.SetStatus(lldb::eReturnStatusFailed);
+    return false;
+  }
+
+  return true;
+}
+
 } // namespace lldb_vscode
Index: lldb/tools/lldb-vscode/JSONUtils.cpp
===================================================================
--- lldb/tools/lldb-vscode/JSONUtils.cpp
+++ lldb/tools/lldb-vscode/JSONUtils.cpp
@@ -1104,10 +1104,6 @@
                                   llvm::StringRef debug_adaptor_path,
                                   llvm::StringRef comm_file,
                                   lldb::pid_t debugger_pid) {
-  llvm::json::Object reverse_request;
-  reverse_request.try_emplace("type", "request");
-  reverse_request.try_emplace("command", "runInTerminal");
-
   llvm::json::Object run_in_terminal_args;
   // This indicates the IDE to open an embedded terminal, instead of opening the
   // terminal in a new window.
@@ -1143,9 +1139,7 @@
   run_in_terminal_args.try_emplace("env",
                                    llvm::json::Value(std::move(environment)));
 
-  reverse_request.try_emplace(
-      "arguments", llvm::json::Value(std::move(run_in_terminal_args)));
-  return reverse_request;
+  return run_in_terminal_args;
 }
 
 // Keep all the top level items from the statistics dump, except for the
Index: lldb/test/API/tools/lldb-vscode/startDebugging/main.c
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/startDebugging/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+  printf("server/client example\n"); // breakpoint 1
+  return 0;
+}
Index: lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
@@ -0,0 +1,33 @@
+"""
+Test lldb-vscode startDebugging reverse request
+"""
+
+
+import vscode
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import lldbvscode_testcase
+import os
+import subprocess
+import json
+
+
+class TestVSCode_startDebugging(lldbvscode_testcase.VSCodeTestCaseBase):
+    def test_startDebugging(self):
+        """
+            Tests the "startDebugging" reverse request. It makes sure that the IDE can
+            start a child debug session.
+        """
+        program = self.getBuildArtifact("a.out")
+        source = "main.c"
+        self.build_and_launch(
+            program,
+            # Ensure we can start child debug session.
+            postRunCommands='dap startDebugging launch {"program":"{}"}'.format(program)
+        )
+
+        breakpoint_line = line_number(source, "// breakpoint")
+
+        self.set_source_breakpoints(source, [breakpoint_line])
+        self.continue_to_next_stop()
\ No newline at end of file
Index: lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
Index: lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -340,6 +340,18 @@
                         },
                         set_sequence=False,
                     )
+                elif response_or_request["command"] == "startDebugging":
+                    self.send_packet(
+                        {
+                            "type": "response",
+                            "seq": -1,
+                            "request_seq": response_or_request["seq"],
+                            "success": True,
+                            "command": "startDebugging",
+                            "body": {},
+                        },
+                        set_sequence=False,
+                    )
                 else:
                     desc = 'unkonwn reverse request "%s"' % (
                         response_or_request["command"]
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to