diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index fb06850d34a19..045fa9ee2a05a 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -114,6 +114,7 @@ class ProcessProperties : public Properties { Args GetAlwaysRunThreadNames() const; FollowForkMode GetFollowForkMode() const; bool TrackMemoryCacheChanges() const; + bool GetUseDelayedBreakpoints() const; protected: Process *m_process; // Can be nullptr for global ProcessProperties @@ -2246,6 +2247,9 @@ class Process : public std::enable_shared_from_this, // Process Breakpoints size_t GetSoftwareBreakpointTrapOpcode(BreakpointSite *bp_site); + enum class BreakpointAction { Enable, Disable }; + +protected: virtual Status EnableBreakpointSite(BreakpointSite *bp_site) { return Status::FromErrorStringWithFormatv( "error: {0} does not support enabling breakpoints", GetPluginName()); @@ -2256,6 +2260,24 @@ class Process : public std::enable_shared_from_this, "error: {0} does not support disabling breakpoints", GetPluginName()); } + /// Compare BreakpointSiteSPs by ID, so that iteration order is independent + /// of pointer addresses. + struct SiteIDCmp { + bool operator()(const lldb::BreakpointSiteSP &lhs, + const lldb::BreakpointSiteSP &rhs) const { + return lhs->GetID() < rhs->GetID(); + } + }; + using BreakpointSiteToActionMap = + std::map; + + virtual llvm::Error + UpdateBreakpointSites(const BreakpointSiteToActionMap &site_to_action); + +public: + llvm::Error ExecuteBreakpointSiteAction(BreakpointSite &site, + Process::BreakpointAction action); + // This is implemented completely using the lldb::Process API. Subclasses // don't need to implement this function unless the standard flow of read // existing opcode, write breakpoint opcode, verify breakpoint opcode doesn't @@ -2286,6 +2308,8 @@ class Process : public std::enable_shared_from_this, bool IsBreakpointSiteEnabled(const BreakpointSite &site); + bool IsBreakpointSitePhysicallyEnabled(const BreakpointSite &site); + // BreakpointLocations use RemoveConstituentFromBreakpointSite to remove // themselves from the constituent's list of this breakpoint sites. void RemoveConstituentFromBreakpointSite(lldb::user_id_t site_id, @@ -3540,6 +3564,21 @@ void PruneThreadPlans(); /// GetExtendedCrashInformation. StructuredData::DictionarySP m_crash_info_dict_sp; + struct DelayedBreakpointCache { + void Enqueue(lldb::BreakpointSiteSP site, BreakpointAction action); + void RemoveSite(lldb::BreakpointSiteSP site) { + m_site_to_action.erase(site); + } + void Clear() { m_site_to_action.clear(); } + + BreakpointSiteToActionMap m_site_to_action; + }; + + DelayedBreakpointCache m_delayed_breakpoints; + std::recursive_mutex m_delayed_breakpoints_mutex; + + llvm::Error FlushDelayedBreakpoints(); + size_t RemoveBreakpointOpcodesFromBuffer(lldb::addr_t addr, size_t size, uint8_t *buf) const; diff --git a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp index b4598a422e579..6166096a4e1d3 100644 --- a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp +++ b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp @@ -633,7 +633,7 @@ Status ProcessKDP::EnableBreakpointSite(BreakpointSite *bp_site) { if (m_comm.LocalBreakpointsAreSupported()) { Status error; - if (!IsBreakpointSiteEnabled(*bp_site)) { + if (!IsBreakpointSitePhysicallyEnabled(*bp_site)) { if (m_comm.SendRequestBreakpoint(true, bp_site->GetLoadAddress())) { SetBreakpointSiteEnabled(*bp_site); bp_site->SetType(BreakpointSite::eExternal); @@ -649,7 +649,7 @@ Status ProcessKDP::EnableBreakpointSite(BreakpointSite *bp_site) { Status ProcessKDP::DisableBreakpointSite(BreakpointSite *bp_site) { if (m_comm.LocalBreakpointsAreSupported()) { Status error; - if (IsBreakpointSiteEnabled(*bp_site)) { + if (IsBreakpointSitePhysicallyEnabled(*bp_site)) { BreakpointSite::Type bp_type = bp_site->GetType(); if (bp_type == BreakpointSite::eExternal) { if (m_destroy_in_process && m_comm.IsRunning()) { diff --git a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp index deb95d9a03ca3..5fbc1f62a065f 100644 --- a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp +++ b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp @@ -636,7 +636,7 @@ StopInfoSP StopInfoMachException::CreateStopReasonWithMachException( addr_t pc = reg_ctx_sp->GetPC(); BreakpointSiteSP bp_site_sp = process_sp->GetBreakpointSiteList().FindByAddress(pc); - if (bp_site_sp && process_sp->IsBreakpointSiteEnabled(*bp_site_sp)) + if (bp_site_sp && process_sp->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) thread.SetThreadStoppedAtUnexecutedBP(pc); switch (exc_type) { @@ -771,7 +771,8 @@ StopInfoSP StopInfoMachException::CreateStopReasonWithMachException( if (!bp_site_sp && reg_ctx_sp) { bp_site_sp = process_sp->GetBreakpointSiteList().FindByAddress(pc); } - if (bp_site_sp && process_sp->IsBreakpointSiteEnabled(*bp_site_sp)) { + if (bp_site_sp && + process_sp->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) { // We've hit this breakpoint, whether it was intended for this thread // or not. Clear this in the Tread object so we step past it on resume. thread.SetThreadHitBreakpointSite(); @@ -865,7 +866,8 @@ bool StopInfoMachException::WasContinueInterrupted(Thread &thread) { // We have a hardware breakpoint -- this is the kernel bug. auto &bp_site_list = process_sp->GetBreakpointSiteList(); for (auto &site : bp_site_list.Sites()) { - if (site->IsHardware() && process_sp->IsBreakpointSiteEnabled(*site)) { + if (site->IsHardware() && + process_sp->IsBreakpointSitePhysicallyEnabled(*site)) { LLDB_LOGF(log, "Thread stopped with insn-step completed mach exception but " "thread was not stepping; there is a hardware breakpoint set."); diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index c41547a2c0961..28b5554069c90 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -415,7 +415,7 @@ void ProcessWindows::RefreshStateAfterStop() { // If we're at a BreakpointSite, mark this as an Unexecuted Breakpoint. // We'll clear that state if we've actually executed the breakpoint. BreakpointSiteSP site(GetBreakpointSiteList().FindByAddress(pc)); - if (site && IsBreakpointSiteEnabled(*site)) + if (site && IsBreakpointSitePhysicallyEnabled(*site)) stop_thread->SetThreadStoppedAtUnexecutedBP(pc); switch (active_exception->GetExceptionCode()) { diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index e8f32c564ee03..adf108919b36e 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1848,7 +1848,7 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo( addr_t pc = thread_sp->GetRegisterContext()->GetPC(); BreakpointSiteSP bp_site_sp = thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc); - if (bp_site_sp && IsBreakpointSiteEnabled(*bp_site_sp)) + if (bp_site_sp && IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) thread_sp->SetThreadStoppedAtUnexecutedBP(pc); if (exc_type != 0) { @@ -2032,7 +2032,7 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo( // BreakpointSites in any other location, but we can't know for // sure what happened so it's a reasonable default. if (bp_site_sp) { - if (IsBreakpointSiteEnabled(*bp_site_sp)) + if (IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) thread_sp->SetThreadHitBreakpointSite(); if (bp_site_sp->ValidForThisThread(*thread_sp)) { @@ -3429,7 +3429,7 @@ Status ProcessGDBRemote::EnableBreakpointSite(BreakpointSite *bp_site) { site_id, (uint64_t)addr); // Breakpoint already exists and is enabled - if (IsBreakpointSiteEnabled(*bp_site)) { + if (IsBreakpointSitePhysicallyEnabled(*bp_site)) { LLDB_LOGF(log, "ProcessGDBRemote::EnableBreakpointSite (size_id = %" PRIu64 ") address = 0x%" PRIx64 " -- SUCCESS (already enabled)", @@ -3450,7 +3450,7 @@ Status ProcessGDBRemote::DisableBreakpointSite(BreakpointSite *bp_site) { ") addr = 0x%8.8" PRIx64, site_id, (uint64_t)addr); - if (!IsBreakpointSiteEnabled(*bp_site)) { + if (!IsBreakpointSitePhysicallyEnabled(*bp_site)) { LLDB_LOGF(log, "ProcessGDBRemote::DisableBreakpointSite (site_id = %" PRIu64 ") addr = 0x%8.8" PRIx64 " -- SUCCESS (already disabled)", @@ -6112,7 +6112,7 @@ void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints( GetBreakpointSiteList().ForEach([this, enable, entry_addr, log](BreakpointSite *bp_site) { - if (IsBreakpointSiteEnabled(*bp_site) && + if (IsBreakpointSitePhysicallyEnabled(*bp_site) && (bp_site->GetType() == BreakpointSite::eSoftware || bp_site->GetType() == BreakpointSite::eExternal)) { // During expression evaluation, retain the expression-return trap @@ -6136,7 +6136,7 @@ void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints( void ProcessGDBRemote::DidForkSwitchHardwareTraps(bool enable) { if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) { GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) { - if (IsBreakpointSiteEnabled(*bp_site) && + if (IsBreakpointSitePhysicallyEnabled(*bp_site) && bp_site->GetType() == BreakpointSite::eHardware) { m_gdb_comm.SendGDBStoppointTypePacket( eBreakpointHardware, enable, bp_site->GetLoadAddress(), diff --git a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp index ac0f451a3d5fa..c254a6841b707 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp @@ -265,7 +265,7 @@ size_t ScriptedProcess::DoWriteMemory(lldb::addr_t vm_addr, const void *buf, Status ScriptedProcess::EnableBreakpointSite(BreakpointSite *bp_site) { assert(bp_site != nullptr); - if (IsBreakpointSiteEnabled(*bp_site)) { + if (IsBreakpointSitePhysicallyEnabled(*bp_site)) { return {}; } diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp index 35774cfa01742..d11d134295579 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp @@ -316,7 +316,7 @@ bool ScriptedThread::CalculateStopInfo() { ProcessSP proc = GetProcess(); if (BreakpointSiteSP bp_site_sp = proc->GetBreakpointSiteList().FindByAddress(pc)) - if (proc->IsBreakpointSiteEnabled(*bp_site_sp)) + if (proc->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) SetThreadStoppedAtUnexecutedBP(pc); } diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 58c41c2584676..cb9a13e5bc80c 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -79,6 +79,17 @@ using namespace lldb; using namespace lldb_private; using namespace std::chrono; +void Process::DelayedBreakpointCache::Enqueue(lldb::BreakpointSiteSP site, + BreakpointAction action) { + auto [previous, inserted] = m_site_to_action.insert({site, action}); + // New site or already enqueued for the same action. + if (inserted || previous->second == action) + return; + // Previously enqueued for the opposite action, don't update the site. + m_site_to_action.erase(previous); + assert(site->m_enabled == (action == BreakpointAction::Enable)); +} + class ProcessOptionValueProperties : public Cloneable { public: @@ -322,6 +333,12 @@ bool ProcessProperties::GetStopOnExec() const { idx, g_process_properties[idx].default_uint_value != 0); } +bool ProcessProperties::GetUseDelayedBreakpoints() const { + const uint32_t idx = ePropertyUseDelayedBreakpoints; + return GetPropertyAtIndexAs( + idx, g_process_properties[idx].default_uint_value != 0); +} + std::chrono::seconds ProcessProperties::GetUtilityExpressionTimeout() const { const uint32_t idx = ePropertyUtilityExpressionTimeout; uint64_t value = GetPropertyAtIndexAs( @@ -1546,7 +1563,8 @@ Process::GetBreakpointSiteList() const { void Process::DisableAllBreakpointSites() { m_breakpoint_site_list.ForEach([this](BreakpointSite *bp_site) -> void { - DisableBreakpointSite(bp_site); + llvm::consumeError( + ExecuteBreakpointSiteAction(*bp_site, BreakpointAction::Disable)); }); } @@ -1564,7 +1582,8 @@ Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id) { BreakpointSiteSP bp_site_sp = m_breakpoint_site_list.FindByID(break_id); if (bp_site_sp) { if (IsBreakpointSiteEnabled(*bp_site_sp)) - error = DisableBreakpointSite(bp_site_sp.get()); + error = Status::FromError( + ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable)); } else { error = Status::FromErrorStringWithFormat( "invalid breakpoint site ID: %" PRIu64, break_id); @@ -1573,12 +1592,40 @@ Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id) { return error; } +llvm::Error Process::ExecuteBreakpointSiteAction(BreakpointSite &site, + BreakpointAction action) { + auto site_sp = site.shared_from_this(); + std::unique_lock guard(m_delayed_breakpoints_mutex); + + // Ignore requests that won't change the Site status. + if (IsBreakpointSiteEnabled(*site_sp) == (action == BreakpointAction::Enable)) + return llvm::Error::success(); + + if (GetUseDelayedBreakpoints()) { + m_delayed_breakpoints.Enqueue(site_sp, action); + return llvm::Error::success(); + } + + m_delayed_breakpoints.RemoveSite(site_sp); + guard.unlock(); + + switch (action) { + case BreakpointAction::Enable: + return EnableBreakpointSite(site_sp.get()).takeError(); + case BreakpointAction::Disable: + return DisableBreakpointSite(site_sp.get()).takeError(); + } + + llvm_unreachable("Unhandled BreakpointAction"); +} + Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) { Status error; BreakpointSiteSP bp_site_sp = m_breakpoint_site_list.FindByID(break_id); if (bp_site_sp) { if (!IsBreakpointSiteEnabled(*bp_site_sp)) - error = EnableBreakpointSite(bp_site_sp.get()); + error = Status::FromError( + ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Enable)); } else { error = Status::FromErrorStringWithFormat( "invalid breakpoint site ID: %" PRIu64, break_id); @@ -1587,6 +1634,20 @@ Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) { } bool Process::IsBreakpointSiteEnabled(const BreakpointSite &site) { + std::lock_guard guard(m_delayed_breakpoints_mutex); + + // `site` won't be mutated, but the cache stores mutable pointers. + auto it = m_delayed_breakpoints.m_site_to_action.find( + const_cast(site).shared_from_this()); + + // If no actions are delayed, use the current state of the site. + if (it == m_delayed_breakpoints.m_site_to_action.end()) + return site.m_enabled; + + return it->second == BreakpointAction::Enable; +} + +bool Process::IsBreakpointSitePhysicallyEnabled(const BreakpointSite &site) { return site.m_enabled; } @@ -1648,6 +1709,35 @@ static addr_t ComputeConstituentLoadAddress(BreakpointLocation &constituent, return resolved_address.GetOpcodeLoadAddress(&target); } +llvm::Error Process::FlushDelayedBreakpoints() { + std::unique_lock guard(m_delayed_breakpoints_mutex); + + // Clear the cache in m_delayed_breakpoints so it can't affect the actual + // enabling of breakpoints. For example, if `EnableSoftwareBreakpoint` is + // called outside of FlushDelayedBreakpoints, it needs to check the delayed + // breakpoints and possibly early return. However, when called from + // FlushDelayedBreakpoints, the queue better be empty so that no early returns + // take place. + auto site_to_action = std::move(m_delayed_breakpoints.m_site_to_action); + m_delayed_breakpoints.m_site_to_action.clear(); + + guard.unlock(); + // Use a copy of the cache so that iteration is safe. + return UpdateBreakpointSites(site_to_action); +} + +llvm::Error Process::UpdateBreakpointSites( + const BreakpointSiteToActionMap &site_to_action) { + llvm::Error error = llvm::Error::success(); + for (auto [site, action] : site_to_action) { + Status new_error = action == BreakpointAction::Enable + ? EnableBreakpointSite(site.get()) + : DisableBreakpointSite(site.get()); + error = llvm::joinErrors(std::move(error), new_error.takeError()); + } + return error; +} + lldb::break_id_t Process::CreateBreakpointSite(const BreakpointLocationSP &constituent, bool use_hardware) { @@ -1667,7 +1757,15 @@ Process::CreateBreakpointSite(const BreakpointLocationSP &constituent, BreakpointSiteSP bp_site_sp( new BreakpointSite(constituent, load_addr, use_hardware)); - Status error = EnableBreakpointSite(bp_site_sp.get()); + + bool bp_from_address = + constituent->GetBreakpoint().GetResolver()->GetResolverTy() == + BreakpointResolver::ResolverTy::AddressResolver; + bool should_be_eager = use_hardware || bp_from_address; + + auto error = should_be_eager ? EnableBreakpointSite(bp_site_sp.get()) + : Status::FromError(ExecuteBreakpointSiteAction( + *bp_site_sp, BreakpointAction::Enable)); if (error.Success()) { constituent->SetBreakpointSite(bp_site_sp); return m_breakpoint_site_list.Add(bp_site_sp); @@ -1692,7 +1790,8 @@ void Process::RemoveConstituentFromBreakpointSite( if (num_constituents == 0) { // Don't try to disable the site if we don't have a live process anymore. if (IsAlive()) - DisableBreakpointSite(bp_site_sp.get()); + llvm::consumeError( + ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable)); m_breakpoint_site_list.RemoveByAddress(bp_site_sp->GetLoadAddress()); } } @@ -3435,6 +3534,9 @@ Status Process::PrivateResume() { "Process::PrivateResume PreResumeActions failed, not resuming."); } else { m_mod_id.BumpResumeID(); + if (auto E = FlushDelayedBreakpoints()) + LLDB_LOG_ERROR(log, std::move(E), + "Failed to update some delayed breakpoints: {0}"); error = DoResume(direction); if (error.Success()) { DidResume(); @@ -3641,6 +3743,10 @@ Status Process::Detach(bool keep_stopped) { m_thread_list.DiscardThreadPlans(); DisableAllBreakpointSites(); + if (auto error = FlushDelayedBreakpoints()) + LLDB_LOG_ERROR( + GetLog(LLDBLog::Process), std::move(error), + "Failed to update some delayed breakpoints during detach: {0}"); error = DoDetach(keep_stopped); if (error.Success()) { @@ -3710,6 +3816,10 @@ Status Process::DestroyImpl(bool force_kill) { // doing this now. m_thread_list.DiscardThreadPlans(); DisableAllBreakpointSites(); + if (auto error = FlushDelayedBreakpoints()) + LLDB_LOG_ERROR( + GetLog(LLDBLog::Process), std::move(error), + "Failed to update some delayed breakpoints during destroy: {0}"); } error = DoDestroy(); diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td index 223a12e059258..23a1bb0b3ce4a 100644 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -319,6 +319,11 @@ let Definition = "process", Path = "target.process" in { Desc<"A list of thread names. Threads with any of these names will " "always be resumed when the process resumes, even when other " "threads are suspended during single-stepping operations.">; + def UseDelayedBreakpoints + : Property<"use-delayed-breakpoints", "Boolean">, + DefaultTrue, + Desc<"Specify whether updating breakpoints may be delayed until the " + "process is about to resume.">; } let Definition = "platform", Path = "platform" in { diff --git a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp index 572454f9b897a..878aba2634f49 100644 --- a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp +++ b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp @@ -121,7 +121,8 @@ bool ThreadPlanStepOverBreakpoint::DoWillResume(StateType resume_state, BreakpointSiteSP bp_site_sp( m_process.GetBreakpointSiteList().FindByAddress(m_breakpoint_addr)); if (bp_site_sp && m_process.IsBreakpointSiteEnabled(*bp_site_sp)) { - m_process.DisableBreakpointSite(bp_site_sp.get()); + llvm::consumeError(m_process.ExecuteBreakpointSiteAction( + *bp_site_sp, Process::BreakpointAction::Disable)); m_reenabled_breakpoint_site = false; } } @@ -167,7 +168,8 @@ void ThreadPlanStepOverBreakpoint::ReenableBreakpointSite() { if (BreakpointSiteSP bp_site_sp = m_process.GetBreakpointSiteList().FindByAddress( m_breakpoint_addr)) - m_process.EnableBreakpointSite(bp_site_sp.get()); + llvm::consumeError(m_process.ExecuteBreakpointSiteAction( + *bp_site_sp, Process::BreakpointAction::Enable)); } } } diff --git a/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/Makefile b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/TestDelayedBreakpoint.py b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/TestDelayedBreakpoint.py new file mode 100644 index 0000000000000..70a28bb935027 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/TestDelayedBreakpoint.py @@ -0,0 +1,41 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import os + + +@skipIfWindows +class TestDelayedBreakpoint(TestBase): + def test(self): + self.build() + logfile = os.path.join(self.getBuildDir(), "log.txt") + self.runCmd(f"log enable -f {logfile} gdb-remote packets") + + target, process, _, _ = lldbutil.run_to_source_breakpoint( + self, "main", lldb.SBFileSpec("main.c") + ) + + self.runCmd(f"proc plugin packet send BEFORE_BPS", check=False) + + breakpoint = target.BreakpointCreateByLocation("main.c", 1) + self.assertGreater(breakpoint.GetNumResolvedLocations(), 0) + + self.runCmd(f"proc plugin packet send AFTER_BPS", check=False) + + lldbutil.continue_to_breakpoint(process, breakpoint) + self.runCmd(f"log disable all") + + self.runCmd(f"proc plugin packet send AFTER_CONTINUE", check=False) + + self.assertTrue(os.path.exists(logfile)) + log_text = open(logfile).read() + + log_before_continue = log_text.split("BEFORE_BPS", 1)[-1].split("AFTER_BPS", 1)[ + 0 + ] + self.assertNotIn("send packet: $Z", log_before_continue) + self.assertNotIn("send packet: $z", log_before_continue) + + log_after = log_text.split("AFTER_BPS", 1)[-1].split("AFTER_CONTINUE", 1)[0] + self.assertIn("send packet: $Z", log_after) diff --git a/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/main.c b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/main.c new file mode 100644 index 0000000000000..a9086f89af86a --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/main.c @@ -0,0 +1,7 @@ +int break_on_me() { + int i = 10; // breakhere + i++; + return i; // secondbreak +} + +int main() { return break_on_me(); }