Skip to content

Commit 0067c5c

Browse files
committed
[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`.
1 parent 00cb966 commit 0067c5c

File tree

5 files changed

+109
-1
lines changed

5 files changed

+109
-1
lines changed

lldb/source/Commands/CommandObjectProcess.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,23 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
468468
case 'b':
469469
m_run_to_bkpt_args.AppendArgument(option_arg);
470470
m_any_bkpts_specified = true;
471-
break;
471+
break;
472+
case 'F':
473+
if (m_base_direction == lldb::RunDirection::eRunReverse) {
474+
error = Status::FromErrorString(
475+
"cannot specify both 'forward' and 'reverse'");
476+
break;
477+
}
478+
m_base_direction = lldb::RunDirection::eRunForward;
479+
break;
480+
case 'R':
481+
if (m_base_direction == lldb::RunDirection::eRunForward) {
482+
error = Status::FromErrorString(
483+
"cannot specify both 'forward' and 'reverse'");
484+
break;
485+
}
486+
m_base_direction = lldb::RunDirection::eRunReverse;
487+
break;
472488
default:
473489
llvm_unreachable("Unimplemented option");
474490
}
@@ -479,6 +495,7 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
479495
m_ignore = 0;
480496
m_run_to_bkpt_args.Clear();
481497
m_any_bkpts_specified = false;
498+
m_base_direction = std::nullopt;
482499
}
483500

484501
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -488,6 +505,7 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
488505
uint32_t m_ignore = 0;
489506
Args m_run_to_bkpt_args;
490507
bool m_any_bkpts_specified = false;
508+
std::optional<lldb::RunDirection> m_base_direction;
491509
};
492510

493511
void DoExecute(Args &command, CommandReturnObject &result) override {
@@ -654,6 +672,10 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
654672
}
655673
}
656674

675+
if (m_options.m_base_direction.has_value()) {
676+
process->SetBaseDirection(*m_options.m_base_direction);
677+
}
678+
657679
const uint32_t iohandler_id = process->GetIOHandlerID();
658680

659681
StreamString stream;

lldb/source/Commands/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,10 @@ let Command = "process continue" in {
744744
Arg<"BreakpointIDRange">, Desc<"Specify a breakpoint to continue to, temporarily "
745745
"ignoring other breakpoints. Can be specified more than once. "
746746
"The continue action will be done synchronously if this option is specified.">;
747+
def thread_continue_forward : Option<"forward", "F">, Group<3>,
748+
Desc<"Execute in forward direction">;
749+
def thread_continue_reverse : Option<"reverse", "R">, Group<3>,
750+
Desc<"Execute in forward direction">;
747751
}
748752

749753
let Command = "process detach" in {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
Test the "process continue --reverse" and "--forward" options.
3+
"""
4+
5+
6+
import lldb
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.gdbclientutils import *
10+
from lldbsuite.test.lldbreverse import ReverseTestBase
11+
from lldbsuite.test import lldbutil
12+
13+
14+
class TestReverseContinue(ReverseTestBase):
15+
@skipIfRemote
16+
@skipIf(macos_version=["<", "15.0"])
17+
def test_reverse_continue(self):
18+
target, process, initial_threads = self.setup_recording()
19+
20+
# Set breakpoint and reverse-continue
21+
trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
22+
self.assertTrue(trigger_bkpt.GetNumLocations() > 0)
23+
self.expect(
24+
"process continue --reverse",
25+
substrs=["stop reason = breakpoint {0}.1".format(trigger_bkpt.GetID())],
26+
)
27+
# `process continue` should preserve current base direction.
28+
self.expect(
29+
"process continue",
30+
STOPPED_DUE_TO_HISTORY_BOUNDARY,
31+
substrs=["stopped", "stop reason = history boundary"],
32+
)
33+
self.expect(
34+
"process continue --forward",
35+
substrs=["stop reason = breakpoint {0}.1".format(trigger_bkpt.GetID())],
36+
)
37+
38+
def setup_recording(self):
39+
"""
40+
Record execution of code between "start_recording" and "stop_recording" breakpoints.
41+
42+
Returns with the target stopped at "stop_recording", with recording disabled,
43+
ready to reverse-execute.
44+
"""
45+
self.build()
46+
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
47+
process = self.connect(target)
48+
49+
# Record execution from the start of the function "start_recording"
50+
# to the start of the function "stop_recording". We want to keep the
51+
# interval that we record as small as possible to minimize the run-time
52+
# of our single-stepping recorder.
53+
start_recording_bkpt = target.BreakpointCreateByName("start_recording", None)
54+
self.assertTrue(start_recording_bkpt.GetNumLocations() > 0)
55+
initial_threads = lldbutil.continue_to_breakpoint(process, start_recording_bkpt)
56+
self.assertEqual(len(initial_threads), 1)
57+
target.BreakpointDelete(start_recording_bkpt.GetID())
58+
self.start_recording()
59+
stop_recording_bkpt = target.BreakpointCreateByName("stop_recording", None)
60+
self.assertTrue(stop_recording_bkpt.GetNumLocations() > 0)
61+
lldbutil.continue_to_breakpoint(process, stop_recording_bkpt)
62+
target.BreakpointDelete(stop_recording_bkpt.GetID())
63+
self.stop_recording()
64+
65+
self.dbg.SetAsync(False)
66+
67+
return target, process, initial_threads
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
static void start_recording() {}
2+
3+
static void trigger_breakpoint() {}
4+
5+
static void stop_recording() {}
6+
7+
int main() {
8+
start_recording();
9+
trigger_breakpoint();
10+
stop_recording();
11+
return 0;
12+
}

0 commit comments

Comments
 (0)