[Lldb-commits] [lldb] 227b218 - Creating a startDebugging reverse DAP request handler in lldb-vscode.

David Goldman via lldb-commits lldb-commits at lists.llvm.org
Thu Jun 29 11:46:56 PDT 2023


Author: John Harrison
Date: 2023-06-29T14:45:57-04:00
New Revision: 227b2180eb2be94986d63c75c144f88be13fc52f

URL: https://github.com/llvm/llvm-project/commit/227b2180eb2be94986d63c75c144f88be13fc52f
DIFF: https://github.com/llvm/llvm-project/commit/227b2180eb2be94986d63c75c144f88be13fc52f.diff

LOG: Creating a startDebugging reverse DAP request handler in lldb-vscode.

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.

Reviewed By: wallace, ivanhernandez13

Differential Revision: https://reviews.llvm.org/D153447

Added: 
    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

Modified: 
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
    lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
    lldb/tools/lldb-vscode/JSONUtils.cpp
    lldb/tools/lldb-vscode/README.md
    lldb/tools/lldb-vscode/VSCode.cpp
    lldb/tools/lldb-vscode/VSCode.h
    lldb/tools/lldb-vscode/lldb-vscode.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
index 236e26c6b9313f..1c77fabeb4afe8 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -126,6 +126,7 @@ def __init__(self, recv, send, init_commands, log_file=None):
         self.thread_stop_reasons = {}
         self.breakpoint_events = []
         self.progress_events = []
+        self.reverse_requests = []
         self.sequence = 1
         self.threads = None
         self.recv_thread.start()
@@ -324,6 +325,7 @@ def send_recv(self, command):
                 self.validate_response(command, response_or_request)
                 return response_or_request
             else:
+                self.reverse_requests.append(response_or_request)
                 if response_or_request["command"] == "runInTerminal":
                     subprocess.Popen(
                         response_or_request["arguments"]["args"],
@@ -340,8 +342,20 @@ def send_recv(self, command):
                         },
                         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"' % (
+                    desc = 'unknown reverse request "%s"' % (
                         response_or_request["command"]
                     )
                     raise ValueError(desc)

diff  --git a/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py b/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
index a9a9c252908616..bd349a6e5eeead 100644
--- a/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
+++ b/lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
@@ -59,6 +59,18 @@ def test_runInTerminal(self):
             program, runInTerminal=True, args=["foobar"], env=["FOO=bar"]
         )
 
+        self.assertEqual(
+            len(self.vscode.reverse_requests), 
+            1, 
+            "make sure we got a reverse request"
+        )
+
+        request = self.vscode.reverse_requests[0]
+        self.assertIn(self.lldbVSCodeExec, request["arguments"]["args"])
+        self.assertIn(program, request["arguments"]["args"])
+        self.assertIn("foobar", request["arguments"]["args"])
+        self.assertIn("FOO", request["arguments"]["env"])
+
         breakpoint_line = line_number(source, "// breakpoint")
 
         self.set_source_breakpoints(source, [breakpoint_line])

