| import lldb | |
| import time | |
| import unittest | |
| 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 TestReverseContinueWatchpoints(ReverseTestBase): | |
| @skipIfRemote | |
| # Watchpoints don't work in single-step mode | |
| @skipIf(macos_version=["<", "15.0"], archs=["arm64"]) | |
| def test_reverse_continue_watchpoint(self): | |
| self.reverse_continue_watchpoint_internal(async_mode=False) | |
| @skipIfRemote | |
| # Watchpoints don't work in single-step mode | |
| @skipIf(macos_version=["<", "15.0"], archs=["arm64"]) | |
| def test_reverse_continue_watchpoint_async(self): | |
| self.reverse_continue_watchpoint_internal(async_mode=True) | |
| def reverse_continue_watchpoint_internal(self, async_mode): | |
| target, process, initial_threads, watch_addr = self.setup_recording(async_mode) | |
| error = lldb.SBError() | |
| wp_opts = lldb.SBWatchpointOptions() | |
| wp_opts.SetWatchpointTypeWrite(lldb.eWatchpointWriteTypeOnModify) | |
| watchpoint = target.WatchpointCreateByAddress(watch_addr, 4, wp_opts, error) | |
| self.assertTrue(watchpoint) | |
| watch_var = target.EvaluateExpression("*g_watched_var_ptr") | |
| self.assertEqual(watch_var.GetValueAsSigned(-1), 2) | |
| # Reverse-continue to the function "trigger_watchpoint". | |
| status = process.ContinueInDirection(lldb.eRunReverse) | |
| self.assertSuccess(status) | |
| self.expect_async_state_changes( | |
| async_mode, process, [lldb.eStateRunning, lldb.eStateStopped] | |
| ) | |
| # We should stop at the point just before the location was modified. | |
| watch_var = target.EvaluateExpression("*g_watched_var_ptr") | |
| self.assertEqual(watch_var.GetValueAsSigned(-1), 1) | |
| self.expect( | |
| "thread list", | |
| STOPPED_DUE_TO_WATCHPOINT, | |
| substrs=["stopped", "trigger_watchpoint", "stop reason = watchpoint 1"], | |
| ) | |
| # Stepping forward one instruction should change the value of the variable. | |
| initial_threads[0].StepInstruction(False) | |
| self.expect_async_state_changes( | |
| async_mode, process, [lldb.eStateRunning, lldb.eStateStopped] | |
| ) | |
| watch_var = target.EvaluateExpression("*g_watched_var_ptr") | |
| self.assertEqual(watch_var.GetValueAsSigned(-1), 2) | |
| self.expect( | |
| "thread list", | |
| STOPPED_DUE_TO_WATCHPOINT, | |
| substrs=["stopped", "trigger_watchpoint", "stop reason = watchpoint 1"], | |
| ) | |
| @skipIfRemote | |
| # Watchpoints don't work in single-step mode | |
| @skipIf(macos_version=["<", "15.0"], archs=["arm64"]) | |
| @skipIf( | |
| oslist=["windows"], | |
| archs=["x86_64"], | |
| bugnumber="github.com/llvm/llvm-project/issues/138084", | |
| ) | |
| def test_reverse_continue_skip_watchpoint(self): | |
| self.reverse_continue_skip_watchpoint_internal(async_mode=False) | |
| @skipIfRemote | |
| # Watchpoints don't work in single-step mode | |
| @skipIf(macos_version=["<", "15.0"], archs=["arm64"]) | |
| @skipIf( | |
| oslist=["windows"], | |
| archs=["x86_64"], | |
| bugnumber="github.com/llvm/llvm-project/issues/138084", | |
| ) | |
| def test_reverse_continue_skip_watchpoint_async(self): | |
| self.reverse_continue_skip_watchpoint_internal(async_mode=True) | |
| def reverse_continue_skip_watchpoint_internal(self, async_mode): | |
| target, process, initial_threads, watch_addr = self.setup_recording(async_mode) | |
| # Reverse-continue over a watchpoint whose condition is false | |
| # (via function call). | |
| # This tests that we continue in the correct direction after hitting | |
| # the watchpoint. | |
| error = lldb.SBError() | |
| wp_opts = lldb.SBWatchpointOptions() | |
| wp_opts.SetWatchpointTypeWrite(lldb.eWatchpointWriteTypeOnModify) | |
| watchpoint = target.WatchpointCreateByAddress(watch_addr, 4, wp_opts, error) | |
| self.assertTrue(watchpoint) | |
| watchpoint.SetCondition("false_condition()") | |
| status = process.ContinueInDirection(lldb.eRunReverse) | |
| self.expect_async_state_changes( | |
| async_mode, process, [lldb.eStateRunning, lldb.eStateStopped] | |
| ) | |
| self.assertSuccess(status) | |
| self.expect( | |
| "thread list", | |
| STOPPED_DUE_TO_HISTORY_BOUNDARY, | |
| substrs=["stopped", "stop reason = history boundary"], | |
| ) | |
| def setup_recording(self, async_mode): | |
| """ | |
| 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()) | |
| frame0 = initial_threads[0].GetFrameAtIndex(0) | |
| watched_var_ptr = frame0.FindValue( | |
| "g_watched_var_ptr", lldb.eValueTypeVariableGlobal | |
| ) | |
| watch_addr = watched_var_ptr.GetValueAsUnsigned(0) | |
| self.assertTrue(watch_addr > 0) | |
| 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(async_mode) | |
| self.expect_async_state_changes(async_mode, process, [lldb.eStateStopped]) | |
| return target, process, initial_threads, watch_addr | |
| def expect_async_state_changes(self, async_mode, process, states): | |
| if not async_mode: | |
| return | |
| listener = self.dbg.GetListener() | |
| lldbutil.expect_state_changes(self, listener, process, states) |