Skip to content

Commit 9bf6c80

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 06cb7b1 commit 9bf6c80

File tree

7 files changed

+160
-1
lines changed

7 files changed

+160
-1
lines changed

lldb/source/Commands/CommandObjectProcess.cpp

Lines changed: 22 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,9 @@ 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+
657678
const uint32_t iohandler_id = process->GetIOHandlerID();
658679

659680
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 reverse 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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
def test_reverse_continue(self):
17+
target, _, _ = self.setup_recording()
18+
19+
# Set breakpoint and reverse-continue
20+
trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
21+
self.assertTrue(trigger_bkpt.GetNumLocations() > 0)
22+
self.expect(
23+
"process continue --reverse",
24+
substrs=["stop reason = breakpoint {0}.1".format(trigger_bkpt.GetID())],
25+
)
26+
# `process continue` should preserve current base direction.
27+
self.expect(
28+
"process continue",
29+
STOPPED_DUE_TO_HISTORY_BOUNDARY,
30+
substrs=["stopped", "stop reason = history boundary"],
31+
)
32+
self.expect(
33+
"process continue --forward",
34+
substrs=["stop reason = breakpoint {0}.1".format(trigger_bkpt.GetID())],
35+
)
36+
37+
def setup_recording(self):
38+
"""
39+
Record execution of code between "start_recording" and "stop_recording" breakpoints.
40+
41+
Returns with the target stopped at "stop_recording", with recording disabled,
42+
ready to reverse-execute.
43+
"""
44+
self.build()
45+
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
46+
process = self.connect(target)
47+
48+
# Record execution from the start of the function "start_recording"
49+
# to the start of the function "stop_recording". We want to keep the
50+
# interval that we record as small as possible to minimize the run-time
51+
# of our single-stepping recorder.
52+
start_recording_bkpt = target.BreakpointCreateByName("start_recording", None)
53+
self.assertTrue(start_recording_bkpt.GetNumLocations() > 0)
54+
initial_threads = lldbutil.continue_to_breakpoint(process, start_recording_bkpt)
55+
self.assertEqual(len(initial_threads), 1)
56+
target.BreakpointDelete(start_recording_bkpt.GetID())
57+
self.start_recording()
58+
stop_recording_bkpt = target.BreakpointCreateByName("stop_recording", None)
59+
self.assertTrue(stop_recording_bkpt.GetNumLocations() > 0)
60+
lldbutil.continue_to_breakpoint(process, stop_recording_bkpt)
61+
target.BreakpointDelete(stop_recording_bkpt.GetID())
62+
self.stop_recording()
63+
64+
self.dbg.SetAsync(False)
65+
66+
return target, process, initial_threads
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Test the "process continue --reverse" and "--forward" options
3+
when reverse-continue is not supported.
4+
"""
5+
6+
7+
import lldb
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test import lldbutil
11+
12+
13+
class TestReverseContinueNotSupported(TestBase):
14+
def test_reverse_continue_not_supported(self):
15+
target = self.connect()
16+
17+
# Set breakpoint and reverse-continue
18+
trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
19+
self.assertTrue(trigger_bkpt, VALID_BREAKPOINT)
20+
# `process continue --forward` should work.
21+
self.expect(
22+
"process continue --forward",
23+
substrs=["stop reason = breakpoint {0}.1".format(trigger_bkpt.GetID())],
24+
)
25+
self.expect(
26+
"process continue --reverse",
27+
error=True,
28+
substrs=["target does not support reverse-continue"],
29+
)
30+
31+
def test_reverse_continue_forward_and_reverse(self):
32+
self.connect()
33+
34+
self.expect(
35+
"process continue --forward --reverse",
36+
error=True,
37+
substrs=["cannot specify both 'forward' and 'reverse'"],
38+
)
39+
40+
def connect(self):
41+
self.build()
42+
exe = self.getBuildArtifact("a.out")
43+
target = self.dbg.CreateTarget(exe)
44+
self.assertTrue(target, VALID_TARGET)
45+
46+
main_bkpt = target.BreakpointCreateByName("main", None)
47+
self.assertTrue(main_bkpt, VALID_BREAKPOINT)
48+
49+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
50+
self.assertTrue(process, PROCESS_IS_VALID)
51+
return target
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+
}

llvm/docs/ReleaseNotes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ Changes to LLDB
220220
information about the current state of the debugger at the bottom of the
221221
terminal. This is on by default and can be configured using the
222222
`show-statusline` and `statusline-format` settings.
223+
* LLDB now supports `process continue --reverse` when used with backends
224+
supporting reverse execution, such as [rr](https://rr-project.org).
223225

224226
### Changes to lldb-dap
225227

0 commit comments

Comments
 (0)