diff --git a/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h b/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h index 0cc17d3672739eaf54f8e4dfe85935a76fb4f22c..ed937663e2dcb42a8f213969a607f86b1fb1126d 100644 --- a/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h +++ b/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h @@ -46,8 +46,8 @@ private: lldb::user_id_t m_breakpoint_site_id; bool m_auto_continue; bool m_reenabled_breakpoint_site; - bool m_stopped_at_my_breakpoint; bool m_handling_signal; + bool m_is_stale; ThreadPlanStepOverBreakpoint(const ThreadPlanStepOverBreakpoint &) = delete; const ThreadPlanStepOverBreakpoint & diff --git a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp index 59fa6f3ecb541108ca091cd834b48e661dcaa7af..8e4f1f013350f45bafbe4ae0db664993b434949d 100644 --- a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp +++ b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp @@ -18,6 +18,12 @@ using namespace lldb_private; // ThreadPlanStepOverBreakpoint: Single steps over a breakpoint bp_site_sp at // the pc. +// Current behavior is to skip signal handlers, if a signal is received, so that +// signals in the background do not interrupt our stepping (otherwise a +// breakpoint can be seen as hit multiple times in a row, even though the +// underlying instruction was not executed). If a breakpoint is hit inside the +// handler (even with a false condition), the plan will finish and the user will +// see another initial breakpoint hit once the control exits the handler. ThreadPlanStepOverBreakpoint::ThreadPlanStepOverBreakpoint(Thread &thread) : ThreadPlan( @@ -28,8 +34,7 @@ ThreadPlanStepOverBreakpoint::ThreadPlanStepOverBreakpoint(Thread &thread) // a breakpoint m_breakpoint_addr(LLDB_INVALID_ADDRESS), m_auto_continue(false), m_reenabled_breakpoint_site(false), - m_stopped_at_my_breakpoint(false), - m_handling_signal(false) + m_handling_signal(false), m_is_stale(false) { m_breakpoint_addr = thread.GetRegisterContext()->GetPC(); m_breakpoint_site_id = @@ -48,8 +53,6 @@ void ThreadPlanStepOverBreakpoint::GetDescription( bool ThreadPlanStepOverBreakpoint::ValidatePlan(Stream *error) { return true; } bool ThreadPlanStepOverBreakpoint::DoPlanExplainsStop(Event *event_ptr) { - m_stopped_at_my_breakpoint = false; - StopInfoSP stop_info_sp = GetPrivateStopInfo(); if (stop_info_sp) { // It's a little surprising that we stop here for a breakpoint hit. @@ -71,7 +74,7 @@ bool ThreadPlanStepOverBreakpoint::DoPlanExplainsStop(Event *event_ptr) { switch (reason) { case eStopReasonTrace: case eStopReasonNone: - return true; + return !m_handling_signal; case eStopReasonBreakpoint: { // It's a little surprising that we stop here for a breakpoint hit. @@ -92,7 +95,6 @@ bool ThreadPlanStepOverBreakpoint::DoPlanExplainsStop(Event *event_ptr) { lldb::addr_t pc_addr = GetThread().GetRegisterContext()->GetPC(); if (pc_addr == m_breakpoint_addr) { - m_stopped_at_my_breakpoint = true; // If we came from a signal handler, just reset the flag and try again. m_handling_signal = false; LLDB_LOGF(log, @@ -103,7 +105,14 @@ bool ThreadPlanStepOverBreakpoint::DoPlanExplainsStop(Event *event_ptr) { return true; } - // Even if we are in a signal handler, handle the breakpoint as usual + if (m_handling_signal) { + LLDB_LOG(log, + "Got breakpoint stop reason inside a signal handler, " + "step over breakpoint is finished for now."); + // Even if we are in a signal handler, handle the breakpoint as usual + // and finish the plan + m_is_stale = true; + } SetAutoContinue(false); return false; @@ -173,7 +182,7 @@ void ThreadPlanStepOverBreakpoint::WillPop() { bool ThreadPlanStepOverBreakpoint::MischiefManaged() { lldb::addr_t pc_addr = GetThread().GetRegisterContext()->GetPC(); - if (pc_addr == m_breakpoint_addr) { + if (pc_addr == m_breakpoint_addr || m_handling_signal) { // If we are still at the PC of our breakpoint, then for some reason we // didn't get a chance to run, or we received a signal and want to try again. return false; @@ -207,7 +216,9 @@ void ThreadPlanStepOverBreakpoint::SetAutoContinue(bool do_it) { } bool ThreadPlanStepOverBreakpoint::ShouldAutoContinue(Event *event_ptr) { - if (m_stopped_at_my_breakpoint) { + lldb::addr_t pc_addr = GetThread().GetRegisterContext()->GetPC(); + + if (pc_addr == m_breakpoint_addr) { // Do not stop again at the breakpoint we are trying to step over Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); LLDB_LOG(log, "Stopped step over breakpoint plan on its own breakpoint, auto-continue."); @@ -217,6 +228,8 @@ bool ThreadPlanStepOverBreakpoint::ShouldAutoContinue(Event *event_ptr) { } bool ThreadPlanStepOverBreakpoint::IsPlanStale() { - // TODO: validate - return !m_handling_signal && GetThread().GetRegisterContext()->GetPC() != m_breakpoint_addr; + if (m_handling_signal) { + return m_is_stale; + } + return GetThread().GetRegisterContext()->GetPC() != m_breakpoint_addr; } diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/Makefile b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..c33ae5685efc70624f9b098c6cfe403dee92d107 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/Makefile @@ -0,0 +1,5 @@ +CXX_SOURCES := main.cpp + +ENABLE_THREADS := YES + +include Makefile.rules diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/SignalDuringBreakpointStepTestCase.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/SignalDuringBreakpointStepTestCase.py new file mode 100644 index 0000000000000000000000000000000000000000..32ebc1a9e06cee2fcb150b649a0a8be4b71a71af --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/SignalDuringBreakpointStepTestCase.py @@ -0,0 +1,178 @@ +""" +This test is intended to create a situation in which signals are received by +a thread while it is stepping off a breakpoint. The intended behavior is to +skip the handler and to not stop at the breakpoint we are trying to step off +the second time, as the corresponding instruction was not executed anyway. +If a breakpoint is hit inside the handler, the breakpoint on the line with +the original instruction should be hit when the handler is finished. + +This checks stepping off breakpoints set on single instructions and function +calls as well, to see a potential pc change when single-stepping. +""" + + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil + + +@skipIfWindows +class SignalDuringBreakpointStepTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + actions = [ + 'step-in-func', + 'step-over', + 'step-over-func', + 'step-out', + 'step-out-func', + 'step-until', + 'step-until-func' + ] + + num_iters = 100 + num_signal_iters = 20 + cur_iter = 0 + + def action2cmd(self, action): + res = action + if res.endswith('-func'): + res = res[:-5] + if res == 'step-until': + res = 'until %s' % self.lines[action] + return res + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line numbers and set the breakpoints + self.main_source = 'main.cpp' + self.main_source_spec = lldb.SBFileSpec(self.main_source) + + self.build() + exe = self.getBuildArtifact("a.out") + + self.target = self.dbg.CreateTarget(exe) + self.assertTrue(self.target, VALID_TARGET) + + self.setup_breakpoint = self.target.BreakpointCreateBySourceRegex( + 'Break here and adjust', + self.main_source_spec) + + self.breakpoints = {} + self.lines = {} + for action in self.actions: + bp = self.target.BreakpointCreateBySourceRegex( + '%s breakpoint' % action, + self.main_source_spec) + self.assertTrue(bp, VALID_BREAKPOINT) + self.breakpoints[action] = bp + self.lines[action] = line_number(self.main_source, '%s line' % action) + self.fall_out_breakpoint = self.target.BreakpointCreateBySourceRegex( + 'Break here to not fall out', + self.main_source_spec) + + def get_thread_stopped_at(self): + frame = self.thread.GetFrameAtIndex(0) + desc = lldbutil.get_description(frame.GetLineEntry()) + return '(stopped at %s for iteration %d)' % (desc, self.cur_iter) + + def check_iteration(self): + cur_iter = self.thread.GetSelectedFrame().EvaluateExpression('g_cur_iter').GetValueAsSigned() + skipped = cur_iter > self.cur_iter + self.assertEquals(self.cur_iter, cur_iter, + 'Expected action iteration %d to %s (was a breakpoint %s?) %s' % + (cur_iter, 'continue' if skipped else 'end', + 'skipped' if skipped else 'hit twice', self.get_thread_stopped_at())) + + def check_stopped_at_breakpoint(self, bp, msg): + self.check_iteration() + thread1 = lldbutil.get_one_thread_stopped_at_breakpoint(self.process, bp) + self.assertEquals(self.thread, thread1, + "%s %s." % (msg, self.get_thread_stopped_at())) + + def check_stopped_at_line(self, line): + self.check_iteration() + desc = self.get_thread_stopped_at() + expect = "%s:%d" % (self.main_source, line) + self.assertTrue(expect in desc, "Expected to stop at %s %s" % (expect, desc)) + self.assertEquals(self.thread.GetStopReason(), lldb.eStopReasonPlanComplete, + "Expected stop reason to be step into/over/out %s." % desc) + + def check_stopped_at_action_breakpoint(self, action): + self.check_stopped_at_breakpoint(self.breakpoints[action], + "Didn't stop at breakpoint for %s action" % action) + + def check_stopped_at_action_line(self, action): + self.check_stopped_at_line(self.lines[action]) + + + def set_up_for_action(self, action): + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # Check we are at line self.setup_breakpoint + self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT, + substrs=["stop reason = breakpoint 1."]) + + # Get the process + self.process = self.target.GetProcess() + + # We should be stopped at the setup site where we can set the action + self.assertEqual( + self.process.GetNumThreads(), + 1, + 'Expected to stop before any additional threads are spawned.') + + action_idx = self.actions.index(action) + self.runCmd("expr action_idx=%d" % action_idx) + self.runCmd("expr NUM_ITERS=%d" % self.num_iters) + self.runCmd("expr NUM_SIGNAL_ITERS=%d" % self.num_signal_iters) + + self.thread = self.process.GetThreadAtIndex(0) + + # Configure signal settings + self.runCmd("process handle SIGRTMIN -p true -s false -n true") + # TODO: signal numbering is wrong for linux musl right now (SIGRTMIN=35!=34) + # For now just configure multiple rt signals + self.runCmd("process handle SIGRTMIN+1 -p true -s false -n true") + + # Continue the inferior so threads are spawned + self.runCmd("continue") + + def set_up_and_iterate(self, action, do_disable_breakpoint, checker): + # Set up + self.set_up_for_action(action) + + # Iterate and check + for i in range(self.num_iters): + self.cur_iter = i + # Check if stopped at the right breakpoint + self.check_stopped_at_action_breakpoint(action) + # Disable the breakpoint, if needed + if do_disable_breakpoint: + self.breakpoints[action].SetEnabled(False) + # Delegate to custom checker + checker(action, do_disable_breakpoint) + # Enable the breakpoint, if we disabled it + if do_disable_breakpoint: + self.breakpoints[action].SetEnabled(True) + # Continue + self.runCmd("continue") + + # Should be at the last breakpoint before exit + self.cur_iter += 1 + self.check_stopped_at_breakpoint(self.fall_out_breakpoint, 'Expected to stop at the last fall-out breakpoint') + + def check_step_off(self, action, is_breakpoint_disabled): + # Step off the breakpoint + self.runCmd("thread %s" % self.action2cmd(action)) + + # Should be stopped at the corresponding line with 'plan complete' reason + self.check_stopped_at_action_line(action) + + def run_to_breakpoint_and_step(self, action, do_disable_breakpoint): + self.set_up_and_iterate(action, do_disable_breakpoint, self.check_step_off) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepIn.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepIn.py new file mode 100644 index 0000000000000000000000000000000000000000..11dd46706a010e2f216b3d9da261c23980647dd1 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepIn.py @@ -0,0 +1,25 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +@skipIfWindows +class SignalDuringBreakpointFuncStepInTestCase(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + # Currently, lldb always steps over the handler from a breakpoint line. + @skipIf(triple='^mips') + def test_step_in_func_breakpoint(self): + self.run_to_breakpoint_and_step('step-in-func', False) + + @skipIf(triple='^mips') + # Currently, lldb always steps into the handler from a non-breakpoint line, + # if the single-stepping is not emulated + @skipIf(archs=no_match(['arm'])) + def test_step_in_func_no_breakpoint(self): + self.run_to_breakpoint_and_step('step-in-func', True) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOut.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOut.py new file mode 100644 index 0000000000000000000000000000000000000000..91619a28ba9840b7b2854716664d1612124cd8ed --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOut.py @@ -0,0 +1,27 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +@skipIfWindows +class SignalDuringBreakpointFuncStepOutTestCase(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + # Currently, lldb might skip the next stop when stepping out of the func, + # if the single-stepping is not emulated + @skipIf(archs=no_match(['arm'])) + def test_step_out_func_breakpoint(self): + self.run_to_breakpoint_and_step('step-out-func', False) + + @skipIf(triple='^mips') + # Currently, lldb might skip the next stop when stepping out of the func, + # if the single-stepping is not emulated + @skipIf(archs=no_match(['arm'])) + def test_step_out_func_no_breakpoint(self): + self.run_to_breakpoint_and_step('step-out-func', True) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOver.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOver.py new file mode 100644 index 0000000000000000000000000000000000000000..e571d4fb75aa9e2414e80f55af9981b21c95d32d --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOver.py @@ -0,0 +1,27 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +@skipIfWindows +class SignalDuringBreakpointFuncStepOverTestCase(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + # Currently, lldb might skip the next stop when stepping out of the func, + # if the single-stepping is not emulated + @skipIf(archs=no_match(['arm'])) + def test_step_over_func_breakpoint(self): + self.run_to_breakpoint_and_step('step-over-func', False) + + @skipIf(triple='^mips') + # Currently, lldb might skip the next stop when stepping out of the func, + # if the single-stepping is not emulated + @skipIf(archs=no_match(['arm'])) + def test_step_over_func_no_breakpoint(self): + self.run_to_breakpoint_and_step('step-over-func', True) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepUntil.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepUntil.py new file mode 100644 index 0000000000000000000000000000000000000000..494cf72b30a2a7a46bd79540f28a0233d295cb56 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepUntil.py @@ -0,0 +1,27 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +@skipIfWindows +class SignalDuringBreakpointFuncStepUntilTestCase(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + # Currently, lldb might skip the next stop when stepping out of the func, + # if the single-stepping is not emulated + @skipIf(archs=no_match(['arm'])) + def test_step_until_func_breakpoint(self): + self.run_to_breakpoint_and_step('step-until-func', False) + + @skipIf(triple='^mips') + # Currently, lldb might skip the next stop when stepping out of the func, + # if the single-stepping is not emulated + @skipIf(archs=no_match(['arm'])) + def test_step_until_func_no_breakpoint(self): + self.run_to_breakpoint_and_step('step-until-func', True) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOut.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOut.py new file mode 100644 index 0000000000000000000000000000000000000000..f445f686591fe5cd654399a96d8e18f7f9ab3c48 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOut.py @@ -0,0 +1,21 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +@skipIfWindows +class SignalDuringBreakpointStepOutTestCase(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + def test_step_out_breakpoint(self): + self.run_to_breakpoint_and_step('step-out', False) + + @skipIf(triple='^mips') + def test_step_out_no_breakpoint(self): + self.run_to_breakpoint_and_step('step-out', True) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOver.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOver.py new file mode 100644 index 0000000000000000000000000000000000000000..17711d2131be065981f056bd9171b539b7115654 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOver.py @@ -0,0 +1,21 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +@skipIfWindows +class SignalDuringBreakpointStepOverTestCase(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + def test_step_over_breakpoint(self): + self.run_to_breakpoint_and_step('step-over', False) + + @skipIf(triple='^mips') + def test_step_over_no_breakpoint(self): + self.run_to_breakpoint_and_step('step-over', True) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepUntil.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepUntil.py new file mode 100644 index 0000000000000000000000000000000000000000..b45c989735c63ce0228f4afe1cfcce8b8f5e2b8e --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepUntil.py @@ -0,0 +1,21 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +@skipIfWindows +class SignalDuringBreakpointStepUntilTestCase(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + def test_step_until_breakpoint(self): + self.run_to_breakpoint_and_step('step-until', False) + + @skipIf(triple='^mips') + def test_step_until_no_breakpoint(self): + self.run_to_breakpoint_and_step('step-until', True) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalStepOverHandlerWithBreakpoint.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalStepOverHandlerWithBreakpoint.py new file mode 100644 index 0000000000000000000000000000000000000000..ddafffdbd39d5d09245f1077ce47ec7b0b872b9a --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalStepOverHandlerWithBreakpoint.py @@ -0,0 +1,126 @@ +""" +This test is intended to create a situation in which signals are received by +a thread while it is stepping off a breakpoint. If a breakpoint is hit inside +the handler, the breakpoint on the line with the original instruction should +be hit when the handler is finished. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +@skipIfWindows +class TestSignalStepOverHandlerWithBreakpoint(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + num_iters = 10 + num_signal_iters = 5 + func_name_target = '' + + def set_up_step_over_handler_with_breakpoint(self): + self.action = 'step-over' + self.handler_bp = self.target.BreakpointCreateBySourceRegex( + 'Break here in signal handler', + self.main_source_spec) + + def set_func_name_target(self): + self.func_name_target = self.thread.GetFrameAtIndex(0).GetFunctionName() + + def step_to_target(self, action): + while self.thread.GetFrameAtIndex(0).GetFunctionName() != self.func_name_target: + self.runCmd(action) + + def check_handler_bp_step_out(self, action, is_breakpoint_disabled): + # Start at the relevand line with breakpoint, enable the handler breakpoint for this iteration + self.handler_bp.SetEnabled(True) + self.set_func_name_target() + + # Step + self.runCmd("thread %s" % self.action2cmd(action)) + + stopped_in_handler = \ + (lldbutil.get_one_thread_stopped_at_breakpoint(self.process, self.handler_bp) == self.thread) + # Disable the handler breakpoint until next iteration + self.handler_bp.SetEnabled(False) + # Try to get to the next line through the handler + if stopped_in_handler: + # Stopped inside the handler, step out until we are back in our parent function + self.step_to_target("thread step-out") + + if is_breakpoint_disabled: + # Should be stopped at the breakpoint line because of step-out, try again + self.check_stopped_at_line( + self.breakpoints[action].GetLocationAtIndex(0).GetAddress().GetLineEntry().GetLine()) + else: + # Should be stopped at our initial action breakpoint again, try again + self.check_stopped_at_action_breakpoint(action) + self.runCmd("continue") + + # Now stopped at the corresponding line + self.check_stopped_at_action_line(action) + + def check_handler_bp_continue(self, action, is_breakpoint_disabled): + # Start at the relevand line with breakpoint, enable the handler breakpoint for this iteration + self.handler_bp.SetEnabled(True) + self.set_func_name_target() + + # Step + self.runCmd("thread %s" % self.action2cmd(action)) + + stopped_in_handler = \ + (lldbutil.get_one_thread_stopped_at_breakpoint(self.process, self.handler_bp) == self.thread) + # Disable the handler breakpoint until next iteration + self.handler_bp.SetEnabled(False) + # Try to get to the next line through the handler + if stopped_in_handler: + # Stopped inside the handler, continue until we are back in our parent function + self.step_to_target("continue") + + if not is_breakpoint_disabled: + # Should be stopped at initial breakpoint again + self.check_stopped_at_action_breakpoint(action) + self.runCmd("continue") + + # Now stopped at the corresponding line + self.check_stopped_at_action_line(action) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + # Currently, lldb might skip the next stop when stepping out of the func, + # if the single-stepping is not emulated + @skipIf(archs=no_match(['arm'])) + # Currently on arm, lldb might get wrong return addresses from a signal handler + # and fail with 'could not create return address breakpoint' + @skipIf(archs='arm') + def test_breakpoint_inside_handler_step_out(self): + self.set_up_step_over_handler_with_breakpoint() + self.set_up_and_iterate(self.action, True, self.check_handler_bp_step_out) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + # Currently on arm, lldb might get wrong return addresses from a signal handler + # and fail with 'could not create return address breakpoint' + @skipIf(archs='arm') + def test_breakpoint_inside_handler_step_out_to_breakpoint(self): + self.set_up_step_over_handler_with_breakpoint() + self.set_up_and_iterate(self.action, False, self.check_handler_bp_step_out) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + def test_breakpoint_inside_handler_continue(self): + self.set_up_step_over_handler_with_breakpoint() + self.set_up_and_iterate(self.action, True, self.check_handler_bp_continue) + + # Atomic sequences are not supported yet for MIPS in LLDB. + # (Copied from concurrent_events/TestConcurrentSignalBreak.py) + @skipIf(triple='^mips') + def test_breakpoint_inside_handler_continue_to_breakpoint(self): + self.set_up_step_over_handler_with_breakpoint() + self.set_up_and_iterate(self.action, False, self.check_handler_bp_continue) diff --git a/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/main.cpp b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..56706c5849e9ce6089b163a3a09cfd15094b11f2 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/main.cpp @@ -0,0 +1,153 @@ +// This test is intended to create a situation in which signals are received by +// a thread while it is stepping off a breakpoint. The intended behavior is to +// skip the handler and to not stop at the breakpoint we are trying to step off +// the second time, as the corresponding instruction was not executed anyway. +// If a breakpoint is hit inside the handler, the breakpoint on the line with +// the original instruction should be hit when the handler is finished. +// +// This checks stepping off breakpoints set on single instructions and function +// calls as well, to see a potential pc change when single-stepping. + +#include "pseudo_barrier.h" + +#include +#include +#include +#include +#include +#include + +pseudo_barrier_t g_barrier; +std::atomic g_send_signals; +int g_action = 0; +int g_signal_count = 0; +int g_cur_iter = 0; + +int NUM_ITERS = 0; +int NUM_SIGNAL_ITERS = 0; + +using action_t = void (*)(); + +void do_action_func(action_t action) { + // Wait until all threads are running + pseudo_barrier_wait(g_barrier); + + // Do the action + for (g_cur_iter = 0; g_cur_iter < NUM_ITERS; g_cur_iter++) { + g_send_signals.store(true); + action(); + } +} + +void step_in_helper() { + g_action++; // step-in-func line +} + +void step_in_func() { + step_in_helper(); // step-in-func breakpoint +} + +void do_something() { g_action++; } + +void step_over_func() { + do_something(); // step-over-func breakpoint + g_action++; // step-over-func line +} + +void step_over() { + g_action++; // step-over breakpoint + g_action++; // step-over line +} + +void step_out_func_helper() { + do_something(); // step-out-func breakpoint +} + +void step_out_helper() { + g_action++; // step-out breakpoint +} + +void step_out_func() { + step_out_func_helper(); + g_action++; // [step-out-func line probably] +} + +void step_out() { + step_out_helper(); + g_action++; // [step-out line probably] +} + +void step_until() { + g_action++; // step-until breakpoint + g_action++; // step-until line +} + +void step_until_func() { + do_something(); // step-until-func breakpoint + g_action++; // step-until-func line +} + +void signal_handler(int sig) { + if (sig == SIGRTMIN) + g_signal_count += 1; // Break here in signal handler +} + +/// Register a simple function to handle signal +void register_signal_handler(int signal, void (*handler)(int)) { + sigset_t empty_sigset; + sigemptyset(&empty_sigset); + + struct sigaction action; + action.sa_sigaction = 0; + action.sa_mask = empty_sigset; + action.sa_flags = 0; + action.sa_handler = handler; + sigaction(signal, &action, 0); +} + +int dotest() { + action_t actions[] = { + step_in_func, + step_over, + step_over_func, + step_out, + step_out_func, + step_until, + step_until_func + }; + + int action_idx = 0; + + register_signal_handler(SIGRTMIN, + signal_handler); // Break here and adjust + // NUM_ITERS and action_idx + + // Don't let either thread do anything until they're both ready. + pseudo_barrier_init(g_barrier, 2); + + pthread_t pid = pthread_self(); + std::thread signaller([pid]() { + // Wait until all threads are running + pseudo_barrier_wait(g_barrier); + + // Send user-defined signals to the current thread + for (int i = 0; i < NUM_ITERS; i++) { + // Wait until the next action iteration cycle + while (!g_send_signals.exchange(false)) + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + + // Send some signals + for (int i = 0; i < NUM_SIGNAL_ITERS; i++) { + pthread_kill(pid, SIGRTMIN); + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + } + } + }); + + do_action_func(actions[action_idx]); + return 0; // Break here to not fall out +} + +int main() { + return dotest(); +}