diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index 5be9cb657382a4c2e40f88e68022ab13f6dda5b7..0e8cd034cdb6b4187bc6c16ec3b5151da4cee5e0 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -449,6 +449,7 @@ protected: Status SetSoftwareBreakpoint(lldb::addr_t addr, uint32_t size_hint); Status RemoveSoftwareBreakpoint(lldb::addr_t addr); + uint32_t GetSoftwareBreakpointRefCount(lldb::addr_t addr) const; virtual llvm::Expected> GetSoftwareBreakpointTrapOpcode(size_t size_hint); diff --git a/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h b/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h index 0cc17d3672739eaf54f8e4dfe85935a76fb4f22c..86f7798487c305be1fc275acba8602a28dcbc4b7 100644 --- a/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h +++ b/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h @@ -46,8 +46,6 @@ 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; ThreadPlanStepOverBreakpoint(const ThreadPlanStepOverBreakpoint &) = delete; const ThreadPlanStepOverBreakpoint & diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp index 493e14cb904b91a860384db4c218fd1c1a93812f..c6dffaeef9bf5eccb7fc8c7aff4fe324de8bec6b 100644 --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -437,6 +437,14 @@ Status NativeProcessProtocol::RemoveSoftwareBreakpoint(lldb::addr_t addr) { return Status(); } +uint32_t +NativeProcessProtocol::GetSoftwareBreakpointRefCount(lldb::addr_t addr) const { + auto it = m_software_breakpoints.find(addr); + if (it == m_software_breakpoints.end()) + return 0; + return it->second.ref_count; +} + llvm::Expected NativeProcessProtocol::EnableSoftwareBreakpoint(lldb::addr_t addr, uint32_t size_hint) { diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp index e07d763c2de773a50aa36903602c439744f5203e..1fa531951801149279800a3c243d547dfab93374 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -678,7 +678,7 @@ void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info, "breakpoint hits, pid = {0}, error = {1}", thread.GetID(), error); if (bp_index != LLDB_INVALID_INDEX32) { - MonitorBreakpoint(thread); + MonitorBreakpoint(thread, true); break; } @@ -709,7 +709,7 @@ void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info, // NO BREAK #endif case TRAP_BRKPT: - MonitorBreakpoint(thread); + MonitorBreakpoint(thread, false); break; case SIGTRAP: @@ -741,18 +741,45 @@ void NativeProcessLinux::MonitorTrace(NativeThreadLinux &thread) { StopRunningThreads(thread.GetID()); } -void NativeProcessLinux::MonitorBreakpoint(NativeThreadLinux &thread) { +void NativeProcessLinux::MonitorBreakpoint(NativeThreadLinux &thread, + bool is_hardware) { Log *log( GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_BREAKPOINTS)); LLDB_LOG(log, "received breakpoint event, pid = {0}", thread.GetID()); - // Mark the thread as stopped at breakpoint. - thread.SetStoppedByBreakpoint(); FixupBreakpointPCAsNeeded(thread); - if (m_threads_stepping_with_breakpoint.find(thread.GetID()) != - m_threads_stepping_with_breakpoint.end()) + addr_t pc = thread.GetRegisterContext().GetPC(); + auto tmp_bp = m_threads_stepping_with_breakpoint.find(thread.GetID()); + if (tmp_bp != m_threads_stepping_with_breakpoint.end()) { + if (tmp_bp->second.autocontinue) { + if (!is_hardware && tmp_bp->second.address == pc && + GetSoftwareBreakpointRefCount(tmp_bp->second.address) == 1) { + // Hit a non-user software breakpoint with the set up address + // => we were skipping the signal handler while stepping and returned to + // the set breakpoint, can safely autocontinue. + LLDB_LOG(log, + "ignoring breakpoint to skip the signal handler, pid = {0}", + thread.GetID()); + // Remove the breakpoint + Status error = RemoveTemporarySteppingBreakpoint(tmp_bp); + if (error.Fail()) + LLDB_LOG(log, "pid = {0} remove signal handler skipping breakpoint: {1}", + thread.GetID(), error); + // Autocontinue + ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER); + return; + } + // We stopped at another breakpoint, it takes priority and stops the step. + // The found temporary breakpoint with autocontinue will be erased + // when all the threads are stopped. + } + // We were single-stepping, mark it as such. thread.SetStoppedByTrace(); + } else { + // Mark the thread as stopped at breakpoint. + thread.SetStoppedByBreakpoint(); + } StopRunningThreads(thread.GetID()); } @@ -936,6 +963,55 @@ static lldb::addr_t ReadFlags(NativeRegisterContext ®siter_context) { LLDB_INVALID_ADDRESS); } +Status NativeProcessLinux::SetTemporarySteppingBreakpoint(lldb::tid_t tid, + lldb::addr_t flags, + lldb::addr_t addr, + bool autocontinue, + bool overwrite) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS)); + Status error; + + auto found_bp = m_threads_stepping_with_breakpoint.find(tid); + if (found_bp != m_threads_stepping_with_breakpoint.end()) { + if (overwrite) { + error = RemoveTemporarySteppingBreakpoint(found_bp); + if (error.Fail()) + LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}", tid, error); + } else { + return Status(); + } + } + + if (m_arch.GetMachine() == llvm::Triple::arm) { + if (flags & 0x20) { + // Thumb mode + error = SetSoftwareBreakpoint(addr, 2); + } else { + // Arm mode + error = SetSoftwareBreakpoint(addr, 4); + } + } else if (m_arch.IsMIPS() || m_arch.GetTriple().isPPC64()) + error = SetSoftwareBreakpoint(addr, 4); + else { + // No size hint is given for the next breakpoint + error = SetSoftwareBreakpoint(addr, 0); + } + + if (error.Fail()) + return error; + + m_threads_stepping_with_breakpoint.insert({tid, {addr, autocontinue}}); + GetThreadByID(tid)->SetSingleStepAContinue(true); + return Status(); +} + +Status NativeProcessLinux::RemoveTemporarySteppingBreakpoint(SteppingBreakpointsTy::iterator at) { + addr_t addr = at->second.address; + GetThreadByID(at->first)->SetSingleStepAContinue(false); + m_threads_stepping_with_breakpoint.erase(at); + return RemoveSoftwareBreakpoint(addr); +} + Status NativeProcessLinux::SetupSoftwareSingleStepping(NativeThreadLinux &thread) { Status error; @@ -996,31 +1072,17 @@ NativeProcessLinux::SetupSoftwareSingleStepping(NativeThreadLinux &thread) { return Status("Instruction emulation failed unexpectedly."); } - if (m_arch.GetMachine() == llvm::Triple::arm) { - if (next_flags & 0x20) { - // Thumb mode - error = SetSoftwareBreakpoint(next_pc, 2); - } else { - // Arm mode - error = SetSoftwareBreakpoint(next_pc, 4); - } - } else if (m_arch.IsMIPS() || m_arch.GetTriple().isPPC64()) - error = SetSoftwareBreakpoint(next_pc, 4); - else { - // No size hint is given for the next breakpoint - error = SetSoftwareBreakpoint(next_pc, 0); - } - - // If setting the breakpoint fails because next_pc is out of the address + error = SetTemporarySteppingBreakpoint(thread.GetID(), next_flags, next_pc, + /*autocontinue=*/false, + /*overwrite=*/true); + // If setting the breakpoint fails because addr is out of the address // space, ignore it and let the debugee segfault. if (error.GetError() == EIO || error.GetError() == EFAULT) { + // No need to call NativeThreadLinux::SetSingleStepAContinue(true) because + // NativeThreadLinux::IsSingleStepAContinue() will return true anyway. return Status(); - } else if (error.Fail()) - return error; - - m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc}); - - return Status(); + } + return error; } bool NativeProcessLinux::SupportHardwareSingleStepping() const { @@ -1786,6 +1848,26 @@ Status NativeProcessLinux::ResumeThread(NativeThreadLinux &thread, return resume_result; } case eStateStepping: { + if (signo != LLDB_INVALID_SIGNAL_NUMBER && + m_signals_to_ignore.find(signo) != m_signals_to_ignore.end()) { + // The signal was ignored, but we might stop in the handler. + // Skip it: set up a breakpoint on current pc and wait to hit it, + // then try again. + auto &rc = thread.GetRegisterContext(); + Status error; + + // Do not overwrite the breakpoint: + // If a single-stepping breakpoint is set, just use that. + // If a signal skipping breakpoint is set, that means we are entering + // another signal handler, just wait until the first one completely + // returns. + // NOTE: recursive signal handlers will finish the step early. + error = SetTemporarySteppingBreakpoint(thread.GetID(), rc.GetFlags(), + rc.GetPC(), /*autocontinue=*/true, + /*overwrite=*/false); + if (error.Fail()) + LLDB_LOG(log, "Failed to set a breakpoint to skip the signal handler: {0}.", error); + } const auto step_result = thread.SingleStep(signo); if (step_result.Success()) SetState(eStateRunning, true); @@ -1831,14 +1913,15 @@ void NativeProcessLinux::SignalIfAllThreadsStopped() { GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_BREAKPOINTS)); // Clear any temporary breakpoints we used to implement software single - // stepping. - for (const auto &thread_info : m_threads_stepping_with_breakpoint) { - Status error = RemoveBreakpoint(thread_info.second); + // stepping and potential signal handler skipping. + for (auto thread_info_iter = m_threads_stepping_with_breakpoint.begin(); + thread_info_iter != m_threads_stepping_with_breakpoint.end();) { + auto to_remove = thread_info_iter++; + tid_t tid = to_remove->first; + Status error = RemoveTemporarySteppingBreakpoint(to_remove); if (error.Fail()) - LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}", - thread_info.first, error); + LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}", tid, error); } - m_threads_stepping_with_breakpoint.clear(); // Notify the delegate about the stop SetCurrentThreadID(m_pending_notification_tid); diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h index b7d70a604144358013c5d9c2587b4627a6cd7e9e..bc73002676dd67b1502660571f29ecf93a9e9139 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -142,8 +142,15 @@ private: lldb::tid_t m_pending_notification_tid = LLDB_INVALID_THREAD_ID; // List of thread ids stepping with a breakpoint with the address of - // the relevan breakpoint - std::map m_threads_stepping_with_breakpoint; + // the relevant breakpoint and whether the process should autocontinue + // (for example, when skipping a signal handler and returning from it, to try + // single-stepping again) + struct SteppingBreakpointInfo { + lldb::addr_t address; + bool autocontinue; + }; + using SteppingBreakpointsTy = std::map; + SteppingBreakpointsTy m_threads_stepping_with_breakpoint; /// Inferior memory (allocated by us) and its size. llvm::DenseMap m_allocated_memory; @@ -166,13 +173,21 @@ private: void MonitorTrace(NativeThreadLinux &thread); - void MonitorBreakpoint(NativeThreadLinux &thread); + void MonitorBreakpoint(NativeThreadLinux &thread, bool is_hardware); void MonitorWatchpoint(NativeThreadLinux &thread, uint32_t wp_index); void MonitorSignal(const siginfo_t &info, NativeThreadLinux &thread, bool exited); + lldb_private::Status SetTemporarySteppingBreakpoint(lldb::tid_t tid, + lldb::addr_t flags, + lldb::addr_t addr, + bool autocontinue, + bool overwrite); + + lldb_private::Status RemoveTemporarySteppingBreakpoint(SteppingBreakpointsTy::iterator at); + Status SetupSoftwareSingleStepping(NativeThreadLinux &thread); bool HasThreadNoLock(lldb::tid_t thread_id); diff --git a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp index 5aec98bdeca6a9572e369715f7e9f4625ea88acf..11ebdb659aef3ad70ee93092bba64bb0b68d31fb 100644 --- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp @@ -91,7 +91,8 @@ NativeThreadLinux::NativeThreadLinux(NativeProcessLinux &process, m_reg_context_up( NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux( process.GetArchitecture(), *this)), - m_stop_description() {} + m_stop_description(), + m_is_single_step_a_continue(false) {} std::string NativeThreadLinux::GetName() { NativeProcessLinux &process = GetProcess(); @@ -245,6 +246,14 @@ Status NativeThreadLinux::Resume(uint32_t signo) { reinterpret_cast(data)); } +bool NativeThreadLinux::IsSingleStepAContinue() { + return !GetProcess().SupportHardwareSingleStepping() || m_is_single_step_a_continue; +} + +void NativeThreadLinux::SetSingleStepAContinue(bool do_it) { + m_is_single_step_a_continue = do_it; +} + Status NativeThreadLinux::SingleStep(uint32_t signo) { const StateType new_state = StateType::eStateStepping; MaybeLogStateChange(new_state); @@ -262,13 +271,16 @@ Status NativeThreadLinux::SingleStep(uint32_t signo) { if (signo != LLDB_INVALID_SIGNAL_NUMBER) data = signo; - // If hardware single-stepping is not supported, we just do a continue. The - // breakpoint on the next instruction has been setup in - // NativeProcessLinux::Resume. + // If hardware single-stepping is not supported or a breakpoint for skipping a + // potential signal handler was set up, we just do a continue. The breakpoint + // in the right place has been setup in + // NativeProcessLinux::Resume[Thread]. + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD)); + LLDB_LOG(log, "NativeThreadLinux stepping with: {0}.", + IsSingleStepAContinue() ? "cont" : "singlestep"); return NativeProcessLinux::PtraceWrapper( - GetProcess().SupportHardwareSingleStepping() ? PTRACE_SINGLESTEP - : PTRACE_CONT, - m_tid, nullptr, reinterpret_cast(data)); + IsSingleStepAContinue() ? PTRACE_CONT : PTRACE_SINGLESTEP, m_tid, nullptr, + reinterpret_cast(data)); } void NativeThreadLinux::SetStoppedBySignal(uint32_t signo, diff --git a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h index fd43c89489f739f809db9d225f731a6bb44a7176..bbb0b95086f1c4bbd5c1690f1a046362441cc337 100644 --- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h @@ -58,6 +58,20 @@ private: /// LLDB_INVALID_SIGNAL_NUMBER, deliver that signal to the thread. Status Resume(uint32_t signo); + /// Return true if single-stepping should be performed as a continue. + /// This is true for software single stepping and skipping signal handlers. + /// Right now this is equivalent to: + /// - a temporary software breakpoint for stepping is installed for + // this thread + /// OR + /// - hardware single stepping is not supported (so that if a breakpoint + /// failed to be set, ptrace does not get called with singlestep when it is + /// not supported). + bool IsSingleStepAContinue(); + + /// Set whether single-stepping should be performed as a continue. + void SetSingleStepAContinue(bool do_it); + /// Single steps the thread. If \p signo is anything but /// LLDB_INVALID_SIGNAL_NUMBER, deliver that signal to the thread. Status SingleStep(uint32_t signo); @@ -103,6 +117,7 @@ private: WatchpointIndexMap m_watchpoint_index_map; WatchpointIndexMap m_hw_break_index_map; std::unique_ptr m_step_workaround; + bool m_is_single_step_a_continue; }; } // namespace process_linux } // namespace lldb_private diff --git a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp index 59fa6f3ecb541108ca091cd834b48e661dcaa7af..f188d827faae4ea91f8adcb47b9590fa740309d0 100644 --- a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp +++ b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp @@ -27,9 +27,8 @@ ThreadPlanStepOverBreakpoint::ThreadPlanStepOverBreakpoint(Thread &thread) // first in the thread plan stack when stepping over // 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_auto_continue(false), m_reenabled_breakpoint_site(false) + { m_breakpoint_addr = thread.GetRegisterContext()->GetPC(); m_breakpoint_site_id = @@ -48,8 +47,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. @@ -92,29 +89,16 @@ 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, "Got breakpoint stop reason but pc: 0x%" PRIx64 - " hasn't changed, resetting m_handling_signal." - " If we came from a signal handler, trying again.", + "hasn't changed.", pc_addr); return true; } - // Even if we are in a signal handler, handle the breakpoint as usual - SetAutoContinue(false); return false; } - case eStopReasonSignal: - if (!m_handling_signal) { - // Next stop may be a signal handler. - LLDB_LOG(log, "Preparing for signal handler handling."); - m_handling_signal = true; - } - return false; default: return false; } @@ -129,12 +113,6 @@ bool ThreadPlanStepOverBreakpoint::ShouldStop(Event *event_ptr) { bool ThreadPlanStepOverBreakpoint::StopOthers() { return true; } StateType ThreadPlanStepOverBreakpoint::GetPlanRunState() { - if (m_handling_signal) { - // Resume & wait to hit our initial breakpoint - Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); - LLDB_LOG(log, "Step over breakpoint resuming through a potential signal handler."); - return eStateRunning; - } return eStateStepping; } @@ -143,19 +121,9 @@ bool ThreadPlanStepOverBreakpoint::DoWillResume(StateType resume_state, if (current_plan) { BreakpointSiteSP bp_site_sp( m_process.GetBreakpointSiteList().FindByAddress(m_breakpoint_addr)); - if (bp_site_sp) { - Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); - if (m_handling_signal) { - // Turn the breakpoint back on and wait to hit it. - // Even if there is no userspace signal handler, we'll immediately stop - // on the breakpoint and try again. - LLDB_LOG(log, "Step over breakpoint reenabling breakpoint to try again after a potential signal handler"); - ReenableBreakpointSite(); - } else if (bp_site_sp->IsEnabled()) { - LLDB_LOG(log, "Step over breakpoint disabling breakpoint."); - m_process.DisableBreakpointSite(bp_site_sp.get()); - m_reenabled_breakpoint_site = false; - } + if (bp_site_sp && bp_site_sp->IsEnabled()) { + m_process.DisableBreakpointSite(bp_site_sp.get()); + m_reenabled_breakpoint_site = false; } } return true; @@ -175,7 +143,7 @@ bool ThreadPlanStepOverBreakpoint::MischiefManaged() { if (pc_addr == m_breakpoint_addr) { // 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. + // didn't get a chance to run. return false; } else { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); @@ -207,16 +175,9 @@ void ThreadPlanStepOverBreakpoint::SetAutoContinue(bool do_it) { } bool ThreadPlanStepOverBreakpoint::ShouldAutoContinue(Event *event_ptr) { - if (m_stopped_at_my_breakpoint) { - // 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."); - return true; - } return m_auto_continue; } bool ThreadPlanStepOverBreakpoint::IsPlanStale() { - // TODO: validate - return !m_handling_signal && GetThread().GetRegisterContext()->GetPC() != m_breakpoint_addr; + 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..f96b9a6a42a3df5df51865ab5b8d153b9105be01 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/SignalDuringBreakpointStepTestCase.py @@ -0,0 +1,177 @@ +""" +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 + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +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' + ] + + should_ignore_signal = False + num_iters = 100 + num_signal_iters = 20 + signal_iter_backoff_us = 1000 + signal_signal_iter_backoff_us = 1000 + handler_sleep_us = 0 + should_nomask = False + 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 check_breakpoint(self, bp): + self.assertTrue(bp and bp.GetNumLocations() == 1, VALID_BREAKPOINT) + + 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() + (self.target, self.process, self.thread, _) = \ + lldbutil.run_to_source_breakpoint(self, + '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.check_breakpoint(bp) + 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) + self.check_breakpoint(self.fall_out_breakpoint) + + 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 get_counter_value(self, var_name): + return self.thread.GetSelectedFrame().EvaluateExpression(var_name).GetValueAsSigned() + + def check_iteration(self): + cur_iter = self.get_counter_value('g_cur_iter') + skipped = cur_iter > self.cur_iter + self.assertEquals(self.cur_iter, cur_iter, + 'Expected action iteration %d to %s (was a breakpoint %s?) %s' % + (self.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): + def to_bool_str(x): return str(x).lower() + + action_idx = self.actions.index(action) + self.runCmd('expr action_idx=%d' % action_idx) + self.runCmd('expr should_ignore_signal=%s' % to_bool_str(self.should_ignore_signal)) + self.runCmd('expr NUM_ITERS=%d' % self.num_iters) + self.runCmd('expr NUM_SIGNAL_ITERS=%d' % self.num_signal_iters) + self.runCmd('expr SIGNAL_ITER_BACKOFF_US=%d' % self.signal_iter_backoff_us) + self.runCmd('expr SIGNAL_SIGNAL_ITER_BACKOFF_US=%d' % self.signal_signal_iter_backoff_us) + self.runCmd('expr HANDLER_SLEEP_US=%d' % self.handler_sleep_us) + self.runCmd('expr should_nomask=%s' % to_bool_str(self.should_nomask)) + + self.thread = self.process.GetThreadAtIndex(0) + + # Configure signal settings + self.runCmd('process handle SIGRTMIN -p true -s false -n false') + # 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 false') + + # 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..a6af159acb2bf945e8fe4cb1eb930f8998ff1c0f --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepIn.py @@ -0,0 +1,22 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +class SignalDuringBreakpointFuncStepInTestCase(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_in_func_breakpoint(self): + self.run_to_breakpoint_and_step('step-in-func', False) + + @skipIf(triple='^mips') + 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..9f44b7943b60301380a88956b44f7307920e3242 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOut.py @@ -0,0 +1,22 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +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') + def test_step_out_func_breakpoint(self): + self.run_to_breakpoint_and_step('step-out-func', False) + + @skipIf(triple='^mips') + 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..41e5793e45ed89e3a5feffe213fa89e98da0422b --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOver.py @@ -0,0 +1,36 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +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') + def test_step_over_func_breakpoint(self): + self.run_to_breakpoint_and_step('step-over-func', False) + + @skipIf(triple='^mips') + def test_step_over_func_no_breakpoint(self): + self.run_to_breakpoint_and_step('step-over-func', True) + + def check_user_bp(self, action, is_breakpoint_disabled): + self.runCmd('thread %s' % self.action2cmd(action)) + self.check_stopped_at_breakpoint(self.user_bp, 'Expected to not skip the user breakpoint') + self.runCmd('continue') + self.check_stopped_at_action_line(action) + + @skipIf(triple='^mips') + def test_user_breakpoint(self): + self.user_bp = self.target.BreakpointCreateBySourceRegex( + 'do something breakpoint', + self.main_source_spec) + self.check_breakpoint(self.user_bp) + self.set_up_and_iterate('step-over-func', False, self.check_user_bp) 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..fd4f0ea492618eff7496b7df94e60e03f8063e43 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepUntil.py @@ -0,0 +1,22 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +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') + def test_step_until_func_breakpoint(self): + self.run_to_breakpoint_and_step('step-until-func', False) + + @skipIf(triple='^mips') + 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..4bd1450ddbe6815ebf5fdfee34ac9e064e9b7c0f --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOut.py @@ -0,0 +1,22 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +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..9dd1006c27aab94e8d06fe4c9ec80dccc1cffe12 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOver.py @@ -0,0 +1,22 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +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..36f69d7e0c513c9e2e49eff4bffc983561844be4 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepUntil.py @@ -0,0 +1,22 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +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/TestSignalStepOverHandler.py b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalStepOverHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..3d6fc74bd8747fa51e47628fefe5fb678b6e506e --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalStepOverHandler.py @@ -0,0 +1,68 @@ +""" +This test is intended to create a situation in which signals are received by +a thread while it is stepping off a breakpoint. Breakpoints inside the handler +should still be hit. If a handler is not set at all, nothing should break. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from SignalDuringBreakpointStepTestCase import SignalDuringBreakpointStepTestCase + + +# Needs os-specific implementation +@skipUnlessPlatform(["linux"]) +class TestSignalStepOverHandler(SignalDuringBreakpointStepTestCase): + + mydir = TestBase.compute_mydir(__file__) + + num_iters = 10 + num_signal_iters = 5 + action = 'step-until-func' + + def set_up_step_over_handler_with_breakpoint(self): + self.handler_bp = self.target.BreakpointCreateBySourceRegex( + 'Break here in signal handler', + self.main_source_spec) + + 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) + + # Step + self.runCmd('thread %s' % self.action2cmd(action)) + + handler_cnt = 0 + self.runCmd('expr g_signal_count = 0') + while lldbutil.get_one_thread_stopped_at_breakpoint(self.process, self.handler_bp) == self.thread: + # Stopped inside the handler, continue until we are back in our parent function + handler_cnt += 1 + self.runCmd('continue') + + # Now stopped at the corresponding line + self.check_stopped_at_action_line(action) + self.assertEquals(handler_cnt, + self.get_counter_value('g_signal_count'), + 'Missed some breakpoint hits inside the signal handler') + self.handler_bp.SetEnabled(False) + + # 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) + + @skipIf(triple='^mips') + def test_recursive_handler(self): + # Test that recursive handlers do not influence stepping + self.handler_useconds_sleep = 2000 + self.should_nomask = True + self.run_to_breakpoint_and_step(self.action, False) + + @skipIf(triple='^mips') + def test_ignored_handler(self): + # Test that ignored handlers do not influence stepping + self.should_ignore_signal = True + self.run_to_breakpoint_and_step(self.action, False) 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..f6e5c1f3768704ea5f0b253d04e169fc81f3cf85 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/main.cpp @@ -0,0 +1,166 @@ +// 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; + +// number of times to repeat the action +int NUM_ITERS = 0; +// number of times to send a signal per action +int NUM_SIGNAL_ITERS = 0; +// number of microseconds to wait between new action checks +int SIGNAL_ITER_BACKOFF_US = 0; +// number of microseconds to wait before sending another signal +int SIGNAL_SIGNAL_ITER_BACKOFF_US = 0; +// number of microseconds to wait inside the signal handler +int HANDLER_SLEEP_US = 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++; } // do something breakpoint + +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 +} + +void step_out() { + step_out_helper(); + g_action++; // step-out line +} + +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 (HANDLER_SLEEP_US > 0) + usleep(HANDLER_SLEEP_US); + 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, sighandler_t handler, int sa_flags = 0) { + sigset_t empty_sigset; + sigemptyset(&empty_sigset); + + struct sigaction action; + action.sa_sigaction = 0; + action.sa_mask = empty_sigset; + action.sa_flags = sa_flags; + 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; + bool should_ignore_signal = false; + bool should_nomask = false; + + // Don't let either thread do anything until they're both ready. + pseudo_barrier_init(g_barrier, 2); // Break here and adjust + // NUM_ITERS, action_idx, and sa_flags + + register_signal_handler(SIGRTMIN, should_ignore_signal ? SIG_IGN : signal_handler, + should_nomask ? SA_NODEFER : 0); + + 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(SIGNAL_ITER_BACKOFF_US)); + + // Send some signals + for (int i = 0; i < NUM_SIGNAL_ITERS; i++) { + pthread_kill(pid, SIGRTMIN); + std::this_thread::sleep_for( + std::chrono::microseconds(SIGNAL_SIGNAL_ITER_BACKOFF_US)); + } + } + }); + + do_action_func(actions[action_idx]); + return 0; // Break here to not fall out +} + +int main() { + return dotest(); +}