[Lldb-commits] [lldb] [llvm] [lldb] Implement CLI support for reverse-continue (PR #132783)
Robert O'Callahan via lldb-commits
lldb-commits at lists.llvm.org
Sun Mar 30 16:49:45 PDT 2025
https://github.com/rocallahan updated https://github.com/llvm/llvm-project/pull/132783
>From 9bf6c80782adc8b76e50880ea0a2eea897274c25 Mon Sep 17 00:00:00 2001
From: Robert O'Callahan <rocallahan at google.com>
Date: Fri, 19 Jul 2024 22:48:14 +1200
Subject: [PATCH] [lldb] Implement CLI support for reverse-continue
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`.
---
lldb/source/Commands/CommandObjectProcess.cpp | 23 ++++++-
lldb/source/Commands/Options.td | 4 ++
.../process/reverse-continue/Makefile | 3 +
.../reverse-continue/TestReverseContinue.py | 66 +++++++++++++++++++
.../TestReverseContinueNotSupported.py | 51 ++++++++++++++
.../commands/process/reverse-continue/main.c | 12 ++++
llvm/docs/ReleaseNotes.md | 2 +
7 files changed, 160 insertions(+), 1 deletion(-)
create mode 100644 lldb/test/API/commands/process/reverse-continue/Makefile
create mode 100644 lldb/test/API/commands/process/reverse-continue/TestReverseContinue.py
create mode 100644 lldb/test/API/commands/process/reverse-continue/TestReverseContinueNotSupported.py
create mode 100644 lldb/test/API/commands/process/reverse-continue/main.c
diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp
index 654dfa83ea444..463f4698a985f 100644
--- a/lldb/source/Commands/CommandObjectProcess.cpp
+++ b/lldb/source/Commands/CommandObjectProcess.cpp
@@ -468,7 +468,23 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
case 'b':
m_run_to_bkpt_args.AppendArgument(option_arg);
m_any_bkpts_specified = true;
- break;
+ break;
+ case 'F':
+ if (m_base_direction == lldb::RunDirection::eRunReverse) {
+ error = Status::FromErrorString(
+ "cannot specify both 'forward' and 'reverse'");
+ break;
+ }
+ m_base_direction = lldb::RunDirection::eRunForward;
+ break;
+ case 'R':
+ if (m_base_direction == lldb::RunDirection::eRunForward) {
+ error = Status::FromErrorString(
+ "cannot specify both 'forward' and 'reverse'");
+ break;
+ }
+ m_base_direction = lldb::RunDirection::eRunReverse;
+ break;
default:
llvm_unreachable("Unimplemented option");
}
@@ -479,6 +495,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 +505,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 +672,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..e8c340b85aa92 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -744,6 +744,10 @@ let Command = "process continue" in {
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">, Group<3>,
+ Desc<"Execute in forward direction">;
+ def thread_continue_reverse : Option<"reverse", "R">, Group<3>,
+ Desc<"Execute in reverse direction">;
}
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..440e908eca344
--- /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=["cannot specify both 'forward' and 'reverse'"],
+ )
+
+ 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 b989f477be6b5..a43107b365b35 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -220,6 +220,8 @@ Changes to LLDB
information about the current state of the debugger at the bottom of the
terminal. This is on by default and can be configured using the
`show-statusline` and `statusline-format` settings.
+* LLDB now supports `process continue --reverse` when used with backends
+ supporting reverse execution, such as [rr](https://rr-project.org).
### Changes to lldb-dap
More information about the lldb-commits
mailing list