[Lldb-commits] [lldb] 2b6c5bb - [lldb-vscode] Implement RestartRequest

Jorge Gorbe Moya via lldb-commits lldb-commits at lists.llvm.org
Tue May 2 14:51:20 PDT 2023


Author: Jorge Gorbe Moya
Date: 2023-05-02T14:50:39-07:00
New Revision: 2b6c5bb995be1b675c1d92e2d46403c76764e5c7

URL: https://github.com/llvm/llvm-project/commit/2b6c5bb995be1b675c1d92e2d46403c76764e5c7
DIFF: https://github.com/llvm/llvm-project/commit/2b6c5bb995be1b675c1d92e2d46403c76764e5c7.diff

LOG: [lldb-vscode] Implement RestartRequest

This is an optional request, but supporting it makes the experience
better when re-launching a big binary that takes significant time to
parse: instead of tearing down and re-create the whole session we just
need to kill the current process and launch a new one.

Some non-obvious comments that might help review this change:

* After killing the process, we get an "exited" event for it. Because
  the process no longer exists some interesting things can occur that
  manifest as flaky failures if not dealt with:

  - `EventIsProcessEvent` relies on `SBEvent::GetBroadcasterClass`,
    which can crash if the broadcaster is no longer there: the event
    only holds a weak_ptr to its broadcaster, and `GetBroadcasterClass`
    uses it without checking.

    Other `EventIs*` functions look at the flavor of the EventData, so I
    have modified EventIsProcessEvent to do that.

  - We keep the PID of the old process so we can detect its "exited"
    event and not terminate the whole session. But sometimes the
    SBProcess we get from the event won't have a PID, for some reason.

* I have factored out the code to launch a process out to a new
  LaunchProcess function, so it can be used from both `request_launch`
  and `request_restart`.

* The restart_runInTerminal test has the same problem with debug builds
  as the original runInTerminal test: when attaching to the launcher
  instance of lldb-vscode it takes a long time to parse its debug info.
  I have used the same workaround to disable that particular test for
  debug builds.

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

Added: 
    lldb/test/API/tools/lldb-vscode/restart/Makefile
    lldb/test/API/tools/lldb-vscode/restart/TestVSCode_restart.py
    lldb/test/API/tools/lldb-vscode/restart/TestVSCode_restart_runInTerminal.py
    lldb/test/API/tools/lldb-vscode/restart/main.c

Modified: 
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
    lldb/source/API/SBProcess.cpp
    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 c2de4ad5c7d9a..16d26a8fa2216 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -588,6 +588,15 @@ def request_continue(self, threadId=None):
         # Caller must still call wait_for_stopped.
         return response
 
+    def request_restart(self):
+        command_dict = {
+            'command': 'restart',
+            'type': 'request',
+        }
+        response = self.send_recv(command_dict)
+        # Caller must still call wait_for_stopped.
+        return response
+
     def request_disconnect(self, terminateDebuggee=None):
         args_dict = {}
         if terminateDebuggee is not None:

