diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -460,6 +460,7 @@ 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/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -419,6 +419,14 @@ 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/FreeBSD/NativeProcessFreeBSD.h b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h --- a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h +++ b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h @@ -10,7 +10,7 @@ #define liblldb_NativeProcessFreeBSD_H_ #include "Plugins/Process/POSIX/NativeProcessELF.h" -#include "Plugins/Process/Utility/NativeProcessSoftwareSingleStep.h" +#include "Plugins/Process/Utility/NativeProcessContinueUntilBreakpoint.h" #include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Utility/ArchSpec.h" @@ -28,7 +28,7 @@ /// /// Changes in the inferior process state are broadcasted. class NativeProcessFreeBSD : public NativeProcessELF, - private NativeProcessSoftwareSingleStep { + private NativeProcessContinueUntilBreakpoint { public: class Factory : public NativeProcessProtocol::Factory { public: diff --git a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp --- a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp +++ b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp @@ -306,11 +306,11 @@ m_threads_stepping_with_breakpoint.find(thread->GetID()); if (thread_info != m_threads_stepping_with_breakpoint.end()) { thread->SetStoppedByTrace(); - Status brkpt_error = RemoveBreakpoint(thread_info->second); + Status brkpt_error = + RemoveTemporarySteppingBreakpoint(*this, thread_info); if (brkpt_error.Fail()) LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}", thread_info->first, brkpt_error); - m_threads_stepping_with_breakpoint.erase(thread_info); } else thread->SetStoppedByBreakpoint(); FixupBreakpointPCAsNeeded(*thread); diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -23,7 +23,7 @@ #include "IntelPTCollector.h" #include "NativeThreadLinux.h" #include "Plugins/Process/POSIX/NativeProcessELF.h" -#include "Plugins/Process/Utility/NativeProcessSoftwareSingleStep.h" +#include "Plugins/Process/Utility/NativeProcessContinueUntilBreakpoint.h" namespace lldb_private { class Status; @@ -38,7 +38,7 @@ /// /// Changes in the inferior process state are broadcasted. class NativeProcessLinux : public NativeProcessELF, - private NativeProcessSoftwareSingleStep { + private NativeProcessContinueUntilBreakpoint { public: class Factory : public NativeProcessProtocol::Factory { public: @@ -135,6 +135,17 @@ bool SupportHardwareSingleStepping() const; + /// Returns 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 IsThreadSingleStepAContinue(NativeThreadLinux &thread) const; + /// Writes a siginfo_t structure corresponding to the given thread ID to the /// memory region pointed to by \p siginfo. Status GetSignalInfo(lldb::tid_t tid, void *siginfo) const; @@ -176,7 +187,7 @@ void MonitorTrace(NativeThreadLinux &thread); - void MonitorBreakpoint(NativeThreadLinux &thread); + void MonitorBreakpoint(NativeThreadLinux &thread, bool is_hardware); void MonitorWatchpoint(NativeThreadLinux &thread, uint32_t wp_index); diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -646,7 +646,7 @@ "breakpoint hits, pid = {0}, error = {1}", thread.GetID(), error); if (bp_index != LLDB_INVALID_INDEX32) { - MonitorBreakpoint(thread); + MonitorBreakpoint(thread, true); break; } @@ -677,7 +677,7 @@ // NO BREAK #endif case TRAP_BRKPT: - MonitorBreakpoint(thread); + MonitorBreakpoint(thread, false); break; case SIGTRAP: @@ -709,17 +709,45 @@ StopRunningThreads(thread.GetID()); } -void NativeProcessLinux::MonitorBreakpoint(NativeThreadLinux &thread) { +void NativeProcessLinux::MonitorBreakpoint(NativeThreadLinux &thread, + bool is_hardware) { Log *log = GetLog(LLDBLog::Process | LLDBLog::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(*this, 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()); } @@ -889,6 +917,12 @@ return true; } +bool NativeProcessLinux::IsThreadSingleStepAContinue( + NativeThreadLinux &thread) const { + return !SupportHardwareSingleStepping() || + ThreadHasSteppingBreakpoint(thread); +} + Status NativeProcessLinux::Resume(const ResumeActionList &resume_actions) { Log *log = GetLog(POSIXLog::Process); LLDB_LOG(log, "pid {0}", GetID()); @@ -1799,6 +1833,27 @@ 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. + error = SetTemporarySteppingBreakpoint(thread, 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); @@ -1843,14 +1898,15 @@ Log *log = GetLog(LLDBLog::Process | LLDBLog::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(*this, 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/NativeThreadLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp @@ -267,13 +267,17 @@ 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]. + bool step_continue = GetProcess().IsThreadSingleStepAContinue(*this); + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOG(log, "NativeThreadLinux stepping with: {0}.", + step_continue ? "cont" : "singlestep"); return NativeProcessLinux::PtraceWrapper( - GetProcess().SupportHardwareSingleStepping() ? PTRACE_SINGLESTEP - : PTRACE_CONT, - m_tid, nullptr, reinterpret_cast(data)); + step_continue ? PTRACE_CONT : PTRACE_SINGLESTEP, m_tid, nullptr, + reinterpret_cast(data)); } void NativeThreadLinux::SetStoppedBySignal(uint32_t signo, diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt --- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt @@ -9,7 +9,7 @@ LinuxSignals.cpp MemoryTagManagerAArch64MTE.cpp MipsLinuxSignals.cpp - NativeProcessSoftwareSingleStep.cpp + NativeProcessContinueUntilBreakpoint.cpp NativeRegisterContextDBReg_arm64.cpp NativeRegisterContextDBReg_x86.cpp NativeRegisterContextRegisterInfo.cpp diff --git a/lldb/source/Plugins/Process/Utility/NativeProcessContinueUntilBreakpoint.h b/lldb/source/Plugins/Process/Utility/NativeProcessContinueUntilBreakpoint.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/NativeProcessContinueUntilBreakpoint.h @@ -0,0 +1,47 @@ +//===-- NativeProcessContinueUntilBreakpoint.h ------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef lldb_NativeProcessContinueUntilBreakpoint_h +#define lldb_NativeProcessContinueUntilBreakpoint_h + +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Host/common/NativeThreadProtocol.h" + +#include + +namespace lldb_private { + +class NativeProcessContinueUntilBreakpoint { +public: + Status SetupSoftwareSingleStepping(NativeThreadProtocol &thread); + + bool ThreadHasSteppingBreakpoint(const NativeThreadProtocol &thread) const; + +protected: + // List of thread ids stepping with a breakpoint with the address of + // the relevant breakpoint and whether the process should autocontinue + // (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; + + Status SetTemporarySteppingBreakpoint(NativeThreadProtocol &thread, + lldb::addr_t flags, lldb::addr_t addr, + bool autocontinue, bool overwrite); + + Status RemoveTemporarySteppingBreakpoint(NativeProcessProtocol &process, + SteppingBreakpointsTy::iterator at); +}; + +} // namespace lldb_private + +#endif // #ifndef lldb_NativeProcessContinueUntilBreakpoint_h diff --git a/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp b/lldb/source/Plugins/Process/Utility/NativeProcessContinueUntilBreakpoint.cpp rename from lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp rename to lldb/source/Plugins/Process/Utility/NativeProcessContinueUntilBreakpoint.cpp --- a/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp +++ b/lldb/source/Plugins/Process/Utility/NativeProcessContinueUntilBreakpoint.cpp @@ -1,4 +1,4 @@ -//===-- NativeProcessSoftwareSingleStep.cpp -------------------------------===// +//===-- NativeProcessContinueUntilBreakpoint.cpp --------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,10 +6,11 @@ // //===----------------------------------------------------------------------===// -#include "NativeProcessSoftwareSingleStep.h" +#include "NativeProcessContinueUntilBreakpoint.h" #include "lldb/Core/EmulateInstruction.h" #include "lldb/Host/common/NativeRegisterContext.h" +#include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/RegisterValue.h" #include @@ -17,6 +18,61 @@ using namespace lldb; using namespace lldb_private; +Status NativeProcessContinueUntilBreakpoint::SetTemporarySteppingBreakpoint( + NativeThreadProtocol &thread, lldb::addr_t flags, lldb::addr_t addr, + bool autocontinue, bool overwrite) { + Log *log = GetLog(LLDBLog::Process); + Status error; + + tid_t tid = thread.GetID(); + NativeProcessProtocol &process = thread.GetProcess(); + const ArchSpec &arch = process.GetArchitecture(); + + auto found_bp = m_threads_stepping_with_breakpoint.find(tid); + if (found_bp != m_threads_stepping_with_breakpoint.end()) { + if (overwrite) { + error = RemoveTemporarySteppingBreakpoint(process, found_bp); + if (error.Fail()) + LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}", tid, error); + } else { + return Status(); + } + } + + int size_hint = 0; + if (arch.GetMachine() == llvm::Triple::arm) { + if (flags & 0x20) { + // Thumb mode + size_hint = 2; + } else { + // Arm mode + size_hint = 4; + } + } else if (arch.IsMIPS() || arch.GetTriple().isPPC64() || + arch.GetTriple().isRISCV() || arch.GetTriple().isLoongArch()) { + size_hint = 4; + } + error = process.SetBreakpoint(addr, size_hint, /*hardware=*/false); + + if (error.Fail()) + return error; + + m_threads_stepping_with_breakpoint.insert({tid, {addr, autocontinue}}); + return Status(); +} + +Status NativeProcessContinueUntilBreakpoint::RemoveTemporarySteppingBreakpoint( + NativeProcessProtocol &process, SteppingBreakpointsTy::iterator at) { + addr_t addr = at->second.address; + m_threads_stepping_with_breakpoint.erase(at); + return process.RemoveBreakpoint(addr, /*hardware=*/false); +} + +bool NativeProcessContinueUntilBreakpoint::ThreadHasSteppingBreakpoint( + const NativeThreadProtocol &thread) const { + return m_threads_stepping_with_breakpoint.count(thread.GetID()) > 0; +} + namespace { struct EmulatorBaton { @@ -94,7 +150,7 @@ LLDB_INVALID_ADDRESS); } -Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping( +Status NativeProcessContinueUntilBreakpoint::SetupSoftwareSingleStepping( NativeThreadProtocol &thread) { Status error; NativeProcessProtocol &process = thread.GetProcess(); @@ -158,28 +214,12 @@ return Status("Instruction emulation failed unexpectedly."); } - int size_hint = 0; - if (arch.GetMachine() == llvm::Triple::arm) { - if (next_flags & 0x20) { - // Thumb mode - size_hint = 2; - } else { - // Arm mode - size_hint = 4; - } - } else if (arch.IsMIPS() || arch.GetTriple().isPPC64() || - arch.GetTriple().isRISCV() || arch.GetTriple().isLoongArch()) - size_hint = 4; - error = process.SetBreakpoint(next_pc, size_hint, /*hardware=*/false); - + error = SetTemporarySteppingBreakpoint(thread, next_flags, next_pc, + /*autocontinue=*/false, + /*overwrite=*/true); // If setting the breakpoint fails because next_pc is out of the address // space, ignore it and let the debugee segfault. - if (error.GetError() == EIO || error.GetError() == EFAULT) { + if (error.GetError() == EIO || error.GetError() == EFAULT) return Status(); - } else if (error.Fail()) - return error; - - m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc}); - - return Status(); + return error; } diff --git a/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.h b/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.h deleted file mode 100644 --- a/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.h +++ /dev/null @@ -1,31 +0,0 @@ -//===-- NativeProcessSoftwareSingleStep.h -----------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef lldb_NativeProcessSoftwareSingleStep_h -#define lldb_NativeProcessSoftwareSingleStep_h - -#include "lldb/Host/common/NativeProcessProtocol.h" -#include "lldb/Host/common/NativeThreadProtocol.h" - -#include - -namespace lldb_private { - -class NativeProcessSoftwareSingleStep { -public: - Status SetupSoftwareSingleStepping(NativeThreadProtocol &thread); - -protected: - // List of thread ids stepping with a breakpoint with the address of - // the relevan breakpoint - std::map m_threads_stepping_with_breakpoint; -}; - -} // namespace lldb_private - -#endif // #ifndef lldb_NativeProcessSoftwareSingleStep_h 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 --- /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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/SignalDuringBreakpointStepTestCase.py @@ -0,0 +1,175 @@ +""" +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): + + 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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepIn.py @@ -0,0 +1,20 @@ +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): + + # 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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOut.py @@ -0,0 +1,20 @@ +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): + + # 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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepOver.py @@ -0,0 +1,34 @@ +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): + + # 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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointFuncStepUntil.py @@ -0,0 +1,20 @@ +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): + + # 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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOut.py @@ -0,0 +1,20 @@ +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): + + # 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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepOver.py @@ -0,0 +1,20 @@ +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): + + # 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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalDuringBreakpointStepUntil.py @@ -0,0 +1,20 @@ +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): + + # 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 --- /dev/null +++ b/lldb/test/API/functionalities/thread/signal_during_breakpoint_step/TestSignalStepOverHandler.py @@ -0,0 +1,66 @@ +""" +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): + + 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 --- /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(); +}