[Lldb-commits] [lldb] [lldb] Fix scripted frame provider cross-thread re-entrant deadlock (PR #208242)

Med Ismail Bennani via lldb-commits lldb-commits at lists.llvm.org
Mon Aug 3 03:14:46 PDT 2026


https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/208242

>From ebc3f34576ca8501d4e6a3890145f5f1bfeb8e07 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Fri, 31 Jul 2026 13:04:00 -0700
Subject: [PATCH 1/2] [lldb] Wrap Target::GetAPIMutex() into a Lockable handle
 (NFC)

GetAPIMutex() returning a live reference makes it impossible to represent
"no synchronization at all" (a genuine no-op), which a later change needs
for a policy-driven bypass case. Introduce TargetAPILock, a small
Lockable type: its constructor only resolves which std::recursive_mutex
to use (or that there is none, for a genuine no-op) without locking
anything, and lock()/try_lock()/unlock() drive the actual
synchronization, exactly like std::recursive_mutex. It's move-only, and
its destructor releases the lock if one is currently held.

Target::GetAPIMutex() now returns TargetAPILock by value instead of
TargetAPILock&, so it no longer needs persistent backing members on
Target -- each call constructs a fresh, unlocked handle over m_mutex or
m_private_mutex directly.

At the ~150 internal call sites this is a mechanical template-argument
substitution: `std::lock_guard<std::recursive_mutex> guard(target->
GetAPIMutex());` becomes `std::lock_guard<TargetAPILock>
guard(target->GetAPIMutex());`, plus a matching `guard.lock();` /
`lock.lock();` right after, since the handle no longer locks itself the
way a real std::recursive_mutex reference implicitly did through
lock_guard's constructor. This is mechanical everywhere except three
places where the lock outlives its constructing statement:
CommandObject's m_api_locker member (held for a whole command's
execution), ValueImpl::GetSP's out-parameter and the ValueLocker that
stores it, and StoppedExecutionContext's m_api_lock/AllowResume()
(transferred to the resuming caller) -- all three keep their existing
std::unique_lock<TargetAPILock> shape, just resolving via GetAPIMutex()
and then locking explicitly.

SBMutex (the public, deferred-lock API backing lldb::SBMutex) resolves
GetAPIMutex() once, at construction, and stores the result in a
std::shared_ptr<TargetAPILock>; lock()/unlock()/try_lock() forward
directly to that handle's own methods. This matches SBMutex's existing
contract (resolve once, lock/unlock arbitrarily later, possibly from a
different thread) without needing any bypass-specific logic, since this
commit doesn't introduce a bypass -- that's a follow-up change.

No behavioral change: every caller still ends up serialized on the same
real mutex it always was.

Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
 lldb/include/lldb/API/SBMutex.h               |   5 +-
 lldb/include/lldb/Interpreter/CommandObject.h |   6 +-
 lldb/include/lldb/Target/ExecutionContext.h   |   7 +-
 lldb/include/lldb/Target/Target.h             |   8 +-
 lldb/include/lldb/Target/TargetAPILock.h      |  80 ++++++++
 lldb/include/lldb/ValueObject/ValueObject.h   |   5 +-
 lldb/source/API/SBAddress.cpp                 |   3 +-
 lldb/source/API/SBBreakpoint.cpp              | 148 +++++++--------
 lldb/source/API/SBBreakpointLocation.cpp      | 104 +++++------
 lldb/source/API/SBBreakpointName.cpp          |  98 +++++-----
 lldb/source/API/SBCommandInterpreter.cpp      |  27 ++-
 lldb/source/API/SBDebugger.cpp                |  11 +-
 lldb/source/API/SBFunction.cpp                |   5 +-
 lldb/source/API/SBInstruction.cpp             |  20 +-
 lldb/source/API/SBMutex.cpp                   |  19 +-
 lldb/source/API/SBProcess.cpp                 | 173 +++++++++---------
 lldb/source/API/SBSymbol.cpp                  |   5 +-
 lldb/source/API/SBTarget.cpp                  | 111 +++++++----
 lldb/source/API/SBThread.cpp                  |   2 +-
 lldb/source/API/SBValue.cpp                   |  10 +-
 lldb/source/API/SBWatchpoint.cpp              |  59 +++---
 lldb/source/Interpreter/CommandObject.cpp     |   7 +-
 lldb/source/Target/ExecutionContext.cpp       |   6 +-
 lldb/source/Target/Target.cpp                 |   6 +-
 lldb/source/ValueObject/ValueObject.cpp       |   8 +-
 lldb/unittests/Target/CMakeLists.txt          |   1 +
 lldb/unittests/Target/TargetAPILockTest.cpp   | 125 +++++++++++++
 27 files changed, 669 insertions(+), 390 deletions(-)
 create mode 100644 lldb/include/lldb/Target/TargetAPILock.h
 create mode 100644 lldb/unittests/Target/TargetAPILockTest.cpp

diff --git a/lldb/include/lldb/API/SBMutex.h b/lldb/include/lldb/API/SBMutex.h
index 826ad077f159f..74ba36bbe0c21 100644
--- a/lldb/include/lldb/API/SBMutex.h
+++ b/lldb/include/lldb/API/SBMutex.h
@@ -10,8 +10,9 @@
 #define LLDB_API_SBMUTEX_H
 
 #include "lldb/API/SBDefines.h"
+#include "lldb/Target/TargetAPILock.h"
 #include "lldb/lldb-forward.h"
-#include <mutex>
+#include <memory>
 
 namespace lldb {
 
@@ -41,7 +42,7 @@ class LLDB_API SBMutex {
   SBMutex(lldb::TargetSP target_sp);
   friend class SBTarget;
 
-  std::shared_ptr<std::recursive_mutex> m_opaque_sp;
+  std::shared_ptr<lldb_private::TargetAPILock> m_opaque_sp;
 };
 
 } // namespace lldb
diff --git a/lldb/include/lldb/Interpreter/CommandObject.h b/lldb/include/lldb/Interpreter/CommandObject.h
index 925377159d749..8a6d86d0f9ed4 100644
--- a/lldb/include/lldb/Interpreter/CommandObject.h
+++ b/lldb/include/lldb/Interpreter/CommandObject.h
@@ -241,8 +241,8 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
 
   static bool IsPairType(ArgumentRepetitionType arg_repeat_type);
 
-  static std::optional<ArgumentRepetitionType> 
-    ArgRepetitionFromString(llvm::StringRef string);
+  static std::optional<ArgumentRepetitionType>
+  ArgRepetitionFromString(llvm::StringRef string);
 
   bool ParseOptions(Args &args, CommandReturnObject &result);
 
@@ -410,7 +410,7 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
 
   CommandInterpreter &m_interpreter;
   ExecutionContext m_exe_ctx;
-  std::unique_lock<std::recursive_mutex> m_api_locker;
+  TargetAPILock m_api_locker;
   std::string m_cmd_name;
   std::string m_cmd_help_short;
   std::string m_cmd_help_long;
diff --git a/lldb/include/lldb/Target/ExecutionContext.h b/lldb/include/lldb/Target/ExecutionContext.h
index bf976f4db8c87..e63b5853741cb 100644
--- a/lldb/include/lldb/Target/ExecutionContext.h
+++ b/lldb/include/lldb/Target/ExecutionContext.h
@@ -14,6 +14,7 @@
 #include "lldb/Host/ProcessRunLock.h"
 #include "lldb/Target/StackID.h"
 #include "lldb/Target/SyntheticFrameProvider.h"
+#include "lldb/Target/TargetAPILock.h"
 #include "lldb/lldb-private.h"
 
 namespace lldb_private {
@@ -569,7 +570,7 @@ struct StoppedExecutionContext : ExecutionContext {
                           lldb::ProcessSP &process_sp,
                           lldb::ThreadSP &thread_sp,
                           lldb::StackFrameSP &frame_sp,
-                          std::unique_lock<std::recursive_mutex> api_lock,
+                          TargetAPILock api_lock,
                           ProcessRunLock::ProcessRunLocker stop_locker)
       : m_api_lock(std::move(api_lock)), m_stop_locker(std::move(stop_locker)) {
     assert(target_sp);
@@ -595,10 +596,10 @@ struct StoppedExecutionContext : ExecutionContext {
   /// Clears this context, unlocking the ProcessRunLock and returning the
   /// locked API lock, allowing callers to resume the process. Similar to
   /// a move operation, this object is no longer usable.
-  [[nodiscard]] std::unique_lock<std::recursive_mutex> AllowResume();
+  [[nodiscard]] TargetAPILock AllowResume();
 
 private:
-  std::unique_lock<std::recursive_mutex> m_api_lock;
+  TargetAPILock m_api_lock;
   ProcessRunLock::ProcessRunLocker m_stop_locker;
 };
 
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index fb43f432a08da..edb17d9a289ee 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -12,6 +12,7 @@
 #include <list>
 #include <map>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -33,6 +34,7 @@
 #include "lldb/Target/SectionLoadHistory.h"
 #include "lldb/Target/Statistics.h"
 #include "lldb/Target/SyntheticFrameProvider.h"
+#include "lldb/Target/TargetAPILock.h"
 #include "lldb/Target/ThreadSpec.h"
 #include "lldb/Utility/ArchSpec.h"
 #include "lldb/Utility/Broadcaster.h"
@@ -761,7 +763,11 @@ class Target : public std::enable_shared_from_this<Target>,
 
   static TargetProperties &GetGlobalProperties();
 
-  std::recursive_mutex &GetAPIMutex();
+  /// Returns a handle resolved to the mutex to serialize on before
+  /// touching the target through the SB API. The handle isn't locked yet;
+  /// lock()/try_lock() it (typically via std::lock_guard<TargetAPILock>/
+  /// std::unique_lock<TargetAPILock>) to actually acquire it.
+  TargetAPILock GetAPIMutex();
 
   void DeleteCurrentProcess();
 
diff --git a/lldb/include/lldb/Target/TargetAPILock.h b/lldb/include/lldb/Target/TargetAPILock.h
new file mode 100644
index 0000000000000..3b8ac4de15626
--- /dev/null
+++ b/lldb/include/lldb/Target/TargetAPILock.h
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TARGET_TARGETAPILOCK_H
+#define LLDB_TARGET_TARGETAPILOCK_H
+
+#include <mutex>
+
+namespace lldb_private {
+
+/// A Lockable handle over a Target's API mutex, returned by
+/// Target::GetAPIMutex(). Resolves which real mutex to use (or that there
+/// is none, for a genuine no-op) without locking anything -- lock()/
+/// try_lock()/unlock() drive the actual synchronization, exactly like
+/// std::recursive_mutex, so this is usable both as the template argument
+/// to std::lock_guard<T>/std::unique_lock<T> for one-shot use, and as a
+/// handle that's locked/unlocked repeatedly over its lifetime (see
+/// lldb::SBMutex).
+///
+/// Default-constructed handles are a genuine no-op: no synchronization
+/// primitive is touched at all. Move-only; the destructor releases the
+/// lock if one is currently held.
+class TargetAPILock {
+public:
+  TargetAPILock() = default;
+  explicit TargetAPILock(std::recursive_mutex &mutex) : m_mutex(&mutex) {}
+
+  TargetAPILock(TargetAPILock &&other) noexcept { *this = std::move(other); }
+  TargetAPILock &operator=(TargetAPILock &&other) noexcept {
+    if (this != &other) {
+      unlock();
+      m_mutex = other.m_mutex;
+      m_locked = other.m_locked;
+      other.m_mutex = nullptr;
+      other.m_locked = false;
+    }
+    return *this;
+  }
+
+  TargetAPILock(const TargetAPILock &) = delete;
+  TargetAPILock &operator=(const TargetAPILock &) = delete;
+
+  ~TargetAPILock() { unlock(); }
+
+  void lock() {
+    if (m_mutex)
+      m_mutex->lock();
+    m_locked = true;
+  }
+
+  void unlock() {
+    if (m_locked) {
+      if (m_mutex)
+        m_mutex->unlock();
+      m_locked = false;
+    }
+  }
+
+  bool try_lock() {
+    bool acquired = m_mutex ? m_mutex->try_lock() : true;
+    if (acquired)
+      m_locked = true;
+    return acquired;
+  }
+
+  bool owns_lock() const { return m_locked; }
+
+private:
+  std::recursive_mutex *m_mutex = nullptr;
+  bool m_locked = false;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_TARGETAPILOCK_H
diff --git a/lldb/include/lldb/ValueObject/ValueObject.h b/lldb/include/lldb/ValueObject/ValueObject.h
index 6032e56ee5c6a..f5fc057f8c1a8 100644
--- a/lldb/include/lldb/ValueObject/ValueObject.h
+++ b/lldb/include/lldb/ValueObject/ValueObject.h
@@ -1290,8 +1290,7 @@ class ValueImpl {
   lldb::ValueObjectSP GetRootSP() { return m_valobj_sp; }
 
   lldb::ValueObjectSP GetSP(Process::StopLocker &stop_locker,
-                            std::unique_lock<std::recursive_mutex> &lock,
-                            Status &error);
+                            TargetAPILock &lock, Status &error);
 
   void SetUseDynamic(lldb::DynamicValueType use_dynamic) {
     m_use_dynamic = use_dynamic;
@@ -1342,7 +1341,7 @@ class ValueLocker {
 
 private:
   Process::StopLocker m_stop_locker;
-  std::unique_lock<std::recursive_mutex> m_lock;
+  TargetAPILock m_lock;
   Status m_lock_error;
 };
 
diff --git a/lldb/source/API/SBAddress.cpp b/lldb/source/API/SBAddress.cpp
index 78acc2e34564d..96581107c9ace 100644
--- a/lldb/source/API/SBAddress.cpp
+++ b/lldb/source/API/SBAddress.cpp
@@ -110,7 +110,8 @@ lldb::addr_t SBAddress::GetLoadAddress(const SBTarget &target) const {
   TargetSP target_sp(target.GetSP());
   if (target_sp) {
     if (m_opaque_up->IsValid()) {
-      std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+      TargetAPILock guard(target_sp->GetAPIMutex());
+      guard.lock();
       addr = m_opaque_up->GetLoadAddress(target_sp.get());
     }
   }
diff --git a/lldb/source/API/SBBreakpoint.cpp b/lldb/source/API/SBBreakpoint.cpp
index bcef2bd366f73..50059498a9ebd 100644
--- a/lldb/source/API/SBBreakpoint.cpp
+++ b/lldb/source/API/SBBreakpoint.cpp
@@ -119,8 +119,8 @@ void SBBreakpoint::ClearAllBreakpointSites() {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->ClearAllBreakpointSites();
   }
 }
@@ -133,8 +133,8 @@ SBBreakpointLocation SBBreakpoint::FindLocationByAddress(addr_t vm_addr) {
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
     if (vm_addr != LLDB_INVALID_ADDRESS) {
-      std::lock_guard<std::recursive_mutex> guard(
-          bkpt_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       Address address;
       Target &target = bkpt_sp->GetTarget();
       if (!target.ResolveLoadAddress(vm_addr, address)) {
@@ -153,8 +153,8 @@ break_id_t SBBreakpoint::FindLocationIDByAddress(addr_t vm_addr) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp && vm_addr != LLDB_INVALID_ADDRESS) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     Address address;
     Target &target = bkpt_sp->GetTarget();
     if (!target.ResolveLoadAddress(vm_addr, address)) {
@@ -173,8 +173,8 @@ SBBreakpointLocation SBBreakpoint::FindLocationByID(break_id_t bp_loc_id) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     sb_bp_location.SetLocation(bkpt_sp->FindLocationByID(bp_loc_id));
   }
 
@@ -188,8 +188,8 @@ SBBreakpointLocation SBBreakpoint::GetLocationAtIndex(uint32_t index) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     sb_bp_location.SetLocation(bkpt_sp->GetLocationAtIndex(index));
   }
 
@@ -202,8 +202,8 @@ void SBBreakpoint::SetEnabled(bool enable) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->SetEnabled(enable);
   }
 }
@@ -213,8 +213,8 @@ bool SBBreakpoint::IsEnabled() {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return bkpt_sp->IsEnabled();
   } else
     return false;
@@ -226,8 +226,8 @@ void SBBreakpoint::SetOneShot(bool one_shot) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->SetOneShot(one_shot);
   }
 }
@@ -237,8 +237,8 @@ bool SBBreakpoint::IsOneShot() const {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return bkpt_sp->IsOneShot();
   } else
     return false;
@@ -249,8 +249,8 @@ bool SBBreakpoint::IsInternal() {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return bkpt_sp->IsInternal();
   } else
     return false;
@@ -262,8 +262,8 @@ void SBBreakpoint::SetIgnoreCount(uint32_t count) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->SetIgnoreCount(count);
   }
 }
@@ -273,8 +273,8 @@ void SBBreakpoint::SetCondition(const char *condition) {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     // Treat a null pointer as resetting the condition.
     if (!condition)
       bkpt_sp->SetCondition(StopCondition());
@@ -290,8 +290,8 @@ const char *SBBreakpoint::GetCondition() {
   if (!bkpt_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      bkpt_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   StopCondition cond = bkpt_sp->GetCondition();
   if (!cond)
     return nullptr;
@@ -303,8 +303,8 @@ void SBBreakpoint::SetAutoContinue(bool auto_continue) {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->SetAutoContinue(auto_continue);
   }
 }
@@ -314,8 +314,8 @@ bool SBBreakpoint::GetAutoContinue() {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return bkpt_sp->IsAutoContinue();
   }
   return false;
@@ -327,8 +327,8 @@ uint32_t SBBreakpoint::GetHitCount() const {
   uint32_t count = 0;
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     count = bkpt_sp->GetHitCount();
   }
 
@@ -341,8 +341,8 @@ uint32_t SBBreakpoint::GetIgnoreCount() const {
   uint32_t count = 0;
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     count = bkpt_sp->GetIgnoreCount();
   }
 
@@ -354,8 +354,8 @@ void SBBreakpoint::SetThreadID(lldb::tid_t tid) {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->SetThreadID(tid);
   }
 }
@@ -366,8 +366,8 @@ lldb::tid_t SBBreakpoint::GetThreadID() {
   lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     tid = bkpt_sp->GetThreadID();
   }
 
@@ -379,8 +379,8 @@ void SBBreakpoint::SetThreadIndex(uint32_t index) {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->GetOptions().GetThreadSpec()->SetIndex(index);
   }
 }
@@ -391,8 +391,8 @@ uint32_t SBBreakpoint::GetThreadIndex() const {
   uint32_t thread_idx = UINT32_MAX;
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     const ThreadSpec *thread_spec =
         bkpt_sp->GetOptions().GetThreadSpecNoCreate();
     if (thread_spec != nullptr)
@@ -408,8 +408,8 @@ void SBBreakpoint::SetThreadName(const char *thread_name) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->GetOptions().GetThreadSpec()->SetName(thread_name);
   }
 }
@@ -421,8 +421,8 @@ const char *SBBreakpoint::GetThreadName() const {
   if (!bkpt_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      bkpt_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   if (const ThreadSpec *thread_spec =
           bkpt_sp->GetOptions().GetThreadSpecNoCreate())
     return ConstString(thread_spec->GetName()).GetCString();
@@ -435,8 +435,8 @@ void SBBreakpoint::SetQueueName(const char *queue_name) {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->GetOptions().GetThreadSpec()->SetQueueName(queue_name);
   }
 }
@@ -448,8 +448,8 @@ const char *SBBreakpoint::GetQueueName() const {
   if (!bkpt_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      bkpt_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   if (const ThreadSpec *thread_spec =
           bkpt_sp->GetOptions().GetThreadSpecNoCreate())
     return ConstString(thread_spec->GetQueueName()).GetCString();
@@ -463,8 +463,8 @@ size_t SBBreakpoint::GetNumResolvedLocations() const {
   size_t num_resolved = 0;
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     num_resolved = bkpt_sp->GetNumResolvedLocations();
   }
   return num_resolved;
@@ -476,8 +476,8 @@ size_t SBBreakpoint::GetNumLocations() const {
   BreakpointSP bkpt_sp = GetSP();
   size_t num_locs = 0;
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     num_locs = bkpt_sp->GetNumLocations();
   }
   return num_locs;
@@ -492,8 +492,8 @@ void SBBreakpoint::SetCommandLineCommands(SBStringList &commands) {
   if (commands.GetSize() == 0)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      bkpt_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   std::unique_ptr<BreakpointOptions::CommandData> cmd_data_up(
       new BreakpointOptions::CommandData(*commands, eScriptLanguageNone));
 
@@ -525,8 +525,8 @@ bool SBBreakpoint::GetDescription(SBStream &s, bool include_locations) {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     s.Printf("SBBreakpoint: id = %i, ", bkpt_sp->GetID());
     bkpt_sp->GetResolverDescription(s.get());
     bkpt_sp->GetFilterDescription(s.get());
@@ -603,8 +603,8 @@ void SBBreakpoint::SetCallback(SBBreakpointHitCallback callback, void *baton) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     BatonSP baton_sp(new SBBreakpointCallbackBaton(callback, baton));
     bkpt_sp->SetCallback(SBBreakpointCallbackBaton
       ::PrivateBreakpointHitCallback, baton_sp,
@@ -628,8 +628,8 @@ SBError SBBreakpoint::SetScriptCallbackFunction(
 
   if (bkpt_sp) {
     Status error;
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     BreakpointOptions &bp_options = bkpt_sp->GetOptions();
     error = bkpt_sp->GetTarget()
         .GetDebugger()
@@ -652,8 +652,8 @@ SBError SBBreakpoint::SetScriptCallbackBody(const char *callback_body_text) {
 
   SBError sb_error;
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     BreakpointOptions &bp_options = bkpt_sp->GetOptions();
     Status error =
         bkpt_sp->GetTarget()
@@ -682,8 +682,8 @@ SBError SBBreakpoint::AddNameWithErrorHandling(const char *new_name) {
 
   SBError status;
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     Status error;
     bkpt_sp->GetTarget().AddNameToBreakpoint(bkpt_sp, new_name, error);
     status.SetError(std::move(error));
@@ -700,8 +700,8 @@ void SBBreakpoint::RemoveName(const char *name_to_remove) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     bkpt_sp->GetTarget().RemoveNameFromBreakpoint(
         bkpt_sp, llvm::StringRef(name_to_remove));
   }
@@ -713,8 +713,8 @@ bool SBBreakpoint::MatchesName(const char *name) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return bkpt_sp->MatchesName(name);
   }
 
@@ -727,8 +727,8 @@ void SBBreakpoint::GetNames(SBStringList &names) {
   BreakpointSP bkpt_sp = GetSP();
 
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     std::vector<std::string> names_vec;
     bkpt_sp->GetNames(names_vec);
     for (const std::string &name : names_vec) {
@@ -802,8 +802,8 @@ lldb::SBError SBBreakpoint::SetIsHardware(bool is_hardware) {
 
   BreakpointSP bkpt_sp = GetSP();
   if (bkpt_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        bkpt_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(bkpt_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return SBError(Status::FromError(bkpt_sp->SetIsHardware(is_hardware)));
   }
   return SBError();
diff --git a/lldb/source/API/SBBreakpointLocation.cpp b/lldb/source/API/SBBreakpointLocation.cpp
index 2feaa5c805a15..8411ed5eddeee 100644
--- a/lldb/source/API/SBBreakpointLocation.cpp
+++ b/lldb/source/API/SBBreakpointLocation.cpp
@@ -87,8 +87,8 @@ addr_t SBBreakpointLocation::GetLoadAddress() {
   BreakpointLocationSP loc_sp = GetSP();
 
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     ret_addr = loc_sp->GetLoadAddress();
   }
 
@@ -100,8 +100,8 @@ void SBBreakpointLocation::SetEnabled(bool enabled) {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     llvm::consumeError(loc_sp->SetEnabled(enabled));
   }
 }
@@ -111,8 +111,8 @@ bool SBBreakpointLocation::IsEnabled() {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return loc_sp->IsEnabled();
   } else
     return false;
@@ -123,8 +123,8 @@ uint32_t SBBreakpointLocation::GetHitCount() {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return loc_sp->GetHitCount();
   } else
     return 0;
@@ -135,8 +135,8 @@ uint32_t SBBreakpointLocation::GetIgnoreCount() {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return loc_sp->GetIgnoreCount();
   } else
     return 0;
@@ -147,8 +147,8 @@ void SBBreakpointLocation::SetIgnoreCount(uint32_t n) {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     loc_sp->SetIgnoreCount(n);
   }
 }
@@ -158,8 +158,8 @@ void SBBreakpointLocation::SetCondition(const char *condition) {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     // Treat a nullptr as clearing the condition
     if (!condition)
       loc_sp->SetCondition(StopCondition());
@@ -175,8 +175,8 @@ const char *SBBreakpointLocation::GetCondition() {
   if (!loc_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      loc_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   StopCondition cond = loc_sp->GetCondition();
   if (!cond)
     return nullptr;
@@ -188,8 +188,8 @@ void SBBreakpointLocation::SetAutoContinue(bool auto_continue) {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     loc_sp->SetAutoContinue(auto_continue);
   }
 }
@@ -199,8 +199,8 @@ bool SBBreakpointLocation::GetAutoContinue() {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return loc_sp->IsAutoContinue();
   }
   return false;
@@ -213,8 +213,8 @@ void SBBreakpointLocation::SetCallback(SBBreakpointHitCallback callback,
   BreakpointLocationSP loc_sp = GetSP();
 
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     BatonSP baton_sp(new SBBreakpointCallbackBaton(callback, baton));
     loc_sp->SetCallback(SBBreakpointCallbackBaton::PrivateBreakpointHitCallback,
                         baton_sp, false);
@@ -235,8 +235,8 @@ SBError SBBreakpointLocation::SetScriptCallbackFunction(
 
   if (loc_sp) {
     Status error;
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     BreakpointOptions &bp_options = loc_sp->GetLocationOptions();
     error = loc_sp->GetBreakpoint()
         .GetTarget()
@@ -261,8 +261,8 @@ SBBreakpointLocation::SetScriptCallbackBody(const char *callback_body_text) {
 
   SBError sb_error;
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     BreakpointOptions &bp_options = loc_sp->GetLocationOptions();
     Status error =
         loc_sp->GetBreakpoint()
@@ -287,8 +287,8 @@ void SBBreakpointLocation::SetCommandLineCommands(SBStringList &commands) {
   if (commands.GetSize() == 0)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      loc_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   std::unique_ptr<BreakpointOptions::CommandData> cmd_data_up(
       new BreakpointOptions::CommandData(*commands, eScriptLanguageNone));
 
@@ -314,8 +314,8 @@ void SBBreakpointLocation::SetThreadID(lldb::tid_t thread_id) {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     loc_sp->SetThreadID(thread_id);
   }
 }
@@ -326,8 +326,8 @@ lldb::tid_t SBBreakpointLocation::GetThreadID() {
   lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return loc_sp->GetThreadID();
   }
   return tid;
@@ -338,8 +338,8 @@ void SBBreakpointLocation::SetThreadIndex(uint32_t index) {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     loc_sp->SetThreadIndex(index);
   }
 }
@@ -350,8 +350,8 @@ uint32_t SBBreakpointLocation::GetThreadIndex() const {
   uint32_t thread_idx = UINT32_MAX;
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return loc_sp->GetThreadIndex();
   }
   return thread_idx;
@@ -362,8 +362,8 @@ void SBBreakpointLocation::SetThreadName(const char *thread_name) {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     loc_sp->SetThreadName(thread_name);
   }
 }
@@ -375,8 +375,8 @@ const char *SBBreakpointLocation::GetThreadName() const {
   if (!loc_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      loc_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   return ConstString(loc_sp->GetThreadName()).GetCString();
 }
 
@@ -385,8 +385,8 @@ void SBBreakpointLocation::SetQueueName(const char *queue_name) {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     loc_sp->SetQueueName(queue_name);
   }
 }
@@ -398,8 +398,8 @@ const char *SBBreakpointLocation::GetQueueName() const {
   if (!loc_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      loc_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   return ConstString(loc_sp->GetQueueName()).GetCString();
 }
 
@@ -408,8 +408,8 @@ bool SBBreakpointLocation::IsResolved() {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return loc_sp->IsResolved();
   }
   return false;
@@ -429,8 +429,8 @@ bool SBBreakpointLocation::GetDescription(SBStream &description,
   BreakpointLocationSP loc_sp = GetSP();
 
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     loc_sp->GetDescription(&strm, level);
     strm.EOL();
   } else
@@ -444,8 +444,8 @@ break_id_t SBBreakpointLocation::GetID() {
 
   BreakpointLocationSP loc_sp = GetSP();
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return loc_sp->GetID();
   } else
     return LLDB_INVALID_BREAK_ID;
@@ -458,8 +458,8 @@ SBBreakpoint SBBreakpointLocation::GetBreakpoint() {
 
   SBBreakpoint sb_bp;
   if (loc_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        loc_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(loc_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     sb_bp = loc_sp->GetBreakpoint().shared_from_this();
   }
 
diff --git a/lldb/source/API/SBBreakpointName.cpp b/lldb/source/API/SBBreakpointName.cpp
index 1dcbecaf6da76..407f5e8291ddc 100644
--- a/lldb/source/API/SBBreakpointName.cpp
+++ b/lldb/source/API/SBBreakpointName.cpp
@@ -209,8 +209,8 @@ void SBBreakpointName::SetEnabled(bool enable) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().SetEnabled(enable);
   UpdateName(*bp_name);
@@ -234,8 +234,8 @@ bool SBBreakpointName::IsEnabled() {
   if (!bp_name)
     return false;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return bp_name->GetOptions().IsEnabled();
 }
@@ -247,8 +247,8 @@ void SBBreakpointName::SetOneShot(bool one_shot) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().SetOneShot(one_shot);
   UpdateName(*bp_name);
@@ -261,8 +261,8 @@ bool SBBreakpointName::IsOneShot() const {
   if (!bp_name)
     return false;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return bp_name->GetOptions().IsOneShot();
 }
@@ -274,8 +274,8 @@ void SBBreakpointName::SetIgnoreCount(uint32_t count) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().SetIgnoreCount(count);
   UpdateName(*bp_name);
@@ -288,8 +288,8 @@ uint32_t SBBreakpointName::GetIgnoreCount() const {
   if (!bp_name)
     return false;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return bp_name->GetOptions().GetIgnoreCount();
 }
@@ -301,8 +301,8 @@ void SBBreakpointName::SetCondition(const char *condition) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().SetCondition(StopCondition(condition));
   UpdateName(*bp_name);
@@ -315,8 +315,8 @@ const char *SBBreakpointName::GetCondition() {
   if (!bp_name)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return ConstString(bp_name->GetOptions().GetCondition().GetText())
       .GetCString();
@@ -329,8 +329,8 @@ void SBBreakpointName::SetAutoContinue(bool auto_continue) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().SetAutoContinue(auto_continue);
   UpdateName(*bp_name);
@@ -343,8 +343,8 @@ bool SBBreakpointName::GetAutoContinue() {
   if (!bp_name)
     return false;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return bp_name->GetOptions().IsAutoContinue();
 }
@@ -356,8 +356,8 @@ void SBBreakpointName::SetThreadID(lldb::tid_t tid) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().SetThreadID(tid);
   UpdateName(*bp_name);
@@ -370,8 +370,8 @@ lldb::tid_t SBBreakpointName::GetThreadID() {
   if (!bp_name)
     return LLDB_INVALID_THREAD_ID;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return bp_name->GetOptions().GetThreadSpec()->GetTID();
 }
@@ -383,8 +383,8 @@ void SBBreakpointName::SetThreadIndex(uint32_t index) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().GetThreadSpec()->SetIndex(index);
   UpdateName(*bp_name);
@@ -397,8 +397,8 @@ uint32_t SBBreakpointName::GetThreadIndex() const {
   if (!bp_name)
     return LLDB_INVALID_THREAD_ID;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return bp_name->GetOptions().GetThreadSpec()->GetIndex();
 }
@@ -410,8 +410,8 @@ void SBBreakpointName::SetThreadName(const char *thread_name) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().GetThreadSpec()->SetName(thread_name);
   UpdateName(*bp_name);
@@ -424,8 +424,8 @@ const char *SBBreakpointName::GetThreadName() const {
   if (!bp_name)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return ConstString(bp_name->GetOptions().GetThreadSpec()->GetName())
       .GetCString();
@@ -438,8 +438,8 @@ void SBBreakpointName::SetQueueName(const char *queue_name) {
   if (!bp_name)
     return;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   bp_name->GetOptions().GetThreadSpec()->SetQueueName(queue_name);
   UpdateName(*bp_name);
@@ -452,8 +452,8 @@ const char *SBBreakpointName::GetQueueName() const {
   if (!bp_name)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   return ConstString(bp_name->GetOptions().GetThreadSpec()->GetQueueName())
       .GetCString();
@@ -468,9 +468,8 @@ void SBBreakpointName::SetCommandLineCommands(SBStringList &commands) {
   if (commands.GetSize() == 0)
     return;
 
-
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
   std::unique_ptr<BreakpointOptions::CommandData> cmd_data_up(
       new BreakpointOptions::CommandData(*commands, eScriptLanguageNone));
 
@@ -510,9 +509,8 @@ void SBBreakpointName::SetHelpString(const char *help_string) {
   if (!bp_name)
     return;
 
-
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
   bp_name->SetHelp(help_string);
 }
 
@@ -526,8 +524,8 @@ bool SBBreakpointName::GetDescription(SBStream &s) {
     return false;
   }
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
   bp_name->GetDescription(s.get(), eDescriptionLevelFull);
   return true;
 }
@@ -539,8 +537,8 @@ void SBBreakpointName::SetCallback(SBBreakpointHitCallback callback,
   BreakpointName *bp_name = GetBreakpointName();
   if (!bp_name)
     return;
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   BatonSP baton_sp(new SBBreakpointCallbackBaton(callback, baton));
   bp_name->GetOptions().SetCallback(SBBreakpointCallbackBaton
@@ -568,8 +566,8 @@ SBError SBBreakpointName::SetScriptCallbackFunction(
     return sb_error;
   }
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   BreakpointOptions &bp_options = bp_name->GetOptions();
   Status error = m_impl_up->GetTarget()
@@ -592,8 +590,8 @@ SBBreakpointName::SetScriptCallbackBody(const char *callback_body_text) {
   if (!bp_name)
     return sb_error;
 
-  std::lock_guard<std::recursive_mutex> guard(
-        m_impl_up->GetTarget()->GetAPIMutex());
+  TargetAPILock guard(m_impl_up->GetTarget()->GetAPIMutex());
+  guard.lock();
 
   BreakpointOptions &bp_options = bp_name->GetOptions();
   Status error = m_impl_up->GetTarget()
diff --git a/lldb/source/API/SBCommandInterpreter.cpp b/lldb/source/API/SBCommandInterpreter.cpp
index ae6b3d3418655..c2af82b0597bd 100644
--- a/lldb/source/API/SBCommandInterpreter.cpp
+++ b/lldb/source/API/SBCommandInterpreter.cpp
@@ -385,7 +385,8 @@ SBProcess SBCommandInterpreter::GetProcess() {
   if (IsValid()) {
     TargetSP target_sp(m_opaque_ptr->GetSelectedTarget());
     if (target_sp) {
-      std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+      TargetAPILock guard(target_sp->GetAPIMutex());
+      guard.lock();
       process_sp = target_sp->GetProcessSP();
       sb_process.SetSP(process_sp);
     }
@@ -472,9 +473,11 @@ void SBCommandInterpreter::SourceInitFileInGlobalDirectory(
   result.Clear();
   if (IsValid()) {
     TargetSP target_sp(m_opaque_ptr->GetSelectedTarget());
-    std::unique_lock<std::recursive_mutex> lock;
-    if (target_sp)
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    TargetAPILock lock;
+    if (target_sp) {
+      lock = TargetAPILock(target_sp->GetAPIMutex());
+      lock.lock();
+    }
     m_opaque_ptr->SourceInitFileGlobal(result.ref());
   } else {
     result->AppendError("SBCommandInterpreter is not valid");
@@ -495,9 +498,11 @@ void SBCommandInterpreter::SourceInitFileInHomeDirectory(
   result.Clear();
   if (IsValid()) {
     TargetSP target_sp(m_opaque_ptr->GetSelectedTarget());
-    std::unique_lock<std::recursive_mutex> lock;
-    if (target_sp)
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    TargetAPILock lock;
+    if (target_sp) {
+      lock = TargetAPILock(target_sp->GetAPIMutex());
+      lock.lock();
+    }
     m_opaque_ptr->SourceInitFileHome(result.ref(), is_repl);
   } else {
     result->AppendError("SBCommandInterpreter is not valid");
@@ -511,9 +516,11 @@ void SBCommandInterpreter::SourceInitFileInCurrentWorkingDirectory(
   result.Clear();
   if (IsValid()) {
     TargetSP target_sp(m_opaque_ptr->GetSelectedTarget());
-    std::unique_lock<std::recursive_mutex> lock;
-    if (target_sp)
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    TargetAPILock lock;
+    if (target_sp) {
+      lock = TargetAPILock(target_sp->GetAPIMutex());
+      lock.lock();
+    }
     m_opaque_ptr->SourceInitFileCwd(result.ref());
   } else {
     result->AppendError("SBCommandInterpreter is not valid");
diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp
index 95c4f761c9963..b03820f6686a0 100644
--- a/lldb/source/API/SBDebugger.cpp
+++ b/lldb/source/API/SBDebugger.cpp
@@ -531,9 +531,11 @@ void SBDebugger::HandleCommand(const char *command) {
   if (m_opaque_sp) {
     TargetSP target_sp(
         m_opaque_sp->GetCommandInterpreter().GetSelectedTarget());
-    std::unique_lock<std::recursive_mutex> lock;
-    if (target_sp)
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    TargetAPILock lock;
+    if (target_sp) {
+      lock = TargetAPILock(target_sp->GetAPIMutex());
+      lock.lock();
+    }
 
     SBCommandInterpreter sb_interpreter(GetCommandInterpreter());
     SBCommandReturnObject result;
@@ -606,7 +608,8 @@ void SBDebugger::HandleProcessEvent(const SBProcess &process,
   char stdio_buffer[1024];
   size_t len;
 
-  std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+  TargetAPILock guard(target_sp->GetAPIMutex());
+  guard.lock();
 
   if (event_type &
       (Process::eBroadcastBitSTDOUT | Process::eBroadcastBitStateChanged)) {
diff --git a/lldb/source/API/SBFunction.cpp b/lldb/source/API/SBFunction.cpp
index c3cdba64417c8..d27f2ced881fd 100644
--- a/lldb/source/API/SBFunction.cpp
+++ b/lldb/source/API/SBFunction.cpp
@@ -129,10 +129,11 @@ SBInstructionList SBFunction::GetInstructions(SBTarget target,
   SBInstructionList sb_instructions;
   if (m_opaque_ptr) {
     TargetSP target_sp(target.GetSP());
-    std::unique_lock<std::recursive_mutex> lock;
+    TargetAPILock lock;
     ModuleSP module_sp(m_opaque_ptr->GetAddress().GetModule());
     if (target_sp && module_sp) {
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+      lock = TargetAPILock(target_sp->GetAPIMutex());
+      lock.lock();
       const bool force_live_memory = true;
       sb_instructions.SetDisassembler(Disassembler::DisassembleRange(
           module_sp->GetArchitecture(), nullptr, flavor,
diff --git a/lldb/source/API/SBInstruction.cpp b/lldb/source/API/SBInstruction.cpp
index dc4d475f5fb3d..bc82a3731d692 100644
--- a/lldb/source/API/SBInstruction.cpp
+++ b/lldb/source/API/SBInstruction.cpp
@@ -117,9 +117,10 @@ const char *SBInstruction::GetMnemonic(SBTarget target) {
 
   ExecutionContext exe_ctx;
   TargetSP target_sp(target.GetSP());
-  std::unique_lock<std::recursive_mutex> lock;
+  TargetAPILock lock;
   if (target_sp) {
-    lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    lock = TargetAPILock(target_sp->GetAPIMutex());
+    lock.lock();
 
     target_sp->CalculateExecutionContext(exe_ctx);
     exe_ctx.SetProcessSP(target_sp->GetProcessSP());
@@ -136,9 +137,10 @@ const char *SBInstruction::GetOperands(SBTarget target) {
 
   ExecutionContext exe_ctx;
   TargetSP target_sp(target.GetSP());
-  std::unique_lock<std::recursive_mutex> lock;
+  TargetAPILock lock;
   if (target_sp) {
-    lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    lock = TargetAPILock(target_sp->GetAPIMutex());
+    lock.lock();
 
     target_sp->CalculateExecutionContext(exe_ctx);
     exe_ctx.SetProcessSP(target_sp->GetProcessSP());
@@ -155,9 +157,10 @@ const char *SBInstruction::GetComment(SBTarget target) {
 
   ExecutionContext exe_ctx;
   TargetSP target_sp(target.GetSP());
-  std::unique_lock<std::recursive_mutex> lock;
+  TargetAPILock lock;
   if (target_sp) {
-    lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    lock = TargetAPILock(target_sp->GetAPIMutex());
+    lock.lock();
 
     target_sp->CalculateExecutionContext(exe_ctx);
     exe_ctx.SetProcessSP(target_sp->GetProcessSP());
@@ -173,9 +176,10 @@ SBInstruction::GetControlFlowKind(lldb::SBTarget target) {
   if (inst_sp) {
     ExecutionContext exe_ctx;
     TargetSP target_sp(target.GetSP());
-    std::unique_lock<std::recursive_mutex> lock;
+    TargetAPILock lock;
     if (target_sp) {
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+      lock = TargetAPILock(target_sp->GetAPIMutex());
+      lock.lock();
 
       target_sp->CalculateExecutionContext(exe_ctx);
       exe_ctx.SetProcessSP(target_sp->GetProcessSP());
diff --git a/lldb/source/API/SBMutex.cpp b/lldb/source/API/SBMutex.cpp
index c7844dec658cc..244f899722019 100644
--- a/lldb/source/API/SBMutex.cpp
+++ b/lldb/source/API/SBMutex.cpp
@@ -16,8 +16,21 @@
 using namespace lldb;
 using namespace lldb_private;
 
-SBMutex::SBMutex() : m_opaque_sp(std::make_shared<std::recursive_mutex>()) {
+namespace {
+/// Backing storage for a standalone (not Target-derived) SBMutex: owns the
+/// std::recursive_mutex that the TargetAPILock member wraps, so both share
+/// one control block and one lifetime.
+struct StandaloneAPILock {
+  std::recursive_mutex mutex;
+  TargetAPILock lock{mutex};
+};
+} // namespace
+
+SBMutex::SBMutex() {
   LLDB_INSTRUMENT_VA(this);
+
+  auto owner = std::make_shared<StandaloneAPILock>();
+  m_opaque_sp = std::shared_ptr<TargetAPILock>(owner, &owner->lock);
 }
 
 SBMutex::SBMutex(const SBMutex &rhs) : m_opaque_sp(rhs.m_opaque_sp) {
@@ -32,8 +45,8 @@ const SBMutex &SBMutex::operator=(const SBMutex &rhs) {
 }
 
 SBMutex::SBMutex(lldb::TargetSP target_sp)
-    : m_opaque_sp(std::shared_ptr<std::recursive_mutex>(
-          target_sp, &target_sp->GetAPIMutex())) {
+    : m_opaque_sp(
+          std::make_shared<TargetAPILock>(target_sp->GetAPIMutex())) {
   LLDB_INSTRUMENT_VA(this, target_sp);
 }
 
diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp
index 08e39f754cf85..54097b893b82f 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -136,8 +136,8 @@ bool SBProcess::RemoteLaunch(char const **argv, char const **envp,
 
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     if (process_sp->GetState() == eStateConnected) {
       if (stop_at_entry)
         launch_flags |= eLaunchFlagStopAtEntry;
@@ -169,8 +169,8 @@ bool SBProcess::RemoteAttachToProcessWithID(lldb::pid_t pid,
 
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     if (process_sp->GetState() == eStateConnected) {
       ProcessAttachInfo attach_info;
       attach_info.SetProcessID(pid);
@@ -195,8 +195,8 @@ uint32_t SBProcess::GetNumThreads() {
     Process::StopLocker stop_locker;
 
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       num_threads = process_sp->GetThreadList().GetSize();
     }
   }
@@ -211,8 +211,8 @@ SBThread SBProcess::GetSelectedThread() const {
   ThreadSP thread_sp;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     thread_sp = process_sp->GetThreadList().GetSelectedThread();
     sb_thread.SetThread(thread_sp);
   }
@@ -228,8 +228,8 @@ SBThread SBProcess::CreateOSPluginThread(lldb::tid_t tid,
   ThreadSP thread_sp;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     thread_sp = process_sp->CreateOSPluginThread(tid, context);
     sb_thread.SetThread(thread_sp);
   }
@@ -352,8 +352,8 @@ bool SBProcess::SetSelectedThread(const SBThread &thread) {
 
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return process_sp->GetThreadList().SetSelectedThreadByID(
         thread.GetThreadID());
   }
@@ -366,8 +366,8 @@ bool SBProcess::SetSelectedThreadByID(lldb::tid_t tid) {
   bool ret_val = false;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     ret_val = process_sp->GetThreadList().SetSelectedThreadByID(tid);
   }
 
@@ -380,8 +380,8 @@ bool SBProcess::SetSelectedThreadByIndexID(uint32_t index_id) {
   bool ret_val = false;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     ret_val = process_sp->GetThreadList().SetSelectedThreadByIndexID(index_id);
   }
 
@@ -397,8 +397,8 @@ SBThread SBProcess::GetThreadAtIndex(size_t index) {
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       thread_sp = process_sp->GetThreadList().GetThreadAtIndex(index, false);
       sb_thread.SetThread(thread_sp);
     }
@@ -415,8 +415,8 @@ uint32_t SBProcess::GetNumQueues() {
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       num_queues = process_sp->GetQueueList().GetSize();
     }
   }
@@ -433,8 +433,8 @@ SBQueue SBProcess::GetQueueAtIndex(size_t index) {
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       queue_sp = process_sp->GetQueueList().GetQueueAtIndex(index);
       sb_queue.SetQueue(queue_sp);
     }
@@ -448,8 +448,8 @@ uint32_t SBProcess::GetStopID(bool include_expression_stops) {
 
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     if (include_expression_stops)
       return process_sp->GetStopID();
     else
@@ -465,8 +465,8 @@ SBEvent SBProcess::GetStopEventForStopID(uint32_t stop_id) {
   EventSP event_sp;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     event_sp = process_sp->GetStopEventForStopID(stop_id);
     sb_event.reset(event_sp);
   }
@@ -478,8 +478,8 @@ void SBProcess::ForceScriptedState(StateType new_state) {
   LLDB_INSTRUMENT_VA(this, new_state);
 
   if (ProcessSP process_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     process_sp->ForceScriptedState(new_state);
   }
 }
@@ -490,8 +490,8 @@ StateType SBProcess::GetState() {
   StateType ret_val = eStateInvalid;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     ret_val = process_sp->GetState();
   }
 
@@ -504,8 +504,8 @@ int SBProcess::GetExitStatus() {
   int exit_status = 0;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     exit_status = process_sp->GetExitStatus();
   }
 
@@ -519,8 +519,8 @@ const char *SBProcess::GetExitDescription() {
   if (!process_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      process_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   return ConstString(process_sp->GetExitDescription()).GetCString();
 }
 
@@ -574,8 +574,8 @@ SBError SBProcess::Continue() {
   ProcessSP process_sp(GetSP());
 
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
 
     if (process_sp->GetTarget().GetDebugger().GetAsyncExecution())
       sb_error.ref() = process_sp->Resume();
@@ -605,8 +605,8 @@ SBError SBProcess::Destroy() {
   SBError sb_error;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     sb_error.SetError(process_sp->Destroy(false));
   } else
     sb_error = Status::FromErrorString("SBProcess is invalid");
@@ -620,8 +620,8 @@ SBError SBProcess::Stop() {
   SBError sb_error;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     sb_error.SetError(process_sp->Halt());
   } else
     sb_error = Status::FromErrorString("SBProcess is invalid");
@@ -635,8 +635,8 @@ SBError SBProcess::Kill() {
   SBError sb_error;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     sb_error.SetError(process_sp->Destroy(true));
   } else
     sb_error = Status::FromErrorString("SBProcess is invalid");
@@ -658,8 +658,8 @@ SBError SBProcess::Detach(bool keep_stopped) {
   SBError sb_error;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     sb_error.SetError(process_sp->Detach(keep_stopped));
   } else
     sb_error = Status::FromErrorString("SBProcess is invalid");
@@ -673,8 +673,8 @@ SBError SBProcess::Signal(int signo) {
   SBError sb_error;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     sb_error.SetError(process_sp->Signal(signo));
   } else
     sb_error = Status::FromErrorString("SBProcess is invalid");
@@ -709,8 +709,8 @@ SBThread SBProcess::GetThreadByID(tid_t tid) {
   if (process_sp) {
     Process::StopLocker stop_locker;
     const bool can_update = stop_locker.TryLock(&process_sp->GetRunLock());
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     thread_sp = process_sp->GetThreadList().FindThreadByID(tid, can_update);
     sb_thread.SetThread(thread_sp);
   }
@@ -727,8 +727,8 @@ SBThread SBProcess::GetThreadByIndexID(uint32_t index_id) {
   if (process_sp) {
     Process::StopLocker stop_locker;
     const bool can_update = stop_locker.TryLock(&process_sp->GetRunLock());
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     thread_sp =
         process_sp->GetThreadList().FindThreadByIndexID(index_id, can_update);
     sb_thread.SetThread(thread_sp);
@@ -844,8 +844,8 @@ lldb::SBAddressRangeList SBProcess::FindRangesInMemory(
     error = Status::FromErrorString("process is running");
     return matches;
   }
-  std::lock_guard<std::recursive_mutex> guard(
-      process_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   matches.m_opaque_up->ref() = process_sp->FindRangesInMemory(
       reinterpret_cast<const uint8_t *>(buf), size, ranges.ref().ref(),
       alignment, max_matches, error.ref());
@@ -870,8 +870,8 @@ lldb::addr_t SBProcess::FindInMemory(const void *buf, uint64_t size,
     return LLDB_INVALID_ADDRESS;
   }
 
-  std::lock_guard<std::recursive_mutex> guard(
-      process_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   return process_sp->FindInMemory(reinterpret_cast<const uint8_t *>(buf), size,
                                   range.ref(), alignment, error.ref());
 }
@@ -889,12 +889,11 @@ size_t SBProcess::ReadMemory(addr_t addr, void *dst, size_t dst_len,
   size_t bytes_read = 0;
   ProcessSP process_sp(GetSP());
 
-
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       bytes_read = process_sp->ReadMemory(addr, dst, dst_len, sb_error.ref());
     } else {
       sb_error = Status::FromErrorString("process is running");
@@ -915,8 +914,8 @@ size_t SBProcess::ReadCStringFromMemory(addr_t addr, void *buf, size_t size,
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       bytes_read = process_sp->ReadCStringFromMemory(addr, (char *)buf, size,
                                                      sb_error.ref());
     } else {
@@ -937,8 +936,8 @@ uint64_t SBProcess::ReadUnsignedFromMemory(addr_t addr, uint32_t byte_size,
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       value = process_sp->ReadUnsignedIntegerFromMemory(addr, byte_size, 0,
                                                         sb_error.ref());
     } else {
@@ -959,8 +958,8 @@ lldb::addr_t SBProcess::ReadPointerFromMemory(addr_t addr,
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       ptr = process_sp->ReadPointerFromMemory(addr, sb_error.ref());
     } else {
       sb_error = Status::FromErrorString("process is running");
@@ -982,8 +981,8 @@ size_t SBProcess::WriteMemory(addr_t addr, const void *src, size_t src_len,
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       bytes_written =
           process_sp->WriteMemory(addr, src, src_len, sb_error.ref());
     } else {
@@ -1060,8 +1059,8 @@ SBProcess::GetNumSupportedHardwareWatchpoints(lldb::SBError &sb_error) const {
   uint32_t num = 0;
   ProcessSP process_sp(GetSP());
   if (process_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     std::optional<uint32_t> actual_num = process_sp->GetWatchpointSlotCount();
     if (actual_num) {
       num = *actual_num;
@@ -1091,8 +1090,8 @@ uint32_t SBProcess::LoadImage(const lldb::SBFileSpec &sb_local_image_spec,
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       PlatformSP platform_sp = process_sp->GetTarget().GetPlatform();
       return platform_sp->LoadImage(process_sp.get(), *sb_local_image_spec,
                                     *sb_remote_image_spec, sb_error.ref());
@@ -1115,8 +1114,8 @@ uint32_t SBProcess::LoadImageUsingPaths(const lldb::SBFileSpec &image_spec,
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       PlatformSP platform_sp = process_sp->GetTarget().GetPlatform();
       size_t num_paths = paths.GetSize();
       std::vector<std::string> paths_vec;
@@ -1148,8 +1147,8 @@ lldb::SBError SBProcess::UnloadImage(uint32_t image_token) {
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       PlatformSP platform_sp = process_sp->GetTarget().GetPlatform();
       sb_error.SetError(
           platform_sp->UnloadImage(process_sp.get(), image_token));
@@ -1169,8 +1168,8 @@ lldb::SBError SBProcess::SendEventData(const char *event_data) {
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       sb_error.SetError(process_sp->SendEventData(event_data));
     } else {
       sb_error = Status::FromErrorString("process is running");
@@ -1225,8 +1224,8 @@ bool SBProcess::IsInstrumentationRuntimePresent(
   if (!process_sp)
     return false;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      process_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+  guard.lock();
 
   InstrumentationRuntimeSP runtime_sp =
       process_sp->GetInstrumentationRuntime(type);
@@ -1278,8 +1277,8 @@ lldb::SBError SBProcess::SaveCore(SBSaveCoreOptions &options) {
     return error;
   }
 
-  std::lock_guard<std::recursive_mutex> guard(
-      process_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+  guard.lock();
 
   if (process_sp->GetState() != eStateStopped) {
     error = Status::FromErrorString("the process is not stopped");
@@ -1301,8 +1300,8 @@ SBProcess::GetMemoryRegionInfo(lldb::addr_t load_addr,
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
 
       sb_error.ref() =
           process_sp->GetMemoryRegionInfo(load_addr, sb_region_info.ref());
@@ -1323,8 +1322,8 @@ lldb::SBMemoryRegionInfoList SBProcess::GetMemoryRegions() {
   ProcessSP process_sp(GetSP());
   Process::StopLocker stop_locker;
   if (process_sp && stop_locker.TryLock(&process_sp->GetRunLock())) {
-    std::lock_guard<std::recursive_mutex> guard(
-        process_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+    guard.lock();
 
     process_sp->GetMemoryRegions(sb_region_list.ref());
   }
@@ -1465,8 +1464,8 @@ lldb::addr_t SBProcess::AllocateMemory(size_t size, uint32_t permissions,
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       addr = process_sp->AllocateMemory(size, permissions, sb_error.ref());
     } else {
       sb_error = Status::FromErrorString("process is running");
@@ -1485,8 +1484,8 @@ lldb::SBError SBProcess::DeallocateMemory(lldb::addr_t ptr) {
   if (process_sp) {
     Process::StopLocker stop_locker;
     if (stop_locker.TryLock(&process_sp->GetRunLock())) {
-      std::lock_guard<std::recursive_mutex> guard(
-          process_sp->GetTarget().GetAPIMutex());
+      TargetAPILock guard(process_sp->GetTarget().GetAPIMutex());
+      guard.lock();
       Status error = process_sp->DeallocateMemory(ptr);
       sb_error.SetError(std::move(error));
     } else {
diff --git a/lldb/source/API/SBSymbol.cpp b/lldb/source/API/SBSymbol.cpp
index 19f2f5e62fd48..bd05576535f5a 100644
--- a/lldb/source/API/SBSymbol.cpp
+++ b/lldb/source/API/SBSymbol.cpp
@@ -127,9 +127,10 @@ SBInstructionList SBSymbol::GetInstructions(SBTarget target,
   SBInstructionList sb_instructions;
   if (m_opaque_ptr) {
     TargetSP target_sp(target.GetSP());
-    std::unique_lock<std::recursive_mutex> lock;
+    TargetAPILock lock;
     if (target_sp && m_opaque_ptr->ValueIsAddress()) {
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+      lock = TargetAPILock(target_sp->GetAPIMutex());
+      lock.lock();
       const Address &symbol_addr = m_opaque_ptr->GetAddressRef();
       ModuleSP module_sp = symbol_addr.GetModule();
       if (module_sp) {
diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp
index 9eca813d8584b..f14602538cac5 100644
--- a/lldb/source/API/SBTarget.cpp
+++ b/lldb/source/API/SBTarget.cpp
@@ -82,7 +82,8 @@ using namespace lldb_private;
 #define DEFAULT_DISASM_BYTE_SIZE 32
 
 static Status AttachToProcess(ProcessAttachInfo &attach_info, Target &target) {
-  std::lock_guard<std::recursive_mutex> guard(target.GetAPIMutex());
+  TargetAPILock guard(target.GetAPIMutex());
+  guard.lock();
 
   auto process_sp = target.GetProcessSP();
   if (process_sp) {
@@ -310,7 +311,8 @@ SBError SBTarget::Install() {
 
   SBError sb_error;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     sb_error.ref() = target_sp->Install(nullptr);
   }
   return sb_error;
@@ -329,7 +331,8 @@ SBProcess SBTarget::Launch(SBListener &listener, char const **argv,
   SBProcess sb_process;
   ProcessSP process_sp;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
 
     if (stop_at_entry)
       launch_flags |= eLaunchFlagStopAtEntry;
@@ -407,7 +410,8 @@ SBProcess SBTarget::Launch(SBLaunchInfo &sb_launch_info, SBError &error) {
 
   SBProcess sb_process;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     StateType state = eStateInvalid;
     {
       ProcessSP process_sp = target_sp->GetProcessSP();
@@ -545,7 +549,8 @@ lldb::SBProcess SBTarget::ConnectRemote(SBListener &listener, const char *url,
   SBProcess sb_process;
   ProcessSP process_sp;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     if (listener.IsValid())
       process_sp =
           target_sp->CreateProcess(listener.m_opaque_sp, plugin_name, nullptr,
@@ -604,7 +609,8 @@ lldb::SBAddress SBTarget::ResolveLoadAddress(lldb::addr_t vm_addr) {
   lldb::SBAddress sb_addr;
   Address &addr = sb_addr.ref();
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     if (target_sp->ResolveLoadAddress(vm_addr, addr))
       return sb_addr;
   }
@@ -621,7 +627,8 @@ lldb::SBAddress SBTarget::ResolveFileAddress(lldb::addr_t file_addr) {
   lldb::SBAddress sb_addr;
   Address &addr = sb_addr.ref();
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     if (target_sp->ResolveFileAddress(file_addr, addr))
       return sb_addr;
   }
@@ -637,7 +644,8 @@ lldb::SBAddress SBTarget::ResolvePastLoadAddress(uint32_t stop_id,
   lldb::SBAddress sb_addr;
   Address &addr = sb_addr.ref();
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     if (target_sp->ResolveLoadAddress(vm_addr, addr))
       return sb_addr;
   }
@@ -672,7 +680,8 @@ size_t SBTarget::ReadMemory(const SBAddress addr, void *buf, size_t size,
 
   size_t bytes_read = 0;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     bytes_read =
         target_sp->ReadMemory(addr.ref(), buf, size, error.ref(), true);
   } else {
@@ -767,7 +776,8 @@ SBBreakpoint SBTarget::BreakpointCreateByLocation(
 
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP(); target_sp && line != 0) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
 
     const LazyBool check_inlines = eLazyBoolCalculate;
     const LazyBool skip_prologue = eLazyBoolCalculate;
@@ -795,7 +805,8 @@ SBBreakpoint SBTarget::BreakpointCreateByLocation(
 
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP(); target_sp && line != 0) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
 
     const LazyBool check_inlines = eLazyBoolCalculate;
     const LazyBool skip_prologue = eLazyBoolCalculate;
@@ -820,7 +831,8 @@ SBBreakpoint SBTarget::BreakpointCreateByName(const char *symbol_name,
 
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
 
     const bool internal = false;
     const bool hardware = false;
@@ -892,7 +904,8 @@ lldb::SBBreakpoint SBTarget::BreakpointCreateByName(
     const bool internal = false;
     const bool hardware = false;
     const LazyBool skip_prologue = eLazyBoolCalculate;
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     FunctionNameType mask = static_cast<FunctionNameType>(name_type_mask);
     sb_bp = target_sp->CreateBreakpoint(module_list.get(), comp_unit_list.get(),
                                         symbol_name, mask, symbol_language,
@@ -935,7 +948,8 @@ lldb::SBBreakpoint SBTarget::BreakpointCreateByNames(
 
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP(); target_sp && num_names > 0) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     const bool internal = false;
     const bool hardware = false;
     FunctionNameType mask = static_cast<FunctionNameType>(name_type_mask);
@@ -980,7 +994,8 @@ lldb::SBBreakpoint SBTarget::BreakpointCreateByRegex(
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP();
       target_sp && symbol_name_regex && symbol_name_regex[0]) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     RegularExpression regexp((llvm::StringRef(symbol_name_regex)));
     const bool internal = false;
     const bool hardware = false;
@@ -999,7 +1014,8 @@ SBBreakpoint SBTarget::BreakpointCreateByAddress(addr_t address) {
 
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     const bool hardware = false;
     sb_bp = target_sp->CreateBreakpoint(address, false, hardware);
   }
@@ -1016,7 +1032,8 @@ SBBreakpoint SBTarget::BreakpointCreateBySBAddress(SBAddress &sb_address) {
   }
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     const bool hardware = false;
     sb_bp = target_sp->CreateBreakpoint(sb_address.ref(), false, hardware);
   }
@@ -1064,7 +1081,8 @@ lldb::SBBreakpoint SBTarget::BreakpointCreateBySourceRegex(
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP();
       target_sp && source_regex && source_regex[0]) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     const bool hardware = false;
     const LazyBool move_to_nearest_code = eLazyBoolCalculate;
     RegularExpression regexp((llvm::StringRef(source_regex)));
@@ -1088,7 +1106,8 @@ SBTarget::BreakpointCreateForException(lldb::LanguageType language,
 
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     const bool hardware = false;
     sb_bp = target_sp->CreateExceptionBreakpoint(language, catch_bp, throw_bp,
                                                   hardware);
@@ -1106,7 +1125,8 @@ lldb::SBBreakpoint SBTarget::BreakpointCreateFromScript(
 
   SBBreakpoint sb_bp;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     Status error;
 
     StructuredData::ObjectSP obj_sp = extra_args.m_impl_up->GetObjectSP();
@@ -1149,7 +1169,8 @@ bool SBTarget::BreakpointDelete(break_id_t bp_id) {
 
   bool result = false;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     result = target_sp->RemoveBreakpointByID(bp_id);
   }
 
@@ -1162,7 +1183,8 @@ SBBreakpoint SBTarget::FindBreakpointByID(break_id_t bp_id) {
   SBBreakpoint sb_breakpoint;
   if (TargetSP target_sp = GetSP();
       target_sp && bp_id != LLDB_INVALID_BREAK_ID) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     sb_breakpoint = target_sp->GetBreakpointByID(bp_id);
   }
 
@@ -1174,7 +1196,8 @@ bool SBTarget::FindBreakpointsByName(const char *name,
   LLDB_INSTRUMENT_VA(this, name, bkpts);
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     llvm::Expected<std::vector<BreakpointSP>> expected_vector =
         target_sp->GetBreakpointList().FindBreakpointsByName(name);
     if (!expected_vector) {
@@ -1195,7 +1218,8 @@ void SBTarget::GetBreakpointNames(SBStringList &names) {
   names.Clear();
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
 
     std::vector<std::string> name_vec;
     target_sp->GetBreakpointNames(name_vec);
@@ -1208,7 +1232,8 @@ void SBTarget::DeleteBreakpointName(const char *name) {
   LLDB_INSTRUMENT_VA(this, name);
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     target_sp->DeleteBreakpointName(llvm::StringRef(name));
   }
 }
@@ -1217,7 +1242,8 @@ bool SBTarget::EnableAllBreakpoints() {
   LLDB_INSTRUMENT_VA(this);
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     target_sp->EnableAllowedBreakpoints();
     return true;
   }
@@ -1228,7 +1254,8 @@ bool SBTarget::DisableAllBreakpoints() {
   LLDB_INSTRUMENT_VA(this);
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     target_sp->DisableAllowedBreakpoints();
     return true;
   }
@@ -1239,7 +1266,8 @@ bool SBTarget::DeleteAllBreakpoints() {
   LLDB_INSTRUMENT_VA(this);
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     target_sp->RemoveAllowedBreakpoints();
     return true;
   }
@@ -1261,7 +1289,8 @@ lldb::SBError SBTarget::BreakpointsCreateFromFile(SBFileSpec &source_file,
 
   SBError sberr;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
 
     BreakpointIDList bp_ids;
 
@@ -1306,7 +1335,8 @@ lldb::SBError SBTarget::BreakpointsWriteToFile(SBFileSpec &dest_file,
 
   SBError sberr;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     BreakpointIDList bp_id_list;
     bkpt_list.CopyToBreakpointIDList(bp_id_list);
     sberr.ref() = target_sp->SerializeBreakpointsToFile(dest_file.ref(),
@@ -1343,7 +1373,8 @@ bool SBTarget::DeleteWatchpoint(watch_id_t wp_id) {
 
   bool result = false;
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     std::unique_lock<std::recursive_mutex> lock;
     target_sp->GetWatchpointList().GetListMutex(lock);
     result = target_sp->RemoveWatchpointByID(wp_id);
@@ -1359,7 +1390,8 @@ SBWatchpoint SBTarget::FindWatchpointByID(lldb::watch_id_t wp_id) {
   lldb::WatchpointSP watchpoint_sp;
   if (TargetSP target_sp = GetSP();
       target_sp && wp_id != LLDB_INVALID_WATCH_ID) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     std::unique_lock<std::recursive_mutex> lock;
     target_sp->GetWatchpointList().GetListMutex(lock);
     watchpoint_sp = target_sp->GetWatchpointList().FindByID(wp_id);
@@ -1404,7 +1436,8 @@ SBTarget::WatchpointCreateByAddress(lldb::addr_t addr, size_t size,
 
   if (TargetSP target_sp = GetSP();
       target_sp && addr != LLDB_INVALID_ADDRESS && size > 0) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     // Target::CreateWatchpoint() is thread safe.
     Status cw_error;
     // This API doesn't take in a type, so we can't figure out what it is.
@@ -1422,7 +1455,8 @@ bool SBTarget::EnableAllWatchpoints() {
   LLDB_INSTRUMENT_VA(this);
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     std::unique_lock<std::recursive_mutex> lock;
     target_sp->GetWatchpointList().GetListMutex(lock);
     target_sp->EnableAllWatchpoints();
@@ -1435,7 +1469,8 @@ bool SBTarget::DisableAllWatchpoints() {
   LLDB_INSTRUMENT_VA(this);
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     std::unique_lock<std::recursive_mutex> lock;
     target_sp->GetWatchpointList().GetListMutex(lock);
     target_sp->DisableAllWatchpoints();
@@ -1500,7 +1535,8 @@ bool SBTarget::DeleteAllWatchpoints() {
   LLDB_INSTRUMENT_VA(this);
 
   if (TargetSP target_sp = GetSP()) {
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     std::unique_lock<std::recursive_mutex> lock;
     target_sp->GetWatchpointList().GetListMutex(lock);
     target_sp->RemoveAllWatchpoints();
@@ -2457,7 +2493,8 @@ lldb::SBValue SBTarget::EvaluateExpression(const char *expr,
     if (expr == nullptr || expr[0] == '\0')
       return expr_result;
 
-    std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+    TargetAPILock guard(target_sp->GetAPIMutex());
+    guard.lock();
     ExecutionContext exe_ctx(m_opaque_sp.get());
 
     frame = exe_ctx.GetFramePtr();
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index fafb533771e56..1a1b820437f6f 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -464,7 +464,7 @@ static Status ResumeNewPlan(StoppedExecutionContext exe_ctx,
   process->GetThreadList().SetSelectedThreadByID(thread->GetID());
 
   // Release the run lock but keep the API lock.
-  std::unique_lock<std::recursive_mutex> api_lock = exe_ctx.AllowResume();
+  TargetAPILock api_lock = exe_ctx.AllowResume();
   if (process->GetTarget().GetDebugger().GetAsyncExecution())
     return process->Resume();
   return process->ResumeSynchronous(nullptr);
diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp
index 61a4a1a6b1835..b29b181ea1cba 100644
--- a/lldb/source/API/SBValue.cpp
+++ b/lldb/source/API/SBValue.cpp
@@ -999,9 +999,9 @@ lldb::ValueObjectSP SBValue::GetSP(ValueLocker &locker) const {
   // IsValid means that the SBValue has a value in it.  But that's not the
   // only time that ValueObjects are useful.  We also want to return the value
   // if there's an error state in it.
-  if (!m_opaque_sp || (!m_opaque_sp->IsValid()
-      && (m_opaque_sp->GetRootSP()
-          && !m_opaque_sp->GetRootSP()->GetError().Fail()))) {
+  if (!m_opaque_sp || (!m_opaque_sp->IsValid() &&
+                       (m_opaque_sp->GetRootSP() &&
+                        !m_opaque_sp->GetRootSP()->GetError().Fail()))) {
     locker.GetError() = Status::FromErrorString("No value");
     return ValueObjectSP();
   }
@@ -1131,7 +1131,6 @@ lldb::SBValue SBValue::EvaluateExpression(const char *expr,
     return SBValue();
   }
 
-
   ValueLocker locker;
   lldb::ValueObjectSP value_sp(GetSP(locker));
   if (!value_sp) {
@@ -1143,7 +1142,8 @@ lldb::SBValue SBValue::EvaluateExpression(const char *expr,
     return SBValue();
   }
 
-  std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+  TargetAPILock guard(target_sp->GetAPIMutex());
+  guard.lock();
   ExecutionContext exe_ctx(target_sp.get());
 
   StackFrame *frame = exe_ctx.GetFramePtr();
diff --git a/lldb/source/API/SBWatchpoint.cpp b/lldb/source/API/SBWatchpoint.cpp
index 30528b8d34652..87d1a42756f12 100644
--- a/lldb/source/API/SBWatchpoint.cpp
+++ b/lldb/source/API/SBWatchpoint.cpp
@@ -108,8 +108,8 @@ addr_t SBWatchpoint::GetWatchAddress() {
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     ret_addr = watchpoint_sp->GetLoadAddress();
   }
 
@@ -123,8 +123,8 @@ size_t SBWatchpoint::GetWatchSize() {
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     watch_size = watchpoint_sp->GetByteSize();
   }
 
@@ -137,7 +137,8 @@ void SBWatchpoint::SetEnabled(bool enabled) {
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
     Target &target = watchpoint_sp->GetTarget();
-    std::lock_guard<std::recursive_mutex> guard(target.GetAPIMutex());
+    TargetAPILock guard(target.GetAPIMutex());
+    guard.lock();
     ProcessSP process_sp = target.GetProcessSP();
     const bool notify = true;
     if (process_sp) {
@@ -156,8 +157,8 @@ bool SBWatchpoint::IsEnabled() {
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return watchpoint_sp->IsEnabled();
   } else
     return false;
@@ -169,8 +170,8 @@ uint32_t SBWatchpoint::GetHitCount() {
   uint32_t count = 0;
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     count = watchpoint_sp->GetHitCount();
   }
 
@@ -182,8 +183,8 @@ uint32_t SBWatchpoint::GetIgnoreCount() {
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     return watchpoint_sp->GetIgnoreCount();
   } else
     return 0;
@@ -194,8 +195,8 @@ void SBWatchpoint::SetIgnoreCount(uint32_t n) {
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     watchpoint_sp->SetIgnoreCount(n);
   }
 }
@@ -207,8 +208,8 @@ const char *SBWatchpoint::GetCondition() {
   if (!watchpoint_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      watchpoint_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   return ConstString(watchpoint_sp->GetConditionText()).GetCString();
 }
 
@@ -217,8 +218,8 @@ void SBWatchpoint::SetCondition(const char *condition) {
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     watchpoint_sp->SetCondition(condition);
   }
 }
@@ -231,8 +232,8 @@ bool SBWatchpoint::GetDescription(SBStream &description,
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     watchpoint_sp->GetDescription(&strm, level);
     strm.EOL();
   } else
@@ -291,8 +292,8 @@ lldb::SBType SBWatchpoint::GetType() {
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     const CompilerType &type = watchpoint_sp->GetCompilerType();
     return lldb::SBType(type);
   }
@@ -304,8 +305,8 @@ WatchpointValueKind SBWatchpoint::GetWatchValueKind() {
 
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
     if (watchpoint_sp->IsWatchVariable())
       return WatchpointValueKind::eWatchPointValueKindVariable;
     return WatchpointValueKind::eWatchPointValueKindExpression;
@@ -320,8 +321,8 @@ const char *SBWatchpoint::GetWatchSpec() {
   if (!watchpoint_sp)
     return nullptr;
 
-  std::lock_guard<std::recursive_mutex> guard(
-      watchpoint_sp->GetTarget().GetAPIMutex());
+  TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+  guard.lock();
   // Store the result of `GetWatchSpec()` as a ConstString
   // so that the C string we return has a sufficiently long
   // lifetime. Note this a memory leak but should be fairly
@@ -333,8 +334,8 @@ bool SBWatchpoint::IsWatchingReads() {
   LLDB_INSTRUMENT_VA(this);
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
 
     return watchpoint_sp->WatchpointRead();
   }
@@ -346,8 +347,8 @@ bool SBWatchpoint::IsWatchingWrites() {
   LLDB_INSTRUMENT_VA(this);
   lldb::WatchpointSP watchpoint_sp(GetSP());
   if (watchpoint_sp) {
-    std::lock_guard<std::recursive_mutex> guard(
-        watchpoint_sp->GetTarget().GetAPIMutex());
+    TargetAPILock guard(watchpoint_sp->GetTarget().GetAPIMutex());
+    guard.lock();
 
     return watchpoint_sp->WatchpointWrite() ||
            watchpoint_sp->WatchpointModify();
diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp
index aa0d4cc58d0ae..b12932441e979 100644
--- a/lldb/source/Interpreter/CommandObject.cpp
+++ b/lldb/source/Interpreter/CommandObject.cpp
@@ -234,9 +234,10 @@ bool CommandObject::CheckRequirements(CommandReturnObject &result) {
     }
 
     if (flags & eCommandTryTargetAPILock) {
-      if (target && !target->IsDummyTarget())
-        m_api_locker =
-            std::unique_lock<std::recursive_mutex>(target->GetAPIMutex());
+      if (target && !target->IsDummyTarget()) {
+        m_api_locker = TargetAPILock(target->GetAPIMutex());
+        m_api_locker.lock();
+      }
     }
   }
 
diff --git a/lldb/source/Target/ExecutionContext.cpp b/lldb/source/Target/ExecutionContext.cpp
index e4b2f07d8d8d1..e58b90bbd3e61 100644
--- a/lldb/source/Target/ExecutionContext.cpp
+++ b/lldb/source/Target/ExecutionContext.cpp
@@ -145,8 +145,8 @@ lldb_private::GetStoppedExecutionContext(
     return llvm::createStringError(
         "StoppedExecutionContext created with a null target");
 
-  auto api_lock =
-      std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+  auto api_lock = TargetAPILock(target_sp->GetAPIMutex());
+  api_lock.lock();
 
   auto process_sp = exe_ctx_ref_ptr->GetProcessSP();
   if (!process_sp)
@@ -173,7 +173,7 @@ lldb_private::GetStoppedExecutionContext(
                                  std::move(api_lock), std::move(stop_locker));
 }
 
-std::unique_lock<std::recursive_mutex> StoppedExecutionContext::AllowResume() {
+TargetAPILock StoppedExecutionContext::AllowResume() {
   Clear();
   m_stop_locker = ProcessRunLock::ProcessRunLocker();
   return std::move(m_api_lock);
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 239c82e02348d..f422efde029e8 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -5994,12 +5994,12 @@ Target::TargetEventData::GetModuleListFromEvent(const Event *event_ptr) {
   return module_list;
 }
 
-std::recursive_mutex &Target::GetAPIMutex() {
+TargetAPILock Target::GetAPIMutex() {
   Policy policy = PolicyStack::Get().Current();
   if (policy.view == Policy::View::Private)
-    return m_private_mutex;
+    return TargetAPILock(m_private_mutex);
 
-  return m_mutex;
+  return TargetAPILock(m_mutex);
 }
 
 /// Get metrics associated with this target in JSON format.
diff --git a/lldb/source/ValueObject/ValueObject.cpp b/lldb/source/ValueObject/ValueObject.cpp
index e2bfa02500f2c..ba973e2516a24 100644
--- a/lldb/source/ValueObject/ValueObject.cpp
+++ b/lldb/source/ValueObject/ValueObject.cpp
@@ -3850,9 +3850,8 @@ bool ValueImpl::IsValid() {
   return target_sp && target_sp->IsValid();
 }
 
-lldb::ValueObjectSP
-ValueImpl::GetSP(Process::StopLocker &stop_locker,
-                 std::unique_lock<std::recursive_mutex> &lock, Status &error) {
+lldb::ValueObjectSP ValueImpl::GetSP(Process::StopLocker &stop_locker,
+                                     TargetAPILock &lock, Status &error) {
   if (!m_valobj_sp) {
     error = Status::FromErrorString("invalid value object");
     return m_valobj_sp;
@@ -3868,7 +3867,8 @@ ValueImpl::GetSP(Process::StopLocker &stop_locker,
   if (!target)
     return ValueObjectSP();
 
-  lock = std::unique_lock<std::recursive_mutex>(target->GetAPIMutex());
+  lock = TargetAPILock(target->GetAPIMutex());
+  lock.lock();
 
   ProcessSP process_sp(value_sp->GetProcessSP());
   if (process_sp && !stop_locker.TryLock(&process_sp->GetRunLock())) {
diff --git a/lldb/unittests/Target/CMakeLists.txt b/lldb/unittests/Target/CMakeLists.txt
index bf08a8f015ba0..588d0e7db2885 100644
--- a/lldb/unittests/Target/CMakeLists.txt
+++ b/lldb/unittests/Target/CMakeLists.txt
@@ -14,6 +14,7 @@ add_lldb_unittest(TargetTests
   ScratchTypeSystemTest.cpp
   StackFrameRecognizerTest.cpp
   SummaryStatisticsTest.cpp
+  TargetAPILockTest.cpp
   FindFileTest.cpp
 
   LINK_COMPONENTS
diff --git a/lldb/unittests/Target/TargetAPILockTest.cpp b/lldb/unittests/Target/TargetAPILockTest.cpp
new file mode 100644
index 0000000000000..0bbc42812be8e
--- /dev/null
+++ b/lldb/unittests/Target/TargetAPILockTest.cpp
@@ -0,0 +1,125 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/TargetAPILock.h"
+#include "gtest/gtest.h"
+
+#include <mutex>
+#include <thread>
+
+using namespace lldb_private;
+
+TEST(TargetAPILockTest, DefaultConstructedIsANoOp) {
+  // No synchronization primitive is touched at all in this state, so
+  // there is no pairing requirement: try_lock() always succeeds, and
+  // lock()/unlock() are callable with no invariant to violate.
+  TargetAPILock lock;
+  EXPECT_TRUE(lock.try_lock());
+  lock.lock();
+  lock.unlock();
+  lock.lock();
+  lock.unlock();
+}
+
+TEST(TargetAPILockTest, ConstructorResolvesButDoesNotLock) {
+  std::recursive_mutex mutex;
+  TargetAPILock lock(mutex);
+  EXPECT_FALSE(lock.owns_lock());
+
+  // Since construction alone didn't lock anything, a background thread
+  // must still be able to acquire the mutex.
+  std::thread t([&mutex]() {
+    TargetAPILock background_lock(mutex);
+    EXPECT_TRUE(background_lock.try_lock());
+    background_lock.unlock();
+  });
+  t.join();
+}
+
+TEST(TargetAPILockTest, WrapsARealMutex) {
+  std::recursive_mutex mutex;
+  TargetAPILock lock(mutex);
+
+  lock.lock();
+  EXPECT_TRUE(lock.owns_lock());
+
+  // Recursive reentrancy is delegated straight to the underlying
+  // std::recursive_mutex: a second handle over the same mutex, locked
+  // from the same thread, must not block.
+  TargetAPILock second_lock(mutex);
+  EXPECT_TRUE(second_lock.try_lock());
+  second_lock.unlock();
+
+  lock.unlock();
+  EXPECT_FALSE(lock.owns_lock());
+
+  // Once fully unlocked (both handles released), a background thread
+  // must be able to acquire the same underlying mutex.
+  std::thread t([&mutex]() {
+    TargetAPILock background_lock(mutex);
+    EXPECT_TRUE(background_lock.try_lock());
+    background_lock.unlock();
+  });
+  t.join();
+}
+
+TEST(TargetAPILockTest, RealMutexBlocksOtherThreads) {
+  std::recursive_mutex mutex;
+  TargetAPILock lock(mutex);
+
+  lock.lock();
+
+  // While held on this thread, a different thread must not be able to
+  // acquire the same underlying mutex.
+  std::thread t([&mutex]() {
+    TargetAPILock background_lock(mutex);
+    EXPECT_FALSE(background_lock.try_lock());
+  });
+  t.join();
+
+  lock.unlock();
+}
+
+TEST(TargetAPILockTest, DestructorReleasesIfLocked) {
+  std::recursive_mutex mutex;
+  {
+    TargetAPILock lock(mutex);
+    lock.lock();
+  }
+
+  std::thread t([&mutex]() {
+    TargetAPILock background_lock(mutex);
+    EXPECT_TRUE(background_lock.try_lock());
+    background_lock.unlock();
+  });
+  t.join();
+}
+
+TEST(TargetAPILockTest, DestructorIsANoOpIfNeverLocked) {
+  std::recursive_mutex mutex;
+  { TargetAPILock lock(mutex); }
+
+  std::thread t([&mutex]() {
+    TargetAPILock background_lock(mutex);
+    EXPECT_TRUE(background_lock.try_lock());
+    background_lock.unlock();
+  });
+  t.join();
+}
+
+TEST(TargetAPILockTest, MoveTransfersOwnership) {
+  std::recursive_mutex mutex;
+  TargetAPILock lock(mutex);
+  lock.lock();
+
+  TargetAPILock moved(std::move(lock));
+  EXPECT_FALSE(lock.owns_lock());
+  EXPECT_TRUE(moved.owns_lock());
+
+  moved.unlock();
+}

>From 055297b95daedcf52b43f0888ca5dc8c4cf8f6ae Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Mon, 3 Aug 2026 02:46:03 -0700
Subject: [PATCH 2/2] [lldb] Fix scripted frame provider cross-thread
 re-entrant deadlock

GetStoppedExecutionContext unconditionally blocked acquiring the
target's API mutex. A thread already holding that mutex (for example a
`bt` command thread, through CommandObjectParsed's
eCommandTryTargetAPILock) can end up waiting on a StackFrameList lock
held by another thread (for example the debugger's event-handler
thread) that is itself blocked re-acquiring the API mutex from inside a
scripted frame provider's Python code that touches the SB API. This is
a classic AB-BA deadlock.

This patch introduces Policy::Capabilities::can_bypass_target_api_mutex,
pushed around every scripted-extension callback in
ScriptedPythonInterface::Dispatch and CallStaticMethod. A thread running
one of these callbacks isn't servicing a client-facing SB API entry
point; it doesn't need the same locking guarantees a top-level SB API
call does for any of the calls it makes during that window, not just the
one that happens to deadlock.

Target::GetAPIMutex() encodes the check itself: when the current
thread's policy says it can bypass, it returns a genuine no-op
TargetAPILock -- no synchronization primitive touched at all -- instead
of the real mutex. Every existing caller keeps its own lock_guard/
unique_lock code unchanged and becomes deadlock-safe automatically,
since a no-op handle can be locked/unlocked from any thread with no
cross-thread hazard.

SBTarget::GetAPIMutex() hands out an SBMutex that can be constructed on
one thread and locked/unlocked/held indefinitely on another, so it
can't resolve GetAPIMutex() once and cache the result the way a scoped
lock_guard does. SBMutex now wraps a new APIMutexHandle, which holds a
TargetSP and resolves GetAPIMutex() fresh on every lock()/try_lock()
call; its unlock() replays the exact resolution the matching lock()/
try_lock() produced rather than re-resolving, so a policy change on the
calling thread between lock() and unlock() can't cause it to release
the wrong mutex (or fail to release the one it actually holds).

This patch adds regression tests for both the original deadlock and for
a blocking SBMutex.lock() call made from inside a callback, including
TestSBMutexReflectsTargetMutex, which confirms SBMutex aliases the
real, shared target mutex rather than the bypass no-op.

Depends on #212872, which introduces TargetAPILock.

Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
 lldb/include/lldb/API/SBMutex.h               |   3 +-
 lldb/include/lldb/Target/Target.h             |  38 +++++
 lldb/include/lldb/Utility/Policy.h            |  11 ++
 lldb/include/lldb/lldb-forward.h              |   1 +
 lldb/source/API/SBMutex.cpp                   |  19 +--
 .../Interfaces/ScriptedPythonInterface.h      |   7 +
 lldb/source/Target/Target.cpp                 |  37 +++++
 lldb/source/Utility/Policy.cpp                |   7 +
 .../Makefile                                  |   2 +
 ...ProviderRegisterCommandAPIMutexDeadlock.py |  88 +++++++++++
 .../frame_provider.py                         |  28 ++++
 .../main.c                                    |   7 +
 .../sbmutex_reflects_target_mutex/Makefile    |   2 +
 .../TestHoldMutexNoDeadlock.py                |  87 +++++++++++
 .../TestSBMutexReflectsTargetMutex.py         | 139 ++++++++++++++++++
 .../hold_mutex_frame_provider.py              |  35 +++++
 .../sbmutex_reflects_target_mutex/main.c      |  12 ++
 .../sbmutex_frame_provider.py                 |  84 +++++++++++
 lldb/unittests/Target/APIMutexHandleTest.cpp  | 124 ++++++++++++++++
 lldb/unittests/Target/CMakeLists.txt          |   1 +
 lldb/unittests/Utility/PolicyTest.cpp         |  17 ++-
 21 files changed, 728 insertions(+), 21 deletions(-)
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/Makefile
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/TestFrameProviderRegisterCommandAPIMutexDeadlock.py
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/frame_provider.py
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/main.c
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/Makefile
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/TestHoldMutexNoDeadlock.py
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/TestSBMutexReflectsTargetMutex.py
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/hold_mutex_frame_provider.py
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/main.c
 create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/sbmutex_frame_provider.py
 create mode 100644 lldb/unittests/Target/APIMutexHandleTest.cpp

diff --git a/lldb/include/lldb/API/SBMutex.h b/lldb/include/lldb/API/SBMutex.h
index 74ba36bbe0c21..2c7f859abdd71 100644
--- a/lldb/include/lldb/API/SBMutex.h
+++ b/lldb/include/lldb/API/SBMutex.h
@@ -10,7 +10,6 @@
 #define LLDB_API_SBMUTEX_H
 
 #include "lldb/API/SBDefines.h"
-#include "lldb/Target/TargetAPILock.h"
 #include "lldb/lldb-forward.h"
 #include <memory>
 
@@ -42,7 +41,7 @@ class LLDB_API SBMutex {
   SBMutex(lldb::TargetSP target_sp);
   friend class SBTarget;
 
-  std::shared_ptr<lldb_private::TargetAPILock> m_opaque_sp;
+  std::shared_ptr<lldb_private::APIMutexHandle> m_opaque_sp;
 };
 
 } // namespace lldb
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index edb17d9a289ee..126578f1a1659 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -574,6 +574,37 @@ class EvaluateExpressionOptions {
   SymbolContextList m_preferred_lookup_contexts;
 };
 
+/// A lockable handle to a Target's API mutex, backing the public
+/// lldb::SBMutex. A handle may be constructed on one thread and then
+/// locked/unlocked on a different one, so lock()/try_lock() resolve
+/// Target::GetAPIMutex() fresh on every call -- rather than caching a
+/// single resolution for the handle's lifetime -- so a handle held
+/// across a call into a scripted callback still picks up the bypass
+/// instead of blocking on the real mutex it no longer needs. The
+/// matching unlock() replays the exact resolution lock()/try_lock()
+/// produced, rather than re-resolving, so the calling thread's policy
+/// at unlock() time can't cause it to release the wrong mutex (or fail
+/// to release the one it actually holds).
+///
+/// If constructed without a target, it owns its own independent mutex.
+class APIMutexHandle {
+public:
+  APIMutexHandle() = default;
+  explicit APIMutexHandle(lldb::TargetSP target_sp);
+
+  void lock();
+  void unlock();
+  bool try_lock();
+
+private:
+  lldb::TargetSP m_target_sp;
+  std::recursive_mutex m_standalone_mutex;
+  /// The resolution GetAPIMutex() produced for the currently outstanding
+  /// acquisition. Set by lock()/try_lock(), replayed (not re-resolved) by
+  /// the matching unlock(). Never used on the standalone-mutex path.
+  std::optional<TargetAPILock> m_held_lock;
+};
+
 // Target
 class Target : public std::enable_shared_from_this<Target>,
                public TargetProperties,
@@ -767,6 +798,13 @@ class Target : public std::enable_shared_from_this<Target>,
   /// touching the target through the SB API. The handle isn't locked yet;
   /// lock()/try_lock() it (typically via std::lock_guard<TargetAPILock>/
   /// std::unique_lock<TargetAPILock>) to actually acquire it.
+  ///
+  /// Returns a genuine no-op handle (no synchronization primitive
+  /// touched at all) when the calling thread is inside a
+  /// scripted-extension callback -- see
+  /// Policy::Capabilities::can_bypass_target_api_mutex and
+  /// APIMutexHandle above for how callers use this correctly across
+  /// such a bypass.
   TargetAPILock GetAPIMutex();
 
   void DeleteCurrentProcess();
diff --git a/lldb/include/lldb/Utility/Policy.h b/lldb/include/lldb/Utility/Policy.h
index afeeab19c2ed0..83248cf61d0c6 100644
--- a/lldb/include/lldb/Utility/Policy.h
+++ b/lldb/include/lldb/Utility/Policy.h
@@ -50,6 +50,11 @@ struct Policy {
     bool can_run_breakpoint_actions = true;
     bool can_load_frame_providers = true;
     bool can_run_frame_recognizers = true;
+    /// Whether the current thread may bypass the target's API mutex
+    /// entirely when it re-enters it, because the thread is already
+    /// running under whatever protections its caller set up rather than
+    /// servicing a top-level SB API entry point itself.
+    bool can_bypass_target_api_mutex = false;
   };
 
   /// Why a private-state policy is being pushed. Distinguishes a PST's
@@ -75,6 +80,7 @@ struct Policy {
   static Policy CreatePrivateState(
       PrivateStatePurpose purpose = PrivateStatePurpose::Default);
   static Policy CreatePublicStateRunningExpression();
+  static Policy CreateScriptedExtensionCall();
   /// @}
 
   void Dump(Stream &s) const;
@@ -140,6 +146,11 @@ class PolicyStack {
     return Guard();
   }
 
+  [[nodiscard]] Guard PushScriptedExtensionCall() {
+    Push(Policy::CreateScriptedExtensionCall());
+    return Guard();
+  }
+
 private:
   void Push(Policy policy) { m_stack.push_back(std::move(policy)); }
 
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 2572aa0dc344b..242867f7ecbaf 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -15,6 +15,7 @@
 namespace lldb_private {
 
 class ABI;
+class APIMutexHandle;
 class ASTResultSynthesizer;
 class ASTStructExtractor;
 class Address;
diff --git a/lldb/source/API/SBMutex.cpp b/lldb/source/API/SBMutex.cpp
index 244f899722019..1be8d69021554 100644
--- a/lldb/source/API/SBMutex.cpp
+++ b/lldb/source/API/SBMutex.cpp
@@ -11,26 +11,12 @@
 #include "lldb/Utility/Instrumentation.h"
 #include "lldb/lldb-forward.h"
 #include <memory>
-#include <mutex>
 
 using namespace lldb;
 using namespace lldb_private;
 
-namespace {
-/// Backing storage for a standalone (not Target-derived) SBMutex: owns the
-/// std::recursive_mutex that the TargetAPILock member wraps, so both share
-/// one control block and one lifetime.
-struct StandaloneAPILock {
-  std::recursive_mutex mutex;
-  TargetAPILock lock{mutex};
-};
-} // namespace
-
-SBMutex::SBMutex() {
+SBMutex::SBMutex() : m_opaque_sp(std::make_shared<APIMutexHandle>()) {
   LLDB_INSTRUMENT_VA(this);
-
-  auto owner = std::make_shared<StandaloneAPILock>();
-  m_opaque_sp = std::shared_ptr<TargetAPILock>(owner, &owner->lock);
 }
 
 SBMutex::SBMutex(const SBMutex &rhs) : m_opaque_sp(rhs.m_opaque_sp) {
@@ -45,8 +31,7 @@ const SBMutex &SBMutex::operator=(const SBMutex &rhs) {
 }
 
 SBMutex::SBMutex(lldb::TargetSP target_sp)
-    : m_opaque_sp(
-          std::make_shared<TargetAPILock>(target_sp->GetAPIMutex())) {
+    : m_opaque_sp(std::make_shared<APIMutexHandle>(target_sp)) {
   LLDB_INSTRUMENT_VA(this, target_sp);
 }
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index d82a59738a7db..49147f8144c39 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -18,6 +18,7 @@
 #include "lldb/API/SBCommandReturnObject.h"
 #include "lldb/Interpreter/Interfaces/ScriptedInterface.h"
 #include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/Policy.h"
 
 #include "../PythonDataObjects.h"
 #include "../SWIGPythonBridge.h"
@@ -421,6 +422,9 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
       return ErrorWithMessage<T>(caller_signature, "missing script class name",
                                  error);
 
+    PolicyStack::Guard policy_guard =
+        PolicyStack::Get().PushScriptedExtensionCall();
+
     Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
                    Locker::FreeLock);
 
@@ -516,6 +520,9 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
       return ErrorWithMessage<T>(caller_signature, "python object ill-formed",
                                  error);
 
+    PolicyStack::Guard policy_guard =
+        PolicyStack::Get().PushScriptedExtensionCall();
+
     Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
                    Locker::FreeLock);
 
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index f422efde029e8..e6547dbf66bc2 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -5994,8 +5994,45 @@ Target::TargetEventData::GetModuleListFromEvent(const Event *event_ptr) {
   return module_list;
 }
 
+APIMutexHandle::APIMutexHandle(lldb::TargetSP target_sp)
+    : m_target_sp(std::move(target_sp)) {}
+
+void APIMutexHandle::lock() {
+  if (!m_target_sp) {
+    m_standalone_mutex.lock();
+    return;
+  }
+  TargetAPILock resolved = m_target_sp->GetAPIMutex();
+  resolved.lock();
+  m_held_lock = std::move(resolved);
+}
+
+void APIMutexHandle::unlock() {
+  if (!m_target_sp) {
+    m_standalone_mutex.unlock();
+    return;
+  }
+  assert(m_held_lock && "APIMutexHandle::unlock() without a held lock");
+  m_held_lock->unlock();
+  m_held_lock.reset();
+}
+
+bool APIMutexHandle::try_lock() {
+  if (!m_target_sp)
+    return m_standalone_mutex.try_lock();
+  TargetAPILock resolved = m_target_sp->GetAPIMutex();
+  if (!resolved.try_lock())
+    return false;
+  m_held_lock = std::move(resolved);
+  return true;
+}
+
 TargetAPILock Target::GetAPIMutex() {
   Policy policy = PolicyStack::Get().Current();
+
+  if (policy.capabilities.can_bypass_target_api_mutex)
+    return TargetAPILock(); // Genuine no-op.
+
   if (policy.view == Policy::View::Private)
     return TargetAPILock(m_private_mutex);
 
diff --git a/lldb/source/Utility/Policy.cpp b/lldb/source/Utility/Policy.cpp
index 4d1999aaf7b92..04293d7a03f85 100644
--- a/lldb/source/Utility/Policy.cpp
+++ b/lldb/source/Utility/Policy.cpp
@@ -64,6 +64,12 @@ Policy Policy::CreatePublicStateRunningExpression() {
   return p;
 }
 
+Policy Policy::CreateScriptedExtensionCall() {
+  Policy p = PolicyStack::Get().Current();
+  p.capabilities.can_bypass_target_api_mutex = true;
+  return p;
+}
+
 PolicyStack::Guard::~Guard() {
   if (!m_active)
     return;
@@ -108,6 +114,7 @@ void Policy::Dump(Stream &s) const {
   s << " bp_actions=" << capabilities.can_run_breakpoint_actions;
   s << " frame_providers=" << capabilities.can_load_frame_providers;
   s << " frame_recognizers=" << capabilities.can_run_frame_recognizers;
+  s << " bypass_api_mutex=" << capabilities.can_bypass_target_api_mutex;
   s << '}';
 }
 
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/Makefile
new file mode 100644
index 0000000000000..c9319d6e6888a
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/Makefile
@@ -0,0 +1,2 @@
+C_SOURCES := main.c
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/TestFrameProviderRegisterCommandAPIMutexDeadlock.py b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/TestFrameProviderRegisterCommandAPIMutexDeadlock.py
new file mode 100644
index 0000000000000..4efb4baae9b0f
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/TestFrameProviderRegisterCommandAPIMutexDeadlock.py
@@ -0,0 +1,88 @@
+"""
+Test that a scripted frame provider whose get_frame_at_index touches SB
+API (self.input_frames) does not deadlock when running `bt` from the
+command interpreter.
+
+GetStoppedExecutionContext (used by SBFrame::IsValid, among others)
+unconditionally blocked acquiring the target's API mutex. The command
+thread running `bt` already holds that mutex (CommandObjectParsed's
+eCommandTryTargetAPILock) and can end up waiting on a StackFrameList
+lock held by the debugger's event-handler thread, which is itself
+blocked re-acquiring the API mutex from inside this provider's Python
+code -- an AB-BA deadlock between the command thread and the
+event-handler thread.
+
+The event-handler thread only runs when commands are driven through
+SBDebugger.RunCommandInterpreter (what the lldb driver itself uses),
+not through plain HandleCommand, so this test drives commands that way.
+
+Note: this is a genuine cross-thread race (the command thread vs. the
+debugger's event-handler thread), not a deterministic sequential
+deadlock, so this test is best-effort -- like the sibling
+runlock_reentrant_deadlock/was_hit_deadlock tests, it raises the odds of
+hitting the race within a single invocation but cannot guarantee it.
+"""
+
+import os
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class TestFrameProviderRegisterCommandAPIMutexDeadlock(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_register_command_then_bt_no_deadlock(self):
+        """
+        Register a scripted frame provider whose get_frame_at_index
+        touches SB API, then repeatedly run `bt` through
+        RunCommandInterpreter. Should complete without deadlocking.
+        """
+        self.build()
+
+        lldbutil.run_to_name_breakpoint(self, "frame3")
+
+        provider_path = os.path.join(self.getSourceDir(), "frame_provider.py")
+
+        commands = ["command script import " + provider_path]
+        commands.append(
+            "target frame-provider register -C frame_provider.DictFrameProvider"
+        )
+        # Run `bt` several times to raise the odds of hitting the race
+        # (see module docstring).
+        commands.extend(["bt"] * 20)
+        commands.append("quit")
+
+        stdin_path = self.getBuildArtifact("stdin.txt")
+        stdout_path = self.getBuildArtifact("stdout.txt")
+        with open(stdin_path, "w") as f:
+            f.write("\n".join(commands) + "\n")
+
+        with open(stdin_path, "r") as in_fileH, open(stdout_path, "w") as out_fileH:
+            in_sbf = lldb.SBFile(in_fileH.fileno(), "r", False)
+            out_sbf = lldb.SBFile(out_fileH.fileno(), "w", False)
+            self.assertSuccess(self.dbg.SetInputFile(in_sbf))
+            self.assertSuccess(self.dbg.SetOutputFile(out_sbf))
+            self.assertSuccess(self.dbg.SetErrorFile(out_sbf))
+
+            options = lldb.SBCommandInterpreterRunOptions()
+            options.SetEchoCommands(False)
+            options.SetPrintResults(True)
+            options.SetStopOnError(False)
+            options.SetStopOnCrash(False)
+
+            # If the API-mutex deadlock regresses, this call hangs forever
+            # (timing out the test run).
+            n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
+                True, False, options, 0, False, False
+            )
+
+        with open(stdout_path, "r") as out_fileH:
+            output = out_fileH.read()
+
+        self.assertFalse(has_crashed, "lldb should not have crashed")
+        self.assertTrue(quit_requested, "quit command should have been processed")
+        self.assertEqual(n_errors, 0, f"unexpected errors in output:\n{output}")
+
+        self.assertIn("successfully registered scripted frame provider", output)
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/frame_provider.py
new file mode 100644
index 0000000000000..2bfab64b1c4d8
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/frame_provider.py
@@ -0,0 +1,28 @@
+"""
+Frame provider that returns dict-based synthetic frames (never identity
+forwarding), while touching self.input_frames from get_frame_at_index.
+
+Returning a dict keeps this test isolated from the frame-aliasing bug:
+dict-based frames always go through ScriptedFrameProvider's
+create_frame_from_dict helper, which builds a brand new StackFrame and
+never reuses (or wraps via BorrowedStackFrame) the parent list's frame
+object. Only the API-mutex deadlock is reachable through this path.
+"""
+
+from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider
+
+
+class DictFrameProvider(ScriptedFrameProvider):
+    @staticmethod
+    def get_description():
+        return "Provider that returns dict-based synthetic frames"
+
+    def get_frame_at_index(self, index):
+        if index >= len(self.input_frames):
+            return None
+        # __getitem__ calls SBFrame.IsValid() internally, which is what
+        # exercises GetStoppedExecutionContext.
+        frame = self.input_frames[index]
+        if frame is None:
+            return None
+        return {"idx": index, "pc": frame.GetPC()}
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/main.c b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/main.c
new file mode 100644
index 0000000000000..1aa56e3eddf7a
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/main.c
@@ -0,0 +1,7 @@
+int frame3() { return 3; }
+
+int frame2() { return frame3(); }
+
+int frame1() { return frame2(); }
+
+int main() { return frame1(); }
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/Makefile
new file mode 100644
index 0000000000000..c9319d6e6888a
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/Makefile
@@ -0,0 +1,2 @@
+C_SOURCES := main.c
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/TestHoldMutexNoDeadlock.py b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/TestHoldMutexNoDeadlock.py
new file mode 100644
index 0000000000000..6e46193bfd83b
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/TestHoldMutexNoDeadlock.py
@@ -0,0 +1,87 @@
+"""
+Test that a scripted frame provider can safely call a blocking
+SBMutex.lock() from inside get_frame_at_index without deadlocking.
+
+LLDB's private state thread can reach this callback without already
+holding the target's real API mutex, so a blocking lock() there is a
+genuinely new acquisition attempt, not a safe same-thread recursive
+re-lock. Target::APIMutexHandle re-resolves Target::GetAPIMutex() on
+every call rather than aliasing whatever mutex was current when the
+SBMutex was constructed, so this thread gets the same thread-local
+bypass mutex the internal machinery does, and lock() never contends
+with anyone.
+
+Like the sibling runlock_reentrant_deadlock/was_hit_deadlock/
+register_command_api_mutex_deadlock tests, this drives a genuine
+cross-thread race and is best-effort: it raises the odds of exercising
+the path within a single invocation but the important guarantee is that
+it cannot hang, not that it hits any particular thread ordering.
+"""
+
+import os
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class TestHoldMutexNoDeadlock(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_hold_mutex_no_deadlock(self):
+        """
+        Register a scripted frame provider that locks and holds
+        target.GetAPIMutex() from get_frame_at_index, then run `bt` and
+        `continue` through RunCommandInterpreter. Should complete
+        without deadlocking.
+        """
+        self.build()
+
+        lldbutil.run_to_name_breakpoint(self, "frame3")
+
+        provider_path = os.path.join(
+            self.getSourceDir(), "hold_mutex_frame_provider.py"
+        )
+        commands = ["command script import " + provider_path]
+        commands.append(
+            "target frame-provider register "
+            "-C hold_mutex_frame_provider.HoldMutexFrameProvider"
+        )
+        # Interleave `bt` with `continue` (hitting the same breakpoint
+        # again, via a loop in main.c) so get_frame_at_index runs
+        # repeatedly instead of once, raising the odds of hitting the
+        # race within a single test invocation.
+        commands.extend(["bt", "continue"] * 20)
+        commands.append("quit")
+
+        stdin_path = self.getBuildArtifact("stdin.txt")
+        stdout_path = self.getBuildArtifact("stdout.txt")
+        with open(stdin_path, "w") as f:
+            f.write("\n".join(commands) + "\n")
+
+        with open(stdin_path, "r") as in_fileH, open(stdout_path, "w") as out_fileH:
+            in_sbf = lldb.SBFile(in_fileH.fileno(), "r", False)
+            out_sbf = lldb.SBFile(out_fileH.fileno(), "w", False)
+            self.assertSuccess(self.dbg.SetInputFile(in_sbf))
+            self.assertSuccess(self.dbg.SetOutputFile(out_sbf))
+            self.assertSuccess(self.dbg.SetErrorFile(out_sbf))
+
+            options = lldb.SBCommandInterpreterRunOptions()
+            options.SetEchoCommands(False)
+            options.SetPrintResults(True)
+            options.SetStopOnError(False)
+            options.SetStopOnCrash(False)
+
+            # If the bypass regresses, this call hangs forever (timing
+            # out the test run).
+            n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
+                True, False, options, 0, False, False
+            )
+
+        with open(stdout_path, "r") as out_fileH:
+            output = out_fileH.read()
+
+        self.assertFalse(has_crashed, "lldb should not have crashed")
+        self.assertTrue(quit_requested, "quit command should have been processed")
+        self.assertEqual(n_errors, 0, f"unexpected errors in output:\n{output}")
+        self.assertIn("successfully registered scripted frame provider", output)
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/TestSBMutexReflectsTargetMutex.py b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/TestSBMutexReflectsTargetMutex.py
new file mode 100644
index 0000000000000..781c8e3873406
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/TestSBMutexReflectsTargetMutex.py
@@ -0,0 +1,139 @@
+"""
+Test that a scripted frame provider calling SBTarget.GetAPIMutex() from
+get_frame_at_index gets a handle that reflects the state of the target's
+real, shared API mutex, even though the callback's own thread is exempt
+from having to serialize on it.
+
+SBMutex is meant to be obtainable inside a bypassed scripted callback and
+locked later, once that bypass no longer applies -- e.g. on a different
+thread with no scripted-extension call on its stack, as this test's own
+provider does (see sbmutex_frame_provider.py). So it must always alias
+the genuine target mutex rather than the thread-local mutex the bypass
+hands out for internal callers. This test drives the same
+kind of command-thread / internal-thread race as
+TestFrameProviderRegisterCommandAPIMutexDeadlock, interleaving `bt` with
+`continue` (hitting the same breakpoint again each time, via a loop in
+main.c) so get_frame_at_index runs many times instead of once.
+
+The provider obtains the mutex from inside get_frame_at_index (safe --
+obtaining a handle doesn't resolve or lock anything), but the actual
+try_lock() runs on a plain background thread it spawns for the check (see
+sbmutex_frame_provider.py for why: ScriptedPythonInterface::Dispatch pushes
+the API-mutex bypass policy for the callback's entire duration, so
+try_lock() on the callback's own thread would always resolve to the
+thread-local bypass mutex -- which no other thread could ever contend on,
+making the check meaningless regardless of what any other thread is doing
+to the real mutex). The background thread never had that policy pushed, so
+its try_lock() resolves to the real, shared mutex: if some other thread
+(e.g. the command thread running `bt`) happens to hold it at that moment,
+this correctly observes it as contended.
+
+Only try_lock() is used, which never blocks, so this cannot deadlock
+regardless of the outcome. An earlier version of this test tried to
+widen the race window by having the callback actually lock() and hold
+the mutex for a short duration, on the assumption that whichever thread
+reaches this callback already holds the real mutex first. That
+assumption is wrong -- LLDB's private state thread can reach this
+callback without already holding it -- so that held mutex.lock() call
+could genuinely block, and reproducibly deadlocked in practice. Do not
+reintroduce a blocking acquisition here.
+
+Observing contention is a genuine cross-thread race, so -- like the
+sibling runlock_reentrant_deadlock/was_hit_deadlock/
+register_command_api_mutex_deadlock tests -- this is best-effort: it
+raises the odds of witnessing it within a single invocation but cannot
+guarantee it, and the test does not require it to pass.
+"""
+
+import os
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class TestSBMutexReflectsTargetMutex(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_sbmutex_reflects_target_mutex(self):
+        """
+        Register a scripted frame provider that checks
+        target.GetAPIMutex().try_lock() from get_frame_at_index, then
+        repeatedly run `bt` and `continue` through RunCommandInterpreter.
+        Should complete without deadlocking, regardless of whether
+        contention is observed.
+        """
+        self.build()
+
+        lldbutil.run_to_name_breakpoint(self, "frame3")
+
+        provider_path = os.path.join(self.getSourceDir(), "sbmutex_frame_provider.py")
+        artifact_path = self.getBuildArtifact("contention.txt")
+        if os.path.exists(artifact_path):
+            os.remove(artifact_path)
+
+        commands = ["command script import " + provider_path]
+        commands.append(
+            "target frame-provider register "
+            "-C sbmutex_frame_provider.ContentionCheckFrameProvider "
+            "-k artifact_path -v " + artifact_path
+        )
+        # `bt` only re-invokes get_frame_at_index when the thread's stack
+        # frame list was invalidated by a new stop, so interleave `bt` with
+        # `continue` (hitting the same breakpoint again, in a loop in
+        # main.c) to get repeated fresh invocations, raising the odds of
+        # hitting the race within a single test invocation.
+        commands.extend(["bt", "continue"] * 20)
+        commands.append("quit")
+
+        stdin_path = self.getBuildArtifact("stdin.txt")
+        stdout_path = self.getBuildArtifact("stdout.txt")
+        with open(stdin_path, "w") as f:
+            f.write("\n".join(commands) + "\n")
+
+        with open(stdin_path, "r") as in_fileH, open(stdout_path, "w") as out_fileH:
+            in_sbf = lldb.SBFile(in_fileH.fileno(), "r", False)
+            out_sbf = lldb.SBFile(out_fileH.fileno(), "w", False)
+            self.assertSuccess(self.dbg.SetInputFile(in_sbf))
+            self.assertSuccess(self.dbg.SetOutputFile(out_sbf))
+            self.assertSuccess(self.dbg.SetErrorFile(out_sbf))
+
+            options = lldb.SBCommandInterpreterRunOptions()
+            options.SetEchoCommands(False)
+            options.SetPrintResults(True)
+            options.SetStopOnError(False)
+            options.SetStopOnCrash(False)
+
+            n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
+                True, False, options, 0, False, False
+            )
+
+        with open(stdout_path, "r") as out_fileH:
+            output = out_fileH.read()
+
+        self.assertFalse(has_crashed, "lldb should not have crashed")
+        self.assertTrue(quit_requested, "quit command should have been processed")
+        self.assertEqual(n_errors, 0, f"unexpected errors in output:\n{output}")
+        self.assertIn("successfully registered scripted frame provider", output)
+
+        self.assertTrue(
+            os.path.exists(artifact_path),
+            "get_frame_at_index should have run and recorded at least one outcome",
+        )
+        with open(artifact_path, "r") as f:
+            outcomes = [line.strip() for line in f if line.strip()]
+
+        self.assertTrue(outcomes, "expected at least one recorded outcome")
+        self.assertTrue(
+            all(
+                o
+                in (
+                    "another thread held the real target API mutex",
+                    "no other thread held the real target API mutex",
+                )
+                for o in outcomes
+            ),
+            f"unexpected outcome values: {outcomes}",
+        )
+        # Whether this specific outcome occurs is a race (see module
+        # docstring); not asserted on here.
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/hold_mutex_frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/hold_mutex_frame_provider.py
new file mode 100644
index 0000000000000..a7d2db5d567d0
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/hold_mutex_frame_provider.py
@@ -0,0 +1,35 @@
+"""
+Frame provider whose get_frame_at_index locks the target's real API
+mutex via SBMutex and holds it briefly, from inside the bypassed
+scripted-extension callback. See TestHoldMutexNoDeadlock.py for why this
+must not deadlock.
+"""
+
+import time
+
+from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider
+
+HOLD_DURATION_SECONDS = 0.2
+
+
+class HoldMutexFrameProvider(ScriptedFrameProvider):
+    @staticmethod
+    def get_description():
+        return (
+            "Provider that holds the real API mutex via SBMutex from get_frame_at_index"
+        )
+
+    def get_frame_at_index(self, index):
+        if index >= len(self.input_frames):
+            return None
+
+        if index == 0:
+            mutex = self.target.GetAPIMutex()
+            mutex.lock()
+            time.sleep(HOLD_DURATION_SECONDS)
+            mutex.unlock()
+
+        frame = self.input_frames[index]
+        if frame is None:
+            return None
+        return {"idx": index, "pc": frame.GetPC()}
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/main.c b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/main.c
new file mode 100644
index 0000000000000..ed95560986ac0
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/main.c
@@ -0,0 +1,12 @@
+int frame3() { return 3; }
+
+int frame2() { return frame3(); }
+
+int frame1() { return frame2(); }
+
+int main() {
+  int result = 0;
+  for (int i = 0; i < 25; ++i)
+    result += frame1();
+  return result;
+}
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/sbmutex_frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/sbmutex_frame_provider.py
new file mode 100644
index 0000000000000..bc35c717f3c17
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/sbmutex_reflects_target_mutex/sbmutex_frame_provider.py
@@ -0,0 +1,84 @@
+"""
+Frame provider whose get_frame_at_index checks whether the target's real
+API mutex is currently held by a different thread.
+
+Used by TestSBMutexReflectsTargetMutex.py to confirm that SBMutex
+(SBTarget::GetAPIMutex()) aliases the real, shared target mutex rather
+than the thread-local mutex the bypass hands out for internal callers.
+
+The check must NOT run directly on this thread while still inside
+get_frame_at_index: ScriptedPythonInterface::Dispatch pushes the
+can_bypass_target_api_mutex policy for its entire duration, which spans
+this whole Python call. Target::GetAPIMutex() re-checks that policy on
+every call, so try_lock() from here would always resolve to the
+thread-local bypass mutex -- which, being thread-local, can never be
+contended by another thread. That would make "another thread held it"
+unreachable and the check meaningless, regardless of what any other
+thread is actually doing to the real mutex.
+
+Instead, the check runs on a plain background thread, spawned fresh from
+here with no scripted-extension call on its stack. That thread never had
+the bypass policy pushed, so Target::GetAPIMutex() on it resolves to the
+real, shared mutex from the start -- exactly the mutex a concurrently
+running `bt` command (via CommandObjectParsed's eCommandTryTargetAPILock)
+may be holding at that moment.
+
+Only try_lock() is used, and it is never held beyond the immediate
+check: an earlier version of this provider held the mutex for a short
+duration to try to widen the race window, but that meant a genuinely
+blocking acquisition from whichever thread invoked this callback -- not
+every internal caller (e.g. the private state thread) already holds the
+real mutex by the time it gets here, so that held the mutex, which
+caused a real deadlock in practice. try_lock() never blocks, so this
+cannot deadlock regardless of the outcome.
+"""
+
+import threading
+
+from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider
+
+
+class ContentionCheckFrameProvider(ScriptedFrameProvider):
+    @staticmethod
+    def get_description():
+        return "Provider that checks SBMutex contention from a background thread"
+
+    def __init__(self, input_frames, args):
+        super().__init__(input_frames, args)
+        self.artifact_path = None
+        if self.args is not None:
+            value = self.args.GetValueForKey("artifact_path")
+            if value.IsValid():
+                self.artifact_path = value.GetStringValue(4096)
+
+    def _check_contention(self, mutex):
+        # Runs on a fresh thread with no scripted-extension call (and so no
+        # can_bypass_target_api_mutex) on its stack -- see module docstring.
+        if mutex.try_lock():
+            # Uncontended: nobody else holds the real mutex right now.
+            # Undo the lock we just took.
+            mutex.unlock()
+            outcome = "no other thread held the real target API mutex"
+        else:
+            outcome = "another thread held the real target API mutex"
+        with open(self.artifact_path, "a") as f:
+            f.write(outcome + "\n")
+
+    def get_frame_at_index(self, index):
+        if index >= len(self.input_frames):
+            return None
+
+        if index == 0 and self.artifact_path:
+            # Obtaining the mutex handle itself doesn't lock anything --
+            # it's safe to do from inside the bypassed callback. Only the
+            # actual try_lock() call, on the background thread, needs to
+            # happen outside the bypass.
+            mutex = self.target.GetAPIMutex()
+            checker = threading.Thread(target=self._check_contention, args=(mutex,))
+            checker.start()
+            checker.join()
+
+        frame = self.input_frames[index]
+        if frame is None:
+            return None
+        return {"idx": index, "pc": frame.GetPC()}
diff --git a/lldb/unittests/Target/APIMutexHandleTest.cpp b/lldb/unittests/Target/APIMutexHandleTest.cpp
new file mode 100644
index 0000000000000..8e421855b46ce
--- /dev/null
+++ b/lldb/unittests/Target/APIMutexHandleTest.cpp
@@ -0,0 +1,124 @@
+//===-- APIMutexHandleTest.cpp ---------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/Target.h"
+#include "Plugins/Platform/Linux/PlatformLinux.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/HostInfo.h"
+#include "lldb/Target/Platform.h"
+#include "lldb/Utility/ArchSpec.h"
+#include "lldb/Utility/Policy.h"
+#include "gtest/gtest.h"
+
+#include <thread>
+
+using namespace lldb_private;
+using namespace lldb;
+
+namespace {
+class APIMutexHandleTest : public ::testing::Test {
+public:
+  void SetUp() override {
+    FileSystem::Initialize();
+    HostInfo::Initialize();
+    platform_linux::PlatformLinux::Initialize();
+  }
+  void TearDown() override {
+    platform_linux::PlatformLinux::Terminate();
+    HostInfo::Terminate();
+    FileSystem::Terminate();
+  }
+};
+
+TargetSP CreateTarget() {
+  ArchSpec arch("x86_64-pc-linux");
+  Platform::SetHostPlatform(
+      platform_linux::PlatformLinux::CreateInstance(true, &arch));
+
+  DebuggerSP debugger_sp = Debugger::CreateInstance();
+  TargetSP target_sp;
+  PlatformSP platform_sp;
+  Status error = debugger_sp->GetTargetList().CreateTarget(
+      *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp);
+  return target_sp;
+}
+} // namespace
+
+TEST_F(APIMutexHandleTest, StandaloneLockUnlock) {
+  // Constructed without a Target: owns its own independent mutex.
+  APIMutexHandle handle;
+  handle.lock();
+  handle.unlock();
+  EXPECT_TRUE(handle.try_lock());
+  handle.unlock();
+}
+
+TEST_F(APIMutexHandleTest, ResolvesTargetMutex) {
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  APIMutexHandle handle(target_sp);
+  handle.lock();
+
+  // The real target mutex is held: a second, independent acquisition of
+  // the same target's mutex must not succeed from another thread.
+  std::thread t([target_sp]() {
+    TargetAPILock other_lock = target_sp->GetAPIMutex();
+    EXPECT_FALSE(other_lock.try_lock());
+  });
+  t.join();
+
+  handle.unlock();
+}
+
+TEST_F(APIMutexHandleTest,
+       UnlockReplaysLockResolutionAcrossPolicyChange) {
+  // Regression test for the cross-thread bypass bug: lock() and unlock()
+  // must agree on which mutex they touch even if the calling thread's
+  // policy changes in between, because unlock() replays lock()'s
+  // resolution rather than re-resolving GetAPIMutex() from the current
+  // policy.
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  APIMutexHandle handle(target_sp);
+
+  // lock() while not bypassed: resolves to the real target mutex.
+  handle.lock();
+
+  // Simulate the calling thread now running inside a scripted-extension
+  // callback -- if unlock() re-resolved GetAPIMutex() here, it would get
+  // the no-op bypass handle instead of the mutex it actually locked.
+  {
+    PolicyStack::Guard guard = PolicyStack::Get().PushScriptedExtensionCall();
+    handle.unlock();
+  }
+
+  // The real mutex must have actually been released: a fresh acquisition
+  // (outside the bypass policy) must succeed immediately.
+  TargetAPILock lock = target_sp->GetAPIMutex();
+  EXPECT_TRUE(lock.try_lock());
+}
+
+TEST_F(APIMutexHandleTest, BypassPolicyMakesTryLockANoOp) {
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  // Hold the real mutex on this "thread" first.
+  TargetAPILock outer_lock = target_sp->GetAPIMutex();
+  outer_lock.lock();
+
+  // A handle resolved while the bypass policy is active never touches the
+  // real (already-held) mutex, so it succeeds even though the real mutex
+  // is contended.
+  PolicyStack::Guard guard = PolicyStack::Get().PushScriptedExtensionCall();
+  APIMutexHandle handle(target_sp);
+  EXPECT_TRUE(handle.try_lock());
+  handle.unlock();
+}
diff --git a/lldb/unittests/Target/CMakeLists.txt b/lldb/unittests/Target/CMakeLists.txt
index 588d0e7db2885..cf772c5bce28a 100644
--- a/lldb/unittests/Target/CMakeLists.txt
+++ b/lldb/unittests/Target/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_lldb_unittest(TargetTests
   ABITest.cpp
+  APIMutexHandleTest.cpp
   DynamicRegisterInfoTest.cpp
   ExecutionContextTest.cpp
   LanguageTest.cpp
diff --git a/lldb/unittests/Utility/PolicyTest.cpp b/lldb/unittests/Utility/PolicyTest.cpp
index 5ad045a03d30b..56edfb68f6855 100644
--- a/lldb/unittests/Utility/PolicyTest.cpp
+++ b/lldb/unittests/Utility/PolicyTest.cpp
@@ -70,6 +70,17 @@ TEST(PolicyTest, PublicStateRunningExpression) {
   EXPECT_TRUE(p.capabilities.can_run_frame_recognizers);
 }
 
+TEST(PolicyTest, ScriptedExtensionCall) {
+  Policy p = Policy::CreateScriptedExtensionCall();
+  EXPECT_TRUE(p.capabilities.can_bypass_target_api_mutex);
+
+  // Inherits the current view/capabilities rather than resetting them.
+  PolicyStack::Guard guard = PolicyStack::Get().PushPrivateState();
+  Policy nested = Policy::CreateScriptedExtensionCall();
+  EXPECT_EQ(nested.view, Policy::View::Private);
+  EXPECT_TRUE(nested.capabilities.can_bypass_target_api_mutex);
+}
+
 TEST(PolicyTest, StackDefaultIsPublicState) {
   Policy current = PolicyStack::Get().Current();
   EXPECT_EQ(current.view, Policy::View::Public);
@@ -145,7 +156,8 @@ TEST(PolicyTest, DumpPublicState) {
   EXPECT_EQ(s.GetString(),
             "policy: view=public, capabilities={"
             "eval_expr=true run_all=true try_all=true "
-            "bp_actions=true frame_providers=true frame_recognizers=true}");
+            "bp_actions=true frame_providers=true frame_recognizers=true "
+            "bypass_api_mutex=false}");
 }
 
 TEST(PolicyTest, DumpPrivateState) {
@@ -154,7 +166,8 @@ TEST(PolicyTest, DumpPrivateState) {
   EXPECT_EQ(s.GetString(),
             "policy: view=private, capabilities={"
             "eval_expr=true run_all=true try_all=true "
-            "bp_actions=true frame_providers=true frame_recognizers=true}");
+            "bp_actions=true frame_providers=true frame_recognizers=true "
+            "bypass_api_mutex=false}");
 }
 
 TEST(PolicyTest, DumpStack) {



More information about the lldb-commits mailing list