[Lldb-commits] [lldb] [lldb] Introduce internal stop hooks (PR #164506)

Julian Lettner via lldb-commits lldb-commits at lists.llvm.org
Fri Oct 24 13:05:57 PDT 2025


https://github.com/yln updated https://github.com/llvm/llvm-project/pull/164506

>From 5f6caec8cbd40aa92233045e231ee791b6fba136 Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Tue, 21 Oct 2025 14:23:17 -0700
Subject: [PATCH 1/6] [lldb] Introduce internal stop hooks

Introduce the concept of internal stop hooks.
These are similar to LLDB's internal breakpoints:
LLDB itself will add them and users of LLDB will
not be able to add or remove them.

This change adds the following 3
independently-useful concepts:
* Maintain a list of internal stop hooks that will
  be populated by LLDB and cannot be added to or
  removed from by users.  They are managed in a
  separate list in
  `Target::m_internal_stop_hooks`.
* `StopHookKind:CodeBased` and `StopHookCoded`
  represent a stop hook defined by a C++ code
  callback (instead of command line expressions or
  a Python class).
* Stop hooks that do not print any output can now
  also suppress the printing of their header and
  description when they are hit via
  `StopHook::GetSuppressOutput`.

Combining these 3 concepts we can model "internal
stop hooks" which serve the same function as
LLDB's internal breakpoints: executing built-in,
LLDB-defined behavior, leveraging the existing
mechanism of stop hooks.

This change also simplifies
`Target::RunStopHooks`.  We already have to
materialize a new list for combining internal and
user stop hooks.  Filter and only add active hooks
to this list to avoid the need for "isActive?"
checks later on.
---
 lldb/include/lldb/Target/Target.h | 63 +++++++++++++++++++++++++++----
 lldb/source/Target/Target.cpp     | 60 +++++++++++++++++------------
 2 files changed, 91 insertions(+), 32 deletions(-)

diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index f4a09237ce897..e745cb93eae9a 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1356,7 +1356,11 @@ class Target : public std::enable_shared_from_this<Target>,
     StopHook(const StopHook &rhs);
     virtual ~StopHook() = default;
 
-    enum class StopHookKind  : uint32_t { CommandBased = 0, ScriptBased };
+    enum class StopHookKind : uint32_t {
+      CommandBased = 0,
+      ScriptBased,
+      CodeBased,
+    };
     enum class StopHookResult : uint32_t {
       KeepStopped = 0,
       RequestContinue,
@@ -1403,6 +1407,12 @@ class Target : public std::enable_shared_from_this<Target>,
 
     bool GetRunAtInitialStop() const { return m_at_initial_stop; }
 
+    void SetSuppressOutput(bool suppress_output) {
+      m_suppress_output = suppress_output;
+    }
+
+    bool GetSuppressOutput() const { return m_suppress_output; }
+
     void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
     virtual void GetSubclassDescription(Stream &s,
                                         lldb::DescriptionLevel level) const = 0;
@@ -1414,6 +1424,7 @@ class Target : public std::enable_shared_from_this<Target>,
     bool m_active = true;
     bool m_auto_continue = false;
     bool m_at_initial_stop = true;
+    bool m_suppress_output = false;
 
     StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
   };
@@ -1433,7 +1444,7 @@ class Target : public std::enable_shared_from_this<Target>,
 
   private:
     StringList m_commands;
-    // Use CreateStopHook to make a new empty stop hook. The GetCommandPointer
+    // Use CreateStopHook to make a new empty stop hook. The SetActionFromString
     // and fill it with commands, and SetSpecifier to set the specifier shared
     // pointer (can be null, that will match anything.)
     StopHookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
@@ -1460,19 +1471,56 @@ class Target : public std::enable_shared_from_this<Target>,
     StructuredDataImpl m_extra_args;
     lldb::ScriptedStopHookInterfaceSP m_interface_sp;
 
-    /// Use CreateStopHook to make a new empty stop hook. The GetCommandPointer
-    /// and fill it with commands, and SetSpecifier to set the specifier shared
-    /// pointer (can be null, that will match anything.)
+    /// Use CreateStopHook to make a new empty stop hook. Use SetScriptCallback
+    /// to set the script to execute, and SetSpecifier to set the specifier
+    /// shared pointer (can be null, that will match anything.)
     StopHookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
         : StopHook(target_sp, uid) {}
     friend class Target;
   };
 
