https://github.com/rocallahan updated https://github.com/llvm/llvm-project/pull/132783
>From 87af424e911ac5b22ee75d801390d29b9b3f367a Mon Sep 17 00:00:00 2001 From: Robert O'Callahan <rocalla...@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 | 13 +++- lldb/source/Commands/Options.td | 8 ++- .../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, 152 insertions(+), 3 deletions(-) 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..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 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 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits