[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
Tue Aug 11 16:56:03 PDT 2026


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

>From 95c86032498cd662c095d07cf99467b8c5fd7776 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Mon, 10 Aug 2026 15:07:10 -0700
Subject: [PATCH 1/2] [lldb] Wrap Target::GetAPIMutex() into a Lockable handle
 (NFC)

While implementing #208242, we realized that we needed a Lockable
TargetAPIMutex wrapper that could skip the locking on re-entrant
threads (when a command (i.e `bt`) triggers scripted extension (i.e
`ScriptedFrameProvider`) that uses SBAPI (i.e. `SBFrame`), and still
behave as a normal mutex otherwise. However, since
`Target::GetAPIMutex()` returns a `std::recursive_mutex&`, that can't
represent "no synchronization at all".

This is why this PR introduces `TargetAPIMutex`, a small Lockable type
that behaves exactly like `std::recursive_mutex`, with no RAII of its
own, so callers can wrap it in `std::lock_guard`/`std::unique_lock`
like they would any other Lockable.

A `TargetAPIMutex` is bound to a `Target` rather than to a specific
mutex, so `lock()`/`try_lock()` resolve which real mutex to use fresh
on every call instead of caching one resolution for the handle's
lifetime. `unlock()` replays whatever the matching `lock()`/`try_lock()`
resolved rather than re-resolving, so a policy change between the two
calls can't release the wrong mutex. That's also what lets a handle be
constructed on one thread and locked/unlocked on another, which the
deferred-lock `SBMutex` needs.

`GetAPIMutex()` now returns `TargetAPIMutex` by value, so every caller
that took its old `std::recursive_mutex&` materializes the handle into
a local and wraps that in a `lock_guard`/`unique_lock` instead. A few
places where the lock outlives its constructing statement
(`CommandObject`'s `m_api_locker`, `ValueLocker`,
`StoppedExecutionContext`) keep the resolved handle as a member and
release it explicitly instead.

`SBMutex` hides its opaque member behind a forward-declared
`MutexVariant`, defined in the .cpp as a
`std::variant<std::recursive_mutex, TargetAPIMutex>`: the no-target
case still owns a real mutex directly, `SBMutex`'s own size doesn't
grow across the ABI boundary, and `std::variant` -- a C++17 feature --
never leaks into the public header, which must stay usable from a
C++11 client.

Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
 lldb/include/lldb/API/SBMutex.h               |   5 +-
 lldb/include/lldb/Interpreter/CommandObject.h |   3 +-
 lldb/include/lldb/Target/ExecutionContext.h   |  18 +-
 lldb/include/lldb/Target/Target.h             |   9 +-
 lldb/include/lldb/Target/TargetAPIMutex.h     |  68 ++++++
 lldb/include/lldb/ValueObject/ValueObject.h   |   8 +-
 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      |  30 ++-
 lldb/source/API/SBDebugger.cpp                |  12 +-
 lldb/source/API/SBFunction.cpp                |   6 +-
 lldb/source/API/SBInstruction.cpp             |  24 ++-
 lldb/source/API/SBMutex.cpp                   |  39 +++-
 lldb/source/API/SBProcess.cpp                 | 173 ++++++++--------
 lldb/source/API/SBSymbol.cpp                  |   6 +-
 lldb/source/API/SBTarget.cpp                  | 111 ++++++----
 lldb/source/API/SBThread.cpp                  |   3 +-
 lldb/source/API/SBValue.cpp                   |  10 +-
 lldb/source/API/SBWatchpoint.cpp              |  59 +++---
 lldb/source/Interpreter/CommandObject.cpp     |   7 +-
 lldb/source/Target/CMakeLists.txt             |   1 +
 lldb/source/Target/ExecutionContext.cpp       |  12 +-
 lldb/source/Target/Target.cpp                 |   9 +-
 lldb/source/Target/TargetAPIMutex.cpp         |  36 ++++
 lldb/source/ValueObject/ValueObject.cpp       |  10 +-
 lldb/tools/lldb-dap/ProtocolUtils.cpp         |   1 +
 lldb/unittests/Target/CMakeLists.txt          |   1 +
 lldb/unittests/Target/TargetAPIMutexTest.cpp  | 195 ++++++++++++++++++
 30 files changed, 807 insertions(+), 402 deletions(-)
 create mode 100644 lldb/include/lldb/Target/TargetAPIMutex.h
 create mode 100644 lldb/source/Target/TargetAPIMutex.cpp
 create mode 100644 lldb/unittests/Target/TargetAPIMutexTest.cpp

diff --git a/lldb/include/lldb/API/SBMutex.h b/lldb/include/lldb/API/SBMutex.h
index 826ad077f159f..856c648cf96ab 100644
--- a/lldb/include/lldb/API/SBMutex.h
+++ b/lldb/include/lldb/API/SBMutex.h
@@ -11,7 +11,7 @@
 
 #include "lldb/API/SBDefines.h"
 #include "lldb/lldb-forward.h"
-#include <mutex>
+#include <memory>
 
 namespace lldb {
 
@@ -41,7 +41,8 @@ class LLDB_API SBMutex {
   SBMutex(lldb::TargetSP target_sp);
   friend class SBTarget;
 
-  std::shared_ptr<std::recursive_mutex> m_opaque_sp;
+  class MutexVariant;
+  std::shared_ptr<MutexVariant> m_opaque_sp;
 };
 
 } // namespace lldb
diff --git a/lldb/include/lldb/Interpreter/CommandObject.h b/lldb/include/lldb/Interpreter/CommandObject.h
index 925377159d749..2b08db93c1f9c 100644
--- a/lldb/include/lldb/Interpreter/CommandObject.h
+++ b/lldb/include/lldb/Interpreter/CommandObject.h
@@ -410,7 +410,8 @@ 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;
+  TargetAPIMutex m_api_mutex;
+  std::unique_lock<TargetAPIMutex> 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..8288bf10ee9e0 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/TargetAPIMutex.h"
 #include "lldb/lldb-private.h"
 
 namespace lldb_private {
@@ -569,12 +570,13 @@ struct StoppedExecutionContext : ExecutionContext {
                           lldb::ProcessSP &process_sp,
                           lldb::ThreadSP &thread_sp,
                           lldb::StackFrameSP &frame_sp,
-                          std::unique_lock<std::recursive_mutex> api_lock,
+                          TargetAPIMutex api_mutex,
                           ProcessRunLock::ProcessRunLocker stop_locker)
-      : m_api_lock(std::move(api_lock)), m_stop_locker(std::move(stop_locker)) {
+      : m_api_mutex(std::move(api_mutex)),
+        m_api_locker(m_api_mutex, std::adopt_lock),
+        m_stop_locker(std::move(stop_locker)) {
     assert(target_sp);
     assert(process_sp);
-    assert(m_api_lock.owns_lock());
     assert(m_stop_locker.IsLocked());
     SetTargetSP(target_sp);
     SetProcessSP(process_sp);
@@ -587,18 +589,22 @@ struct StoppedExecutionContext : ExecutionContext {
   StoppedExecutionContext(StoppedExecutionContext &&other)
       : StoppedExecutionContext(other.m_target_sp, other.m_process_sp,
                                 other.m_thread_sp, other.m_frame_sp,
-                                std::move(other.m_api_lock),
+                                std::move(other.m_api_mutex),
                                 std::move(other.m_stop_locker)) {
+    // Ownership already transferred above; don't let `other`'s destructor
+    // unlock the mutex `this` now owns.
+    other.m_api_locker.release();
     other.Clear();
   }
 
   /// 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]] TargetAPIMutex AllowResume();
 
 private:
-  std::unique_lock<std::recursive_mutex> m_api_lock;
+  TargetAPIMutex m_api_mutex;
+  std::unique_lock<TargetAPIMutex> m_api_locker;
   ProcessRunLock::ProcessRunLocker m_stop_locker;
 };
 
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index a5247376cbdf5..71ba92e624404 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/TargetAPIMutex.h"
 #include "lldb/Target/ThreadSpec.h"
 #include "lldb/Utility/ArchSpec.h"
 #include "lldb/Utility/Broadcaster.h"
@@ -585,6 +587,7 @@ class Target : public std::enable_shared_from_this<Target>,
 public:
   friend class TargetList;
   friend class Debugger;
+  friend class TargetAPIMutex;
 
   /// Broadcaster event bits definitions.
   enum {
@@ -765,7 +768,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<TargetAPIMutex>/
+  /// std::unique_lock<TargetAPIMutex>) to actually acquire it.
+  TargetAPIMutex GetAPIMutex();
 
   void DeleteCurrentProcess();
 
diff --git a/lldb/include/lldb/Target/TargetAPIMutex.h b/lldb/include/lldb/Target/TargetAPIMutex.h
new file mode 100644
index 0000000000000..5cf54524d6a4d
--- /dev/null
+++ b/lldb/include/lldb/Target/TargetAPIMutex.h
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TARGETAPIMUTEX_H
+#define LLDB_TARGET_TARGETAPIMUTEX_H
+
+#include "lldb/lldb-forward.h"
+#include <memory>
+#include <mutex>
+
+namespace lldb_private {
+
+/// A Lockable handle over a Target's API mutex, returned by
+/// Target::GetAPIMutex() and backing the public lldb::SBMutex.
+///
+/// Behaves like std::recursive_mutex: lock()/try_lock()/unlock() drive
+/// the actual synchronization, with the same contract (unlock() without
+/// a matching successful lock()/try_lock() is caller error). It carries
+/// no RAII of its own; wrap it in std::lock_guard<TargetAPIMutex> or
+/// std::unique_lock<TargetAPIMutex> for scope-based locking, exactly as
+/// with any other Lockable.
+///
+/// A handle may be constructed on one thread and then locked/unlocked
+/// on a different one, so lock()/try_lock() (re-)resolve which real
+/// mutex to use fresh on every call, rather than caching a single
+/// resolution for the handle's lifetime. The matching unlock() replays
+/// the exact resolution that call 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).
+///
+/// Default-constructed (or moved-from) handles are a genuine no-op: no
+/// synchronization primitive is touched at all.
+class TargetAPIMutex {
+public:
+  TargetAPIMutex() = default;
+  explicit TargetAPIMutex(lldb::TargetSP target_sp)
+      : m_target_sp(std::move(target_sp)) {}
+
+  TargetAPIMutex(TargetAPIMutex &&other) noexcept = default;
+  TargetAPIMutex &operator=(TargetAPIMutex &&other) noexcept = default;
+
+  TargetAPIMutex(const TargetAPIMutex &) = delete;
+  TargetAPIMutex &operator=(const TargetAPIMutex &) = delete;
+
+  void lock();
+  bool try_lock();
+  void unlock() {
+    if (m_mutex)
+      m_mutex->unlock();
+  }
+
+private:
+  /// An aliasing shared_ptr into m_target_sp's own mutex, resolved fresh
+  /// on every lock()/try_lock() call. Null when this handle is a genuine
+  /// no-op.
+  std::shared_ptr<std::recursive_mutex> m_mutex;
+  lldb::TargetSP m_target_sp;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_TARGETAPIMUTEX_H
diff --git a/lldb/include/lldb/ValueObject/ValueObject.h b/lldb/include/lldb/ValueObject/ValueObject.h
index 6032e56ee5c6a..c370993d372b7 100644
--- a/lldb/include/lldb/ValueObject/ValueObject.h
+++ b/lldb/include/lldb/ValueObject/ValueObject.h
@@ -1290,7 +1290,8 @@ class ValueImpl {
   lldb::ValueObjectSP GetRootSP() { return m_valobj_sp; }
 
   lldb::ValueObjectSP GetSP(Process::StopLocker &stop_locker,
-                            std::unique_lock<std::recursive_mutex> &lock,
+                            TargetAPIMutex &api_mutex,
+                            std::unique_lock<TargetAPIMutex> &lock,
                             Status &error);
 
   void SetUseDynamic(lldb::DynamicValueType use_dynamic) {
@@ -1335,14 +1336,15 @@ class ValueLocker {
   ValueLocker() = default;
 
   lldb::ValueObjectSP GetLockedSP(ValueImpl &in_value) {
-    return in_value.GetSP(m_stop_locker, m_lock, m_lock_error);
+    return in_value.GetSP(m_stop_locker, m_api_mutex, m_lock, m_lock_error);
   }
 
   Status &GetError() { return m_lock_error; }
 
 private:
   Process::StopLocker m_stop_locker;
-  std::unique_lock<std::recursive_mutex> m_lock;
+  TargetAPIMutex m_api_mutex;
+  std::unique_lock<TargetAPIMutex> m_lock;
   Status m_lock_error;
 };
 
diff --git a/lldb/source/API/SBAddress.cpp b/lldb/source/API/SBAddress.cpp
index 78acc2e34564d..5015b6d81d732 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());
+      TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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..75d54cabfed22 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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = bkpt_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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..729721943ebdd 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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = loc_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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..f57a0918743ea 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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = m_impl_up->GetTarget()->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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..57c6ae375c0ac 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());
+      TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_lock);
       process_sp = target_sp->GetProcessSP();
       sb_process.SetSP(process_sp);
     }
@@ -472,9 +473,12 @@ 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());
+    TargetAPIMutex api_lock;
+    std::unique_lock<TargetAPIMutex> guard;
+    if (target_sp) {
+      api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+      guard = std::unique_lock<TargetAPIMutex>(api_lock);
+    }
     m_opaque_ptr->SourceInitFileGlobal(result.ref());
   } else {
     result->AppendError("SBCommandInterpreter is not valid");
@@ -495,9 +499,12 @@ 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());
+    TargetAPIMutex api_lock;
+    std::unique_lock<TargetAPIMutex> guard;
+    if (target_sp) {
+      api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+      guard = std::unique_lock<TargetAPIMutex>(api_lock);
+    }
     m_opaque_ptr->SourceInitFileHome(result.ref(), is_repl);
   } else {
     result->AppendError("SBCommandInterpreter is not valid");
@@ -511,9 +518,12 @@ 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());
+    TargetAPIMutex api_lock;
+    std::unique_lock<TargetAPIMutex> guard;
+    if (target_sp) {
+      api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+      guard = std::unique_lock<TargetAPIMutex>(api_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..71cd0f5841654 100644
--- a/lldb/source/API/SBDebugger.cpp
+++ b/lldb/source/API/SBDebugger.cpp
@@ -531,9 +531,12 @@ 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());
+    TargetAPIMutex api_lock;
+    std::unique_lock<TargetAPIMutex> guard;
+    if (target_sp) {
+      api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+      guard = std::unique_lock<TargetAPIMutex>(api_lock);
+    }
 
     SBCommandInterpreter sb_interpreter(GetCommandInterpreter());
     SBCommandReturnObject result;
@@ -606,7 +609,8 @@ void SBDebugger::HandleProcessEvent(const SBProcess &process,
   char stdio_buffer[1024];
   size_t len;
 
-  std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+  TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_lock);
 
   if (event_type &
       (Process::eBroadcastBitSTDOUT | Process::eBroadcastBitStateChanged)) {
diff --git a/lldb/source/API/SBFunction.cpp b/lldb/source/API/SBFunction.cpp
index c3cdba64417c8..2029069cdb3cb 100644
--- a/lldb/source/API/SBFunction.cpp
+++ b/lldb/source/API/SBFunction.cpp
@@ -129,10 +129,12 @@ SBInstructionList SBFunction::GetInstructions(SBTarget target,
   SBInstructionList sb_instructions;
   if (m_opaque_ptr) {
     TargetSP target_sp(target.GetSP());
-    std::unique_lock<std::recursive_mutex> lock;
+    TargetAPIMutex api_lock;
+    std::unique_lock<TargetAPIMutex> guard;
     ModuleSP module_sp(m_opaque_ptr->GetAddress().GetModule());
     if (target_sp && module_sp) {
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+      api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+      guard = std::unique_lock<TargetAPIMutex>(api_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..ad66ebec3abcf 100644
--- a/lldb/source/API/SBInstruction.cpp
+++ b/lldb/source/API/SBInstruction.cpp
@@ -117,9 +117,11 @@ const char *SBInstruction::GetMnemonic(SBTarget target) {
 
   ExecutionContext exe_ctx;
   TargetSP target_sp(target.GetSP());
-  std::unique_lock<std::recursive_mutex> lock;
+  TargetAPIMutex api_lock;
+  std::unique_lock<TargetAPIMutex> guard;
   if (target_sp) {
-    lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+    guard = std::unique_lock<TargetAPIMutex>(api_lock);
 
     target_sp->CalculateExecutionContext(exe_ctx);
     exe_ctx.SetProcessSP(target_sp->GetProcessSP());
@@ -136,9 +138,11 @@ const char *SBInstruction::GetOperands(SBTarget target) {
 
   ExecutionContext exe_ctx;
   TargetSP target_sp(target.GetSP());
-  std::unique_lock<std::recursive_mutex> lock;
+  TargetAPIMutex api_lock;
+  std::unique_lock<TargetAPIMutex> guard;
   if (target_sp) {
-    lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+    guard = std::unique_lock<TargetAPIMutex>(api_lock);
 
     target_sp->CalculateExecutionContext(exe_ctx);
     exe_ctx.SetProcessSP(target_sp->GetProcessSP());
@@ -155,9 +159,11 @@ const char *SBInstruction::GetComment(SBTarget target) {
 
   ExecutionContext exe_ctx;
   TargetSP target_sp(target.GetSP());
-  std::unique_lock<std::recursive_mutex> lock;
+  TargetAPIMutex api_lock;
+  std::unique_lock<TargetAPIMutex> guard;
   if (target_sp) {
-    lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+    api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+    guard = std::unique_lock<TargetAPIMutex>(api_lock);
 
     target_sp->CalculateExecutionContext(exe_ctx);
     exe_ctx.SetProcessSP(target_sp->GetProcessSP());
@@ -173,9 +179,11 @@ SBInstruction::GetControlFlowKind(lldb::SBTarget target) {
   if (inst_sp) {
     ExecutionContext exe_ctx;
     TargetSP target_sp(target.GetSP());
-    std::unique_lock<std::recursive_mutex> lock;
+    TargetAPIMutex api_lock;
+    std::unique_lock<TargetAPIMutex> guard;
     if (target_sp) {
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+      api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+      guard = std::unique_lock<TargetAPIMutex>(api_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..c28cf9374cb21 100644
--- a/lldb/source/API/SBMutex.cpp
+++ b/lldb/source/API/SBMutex.cpp
@@ -7,16 +7,41 @@
 //===----------------------------------------------------------------------===//
 
 #include "lldb/API/SBMutex.h"
-#include "lldb/Target/Target.h"
+#include "lldb/Target/TargetAPIMutex.h"
 #include "lldb/Utility/Instrumentation.h"
 #include "lldb/lldb-forward.h"
 #include <memory>
 #include <mutex>
+#include <variant>
 
 using namespace lldb;
 using namespace lldb_private;
 
-SBMutex::SBMutex() : m_opaque_sp(std::make_shared<std::recursive_mutex>()) {
+/// Holds either a standalone std::recursive_mutex (default-constructed
+/// SBMutex, no Target to resolve through) or a TargetAPIMutex (constructed
+/// from a Target). Kept out of SBMutex.h since std::variant is a C++17
+/// feature and the public SB headers must stay usable from a C++11 client.
+class SBMutex::MutexVariant {
+public:
+  MutexVariant() : m_variant(std::in_place_type<std::recursive_mutex>) {}
+  explicit MutexVariant(lldb::TargetSP target_sp)
+      : m_variant(std::in_place_type<TargetAPIMutex>, std::move(target_sp)) {}
+
+  void lock() {
+    std::visit([](auto &mutex) { mutex.lock(); }, m_variant);
+  }
+  void unlock() {
+    std::visit([](auto &mutex) { mutex.unlock(); }, m_variant);
+  }
+  bool try_lock() {
+    return std::visit([](auto &mutex) { return mutex.try_lock(); }, m_variant);
+  }
+
+private:
+  std::variant<std::recursive_mutex, TargetAPIMutex> m_variant;
+};
+
+SBMutex::SBMutex() : m_opaque_sp(std::make_shared<MutexVariant>()) {
   LLDB_INSTRUMENT_VA(this);
 }
 
@@ -32,8 +57,7 @@ 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<MutexVariant>(target_sp)) {
   LLDB_INSTRUMENT_VA(this, target_sp);
 }
 
@@ -62,8 +86,7 @@ void SBMutex::unlock() const {
 bool SBMutex::try_lock() const {
   LLDB_INSTRUMENT_VA(this);
 
-  if (m_opaque_sp)
-    return m_opaque_sp->try_lock();
-
-  return false;
+  if (!m_opaque_sp)
+    return false;
+  return m_opaque_sp->try_lock();
 }
diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp
index 08e39f754cf85..0984daa0e92b9 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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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());
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_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..25468f88c2de7 100644
--- a/lldb/source/API/SBSymbol.cpp
+++ b/lldb/source/API/SBSymbol.cpp
@@ -127,9 +127,11 @@ SBInstructionList SBSymbol::GetInstructions(SBTarget target,
   SBInstructionList sb_instructions;
   if (m_opaque_ptr) {
     TargetSP target_sp(target.GetSP());
-    std::unique_lock<std::recursive_mutex> lock;
+    TargetAPIMutex api_lock;
+    std::unique_lock<TargetAPIMutex> guard;
     if (target_sp && m_opaque_ptr->ValueIsAddress()) {
-      lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+      api_lock = TargetAPIMutex(target_sp->GetAPIMutex());
+      guard = std::unique_lock<TargetAPIMutex>(api_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..b6ee17091ef6b 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());
+  TargetAPIMutex api_lock = target.GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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..90cd0fe447540 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -464,7 +464,8 @@ 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();
+  TargetAPIMutex api_mutex = exe_ctx.AllowResume();
+  std::lock_guard<TargetAPIMutex> guard(api_mutex, std::adopt_lock);
   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..f76e923595544 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());
+  TargetAPIMutex api_lock = target_sp->GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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..065c90e997088 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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = target.GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+  TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+  std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_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());
+    TargetAPIMutex api_lock = watchpoint_sp->GetTarget().GetAPIMutex();
+    std::lock_guard<TargetAPIMutex> guard(api_lock);
 
     return watchpoint_sp->WatchpointWrite() ||
            watchpoint_sp->WatchpointModify();
diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp
index aa0d4cc58d0ae..efa02e60a23e2 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_mutex = target->GetAPIMutex();
+        m_api_locker = std::unique_lock<TargetAPIMutex>(m_api_mutex);
+      }
     }
   }
 
diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt
index 90fe6fc3ad973..2874394c400aa 100644
--- a/lldb/source/Target/CMakeLists.txt
+++ b/lldb/source/Target/CMakeLists.txt
@@ -56,6 +56,7 @@ add_lldb_library(lldbTarget
   StructuredDataPlugin.cpp
   SystemRuntime.cpp
   Target.cpp
+  TargetAPIMutex.cpp
   TargetList.cpp
   Thread.cpp
   ThreadCollection.cpp
diff --git a/lldb/source/Target/ExecutionContext.cpp b/lldb/source/Target/ExecutionContext.cpp
index e4b2f07d8d8d1..e26faa4cd1db1 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_mutex = target_sp->GetAPIMutex();
+  std::unique_lock<TargetAPIMutex> api_locker(api_mutex);
 
   auto process_sp = exe_ctx_ref_ptr->GetProcessSP();
   if (!process_sp)
@@ -169,14 +169,16 @@ lldb_private::GetStoppedExecutionContext(
         exe_ctx_ref_ptr->m_frame_list_id->second);
   }
 
+  api_locker.release();
   return StoppedExecutionContext(target_sp, process_sp, thread_sp, frame_sp,
-                                 std::move(api_lock), std::move(stop_locker));
+                                 std::move(api_mutex), std::move(stop_locker));
 }
 
-std::unique_lock<std::recursive_mutex> StoppedExecutionContext::AllowResume() {
+TargetAPIMutex StoppedExecutionContext::AllowResume() {
   Clear();
   m_stop_locker = ProcessRunLock::ProcessRunLocker();
-  return std::move(m_api_lock);
+  m_api_locker.release();
+  return std::move(m_api_mutex);
 }
 
 ExecutionContext::ExecutionContext(ExecutionContextScope *exe_scope_ptr)
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 6d4e200ea2241..ca42d027f4c22 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -66,7 +66,6 @@
 #include "lldb/Utility/LLDBAssert.h"
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
-#include "lldb/Utility/Policy.h"
 #include "lldb/Utility/RealpathPrefixes.h"
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/StreamString.h"
@@ -6020,12 +6019,8 @@ Target::TargetEventData::GetModuleListFromEvent(const Event *event_ptr) {
   return module_list;
 }
 
-std::recursive_mutex &Target::GetAPIMutex() {
-  Policy policy = PolicyStack::Get().Current();
-  if (policy.view == Policy::View::Private)
-    return m_private_mutex;
-
-  return m_mutex;
+TargetAPIMutex Target::GetAPIMutex() {
+  return TargetAPIMutex(shared_from_this());
 }
 
 /// Get metrics associated with this target in JSON format.
diff --git a/lldb/source/Target/TargetAPIMutex.cpp b/lldb/source/Target/TargetAPIMutex.cpp
new file mode 100644
index 0000000000000..26079c540ee2a
--- /dev/null
+++ b/lldb/source/Target/TargetAPIMutex.cpp
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/TargetAPIMutex.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Utility/Policy.h"
+
+using namespace lldb_private;
+
+void TargetAPIMutex::lock() {
+  if (m_target_sp) {
+    Policy policy = PolicyStack::Get().Current();
+    std::recursive_mutex &real_mutex = policy.view == Policy::View::Private
+                                           ? m_target_sp->m_private_mutex
+                                           : m_target_sp->m_mutex;
+    m_mutex = std::shared_ptr<std::recursive_mutex>(m_target_sp, &real_mutex);
+  }
+  if (m_mutex)
+    m_mutex->lock();
+}
+
+bool TargetAPIMutex::try_lock() {
+  if (m_target_sp) {
+    Policy policy = PolicyStack::Get().Current();
+    std::recursive_mutex &real_mutex = policy.view == Policy::View::Private
+                                           ? m_target_sp->m_private_mutex
+                                           : m_target_sp->m_mutex;
+    m_mutex = std::shared_ptr<std::recursive_mutex>(m_target_sp, &real_mutex);
+  }
+  return m_mutex ? m_mutex->try_lock() : true;
+}
diff --git a/lldb/source/ValueObject/ValueObject.cpp b/lldb/source/ValueObject/ValueObject.cpp
index b36a020d262c4..a0226108e378d 100644
--- a/lldb/source/ValueObject/ValueObject.cpp
+++ b/lldb/source/ValueObject/ValueObject.cpp
@@ -3851,9 +3851,10 @@ 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,
+                                     TargetAPIMutex &api_mutex,
+                                     std::unique_lock<TargetAPIMutex> &lock,
+                                     Status &error) {
   if (!m_valobj_sp) {
     error = Status::FromErrorString("invalid value object");
     return m_valobj_sp;
@@ -3869,7 +3870,8 @@ ValueImpl::GetSP(Process::StopLocker &stop_locker,
   if (!target)
     return ValueObjectSP();
 
-  lock = std::unique_lock<std::recursive_mutex>(target->GetAPIMutex());
+  api_mutex = target->GetAPIMutex();
+  lock = std::unique_lock<TargetAPIMutex>(api_mutex);
 
   ProcessSP process_sp(value_sp->GetProcessSP());
   if (process_sp && !stop_locker.TryLock(&process_sp->GetRunLock())) {
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp
index fb27859c5726f..d7d583a318d3a 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.cpp
+++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp
@@ -21,6 +21,7 @@
 #include "lldb/Host/PosixApi.h" // Adds PATH_MAX for windows
 
 #include <iomanip>
+#include <mutex>
 #include <optional>
 #include <sstream>
 
diff --git a/lldb/unittests/Target/CMakeLists.txt b/lldb/unittests/Target/CMakeLists.txt
index b12c781cf5789..674e0f1b3d407 100644
--- a/lldb/unittests/Target/CMakeLists.txt
+++ b/lldb/unittests/Target/CMakeLists.txt
@@ -13,6 +13,7 @@ add_lldb_unittest(TargetTests
   ScratchTypeSystemTest.cpp
   StackFrameRecognizerTest.cpp
   SummaryStatisticsTest.cpp
+  TargetAPIMutexTest.cpp
   FindFileTest.cpp
 
   LINK_COMPONENTS
diff --git a/lldb/unittests/Target/TargetAPIMutexTest.cpp b/lldb/unittests/Target/TargetAPIMutexTest.cpp
new file mode 100644
index 0000000000000..2650723ec6f4e
--- /dev/null
+++ b/lldb/unittests/Target/TargetAPIMutexTest.cpp
@@ -0,0 +1,195 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/TargetAPIMutex.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/Target/Target.h"
+#include "lldb/Utility/ArchSpec.h"
+#include "gtest/gtest.h"
+
+#include <thread>
+
+using namespace lldb_private;
+using namespace lldb;
+
+TEST(TargetAPIMutexTest, 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.
+  TargetAPIMutex lock;
+  EXPECT_TRUE(lock.try_lock());
+  lock.lock();
+  lock.unlock();
+  lock.lock();
+  lock.unlock();
+}
+
+namespace {
+class TargetAPIMutexTargetTest : 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(TargetAPIMutexTargetTest, WrapsTheTargetMutex) {
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  TargetAPIMutex lock(target_sp);
+  lock.lock();
+
+  // Recursive reentrancy is delegated straight to the underlying
+  // std::recursive_mutex: a second handle over the same target, locked
+  // from the same thread, must not block.
+  TargetAPIMutex second_lock(target_sp);
+  EXPECT_TRUE(second_lock.try_lock());
+  second_lock.unlock();
+
+  lock.unlock();
+
+  std::thread t([target_sp]() {
+    TargetAPIMutex background_lock(target_sp);
+    EXPECT_TRUE(background_lock.try_lock());
+    background_lock.unlock();
+  });
+  t.join();
+}
+
+TEST_F(TargetAPIMutexTargetTest, RealMutexBlocksOtherThreads) {
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  TargetAPIMutex lock(target_sp);
+  lock.lock();
+
+  std::thread t([target_sp]() {
+    TargetAPIMutex background_lock(target_sp);
+    EXPECT_FALSE(background_lock.try_lock());
+  });
+  t.join();
+
+  lock.unlock();
+}
+
+TEST_F(TargetAPIMutexTargetTest, BareHandleDoesNotAutoUnlockOnDestruction) {
+  // TargetAPIMutex carries no RAII of its own -- exactly like
+  // std::recursive_mutex, a bare handle going out of scope without an
+  // explicit unlock() leaves the real mutex held. Callers that want
+  // scope-based release must wrap it in std::lock_guard/std::unique_lock.
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  {
+    TargetAPIMutex lock(target_sp);
+    lock.lock();
+  }
+
+  std::thread t([target_sp]() {
+    TargetAPIMutex background_lock(target_sp);
+    EXPECT_FALSE(background_lock.try_lock());
+  });
+  t.join();
+}
+
+TEST_F(TargetAPIMutexTargetTest, LockGuardReleasesOnScopeExit) {
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  {
+    TargetAPIMutex lock(target_sp);
+    std::lock_guard<TargetAPIMutex> guard(lock);
+  }
+
+  std::thread t([target_sp]() {
+    TargetAPIMutex background_lock(target_sp);
+    EXPECT_TRUE(background_lock.try_lock());
+    background_lock.unlock();
+  });
+  t.join();
+}
+
+TEST_F(TargetAPIMutexTargetTest, MoveTransfersOwnership) {
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  TargetAPIMutex lock(target_sp);
+  lock.lock();
+
+  TargetAPIMutex moved(std::move(lock));
+
+  // The moved-from handle no longer references the real mutex: unlocking
+  // it is a no-op, so the mutex stays held until `moved` releases it.
+  lock.unlock();
+  std::thread contended([target_sp]() {
+    TargetAPIMutex background_lock(target_sp);
+    EXPECT_FALSE(background_lock.try_lock());
+  });
+  contended.join();
+
+  moved.unlock();
+  std::thread released([target_sp]() {
+    TargetAPIMutex background_lock(target_sp);
+    EXPECT_TRUE(background_lock.try_lock());
+    background_lock.unlock();
+  });
+  released.join();
+}
+
+TEST_F(TargetAPIMutexTargetTest, ResolvesFreshOnEachLockCall) {
+  // lock()/try_lock() re-resolve the real mutex on every call rather
+  // than caching a single resolution for the handle's lifetime: a
+  // handle can be locked, unlocked, and locked again, each time
+  // correctly contending with other threads for the same target mutex.
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  TargetAPIMutex lock(target_sp);
+  lock.lock();
+  lock.unlock();
+
+  std::thread t([target_sp]() {
+    TargetAPIMutex background_lock(target_sp);
+    background_lock.lock();
+    background_lock.unlock();
+  });
+  t.join();
+
+  lock.lock();
+  std::thread contended([target_sp]() {
+    TargetAPIMutex background_lock(target_sp);
+    EXPECT_FALSE(background_lock.try_lock());
+  });
+  contended.join();
+  lock.unlock();
+}

>From 44441ad1a3508a30c4f99c2208817f83389b10a8 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Thu, 6 Aug 2026 15:06:56 -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.

TargetAPILock::lock()/try_lock() check this capability when resolving
which mutex to use: when the current thread's policy says it can
bypass, they leave the handle pointing at nothing -- 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.

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's per-call
resolve/replay behavior that makes this bypass safe to observe through
a deferred-lock SBMutex.

Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
 lldb/include/lldb/Utility/Policy.h            |  11 ++
 .../Interfaces/ScriptedPythonInterface.h      |   7 +
 lldb/source/Target/TargetAPIMutex.cpp         |  24 ++-
 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/TargetAPIMutexTest.cpp  |  42 ++++++
 lldb/unittests/Utility/PolicyTest.cpp         |  17 ++-
 16 files changed, 582 insertions(+), 10 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

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/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index ce48f2468d380..9ad4d4e40cecf 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"
@@ -413,6 +414,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);
 
@@ -536,6 +540,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/TargetAPIMutex.cpp b/lldb/source/Target/TargetAPIMutex.cpp
index 26079c540ee2a..c07ecedfbae82 100644
--- a/lldb/source/Target/TargetAPIMutex.cpp
+++ b/lldb/source/Target/TargetAPIMutex.cpp
@@ -15,10 +15,14 @@ using namespace lldb_private;
 void TargetAPIMutex::lock() {
   if (m_target_sp) {
     Policy policy = PolicyStack::Get().Current();
-    std::recursive_mutex &real_mutex = policy.view == Policy::View::Private
-                                           ? m_target_sp->m_private_mutex
-                                           : m_target_sp->m_mutex;
-    m_mutex = std::shared_ptr<std::recursive_mutex>(m_target_sp, &real_mutex);
+    if (policy.capabilities.can_bypass_target_api_mutex) {
+      m_mutex = nullptr; // Genuine no-op.
+    } else {
+      std::recursive_mutex &real_mutex = policy.view == Policy::View::Private
+                                             ? m_target_sp->m_private_mutex
+                                             : m_target_sp->m_mutex;
+      m_mutex = std::shared_ptr<std::recursive_mutex>(m_target_sp, &real_mutex);
+    }
   }
   if (m_mutex)
     m_mutex->lock();
@@ -27,10 +31,14 @@ void TargetAPIMutex::lock() {
 bool TargetAPIMutex::try_lock() {
   if (m_target_sp) {
     Policy policy = PolicyStack::Get().Current();
-    std::recursive_mutex &real_mutex = policy.view == Policy::View::Private
-                                           ? m_target_sp->m_private_mutex
-                                           : m_target_sp->m_mutex;
-    m_mutex = std::shared_ptr<std::recursive_mutex>(m_target_sp, &real_mutex);
+    if (policy.capabilities.can_bypass_target_api_mutex) {
+      m_mutex = nullptr; // Genuine no-op.
+    } else {
+      std::recursive_mutex &real_mutex = policy.view == Policy::View::Private
+                                             ? m_target_sp->m_private_mutex
+                                             : m_target_sp->m_mutex;
+      m_mutex = std::shared_ptr<std::recursive_mutex>(m_target_sp, &real_mutex);
+    }
   }
   return m_mutex ? m_mutex->try_lock() : true;
 }
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/TargetAPIMutexTest.cpp b/lldb/unittests/Target/TargetAPIMutexTest.cpp
index 2650723ec6f4e..f2a6c957ea1f5 100644
--- a/lldb/unittests/Target/TargetAPIMutexTest.cpp
+++ b/lldb/unittests/Target/TargetAPIMutexTest.cpp
@@ -14,6 +14,7 @@
 #include "lldb/Target/Platform.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/ArchSpec.h"
+#include "lldb/Utility/Policy.h"
 #include "gtest/gtest.h"
 
 #include <thread>
@@ -193,3 +194,44 @@ TEST_F(TargetAPIMutexTargetTest, ResolvesFreshOnEachLockCall) {
   contended.join();
   lock.unlock();
 }
+
+TEST_F(TargetAPILockTargetTest, 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 from the current policy.
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  TargetAPILock lock(target_sp);
+  lock.lock();
+
+  // Simulate the calling thread now running inside a scripted-extension
+  // callback -- if unlock() re-resolved here, it would see the bypass
+  // and skip releasing the mutex it actually locked.
+  {
+    PolicyStack::Guard guard = PolicyStack::Get().PushScriptedExtensionCall();
+    lock.unlock();
+  }
+
+  // The real mutex must have actually been released: a fresh
+  // acquisition (outside the bypass policy) must succeed immediately.
+  TargetAPILock other_lock(target_sp);
+  EXPECT_TRUE(other_lock.try_lock());
+}
+
+TEST_F(TargetAPILockTargetTest, BypassPolicyMakesTryLockANoOp) {
+  TargetSP target_sp = CreateTarget();
+  ASSERT_TRUE(target_sp);
+
+  TargetAPILock outer_lock(target_sp);
+  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();
+  TargetAPILock lock(target_sp);
+  EXPECT_TRUE(lock.try_lock());
+  lock.unlock();
+}
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