diff  --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp
index 2004b66eafe34..4af1a983fe20f 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -779,8 +779,8 @@ SBProcess::GetStructuredDataFromEvent(const lldb::SBEvent &event) {
 bool SBProcess::EventIsProcessEvent(const SBEvent &event) {
   LLDB_INSTRUMENT_VA(event);
 
-  return (event.GetBroadcasterClass() == SBProcess::GetBroadcasterClass()) &&
-         !EventIsStructuredDataEvent(event);
+  return Process::ProcessEventData::GetEventDataFromEvent(event.get()) !=
+         nullptr;
 }
 
 bool SBProcess::EventIsStructuredDataEvent(const lldb::SBEvent &event) {

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

diff  --git a/lldb/test/API/tools/lldb-vscode/restart/TestVSCode_restart.py b/lldb/test/API/tools/lldb-vscode/restart/TestVSCode_restart.py
new file mode 100644
index 0000000000000..6715b167bcdb0
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/restart/TestVSCode_restart.py
@@ -0,0 +1,82 @@
+"""
+Test lldb-vscode RestartRequest.
+"""
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import line_number
+import lldbvscode_testcase
+
+
+class TestVSCode_restart(lldbvscode_testcase.VSCodeTestCaseBase):
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_basic_functionality(self):
+        '''
+            Tests the basic restarting functionality: set two breakpoints in
+            sequence, restart at the second, check that we hit the first one.
+        '''
+        line_A = line_number('main.c', '// breakpoint A')
+        line_B = line_number('main.c', '// breakpoint B')
+
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        [bp_A, bp_B] = self.set_source_breakpoints('main.c', [line_A, line_B])
+
+        # Verify we hit A, then B.
+        self.vscode.request_configurationDone()
+        self.verify_breakpoint_hit([bp_A])
+        self.vscode.request_continue()
+        self.verify_breakpoint_hit([bp_B])
+
+        # Make sure i has been modified from its initial value of 0.
+        self.assertEquals(int(self.vscode.get_local_variable_value('i')),
+                          1234, 'i != 1234 after hitting breakpoint B')
+
+        # Restart then check we stop back at A and program state has been reset.
+        self.vscode.request_restart()
+        self.verify_breakpoint_hit([bp_A])
+        self.assertEquals(int(self.vscode.get_local_variable_value('i')),
+                          0, 'i != 0 after hitting breakpoint A on restart')
+
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_stopOnEntry(self):
+        '''
+            Check that the stopOnEntry setting is still honored after a restart.
+        '''
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, stopOnEntry=True)
+        [bp_main] = self.set_function_breakpoints(['main'])
+        self.vscode.request_configurationDone()
+
+        # Once the "configuration done" event is sent, we should get a stopped
+        # event immediately because of stopOnEntry.
+        stopped_events = self.vscode.wait_for_stopped()
+        for stopped_event in stopped_events:
+            if 'body' in stopped_event:
+                body = stopped_event['body']
+                if 'reason' in body:
+                    reason = body['reason']
+                    self.assertNotEqual(
+                        reason, 'breakpoint',
+                        'verify stop isn\'t "main" breakpoint')
+
+        # Then, if we continue, we should hit the breakpoint at main.
+        self.vscode.request_continue()
+        self.verify_breakpoint_hit([bp_main])
+
+        # Restart and check that we still get a stopped event before reaching
+        # main.
+        self.vscode.request_restart()
+        stopped_events = self.vscode.wait_for_stopped()
+        for stopped_event in stopped_events:
+            if 'body' in stopped_event:
+                body = stopped_event['body']
+                if 'reason' in body:
+                    reason = body['reason']
+                    self.assertNotEqual(
+                        reason, 'breakpoint',
+                        'verify stop after restart isn\'t "main" breakpoint')
+

diff  --git a/lldb/test/API/tools/lldb-vscode/restart/TestVSCode_restart_runInTerminal.py b/lldb/test/API/tools/lldb-vscode/restart/TestVSCode_restart_runInTerminal.py
new file mode 100644
index 0000000000000..f250e014cfe3d
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/restart/TestVSCode_restart_runInTerminal.py
@@ -0,0 +1,104 @@
+"""
+Test lldb-vscode RestartRequest.
+"""
+
+import os
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import line_number
+import lldbvscode_testcase
+
+
+class TestVSCode_restart_runInTerminal(lldbvscode_testcase.VSCodeTestCaseBase):
+
+    def isTestSupported(self):
+        try:
+            # We skip this test for debug builds because it takes too long
+            # parsing lldb's own debug info. Release builds are fine.
+            # Checking the size of the lldb-vscode binary seems to be a decent
+            # proxy for a quick detection. It should be far less than 1 MB in
+            # Release builds.
+            return os.path.getsize(os.environ["LLDBVSCODE_EXEC"]) < 1000000
+        except:
+            return False
+
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_basic_functionality(self):
+        '''
+            Test basic restarting functionality when the process is running in
+            a terminal.
+        '''
+        if not self.isTestSupported():
+            return
+        line_A = line_number('main.c', '// breakpoint A')
+        line_B = line_number('main.c', '// breakpoint B')
+
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, runInTerminal=True)
+        [bp_A, bp_B] = self.set_source_breakpoints('main.c', [line_A, line_B])
+
+        # Verify we hit A, then B.
+        self.vscode.request_configurationDone()
+        self.verify_breakpoint_hit([bp_A])
+        self.vscode.request_continue()
+        self.verify_breakpoint_hit([bp_B])
+
+        # Make sure i has been modified from its initial value of 0.
+        self.assertEquals(int(self.vscode.get_local_variable_value('i')),
+                          1234, 'i != 1234 after hitting breakpoint B')
+
+        # Restart.
+        self.vscode.request_restart()
+
+        # Finally, check we stop back at A and program state has been reset.
+        self.verify_breakpoint_hit([bp_A])
+        self.assertEquals(int(self.vscode.get_local_variable_value('i')),
+                          0, 'i != 0 after hitting breakpoint A on restart')
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_stopOnEntry(self):
+        '''
+            Check that stopOnEntry works correctly when using runInTerminal.
+        '''
+        if not self.isTestSupported():
+            return
+        line_A = line_number('main.c', '// breakpoint A')
+        line_B = line_number('main.c', '// breakpoint B')
+
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, runInTerminal=True, stopOnEntry=True)
+        [bp_main] = self.set_function_breakpoints(['main'])
+        self.vscode.request_configurationDone()
+
+        # When using stopOnEntry, configurationDone doesn't result in a running
+        # process, we should immediately get a stopped event instead.
+        stopped_events = self.vscode.wait_for_stopped()
+        # We should be stopped at the entry point.
+        for stopped_event in stopped_events:
+            if 'body' in stopped_event:
+                body = stopped_event['body']
+                if 'reason' in body:
+                    reason = body['reason']
+                    self.assertNotEqual(
+                        reason, 'breakpoint',
+                        'verify stop isn\'t a breakpoint')
+
+        # Then, if we continue, we should hit the breakpoint at main.
+        self.vscode.request_continue()
+        self.verify_breakpoint_hit([bp_main])
+
+        # Restart and check that we still get a stopped event before reaching
+        # main.
+        self.vscode.request_restart()
+        stopped_events = self.vscode.wait_for_stopped()
+        for stopped_event in stopped_events:
+            if 'body' in stopped_event:
+                body = stopped_event['body']
+                if 'reason' in body:
+                    reason = body['reason']
+                    self.assertNotEqual(
+                        reason, 'breakpoint',
+                        'verify stop after restart isn\'t "main" breakpoint')
+