+  class StopHookCoded : public StopHook {
+  public:
+    ~StopHookCoded() override = default;
+
+    using HandleStopCallback = StopHookResult(ExecutionContext &exc_ctx,
+                                              lldb::StreamSP output);
+
+    void SetCallback(llvm::StringRef name, HandleStopCallback *callback) {
+      m_name = name;
+      m_callback = callback;
+    }
+
+    StopHookResult HandleStop(ExecutionContext &exc_ctx,
+                              lldb::StreamSP output) override {
+      return m_callback(exc_ctx, output);
+    }
+
+    void GetSubclassDescription(Stream &s,
+                                lldb::DescriptionLevel level) const override {
+      s.Indent();
+      s.Printf("%s (built-in)\n", m_name.c_str());
+    }
+
+  private:
+    std::string m_name;
+    HandleStopCallback *m_callback;
+
+    /// Use CreateStopHook to make a new empty stop hook. Use SetCallback to set
+    /// the callback to execute, and SetSpecifier to set the specifier shared
+    /// pointer (can be null, that will match anything.)
+    StopHookCoded(lldb::TargetSP target_sp, lldb::user_id_t uid)
+        : StopHook(target_sp, uid) {}
+    friend class Target;
+  };
+
+  void RegisterInternalStopHooks();
+
   typedef std::shared_ptr<StopHook> StopHookSP;
 
   /// Add an empty stop hook to the Target's stop hook list, and returns a
-  /// shared pointer to it in new_hook. Returns the id of the new hook.
-  StopHookSP CreateStopHook(StopHook::StopHookKind kind);
+  /// shared pointer to it in new_hook.
+  StopHookSP CreateStopHook(StopHook::StopHookKind kind, bool internal = false);
 
   /// If you tried to create a stop hook, and that failed, call this to
   /// remove the stop hook, as it will also reset the stop hook counter.
@@ -1656,6 +1704,7 @@ class Target : public std::enable_shared_from_this<Target>,
   typedef std::map<lldb::user_id_t, StopHookSP> StopHookCollection;
   StopHookCollection m_stop_hooks;
   lldb::user_id_t m_stop_hook_next_id;
+  std::vector<StopHookSP> m_internal_stop_hooks;
   uint32_t m_latest_stop_hook_id; /// This records the last natural stop at
                                   /// which we ran a stop-hook.
   bool m_valid;
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index e224a12e33463..abfcee6e2c34b 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -183,8 +183,8 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch,
       m_watchpoint_list(), m_process_sp(), m_search_filter_sp(),
       m_image_search_paths(ImageSearchPathsChanged, this),
       m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0),
