Author: Robert O'Callahan
Date: 2025-04-23T16:16:30-07:00
New Revision: 239718055d7260caa3e6631e82d68ac27e01c1f4

URL: 
https://github.com/llvm/llvm-project/commit/239718055d7260caa3e6631e82d68ac27e01c1f4
DIFF: 
https://github.com/llvm/llvm-project/commit/239718055d7260caa3e6631e82d68ac27e01c1f4.diff

LOG: [lldb] Implement CLI support for reverse-continue (#132783)

This introduces the options "-F/--forward" and "-R/--reverse" to
`process continue`.

These only work if you're running with a gdbserver backend that supports
reverse execution, such as rr. For testing we rely on the fake
reverse-execution functionality in `lldbreverse.py`.

Added: 
    lldb/test/API/commands/process/reverse-continue/Makefile
    lldb/test/API/commands/process/reverse-continue/TestReverseContinue.py
    
lldb/test/API/commands/process/reverse-continue/TestReverseContinueNotSupported.py
    lldb/test/API/commands/process/reverse-continue/main.c

Modified: 
    lldb/source/Commands/CommandObjectProcess.cpp
    lldb/source/Commands/Options.td
    llvm/docs/ReleaseNotes.md

Removed: 
    


################################################################################
diff  --git a/lldb/source/Commands/CommandObjectProcess.cpp 
b/lldb/source/Commands/CommandObjectProcess.cpp
index 654dfa83ea444..ed80c854ed66e 100644
--- a/lldb/source/Commands/CommandObjectProcess.cpp
+++ b/lldb/source/Commands/CommandObjectProcess.cpp
@@ -468,7 +468,13 @@ class CommandObjectProcessContinue : public 
CommandObjectParsed {
       case 'b':
         m_run_to_bkpt_args.AppendArgument(option_arg);
         m_any_bkpts_specified = true;
-      break;
+        break;
+      case 'F':
+        m_base_direction = lldb::RunDirection::eRunForward;
+        break;
+      case 'R':
+        m_base_direction = lldb::RunDirection::eRunReverse;
+        break;
       default:
         llvm_unreachable("Unimplemented option");
       }
@@ -479,6 +485,7 @@ class CommandObjectProcessContinue : public 
CommandObjectParsed {
       m_ignore = 0;
       m_run_to_bkpt_args.Clear();
       m_any_bkpts_specified = false;
+      m_base_direction = std::nullopt;
     }
 
     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -488,6 +495,7 @@ class CommandObjectProcessContinue : public 
CommandObjectParsed {
     uint32_t m_ignore = 0;
     Args m_run_to_bkpt_args;
     bool m_any_bkpts_specified = false;
+    std::optional<lldb::RunDirection> m_base_direction;
   };
 
   void DoExecute(Args &command, CommandReturnObject &result) override {
@@ -654,6 +662,9 @@ class CommandObjectProcessContinue : public 
CommandObjectParsed {
         }
       }
 
+      if (m_options.m_base_direction.has_value())
+        process->SetBaseDirection(*m_options.m_base_direction);
+
       const uint32_t iohandler_id = process->GetIOHandlerID();
 
       StreamString stream;

diff  --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index cc579d767eb06..53864ff29327d 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -737,13 +737,17 @@ let Command = "process attach" in {
 }
 
 let Command = "process continue" in {
-  def process_continue_ignore_count : Option<"ignore-count", "i">, Group<1>,
+  def process_continue_ignore_count : Option<"ignore-count", "i">, 
Groups<[1,2]>,
     Arg<"UnsignedInteger">, Desc<"Ignore <N> crossings of the breakpoint (if 
it"
     " exists) for the currently selected thread.">;
-  def process_continue_run_to_bkpt : Option<"continue-to-bkpt", "b">, Group<2>,
+  def process_continue_run_to_bkpt : Option<"continue-to-bkpt", "b">, 
Groups<[3,4]>,
     Arg<"BreakpointIDRange">, Desc<"Specify a breakpoint to continue to, 
temporarily "
     "ignoring other breakpoints.  Can be specified more than once.  "
     "The continue action will be done synchronously if this option is 
specified.">;
+  def thread_continue_forward : Option<"forward", "F">, Groups<[1,3]>,
+    Desc<"Set the direction to forward before continuing.">;
+  def thread_continue_reverse : Option<"reverse", "R">, Groups<[2,4]>,
+    Desc<"Set the direction to reverse before continuing.">;
 }
 
 let Command = "process detach" in {

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

diff  --git 
a/lldb/test/API/commands/process/reverse-continue/TestReverseContinue.py 
b/lldb/test/API/commands/process/reverse-continue/TestReverseContinue.py
new file mode 100644
index 0000000000000..c04d2b9d4b5a5
--- /dev/null
+++ b/lldb/test/API/commands/process/reverse-continue/TestReverseContinue.py
@@ -0,0 +1,66 @@
+"""
+Test the "process continue --reverse" and "--forward" options.
+"""
+
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test.gdbclientutils import *
+from lldbsuite.test.lldbreverse import ReverseTestBase
+from lldbsuite.test import lldbutil
+
+
+class TestReverseContinue(ReverseTestBase):
+    @skipIfRemote
+    def test_reverse_continue(self):
+        target, _, _ = self.setup_recording()
+
+        # Set breakpoint and reverse-continue
+        trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", 
None)
+        self.assertTrue(trigger_bkpt.GetNumLocations() > 0)
+        self.expect(
+            "process continue --reverse",
+            substrs=["stop reason = breakpoint 
{0}.1".format(trigger_bkpt.GetID())],
+        )
+        # `process continue` should preserve current base direction.
+        self.expect(
+            "process continue",
+            STOPPED_DUE_TO_HISTORY_BOUNDARY,
+            substrs=["stopped", "stop reason = history boundary"],
+        )
+        self.expect(
+            "process continue --forward",
+            substrs=["stop reason = breakpoint 
{0}.1".format(trigger_bkpt.GetID())],
+        )
+
+    def setup_recording(self):
+        """
+        Record execution of code between "start_recording" and 
"stop_recording" breakpoints.
+
+        Returns with the target stopped at "stop_recording", with recording 
disabled,
+        ready to reverse-execute.
+        """
+        self.build()
+        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        process = self.connect(target)
+
+        # Record execution from the start of the function "start_recording"
+        # to the start of the function "stop_recording". We want to keep the
+        # interval that we record as small as possible to minimize the run-time
+        # of our single-stepping recorder.
+        start_recording_bkpt = 
target.BreakpointCreateByName("start_recording", None)
+        self.assertTrue(start_recording_bkpt.GetNumLocations() > 0)
+        initial_threads = lldbutil.continue_to_breakpoint(process, 
start_recording_bkpt)
+        self.assertEqual(len(initial_threads), 1)
+        target.BreakpointDelete(start_recording_bkpt.GetID())
+        self.start_recording()
+        stop_recording_bkpt = target.BreakpointCreateByName("stop_recording", 
None)
+        self.assertTrue(stop_recording_bkpt.GetNumLocations() > 0)
+        lldbutil.continue_to_breakpoint(process, stop_recording_bkpt)
+        target.BreakpointDelete(stop_recording_bkpt.GetID())
+        self.stop_recording()
+
+        self.dbg.SetAsync(False)
+
+        return target, process, initial_threads

diff  --git 
a/lldb/test/API/commands/process/reverse-continue/TestReverseContinueNotSupported.py
 
b/lldb/test/API/commands/process/reverse-continue/TestReverseContinueNotSupported.py
new file mode 100644
index 0000000000000..3d318120b55cd
--- /dev/null
+++ 
b/lldb/test/API/commands/process/reverse-continue/TestReverseContinueNotSupported.py
@@ -0,0 +1,51 @@
+"""
+Test the "process continue --reverse" and "--forward" options
+when reverse-continue is not supported.
+"""
+
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+
+class TestReverseContinueNotSupported(TestBase):
+    def test_reverse_continue_not_supported(self):
+        target = self.connect()
+
+        # Set breakpoint and reverse-continue
+        trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", 
None)
+        self.assertTrue(trigger_bkpt, VALID_BREAKPOINT)
+        # `process continue --forward` should work.
+        self.expect(
+            "process continue --forward",
+            substrs=["stop reason = breakpoint 
{0}.1".format(trigger_bkpt.GetID())],
+        )
+        self.expect(
+            "process continue --reverse",
+            error=True,
+            substrs=["target does not support reverse-continue"],
+        )
+
+    def test_reverse_continue_forward_and_reverse(self):
+        self.connect()
+
+        self.expect(
+            "process continue --forward --reverse",
+            error=True,
+            substrs=["invalid combination of options for the given command"],
+        )
+
+    def connect(self):
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        main_bkpt = target.BreakpointCreateByName("main", None)
+        self.assertTrue(main_bkpt, VALID_BREAKPOINT)
+
+        process = target.LaunchSimple(None, None, 
self.get_process_working_directory())
+        self.assertTrue(process, PROCESS_IS_VALID)
+        return target

diff  --git a/lldb/test/API/commands/process/reverse-continue/main.c 
b/lldb/test/API/commands/process/reverse-continue/main.c
new file mode 100644
index 0000000000000..ccec2bb27658d
--- /dev/null
+++ b/lldb/test/API/commands/process/reverse-continue/main.c
@@ -0,0 +1,12 @@
+static void start_recording() {}
+
+static void trigger_breakpoint() {}
+
+static void stop_recording() {}
+
+int main() {
+  start_recording();
+  trigger_breakpoint();
+  stop_recording();
+  return 0;
+}

diff  --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index a3f91224ca24e..6f6ecc8924438 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -235,6 +235,10 @@ Changes to LLDB
 * The `min-gdbserver-port` and `max-gdbserver-port` options have been removed
   from `lldb-server`'s platform mode. Since the changes to `lldb-server`'s port
   handling in LLDB 20, these options have had no effect.
+* LLDB now supports `process continue --reverse` when used with debug servers
+  supporting reverse execution, such as [rr](https://rr-project.org).
+  When using reverse execution, `process continue --forward` returns to the
+  forward execution.
 
 ### Changes to lldb-dap
 


        
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to