diff  --git a/lldb/test/API/tools/lldb-vscode/restart/main.c b/lldb/test/API/tools/lldb-vscode/restart/main.c
new file mode 100644
index 0000000000000..710af8e975020
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/restart/main.c
@@ -0,0 +1,9 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[], char const *envp[]) {
+  int i = 0;
+  printf("Do something\n"); // breakpoint A
+  printf("Do something else\n");
+  i = 1234;
+  return 0;  // breakpoint B
+}

diff  --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp
index 6cbdbfa3c24ef..ca1a626e514c9 100644
--- a/lldb/tools/lldb-vscode/VSCode.cpp
+++ b/lldb/tools/lldb-vscode/VSCode.cpp
@@ -39,8 +39,10 @@ VSCode::VSCode()
            {"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), is_attach(false), configuration_done_sent(false),
-      reverse_request_seq(0), waiting_for_run_in_terminal(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),
       progress_event_reporter(
           [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }) {
   const char *log_file_path = getenv("LLDBVSCODE_LOG");

diff  --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h
index aa325038b2e79..48bef18de2a23 100644
--- a/lldb/tools/lldb-vscode/VSCode.h
+++ b/lldb/tools/lldb-vscode/VSCode.h
@@ -15,6 +15,7 @@
 #include <cstdio>
 #include <iosfwd>
 #include <map>
+#include <optional>
 #include <set>
 #include <thread>
 
@@ -141,10 +142,17 @@ struct VSCode {
   std::vector<std::string> exit_commands;
   std::vector<std::string> stop_commands;
   std::vector<std::string> terminate_commands;
+  // A copy of the last LaunchRequest or AttachRequest so we can reuse its
+  // 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;
   bool stop_at_entry;
   bool is_attach;
+  // The process event thread normally responds to process exited events by
+  // shutting down the entire adapter. When we're restarting, we keep the id of
+  // 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;

diff  --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp
index 8257a1ffbe9b7..f56a5368939e2 100644
--- a/lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -458,7 +458,8 @@ void EventThreadFunction() {
             // manually send a stopped event in request_configurationDone(...)
             // so don't send any before then.
             if (g_vsc.configuration_done_sent) {
-              // Only report a stopped event if the process was not restarted.
+              // Only report a stopped event if the process was not
+              // automatically restarted.
               if (!lldb::SBProcess::GetRestartedFromEvent(event)) {
                 SendStdOutStdErr(process);
                 SendThreadStoppedEvent();
@@ -468,14 +469,22 @@ void EventThreadFunction() {
           case lldb::eStateRunning:
             g_vsc.WillContinue();
             break;
-          case lldb::eStateExited: {
-            // Run any exit LLDB commands the user specified in the
-            // launch.json
-            g_vsc.RunExitCommands();
-            SendProcessExitedEvent(process);
-            SendTerminatedEvent();
-            done = true;
-          } break;
+          case lldb::eStateExited:
+            // When restarting, we can get an "exited" event for the process we
+            // just killed with the old PID, or even with no PID. In that case
+            // we don't have to terminate the session.
+            if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID ||
+                process.GetProcessID() == g_vsc.restarting_process_id) {
+              g_vsc.restarting_process_id = LLDB_INVALID_PROCESS_ID;
+            } else {
+              // Run any exit LLDB commands the user specified in the
+              // launch.json
+              g_vsc.RunExitCommands();
+              SendProcessExitedEvent(process);
+              SendTerminatedEvent();
+              done = true;
+            }
+            break;
           }
         } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) ||
                    (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) {
@@ -592,6 +601,7 @@ void SetSourceMapFromArguments(const llvm::json::Object &arguments) {
 // }
 void request_attach(const llvm::json::Object &request) {
   g_vsc.is_attach = true;
+  g_vsc.last_launch_or_attach_request = request;
   llvm::json::Object response;
   lldb::SBError error;
   FillResponse(request, response);
@@ -1527,7 +1537,7 @@ void request_initialize(const llvm::json::Object &request) {
   // The debug adapter supports the RestartRequest. In this case a client
   // should not implement 'restart' by terminating and relaunching the adapter
   // but by calling the RestartRequest.
-  body.try_emplace("supportsRestartRequest", false);
+  body.try_emplace("supportsRestartRequest", true);
   // The debug adapter supports 'exceptionOptions' on the
   // setExceptionBreakpoints request.
   body.try_emplace("supportsExceptionOptions", true);
@@ -1622,6 +1632,71 @@ llvm::Error request_runInTerminal(const llvm::json::Object &launch_request) {
                                  error.GetCString());
 }
 
+// Takes a LaunchRequest object and launches the process, also handling
+// runInTerminal if applicable. It doesn't do any of the additional
+// initialization and bookkeeping stuff that is needed for `request_launch`.
+// This way we can reuse the process launching logic for RestartRequest too.
+lldb::SBError LaunchProcess(const llvm::json::Object &request) {
+  lldb::SBError error;
+  auto arguments = request.getObject("arguments");
+  auto launchCommands = GetStrings(arguments, "launchCommands");
+
+  // Instantiate a launch info instance for the target.
+  auto launch_info = g_vsc.target.GetLaunchInfo();
+
+  // Grab the current working directory if there is one and set it in the
+  // launch info.
+  const auto cwd = GetString(arguments, "cwd");
+  if (!cwd.empty())
+    launch_info.SetWorkingDirectory(cwd.data());
+
+  // Extract any extra arguments and append them to our program arguments for
+  // when we launch
+  auto args = GetStrings(arguments, "args");
+  if (!args.empty())
+    launch_info.SetArguments(MakeArgv(args).data(), true);
+
+  // Pass any environment variables along that the user specified.
+  auto envs = GetStrings(arguments, "env");
+  if (!envs.empty())
+    launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true);
+
+  auto flags = launch_info.GetLaunchFlags();
+
+  if (GetBoolean(arguments, "disableASLR", true))
+    flags |= lldb::eLaunchFlagDisableASLR;
+  if (GetBoolean(arguments, "disableSTDIO", false))
+    flags |= lldb::eLaunchFlagDisableSTDIO;
+  if (GetBoolean(arguments, "shellExpandArguments", false))
+    flags |= lldb::eLaunchFlagShellExpandArguments;
+  const bool detachOnError = GetBoolean(arguments, "detachOnError", false);
+  launch_info.SetDetachOnError(detachOnError);
+  launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug |
+                             lldb::eLaunchFlagStopAtEntry);
+  const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
+
+  if (GetBoolean(arguments, "runInTerminal", false)) {
+    if (llvm::Error err = request_runInTerminal(request))
+      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
+    // the launch call and the launch will happen synchronously
+    g_vsc.debugger.SetAsync(false);
+    g_vsc.target.Launch(launch_info, error);
+    g_vsc.debugger.SetAsync(true);
+  } else {
+    g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands);
+    // The custom commands might have created a new target so we should use the
+    // selected target after these commands are run.
+    g_vsc.target = g_vsc.debugger.GetSelectedTarget();
+    // Make sure the process is launched and stopped at the entry point before
+    // proceeding as the the launch commands are not run using the synchronous
+    // mode.
+    error = g_vsc.WaitForProcessToStop(timeout_seconds);
+  }
+  return error;
+}
+
 // "LaunchRequest": {
 //   "allOf": [ { "$ref": "#/definitions/Request" }, {
 //     "type": "object",
@@ -1658,8 +1733,8 @@ llvm::Error request_runInTerminal(const llvm::json::Object &launch_request) {
 // }
 void request_launch(const llvm::json::Object &request) {
   g_vsc.is_attach = false;
+  g_vsc.last_launch_or_attach_request = request;
   llvm::json::Object response;
-  lldb::SBError error;
   FillResponse(request, response);
   auto arguments = request.getObject("arguments");
   g_vsc.init_commands = GetStrings(arguments, "initCommands");
@@ -1667,12 +1742,10 @@ void request_launch(const llvm::json::Object &request) {
   g_vsc.stop_commands = GetStrings(arguments, "stopCommands");
   g_vsc.exit_commands = GetStrings(arguments, "exitCommands");
   g_vsc.terminate_commands = GetStrings(arguments, "terminateCommands");
-  auto launchCommands = GetStrings(arguments, "launchCommands");
   std::vector<std::string> postRunCommands =
       GetStrings(arguments, "postRunCommands");
   g_vsc.stop_at_entry = GetBoolean(arguments, "stopOnEntry", false);
   const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot");
-  const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
 
   // This is a hack for loading DWARF in .o files on Mac where the .o files
   // in the debug map of the main executable have relative paths which require
@@ -1697,76 +1770,27 @@ void request_launch(const llvm::json::Object &request) {
     return;
   }
 
-  // Instantiate a launch info instance for the target.
-  auto launch_info = g_vsc.target.GetLaunchInfo();
-
-  // Grab the current working directory if there is one and set it in the
-  // launch info.
-  const auto cwd = GetString(arguments, "cwd");
-  if (!cwd.empty())
-    launch_info.SetWorkingDirectory(cwd.data());
-
-  // Extract any extra arguments and append them to our program arguments for
-  // when we launch
-  auto args = GetStrings(arguments, "args");
-  if (!args.empty())
-    launch_info.SetArguments(MakeArgv(args).data(), true);
-
-  // Pass any environment variables along that the user specified.
-  auto envs = GetStrings(arguments, "env");
-  if (!envs.empty())
-    launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true);
-
-  auto flags = launch_info.GetLaunchFlags();
-
-  if (GetBoolean(arguments, "disableASLR", true))
-    flags |= lldb::eLaunchFlagDisableASLR;
-  if (GetBoolean(arguments, "disableSTDIO", false))
-    flags |= lldb::eLaunchFlagDisableSTDIO;
-  if (GetBoolean(arguments, "shellExpandArguments", false))
-    flags |= lldb::eLaunchFlagShellExpandArguments;
-  const bool detatchOnError = GetBoolean(arguments, "detachOnError", false);
-  launch_info.SetDetachOnError(detatchOnError);
-  launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug |
-                             lldb::eLaunchFlagStopAtEntry);
-
   // Run any pre run LLDB commands the user specified in the launch.json
   g_vsc.RunPreRunCommands();
 
-  if (GetBoolean(arguments, "runInTerminal", false)) {
-    if (llvm::Error err = request_runInTerminal(request))
-      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
-    // the launch call and the launch will happen synchronously
-    g_vsc.debugger.SetAsync(false);
-    g_vsc.target.Launch(launch_info, error);
-    g_vsc.debugger.SetAsync(true);
-  } else {
-    g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands);
-    // The custom commands might have created a new target so we should use the
-    // selected target after these commands are run.
-    g_vsc.target = g_vsc.debugger.GetSelectedTarget();
-    // Make sure the process is launched and stopped at the entry point before
-    // proceeding as the the launch commands are not run using the synchronous
-    // mode.
-    error = g_vsc.WaitForProcessToStop(timeout_seconds);
-  }
+  status = LaunchProcess(request);
 
-  if (error.Fail()) {
+  if (status.Fail()) {
     response["success"] = llvm::json::Value(false);
-    EmplaceSafeString(response, "message", std::string(error.GetCString()));
+    EmplaceSafeString(response, "message", std::string(status.GetCString()));
   } else {
     g_vsc.RunLLDBCommands("Running postRunCommands:", postRunCommands);
   }
 
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 
-  if (g_vsc.is_attach)
-    SendProcessEvent(Attach); // this happens when doing runInTerminal
-  else
-    SendProcessEvent(Launch);
-  g_vsc.SendJSON(llvm::json::Value(CreateEventObject("initialized")));
+  if (!status.Fail()) {
+    if (g_vsc.is_attach)
+      SendProcessEvent(Attach);  // this happens when doing runInTerminal
+    else
+      SendProcessEvent(Launch);
+  }
+  g_vsc.SendJSON(CreateEventObject("initialized"));
 }
 
 // "NextRequest": {
@@ -1867,6 +1891,118 @@ void request_pause(const llvm::json::Object &request) {
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 }
 
+
+// "RestartRequest": {
+//   "allOf": [ { "$ref": "#/definitions/Request" }, {
+//     "type": "object",
+//     "description": "Restarts a debug session. Clients should only call this
+//     request if the corresponding capability `supportsRestartRequest` is
+//     true.\nIf the capability is missing or has the value false, a typical
+//     client emulates `restart` by terminating the debug adapter first and then
+//     launching it anew.",
+//     "properties": {
+//       "command": {
+//         "type": "string",
+//         "enum": [ "restart" ]
+//       },
+//       "arguments": {
+//         "$ref": "#/definitions/RestartArguments"
+//       }
+//     },
+//     "required": [ "command" ]
+//   }]
+// },
+// "RestartArguments": {
+//   "type": "object",
+//   "description": "Arguments for `restart` request.",
+//   "properties": {
+//     "arguments": {
+//       "oneOf": [
+//         { "$ref": "#/definitions/LaunchRequestArguments" },
+//         { "$ref": "#/definitions/AttachRequestArguments" }
+//       ],
+//       "description": "The latest version of the `launch` or `attach`
+//       configuration."
+//     }
+//   }
+// },
+// "RestartResponse": {
+//   "allOf": [ { "$ref": "#/definitions/Response" }, {
+//     "type": "object",
+//     "description": "Response to `restart` request. This is just an
+//     acknowledgement, so no body field is required."
+//   }]
+// },
+void request_restart(const llvm::json::Object &request) {
+  llvm::json::Object response;
+  FillResponse(request, response);
+  if (!g_vsc.last_launch_or_attach_request) {
+    response["success"] = llvm::json::Value(false);
+    EmplaceSafeString(response, "message",
+                      "Restart request received but no process was launched.");
+    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
+  // Check if we were in a "launch" session or an "attach" session.
+  //
+  // Restarting is not well defined when we started the session by attaching to
+  // an existing process, because we don't know how the process was started, so
+  // we don't support it.
+  //
+  // Note that when using runInTerminal we're technically attached, but it's an
+  // implementation detail. The adapter *did* launch the process in response to
+  // a "launch" command, so we can still stop it and re-run it. This is why we
+  // don't just check `g_vsc.is_attach`.
+  if (GetString(*g_vsc.last_launch_or_attach_request, "command") == "attach") {
+    response["success"] = llvm::json::Value(false);
+    EmplaceSafeString(response, "message",
+                      "Restarting an \"attach\" session is not supported.");
+    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
+
+  // The optional `arguments` field in RestartRequest can contain an updated
+  // version of the launch arguments. If there's one, use it.
+  auto request_arguments = request.getObject("arguments");
+  if (request_arguments) {
+    llvm::json::Object arguments = *request_arguments;
+    (*g_vsc.last_launch_or_attach_request)["arguments"] =
+        llvm::json::Value(std::move(arguments));
+  }
+
+  // Keep track of the old PID so when we get a "process exited" event from the
+  // killed process we can detect it and not shut down the whole session.
+  lldb::SBProcess process = g_vsc.target.GetProcess();
+  g_vsc.restarting_process_id = process.GetProcessID();
+
+  // Stop the current process if necessary. The logic here is similar to
+  // CommandObjectProcessLaunchOrAttach::StopProcessIfNecessary, except that
+  // we don't ask the user for confirmation.
+  g_vsc.debugger.SetAsync(false);
+  if (process.IsValid()) {
+    lldb::StateType state = process.GetState();
+    if (state != lldb::eStateConnected) {
+      process.Kill();
+    }
+    // Clear the list of thread ids to avoid sending "thread exited" events
+    // for threads of the process we are terminating.
+    g_vsc.thread_ids.clear();
+  }
+  g_vsc.debugger.SetAsync(true);
+  LaunchProcess(*g_vsc.last_launch_or_attach_request);
+
+  // This is normally done after receiving a "configuration done" request.
+  // Because we're restarting, configuration has already happened so we can
+  // continue the process right away.
+  if (g_vsc.stop_at_entry) {
+    SendThreadStoppedEvent();
+  } else {
+    g_vsc.target.GetProcess().Continue();
+  }
+
+  g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+}
+
 // "ScopesRequest": {
 //   "allOf": [ { "$ref": "#/definitions/Request" }, {
 //     "type": "object",
@@ -3084,6 +3220,7 @@ void RegisterRequestCallbacks() {
   g_vsc.RegisterRequestCallback("launch", request_launch);
   g_vsc.RegisterRequestCallback("next", request_next);
   g_vsc.RegisterRequestCallback("pause", request_pause);
+  g_vsc.RegisterRequestCallback("restart", request_restart);
   g_vsc.RegisterRequestCallback("scopes", request_scopes);
   g_vsc.RegisterRequestCallback("setBreakpoints", request_setBreakpoints);
   g_vsc.RegisterRequestCallback("setExceptionBreakpoints",


        


More information about the lldb-commits mailing list