-      m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false),
-      m_is_dummy_target(is_dummy_target),
+      m_internal_stop_hooks(), m_latest_stop_hook_id(0), m_valid(true),
+      m_suppress_stop_hooks(false), m_is_dummy_target(is_dummy_target),
       m_target_unique_id(g_target_unique_id++),
       m_frame_recognizer_manager_up(
           std::make_unique<StackFrameRecognizerManager>()) {
@@ -217,6 +217,7 @@ Target::~Target() {
 void Target::PrimeFromDummyTarget(Target &target) {
   m_stop_hooks = target.m_stop_hooks;
   m_stop_hook_next_id = target.m_stop_hook_next_id;
+  m_internal_stop_hooks = target.m_internal_stop_hooks;
 
   for (const auto &breakpoint_sp : target.m_breakpoint_list.Breakpoints()) {
     if (breakpoint_sp->IsInternal())
@@ -383,6 +384,7 @@ void Target::Destroy() {
   m_image_search_paths.Clear(notify);
   m_stop_hooks.clear();
   m_stop_hook_next_id = 0;
+  m_internal_stop_hooks.clear();
   m_suppress_stop_hooks = false;
   m_repl_map.clear();
   Args signal_args;
@@ -3041,8 +3043,9 @@ SourceManager &Target::GetSourceManager() {
   return *m_source_manager_up;
 }
 
-Target::StopHookSP Target::CreateStopHook(StopHook::StopHookKind kind) {
-  lldb::user_id_t new_uid = ++m_stop_hook_next_id;
+Target::StopHookSP Target::CreateStopHook(StopHook::StopHookKind kind,
+                                          bool internal) {
+  user_id_t new_uid = (internal ? LLDB_INVALID_UID : ++m_stop_hook_next_id);
   Target::StopHookSP stop_hook_sp;
   switch (kind) {
   case StopHook::StopHookKind::CommandBased:
@@ -3051,8 +3054,14 @@ Target::StopHookSP Target::CreateStopHook(StopHook::StopHookKind kind) {
   case StopHook::StopHookKind::ScriptBased:
     stop_hook_sp.reset(new StopHookScripted(shared_from_this(), new_uid));
     break;
+  case StopHook::StopHookKind::CodeBased:
+    stop_hook_sp.reset(new StopHookCoded(shared_from_this(), new_uid));
+    break;
   }
-  m_stop_hooks[new_uid] = stop_hook_sp;
+  if (internal)
+    m_internal_stop_hooks.push_back(stop_hook_sp);
+  else
+    m_stop_hooks[new_uid] = stop_hook_sp;
   return stop_hook_sp;
 }
 
@@ -3111,16 +3120,20 @@ bool Target::RunStopHooks(bool at_initial_stop) {
   if (!(state == eStateStopped || state == eStateAttaching))
     return false;
 
-  if (m_stop_hooks.empty())
-    return false;
+  auto is_active = [at_initial_stop](StopHookSP hook) {
+    bool should_run_now = (!at_initial_stop || hook->GetRunAtInitialStop());
+    return hook->IsActive() && should_run_now;
+  };
 
-  bool no_active_hooks =
-      llvm::none_of(m_stop_hooks, [at_initial_stop](auto &p) {
-        bool should_run_now =
-            !at_initial_stop || p.second->GetRunAtInitialStop();
-        return p.second->IsActive() && should_run_now;
-      });
-  if (no_active_hooks)
+  // Create list of active internal and user stop hooks.
+  std::vector<StopHookSP> active_hooks;
+  llvm::copy_if(m_internal_stop_hooks, std::back_inserter(active_hooks),
+                is_active);
+  for (auto &[_, hook] : m_stop_hooks) {
+    if (is_active(hook))
+      active_hooks.push_back(hook);
+  }
+  if (active_hooks.empty())
     return false;
 
   // Make sure we check that we are not stopped because of us running a user
@@ -3169,24 +3182,21 @@ bool Target::RunStopHooks(bool at_initial_stop) {
   StreamSP output_sp = m_debugger.GetAsyncOutputStream();
   auto on_exit = llvm::make_scope_exit([output_sp] { output_sp->Flush(); });
 
-  bool print_hook_header = (m_stop_hooks.size() != 1);
-  bool print_thread_header = (num_exe_ctx != 1);
+  size_t num_hooks_with_output = llvm::count_if(
+      active_hooks, [](auto h) { return !h->GetSuppressOutput(); });
+  bool print_hook_header = (num_hooks_with_output > 1);
+  bool print_thread_header = (num_exe_ctx > 1);
   bool should_stop = false;
   bool requested_continue = false;
 
-  for (auto stop_entry : m_stop_hooks) {
-    StopHookSP cur_hook_sp = stop_entry.second;
-    if (!cur_hook_sp->IsActive())
-      continue;
-    if (at_initial_stop && !cur_hook_sp->GetRunAtInitialStop())
-      continue;
-
+  for (auto cur_hook_sp : active_hooks) {
     bool any_thread_matched = false;
     for (auto exc_ctx : exc_ctx_with_reasons) {
       if (!cur_hook_sp->ExecutionContextPasses(exc_ctx))
         continue;
 
-      if (print_hook_header && !any_thread_matched) {
+      bool suppress_output = cur_hook_sp->GetSuppressOutput();
+      if (print_hook_header && !any_thread_matched && !suppress_output) {
         StreamString s;
         cur_hook_sp->GetDescription(s, eDescriptionLevelBrief);
         if (s.GetSize() != 0)
@@ -3197,7 +3207,7 @@ bool Target::RunStopHooks(bool at_initial_stop) {
         any_thread_matched = true;
       }
 
-      if (print_thread_header)
+      if (print_thread_header && !suppress_output)
         output_sp->Printf("-- Thread %d\n",
                           exc_ctx.GetThreadPtr()->GetIndexID());
 

>From eae847921bc5491e91e95bc535a95981afd0c9f9 Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Tue, 21 Oct 2025 14:57:37 -0700
Subject: [PATCH 2/6] [NFC][lldb] Fix outdated comment

---
 lldb/include/lldb/Target/Target.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index e745cb93eae9a..29b2f6435118c 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1444,8 +1444,8 @@ class Target : public std::enable_shared_from_this<Target>,
 
   private:
     StringList m_commands;
-    // Use CreateStopHook to make a new empty stop hook. The SetActionFromString
-    // and fill it with commands, and SetSpecifier to set the specifier shared
+    // Use CreateStopHook to make a new empty stop hook. Use SetActionFromString
+    // to fill it with commands, and SetSpecifier to set the specifier shared
     // pointer (can be null, that will match anything.)
     StopHookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
         : StopHook(target_sp, uid) {}
@@ -1519,7 +1519,7 @@ class Target : public std::enable_shared_from_this<Target>,
   typedef std::shared_ptr<StopHook> StopHookSP;
 
   /// Add an empty stop hook to the Target's stop hook list, and returns a
-  /// shared pointer to it in new_hook.
+  /// shared pointer to the new hook.
   StopHookSP CreateStopHook(StopHook::StopHookKind kind, bool internal = false);
 
   /// If you tried to create a stop hook, and that failed, call this to

>From 3370a8243bc9bc9ced6f3bab2b0b1ef619c33329 Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Thu, 23 Oct 2025 13:58:25 -0700
Subject: [PATCH 3/6] [lldb] Add option to show internal stop hooks

Add option to the existing "print list of stop
hooks" command to show the internal stop hooks
instead:
```
target stop-hook list --internal
```
---
 lldb/include/lldb/Target/Target.h             |  4 ++
 .../Commands/CommandObjectBreakpoint.cpp      |  4 +-
 lldb/source/Commands/CommandObjectTarget.cpp  | 51 ++++++++++++++++++-
 lldb/source/Commands/Options.td               | 10 +++-
 4 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 29b2f6435118c..b59c5b5e84d47 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1552,6 +1552,10 @@ class Target : public std::enable_shared_from_this<Target>,
 
   void SetAllStopHooksActiveState(bool active_state);
 
+  const std::vector<StopHookSP> &GetInternalStopHooks() const {
+    return m_internal_stop_hooks;
+  }
+
   size_t GetNumStopHooks() const { return m_stop_hooks.size(); }
 
   StopHookSP GetStopHookAtIndex(size_t index) {
diff --git a/lldb/source/Commands/CommandObjectBreakpoint.cpp b/lldb/source/Commands/CommandObjectBreakpoint.cpp
index de0a7e7093411..5a5512610cd33 100644
--- a/lldb/source/Commands/CommandObjectBreakpoint.cpp
+++ b/lldb/source/Commands/CommandObjectBreakpoint.cpp
@@ -1114,9 +1114,7 @@ class CommandObjectBreakpointList : public CommandObjectParsed {
   CommandObjectBreakpointList(CommandInterpreter &interpreter)
       : CommandObjectParsed(
             interpreter, "breakpoint list",
-            "List some or all breakpoints at configurable levels of detail.",
-            nullptr) {
-    CommandArgumentData bp_id_arg;
+            "List some or all breakpoints at configurable levels of detail.") {
 
     // Define the first (and only) variant of this arg.
     AddSimpleArgumentList(eArgTypeBreakpointID, eArgRepeatOptional);
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index c59d02812f328..fa5a80ed7a6ed 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5223,19 +5223,65 @@ class CommandObjectTargetStopHookEnableDisable : public CommandObjectParsed {
 #pragma mark CommandObjectTargetStopHookList
 
 // CommandObjectTargetStopHookList
+#define LLDB_OPTIONS_target_stop_hook_list
+#include "CommandOptions.inc"
 
 class CommandObjectTargetStopHookList : public CommandObjectParsed {
 public:
   CommandObjectTargetStopHookList(CommandInterpreter &interpreter)
       : CommandObjectParsed(interpreter, "target stop-hook list",
-                            "List all stop-hooks.", "target stop-hook list") {}
+                            "List all stop-hooks.") {}
 
   ~CommandObjectTargetStopHookList() override = default;
 
+  Options *GetOptions() override { return &m_options; }
+
+  class CommandOptions : public Options {
+  public:
+    CommandOptions() = default;
+    ~CommandOptions() override = default;
+
+    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+                          ExecutionContext *execution_context) override {
+      Status error;
+      const int short_option = m_getopt_table[option_idx].val;
+
+      switch (short_option) {
+      case 'i':
+        m_internal = true;
+        break;
+      default:
+        llvm_unreachable("Unimplemented option");
+      }
+
+      return error;
+    }
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override {
+      m_internal = false;
+    }
+
+    llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+      return llvm::ArrayRef(g_target_stop_hook_list_options);
+    }
+
+    // Instance variables to hold the values for command options.
+    bool m_internal = false;
+  };
+
 protected:
   void DoExecute(Args &command, CommandReturnObject &result) override {
     Target &target = GetTarget();
 
+    if (m_options.m_internal) {
+      for (auto &hook : target.GetInternalStopHooks()) {
+        hook->GetDescription(result.GetOutputStream(), eDescriptionLevelFull);
+        result.GetOutputStream().PutCString("\n");
+      }
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+      return;
+    }
+
     size_t num_hooks = target.GetNumStopHooks();
     if (num_hooks == 0) {
       result.GetOutputStream().PutCString("No stop hooks.\n");
@@ -5250,6 +5296,9 @@ class CommandObjectTargetStopHookList : public CommandObjectParsed {
     }
     result.SetStatus(eReturnStatusSuccessFinishResult);
   }
+
+private:
+  CommandOptions m_options;
 };
 
 #pragma mark CommandObjectMultiwordTargetStopHooks
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index a9f054e1d3d45..ed061312e2bb4 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -77,7 +77,7 @@ let Command = "breakpoint list" in {
   // FIXME: We need to add an "internal" command, and then add this sort of
   // thing to it. But I need to see it for now, and don't want to wait.
   def blist_internal : Option<"internal", "i">,
-                       Desc<"Show debugger ${i}nternal breakpoints">;
+                       Desc<"Show debugger ${i}nternal breakpoints.">;
   def blist_brief : Option<"brief", "b">,
                     Group<1>,
                     Desc<"Give a ${b}rief description of the breakpoint (no "
@@ -1686,7 +1686,7 @@ let Command = "target modules lookup" in {
                                        "match, if a best match is available.">;
 }
 
-let Command = "target stop hook add" in {
+let Command = "target stop_hook add" in {
   def target_stop_hook_add_one_liner
       : Option<"one-liner", "o">,
         GroupRange<1, 3>,
@@ -1762,6 +1762,12 @@ let Command = "target stop hook add" in {
              "Defaults to true.">;
 }
 
+let Command = "target stop_hook list" in {
+  def target_stop_hook_list_internal
+      : Option<"internal", "i">,
+        Desc<"Show debugger ${i}nternal stop hooks.">;
+}
+
 let Command = "thread backtrace" in {
   def thread_backtrace_count : Option<"count", "c">,
                                Group<1>,

>From 9854d30d805e0f8c2786583b81344ea17869f0a0 Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Thu, 23 Oct 2025 16:22:44 -0700
Subject: [PATCH 4/6] [lldb] Unify printing of internal and user stop hooks

Unify the printing of internal and user
(non-internal) stop hooks to make it more similar
to how we treat it for breakpoints.

This means replacing the `GetNumStopHooks()` and
`GetStopHookAtIndex()` with a copy of the stop
hooks (in C++20, we can change this to a constant
view). These functions were ever only used to
iterate the entire collection of stop hooks and
the implementation of `GetStopHookAtIndex()` turns
this into a `N^2` operation.

This change also allows us to cleanup a bit of
code at the call site.
---
 lldb/include/lldb/Target/Target.h            | 18 +--------------
 lldb/source/Commands/CommandCompletions.cpp  |  4 +---
 lldb/source/Commands/CommandObjectTarget.cpp | 24 ++++++--------------
 lldb/source/Target/Target.cpp                | 17 ++++++++++++++
 4 files changed, 26 insertions(+), 37 deletions(-)

diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index b59c5b5e84d47..5e7ba3028385f 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1552,23 +1552,7 @@ class Target : public std::enable_shared_from_this<Target>,
 
   void SetAllStopHooksActiveState(bool active_state);
 
-  const std::vector<StopHookSP> &GetInternalStopHooks() const {
-    return m_internal_stop_hooks;
-  }
-
-  size_t GetNumStopHooks() const { return m_stop_hooks.size(); }
-
-  StopHookSP GetStopHookAtIndex(size_t index) {
-    if (index >= GetNumStopHooks())
-      return StopHookSP();
-    StopHookCollection::iterator pos = m_stop_hooks.begin();
-
-    while (index > 0) {
-      pos++;
-      index--;
-    }
-    return (*pos).second;
-  }
+  const std::vector<StopHookSP> GetStopHooks(bool internal = false) const;
 
   lldb::PlatformSP GetPlatform() { return m_platform_sp; }
 
diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp
index b2fc893e13fe3..c60d30326a3b4 100644
--- a/lldb/source/Commands/CommandCompletions.cpp
+++ b/lldb/source/Commands/CommandCompletions.cpp
@@ -777,13 +777,11 @@ void CommandCompletions::StopHookIDs(CommandInterpreter &interpreter,
   if (!target_sp)
     return;
 
-  const size_t num = target_sp->GetNumStopHooks();
-  for (size_t idx = 0; idx < num; ++idx) {
+  for (auto &stophook_sp : target_sp->GetStopHooks()) {
     StreamString strm;
     // The value 11 is an offset to make the completion description looks
     // neater.
     strm.SetIndentLevel(11);
-    const Target::StopHookSP stophook_sp = target_sp->GetStopHookAtIndex(idx);
     stophook_sp->GetDescription(strm, lldb::eDescriptionLevelInitial);
     request.TryCompleteCurrentArg(std::to_string(stophook_sp->GetID()),
                                   strm.GetString());
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index fa5a80ed7a6ed..8de6521e65b25 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5273,27 +5273,17 @@ class CommandObjectTargetStopHookList : public CommandObjectParsed {
   void DoExecute(Args &command, CommandReturnObject &result) override {
     Target &target = GetTarget();
 
-    if (m_options.m_internal) {
-      for (auto &hook : target.GetInternalStopHooks()) {
-        hook->GetDescription(result.GetOutputStream(), eDescriptionLevelFull);
+    bool printed_hook = false;
+    for (auto &hook : target.GetStopHooks(m_options.m_internal)) {
+      if (printed_hook)
         result.GetOutputStream().PutCString("\n");
-      }
-      result.SetStatus(eReturnStatusSuccessFinishResult);
-      return;
+      hook->GetDescription(result.GetOutputStream(), eDescriptionLevelFull);
+      printed_hook = true;
     }
 
-    size_t num_hooks = target.GetNumStopHooks();
-    if (num_hooks == 0) {
+    if (!printed_hook)
       result.GetOutputStream().PutCString("No stop hooks.\n");
-    } else {
-      for (size_t i = 0; i < num_hooks; i++) {
-        Target::StopHookSP this_hook = target.GetStopHookAtIndex(i);
-        if (i > 0)
-          result.GetOutputStream().PutCString("\n");
-        this_hook->GetDescription(result.GetOutputStream(),
-                                  eDescriptionLevelFull);
-      }
-    }
+
     result.SetStatus(eReturnStatusSuccessFinishResult);
   }
 
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index abfcee6e2c34b..d070c3d953d4a 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3107,6 +3107,23 @@ void Target::SetAllStopHooksActiveState(bool active_state) {
   }
 }
 
+// FIXME:  Ideally we would like to return a `const &` (const reference) instead
+//   of creating copy here, but that is not possible due to different container
+//   types.  In C++20, we should be able to use `std::ranges::views::values` to
+//   adapt the key-pair entries in the `std::map` (behind `StopHookCollection`)
+//   to avoid creating the copy.
+const std::vector<Target::StopHookSP>
+Target::GetStopHooks(bool internal) const {
+  if (internal)
+    return m_internal_stop_hooks;
+
+  std::vector<StopHookSP> stop_hooks;
+  for (auto &[_, hook] : m_stop_hooks)
+    stop_hooks.push_back(hook);
+
+  return stop_hooks;
+}
+
 bool Target::RunStopHooks(bool at_initial_stop) {
   if (m_suppress_stop_hooks)
     return false;

>From c84303eb7ae8555c472fbc5af00eda2831257943 Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Fri, 24 Oct 2025 12:23:35 -0700
Subject: [PATCH 5/6] [lldb] Remove unimplemented function

Remove unused and unimplemented function
`GetStopHookSize()` declared in `Target.h`.
---
 lldb/include/lldb/Target/Target.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 5e7ba3028385f..c375df248154f 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1532,8 +1532,6 @@ class Target : public std::enable_shared_from_this<Target>,
   // control over the process for the first time.
   bool RunStopHooks(bool at_initial_stop = false);
 
-  size_t GetStopHookSize();
-
   bool SetSuppresStopHooks(bool suppress) {
     bool old_value = m_suppress_stop_hooks;
     m_suppress_stop_hooks = suppress;

>From 27b1441d3e27a12a43363b373862c83238d16d59 Mon Sep 17 00:00:00 2001
From: Julian Lettner <jlettner at apple.com>
Date: Fri, 24 Oct 2025 12:57:33 -0700
Subject: [PATCH 6/6] [lldb] Add test for stop hook ordering

Add test for stop hook user ID assignment,
ordering, and printing.
---
 .../ExecControl/StopHook/stop-hook-list.test  | 70 +++++++++++++++++++
 1 file changed, 70 insertions(+)
 create mode 100644 lldb/test/Shell/ExecControl/StopHook/stop-hook-list.test

diff --git a/lldb/test/Shell/ExecControl/StopHook/stop-hook-list.test b/lldb/test/Shell/ExecControl/StopHook/stop-hook-list.test
new file mode 100644
index 0000000000000..42d0a67c60dfa
--- /dev/null
+++ b/lldb/test/Shell/ExecControl/StopHook/stop-hook-list.test
@@ -0,0 +1,70 @@
+# Test stop hook user ID assignment, ordering, and printing.
+#
+# RUN: %lldb -b -s %s | FileCheck %s
+
+# Create some stop hooks
+target stop-hook add -o 'print "Hello"'
+target stop-hook add -o 'print "world,"'
+target stop-hook add -o 'print "nice"'
+target stop-hook add -o 'print "weather"'
+target stop-hook add -o 'print "today!"'
+
+# Print hooks
+target stop-hook list
+
+# CHECK: (lldb) target stop-hook list
+# CHECK: Hook: 1
+# CHECK:   "Hello"
+# CHECK: Hook: 2
+# CHECK:   "world,"
+# CHECK: Hook: 3
+# CHECK:   "nice"
+# CHECK: Hook: 4
+# CHECK:   "weather"
+# CHECK: Hook: 5
+# CHECK:   "today!"
+
+# Delete last hook, then add new one
+target stop-hook delete 5
+target stop-hook add -o 'print "Sunshine,"'
+
+# Stop hook gets new user ID (it is not reused)
+# CHECK: (lldb) target stop-hook add -o 'print "Sunshine,"'
+# CHECK: Stop hook #6 added.
+
+target stop-hook list
+# CHECK: (lldb) target stop-hook list
+# CHECK:     Hook: 4
+# CHECK-NOT: Hook: 5
+# CHECK:     Hook: 6
+
+# Add a few more hooks
+target stop-hook add -o 'print "rain,"'
+target stop-hook add -o 'print "and wind!"'
+target stop-hook add -o 'print "It is all okay!"'
+# CHECK: Stop hook #7 added.
+# CHECK: Stop hook #8 added.
+# CHECK: Stop hook #9 added.
+
+# Delete a few hooks
+target stop-hook delete 1
+target stop-hook delete 3
+target stop-hook delete 7
+target stop-hook delete 9
+
+# Check that the list is still well-ordered
+target stop-hook list
+# CHECK: (lldb) target stop-hook list
+# CHECK-NOT: Hook: 1
+# CHECK:     Hook: 2
+# CHECK:       "world,"
+# CHECK-NOT: Hook: 3
+# CHECK:     Hook: 4
+# CHECK:       "weather"
+# CHECK-NOT: Hook: 5
+# CHECK:     Hook: 6
+# CHECK:       "Sunshine,"
+# CHECK-NOT: Hook: 7
+# CHECK:     Hook: 8
+# CHECK:       "and wind!"
+# CHECK-NOT: Hook: 9



More information about the lldb-commits mailing list