Skip to content

Commit 39b5c70

Browse files
committed
[lldb] Add tests for watchpoints during reverse execution
1 parent 3d5ac5c commit 39b5c70

File tree

3 files changed

+142
-2
lines changed

3 files changed

+142
-2
lines changed

lldb/test/API/functionalities/reverse-execution/TestReverseContinueNotSupported.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ def test_reverse_continue_not_supported(self):
2626
status, "error: gdb-remote does not support reverse execution of processes"
2727
)
2828

29-
status = process.ContinueInDirection(lldb.eRunForward)
30-
self.assertSuccess(status)
29+
self.assertSuccess(process.ContinueInDirection(lldb.eRunForward))
3130
self.assertState(process.GetState(), lldb.eStateExited)
3231
self.assertEqual(process.GetExitStatus(), 0)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import lldb
2+
import time
3+
import unittest
4+
from lldbsuite.test.lldbtest import *
5+
from lldbsuite.test.decorators import *
6+
from lldbsuite.test.gdbclientutils import *
7+
from lldbsuite.test.lldbreverse import ReverseTestBase
8+
from lldbsuite.test import lldbutil
9+
10+
11+
class TestReverseContinueWatchpoints(ReverseTestBase):
12+
def test_reverse_continue_watchpoint(self):
13+
self.reverse_continue_watchpoint_internal(async_mode=False)
14+
15+
def test_reverse_continue_watchpoint_async(self):
16+
self.reverse_continue_watchpoint_internal(async_mode=True)
17+
18+
def reverse_continue_watchpoint_internal(self, async_mode):
19+
target, process, initial_threads, watch_addr = self.setup_recording(async_mode)
20+
21+
error = lldb.SBError()
22+
wp_opts = lldb.SBWatchpointOptions()
23+
wp_opts.SetWatchpointTypeWrite(lldb.eWatchpointWriteTypeOnModify)
24+
watchpoint = target.WatchpointCreateByAddress(watch_addr, 4, wp_opts, error)
25+
self.assertTrue(watchpoint)
26+
27+
watch_var = target.EvaluateExpression("*g_watched_var_ptr")
28+
self.assertEqual(watch_var.GetValueAsSigned(-1), 2)
29+
30+
# Reverse-continue to the function "trigger_watchpoint".
31+
status = process.ContinueInDirection(lldb.eRunReverse)
32+
self.assertSuccess(status)
33+
self.expect_async_state_changes(
34+
async_mode, process, [lldb.eStateRunning, lldb.eStateStopped]
35+
)
36+
# We should stop at the point just before the location was modified.
37+
watch_var = target.EvaluateExpression("*g_watched_var_ptr")
38+
self.assertEqual(watch_var.GetValueAsSigned(-1), 1)
39+
self.expect(
40+
"thread list",
41+
STOPPED_DUE_TO_WATCHPOINT,
42+
substrs=["stopped", "trigger_watchpoint", "stop reason = watchpoint 1"],
43+
)
44+
45+
# Stepping forward one instruction should change the value of the variable.
46+
initial_threads[0].StepInstruction(False)
47+
self.expect_async_state_changes(
48+
async_mode, process, [lldb.eStateRunning, lldb.eStateStopped]
49+
)
50+
watch_var = target.EvaluateExpression("*g_watched_var_ptr")
51+
self.assertEqual(watch_var.GetValueAsSigned(-1), 2)
52+
self.expect(
53+
"thread list",
54+
STOPPED_DUE_TO_WATCHPOINT,
55+
substrs=["stopped", "trigger_watchpoint", "stop reason = watchpoint 1"],
56+
)
57+
58+
def test_reverse_continue_skip_watchpoint(self):
59+
self.reverse_continue_skip_watchpoint_internal(async_mode=False)
60+
61+
def test_reverse_continue_skip_watchpoint_async(self):
62+
self.reverse_continue_skip_watchpoint_internal(async_mode=True)
63+
64+
def reverse_continue_skip_watchpoint_internal(self, async_mode):
65+
target, process, initial_threads, watch_addr = self.setup_recording(async_mode)
66+
67+
# Reverse-continue over a watchpoint whose condition is false
68+
# (via function call).
69+
# This tests that we continue in the correct direction after hitting
70+
# the watchpoint.
71+
error = lldb.SBError()
72+
wp_opts = lldb.SBWatchpointOptions()
73+
wp_opts.SetWatchpointTypeWrite(lldb.eWatchpointWriteTypeOnModify)
74+
watchpoint = target.WatchpointCreateByAddress(watch_addr, 4, wp_opts, error)
75+
self.assertTrue(watchpoint)
76+
watchpoint.SetCondition("false_condition()")
77+
status = process.ContinueInDirection(lldb.eRunReverse)
78+
self.expect_async_state_changes(
79+
async_mode, process, [lldb.eStateRunning, lldb.eStateStopped]
80+
)
81+
self.assertSuccess(status)
82+
self.expect(
83+
"thread list",
84+
STOPPED_DUE_TO_HISTORY_BOUNDARY,
85+
substrs=["stopped", "stop reason = history boundary"],
86+
)
87+
88+
def setup_recording(self, async_mode):
89+
"""
90+
Record execution of code between "start_recording" and "stop_recording" breakpoints.
91+
92+
Returns with the target stopped at "stop_recording", with recording disabled,
93+
ready to reverse-execute.
94+
"""
95+
self.build()
96+
target = self.dbg.CreateTarget("")
97+
process = self.connect(target)
98+
99+
# Record execution from the start of the function "start_recording"
100+
# to the start of the function "stop_recording". We want to keep the
101+
# interval that we record as small as possible to minimize the run-time
102+
# of our single-stepping recorder.
103+
start_recording_bkpt = target.BreakpointCreateByName("start_recording", None)
104+
initial_threads = lldbutil.continue_to_breakpoint(process, start_recording_bkpt)
105+
self.assertEqual(len(initial_threads), 1)
106+
target.BreakpointDelete(start_recording_bkpt.GetID())
107+
108+
frame0 = initial_threads[0].GetFrameAtIndex(0)
109+
watched_var_ptr = frame0.FindValue(
110+
"g_watched_var_ptr", lldb.eValueTypeVariableGlobal
111+
)
112+
watch_addr = watched_var_ptr.GetValueAsUnsigned(0)
113+
self.assertTrue(watch_addr > 0)
114+
115+
self.start_recording()
116+
stop_recording_bkpt = target.BreakpointCreateByName("stop_recording", None)
117+
lldbutil.continue_to_breakpoint(process, stop_recording_bkpt)
118+
target.BreakpointDelete(stop_recording_bkpt.GetID())
119+
self.stop_recording()
120+
121+
self.dbg.SetAsync(async_mode)
122+
self.expect_async_state_changes(async_mode, process, [lldb.eStateStopped])
123+
124+
return target, process, initial_threads, watch_addr
125+
126+
def expect_async_state_changes(self, async_mode, process, states):
127+
if not async_mode:
128+
return
129+
listener = self.dbg.GetListener()
130+
lldbutil.expect_state_changes(self, listener, process, states)

lldb/test/API/functionalities/reverse-execution/main.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
int false_condition() { return 0; }
22

3+
int *g_watched_var_ptr;
4+
35
static void start_recording() {}
46

7+
static void trigger_watchpoint() { *g_watched_var_ptr = 2; }
8+
59
static void trigger_breakpoint() {}
610

711
static void stop_recording() {}
812

913
int main() {
14+
// The watched memory location is on the stack because
15+
// that's what our reverse execution engine records and
16+
// replays.
17+
int watched_var = 1;
18+
g_watched_var_ptr = &watched_var;
19+
1020
start_recording();
21+
trigger_watchpoint();
1122
trigger_breakpoint();
1223
stop_recording();
1324
return 0;

0 commit comments

Comments
 (0)