diff  --git a/lldb/test/API/tools/lldb-vscode/startDebugging/Makefile b/lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
new file mode 100644
index 00000000000000..10495940055b63
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/startDebugging/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py b/lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
new file mode 100644
index 00000000000000..e1fc7f7fa17fb9
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/startDebugging/TestVSCode_startDebugging.py
@@ -0,0 +1,39 @@
+"""
+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
+
+
+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)
+
+        breakpoint_line = line_number(source, "// breakpoint")
+
+        self.set_source_breakpoints(source, [breakpoint_line])
+        self.continue_to_next_stop()
+        self.vscode.request_evaluate(
+            '`lldb-vscode startDebugging attach \'{"pid":321}\'', context='repl'
+        )
+
+        self.assertEqual(
+            len(self.vscode.reverse_requests), 
+            1, 
+            "make sure we got a reverse request"
+        )
+
+        request = self.vscode.reverse_requests[0]
+        self.assertEqual(request["arguments"]["configuration"]["pid"], 321)
+        self.assertEqual(request["arguments"]["request"], "attach")
\ No newline at end of file

diff  --git a/lldb/test/API/tools/lldb-vscode/startDebugging/main.c b/lldb/test/API/tools/lldb-vscode/startDebugging/main.c
new file mode 100644
index 00000000000000..27bc22b94794b6
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/startDebugging/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+  printf("example\n"); // breakpoint 1
+  return 0;
+}

diff  --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp
index d854bc6608aca9..bf7f766a58e3da 100644
--- a/lldb/tools/lldb-vscode/JSONUtils.cpp
+++ b/lldb/tools/lldb-vscode/JSONUtils.cpp
@@ -1104,10 +1104,6 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
                                   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 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
   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

diff  --git a/lldb/tools/lldb-vscode/README.md b/lldb/tools/lldb-vscode/README.md
index f82293daa51281..67dfa54ed4519b 100644
--- a/lldb/tools/lldb-vscode/README.md
+++ b/lldb/tools/lldb-vscode/README.md
@@ -11,6 +11,8 @@
 		- [Attach to process using process ID](#attach-using-pid)
 		- [Attach to process by name](#attach-by-name)
 		- [Loading a core file](#loading-a-core-file)
+- [Custom Debugger Commands](#custom-debugger-commands)
+  - [startDebugging](#startDebugging)
 
 # Introduction
 
@@ -203,3 +205,33 @@ This loads the coredump file `/cores/123.core` associated with the program
   "program": "/tmp/a.out"
 }
 ```
+
+# Custom debugger commands
+
+The `lldb-vscode` tool includes additional custom commands to support the Debug
+Adapter Protocol features.
+
+## startDebugging
+
+Using the command `lldb-vscode startDebugging` it is possible to trigger a
+reverse request to the client requesting a child debug session with the
+specified configuration. For example, this can be used to attached to forked or
+spawned processes. For more information see
+[Reverse Requests StartDebugging](https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_StartDebugging).
+
+The custom command has the following format:
+
+```
+lldb-vscode startDebugging <launch|attach> <configuration>
+```
+
+This will launch a server and then request a child debug session for a client.
+
+```javascript
+{
+  "program": "server",
+  "postRunCommand": [
+    "lldb-vscode startDebugging launch '{\"program\":\"client\"}'"
+  ]
+}
+```

diff  --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp
index ca1a626e514c91..b1c6817c2128b9 100644
--- a/lldb/tools/lldb-vscode/VSCode.cpp
+++ b/lldb/tools/lldb-vscode/VSCode.cpp
@@ -41,10 +41,10 @@ VSCode::VSCode()
       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,83 @@ bool VSCode::HandleObject(const llvm::json::Object &object) {
       return false; // Fail
     }
   }
+
+  if (packet_type == "response") {
+    auto id = GetSigned(object, "request_seq", 0);
+    ResponseCallback response_handler = [](llvm::Expected<llvm::json::Value>) {
+      llvm::errs() << "Unhandled response\n";
+    };
+
+    {
+      std::lock_guard<std::mutex> locker(call_mutex);
+      auto inflight = inflight_reverse_requests.find(id);
+      if (inflight != inflight_reverse_requests.end()) {
+        response_handler = std::move(inflight->second);
+        inflight_reverse_requests.erase(inflight);
+      }
+    }
+
+    // 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_handler(Result);
+    } else {
+      llvm::StringRef message = GetString(object, "message");
+      if (message.empty()) {
+        message = "Unknown error, response failed";
+      }
+      response_handler(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();
+}
+
+void VSCode::SendReverseRequest(llvm::StringRef command,
+                                llvm::json::Value arguments,
+                                ResponseCallback callback) {
+  int64_t id;
+  {
+    std::lock_guard<std::mutex> locker(call_mutex);
+    id = ++reverse_request_seq;
+    inflight_reverse_requests.emplace(id, std::move(callback));
+  }
+
+  SendJSON(llvm::json::Object{
+      {"type", "request"},
+      {"seq", id},
+      {"command", command},
+      {"arguments", std::move(arguments)},
+  });
 }
 
 void VSCode::RegisterRequestCallback(std::string request,
@@ -610,4 +669,63 @@ int64_t Variables::InsertExpandableVariable(lldb::SBValue variable,
   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]};
+  std::string raw_configuration{command[1]};
+
+  int i = 2;
+  while (command[i]) {
+    raw_configuration.append(" ").append(command[i]);
+  }
+
+  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;
+  }
+
+  g_vsc.SendReverseRequest(
+      "startDebugging",
+      llvm::json::Object{{"request", request},
+                         {"configuration", std::move(*configuration)}},
+      [](llvm::Expected<llvm::json::Value> value) {
+        if (!value) {
+          llvm::Error err = value.takeError();
+          llvm::errs() << "reverse start debugging request failed: "
+                       << llvm::toString(std::move(err)) << "\n";
+        }
+      });
+
+  result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
+
+  return true;
+}
+
 } // namespace lldb_vscode

diff  --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h
index 48bef18de2a23f..2d67da2bbc092e 100644
--- a/lldb/tools/lldb-vscode/VSCode.h
+++ b/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>
@@ -73,6 +75,7 @@ enum VSCodeBroadcasterBits {
 };
 
 typedef void (*RequestCallback)(const llvm::json::Object &command);
+typedef void (*ResponseCallback)(llvm::Expected<llvm::json::Value> value);
 
 enum class PacketStatus {
   Success = 0,
@@ -121,6 +124,11 @@ struct Variables {
   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 +154,7 @@ struct VSCode {
   // 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 +162,18 @@ struct VSCode {
   // 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<int /* request_seq */, ResponseCallback /* reply handler */>
+      inflight_reverse_requests;
+  StartDebuggingRequestHandler start_debugging_request_handler;
+
   VSCode();
   ~VSCode();
   VSCode(const VSCode &rhs) = delete;
@@ -224,19 +237,20 @@ struct VSCode {
   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);
+  /// \param[in] callback
+  ///   A callback to execute when the response arrives.
+  void SendReverseRequest(llvm::StringRef command, llvm::json::Value arguments,
+                          ResponseCallback callback);
 
   /// Registers a callback handler for a Debug Adapter Protocol request
   ///

diff  --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp
index 126719e1494e5f..b9757d166958bf 100644
--- a/lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -1471,6 +1471,13 @@ void request_initialize(const llvm::json::Object &request) {
 
   g_vsc.debugger =
       lldb::SBDebugger::Create(source_init_file, log_cb, nullptr);
+  auto cmd = g_vsc.debugger.GetCommandInterpreter().AddMultiwordCommand(
+      "lldb-vscode", nullptr);
+  cmd.AddCommand(
+      "startDebugging", &g_vsc.start_debugging_request_handler,
+      "Sends a startDebugging request from the debug adapter to the client to "
+      "start a 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 @@ void request_initialize(const llvm::json::Object &request) {
   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,13 +1590,15 @@ llvm::Error request_runInTerminal(const llvm::json::Object &launch_request) {
 #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)
-    return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                   "Process cannot be launched by the IDE. %s",
-                                   comm_channel.GetLauncherError().c_str());
+  g_vsc.SendReverseRequest("runInTerminal", std::move(reverse_request),
+                           [](llvm::Expected<llvm::json::Value> value) {
+                             if (!value) {
+                               llvm::Error err = value.takeError();
+                               llvm::errs()
+                                   << "runInTerminal request failed: "
+                                   << llvm::toString(std::move(err)) << "\n";
+                             }
+                           });
 
   if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
     attach_info.SetProcessID(*pid);
@@ -1676,7 +1686,7 @@ lldb::SBError LaunchProcess(const llvm::json::Object &request) {
   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 +3474,13 @@ int main(int argc, char *argv[]) {
     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;
 }


        


More information about the lldb-commits mailing list