llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: Pavel Labath (labath) <details> <summary>Changes</summary> and the follow-up in [lldb] Disable delayed breakpoints on Windows (#<!-- -->195241). This makes lldb incompatible with stub which do not implement reference-counted breakpoints (see https://github.com/llvm/llvm-project/pull/192971#issuecomment-4371375301 for more information). This reverts commits 776ee6f0dd2f4bc87d565d642d95f399224a565f and f5eb7a9ef4c8832a99f696b22a00a55ba6718153. --- Patch is 25.73 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/195652.diff 14 Files Affected: - (modified) lldb/include/lldb/Target/Process.h (-46) - (modified) lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp (+2-2) - (modified) lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp (+3-5) - (modified) lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp (+1-1) - (modified) lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h (-2) - (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp (+6-6) - (modified) lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp (+1-1) - (modified) lldb/source/Plugins/Process/scripted/ScriptedThread.cpp (+1-1) - (modified) lldb/source/Target/Process.cpp (+5-115) - (modified) lldb/source/Target/TargetProperties.td (-5) - (modified) lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp (+2-4) - (removed) lldb/test/API/functionalities/breakpoint/delayed_breakpoints/Makefile (-3) - (removed) lldb/test/API/functionalities/breakpoint/delayed_breakpoints/TestDelayedBreakpoint.py (-41) - (removed) lldb/test/API/functionalities/breakpoint/delayed_breakpoints/main.c (-7) ``````````diff diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 67a9a883cd36c..fb06850d34a19 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -114,7 +114,6 @@ 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 @@ -2247,9 +2246,6 @@ class Process : public std::enable_shared_from_this<Process>, // 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()); @@ -2260,24 +2256,6 @@ class Process : public std::enable_shared_from_this<Process>, "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<lldb::BreakpointSiteSP, BreakpointAction, SiteIDCmp>; - - 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 @@ -2308,15 +2286,6 @@ class Process : public std::enable_shared_from_this<Process>, bool IsBreakpointSiteEnabled(const BreakpointSite &site); - bool IsBreakpointSitePhysicallyEnabled(const BreakpointSite &site); - - /// Reports whether this process should delay physically enabling/disabling - /// breakpoints until the process is about to resume. The default honors the - /// user-facing `target.process.use-delayed-breakpoints` setting. - virtual bool ShouldUseDelayedBreakpoints() const { - return GetUseDelayedBreakpoints(); - } - // BreakpointLocations use RemoveConstituentFromBreakpointSite to remove // themselves from the constituent's list of this breakpoint sites. void RemoveConstituentFromBreakpointSite(lldb::user_id_t site_id, @@ -3571,21 +3540,6 @@ 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 6166096a4e1d3..b4598a422e579 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 (!IsBreakpointSitePhysicallyEnabled(*bp_site)) { + if (!IsBreakpointSiteEnabled(*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 (IsBreakpointSitePhysicallyEnabled(*bp_site)) { + if (IsBreakpointSiteEnabled(*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 5fbc1f62a065f..deb95d9a03ca3 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->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) + if (bp_site_sp && process_sp->IsBreakpointSiteEnabled(*bp_site_sp)) thread.SetThreadStoppedAtUnexecutedBP(pc); switch (exc_type) { @@ -771,8 +771,7 @@ StopInfoSP StopInfoMachException::CreateStopReasonWithMachException( if (!bp_site_sp && reg_ctx_sp) { bp_site_sp = process_sp->GetBreakpointSiteList().FindByAddress(pc); } - if (bp_site_sp && - process_sp->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) { + if (bp_site_sp && process_sp->IsBreakpointSiteEnabled(*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(); @@ -866,8 +865,7 @@ 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->IsBreakpointSitePhysicallyEnabled(*site)) { + if (site->IsHardware() && process_sp->IsBreakpointSiteEnabled(*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 28b5554069c90..c41547a2c0961 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 && IsBreakpointSitePhysicallyEnabled(*site)) + if (site && IsBreakpointSiteEnabled(*site)) stop_thread->SetThreadStoppedAtUnexecutedBP(pc); switch (active_exception->GetExceptionCode()) { diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h index 228619d0e3d5e..c1478dd30c4d2 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h @@ -44,8 +44,6 @@ class ProcessWindows : public Process, public ProcessDebugger { Status EnableBreakpointSite(BreakpointSite *bp_site) override; Status DisableBreakpointSite(BreakpointSite *bp_site) override; - bool ShouldUseDelayedBreakpoints() const override { return false; } - Status DoDetach(bool keep_stopped) override; Status DoLaunch(Module *exe_module, ProcessLaunchInfo &launch_info) override; Status DoAttachToProcessWithID( diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index adf108919b36e..e8f32c564ee03 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 && IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) + if (bp_site_sp && IsBreakpointSiteEnabled(*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 (IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) + if (IsBreakpointSiteEnabled(*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 (IsBreakpointSitePhysicallyEnabled(*bp_site)) { + if (IsBreakpointSiteEnabled(*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 (!IsBreakpointSitePhysicallyEnabled(*bp_site)) { + if (!IsBreakpointSiteEnabled(*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 (IsBreakpointSitePhysicallyEnabled(*bp_site) && + if (IsBreakpointSiteEnabled(*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 (IsBreakpointSitePhysicallyEnabled(*bp_site) && + if (IsBreakpointSiteEnabled(*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 c254a6841b707..ac0f451a3d5fa 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 (IsBreakpointSitePhysicallyEnabled(*bp_site)) { + if (IsBreakpointSiteEnabled(*bp_site)) { return {}; } diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp index d11d134295579..35774cfa01742 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->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) + if (proc->IsBreakpointSiteEnabled(*bp_site_sp)) SetThreadStoppedAtUnexecutedBP(pc); } diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 114bbd7355f0c..58c41c2584676 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -79,17 +79,6 @@ 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<ProcessOptionValueProperties, OptionValueProperties> { public: @@ -333,12 +322,6 @@ bool ProcessProperties::GetStopOnExec() const { idx, g_process_properties[idx].default_uint_value != 0); } -bool ProcessProperties::GetUseDelayedBreakpoints() const { - const uint32_t idx = ePropertyUseDelayedBreakpoints; - return GetPropertyAtIndexAs<bool>( - idx, g_process_properties[idx].default_uint_value != 0); -} - std::chrono::seconds ProcessProperties::GetUtilityExpressionTimeout() const { const uint32_t idx = ePropertyUtilityExpressionTimeout; uint64_t value = GetPropertyAtIndexAs<uint64_t>( @@ -1563,8 +1546,7 @@ Process::GetBreakpointSiteList() const { void Process::DisableAllBreakpointSites() { m_breakpoint_site_list.ForEach([this](BreakpointSite *bp_site) -> void { - llvm::consumeError( - ExecuteBreakpointSiteAction(*bp_site, BreakpointAction::Disable)); + DisableBreakpointSite(bp_site); }); } @@ -1582,8 +1564,7 @@ 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 = Status::FromError( - ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable)); + error = DisableBreakpointSite(bp_site_sp.get()); } else { error = Status::FromErrorStringWithFormat( "invalid breakpoint site ID: %" PRIu64, break_id); @@ -1592,40 +1573,12 @@ 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<std::recursive_mutex> 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 (ShouldUseDelayedBreakpoints()) { - 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 = Status::FromError( - ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Enable)); + error = EnableBreakpointSite(bp_site_sp.get()); } else { error = Status::FromErrorStringWithFormat( "invalid breakpoint site ID: %" PRIu64, break_id); @@ -1634,20 +1587,6 @@ Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) { } bool Process::IsBreakpointSiteEnabled(const BreakpointSite &site) { - std::lock_guard<std::recursive_mutex> 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<BreakpointSite &>(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; } @@ -1709,35 +1648,6 @@ static addr_t ComputeConstituentLoadAddress(BreakpointLocation &constituent, return resolved_address.GetOpcodeLoadAddress(&target); } -llvm::Error Process::FlushDelayedBreakpoints() { - std::unique_lock<std::recursive_mutex> 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) { @@ -1757,15 +1667,7 @@ Process::CreateBreakpointSite(const BreakpointLocationSP &constituent, BreakpointSiteSP bp_site_sp( new BreakpointSite(constituent, load_addr, use_hardware)); - - 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)); + Status error = EnableBreakpointSite(bp_site_sp.get()); if (error.Success()) { constituent->SetBreakpointSite(bp_site_sp); return m_breakpoint_site_list.Add(bp_site_sp); @@ -1790,8 +1692,7 @@ 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()) - llvm::consumeError( - ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable)); + DisableBreakpointSite(bp_site_sp.get()); m_breakpoint_site_list.RemoveByAddress(bp_site_sp->GetLoadAddress()); } } @@ -3534,9 +3435,6 @@ Status Process::PrivateResume() { "Process::PrivateResume PreResumeActions fail... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/195652 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
