[Lldb-commits] [lldb] [LLDB] Add module hook implementation (PR #185465)
Bar Soloveychik via lldb-commits
lldb-commits at lists.llvm.org
Wed Apr 8 15:50:11 PDT 2026
https://github.com/barsolo2000 updated https://github.com/llvm/llvm-project/pull/185465
>From 70a47a2eeef8246c849a114ef47ff0efe4d17300 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Mon, 9 Mar 2026 10:24:52 -0700
Subject: [PATCH 01/12] [LLDB] Add module hook
---
.../Interfaces/ScriptedModuleHookInterface.h | 35 ++
.../lldb/Interpreter/ScriptInterpreter.h | 5 +
lldb/include/lldb/Target/Target.h | 104 ++++++
lldb/include/lldb/lldb-forward.h | 3 +
lldb/source/Commands/CommandObjectTarget.cpp | 328 ++++++++++++++++++
lldb/source/Commands/Options.td | 11 +
.../ScriptInterpreter/Python/CMakeLists.txt | 1 +
.../ScriptInterpreterPythonInterfaces.cpp | 2 +
.../ScriptInterpreterPythonInterfaces.h | 1 +
.../ScriptedModuleHookPythonInterface.cpp | 64 ++++
.../ScriptedModuleHookPythonInterface.h | 52 +++
.../Python/ScriptInterpreterPython.cpp | 5 +
.../Python/ScriptInterpreterPythonImpl.h | 3 +
lldb/source/Target/Target.cpp | 269 ++++++++++++++
.../delete/TestTargetModuleHookDelete.py | 19 +
.../disable/TestTargetModuleHookDisable.py | 19 +
.../enable/TestTargetModuleHookEnable.py | 19 +
.../Commands/command-module-hook-fire.test | 10 +
.../Shell/Commands/command-module-hook.test | 113 ++++++
19 files changed, 1063 insertions(+)
create mode 100644 lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
create mode 100644 lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
create mode 100644 lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
create mode 100644 lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
create mode 100644 lldb/test/Shell/Commands/command-module-hook-fire.test
create mode 100644 lldb/test/Shell/Commands/command-module-hook.test
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
new file mode 100644
index 0000000000000..7a06349eb9161
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
@@ -0,0 +1,35 @@
+//===-- ScriptedModuleHookInterface.h ----------------------------*- C++
+//-*-===//
+//
+// 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_INTERPRETER_INTERFACES_SCRIPTEDMODULEHOOKINTERFACE_H
+#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDMODULEHOOKINTERFACE_H
+
+#include "lldb/lldb-private.h"
+
+#include "ScriptedInterface.h"
+
+namespace lldb_private {
+class ScriptedModuleHookInterface : public ScriptedInterface {
+public:
+ virtual llvm::Expected<StructuredData::GenericSP>
+ CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
+ const StructuredDataImpl &args_sp) = 0;
+
+ /// Called when modules are loaded into the target. Unlike stop hooks,
+ /// module hooks do not control whether the process should stop.
+ virtual void HandleModuleLoaded(lldb::StreamSP &output_sp) {}
+
+ /// Called when modules are unloaded from the target. Optional for
+ /// scripted hooks; if not implemented, the hook silently does nothing
+ /// on unload.
+ virtual void HandleModuleUnloaded(lldb::StreamSP &output_sp) {}
+};
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDMODULEHOOKINTERFACE_H
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index dac74cc8ab1da..7838ad5e0adbf 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -562,6 +562,11 @@ class ScriptInterpreter : public PluginInterface {
return {};
}
+ virtual lldb::ScriptedModuleHookInterfaceSP
+ CreateScriptedModuleHookInterface() {
+ return {};
+ }
+
virtual lldb::ScriptedBreakpointInterfaceSP
CreateScriptedBreakpointInterface() {
return {};
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 67f373aa5a325..ae758d3875c0b 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1657,6 +1657,106 @@ class Target : public std::enable_shared_from_this<Target>,
typedef std::shared_ptr<StopHook> StopHookSP;
+ // Target Module Hooks
+ //
+ // Module hooks fire whenever modules are loaded or unloaded from the
+ // target (via ModulesDidLoad / ModulesDidUnload).
+ class ModuleHook : public UserID {
+ public:
+ ModuleHook(const ModuleHook &rhs);
+ virtual ~ModuleHook() = default;
+
+ enum class ModuleHookKind : uint32_t { CommandBased = 0, ScriptBased };
+
+ lldb::TargetSP &GetTarget() { return m_target_sp; }
+
+ bool IsActive() { return m_active; }
+ void SetIsActive(bool is_active) { m_active = is_active; }
+
+ bool GetFireOnUnload() const { return m_fire_on_unload; }
+ void SetFireOnUnload(bool fire) { m_fire_on_unload = fire; }
+
+ void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
+ virtual void GetSubclassDescription(Stream &s,
+ lldb::DescriptionLevel level) const = 0;
+
+ virtual void HandleModuleLoaded(lldb::StreamSP output) = 0;
+ virtual void HandleModuleUnloaded(lldb::StreamSP output) = 0;
+
+ protected:
+ lldb::TargetSP m_target_sp;
+ bool m_active = true;
+ bool m_fire_on_unload = false;
+
+ ModuleHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
+ };
+
+ class ModuleHookCommandLine : public ModuleHook {
+ public:
+ ~ModuleHookCommandLine() override = default;
+
+ StringList &GetCommands() { return m_commands; }
+ void SetActionFromString(const std::string &string);
+ void SetActionFromStrings(const std::vector<std::string> &strings);
+
+ void HandleModuleLoaded(lldb::StreamSP output) override;
+ void HandleModuleUnloaded(lldb::StreamSP output) override;
+ void GetSubclassDescription(Stream &s,
+ lldb::DescriptionLevel level) const override;
+
+ private:
+ StringList m_commands;
+
+ ModuleHookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
+ : ModuleHook(target_sp, uid) {}
+ friend class Target;
+ };
+
+ class ModuleHookScripted : public ModuleHook {
+ public:
+ ~ModuleHookScripted() override = default;
+
+ void HandleModuleLoaded(lldb::StreamSP output) override;
+ void HandleModuleUnloaded(lldb::StreamSP output) override;
+
+ Status SetScriptCallback(std::string class_name,
+ StructuredData::ObjectSP extra_args_sp);
+
+ void GetSubclassDescription(Stream &s,
+ lldb::DescriptionLevel level) const override;
+
+ private:
+ std::string m_class_name;
+ StructuredDataImpl m_extra_args;
+ lldb::ScriptedModuleHookInterfaceSP m_interface_sp;
+
+ ModuleHookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
+ : ModuleHook(target_sp, uid) {}
+ friend class Target;
+ };
+
+ typedef std::shared_ptr<ModuleHook> ModuleHookSP;
+
+ ModuleHookSP CreateModuleHook(ModuleHook::ModuleHookKind kind);
+
+ void UndoCreateModuleHook(lldb::user_id_t uid);
+
+ bool RemoveModuleHookByID(lldb::user_id_t uid);
+
+ void RemoveAllModuleHooks();
+
+ ModuleHookSP GetModuleHookByID(lldb::user_id_t uid);
+
+ bool SetModuleHookActiveStateByID(lldb::user_id_t uid, bool active_state);
+
+ void SetAllModuleHooksActiveState(bool active_state);
+
+ size_t GetNumModuleHooks() const { return m_module_hooks.size(); }
+
+ ModuleHookSP GetModuleHookAtIndex(size_t index);
+
+ void RunModuleHooks(bool is_load);
+
/// Add an empty stop hook to the Target's stop hook list, and returns a
/// shared pointer to the new hook.
StopHookSP CreateStopHook(StopHook::StopHookKind kind, bool internal = false);
@@ -1844,6 +1944,10 @@ class Target : public std::enable_shared_from_this<Target>,
bool m_valid;
bool m_suppress_stop_hooks; /// Used to not run stop hooks for expressions
bool m_is_dummy_target;
+
+ typedef std::map<lldb::user_id_t, ModuleHookSP> ModuleHookCollection;
+ ModuleHookCollection m_module_hooks;
+ lldb::user_id_t m_module_hook_next_id = 0;
unsigned m_next_persistent_variable_index = 0;
lldb::user_id_t m_target_unique_id =
LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; ///< The globally unique ID
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index ccfe5efa19e1d..fcff135236ad0 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -191,6 +191,7 @@ class ScriptedFrameInterface;
class ScriptedFrameProviderInterface;
class ScriptedMetadata;
class ScriptedBreakpointInterface;
+class ScriptedModuleHookInterface;
class ScriptedPlatformInterface;
class ScriptedProcessInterface;
class ScriptedStopHookInterface;
@@ -425,6 +426,8 @@ typedef std::unique_ptr<lldb_private::ScriptedProcessInterface>
ScriptedProcessInterfaceUP;
typedef std::shared_ptr<lldb_private::ScriptedStopHookInterface>
ScriptedStopHookInterfaceSP;
+typedef std::shared_ptr<lldb_private::ScriptedModuleHookInterface>
+ ScriptedModuleHookInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedThreadInterface>
ScriptedThreadInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedThreadPlanInterface>
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 704e6f0819afe..19d8d1d3f15ff 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5291,6 +5291,331 @@ class CommandObjectMultiwordTargetStopHooks : public CommandObjectMultiword {
~CommandObjectMultiwordTargetStopHooks() override = default;
};
+#pragma mark CommandObjectTargetModuleHookAdd
+
+#define LLDB_OPTIONS_target_module_hook_add
+#include "CommandOptions.inc"
+
+class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
+ public IOHandlerDelegateMultiline {
+public:
+ class CommandOptions : public OptionGroup {
+ public:
+ CommandOptions() = default;
+ ~CommandOptions() override = default;
+
+ llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+ return llvm::ArrayRef(g_target_module_hook_add_options);
+ }
+
+ Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+ ExecutionContext *execution_context) override {
+ Status error;
+ const int short_option =
+ g_target_module_hook_add_options[option_idx].short_option;
+ switch (short_option) {
+ case 'o':
+ m_use_one_liner = true;
+ m_one_liner.push_back(std::string(option_arg));
+ break;
+ case 'u':
+ m_on_unload = true;
+ break;
+ default:
+ llvm_unreachable("unhandled option");
+ }
+ return error;
+ }
+
+ void OptionParsingStarting(ExecutionContext *execution_context) override {
+ m_use_one_liner = false;
+ m_one_liner.clear();
+ m_on_unload = false;
+ }
+
+ std::vector<std::string> m_one_liner;
+ bool m_use_one_liner = false;
+ bool m_on_unload = false;
+ };
+
+ CommandObjectTargetModuleHookAdd(CommandInterpreter &interpreter)
+ : CommandObjectParsed(
+ interpreter, "target module-hook add",
+ "Add a hook to be executed whenever modules are loaded into the "
+ "target.",
+ "target module-hook add"),
+ IOHandlerDelegateMultiline("DONE",
+ IOHandlerDelegate::Completion::LLDBCommand),
+ m_python_class_options("scripted module hook", false, 'P') {
+ SetHelpLong(R"help(
+Command-based module hooks allow running LLDB commands every time modules are
+loaded into the target. For example:
+
+ target module-hook add -o "script print('module loaded')"
+
+Use --on-unload (-u) to also fire the hook when modules are unloaded:
+
+ target module-hook add -u -o "script print('module event')"
+
+Python-based module hooks allow running a Python class:
+
+ target module-hook add -P MyHook
+
+The Python class should implement:
+
+ class MyHook:
+ def __init__(self, target, extra_args, internal_dict):
+ self.target = target
+ def handle_module_loaded(self, stream):
+ pass
+ def handle_module_unloaded(self, stream):
+ pass
+
+handle_module_loaded is required. handle_module_unloaded is optional and only
+called when --on-unload is specified.
+)help");
+ m_all_options.Append(&m_python_class_options,
+ LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_2);
+ m_all_options.Append(&m_options);
+ m_all_options.Finalize();
+ }
+
+ ~CommandObjectTargetModuleHookAdd() override = default;
+
+ Options *GetOptions() override { return &m_all_options; }
+
+protected:
+ void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
+ if (interactive) {
+ if (lldb::LockableStreamFileSP output_sp =
+ io_handler.GetOutputStreamFileSP()) {
+ LockedStreamFile locked_stream = output_sp->Lock();
+ locked_stream.PutCString(
+ "Enter your module hook command(s). Type 'DONE' to end.\n");
+ }
+ }
+ }
+
+ void IOHandlerInputComplete(IOHandler &io_handler,
+ std::string &line) override {
+ if (m_module_hook_sp) {
+ if (line.empty()) {
+ if (lldb::LockableStreamFileSP error_sp =
+ io_handler.GetErrorStreamFileSP()) {
+ LockedStreamFile locked_stream = error_sp->Lock();
+ locked_stream.Printf("error: module hook #%" PRIu64
+ " aborted, no commands.\n",
+ m_module_hook_sp->GetID());
+ }
+ GetTarget().UndoCreateModuleHook(m_module_hook_sp->GetID());
+ } else {
+ auto *hook = static_cast<Target::ModuleHookCommandLine *>(
+ m_module_hook_sp.get());
+ hook->SetActionFromString(line);
+ if (lldb::LockableStreamFileSP output_sp =
+ io_handler.GetOutputStreamFileSP()) {
+ LockedStreamFile locked_stream = output_sp->Lock();
+ locked_stream.Printf("Module hook #%" PRIu64 " added.\n",
+ m_module_hook_sp->GetID());
+ }
+ }
+ m_module_hook_sp.reset();
+ }
+ io_handler.SetIsDone(true);
+ }
+
+ void DoExecute(Args &command, CommandReturnObject &result) override {
+ m_module_hook_sp.reset();
+ Target &target = GetTarget();
+
+ Target::ModuleHook::ModuleHookKind hook_kind;
+ if (m_python_class_options.GetName().empty())
+ hook_kind = Target::ModuleHook::ModuleHookKind::CommandBased;
+ else
+ hook_kind = Target::ModuleHook::ModuleHookKind::ScriptBased;
+
+ Target::ModuleHookSP new_hook_sp = target.CreateModuleHook(hook_kind);
+
+ if (m_options.m_on_unload)
+ new_hook_sp->SetFireOnUnload(true);
+
+ if (m_options.m_use_one_liner) {
+ auto *hook =
+ static_cast<Target::ModuleHookCommandLine *>(new_hook_sp.get());
+ hook->SetActionFromStrings(m_options.m_one_liner);
+ result.AppendMessageWithFormat("Module hook #%" PRIu64 " added.\n",
+ new_hook_sp->GetID());
+ } else if (!m_python_class_options.GetName().empty()) {
+ auto *hook = static_cast<Target::ModuleHookScripted *>(new_hook_sp.get());
+ Status callback_error =
+ hook->SetScriptCallback(m_python_class_options.GetName(),
+ m_python_class_options.GetStructuredData());
+ if (callback_error.Fail()) {
+ result.AppendErrorWithFormat("error: couldn't add module hook: %s\n",
+ callback_error.AsCString());
+ target.UndoCreateModuleHook(new_hook_sp->GetID());
+ return;
+ }
+ result.AppendMessageWithFormat("Module hook #%" PRIu64 " added.\n",
+ new_hook_sp->GetID());
+ } else {
+ m_module_hook_sp = new_hook_sp;
+ m_interpreter.GetLLDBCommandsFromIOHandler("> ", // prompt
+ *this); // delegate
+ }
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ }
+
+private:
+ CommandOptions m_options;
+ OptionGroupPythonClassWithDict m_python_class_options;
+ OptionGroupOptions m_all_options;
+ Target::ModuleHookSP m_module_hook_sp;
+};
+
+#pragma mark CommandObjectTargetModuleHookDelete
+
+class CommandObjectTargetModuleHookDelete : public CommandObjectParsed {
+public:
+ CommandObjectTargetModuleHookDelete(CommandInterpreter &interpreter)
+ : CommandObjectParsed(interpreter, "target module-hook delete",
+ "Delete a module-hook.",
+ "target module-hook delete [<id>]") {
+ AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar);
+ }
+
+ ~CommandObjectTargetModuleHookDelete() override = default;
+
+protected:
+ void DoExecute(Args &command, CommandReturnObject &result) override {
+ Target &target = GetTarget();
+ if (command.GetArgumentCount() == 0) {
+ target.RemoveAllModuleHooks();
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ return;
+ }
+
+ for (size_t i = 0; i < command.GetArgumentCount(); i++) {
+ lldb::user_id_t user_id;
+ if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) {
+ result.AppendErrorWithFormat("invalid module hook id: \"%s\".\n",
+ command.GetArgumentAtIndex(i));
+ return;
+ }
+ if (!target.RemoveModuleHookByID(user_id)) {
+ result.AppendErrorWithFormat("unknown module hook id: \"%s\".\n",
+ command.GetArgumentAtIndex(i));
+ return;
+ }
+ }
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ }
+};
+
+#pragma mark CommandObjectTargetModuleHookEnableDisable
+
+class CommandObjectTargetModuleHookEnableDisable : public CommandObjectParsed {
+public:
+ CommandObjectTargetModuleHookEnableDisable(CommandInterpreter &interpreter,
+ bool enable, const char *name,
+ const char *help,
+ const char *syntax)
+ : CommandObjectParsed(interpreter, name, help, syntax), m_enable(enable) {
+ AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar);
+ }
+
+ ~CommandObjectTargetModuleHookEnableDisable() override = default;
+
+protected:
+ void DoExecute(Args &command, CommandReturnObject &result) override {
+ Target &target = GetTarget();
+ if (command.GetArgumentCount() == 0) {
+ target.SetAllModuleHooksActiveState(m_enable);
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ return;
+ }
+
+ for (size_t i = 0; i < command.GetArgumentCount(); i++) {
+ lldb::user_id_t user_id;
+ if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) {
+ result.AppendErrorWithFormat("invalid module hook id: \"%s\".\n",
+ command.GetArgumentAtIndex(i));
+ return;
+ }
+ if (!target.SetModuleHookActiveStateByID(user_id, m_enable)) {
+ result.AppendErrorWithFormat("unknown module hook id: \"%s\".\n",
+ command.GetArgumentAtIndex(i));
+ return;
+ }
+ }
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ }
+
+private:
+ bool m_enable;
+};
+
+#pragma mark CommandObjectTargetModuleHookList
+
+class CommandObjectTargetModuleHookList : public CommandObjectParsed {
+public:
+ CommandObjectTargetModuleHookList(CommandInterpreter &interpreter)
+ : CommandObjectParsed(interpreter, "target module-hook list",
+ "List all module-hooks.",
+ "target module-hook list") {}
+
+ ~CommandObjectTargetModuleHookList() override = default;
+
+protected:
+ void DoExecute(Args &command, CommandReturnObject &result) override {
+ Target &target = GetTarget();
+ size_t num_hooks = target.GetNumModuleHooks();
+ if (num_hooks == 0) {
+ result.GetOutputStream().PutCString("No module hooks.\n");
+ } else {
+ for (size_t i = 0; i < num_hooks; i++) {
+ Target::ModuleHookSP hook_sp = target.GetModuleHookAtIndex(i);
+ if (hook_sp)
+ hook_sp->GetDescription(result.GetOutputStream(),
+ eDescriptionLevelFull);
+ }
+ }
+ result.SetStatus(eReturnStatusSuccessFinishResult);
+ }
+};
+
+#pragma mark CommandObjectMultiwordTargetModuleHooks
+
+class CommandObjectMultiwordTargetModuleHooks : public CommandObjectMultiword {
+public:
+ CommandObjectMultiwordTargetModuleHooks(CommandInterpreter &interpreter)
+ : CommandObjectMultiword(
+ interpreter, "target module-hook",
+ "Commands for operating on debugger target module-hooks.",
+ "target module-hook <subcommand> [<subcommand-options>]") {
+ LoadSubCommand("add", CommandObjectSP(new CommandObjectTargetModuleHookAdd(
+ interpreter)));
+ LoadSubCommand(
+ "delete",
+ CommandObjectSP(new CommandObjectTargetModuleHookDelete(interpreter)));
+ LoadSubCommand(
+ "disable",
+ CommandObjectSP(new CommandObjectTargetModuleHookEnableDisable(
+ interpreter, false, "target module-hook disable [<id>]",
+ "Disable a module-hook.", "target module-hook disable")));
+ LoadSubCommand(
+ "enable",
+ CommandObjectSP(new CommandObjectTargetModuleHookEnableDisable(
+ interpreter, true, "target module-hook enable [<id>]",
+ "Enable a module-hook.", "target module-hook enable")));
+ LoadSubCommand(
+ "list",
+ CommandObjectSP(new CommandObjectTargetModuleHookList(interpreter)));
+ }
+
+ ~CommandObjectMultiwordTargetModuleHooks() override = default;
+};
+
#pragma mark CommandObjectTargetDumpTypesystem
/// Dumps the TypeSystem of the selected Target.
@@ -5584,6 +5909,9 @@ CommandObjectMultiwordTarget::CommandObjectMultiwordTarget(
LoadSubCommand(
"stop-hook",
CommandObjectSP(new CommandObjectMultiwordTargetStopHooks(interpreter)));
+ LoadSubCommand("module-hook",
+ CommandObjectSP(
+ new CommandObjectMultiwordTargetModuleHooks(interpreter)));
LoadSubCommand("modules",
CommandObjectSP(new CommandObjectTargetModules(interpreter)));
LoadSubCommand("symbols",
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 54e9f022880c4..16c8a638e9166 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1901,6 +1901,17 @@ let Command = "target stop_hook list" in {
Desc<"Show debugger ${i}nternal stop hooks.">;
}
+let Command = "target module_hook add" in {
+ def target_module_hook_add_one_liner
+ : Option<"one-liner", "o">,
+ Arg<"OneLiner">,
+ Desc<"Add a command for the module hook. Can be specified more than "
+ "once, and commands will be run in the order they appear.">;
+ def target_module_hook_add_on_unload
+ : Option<"on-unload", "u">,
+ Desc<"Also fire the hook when modules are ${u}nloaded.">;
+}
+
let Command = "thread backtrace" in {
def thread_backtrace_count : Option<"count", "c">,
Group<1>,
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
index 303e17a938912..817520bc37271 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
@@ -31,6 +31,7 @@ add_lldb_library(lldbPluginScriptInterpreterPython PLUGIN
Interfaces/ScriptedProcessPythonInterface.cpp
Interfaces/ScriptedPythonInterface.cpp
Interfaces/ScriptedStopHookPythonInterface.cpp
+ Interfaces/ScriptedModuleHookPythonInterface.cpp
Interfaces/ScriptedBreakpointPythonInterface.cpp
Interfaces/ScriptedThreadPlanPythonInterface.cpp
Interfaces/ScriptedThreadPythonInterface.cpp
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
index 3f6f9cfd4da8d..d7368de2bda67 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
@@ -24,6 +24,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() {
ScriptedPlatformPythonInterface::Initialize();
ScriptedProcessPythonInterface::Initialize();
ScriptedStopHookPythonInterface::Initialize();
+ ScriptedModuleHookPythonInterface::Initialize();
ScriptedBreakpointPythonInterface::Initialize();
ScriptedThreadPlanPythonInterface::Initialize();
ScriptedFrameProviderPythonInterface::Initialize();
@@ -34,6 +35,7 @@ void ScriptInterpreterPythonInterfaces::Terminate() {
ScriptedPlatformPythonInterface::Terminate();
ScriptedProcessPythonInterface::Terminate();
ScriptedStopHookPythonInterface::Terminate();
+ ScriptedModuleHookPythonInterface::Terminate();
ScriptedBreakpointPythonInterface::Terminate();
ScriptedThreadPlanPythonInterface::Terminate();
ScriptedFrameProviderPythonInterface::Terminate();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
index 58b19760cb8e1..6866aabb22666 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
@@ -16,6 +16,7 @@
#include "ScriptedBreakpointPythonInterface.h"
#include "ScriptedFrameProviderPythonInterface.h"
#include "ScriptedFramePythonInterface.h"
+#include "ScriptedModuleHookPythonInterface.h"
#include "ScriptedPlatformPythonInterface.h"
#include "ScriptedProcessPythonInterface.h"
#include "ScriptedStopHookPythonInterface.h"
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
new file mode 100644
index 0000000000000..5b2de9a4a27e2
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
@@ -0,0 +1,64 @@
+//===-- ScriptedModuleHookPythonInterface.cpp
+//------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/lldb-enumerations.h"
+
+#include "../SWIGPythonBridge.h"
+#include "../ScriptInterpreterPythonImpl.h"
+#include "../lldb-python.h"
+#include "ScriptedModuleHookPythonInterface.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::python;
+
+ScriptedModuleHookPythonInterface::ScriptedModuleHookPythonInterface(
+ ScriptInterpreterPythonImpl &interpreter)
+ : ScriptedModuleHookInterface(), ScriptedPythonInterface(interpreter) {}
+
+llvm::Expected<StructuredData::GenericSP>
+ScriptedModuleHookPythonInterface::CreatePluginObject(
+ llvm::StringRef class_name, lldb::TargetSP target_sp,
+ const StructuredDataImpl &args_sp) {
+ return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr,
+ target_sp, args_sp);
+}
+
+void ScriptedModuleHookPythonInterface::HandleModuleLoaded(
+ lldb::StreamSP &output_sp) {
+ Status error;
+ // We pass only the output stream to Python. The Python class can access
+ // self.target (set during __init__) to query loaded modules if needed.
+ Dispatch("handle_module_loaded", error, output_sp);
+}
+
+void ScriptedModuleHookPythonInterface::HandleModuleUnloaded(
+ lldb::StreamSP &output_sp) {
+ Status error;
+ // Optional method. If the Python class does not implement
+ // handle_module_unloaded, the dispatch will fail silently.
+ Dispatch("handle_module_unloaded", error, output_sp);
+}
+
+void ScriptedModuleHookPythonInterface::Initialize() {
+ const std::vector<llvm::StringRef> ci_usages = {
+ "target module-hook add -P <script-name> [-k key -v value ...]"};
+ const std::vector<llvm::StringRef> api_usages = {};
+ PluginManager::RegisterPlugin(
+ GetPluginNameStatic(),
+ llvm::StringRef("Perform actions whenever modules are loaded into the "
+ "target."),
+ CreateInstance, eScriptLanguagePython, {ci_usages, api_usages});
+}
+
+void ScriptedModuleHookPythonInterface::Terminate() {
+ PluginManager::UnregisterPlugin(CreateInstance);
+}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
new file mode 100644
index 0000000000000..1a5f4f14c0eab
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
@@ -0,0 +1,52 @@
+//===-- ScriptedModuleHookPythonInterface.h ----------------------*- C++
+//-*-===//
+//
+// 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_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDMODULEHOOKPYTHONINTERFACE_H
+#define LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDMODULEHOOKPYTHONINTERFACE_H
+
+#include "lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h"
+
+#include "ScriptedPythonInterface.h"
+
+namespace lldb_private {
+class ScriptedModuleHookPythonInterface : public ScriptedModuleHookInterface,
+ public ScriptedPythonInterface,
+ public PluginInterface {
+public:
+ ScriptedModuleHookPythonInterface(ScriptInterpreterPythonImpl &interpreter);
+
+ llvm::Expected<StructuredData::GenericSP>
+ CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
+ const StructuredDataImpl &args_sp) override;
+
+ llvm::SmallVector<AbstractMethodRequirement>
+ GetAbstractMethodRequirements() const override {
+ return llvm::SmallVector<AbstractMethodRequirement>(
+ {{"handle_module_loaded", 1}});
+ }
+
+ void HandleModuleLoaded(lldb::StreamSP &output_sp) override;
+
+ /// Optional: only called if the Python class implements
+ /// handle_module_unloaded. Silently does nothing otherwise.
+ void HandleModuleUnloaded(lldb::StreamSP &output_sp) override;
+
+ static void Initialize();
+
+ static void Terminate();
+
+ static llvm::StringRef GetPluginNameStatic() {
+ return "ScriptedModuleHookPythonInterface";
+ }
+
+ llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+};
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDMODULEHOOKPYTHONINTERFACE_H
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index f962fb884d146..827467d738686 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1522,6 +1522,11 @@ ScriptInterpreterPythonImpl::CreateScriptedStopHookInterface() {
return std::make_shared<ScriptedStopHookPythonInterface>(*this);
}
+ScriptedModuleHookInterfaceSP
+ScriptInterpreterPythonImpl::CreateScriptedModuleHookInterface() {
+ return std::make_shared<ScriptedModuleHookPythonInterface>(*this);
+}
+
ScriptedBreakpointInterfaceSP
ScriptInterpreterPythonImpl::CreateScriptedBreakpointInterface() {
return std::make_shared<ScriptedBreakpointPythonInterface>(*this);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index f08b2334f9ccb..59c92b08e127e 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -90,6 +90,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
lldb::ScriptedStopHookInterfaceSP CreateScriptedStopHookInterface() override;
+ lldb::ScriptedModuleHookInterfaceSP
+ CreateScriptedModuleHookInterface() override;
+
lldb::ScriptedBreakpointInterfaceSP
CreateScriptedBreakpointInterface() override;
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index cad86bf956e38..50d272ffdb0a4 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -38,6 +38,7 @@
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/Interfaces/ScriptedBreakpointInterface.h"
+#include "lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedStopHookInterface.h"
#include "lldb/Interpreter/OptionGroupWatchpoint.h"
#include "lldb/Interpreter/OptionValueEnumeration.h"
@@ -1859,6 +1860,7 @@ void Target::ModulesDidLoad(ModuleList &module_list) {
if (m_process_sp) {
m_process_sp->ModulesDidLoad(module_list);
}
+ RunModuleHooks(/*is_load=*/true);
auto data_sp =
std::make_shared<TargetEventData>(shared_from_this(), module_list);
BroadcastEvent(eBroadcastBitModulesLoaded, data_sp);
@@ -1918,6 +1920,8 @@ void Target::ModulesDidUnload(ModuleList &module_list, bool delete_locations) {
if (should_flush_type_systems)
m_scratch_type_system_map.Clear();
+
+ RunModuleHooks(/*is_load=*/false);
}
}
@@ -4248,6 +4252,271 @@ void Target::StopHookScripted::GetSubclassDescription(
as_dict->ForEach(print_one_element);
}
+// ModuleHook
+
+Target::ModuleHook::ModuleHook(lldb::TargetSP target_sp, lldb::user_id_t uid)
+ : UserID(uid), m_target_sp(std::move(target_sp)) {}
+
+Target::ModuleHook::ModuleHook(const ModuleHook &rhs)
+ : UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), m_active(rhs.m_active),
+ m_fire_on_unload(rhs.m_fire_on_unload) {}
+
+void Target::ModuleHook::GetDescription(Stream &s,
+ lldb::DescriptionLevel level) const {
+ s.Printf("Hook: %" PRIu64 "\n", GetID());
+ if (level == eDescriptionLevelBrief)
+ return;
+ s.IndentMore();
+ s.Indent();
+ s.Printf("State: %s\n", m_active ? "enabled" : "disabled");
+ if (m_fire_on_unload) {
+ s.Indent();
+ s.PutCString("Fires on: load, unload\n");
+ }
+ GetSubclassDescription(s, level);
+ s.IndentLess();
+}
+
+// ModuleHookCommandLine
+
+void Target::ModuleHookCommandLine::SetActionFromString(
+ const std::string &string) {
+ GetCommands().SplitIntoLines(string);
+}
+
+void Target::ModuleHookCommandLine::SetActionFromStrings(
+ const std::vector<std::string> &strings) {
+ for (const auto &string : strings)
+ GetCommands().AppendString(string.c_str());
+}
+
+void Target::ModuleHookCommandLine::GetSubclassDescription(
+ Stream &s, lldb::DescriptionLevel level) const {
+ if (level == eDescriptionLevelBrief) {
+ if (m_commands.GetSize() == 1)
+ s.PutCString(m_commands.GetStringAtIndex(0));
+ else
+ s.Printf("%" PRIu64 " commands", (uint64_t)m_commands.GetSize());
+ return;
+ }
+
+ s.Indent("Commands: \n");
+ s.IndentMore();
+ for (uint32_t i = 0; i < m_commands.GetSize(); i++) {
+ s.Indent(m_commands.GetStringAtIndex(i));
+ s.PutCString("\n");
+ }
+ s.IndentLess();
+}
+
+void Target::ModuleHookCommandLine::HandleModuleLoaded(StreamSP output_sp) {
+ if (!m_commands.GetSize())
+ return;
+
+ TargetSP target_sp = GetTarget();
+ if (!target_sp)
+ return;
+
+ CommandReturnObject result(false);
+ result.SetImmediateOutputStream(output_sp);
+ result.SetInteractive(false);
+ Debugger &debugger = target_sp->GetDebugger();
+
+ ExecutionContext exe_ctx;
+ if (target_sp->GetProcessSP())
+ exe_ctx.SetContext(target_sp->GetProcessSP());
+ else
+ exe_ctx.SetContext(target_sp, false);
+
+ CommandInterpreterRunOptions options;
+ options.SetStopOnContinue(true);
+ options.SetStopOnError(true);
+ options.SetEchoCommands(false);
+ options.SetPrintResults(true);
+ options.SetPrintErrors(true);
+ options.SetAddToHistory(false);
+
+ bool old_async = debugger.GetAsyncExecution();
+ debugger.SetAsyncExecution(true);
+ debugger.GetCommandInterpreter().HandleCommands(GetCommands(), exe_ctx,
+ options, result);
+ debugger.SetAsyncExecution(old_async);
+}
+
+void Target::ModuleHookCommandLine::HandleModuleUnloaded(StreamSP output_sp) {
+ // Command-based hooks run the same commands on unload as on load.
+ HandleModuleLoaded(output_sp);
+}
+
+// ModuleHookScripted
+
+Status Target::ModuleHookScripted::SetScriptCallback(
+ std::string class_name, StructuredData::ObjectSP extra_args_sp) {
+ ScriptInterpreter *script_interp =
+ GetTarget()->GetDebugger().GetScriptInterpreter();
+ if (!script_interp)
+ return Status::FromErrorString("No script interpreter installed.");
+
+ m_interface_sp = script_interp->CreateScriptedModuleHookInterface();
+ if (!m_interface_sp)
+ return Status::FromErrorStringWithFormat(
+ "ScriptedModuleHook::%s () - ERROR: %s", __FUNCTION__,
+ "Script interpreter couldn't create Scripted Module Hook Interface");
+
+ m_class_name = std::move(class_name);
+ m_extra_args.SetObjectSP(extra_args_sp);
+
+ auto obj_or_err = m_interface_sp->CreatePluginObject(
+ m_class_name, GetTarget(), m_extra_args);
+ if (!obj_or_err)
+ return Status::FromError(obj_or_err.takeError());
+
+ StructuredData::ObjectSP object_sp = *obj_or_err;
+ if (!object_sp || !object_sp->IsValid())
+ return Status::FromErrorStringWithFormat(
+ "ScriptedModuleHook::%s () - ERROR: %s", __FUNCTION__,
+ "Failed to create valid script object");
+
+ return {};
+}
+
+void Target::ModuleHookScripted::HandleModuleLoaded(StreamSP output_sp) {
+ if (!m_interface_sp)
+ return;
+
+ StreamSP stream = std::make_shared<StreamString>();
+ m_interface_sp->HandleModuleLoaded(stream);
+ output_sp->PutCString(
+ reinterpret_cast<StreamString *>(stream.get())->GetData());
+}
+
+void Target::ModuleHookScripted::HandleModuleUnloaded(StreamSP output_sp) {
+ if (!m_interface_sp)
+ return;
+
+ StreamSP stream = std::make_shared<StreamString>();
+ m_interface_sp->HandleModuleUnloaded(stream);
+ output_sp->PutCString(
+ reinterpret_cast<StreamString *>(stream.get())->GetData());
+}
+
+void Target::ModuleHookScripted::GetSubclassDescription(
+ Stream &s, lldb::DescriptionLevel level) const {
+ if (level == eDescriptionLevelBrief) {
+ s.PutCString(m_class_name);
+ return;
+ }
+ s.Indent("Class:");
+ s.Printf("%s\n", m_class_name.c_str());
+
+ if (!m_extra_args.IsValid())
+ return;
+ StructuredData::ObjectSP object_sp = m_extra_args.GetObjectSP();
+ if (!object_sp || !object_sp->IsValid())
+ return;
+
+ StructuredData::Dictionary *as_dict = object_sp->GetAsDictionary();
+ if (!as_dict || !as_dict->IsValid())
+ return;
+
+ uint32_t num_keys = as_dict->GetSize();
+ if (num_keys == 0)
+ return;
+
+ s.Indent("Args:\n");
+ s.IndentMore();
+
+ auto print_one_element = [&s](llvm::StringRef key,
+ StructuredData::Object *object) {
+ s.Indent();
+ s.Format("{0} : {1}\n", key, object->GetStringValue());
+ return true;
+ };
+
+ as_dict->ForEach(print_one_element);
+ s.IndentLess();
+}
+
+// Module Hook management methods
+
+Target::ModuleHookSP Target::CreateModuleHook(ModuleHook::ModuleHookKind kind) {
+ lldb::user_id_t new_uid = ++m_module_hook_next_id;
+ ModuleHookSP hook_sp;
+ switch (kind) {
+ case ModuleHook::ModuleHookKind::CommandBased:
+ hook_sp.reset(new ModuleHookCommandLine(shared_from_this(), new_uid));
+ break;
+ case ModuleHook::ModuleHookKind::ScriptBased:
+ hook_sp.reset(new ModuleHookScripted(shared_from_this(), new_uid));
+ break;
+ }
+ m_module_hooks[new_uid] = hook_sp;
+ return hook_sp;
+}
+
+void Target::UndoCreateModuleHook(lldb::user_id_t uid) {
+ if (!RemoveModuleHookByID(uid))
+ return;
+ if (uid > 0)
+ --m_module_hook_next_id;
+}
+
+bool Target::RemoveModuleHookByID(lldb::user_id_t uid) {
+ size_t num_removed = m_module_hooks.erase(uid);
+ return (num_removed != 0);
+}
+
+void Target::RemoveAllModuleHooks() { m_module_hooks.clear(); }
+
+Target::ModuleHookSP Target::GetModuleHookByID(lldb::user_id_t uid) {
+ auto iter = m_module_hooks.find(uid);
+ if (iter == m_module_hooks.end())
+ return {};
+ return iter->second;
+}
+
+Target::ModuleHookSP Target::GetModuleHookAtIndex(size_t index) {
+ if (index >= m_module_hooks.size())
+ return {};
+ auto iter = m_module_hooks.begin();
+ std::advance(iter, index);
+ return iter->second;
+}
+
+bool Target::SetModuleHookActiveStateByID(lldb::user_id_t uid,
+ bool active_state) {
+ auto iter = m_module_hooks.find(uid);
+ if (iter == m_module_hooks.end())
+ return false;
+ iter->second->SetIsActive(active_state);
+ return true;
+}
+
+void Target::SetAllModuleHooksActiveState(bool active_state) {
+ for (auto &[_, hook] : m_module_hooks)
+ hook->SetIsActive(active_state);
+}
+
+void Target::RunModuleHooks(bool is_load) {
+ if (m_module_hooks.empty())
+ return;
+
+ StreamSP output_sp = m_debugger.GetAsyncOutputStream();
+
+ for (auto &[_, hook_sp] : m_module_hooks) {
+ if (!hook_sp->IsActive())
+ continue;
+ if (!is_load && !hook_sp->GetFireOnUnload())
+ continue;
+ if (is_load)
+ hook_sp->HandleModuleLoaded(output_sp);
+ else
+ hook_sp->HandleModuleUnloaded(output_sp);
+ }
+
+ output_sp->Flush();
+}
+
static constexpr OptionEnumValueElement g_dynamic_value_types[] = {
{
eNoDynamicValues,
diff --git a/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py b/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
new file mode 100644
index 0000000000000..51f4f386b2b0a
--- /dev/null
+++ b/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
@@ -0,0 +1,19 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ @no_debug_info_test
+ def test_invalid_arg(self):
+ self.expect(
+ "target module-hook delete -1",
+ error=True,
+ startstr='error: invalid module hook id: "-1".',
+ )
+ self.expect(
+ "target module-hook delete abcdfx",
+ error=True,
+ startstr='error: invalid module hook id: "abcdfx".',
+ )
diff --git a/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py b/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
new file mode 100644
index 0000000000000..455e555072f8e
--- /dev/null
+++ b/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
@@ -0,0 +1,19 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ @no_debug_info_test
+ def test_invalid_arg(self):
+ self.expect(
+ "target module-hook disable -1",
+ error=True,
+ startstr='error: invalid module hook id: "-1".',
+ )
+ self.expect(
+ "target module-hook disable abcdfx",
+ error=True,
+ startstr='error: invalid module hook id: "abcdfx".',
+ )
diff --git a/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py b/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
new file mode 100644
index 0000000000000..12f1d66ba03d0
--- /dev/null
+++ b/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
@@ -0,0 +1,19 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ @no_debug_info_test
+ def test_invalid_arg(self):
+ self.expect(
+ "target module-hook enable -1",
+ error=True,
+ startstr='error: invalid module hook id: "-1".',
+ )
+ self.expect(
+ "target module-hook enable abcdfx",
+ error=True,
+ startstr='error: invalid module hook id: "abcdfx".',
+ )
diff --git a/lldb/test/Shell/Commands/command-module-hook-fire.test b/lldb/test/Shell/Commands/command-module-hook-fire.test
new file mode 100644
index 0000000000000..84791d91351e1
--- /dev/null
+++ b/lldb/test/Shell/Commands/command-module-hook-fire.test
@@ -0,0 +1,10 @@
+# Test that module hooks fire when modules are loaded.
+#
+# RUN: %clang_host -g %S/Inputs/main.c -o %t
+# RUN: %lldb -b -o 'file %t' \
+# RUN: -o 'target module-hook add -o "script print(\"HOOK_FIRED\")"' \
+# RUN: -o 'target modules add %t' \
+# RUN: 2>&1 | FileCheck %s
+
+# CHECK: Module hook #1 added.
+# CHECK: HOOK_FIRED
diff --git a/lldb/test/Shell/Commands/command-module-hook.test b/lldb/test/Shell/Commands/command-module-hook.test
new file mode 100644
index 0000000000000..3cc69a07732f6
--- /dev/null
+++ b/lldb/test/Shell/Commands/command-module-hook.test
@@ -0,0 +1,113 @@
+# Test module hook add/list/delete/enable/disable commands.
+#
+# RUN: %lldb -b -s %s 2>&1 | FileCheck %s
+
+# Adding hooks assigns incrementing IDs.
+target module-hook add -o "script print('hook1')"
+# CHECK: Module hook #1 added.
+target module-hook add -o "script print('hook2')"
+# CHECK: Module hook #2 added.
+target module-hook add -o "script print('hook3')"
+# CHECK: Module hook #3 added.
+
+# List all hooks.
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: Hook: 1
+# CHECK: State: enabled
+# CHECK: Commands:
+# CHECK: script print('hook1')
+# CHECK: Hook: 2
+# CHECK: State: enabled
+# CHECK: Commands:
+# CHECK: script print('hook2')
+# CHECK: Hook: 3
+# CHECK: State: enabled
+# CHECK: Commands:
+# CHECK: script print('hook3')
+
+# Disable a hook.
+target module-hook disable 2
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: Hook: 1
+# CHECK: State: enabled
+# CHECK: Hook: 2
+# CHECK: State: disabled
+# CHECK: Hook: 3
+# CHECK: State: enabled
+
+# Re-enable the hook.
+target module-hook enable 2
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: Hook: 1
+# CHECK: State: enabled
+# CHECK: Hook: 2
+# CHECK: State: enabled
+# CHECK: Hook: 3
+# CHECK: State: enabled
+
+# Delete a hook in the middle.
+target module-hook delete 2
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: Hook: 1
+# CHECK-NOT: Hook: 2
+# CHECK: Hook: 3
+
+# New hook gets a new ID (not reused).
+target module-hook add -o "script print('hook4')"
+# CHECK: Module hook #4 added.
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: Hook: 1
+# CHECK-NOT: Hook: 2
+# CHECK: Hook: 3
+# CHECK: Hook: 4
+
+# Delete all hooks.
+target module-hook delete
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: No module hooks.
+
+# Add a hook with --on-unload and verify the description.
+target module-hook add -u -o "script print('load+unload')"
+# CHECK: Module hook #5 added.
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: Hook: 5
+# CHECK: State: enabled
+# CHECK: Fires on: load, unload
+# CHECK: Commands:
+# CHECK: script print('load+unload')
+
+# Add a hook with multiple one-liner commands.
+target module-hook add -o "script print('first')" -o "script print('second')"
+# CHECK: Module hook #6 added.
+target module-hook list
+# CHECK: Hook: 6
+# CHECK: State: enabled
+# CHECK-NOT: Fires on:
+# CHECK: Commands:
+# CHECK: script print('first')
+# CHECK: script print('second')
+
+# Disable all hooks.
+target module-hook disable
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: Hook: 5
+# CHECK: State: disabled
+# CHECK: Hook: 6
+# CHECK: State: disabled
+
+# Enable all hooks.
+target module-hook enable
+target module-hook list
+# CHECK: (lldb) target module-hook list
+# CHECK: Hook: 5
+# CHECK: State: enabled
+# CHECK: Hook: 6
+# CHECK: State: enabled
>From e65d950e63eccdafef6aaa84800e2a99bfb0e9d8 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Mon, 9 Mar 2026 10:53:25 -0700
Subject: [PATCH 02/12] Rename module-hook to avoid ambiguity with modules
---
lldb/source/Commands/CommandObjectTarget.cpp | 38 ++++++------
lldb/source/Commands/Options.td | 6 +-
.../delete/TestTargetModuleHookDelete.py | 4 +-
.../disable/TestTargetModuleHookDisable.py | 4 +-
.../enable/TestTargetModuleHookEnable.py | 4 +-
.../Commands/command-module-hook-fire.test | 2 +-
.../Shell/Commands/command-module-hook.test | 62 +++++++++----------
7 files changed, 60 insertions(+), 60 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 19d8d1d3f15ff..5d840d980fe34 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5293,7 +5293,7 @@ class CommandObjectMultiwordTargetStopHooks : public CommandObjectMultiword {
#pragma mark CommandObjectTargetModuleHookAdd
-#define LLDB_OPTIONS_target_module_hook_add
+#define LLDB_OPTIONS_target_modulehook_add
#include "CommandOptions.inc"
class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
@@ -5305,14 +5305,14 @@ class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
~CommandOptions() override = default;
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
- return llvm::ArrayRef(g_target_module_hook_add_options);
+ return llvm::ArrayRef(g_target_modulehook_add_options);
}
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) override {
Status error;
const int short_option =
- g_target_module_hook_add_options[option_idx].short_option;
+ g_target_modulehook_add_options[option_idx].short_option;
switch (short_option) {
case 'o':
m_use_one_liner = true;
@@ -5340,10 +5340,10 @@ class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
CommandObjectTargetModuleHookAdd(CommandInterpreter &interpreter)
: CommandObjectParsed(
- interpreter, "target module-hook add",
+ interpreter, "target modulehook add",
"Add a hook to be executed whenever modules are loaded into the "
"target.",
- "target module-hook add"),
+ "target modulehook add"),
IOHandlerDelegateMultiline("DONE",
IOHandlerDelegate::Completion::LLDBCommand),
m_python_class_options("scripted module hook", false, 'P') {
@@ -5351,15 +5351,15 @@ class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
Command-based module hooks allow running LLDB commands every time modules are
loaded into the target. For example:
- target module-hook add -o "script print('module loaded')"
+ target modulehook add -o "script print('module loaded')"
Use --on-unload (-u) to also fire the hook when modules are unloaded:
- target module-hook add -u -o "script print('module event')"
+ target modulehook add -u -o "script print('module event')"
Python-based module hooks allow running a Python class:
- target module-hook add -P MyHook
+ target modulehook add -P MyHook
The Python class should implement:
@@ -5478,9 +5478,9 @@ called when --on-unload is specified.
class CommandObjectTargetModuleHookDelete : public CommandObjectParsed {
public:
CommandObjectTargetModuleHookDelete(CommandInterpreter &interpreter)
- : CommandObjectParsed(interpreter, "target module-hook delete",
+ : CommandObjectParsed(interpreter, "target modulehook delete",
"Delete a module-hook.",
- "target module-hook delete [<id>]") {
+ "target modulehook delete [<id>]") {
AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar);
}
@@ -5560,9 +5560,9 @@ class CommandObjectTargetModuleHookEnableDisable : public CommandObjectParsed {
class CommandObjectTargetModuleHookList : public CommandObjectParsed {
public:
CommandObjectTargetModuleHookList(CommandInterpreter &interpreter)
- : CommandObjectParsed(interpreter, "target module-hook list",
+ : CommandObjectParsed(interpreter, "target modulehook list",
"List all module-hooks.",
- "target module-hook list") {}
+ "target modulehook list") {}
~CommandObjectTargetModuleHookList() override = default;
@@ -5590,9 +5590,9 @@ class CommandObjectMultiwordTargetModuleHooks : public CommandObjectMultiword {
public:
CommandObjectMultiwordTargetModuleHooks(CommandInterpreter &interpreter)
: CommandObjectMultiword(
- interpreter, "target module-hook",
+ interpreter, "target modulehook",
"Commands for operating on debugger target module-hooks.",
- "target module-hook <subcommand> [<subcommand-options>]") {
+ "target modulehook <subcommand> [<subcommand-options>]") {
LoadSubCommand("add", CommandObjectSP(new CommandObjectTargetModuleHookAdd(
interpreter)));
LoadSubCommand(
@@ -5601,13 +5601,13 @@ class CommandObjectMultiwordTargetModuleHooks : public CommandObjectMultiword {
LoadSubCommand(
"disable",
CommandObjectSP(new CommandObjectTargetModuleHookEnableDisable(
- interpreter, false, "target module-hook disable [<id>]",
- "Disable a module-hook.", "target module-hook disable")));
+ interpreter, false, "target modulehook disable [<id>]",
+ "Disable a module-hook.", "target modulehook disable")));
LoadSubCommand(
"enable",
CommandObjectSP(new CommandObjectTargetModuleHookEnableDisable(
- interpreter, true, "target module-hook enable [<id>]",
- "Enable a module-hook.", "target module-hook enable")));
+ interpreter, true, "target modulehook enable [<id>]",
+ "Enable a module-hook.", "target modulehook enable")));
LoadSubCommand(
"list",
CommandObjectSP(new CommandObjectTargetModuleHookList(interpreter)));
@@ -5909,7 +5909,7 @@ CommandObjectMultiwordTarget::CommandObjectMultiwordTarget(
LoadSubCommand(
"stop-hook",
CommandObjectSP(new CommandObjectMultiwordTargetStopHooks(interpreter)));
- LoadSubCommand("module-hook",
+ LoadSubCommand("modulehook",
CommandObjectSP(
new CommandObjectMultiwordTargetModuleHooks(interpreter)));
LoadSubCommand("modules",
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 16c8a638e9166..a8dc519a77bec 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1901,13 +1901,13 @@ let Command = "target stop_hook list" in {
Desc<"Show debugger ${i}nternal stop hooks.">;
}
-let Command = "target module_hook add" in {
- def target_module_hook_add_one_liner
+let Command = "target modulehook add" in {
+ def target_modulehook_add_one_liner
: Option<"one-liner", "o">,
Arg<"OneLiner">,
Desc<"Add a command for the module hook. Can be specified more than "
"once, and commands will be run in the order they appear.">;
- def target_module_hook_add_on_unload
+ def target_modulehook_add_on_unload
: Option<"on-unload", "u">,
Desc<"Also fire the hook when modules are ${u}nloaded.">;
}
diff --git a/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py b/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
index 51f4f386b2b0a..a071ecd73e883 100644
--- a/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
+++ b/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
@@ -8,12 +8,12 @@ class TestCase(TestBase):
@no_debug_info_test
def test_invalid_arg(self):
self.expect(
- "target module-hook delete -1",
+ "target modulehook delete -1",
error=True,
startstr='error: invalid module hook id: "-1".',
)
self.expect(
- "target module-hook delete abcdfx",
+ "target modulehook delete abcdfx",
error=True,
startstr='error: invalid module hook id: "abcdfx".',
)
diff --git a/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py b/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
index 455e555072f8e..1541b0006b0c0 100644
--- a/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
+++ b/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
@@ -8,12 +8,12 @@ class TestCase(TestBase):
@no_debug_info_test
def test_invalid_arg(self):
self.expect(
- "target module-hook disable -1",
+ "target modulehook disable -1",
error=True,
startstr='error: invalid module hook id: "-1".',
)
self.expect(
- "target module-hook disable abcdfx",
+ "target modulehook disable abcdfx",
error=True,
startstr='error: invalid module hook id: "abcdfx".',
)
diff --git a/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py b/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
index 12f1d66ba03d0..dbb16cd10022b 100644
--- a/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
+++ b/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
@@ -8,12 +8,12 @@ class TestCase(TestBase):
@no_debug_info_test
def test_invalid_arg(self):
self.expect(
- "target module-hook enable -1",
+ "target modulehook enable -1",
error=True,
startstr='error: invalid module hook id: "-1".',
)
self.expect(
- "target module-hook enable abcdfx",
+ "target modulehook enable abcdfx",
error=True,
startstr='error: invalid module hook id: "abcdfx".',
)
diff --git a/lldb/test/Shell/Commands/command-module-hook-fire.test b/lldb/test/Shell/Commands/command-module-hook-fire.test
index 84791d91351e1..5876ea05fb1fe 100644
--- a/lldb/test/Shell/Commands/command-module-hook-fire.test
+++ b/lldb/test/Shell/Commands/command-module-hook-fire.test
@@ -2,7 +2,7 @@
#
# RUN: %clang_host -g %S/Inputs/main.c -o %t
# RUN: %lldb -b -o 'file %t' \
-# RUN: -o 'target module-hook add -o "script print(\"HOOK_FIRED\")"' \
+# RUN: -o 'target modulehook add -o "script print(\"HOOK_FIRED\")"' \
# RUN: -o 'target modules add %t' \
# RUN: 2>&1 | FileCheck %s
diff --git a/lldb/test/Shell/Commands/command-module-hook.test b/lldb/test/Shell/Commands/command-module-hook.test
index 3cc69a07732f6..4118a6f18501a 100644
--- a/lldb/test/Shell/Commands/command-module-hook.test
+++ b/lldb/test/Shell/Commands/command-module-hook.test
@@ -3,16 +3,16 @@
# RUN: %lldb -b -s %s 2>&1 | FileCheck %s
# Adding hooks assigns incrementing IDs.
-target module-hook add -o "script print('hook1')"
+target modulehook add -o "script print('hook1')"
# CHECK: Module hook #1 added.
-target module-hook add -o "script print('hook2')"
+target modulehook add -o "script print('hook2')"
# CHECK: Module hook #2 added.
-target module-hook add -o "script print('hook3')"
+target modulehook add -o "script print('hook3')"
# CHECK: Module hook #3 added.
# List all hooks.
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: Hook: 1
# CHECK: State: enabled
# CHECK: Commands:
@@ -27,9 +27,9 @@ target module-hook list
# CHECK: script print('hook3')
# Disable a hook.
-target module-hook disable 2
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook disable 2
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: Hook: 1
# CHECK: State: enabled
# CHECK: Hook: 2
@@ -38,9 +38,9 @@ target module-hook list
# CHECK: State: enabled
# Re-enable the hook.
-target module-hook enable 2
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook enable 2
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: Hook: 1
# CHECK: State: enabled
# CHECK: Hook: 2
@@ -49,34 +49,34 @@ target module-hook list
# CHECK: State: enabled
# Delete a hook in the middle.
-target module-hook delete 2
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook delete 2
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: Hook: 1
# CHECK-NOT: Hook: 2
# CHECK: Hook: 3
# New hook gets a new ID (not reused).
-target module-hook add -o "script print('hook4')"
+target modulehook add -o "script print('hook4')"
# CHECK: Module hook #4 added.
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: Hook: 1
# CHECK-NOT: Hook: 2
# CHECK: Hook: 3
# CHECK: Hook: 4
# Delete all hooks.
-target module-hook delete
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook delete
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: No module hooks.
# Add a hook with --on-unload and verify the description.
-target module-hook add -u -o "script print('load+unload')"
+target modulehook add -u -o "script print('load+unload')"
# CHECK: Module hook #5 added.
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: Hook: 5
# CHECK: State: enabled
# CHECK: Fires on: load, unload
@@ -84,9 +84,9 @@ target module-hook list
# CHECK: script print('load+unload')
# Add a hook with multiple one-liner commands.
-target module-hook add -o "script print('first')" -o "script print('second')"
+target modulehook add -o "script print('first')" -o "script print('second')"
# CHECK: Module hook #6 added.
-target module-hook list
+target modulehook list
# CHECK: Hook: 6
# CHECK: State: enabled
# CHECK-NOT: Fires on:
@@ -95,18 +95,18 @@ target module-hook list
# CHECK: script print('second')
# Disable all hooks.
-target module-hook disable
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook disable
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: Hook: 5
# CHECK: State: disabled
# CHECK: Hook: 6
# CHECK: State: disabled
# Enable all hooks.
-target module-hook enable
-target module-hook list
-# CHECK: (lldb) target module-hook list
+target modulehook enable
+target modulehook list
+# CHECK: (lldb) target modulehook list
# CHECK: Hook: 5
# CHECK: State: enabled
# CHECK: Hook: 6
>From 2fe1c9032a9c4ce2205d749a37f0f2fccdaa9d68 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Mon, 9 Mar 2026 12:09:07 -0700
Subject: [PATCH 03/12] fix suggested fixes
---
.../Interfaces/ScriptedModuleHookInterface.h | 3 +--
lldb/include/lldb/Target/Target.h | 18 +++++++++++-------
lldb/source/Commands/CommandObjectTarget.cpp | 2 +-
.../ScriptedModuleHookPythonInterface.h | 3 +--
4 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
index 7a06349eb9161..bbf9531045cde 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
@@ -1,5 +1,4 @@
-//===-- ScriptedModuleHookInterface.h ----------------------------*- C++
-//-*-===//
+//===-- ScriptedModuleHookInterface.h ----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index ae758d3875c0b..90d8e3ef1bc47 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1659,8 +1659,9 @@ class Target : public std::enable_shared_from_this<Target>,
// Target Module Hooks
//
- // Module hooks fire whenever modules are loaded or unloaded from the
- // target (via ModulesDidLoad / ModulesDidUnload).
+ // Module hooks fire whenever modules are loaded into the target
+ // (via ModulesDidLoad). Optionally, they can also fire on unload
+ // (via ModulesDidUnload) when the fire_on_unload flag is set.
class ModuleHook : public UserID {
public:
ModuleHook(const ModuleHook &rhs);
@@ -1699,10 +1700,10 @@ class Target : public std::enable_shared_from_this<Target>,
void SetActionFromString(const std::string &string);
void SetActionFromStrings(const std::vector<std::string> &strings);
- void HandleModuleLoaded(lldb::StreamSP output) override;
- void HandleModuleUnloaded(lldb::StreamSP output) override;
void GetSubclassDescription(Stream &s,
lldb::DescriptionLevel level) const override;
+ void HandleModuleLoaded(lldb::StreamSP output) override;
+ void HandleModuleUnloaded(lldb::StreamSP output) override;
private:
StringList m_commands;
@@ -1716,15 +1717,15 @@ class Target : public std::enable_shared_from_this<Target>,
public:
~ModuleHookScripted() override = default;
+ void GetSubclassDescription(Stream &s,
+ lldb::DescriptionLevel level) const override;
+
void HandleModuleLoaded(lldb::StreamSP output) override;
void HandleModuleUnloaded(lldb::StreamSP output) override;
Status SetScriptCallback(std::string class_name,
StructuredData::ObjectSP extra_args_sp);
- void GetSubclassDescription(Stream &s,
- lldb::DescriptionLevel level) const override;
-
private:
std::string m_class_name;
StructuredDataImpl m_extra_args;
@@ -1739,6 +1740,9 @@ class Target : public std::enable_shared_from_this<Target>,
ModuleHookSP CreateModuleHook(ModuleHook::ModuleHookKind kind);
+ /// Removes the most recently created module hook. Used to roll back a
+ /// hook creation when an error occurs (e.g., invalid script class name
+ /// or empty interactive input).
void UndoCreateModuleHook(lldb::user_id_t uid);
bool RemoveModuleHookByID(lldb::user_id_t uid);
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 5d840d980fe34..aed2b5e045d20 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5359,7 +5359,7 @@ Use --on-unload (-u) to also fire the hook when modules are unloaded:
Python-based module hooks allow running a Python class:
- target modulehook add -P MyHook
+ target modulehook add -P mymodule.MyHook
The Python class should implement:
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
index 1a5f4f14c0eab..c63db160e7dc8 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
@@ -1,5 +1,4 @@
-//===-- ScriptedModuleHookPythonInterface.h ----------------------*- C++
-//-*-===//
+//===-- ScriptedModuleHookPythonInterface.h ----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
>From d7bf491460bb13eee45f21ff03f5995f112e72e4 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 10 Mar 2026 10:36:06 -0700
Subject: [PATCH 04/12] New hook logic
---
.../Interfaces/ScriptedHookInterface.h | 38 ++
.../lldb/Interpreter/ScriptInterpreter.h | 3 +-
lldb/include/lldb/Target/Target.h | 134 +++--
lldb/include/lldb/lldb-forward.h | 6 +-
lldb/source/Commands/CommandObjectTarget.cpp | 557 ++++++++++++++----
lldb/source/Commands/Options.td | 75 ++-
.../ScriptInterpreter/Python/CMakeLists.txt | 2 +-
.../ScriptInterpreterPythonInterfaces.cpp | 4 +-
.../ScriptInterpreterPythonInterfaces.h | 2 +-
.../ScriptedHookPythonInterface.cpp | 80 +++
.../Interfaces/ScriptedHookPythonInterface.h | 50 ++
.../ScriptedModuleHookPythonInterface.cpp | 2 +-
.../Python/ScriptInterpreterPython.cpp | 6 +-
.../Python/ScriptInterpreterPythonImpl.h | 3 +-
lldb/source/Target/Target.cpp | 290 +++++++--
.../delete/TestTargetModuleHookDelete.py | 8 +-
.../disable/TestTargetModuleHookDisable.py | 8 +-
.../enable/TestTargetModuleHookEnable.py | 8 +-
.../Commands/command-module-hook-fire.test | 4 +-
.../Shell/Commands/command-module-hook.test | 126 ++--
20 files changed, 1130 insertions(+), 276 deletions(-)
create mode 100644 lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
new file mode 100644
index 0000000000000..03053ed26879c
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
@@ -0,0 +1,38 @@
+//===-- ScriptedHookInterface.h ---------------------------------*- C++ -*-===//
+//
+// 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_INTERPRETER_INTERFACES_SCRIPTEDHOOKINTERFACE_H
+#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDHOOKINTERFACE_H
+
+#include "lldb/lldb-private.h"
+
+#include "ScriptedInterface.h"
+
+namespace lldb_private {
+class ScriptedHookInterface : public ScriptedInterface {
+public:
+ virtual llvm::Expected<StructuredData::GenericSP>
+ CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
+ const StructuredDataImpl &args_sp) = 0;
+
+ /// Called when modules are loaded into the target.
+ virtual void HandleModuleLoaded(lldb::StreamSP &output_sp) {}
+
+ /// Called when modules are unloaded from the target. Optional.
+ virtual void HandleModuleUnloaded(lldb::StreamSP &output_sp) {}
+
+ /// Called when the process stops. Returns "should_stop" -- if false, the
+ /// process will continue. Defaults to true (stop on unimplemented).
+ virtual llvm::Expected<bool> HandleStop(ExecutionContext &exe_ctx,
+ lldb::StreamSP &output_sp) {
+ return true;
+ }
+};
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDHOOKINTERFACE_H
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 7838ad5e0adbf..0c37c119540f6 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -562,8 +562,7 @@ class ScriptInterpreter : public PluginInterface {
return {};
}
- virtual lldb::ScriptedModuleHookInterfaceSP
- CreateScriptedModuleHookInterface() {
+ virtual lldb::ScriptedHookInterfaceSP CreateScriptedHookInterface() {
return {};
}
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 90d8e3ef1bc47..085318d9fdefa 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1657,44 +1657,98 @@ class Target : public std::enable_shared_from_this<Target>,
typedef std::shared_ptr<StopHook> StopHookSP;
- // Target Module Hooks
+ // Target Hooks
//
- // Module hooks fire whenever modules are loaded into the target
- // (via ModulesDidLoad). Optionally, they can also fire on unload
- // (via ModulesDidUnload) when the fire_on_unload flag is set.
- class ModuleHook : public UserID {
+ // Hooks fire on lifecycle events. By default, they fire when modules
+ // are loaded into the target (via ModulesDidLoad). They can also fire
+ // on module unload (via ModulesDidUnload) and on process stop (via
+ // RunStopHooks), controlled by the event mask.
+ class Hook : public UserID {
public:
- ModuleHook(const ModuleHook &rhs);
- virtual ~ModuleHook() = default;
+ Hook(const Hook &rhs);
+ virtual ~Hook() = default;
- enum class ModuleHookKind : uint32_t { CommandBased = 0, ScriptBased };
+ enum class HookKind : uint32_t { CommandBased = 0, ScriptBased };
+
+ /// Event mask bits controlling when this hook fires.
+ enum EventMask : uint32_t {
+ kModulesLoaded = (1u << 0),
+ kModulesUnloaded = (1u << 1),
+ kProcessStop = (1u << 2),
+ };
lldb::TargetSP &GetTarget() { return m_target_sp; }
bool IsActive() { return m_active; }
void SetIsActive(bool is_active) { m_active = is_active; }
- bool GetFireOnUnload() const { return m_fire_on_unload; }
- void SetFireOnUnload(bool fire) { m_fire_on_unload = fire; }
+ uint32_t GetEventMask() const { return m_event_mask; }
+ void SetEventMask(uint32_t mask) { m_event_mask = mask; }
+ void AddEvent(uint32_t event) { m_event_mask |= event; }
+ void RemoveEvent(uint32_t event) { m_event_mask &= ~event; }
+ bool FiresOn(uint32_t event) const { return m_event_mask & event; }
- void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
- virtual void GetSubclassDescription(Stream &s,
- lldb::DescriptionLevel level) const = 0;
+ // Stop-hook features (only relevant when kProcessStop is set)
+
+ /// Set the specifier. The hook will own the specifier.
+ void SetSpecifier(SymbolContextSpecifier *specifier);
+ SymbolContextSpecifier *GetSpecifier() { return m_specifier_sp.get(); }
+
+ /// Check if the execution context passes the specifier and thread spec
+ /// filters. Always returns true if no filters are set.
+ bool ExecutionContextPasses(const ExecutionContext &exe_ctx);
+
+ /// Set the thread specifier. The hook will own the thread specifier.
+ void SetThreadSpecifier(ThreadSpec *specifier);
+ ThreadSpec *GetThreadSpecifier() { return m_thread_spec_up.get(); }
+
+ void SetAutoContinue(bool auto_continue) {
+ m_auto_continue = auto_continue;
+ }
+ bool GetAutoContinue() const { return m_auto_continue; }
+
+ void SetRunAtInitialStop(bool at_initial_stop) {
+ m_at_initial_stop = at_initial_stop;
+ }
+ 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; }
+
+ // Event handler methods
virtual void HandleModuleLoaded(lldb::StreamSP output) = 0;
virtual void HandleModuleUnloaded(lldb::StreamSP output) = 0;
+ /// Called when the process stops. Returns a StopHookResult indicating
+ /// whether the process should remain stopped or continue.
+ virtual StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
+ lldb::StreamSP output) = 0;
+
+ void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
+ virtual void GetSubclassDescription(Stream &s,
+ lldb::DescriptionLevel level) const = 0;
+
protected:
lldb::TargetSP m_target_sp;
bool m_active = true;
- bool m_fire_on_unload = false;
+ uint32_t m_event_mask = kModulesLoaded; // Default: fire on load only
+
+ // Stop-hook filter fields (only used when kProcessStop is set)
+ lldb::SymbolContextSpecifierSP m_specifier_sp;
+ std::unique_ptr<ThreadSpec> m_thread_spec_up;
+ bool m_auto_continue = false;
+ bool m_at_initial_stop = true;
+ bool m_suppress_output = false;
- ModuleHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
+ Hook(lldb::TargetSP target_sp, lldb::user_id_t uid);
};
- class ModuleHookCommandLine : public ModuleHook {
+ class HookCommandLine : public Hook {
public:
- ~ModuleHookCommandLine() override = default;
+ ~HookCommandLine() override = default;
StringList &GetCommands() { return m_commands; }
void SetActionFromString(const std::string &string);
@@ -1704,24 +1758,28 @@ class Target : public std::enable_shared_from_this<Target>,
lldb::DescriptionLevel level) const override;
void HandleModuleLoaded(lldb::StreamSP output) override;
void HandleModuleUnloaded(lldb::StreamSP output) override;
+ StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
+ lldb::StreamSP output) override;
private:
StringList m_commands;
- ModuleHookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
- : ModuleHook(target_sp, uid) {}
+ HookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
+ : Hook(target_sp, uid) {}
friend class Target;
};
- class ModuleHookScripted : public ModuleHook {
+ class HookScripted : public Hook {
public:
- ~ModuleHookScripted() override = default;
+ ~HookScripted() override = default;
void GetSubclassDescription(Stream &s,
lldb::DescriptionLevel level) const override;
void HandleModuleLoaded(lldb::StreamSP output) override;
void HandleModuleUnloaded(lldb::StreamSP output) override;
+ StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
+ lldb::StreamSP output) override;
Status SetScriptCallback(std::string class_name,
StructuredData::ObjectSP extra_args_sp);
@@ -1729,35 +1787,35 @@ class Target : public std::enable_shared_from_this<Target>,
private:
std::string m_class_name;
StructuredDataImpl m_extra_args;
- lldb::ScriptedModuleHookInterfaceSP m_interface_sp;
+ lldb::ScriptedHookInterfaceSP m_interface_sp;
- ModuleHookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
- : ModuleHook(target_sp, uid) {}
+ HookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
+ : Hook(target_sp, uid) {}
friend class Target;
};
- typedef std::shared_ptr<ModuleHook> ModuleHookSP;
+ typedef std::shared_ptr<Hook> HookSP;
- ModuleHookSP CreateModuleHook(ModuleHook::ModuleHookKind kind);
+ HookSP CreateHook(Hook::HookKind kind);
- /// Removes the most recently created module hook. Used to roll back a
+ /// Removes the most recently created hook. Used to roll back a
/// hook creation when an error occurs (e.g., invalid script class name
/// or empty interactive input).
- void UndoCreateModuleHook(lldb::user_id_t uid);
+ void UndoCreateHook(lldb::user_id_t uid);
- bool RemoveModuleHookByID(lldb::user_id_t uid);
+ bool RemoveHookByID(lldb::user_id_t uid);
- void RemoveAllModuleHooks();
+ void RemoveAllHooks();
- ModuleHookSP GetModuleHookByID(lldb::user_id_t uid);
+ HookSP GetHookByID(lldb::user_id_t uid);
- bool SetModuleHookActiveStateByID(lldb::user_id_t uid, bool active_state);
+ bool SetHookActiveStateByID(lldb::user_id_t uid, bool active_state);
- void SetAllModuleHooksActiveState(bool active_state);
+ void SetAllHooksActiveState(bool active_state);
- size_t GetNumModuleHooks() const { return m_module_hooks.size(); }
+ size_t GetNumHooks() const { return m_hooks.size(); }
- ModuleHookSP GetModuleHookAtIndex(size_t index);
+ HookSP GetHookAtIndex(size_t index);
void RunModuleHooks(bool is_load);
@@ -1949,9 +2007,9 @@ class Target : public std::enable_shared_from_this<Target>,
bool m_suppress_stop_hooks; /// Used to not run stop hooks for expressions
bool m_is_dummy_target;
- typedef std::map<lldb::user_id_t, ModuleHookSP> ModuleHookCollection;
- ModuleHookCollection m_module_hooks;
- lldb::user_id_t m_module_hook_next_id = 0;
+ typedef std::map<lldb::user_id_t, HookSP> HookCollection;
+ HookCollection m_hooks;
+ lldb::user_id_t m_hook_next_id = 0;
unsigned m_next_persistent_variable_index = 0;
lldb::user_id_t m_target_unique_id =
LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; ///< The globally unique ID
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index fcff135236ad0..67888f7d32ed1 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -191,7 +191,7 @@ class ScriptedFrameInterface;
class ScriptedFrameProviderInterface;
class ScriptedMetadata;
class ScriptedBreakpointInterface;
-class ScriptedModuleHookInterface;
+class ScriptedHookInterface;
class ScriptedPlatformInterface;
class ScriptedProcessInterface;
class ScriptedStopHookInterface;
@@ -426,8 +426,8 @@ typedef std::unique_ptr<lldb_private::ScriptedProcessInterface>
ScriptedProcessInterfaceUP;
typedef std::shared_ptr<lldb_private::ScriptedStopHookInterface>
ScriptedStopHookInterfaceSP;
-typedef std::shared_ptr<lldb_private::ScriptedModuleHookInterface>
- ScriptedModuleHookInterfaceSP;
+typedef std::shared_ptr<lldb_private::ScriptedHookInterface>
+ ScriptedHookInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedThreadInterface>
ScriptedThreadInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedThreadPlanInterface>
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index aed2b5e045d20..d2a6e77b5f458 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5291,13 +5291,13 @@ class CommandObjectMultiwordTargetStopHooks : public CommandObjectMultiword {
~CommandObjectMultiwordTargetStopHooks() override = default;
};
-#pragma mark CommandObjectTargetModuleHookAdd
+#pragma mark CommandObjectTargetHookAdd
-#define LLDB_OPTIONS_target_modulehook_add
+#define LLDB_OPTIONS_target_hook_add
#include "CommandOptions.inc"
-class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
- public IOHandlerDelegateMultiline {
+class CommandObjectTargetHookAdd : public CommandObjectParsed,
+ public IOHandlerDelegateMultiline {
public:
class CommandOptions : public OptionGroup {
public:
@@ -5305,14 +5305,14 @@ class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
~CommandOptions() override = default;
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
- return llvm::ArrayRef(g_target_modulehook_add_options);
+ return llvm::ArrayRef(g_target_hook_add_options);
}
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) override {
Status error;
const int short_option =
- g_target_modulehook_add_options[option_idx].short_option;
+ g_target_hook_add_options[option_idx].short_option;
switch (short_option) {
case 'o':
m_use_one_liner = true;
@@ -5321,6 +5321,9 @@ class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
case 'u':
m_on_unload = true;
break;
+ case 'S':
+ m_on_stop = true;
+ break;
default:
llvm_unreachable("unhandled option");
}
@@ -5331,48 +5334,60 @@ class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
m_use_one_liner = false;
m_one_liner.clear();
m_on_unload = false;
+ m_on_stop = false;
}
std::vector<std::string> m_one_liner;
bool m_use_one_liner = false;
bool m_on_unload = false;
+ bool m_on_stop = false;
};
- CommandObjectTargetModuleHookAdd(CommandInterpreter &interpreter)
+ CommandObjectTargetHookAdd(CommandInterpreter &interpreter)
: CommandObjectParsed(
- interpreter, "target modulehook add",
- "Add a hook to be executed whenever modules are loaded into the "
- "target.",
- "target modulehook add"),
+ interpreter, "target hook add",
+ "Add a hook to be executed on target lifecycle events.",
+ "target hook add"),
IOHandlerDelegateMultiline("DONE",
IOHandlerDelegate::Completion::LLDBCommand),
- m_python_class_options("scripted module hook", false, 'P') {
+ m_python_class_options("scripted hook", false, 'P') {
SetHelpLong(R"help(
-Command-based module hooks allow running LLDB commands every time modules are
-loaded into the target. For example:
+Hooks fire on target lifecycle events. By default, hooks fire when modules are
+loaded into the target. Use --on-unload (-u) to also fire on module unload, and
+--on-stop (-S) to also fire when the process stops.
- target modulehook add -o "script print('module loaded')"
+Command-based hooks:
-Use --on-unload (-u) to also fire the hook when modules are unloaded:
+ target hook add -o "script print('module loaded')"
+ target hook add -u -o "script print('module event')"
+ target hook add -S -o "bt"
+ target hook add -u -S -o "script print('all events')"
- target modulehook add -u -o "script print('module event')"
+Use 'target hook add-filter' to add stop-event filters to an existing hook:
-Python-based module hooks allow running a Python class:
+ target hook add -S -o "bt"
+ target hook add-filter -s mylib.so -n main 1
- target modulehook add -P mymodule.MyHook
+Python-based hooks:
+
+ target hook add -P mymodule.MyHook
+ target hook add -u -S -P mymodule.MyHook
The Python class should implement:
class MyHook:
def __init__(self, target, extra_args, internal_dict):
self.target = target
- def handle_module_loaded(self, stream):
+ def handle_module_loaded(self, stream): # required
pass
- def handle_module_unloaded(self, stream):
+ def handle_module_unloaded(self, stream): # optional
pass
+ def handle_stop(self, exe_ctx, stream): # optional, return bool
+ return True # True = should_stop
-handle_module_loaded is required. handle_module_unloaded is optional and only
-called when --on-unload is specified.
+handle_module_loaded is required. handle_module_unloaded is only called when
+--on-unload is specified. handle_stop is only called when --on-stop is
+specified and should return True to stop or False to continue.
)help");
m_all_options.Append(&m_python_class_options,
LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_2);
@@ -5380,7 +5395,7 @@ called when --on-unload is specified.
m_all_options.Finalize();
}
- ~CommandObjectTargetModuleHookAdd() override = default;
+ ~CommandObjectTargetHookAdd() override = default;
Options *GetOptions() override { return &m_all_options; }
@@ -5391,75 +5406,78 @@ called when --on-unload is specified.
io_handler.GetOutputStreamFileSP()) {
LockedStreamFile locked_stream = output_sp->Lock();
locked_stream.PutCString(
- "Enter your module hook command(s). Type 'DONE' to end.\n");
+ "Enter your hook command(s). Type 'DONE' to end.\n");
}
}
}
void IOHandlerInputComplete(IOHandler &io_handler,
std::string &line) override {
- if (m_module_hook_sp) {
+ if (m_hook_sp) {
if (line.empty()) {
if (lldb::LockableStreamFileSP error_sp =
io_handler.GetErrorStreamFileSP()) {
LockedStreamFile locked_stream = error_sp->Lock();
- locked_stream.Printf("error: module hook #%" PRIu64
+ locked_stream.Printf("error: hook #%" PRIu64
" aborted, no commands.\n",
- m_module_hook_sp->GetID());
+ m_hook_sp->GetID());
}
- GetTarget().UndoCreateModuleHook(m_module_hook_sp->GetID());
+ GetTarget().UndoCreateHook(m_hook_sp->GetID());
} else {
- auto *hook = static_cast<Target::ModuleHookCommandLine *>(
- m_module_hook_sp.get());
+ auto *hook = static_cast<Target::HookCommandLine *>(m_hook_sp.get());
hook->SetActionFromString(line);
if (lldb::LockableStreamFileSP output_sp =
io_handler.GetOutputStreamFileSP()) {
LockedStreamFile locked_stream = output_sp->Lock();
- locked_stream.Printf("Module hook #%" PRIu64 " added.\n",
- m_module_hook_sp->GetID());
+ locked_stream.Printf("Hook #%" PRIu64 " added.\n",
+ m_hook_sp->GetID());
}
}
- m_module_hook_sp.reset();
+ m_hook_sp.reset();
}
io_handler.SetIsDone(true);
}
void DoExecute(Args &command, CommandReturnObject &result) override {
- m_module_hook_sp.reset();
+ m_hook_sp.reset();
Target &target = GetTarget();
- Target::ModuleHook::ModuleHookKind hook_kind;
+ Target::Hook::HookKind hook_kind;
if (m_python_class_options.GetName().empty())
- hook_kind = Target::ModuleHook::ModuleHookKind::CommandBased;
+ hook_kind = Target::Hook::HookKind::CommandBased;
else
- hook_kind = Target::ModuleHook::ModuleHookKind::ScriptBased;
+ hook_kind = Target::Hook::HookKind::ScriptBased;
- Target::ModuleHookSP new_hook_sp = target.CreateModuleHook(hook_kind);
+ Target::HookSP new_hook_sp = target.CreateHook(hook_kind);
+ // Build event mask.
+ uint32_t event_mask = Target::Hook::kModulesLoaded;
if (m_options.m_on_unload)
- new_hook_sp->SetFireOnUnload(true);
+ event_mask |= Target::Hook::kModulesUnloaded;
+ if (m_options.m_on_stop)
+ event_mask |= Target::Hook::kProcessStop;
+ new_hook_sp->SetEventMask(event_mask);
if (m_options.m_use_one_liner) {
- auto *hook =
- static_cast<Target::ModuleHookCommandLine *>(new_hook_sp.get());
+ auto *hook = static_cast<Target::HookCommandLine *>(new_hook_sp.get());
hook->SetActionFromStrings(m_options.m_one_liner);
- result.AppendMessageWithFormat("Module hook #%" PRIu64 " added.\n",
+ result.AppendMessageWithFormat("Hook #%" PRIu64 " added.\n",
new_hook_sp->GetID());
} else if (!m_python_class_options.GetName().empty()) {
- auto *hook = static_cast<Target::ModuleHookScripted *>(new_hook_sp.get());
+ auto *hook = static_cast<Target::HookScripted *>(new_hook_sp.get());
Status callback_error =
hook->SetScriptCallback(m_python_class_options.GetName(),
m_python_class_options.GetStructuredData());
if (callback_error.Fail()) {
- result.AppendErrorWithFormat("error: couldn't add module hook: %s\n",
+ result.AppendErrorWithFormat("error: couldn't add hook: %s\n",
callback_error.AsCString());
- target.UndoCreateModuleHook(new_hook_sp->GetID());
+ target.UndoCreateHook(new_hook_sp->GetID());
return;
}
- result.AppendMessageWithFormat("Module hook #%" PRIu64 " added.\n",
+ result.AppendMessageWithFormat("Hook #%" PRIu64 " added.\n",
new_hook_sp->GetID());
} else {
- m_module_hook_sp = new_hook_sp;
+ m_hook_sp = new_hook_sp;
m_interpreter.GetLLDBCommandsFromIOHandler("> ", // prompt
*this); // delegate
}
@@ -5470,27 +5488,295 @@ called when --on-unload is specified.
CommandOptions m_options;
OptionGroupPythonClassWithDict m_python_class_options;
OptionGroupOptions m_all_options;
- Target::ModuleHookSP m_module_hook_sp;
+ Target::HookSP m_hook_sp;
};
-#pragma mark CommandObjectTargetModuleHookDelete
+#pragma mark CommandObjectTargetHookAddFilter
-class CommandObjectTargetModuleHookDelete : public CommandObjectParsed {
+#define LLDB_OPTIONS_target_hook_add_filter
+#include "CommandOptions.inc"
+
+class CommandObjectTargetHookAddFilter : public CommandObjectParsed {
public:
- CommandObjectTargetModuleHookDelete(CommandInterpreter &interpreter)
- : CommandObjectParsed(interpreter, "target modulehook delete",
- "Delete a module-hook.",
- "target modulehook delete [<id>]") {
+ class CommandOptions : public Options {
+ public:
+ CommandOptions() = default;
+ ~CommandOptions() override = default;
+
+ llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+ return llvm::ArrayRef(g_target_hook_add_filter_options);
+ }
+
+ Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+ ExecutionContext *execution_context) override {
+ Status error;
+ const int short_option =
+ g_target_hook_add_filter_options[option_idx].short_option;
+ switch (short_option) {
+ case 's':
+ m_module_name = std::string(option_arg);
+ m_sym_ctx_specified = true;
+ break;
+ case 'x': {
+ uint32_t thread_index;
+ if (option_arg.getAsInteger(0, thread_index))
+ error = Status::FromErrorStringWithFormat("invalid thread index '%s'",
+ option_arg.str().c_str());
+ else
+ m_thread_index = thread_index;
+ m_thread_specified = true;
+ break;
+ }
+ case 't': {
+ lldb::tid_t thread_id;
+ if (option_arg.getAsInteger(0, thread_id))
+ error = Status::FromErrorStringWithFormat("invalid thread id '%s'",
+ option_arg.str().c_str());
+ else
+ m_thread_id = thread_id;
+ m_thread_specified = true;
+ break;
+ }
+ case 'T':
+ m_thread_name = std::string(option_arg);
+ m_thread_specified = true;
+ break;
+ case 'q':
+ m_queue_name = std::string(option_arg);
+ m_thread_specified = true;
+ break;
+ case 'f':
+ m_file_name = std::string(option_arg);
+ m_sym_ctx_specified = true;
+ break;
+ case 'l': {
+ uint32_t line;
+ if (option_arg.getAsInteger(0, line))
+ error = Status::FromErrorStringWithFormat(
+ "invalid start line number '%s'", option_arg.str().c_str());
+ else
+ m_line_start = line;
+ m_sym_ctx_specified = true;
+ break;
+ }
+ case 'e': {
+ uint32_t line;
+ if (option_arg.getAsInteger(0, line))
+ error = Status::FromErrorStringWithFormat(
+ "invalid end line number '%s'", option_arg.str().c_str());
+ else
+ m_line_end = line;
+ m_sym_ctx_specified = true;
+ break;
+ }
+ case 'c':
+ m_class_name = std::string(option_arg);
+ m_sym_ctx_specified = true;
+ break;
+ case 'n':
+ m_function_name = std::string(option_arg);
+ m_sym_ctx_specified = true;
+ break;
+ case 'G': {
+ bool value, success;
+ value = OptionArgParser::ToBoolean(option_arg, false, &success);
+ if (success)
+ m_auto_continue = value;
+ else
+ error = Status::FromErrorStringWithFormat(
+ "invalid boolean value '%s' passed for -G option",
+ option_arg.str().c_str());
+ break;
+ }
+ case 'I': {
+ bool value, success;
+ value = OptionArgParser::ToBoolean(option_arg, true, &success);
+ if (success)
+ m_at_initial_stop = value;
+ else
+ error = Status::FromErrorStringWithFormat(
+ "invalid boolean value '%s' passed for -I option",
+ option_arg.str().c_str());
+ break;
+ }
+ default:
+ llvm_unreachable("unhandled option");
+ }
+ return error;
+ }
+
+ void OptionParsingStarting(ExecutionContext *execution_context) override {
+ m_sym_ctx_specified = false;
+ m_thread_specified = false;
+ m_module_name.clear();
+ m_file_name.clear();
+ m_class_name.clear();
+ m_function_name.clear();
+ m_line_start = 0;
+ m_line_end = UINT_MAX;
+ m_thread_id = LLDB_INVALID_THREAD_ID;
+ m_thread_index = UINT32_MAX;
+ m_thread_name.clear();
+ m_queue_name.clear();
+ m_auto_continue = false;
+ m_at_initial_stop = true;
+ }
+
+ bool m_sym_ctx_specified = false;
+ bool m_thread_specified = false;
+ std::string m_module_name;
+ std::string m_file_name;
+ std::string m_class_name;
+ std::string m_function_name;
+ uint32_t m_line_start = 0;
+ uint32_t m_line_end = UINT_MAX;
+ lldb::tid_t m_thread_id = LLDB_INVALID_THREAD_ID;
+ uint32_t m_thread_index = UINT32_MAX;
+ std::string m_thread_name;
+ std::string m_queue_name;
+ bool m_auto_continue = false;
+ bool m_at_initial_stop = true;
+ };
+
+ CommandObjectTargetHookAddFilter(CommandInterpreter &interpreter)
+ : CommandObjectParsed(
+ interpreter, "target hook add-filter",
+ "Add stop-event filters to an existing hook.",
+ "target hook add-filter [<filter-options>] <hook-id>") {
+ SetHelpLong(R"help(
+Adds stop-event filters to an existing hook. Filters control when the hook's
+stop handler fires based on the current execution context (module, function,
+source file/line, thread, etc.).
+
+If the hook does not already fire on process stop, the stop event is
+automatically added to its event mask.
+
+Examples:
+
+ target hook add -S -o "bt"
+ target hook add-filter -s mylib.so 1
+
+ target hook add -S -o "bt"
+ target hook add-filter -n main -G true 1
+)help");
+ AddSimpleArgumentList(eArgTypeStopHookID);
+ }
+
+ ~CommandObjectTargetHookAddFilter() override = default;
+
+ Options *GetOptions() override { return &m_options; }
+
+protected:
+ void DoExecute(Args &command, CommandReturnObject &result) override {
+ Target &target = GetTarget();
+
+ if (command.GetArgumentCount() != 1) {
+ result.AppendError("exactly one hook id is required.");
+ return;
+ }
+
+ lldb::user_id_t hook_id;
+ if (!llvm::to_integer(command.GetArgumentAtIndex(0), hook_id)) {
+ result.AppendErrorWithFormat("invalid hook id: \"%s\".\n",
+ command.GetArgumentAtIndex(0));
+ return;
+ }
+
+ Target::HookSP hook_sp = target.GetHookByID(hook_id);
+ if (!hook_sp) {
+ result.AppendErrorWithFormat("unknown hook id: \"%" PRIu64 "\".\n",
+ hook_id);
+ return;
+ }
+
+ // Automatically add the stop event if not already present.
+ if (!hook_sp->FiresOn(Target::Hook::kProcessStop))
+ hook_sp->AddEvent(Target::Hook::kProcessStop);
+
+ // Set up symbol context specifier if filter options were provided.
+ if (m_options.m_sym_ctx_specified) {
+ auto specifier_up = std::make_unique<SymbolContextSpecifier>(
+ GetDebugger().GetSelectedTarget());
+
+ if (!m_options.m_module_name.empty())
+ specifier_up->AddSpecification(
+ m_options.m_module_name.c_str(),
+ SymbolContextSpecifier::eModuleSpecified);
+
+ if (!m_options.m_class_name.empty())
+ specifier_up->AddSpecification(
+ m_options.m_class_name.c_str(),
+ SymbolContextSpecifier::eClassOrNamespaceSpecified);
+
+ if (!m_options.m_file_name.empty())
+ specifier_up->AddSpecification(m_options.m_file_name.c_str(),
+ SymbolContextSpecifier::eFileSpecified);
+
+ if (m_options.m_line_start != 0)
+ specifier_up->AddLineSpecification(
+ m_options.m_line_start,
+ SymbolContextSpecifier::eLineStartSpecified);
+
+ if (m_options.m_line_end != UINT_MAX)
+ specifier_up->AddLineSpecification(
+ m_options.m_line_end, SymbolContextSpecifier::eLineEndSpecified);
+
+ if (!m_options.m_function_name.empty())
+ specifier_up->AddSpecification(
+ m_options.m_function_name.c_str(),
+ SymbolContextSpecifier::eFunctionSpecified);
+
+ hook_sp->SetSpecifier(specifier_up.release());
+ }
+
+ // Set up thread specifier.
+ if (m_options.m_thread_specified) {
+ ThreadSpec *thread_spec = new ThreadSpec();
+
+ if (m_options.m_thread_id != LLDB_INVALID_THREAD_ID)
+ thread_spec->SetTID(m_options.m_thread_id);
+
+ if (m_options.m_thread_index != UINT32_MAX)
+ thread_spec->SetIndex(m_options.m_thread_index);
+
+ if (!m_options.m_thread_name.empty())
+ thread_spec->SetName(m_options.m_thread_name.c_str());
+
+ if (!m_options.m_queue_name.empty())
+ thread_spec->SetQueueName(m_options.m_queue_name.c_str());
+
+ hook_sp->SetThreadSpecifier(thread_spec);
+ }
+
+ hook_sp->SetAutoContinue(m_options.m_auto_continue);
+ hook_sp->SetRunAtInitialStop(m_options.m_at_initial_stop);
+
+ result.AppendMessageWithFormat("Filter added to hook #%" PRIu64 ".\n",
+ hook_id);
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ }
+
+private:
+ CommandOptions m_options;
+};
+
+#pragma mark CommandObjectTargetHookDelete
+
+class CommandObjectTargetHookDelete : public CommandObjectParsed {
+public:
+ CommandObjectTargetHookDelete(CommandInterpreter &interpreter)
+ : CommandObjectParsed(interpreter, "target hook delete", "Delete a hook.",
+ "target hook delete [<id>]") {
AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar);
}
- ~CommandObjectTargetModuleHookDelete() override = default;
+ ~CommandObjectTargetHookDelete() override = default;
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetTarget();
if (command.GetArgumentCount() == 0) {
- target.RemoveAllModuleHooks();
+ target.RemoveAllHooks();
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return;
}
@@ -5498,12 +5784,12 @@ class CommandObjectTargetModuleHookDelete : public CommandObjectParsed {
for (size_t i = 0; i < command.GetArgumentCount(); i++) {
lldb::user_id_t user_id;
if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) {
- result.AppendErrorWithFormat("invalid module hook id: \"%s\".\n",
+ result.AppendErrorWithFormat("invalid hook id: \"%s\".\n",
command.GetArgumentAtIndex(i));
return;
}
- if (!target.RemoveModuleHookByID(user_id)) {
- result.AppendErrorWithFormat("unknown module hook id: \"%s\".\n",
+ if (!target.RemoveHookByID(user_id)) {
+ result.AppendErrorWithFormat("unknown hook id: \"%s\".\n",
command.GetArgumentAtIndex(i));
return;
}
@@ -5512,40 +5798,92 @@ class CommandObjectTargetModuleHookDelete : public CommandObjectParsed {
}
};
-#pragma mark CommandObjectTargetModuleHookEnableDisable
+#pragma mark CommandObjectTargetHookEnableDisable
-class CommandObjectTargetModuleHookEnableDisable : public CommandObjectParsed {
+class CommandObjectTargetHookEnableDisable : public CommandObjectParsed {
public:
- CommandObjectTargetModuleHookEnableDisable(CommandInterpreter &interpreter,
- bool enable, const char *name,
- const char *help,
- const char *syntax)
+ CommandObjectTargetHookEnableDisable(CommandInterpreter &interpreter,
+ bool enable, const char *name,
+ const char *help, const char *syntax)
: CommandObjectParsed(interpreter, name, help, syntax), m_enable(enable) {
AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar);
}
- ~CommandObjectTargetModuleHookEnableDisable() override = default;
+ ~CommandObjectTargetHookEnableDisable() override = default;
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetTarget();
- if (command.GetArgumentCount() == 0) {
- target.SetAllModuleHooksActiveState(m_enable);
+
+ // Check if the first argument is an event type name for per-event
+ // toggling: module-loaded, module-unloaded, stop-hook.
+ uint32_t event_type = 0;
+ size_t id_start_idx = 0;
+
+ if (command.GetArgumentCount() > 0) {
+ llvm::StringRef first_arg = command.GetArgumentAtIndex(0);
+ if (first_arg == "module-loaded") {
+ event_type = Target::Hook::kModulesLoaded;
+ id_start_idx = 1;
+ } else if (first_arg == "module-unloaded") {
+ event_type = Target::Hook::kModulesUnloaded;
+ id_start_idx = 1;
+ } else if (first_arg == "stop-hook") {
+ event_type = Target::Hook::kProcessStop;
+ id_start_idx = 1;
+ }
+ }
+
+ // If no hook IDs given (after possibly consuming event type), apply to all.
+ if (command.GetArgumentCount() == id_start_idx) {
+ if (event_type) {
+ // Per-event toggle on all hooks.
+ size_t num_hooks = target.GetNumHooks();
+ for (size_t i = 0; i < num_hooks; i++) {
+ Target::HookSP hook_sp = target.GetHookAtIndex(i);
+ if (!hook_sp)
+ continue;
+ if (m_enable)
+ hook_sp->AddEvent(event_type);
+ else
+ hook_sp->RemoveEvent(event_type);
+ }
+ } else {
+ // Whole-hook toggle on all hooks.
+ target.SetAllHooksActiveState(m_enable);
+ }
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return;
}
- for (size_t i = 0; i < command.GetArgumentCount(); i++) {
+ // Process hook IDs.
+ for (size_t i = id_start_idx; i < command.GetArgumentCount(); i++) {
lldb::user_id_t user_id;
if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) {
- result.AppendErrorWithFormat("invalid module hook id: \"%s\".\n",
+ result.AppendErrorWithFormat("invalid hook id: \"%s\".\n",
command.GetArgumentAtIndex(i));
return;
}
- if (!target.SetModuleHookActiveStateByID(user_id, m_enable)) {
- result.AppendErrorWithFormat("unknown module hook id: \"%s\".\n",
- command.GetArgumentAtIndex(i));
- return;
+
+ if (event_type) {
+ // Per-event toggle on a specific hook.
+ Target::HookSP hook_sp = target.GetHookByID(user_id);
+ if (!hook_sp) {
+ result.AppendErrorWithFormat("unknown hook id: \"%s\".\n",
+ command.GetArgumentAtIndex(i));
+ return;
+ }
+ if (m_enable)
+ hook_sp->AddEvent(event_type);
+ else
+ hook_sp->RemoveEvent(event_type);
+ } else {
+ // Whole-hook toggle.
+ if (!target.SetHookActiveStateByID(user_id, m_enable)) {
+ result.AppendErrorWithFormat("unknown hook id: \"%s\".\n",
+ command.GetArgumentAtIndex(i));
+ return;
+ }
}
}
result.SetStatus(eReturnStatusSuccessFinishNoResult);
@@ -5555,26 +5893,25 @@ class CommandObjectTargetModuleHookEnableDisable : public CommandObjectParsed {
bool m_enable;
};
-#pragma mark CommandObjectTargetModuleHookList
+#pragma mark CommandObjectTargetHookList
-class CommandObjectTargetModuleHookList : public CommandObjectParsed {
+class CommandObjectTargetHookList : public CommandObjectParsed {
public:
- CommandObjectTargetModuleHookList(CommandInterpreter &interpreter)
- : CommandObjectParsed(interpreter, "target modulehook list",
- "List all module-hooks.",
- "target modulehook list") {}
+ CommandObjectTargetHookList(CommandInterpreter &interpreter)
+ : CommandObjectParsed(interpreter, "target hook list", "List all hooks.",
+ "target hook list") {}
- ~CommandObjectTargetModuleHookList() override = default;
+ ~CommandObjectTargetHookList() override = default;
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetTarget();
- size_t num_hooks = target.GetNumModuleHooks();
+ size_t num_hooks = target.GetNumHooks();
if (num_hooks == 0) {
- result.GetOutputStream().PutCString("No module hooks.\n");
+ result.GetOutputStream().PutCString("No hooks.\n");
} else {
for (size_t i = 0; i < num_hooks; i++) {
- Target::ModuleHookSP hook_sp = target.GetModuleHookAtIndex(i);
+ Target::HookSP hook_sp = target.GetHookAtIndex(i);
if (hook_sp)
hook_sp->GetDescription(result.GetOutputStream(),
eDescriptionLevelFull);
@@ -5584,36 +5921,37 @@ class CommandObjectTargetModuleHookList : public CommandObjectParsed {
}
};
-#pragma mark CommandObjectMultiwordTargetModuleHooks
+#pragma mark CommandObjectMultiwordTargetHooks
-class CommandObjectMultiwordTargetModuleHooks : public CommandObjectMultiword {
+class CommandObjectMultiwordTargetHooks : public CommandObjectMultiword {
public:
- CommandObjectMultiwordTargetModuleHooks(CommandInterpreter &interpreter)
+ CommandObjectMultiwordTargetHooks(CommandInterpreter &interpreter)
: CommandObjectMultiword(
- interpreter, "target modulehook",
- "Commands for operating on debugger target module-hooks.",
- "target modulehook <subcommand> [<subcommand-options>]") {
- LoadSubCommand("add", CommandObjectSP(new CommandObjectTargetModuleHookAdd(
- interpreter)));
- LoadSubCommand(
- "delete",
- CommandObjectSP(new CommandObjectTargetModuleHookDelete(interpreter)));
+ interpreter, "target hook",
+ "Commands for operating on target hooks.",
+ "target hook <subcommand> [<subcommand-options>]") {
LoadSubCommand(
- "disable",
- CommandObjectSP(new CommandObjectTargetModuleHookEnableDisable(
- interpreter, false, "target modulehook disable [<id>]",
- "Disable a module-hook.", "target modulehook disable")));
+ "add", CommandObjectSP(new CommandObjectTargetHookAdd(interpreter)));
LoadSubCommand(
- "enable",
- CommandObjectSP(new CommandObjectTargetModuleHookEnableDisable(
- interpreter, true, "target modulehook enable [<id>]",
- "Enable a module-hook.", "target modulehook enable")));
+ "add-filter",
+ CommandObjectSP(new CommandObjectTargetHookAddFilter(interpreter)));
+ LoadSubCommand("delete", CommandObjectSP(new CommandObjectTargetHookDelete(
+ interpreter)));
+ LoadSubCommand("disable",
+ CommandObjectSP(new CommandObjectTargetHookEnableDisable(
+ interpreter, false, "target hook disable",
+ "Disable a hook or a specific event on a hook.",
+ "target hook disable [<event-type>] [<id> ...]")));
+ LoadSubCommand("enable",
+ CommandObjectSP(new CommandObjectTargetHookEnableDisable(
+ interpreter, true, "target hook enable",
+ "Enable a hook or a specific event on a hook.",
+ "target hook enable [<event-type>] [<id> ...]")));
LoadSubCommand(
- "list",
- CommandObjectSP(new CommandObjectTargetModuleHookList(interpreter)));
+ "list", CommandObjectSP(new CommandObjectTargetHookList(interpreter)));
}
- ~CommandObjectMultiwordTargetModuleHooks() override = default;
+ ~CommandObjectMultiwordTargetHooks() override = default;
};
#pragma mark CommandObjectTargetDumpTypesystem
@@ -5909,9 +6247,8 @@ CommandObjectMultiwordTarget::CommandObjectMultiwordTarget(
LoadSubCommand(
"stop-hook",
CommandObjectSP(new CommandObjectMultiwordTargetStopHooks(interpreter)));
- LoadSubCommand("modulehook",
- CommandObjectSP(
- new CommandObjectMultiwordTargetModuleHooks(interpreter)));
+ LoadSubCommand("hook", CommandObjectSP(new CommandObjectMultiwordTargetHooks(
+ interpreter)));
LoadSubCommand("modules",
CommandObjectSP(new CommandObjectTargetModules(interpreter)));
LoadSubCommand("symbols",
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index a8dc519a77bec..debc8837f20f0 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1901,15 +1901,82 @@ let Command = "target stop_hook list" in {
Desc<"Show debugger ${i}nternal stop hooks.">;
}
-let Command = "target modulehook add" in {
- def target_modulehook_add_one_liner
+let Command = "target hook add" in {
+ def target_hook_add_one_liner
: Option<"one-liner", "o">,
Arg<"OneLiner">,
- Desc<"Add a command for the module hook. Can be specified more than "
+ Desc<"Add a command for the hook. Can be specified more than "
"once, and commands will be run in the order they appear.">;
- def target_modulehook_add_on_unload
+ def target_hook_add_on_unload
: Option<"on-unload", "u">,
Desc<"Also fire the hook when modules are ${u}nloaded.">;
+ def target_hook_add_on_stop
+ : Option<"on-stop", "S">,
+ Desc<"Also fire the hook when the process ${S}tops.">;
+}
+
+let Command = "target hook add_filter" in {
+ def target_hook_add_filter_shlib
+ : Option<"shlib", "s">,
+ Arg<"ShlibName">,
+ Completion<"Module">,
+ Desc<"Set the module within which the stop handler is to be run.">;
+ def target_hook_add_filter_thread_index
+ : Option<"thread-index", "x">,
+ Arg<"ThreadIndex">,
+ Desc<"The stop handler is run only for the thread whose inde${x} "
+ "matches this argument.">;
+ def target_hook_add_filter_thread_id
+ : Option<"thread-id", "t">,
+ Arg<"ThreadID">,
+ Desc<"The stop handler is run only for the ${t}hread whose TID "
+ "matches this argument.">;
+ def target_hook_add_filter_thread_name
+ : Option<"thread-name", "T">,
+ Arg<"ThreadName">,
+ Desc<"The stop handler is run only for the ${T}hread whose thread "
+ "name matches this argument.">;
+ def target_hook_add_filter_queue_name
+ : Option<"queue-name", "q">,
+ Arg<"QueueName">,
+ Desc<"The stop handler is run only for threads in the ${q}ueue whose "
+ "name is given by this argument.">;
+ def target_hook_add_filter_file
+ : Option<"file", "f">,
+ Arg<"Filename">,
+ Desc<"Specify the source ${f}ile within which the stop handler is "
+ "to be run.">,
+ Completion<"SourceFile">;
+ def target_hook_add_filter_start_line
+ : Option<"start-line", "l">,
+ Arg<"LineNum">,
+ Desc<"Set the start of the ${l}ine range for which the stop handler "
+ "is to be run.">;
+ def target_hook_add_filter_end_line
+ : Option<"end-line", "e">,
+ Arg<"LineNum">,
+ Desc<"Set the ${e}nd of the line range for which the stop handler "
+ "is to be run.">;
+ def target_hook_add_filter_classname
+ : Option<"classname", "c">,
+ Arg<"ClassName">,
+ Desc<"Specify the ${c}lass within which the stop handler is to "
+ "be run.">;
+ def target_hook_add_filter_name
+ : Option<"name", "n">,
+ Arg<"FunctionName">,
+ Desc<"Set the function ${n}ame within which the stop handler will "
+ "be run.">,
+ Completion<"Symbol">;
+ def target_hook_add_filter_auto_continue
+ : Option<"auto-continue", "G">,
+ Arg<"Boolean">,
+ Desc<"The hook will auto-continue after running its stop handler.">;
+ def target_hook_add_filter_at_initial_stop
+ : Option<"at-initial-stop", "I">,
+ Arg<"Boolean">,
+ Desc<"Whether the stop handler will trigger when lldb ${I}nitially "
+ "gains control of the process. Defaults to true.">;
}
let Command = "thread backtrace" in {
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
index 817520bc37271..69dd9e669ad9d 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
@@ -31,7 +31,7 @@ add_lldb_library(lldbPluginScriptInterpreterPython PLUGIN
Interfaces/ScriptedProcessPythonInterface.cpp
Interfaces/ScriptedPythonInterface.cpp
Interfaces/ScriptedStopHookPythonInterface.cpp
- Interfaces/ScriptedModuleHookPythonInterface.cpp
+ Interfaces/ScriptedHookPythonInterface.cpp
Interfaces/ScriptedBreakpointPythonInterface.cpp
Interfaces/ScriptedThreadPlanPythonInterface.cpp
Interfaces/ScriptedThreadPythonInterface.cpp
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
index d7368de2bda67..9e0ee5f23735f 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
@@ -24,7 +24,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() {
ScriptedPlatformPythonInterface::Initialize();
ScriptedProcessPythonInterface::Initialize();
ScriptedStopHookPythonInterface::Initialize();
- ScriptedModuleHookPythonInterface::Initialize();
+ ScriptedHookPythonInterface::Initialize();
ScriptedBreakpointPythonInterface::Initialize();
ScriptedThreadPlanPythonInterface::Initialize();
ScriptedFrameProviderPythonInterface::Initialize();
@@ -35,7 +35,7 @@ void ScriptInterpreterPythonInterfaces::Terminate() {
ScriptedPlatformPythonInterface::Terminate();
ScriptedProcessPythonInterface::Terminate();
ScriptedStopHookPythonInterface::Terminate();
- ScriptedModuleHookPythonInterface::Terminate();
+ ScriptedHookPythonInterface::Terminate();
ScriptedBreakpointPythonInterface::Terminate();
ScriptedThreadPlanPythonInterface::Terminate();
ScriptedFrameProviderPythonInterface::Terminate();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
index 6866aabb22666..032e62d7eab9e 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
@@ -16,7 +16,7 @@
#include "ScriptedBreakpointPythonInterface.h"
#include "ScriptedFrameProviderPythonInterface.h"
#include "ScriptedFramePythonInterface.h"
-#include "ScriptedModuleHookPythonInterface.h"
+#include "ScriptedHookPythonInterface.h"
#include "ScriptedPlatformPythonInterface.h"
#include "ScriptedProcessPythonInterface.h"
#include "ScriptedStopHookPythonInterface.h"
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
new file mode 100644
index 0000000000000..0695feacabb1f
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
@@ -0,0 +1,80 @@
+//===-- ScriptedHookPythonInterface.cpp
+//------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Target/ExecutionContext.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/lldb-enumerations.h"
+
+#include "../SWIGPythonBridge.h"
+#include "../ScriptInterpreterPythonImpl.h"
+#include "../lldb-python.h"
+#include "ScriptedHookPythonInterface.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::python;
+
+ScriptedHookPythonInterface::ScriptedHookPythonInterface(
+ ScriptInterpreterPythonImpl &interpreter)
+ : ScriptedHookInterface(), ScriptedPythonInterface(interpreter) {}
+
+llvm::Expected<StructuredData::GenericSP>
+ScriptedHookPythonInterface::CreatePluginObject(
+ llvm::StringRef class_name, lldb::TargetSP target_sp,
+ const StructuredDataImpl &args_sp) {
+ return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr,
+ target_sp, args_sp);
+}
+
+void ScriptedHookPythonInterface::HandleModuleLoaded(
+ lldb::StreamSP &output_sp) {
+ Status error;
+ Dispatch("handle_module_loaded", error, output_sp);
+}
+
+void ScriptedHookPythonInterface::HandleModuleUnloaded(
+ lldb::StreamSP &output_sp) {
+ Status error;
+ Dispatch("handle_module_unloaded", error, output_sp);
+}
+
+llvm::Expected<bool>
+ScriptedHookPythonInterface::HandleStop(ExecutionContext &exe_ctx,
+ lldb::StreamSP &output_sp) {
+ ExecutionContextRefSP exe_ctx_ref_sp =
+ std::make_shared<ExecutionContextRef>(exe_ctx);
+ Status error;
+ StructuredData::ObjectSP obj =
+ Dispatch("handle_stop", error, exe_ctx_ref_sp, output_sp);
+
+ if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+ error)) {
+ if (!obj)
+ return true;
+ return error.ToError();
+ }
+
+ return obj->GetBooleanValue();
+}
+
+void ScriptedHookPythonInterface::Initialize() {
+ const std::vector<llvm::StringRef> ci_usages = {
+ "target hook add -P <script-name> [-k key -v value ...]"};
+ const std::vector<llvm::StringRef> api_usages = {};
+ PluginManager::RegisterPlugin(
+ GetPluginNameStatic(),
+ llvm::StringRef("Perform actions on target lifecycle events (module "
+ "load/unload, process stop)."),
+ CreateInstance, eScriptLanguagePython, {ci_usages, api_usages});
+}
+
+void ScriptedHookPythonInterface::Terminate() {
+ PluginManager::UnregisterPlugin(CreateInstance);
+}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
new file mode 100644
index 0000000000000..8b3f753c75038
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
@@ -0,0 +1,50 @@
+//===-- ScriptedHookPythonInterface.h ----------------------------*- C++
+//-*-===//
+//
+// 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_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDHOOKPYTHONINTERFACE_H
+#define LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDHOOKPYTHONINTERFACE_H
+
+#include "lldb/Interpreter/Interfaces/ScriptedHookInterface.h"
+
+#include "ScriptedPythonInterface.h"
+
+namespace lldb_private {
+class ScriptedHookPythonInterface : public ScriptedHookInterface,
+ public ScriptedPythonInterface,
+ public PluginInterface {
+public:
+ ScriptedHookPythonInterface(ScriptInterpreterPythonImpl &interpreter);
+
+ llvm::Expected<StructuredData::GenericSP>
+ CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
+ const StructuredDataImpl &args_sp) override;
+
+ llvm::SmallVector<AbstractMethodRequirement>
+ GetAbstractMethodRequirements() const override {
+ return llvm::SmallVector<AbstractMethodRequirement>(
+ {{"handle_module_loaded", 1}});
+ }
+
+ void HandleModuleLoaded(lldb::StreamSP &output_sp) override;
+ void HandleModuleUnloaded(lldb::StreamSP &output_sp) override;
+ llvm::Expected<bool> HandleStop(ExecutionContext &exe_ctx,
+ lldb::StreamSP &output_sp) override;
+
+ static void Initialize();
+ static void Terminate();
+
+ static llvm::StringRef GetPluginNameStatic() {
+ return "ScriptedHookPythonInterface";
+ }
+
+ llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+};
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDHOOKPYTHONINTERFACE_H
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
index 5b2de9a4a27e2..d0af4f3918951 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
@@ -50,7 +50,7 @@ void ScriptedModuleHookPythonInterface::HandleModuleUnloaded(
void ScriptedModuleHookPythonInterface::Initialize() {
const std::vector<llvm::StringRef> ci_usages = {
- "target module-hook add -P <script-name> [-k key -v value ...]"};
+ "target hook add -P <script-name> [-k key -v value ...]"};
const std::vector<llvm::StringRef> api_usages = {};
PluginManager::RegisterPlugin(
GetPluginNameStatic(),
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 827467d738686..ba93f289378e8 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1522,9 +1522,9 @@ ScriptInterpreterPythonImpl::CreateScriptedStopHookInterface() {
return std::make_shared<ScriptedStopHookPythonInterface>(*this);
}
-ScriptedModuleHookInterfaceSP
-ScriptInterpreterPythonImpl::CreateScriptedModuleHookInterface() {
- return std::make_shared<ScriptedModuleHookPythonInterface>(*this);
+ScriptedHookInterfaceSP
+ScriptInterpreterPythonImpl::CreateScriptedHookInterface() {
+ return std::make_shared<ScriptedHookPythonInterface>(*this);
}
ScriptedBreakpointInterfaceSP
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index 59c92b08e127e..863cf27785824 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -90,8 +90,7 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
lldb::ScriptedStopHookInterfaceSP CreateScriptedStopHookInterface() override;
- lldb::ScriptedModuleHookInterfaceSP
- CreateScriptedModuleHookInterface() override;
+ lldb::ScriptedHookInterfaceSP CreateScriptedHookInterface() override;
lldb::ScriptedBreakpointInterfaceSP
CreateScriptedBreakpointInterface() override;
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 50d272ffdb0a4..da6679a832232 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -38,7 +38,7 @@
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/Interfaces/ScriptedBreakpointInterface.h"
-#include "lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h"
+#include "lldb/Interpreter/Interfaces/ScriptedHookInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedStopHookInterface.h"
#include "lldb/Interpreter/OptionGroupWatchpoint.h"
#include "lldb/Interpreter/OptionValueEnumeration.h"
@@ -3153,7 +3153,16 @@ bool Target::RunStopHooks(bool at_initial_stop) {
if (is_active(hook))
active_hooks.push_back(hook);
}
- if (active_hooks.empty())
+
+ // Also collect unified hooks that fire on process stop.
+ std::vector<HookSP> active_unified_hooks;
+ for (auto &[_, hook] : m_hooks) {
+ if (hook->IsActive() && hook->FiresOn(Hook::kProcessStop) &&
+ (!at_initial_stop || hook->GetRunAtInitialStop()))
+ active_unified_hooks.push_back(hook);
+ }
+
+ if (active_hooks.empty() && active_unified_hooks.empty())
return false;
// Make sure we check that we are not stopped because of us running a user
@@ -3204,6 +3213,8 @@ bool Target::RunStopHooks(bool at_initial_stop) {
size_t num_hooks_with_output = llvm::count_if(
active_hooks, [](auto h) { return !h->GetSuppressOutput(); });
+ num_hooks_with_output += llvm::count_if(
+ active_unified_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;
@@ -3267,6 +3278,53 @@ bool Target::RunStopHooks(bool at_initial_stop) {
}
}
+ // Run unified hooks that fire on process stop.
+ for (auto cur_hook_sp : active_unified_hooks) {
+ bool any_thread_matched = false;
+ for (auto exc_ctx : exc_ctx_with_reasons) {
+ if (!cur_hook_sp->ExecutionContextPasses(exc_ctx))
+ continue;
+
+ 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)
+ output_sp->Printf("\n- Hook %" PRIu64 " (%s)\n", cur_hook_sp->GetID(),
+ s.GetData());
+ else
+ output_sp->Printf("\n- Hook %" PRIu64 "\n", cur_hook_sp->GetID());
+ any_thread_matched = true;
+ }
+
+ if (print_thread_header && !suppress_output)
+ output_sp->Printf("-- Thread %d\n",
+ exc_ctx.GetThreadPtr()->GetIndexID());
+
+ auto result = cur_hook_sp->HandleStop(exc_ctx, output_sp);
+ switch (result) {
+ case StopHook::StopHookResult::KeepStopped:
+ if (cur_hook_sp->GetAutoContinue())
+ requested_continue = true;
+ else
+ should_stop = true;
+ break;
+ case StopHook::StopHookResult::RequestContinue:
+ requested_continue = true;
+ break;
+ case StopHook::StopHookResult::NoPreference:
+ break;
+ case StopHook::StopHookResult::AlreadyContinued:
+ output_sp->Printf("\nAborting stop hooks, hook %" PRIu64
+ " set the program running.\n"
+ " Consider using '-G true' to make "
+ "stop hooks auto-continue.\n",
+ cur_hook_sp->GetID());
+ return true;
+ }
+ }
+ }
+
// Resume iff at least one hook requested to continue and no hook asked to
// stop.
if (requested_continue && !should_stop) {
@@ -4252,45 +4310,111 @@ void Target::StopHookScripted::GetSubclassDescription(
as_dict->ForEach(print_one_element);
}
-// ModuleHook
+// Hook
-Target::ModuleHook::ModuleHook(lldb::TargetSP target_sp, lldb::user_id_t uid)
+Target::Hook::Hook(lldb::TargetSP target_sp, lldb::user_id_t uid)
: UserID(uid), m_target_sp(std::move(target_sp)) {}
-Target::ModuleHook::ModuleHook(const ModuleHook &rhs)
+Target::Hook::Hook(const Hook &rhs)
: UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), m_active(rhs.m_active),
- m_fire_on_unload(rhs.m_fire_on_unload) {}
+ m_event_mask(rhs.m_event_mask), m_specifier_sp(rhs.m_specifier_sp),
+ m_auto_continue(rhs.m_auto_continue),
+ m_at_initial_stop(rhs.m_at_initial_stop),
+ m_suppress_output(rhs.m_suppress_output) {
+ if (rhs.m_thread_spec_up)
+ m_thread_spec_up = std::make_unique<ThreadSpec>(*rhs.m_thread_spec_up);
+}
+
+void Target::Hook::SetSpecifier(SymbolContextSpecifier *specifier) {
+ m_specifier_sp.reset(specifier);
+}
+
+void Target::Hook::SetThreadSpecifier(ThreadSpec *specifier) {
+ m_thread_spec_up.reset(specifier);
+}
+
+bool Target::Hook::ExecutionContextPasses(const ExecutionContext &exc_ctx) {
+ SymbolContextSpecifier *specifier = GetSpecifier();
+ if (!specifier)
+ return true;
+
+ bool will_run = true;
+ if (exc_ctx.GetFramePtr())
+ will_run = GetSpecifier()->SymbolContextMatches(
+ exc_ctx.GetFramePtr()->GetSymbolContext(eSymbolContextEverything));
+ if (will_run && GetThreadSpecifier() != nullptr)
+ will_run =
+ GetThreadSpecifier()->ThreadPassesBasicTests(exc_ctx.GetThreadRef());
-void Target::ModuleHook::GetDescription(Stream &s,
- lldb::DescriptionLevel level) const {
+ return will_run;
+}
+
+void Target::Hook::GetDescription(Stream &s,
+ lldb::DescriptionLevel level) const {
s.Printf("Hook: %" PRIu64 "\n", GetID());
if (level == eDescriptionLevelBrief)
return;
s.IndentMore();
s.Indent();
s.Printf("State: %s\n", m_active ? "enabled" : "disabled");
- if (m_fire_on_unload) {
+
+ // Show which events this hook fires on (only if not the default: load only)
+ if (m_event_mask != kModulesLoaded) {
+ std::string fires_on;
+ if (m_event_mask & kModulesLoaded)
+ fires_on += "load";
+ if (m_event_mask & kModulesUnloaded) {
+ if (!fires_on.empty())
+ fires_on += ", ";
+ fires_on += "unload";
+ }
+ if (m_event_mask & kProcessStop) {
+ if (!fires_on.empty())
+ fires_on += ", ";
+ fires_on += "stop";
+ }
s.Indent();
- s.PutCString("Fires on: load, unload\n");
+ s.Printf("Fires on: %s\n", fires_on.c_str());
+ }
+
+ if (m_auto_continue)
+ s.Indent("AutoContinue on\n");
+
+ if (m_specifier_sp) {
+ s.Indent();
+ s.PutCString("Specifier:\n");
+ s.IndentMore();
+ m_specifier_sp->GetDescription(&s, level);
+ s.IndentLess();
+ }
+
+ if (m_thread_spec_up) {
+ StreamString tmp;
+ s.Indent("Thread:\n");
+ m_thread_spec_up->GetDescription(&tmp, level);
+ s.IndentMore();
+ s.Indent(tmp.GetString());
+ s.PutCString("\n");
+ s.IndentLess();
}
+
GetSubclassDescription(s, level);
s.IndentLess();
}
-// ModuleHookCommandLine
+// HookCommandLine
-void Target::ModuleHookCommandLine::SetActionFromString(
- const std::string &string) {
+void Target::HookCommandLine::SetActionFromString(const std::string &string) {
GetCommands().SplitIntoLines(string);
}
-void Target::ModuleHookCommandLine::SetActionFromStrings(
+void Target::HookCommandLine::SetActionFromStrings(
const std::vector<std::string> &strings) {
for (const auto &string : strings)
GetCommands().AppendString(string.c_str());
}
-void Target::ModuleHookCommandLine::GetSubclassDescription(
+void Target::HookCommandLine::GetSubclassDescription(
Stream &s, lldb::DescriptionLevel level) const {
if (level == eDescriptionLevelBrief) {
if (m_commands.GetSize() == 1)
@@ -4309,7 +4433,7 @@ void Target::ModuleHookCommandLine::GetSubclassDescription(
s.IndentLess();
}
-void Target::ModuleHookCommandLine::HandleModuleLoaded(StreamSP output_sp) {
+void Target::HookCommandLine::HandleModuleLoaded(StreamSP output_sp) {
if (!m_commands.GetSize())
return;
@@ -4343,25 +4467,58 @@ void Target::ModuleHookCommandLine::HandleModuleLoaded(StreamSP output_sp) {
debugger.SetAsyncExecution(old_async);
}
-void Target::ModuleHookCommandLine::HandleModuleUnloaded(StreamSP output_sp) {
+void Target::HookCommandLine::HandleModuleUnloaded(StreamSP output_sp) {
// Command-based hooks run the same commands on unload as on load.
HandleModuleLoaded(output_sp);
}
-// ModuleHookScripted
+Target::StopHook::StopHookResult
+Target::HookCommandLine::HandleStop(ExecutionContext &exc_ctx,
+ StreamSP output_sp) {
+ assert(exc_ctx.GetTargetPtr() && "Can't call HandleStop on a context "
+ "with no target");
+
+ if (!m_commands.GetSize())
+ return StopHook::StopHookResult::KeepStopped;
+
+ CommandReturnObject result(false);
+ result.SetImmediateOutputStream(output_sp);
+ result.SetInteractive(false);
+ Debugger &debugger = exc_ctx.GetTargetPtr()->GetDebugger();
+ CommandInterpreterRunOptions options;
+ options.SetStopOnContinue(true);
+ options.SetStopOnError(true);
+ options.SetEchoCommands(false);
+ options.SetPrintResults(true);
+ options.SetPrintErrors(true);
+ options.SetAddToHistory(false);
+
+ bool old_async = debugger.GetAsyncExecution();
+ debugger.SetAsyncExecution(true);
+ debugger.GetCommandInterpreter().HandleCommands(GetCommands(), exc_ctx,
+ options, result);
+ debugger.SetAsyncExecution(old_async);
+ lldb::ReturnStatus status = result.GetStatus();
+ if (status == eReturnStatusSuccessContinuingNoResult ||
+ status == eReturnStatusSuccessContinuingResult)
+ return StopHook::StopHookResult::AlreadyContinued;
+ return StopHook::StopHookResult::KeepStopped;
+}
+
+// HookScripted
-Status Target::ModuleHookScripted::SetScriptCallback(
+Status Target::HookScripted::SetScriptCallback(
std::string class_name, StructuredData::ObjectSP extra_args_sp) {
ScriptInterpreter *script_interp =
GetTarget()->GetDebugger().GetScriptInterpreter();
if (!script_interp)
return Status::FromErrorString("No script interpreter installed.");
- m_interface_sp = script_interp->CreateScriptedModuleHookInterface();
+ m_interface_sp = script_interp->CreateScriptedHookInterface();
if (!m_interface_sp)
return Status::FromErrorStringWithFormat(
- "ScriptedModuleHook::%s () - ERROR: %s", __FUNCTION__,
- "Script interpreter couldn't create Scripted Module Hook Interface");
+ "ScriptedHook::%s () - ERROR: %s", __FUNCTION__,
+ "Script interpreter couldn't create Scripted Hook Interface");
m_class_name = std::move(class_name);
m_extra_args.SetObjectSP(extra_args_sp);
@@ -4374,13 +4531,13 @@ Status Target::ModuleHookScripted::SetScriptCallback(
StructuredData::ObjectSP object_sp = *obj_or_err;
if (!object_sp || !object_sp->IsValid())
return Status::FromErrorStringWithFormat(
- "ScriptedModuleHook::%s () - ERROR: %s", __FUNCTION__,
+ "ScriptedHook::%s () - ERROR: %s", __FUNCTION__,
"Failed to create valid script object");
return {};
}
-void Target::ModuleHookScripted::HandleModuleLoaded(StreamSP output_sp) {
+void Target::HookScripted::HandleModuleLoaded(StreamSP output_sp) {
if (!m_interface_sp)
return;
@@ -4390,7 +4547,7 @@ void Target::ModuleHookScripted::HandleModuleLoaded(StreamSP output_sp) {
reinterpret_cast<StreamString *>(stream.get())->GetData());
}
-void Target::ModuleHookScripted::HandleModuleUnloaded(StreamSP output_sp) {
+void Target::HookScripted::HandleModuleUnloaded(StreamSP output_sp) {
if (!m_interface_sp)
return;
@@ -4400,7 +4557,27 @@ void Target::ModuleHookScripted::HandleModuleUnloaded(StreamSP output_sp) {
reinterpret_cast<StreamString *>(stream.get())->GetData());
}
-void Target::ModuleHookScripted::GetSubclassDescription(
+Target::StopHook::StopHookResult
+Target::HookScripted::HandleStop(ExecutionContext &exc_ctx,
+ StreamSP output_sp) {
+ assert(exc_ctx.GetTargetPtr() && "Can't call HandleStop on a context "
+ "with no target");
+
+ if (!m_interface_sp)
+ return StopHook::StopHookResult::KeepStopped;
+
+ lldb::StreamSP stream = std::make_shared<lldb_private::StreamString>();
+ auto should_stop_or_err = m_interface_sp->HandleStop(exc_ctx, stream);
+ output_sp->PutCString(
+ reinterpret_cast<StreamString *>(stream.get())->GetData());
+ if (!should_stop_or_err)
+ return StopHook::StopHookResult::KeepStopped;
+
+ return *should_stop_or_err ? StopHook::StopHookResult::KeepStopped
+ : StopHook::StopHookResult::RequestContinue;
+}
+
+void Target::HookScripted::GetSubclassDescription(
Stream &s, lldb::DescriptionLevel level) const {
if (level == eDescriptionLevelBrief) {
s.PutCString(m_class_name);
@@ -4437,76 +4614,77 @@ void Target::ModuleHookScripted::GetSubclassDescription(
s.IndentLess();
}
-// Module Hook management methods
+// Hook management methods
-Target::ModuleHookSP Target::CreateModuleHook(ModuleHook::ModuleHookKind kind) {
- lldb::user_id_t new_uid = ++m_module_hook_next_id;
- ModuleHookSP hook_sp;
+Target::HookSP Target::CreateHook(Hook::HookKind kind) {
+ lldb::user_id_t new_uid = ++m_hook_next_id;
+ HookSP hook_sp;
switch (kind) {
- case ModuleHook::ModuleHookKind::CommandBased:
- hook_sp.reset(new ModuleHookCommandLine(shared_from_this(), new_uid));
+ case Hook::HookKind::CommandBased:
+ hook_sp.reset(new HookCommandLine(shared_from_this(), new_uid));
break;
- case ModuleHook::ModuleHookKind::ScriptBased:
- hook_sp.reset(new ModuleHookScripted(shared_from_this(), new_uid));
+ case Hook::HookKind::ScriptBased:
+ hook_sp.reset(new HookScripted(shared_from_this(), new_uid));
break;
}
- m_module_hooks[new_uid] = hook_sp;
+ m_hooks[new_uid] = hook_sp;
return hook_sp;
}
-void Target::UndoCreateModuleHook(lldb::user_id_t uid) {
- if (!RemoveModuleHookByID(uid))
+void Target::UndoCreateHook(lldb::user_id_t uid) {
+ if (!RemoveHookByID(uid))
return;
if (uid > 0)
- --m_module_hook_next_id;
+ --m_hook_next_id;
}
-bool Target::RemoveModuleHookByID(lldb::user_id_t uid) {
- size_t num_removed = m_module_hooks.erase(uid);
+bool Target::RemoveHookByID(lldb::user_id_t uid) {
+ size_t num_removed = m_hooks.erase(uid);
return (num_removed != 0);
}
-void Target::RemoveAllModuleHooks() { m_module_hooks.clear(); }
+void Target::RemoveAllHooks() { m_hooks.clear(); }
-Target::ModuleHookSP Target::GetModuleHookByID(lldb::user_id_t uid) {
- auto iter = m_module_hooks.find(uid);
- if (iter == m_module_hooks.end())
+Target::HookSP Target::GetHookByID(lldb::user_id_t uid) {
+ auto iter = m_hooks.find(uid);
+ if (iter == m_hooks.end())
return {};
return iter->second;
}
-Target::ModuleHookSP Target::GetModuleHookAtIndex(size_t index) {
- if (index >= m_module_hooks.size())
+Target::HookSP Target::GetHookAtIndex(size_t index) {
+ if (index >= m_hooks.size())
return {};
- auto iter = m_module_hooks.begin();
+ auto iter = m_hooks.begin();
std::advance(iter, index);
return iter->second;
}
-bool Target::SetModuleHookActiveStateByID(lldb::user_id_t uid,
- bool active_state) {
- auto iter = m_module_hooks.find(uid);
- if (iter == m_module_hooks.end())
+bool Target::SetHookActiveStateByID(lldb::user_id_t uid, bool active_state) {
+ auto iter = m_hooks.find(uid);
+ if (iter == m_hooks.end())
return false;
iter->second->SetIsActive(active_state);
return true;
}
-void Target::SetAllModuleHooksActiveState(bool active_state) {
- for (auto &[_, hook] : m_module_hooks)
+void Target::SetAllHooksActiveState(bool active_state) {
+ for (auto &[_, hook] : m_hooks)
hook->SetIsActive(active_state);
}
void Target::RunModuleHooks(bool is_load) {
- if (m_module_hooks.empty())
+ if (m_hooks.empty())
return;
+ uint32_t event = is_load ? Hook::kModulesLoaded : Hook::kModulesUnloaded;
+
StreamSP output_sp = m_debugger.GetAsyncOutputStream();
- for (auto &[_, hook_sp] : m_module_hooks) {
+ for (auto &[_, hook_sp] : m_hooks) {
if (!hook_sp->IsActive())
continue;
- if (!is_load && !hook_sp->GetFireOnUnload())
+ if (!hook_sp->FiresOn(event))
continue;
if (is_load)
hook_sp->HandleModuleLoaded(output_sp);
diff --git a/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py b/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
index a071ecd73e883..28ac8293cca03 100644
--- a/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
+++ b/lldb/test/API/commands/target/module-hook/delete/TestTargetModuleHookDelete.py
@@ -8,12 +8,12 @@ class TestCase(TestBase):
@no_debug_info_test
def test_invalid_arg(self):
self.expect(
- "target modulehook delete -1",
+ "target hook delete -1",
error=True,
- startstr='error: invalid module hook id: "-1".',
+ startstr='error: invalid hook id: "-1".',
)
self.expect(
- "target modulehook delete abcdfx",
+ "target hook delete abcdfx",
error=True,
- startstr='error: invalid module hook id: "abcdfx".',
+ startstr='error: invalid hook id: "abcdfx".',
)
diff --git a/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py b/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
index 1541b0006b0c0..3a77a1da5f8e0 100644
--- a/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
+++ b/lldb/test/API/commands/target/module-hook/disable/TestTargetModuleHookDisable.py
@@ -8,12 +8,12 @@ class TestCase(TestBase):
@no_debug_info_test
def test_invalid_arg(self):
self.expect(
- "target modulehook disable -1",
+ "target hook disable -1",
error=True,
- startstr='error: invalid module hook id: "-1".',
+ startstr='error: invalid hook id: "-1".',
)
self.expect(
- "target modulehook disable abcdfx",
+ "target hook disable abcdfx",
error=True,
- startstr='error: invalid module hook id: "abcdfx".',
+ startstr='error: invalid hook id: "abcdfx".',
)
diff --git a/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py b/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
index dbb16cd10022b..8ab024b88c2d3 100644
--- a/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
+++ b/lldb/test/API/commands/target/module-hook/enable/TestTargetModuleHookEnable.py
@@ -8,12 +8,12 @@ class TestCase(TestBase):
@no_debug_info_test
def test_invalid_arg(self):
self.expect(
- "target modulehook enable -1",
+ "target hook enable -1",
error=True,
- startstr='error: invalid module hook id: "-1".',
+ startstr='error: invalid hook id: "-1".',
)
self.expect(
- "target modulehook enable abcdfx",
+ "target hook enable abcdfx",
error=True,
- startstr='error: invalid module hook id: "abcdfx".',
+ startstr='error: invalid hook id: "abcdfx".',
)
diff --git a/lldb/test/Shell/Commands/command-module-hook-fire.test b/lldb/test/Shell/Commands/command-module-hook-fire.test
index 5876ea05fb1fe..28ccd853bc803 100644
--- a/lldb/test/Shell/Commands/command-module-hook-fire.test
+++ b/lldb/test/Shell/Commands/command-module-hook-fire.test
@@ -2,9 +2,9 @@
#
# RUN: %clang_host -g %S/Inputs/main.c -o %t
# RUN: %lldb -b -o 'file %t' \
-# RUN: -o 'target modulehook add -o "script print(\"HOOK_FIRED\")"' \
+# RUN: -o 'target hook add -o "script print(\"HOOK_FIRED\")"' \
# RUN: -o 'target modules add %t' \
# RUN: 2>&1 | FileCheck %s
-# CHECK: Module hook #1 added.
+# CHECK: Hook #1 added.
# CHECK: HOOK_FIRED
diff --git a/lldb/test/Shell/Commands/command-module-hook.test b/lldb/test/Shell/Commands/command-module-hook.test
index 4118a6f18501a..58676881d902a 100644
--- a/lldb/test/Shell/Commands/command-module-hook.test
+++ b/lldb/test/Shell/Commands/command-module-hook.test
@@ -1,18 +1,18 @@
-# Test module hook add/list/delete/enable/disable commands.
+# Test hook add/list/delete/enable/disable/add-filter commands.
#
# RUN: %lldb -b -s %s 2>&1 | FileCheck %s
# Adding hooks assigns incrementing IDs.
-target modulehook add -o "script print('hook1')"
-# CHECK: Module hook #1 added.
-target modulehook add -o "script print('hook2')"
-# CHECK: Module hook #2 added.
-target modulehook add -o "script print('hook3')"
-# CHECK: Module hook #3 added.
+target hook add -o "script print('hook1')"
+# CHECK: Hook #1 added.
+target hook add -o "script print('hook2')"
+# CHECK: Hook #2 added.
+target hook add -o "script print('hook3')"
+# CHECK: Hook #3 added.
# List all hooks.
-target modulehook list
-# CHECK: (lldb) target modulehook list
+target hook list
+# CHECK: (lldb) target hook list
# CHECK: Hook: 1
# CHECK: State: enabled
# CHECK: Commands:
@@ -27,9 +27,9 @@ target modulehook list
# CHECK: script print('hook3')
# Disable a hook.
-target modulehook disable 2
-target modulehook list
-# CHECK: (lldb) target modulehook list
+target hook disable 2
+target hook list
+# CHECK: (lldb) target hook list
# CHECK: Hook: 1
# CHECK: State: enabled
# CHECK: Hook: 2
@@ -38,9 +38,9 @@ target modulehook list
# CHECK: State: enabled
# Re-enable the hook.
-target modulehook enable 2
-target modulehook list
-# CHECK: (lldb) target modulehook list
+target hook enable 2
+target hook list
+# CHECK: (lldb) target hook list
# CHECK: Hook: 1
# CHECK: State: enabled
# CHECK: Hook: 2
@@ -49,34 +49,34 @@ target modulehook list
# CHECK: State: enabled
# Delete a hook in the middle.
-target modulehook delete 2
-target modulehook list
-# CHECK: (lldb) target modulehook list
+target hook delete 2
+target hook list
+# CHECK: (lldb) target hook list
# CHECK: Hook: 1
# CHECK-NOT: Hook: 2
# CHECK: Hook: 3
# New hook gets a new ID (not reused).
-target modulehook add -o "script print('hook4')"
-# CHECK: Module hook #4 added.
-target modulehook list
-# CHECK: (lldb) target modulehook list
+target hook add -o "script print('hook4')"
+# CHECK: Hook #4 added.
+target hook list
+# CHECK: (lldb) target hook list
# CHECK: Hook: 1
# CHECK-NOT: Hook: 2
# CHECK: Hook: 3
# CHECK: Hook: 4
# Delete all hooks.
-target modulehook delete
-target modulehook list
-# CHECK: (lldb) target modulehook list
-# CHECK: No module hooks.
+target hook delete
+target hook list
+# CHECK: (lldb) target hook list
+# CHECK: No hooks.
# Add a hook with --on-unload and verify the description.
-target modulehook add -u -o "script print('load+unload')"
-# CHECK: Module hook #5 added.
-target modulehook list
-# CHECK: (lldb) target modulehook list
+target hook add -u -o "script print('load+unload')"
+# CHECK: Hook #5 added.
+target hook list
+# CHECK: (lldb) target hook list
# CHECK: Hook: 5
# CHECK: State: enabled
# CHECK: Fires on: load, unload
@@ -84,9 +84,9 @@ target modulehook list
# CHECK: script print('load+unload')
# Add a hook with multiple one-liner commands.
-target modulehook add -o "script print('first')" -o "script print('second')"
-# CHECK: Module hook #6 added.
-target modulehook list
+target hook add -o "script print('first')" -o "script print('second')"
+# CHECK: Hook #6 added.
+target hook list
# CHECK: Hook: 6
# CHECK: State: enabled
# CHECK-NOT: Fires on:
@@ -95,19 +95,67 @@ target modulehook list
# CHECK: script print('second')
# Disable all hooks.
-target modulehook disable
-target modulehook list
-# CHECK: (lldb) target modulehook list
+target hook disable
+target hook list
+# CHECK: (lldb) target hook list
# CHECK: Hook: 5
# CHECK: State: disabled
# CHECK: Hook: 6
# CHECK: State: disabled
# Enable all hooks.
-target modulehook enable
-target modulehook list
-# CHECK: (lldb) target modulehook list
+target hook enable
+target hook list
+# CHECK: (lldb) target hook list
# CHECK: Hook: 5
# CHECK: State: enabled
# CHECK: Hook: 6
# CHECK: State: enabled
+
+# Add a hook with --on-stop.
+target hook delete
+target hook add -S -o "bt"
+# CHECK: Hook #7 added.
+target hook list
+# CHECK: (lldb) target hook list
+# CHECK: Hook: 7
+# CHECK: State: enabled
+# CHECK: Fires on: load, stop
+# CHECK: Commands:
+# CHECK: bt
+
+# Add a hook with both --on-unload and --on-stop.
+target hook add -u -S -o "script print('all events')"
+# CHECK: Hook #8 added.
+target hook list
+# CHECK: Hook: 8
+# CHECK: State: enabled
+# CHECK: Fires on: load, unload, stop
+
+# Add a filter to hook #7 using add-filter.
+target hook add-filter -s mylib.so 7
+# CHECK: Filter added to hook #7.
+target hook list
+# CHECK: (lldb) target hook list
+# CHECK: Hook: 7
+# CHECK: State: enabled
+# CHECK: Fires on: load, stop
+# CHECK: Specifier:
+# CHECK: Module: mylib.so
+
+# Per-event disable: remove stop from hook #7.
+target hook disable stop-hook 7
+target hook list
+# CHECK: (lldb) target hook list
+# CHECK: Hook: 7
+# CHECK: State: enabled
+# CHECK-NOT: Fires on: load, stop
+# CHECK: Specifier:
+
+# Per-event enable: re-add stop to hook #7.
+target hook enable stop-hook 7
+target hook list
+# CHECK: (lldb) target hook list
+# CHECK: Hook: 7
+# CHECK: State: enabled
+# CHECK: Fires on: load, stop
>From 2687fbd02f295b47c2b4aa7d2cefb4b49b8d7c9f Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 10 Mar 2026 10:44:52 -0700
Subject: [PATCH 05/12] fix format
---
.../Python/Interfaces/ScriptedHookPythonInterface.cpp | 3 +--
.../Python/Interfaces/ScriptedHookPythonInterface.h | 3 +--
.../Python/Interfaces/ScriptedModuleHookPythonInterface.cpp | 3 +--
3 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
index 0695feacabb1f..db217ba0414da 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
@@ -1,5 +1,4 @@
-//===-- ScriptedHookPythonInterface.cpp
-//------------------------------------===//
+//===-- ScriptedHookPythonInterface.cpp ------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
index 8b3f753c75038..d0d00d8e9e803 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
@@ -1,5 +1,4 @@
-//===-- ScriptedHookPythonInterface.h ----------------------------*- C++
-//-*-===//
+//===-- ScriptedHookPythonInterface.h ----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
index d0af4f3918951..f10e78b806c76 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
@@ -1,5 +1,4 @@
-//===-- ScriptedModuleHookPythonInterface.cpp
-//------------------------------===//
+//===-- ScriptedModuleHookPythonInterface.cpp ------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
>From 056622b05f05e1d8750f84d76d26197801386c7f Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 10 Mar 2026 11:05:38 -0700
Subject: [PATCH 06/12] Fixed format and small bug where we didn't copy m_hooks
and m_hook_next_id
---
.../include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h | 2 +-
.../lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h | 2 +-
.../Python/Interfaces/ScriptedHookPythonInterface.cpp | 2 +-
.../Python/Interfaces/ScriptedHookPythonInterface.h | 2 +-
.../Python/Interfaces/ScriptedModuleHookPythonInterface.cpp | 2 +-
.../Python/Interfaces/ScriptedModuleHookPythonInterface.h | 2 +-
lldb/source/Target/Target.cpp | 2 ++
7 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
index 03053ed26879c..1ea76e2ab875f 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
@@ -26,7 +26,7 @@ class ScriptedHookInterface : public ScriptedInterface {
/// Called when modules are unloaded from the target. Optional.
virtual void HandleModuleUnloaded(lldb::StreamSP &output_sp) {}
- /// Called when the process stops. Returns "should_stop" -- if false, the
+ /// Called when the process stops. Returns "should_stop" if false, the
/// process will continue. Defaults to true (stop on unimplemented).
virtual llvm::Expected<bool> HandleStop(ExecutionContext &exe_ctx,
lldb::StreamSP &output_sp) {
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
index bbf9531045cde..e5a95384cac56 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
@@ -1,4 +1,4 @@
-//===-- ScriptedModuleHookInterface.h ----------------------------*- C++ -*-===//
+//===-- ScriptedModuleHookInterface.h ---------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
index db217ba0414da..78ca14c99bb46 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
@@ -1,4 +1,4 @@
-//===-- ScriptedHookPythonInterface.cpp ------------------------------------===//
+//===-- ScriptedHookPythonInterface.cpp -----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
index d0d00d8e9e803..9e8b8f7605992 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
@@ -1,4 +1,4 @@
-//===-- ScriptedHookPythonInterface.h ----------------------------*- C++ -*-===//
+//===-- ScriptedHookPythonInterface.h ---------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
index f10e78b806c76..1147aaf6cbd5c 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
@@ -1,4 +1,4 @@
-//===-- ScriptedModuleHookPythonInterface.cpp ------------------------------===//
+//===-- ScriptedModuleHookPythonInterface.cpp -----------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
index c63db160e7dc8..3fdb392352b3b 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
@@ -1,4 +1,4 @@
-//===-- ScriptedModuleHookPythonInterface.h ----------------------*- C++ -*-===//
+//===-- ScriptedModuleHookPythonInterface.h ---------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index da6679a832232..aabb16d2f651d 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -223,6 +223,8 @@ 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;
+ m_hooks = target.m_hooks;
+ m_hook_next_id = target.m_hook_next_id;
for (const auto &breakpoint_sp : target.m_breakpoint_list.Breakpoints()) {
if (breakpoint_sp->IsInternal())
>From 4a5028177603cb62e227f3f64e8856c242ac9950 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Thu, 12 Mar 2026 10:37:51 -0700
Subject: [PATCH 07/12] Fixed recent comments
---
lldb/include/lldb/Target/Target.h | 35 ++++++----
lldb/source/Commands/CommandObjectTarget.cpp | 2 +-
lldb/source/Target/Target.cpp | 68 ++++++++++++--------
3 files changed, 63 insertions(+), 42 deletions(-)
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 085318d9fdefa..428cc627da9fb 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1671,6 +1671,9 @@ class Target : public std::enable_shared_from_this<Target>,
enum class HookKind : uint32_t { CommandBased = 0, ScriptBased };
/// Event mask bits controlling when this hook fires.
+ // FIXME: Add more event types as needed, e.g.:
+ // kProcessExited (fires when the process exits).
+ // kProcessDetached (fires when the debugger detaches).
enum EventMask : uint32_t {
kModulesLoaded = (1u << 0),
kModulesUnloaded = (1u << 1),
@@ -1688,17 +1691,19 @@ class Target : public std::enable_shared_from_this<Target>,
void RemoveEvent(uint32_t event) { m_event_mask &= ~event; }
bool FiresOn(uint32_t event) const { return m_event_mask & event; }
- // Stop-hook features (only relevant when kProcessStop is set)
+ // Filter fields
- /// Set the specifier. The hook will own the specifier.
- void SetSpecifier(SymbolContextSpecifier *specifier);
- SymbolContextSpecifier *GetSpecifier() { return m_specifier_sp.get(); }
+ /// Set the symbol context specifier. The hook takes ownership.
+ void SetSCSpecifier(SymbolContextSpecifier *specifier);
+ SymbolContextSpecifier *GetSCSpecifier() {
+ return m_sc_specifier_sp.get();
+ }
/// Check if the execution context passes the specifier and thread spec
/// filters. Always returns true if no filters are set.
bool ExecutionContextPasses(const ExecutionContext &exe_ctx);
- /// Set the thread specifier. The hook will own the thread specifier.
+ /// Set the thread specifier. The hook takes ownership.
void SetThreadSpecifier(ThreadSpec *specifier);
ThreadSpec *GetThreadSpecifier() { return m_thread_spec_up.get(); }
@@ -1727,17 +1732,15 @@ class Target : public std::enable_shared_from_this<Target>,
virtual StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
lldb::StreamSP output) = 0;
- void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
- virtual void GetSubclassDescription(Stream &s,
- lldb::DescriptionLevel level) const = 0;
+ virtual void GetDescription(Stream &s,
+ lldb::DescriptionLevel level) const;
protected:
lldb::TargetSP m_target_sp;
bool m_active = true;
uint32_t m_event_mask = kModulesLoaded; // Default: fire on load only
- // Stop-hook filter fields (only used when kProcessStop is set)
- lldb::SymbolContextSpecifierSP m_specifier_sp;
+ lldb::SymbolContextSpecifierSP m_sc_specifier_sp;
std::unique_ptr<ThreadSpec> m_thread_spec_up;
bool m_auto_continue = false;
bool m_at_initial_stop = true;
@@ -1751,11 +1754,15 @@ class Target : public std::enable_shared_from_this<Target>,
~HookCommandLine() override = default;
StringList &GetCommands() { return m_commands; }
+
+ /// Populate the command list by splitting a single string on newlines.
void SetActionFromString(const std::string &string);
+
+ /// Populate the command list from a vector of individual command strings.
void SetActionFromStrings(const std::vector<std::string> &strings);
- void GetSubclassDescription(Stream &s,
- lldb::DescriptionLevel level) const override;
+ void GetDescription(Stream &s,
+ lldb::DescriptionLevel level) const override;
void HandleModuleLoaded(lldb::StreamSP output) override;
void HandleModuleUnloaded(lldb::StreamSP output) override;
StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
@@ -1773,8 +1780,8 @@ class Target : public std::enable_shared_from_this<Target>,
public:
~HookScripted() override = default;
- void GetSubclassDescription(Stream &s,
- lldb::DescriptionLevel level) const override;
+ void GetDescription(Stream &s,
+ lldb::DescriptionLevel level) const override;
void HandleModuleLoaded(lldb::StreamSP output) override;
void HandleModuleUnloaded(lldb::StreamSP output) override;
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index d2a6e77b5f458..b2c13ace935b9 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5726,7 +5726,7 @@ automatically added to its event mask.
m_options.m_function_name.c_str(),
SymbolContextSpecifier::eFunctionSpecified);
- hook_sp->SetSpecifier(specifier_up.release());
+ hook_sp->SetSCSpecifier(specifier_up.release());
}
// Set up thread specifier.
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index aabb16d2f651d..0ae8fa52bd31a 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4319,7 +4319,8 @@ Target::Hook::Hook(lldb::TargetSP target_sp, lldb::user_id_t uid)
Target::Hook::Hook(const Hook &rhs)
: UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), m_active(rhs.m_active),
- m_event_mask(rhs.m_event_mask), m_specifier_sp(rhs.m_specifier_sp),
+ m_event_mask(rhs.m_event_mask),
+ m_sc_specifier_sp(rhs.m_sc_specifier_sp),
m_auto_continue(rhs.m_auto_continue),
m_at_initial_stop(rhs.m_at_initial_stop),
m_suppress_output(rhs.m_suppress_output) {
@@ -4327,8 +4328,8 @@ Target::Hook::Hook(const Hook &rhs)
m_thread_spec_up = std::make_unique<ThreadSpec>(*rhs.m_thread_spec_up);
}
-void Target::Hook::SetSpecifier(SymbolContextSpecifier *specifier) {
- m_specifier_sp.reset(specifier);
+void Target::Hook::SetSCSpecifier(SymbolContextSpecifier *specifier) {
+ m_sc_specifier_sp.reset(specifier);
}
void Target::Hook::SetThreadSpecifier(ThreadSpec *specifier) {
@@ -4336,13 +4337,13 @@ void Target::Hook::SetThreadSpecifier(ThreadSpec *specifier) {
}
bool Target::Hook::ExecutionContextPasses(const ExecutionContext &exc_ctx) {
- SymbolContextSpecifier *specifier = GetSpecifier();
+ SymbolContextSpecifier *specifier = GetSCSpecifier();
if (!specifier)
return true;
bool will_run = true;
if (exc_ctx.GetFramePtr())
- will_run = GetSpecifier()->SymbolContextMatches(
+ will_run = specifier->SymbolContextMatches(
exc_ctx.GetFramePtr()->GetSymbolContext(eSymbolContextEverything));
if (will_run && GetThreadSpecifier() != nullptr)
will_run =
@@ -4360,7 +4361,6 @@ void Target::Hook::GetDescription(Stream &s,
s.Indent();
s.Printf("State: %s\n", m_active ? "enabled" : "disabled");
- // Show which events this hook fires on (only if not the default: load only)
if (m_event_mask != kModulesLoaded) {
std::string fires_on;
if (m_event_mask & kModulesLoaded)
@@ -4382,11 +4382,11 @@ void Target::Hook::GetDescription(Stream &s,
if (m_auto_continue)
s.Indent("AutoContinue on\n");
- if (m_specifier_sp) {
+ if (m_sc_specifier_sp) {
s.Indent();
s.PutCString("Specifier:\n");
s.IndentMore();
- m_specifier_sp->GetDescription(&s, level);
+ m_sc_specifier_sp->GetDescription(&s, level);
s.IndentLess();
}
@@ -4400,24 +4400,12 @@ void Target::Hook::GetDescription(Stream &s,
s.IndentLess();
}
- GetSubclassDescription(s, level);
s.IndentLess();
}
-// HookCommandLine
-
-void Target::HookCommandLine::SetActionFromString(const std::string &string) {
- GetCommands().SplitIntoLines(string);
-}
-
-void Target::HookCommandLine::SetActionFromStrings(
- const std::vector<std::string> &strings) {
- for (const auto &string : strings)
- GetCommands().AppendString(string.c_str());
-}
-
-void Target::HookCommandLine::GetSubclassDescription(
+void Target::HookCommandLine::GetDescription(
Stream &s, lldb::DescriptionLevel level) const {
+ Hook::GetDescription(s, level);
if (level == eDescriptionLevelBrief) {
if (m_commands.GetSize() == 1)
s.PutCString(m_commands.GetStringAtIndex(0));
@@ -4426,6 +4414,7 @@ void Target::HookCommandLine::GetSubclassDescription(
return;
}
+ s.IndentMore();
s.Indent("Commands: \n");
s.IndentMore();
for (uint32_t i = 0; i < m_commands.GetSize(); i++) {
@@ -4433,6 +4422,19 @@ void Target::HookCommandLine::GetSubclassDescription(
s.PutCString("\n");
}
s.IndentLess();
+ s.IndentLess();
+}
+
+// HookCommandLine
+
+void Target::HookCommandLine::SetActionFromString(const std::string &string) {
+ GetCommands().SplitIntoLines(string);
+}
+
+void Target::HookCommandLine::SetActionFromStrings(
+ const std::vector<std::string> &strings) {
+ for (const auto &string : strings)
+ GetCommands().AppendString(string.c_str());
}
void Target::HookCommandLine::HandleModuleLoaded(StreamSP output_sp) {
@@ -4579,28 +4581,39 @@ Target::HookScripted::HandleStop(ExecutionContext &exc_ctx,
: StopHook::StopHookResult::RequestContinue;
}
-void Target::HookScripted::GetSubclassDescription(
+void Target::HookScripted::GetDescription(
Stream &s, lldb::DescriptionLevel level) const {
+ Hook::GetDescription(s, level);
if (level == eDescriptionLevelBrief) {
s.PutCString(m_class_name);
return;
}
+
+ s.IndentMore();
s.Indent("Class:");
s.Printf("%s\n", m_class_name.c_str());
- if (!m_extra_args.IsValid())
+ if (!m_extra_args.IsValid()) {
+ s.IndentLess();
return;
+ }
StructuredData::ObjectSP object_sp = m_extra_args.GetObjectSP();
- if (!object_sp || !object_sp->IsValid())
+ if (!object_sp || !object_sp->IsValid()) {
+ s.IndentLess();
return;
+ }
StructuredData::Dictionary *as_dict = object_sp->GetAsDictionary();
- if (!as_dict || !as_dict->IsValid())
+ if (!as_dict || !as_dict->IsValid()) {
+ s.IndentLess();
return;
+ }
uint32_t num_keys = as_dict->GetSize();
- if (num_keys == 0)
+ if (num_keys == 0) {
+ s.IndentLess();
return;
+ }
s.Indent("Args:\n");
s.IndentMore();
@@ -4614,6 +4627,7 @@ void Target::HookScripted::GetSubclassDescription(
as_dict->ForEach(print_one_element);
s.IndentLess();
+ s.IndentLess();
}
// Hook management methods
>From e24a2d993530c5ed725b238a569c0722b8ad092a Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Thu, 12 Mar 2026 10:40:40 -0700
Subject: [PATCH 08/12] More fixes/comments
---
lldb/include/lldb/Target/Target.h | 14 +++++---------
lldb/source/Target/Target.cpp | 7 +++----
2 files changed, 8 insertions(+), 13 deletions(-)
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 428cc627da9fb..1d7094878562d 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1695,9 +1695,7 @@ class Target : public std::enable_shared_from_this<Target>,
/// Set the symbol context specifier. The hook takes ownership.
void SetSCSpecifier(SymbolContextSpecifier *specifier);
- SymbolContextSpecifier *GetSCSpecifier() {
- return m_sc_specifier_sp.get();
- }
+ SymbolContextSpecifier *GetSCSpecifier() { return m_sc_specifier_sp.get(); }
/// Check if the execution context passes the specifier and thread spec
/// filters. Always returns true if no filters are set.
@@ -1732,8 +1730,7 @@ class Target : public std::enable_shared_from_this<Target>,
virtual StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
lldb::StreamSP output) = 0;
- virtual void GetDescription(Stream &s,
- lldb::DescriptionLevel level) const;
+ virtual void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
protected:
lldb::TargetSP m_target_sp;
@@ -1753,6 +1750,7 @@ class Target : public std::enable_shared_from_this<Target>,
public:
~HookCommandLine() override = default;
+ /// Return the list of commands that this hook runs.
StringList &GetCommands() { return m_commands; }
/// Populate the command list by splitting a single string on newlines.
@@ -1761,8 +1759,7 @@ class Target : public std::enable_shared_from_this<Target>,
/// Populate the command list from a vector of individual command strings.
void SetActionFromStrings(const std::vector<std::string> &strings);
- void GetDescription(Stream &s,
- lldb::DescriptionLevel level) const override;
+ void GetDescription(Stream &s, lldb::DescriptionLevel level) const override;
void HandleModuleLoaded(lldb::StreamSP output) override;
void HandleModuleUnloaded(lldb::StreamSP output) override;
StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
@@ -1780,8 +1777,7 @@ class Target : public std::enable_shared_from_this<Target>,
public:
~HookScripted() override = default;
- void GetDescription(Stream &s,
- lldb::DescriptionLevel level) const override;
+ void GetDescription(Stream &s, lldb::DescriptionLevel level) const override;
void HandleModuleLoaded(lldb::StreamSP output) override;
void HandleModuleUnloaded(lldb::StreamSP output) override;
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 0ae8fa52bd31a..7313d47c42e05 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4319,8 +4319,7 @@ Target::Hook::Hook(lldb::TargetSP target_sp, lldb::user_id_t uid)
Target::Hook::Hook(const Hook &rhs)
: UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), m_active(rhs.m_active),
- m_event_mask(rhs.m_event_mask),
- m_sc_specifier_sp(rhs.m_sc_specifier_sp),
+ m_event_mask(rhs.m_event_mask), m_sc_specifier_sp(rhs.m_sc_specifier_sp),
m_auto_continue(rhs.m_auto_continue),
m_at_initial_stop(rhs.m_at_initial_stop),
m_suppress_output(rhs.m_suppress_output) {
@@ -4581,8 +4580,8 @@ Target::HookScripted::HandleStop(ExecutionContext &exc_ctx,
: StopHook::StopHookResult::RequestContinue;
}
-void Target::HookScripted::GetDescription(
- Stream &s, lldb::DescriptionLevel level) const {
+void Target::HookScripted::GetDescription(Stream &s,
+ lldb::DescriptionLevel level) const {
Hook::GetDescription(s, level);
if (level == eDescriptionLevelBrief) {
s.PutCString(m_class_name);
>From 013eda1f67a6da6cbdd2c31c994229f2d59d06cd Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Thu, 19 Mar 2026 13:38:14 -0700
Subject: [PATCH 09/12] coequal triggers, merged filters, consistent naming
---
lldb/include/lldb/Target/Target.h | 64 +--
lldb/source/Commands/CommandObjectTarget.cpp | 428 +++++++-----------
lldb/source/Commands/Options.td | 34 +-
lldb/source/Target/Target.cpp | 39 +-
.../Commands/command-module-hook-fire.test | 2 +-
.../Shell/Commands/command-module-hook.test | 60 +--
6 files changed, 277 insertions(+), 350 deletions(-)
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 1d7094878562d..6c92fe12f2d35 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1659,10 +1659,9 @@ class Target : public std::enable_shared_from_this<Target>,
// Target Hooks
//
- // Hooks fire on lifecycle events. By default, they fire when modules
- // are loaded into the target (via ModulesDidLoad). They can also fire
- // on module unload (via ModulesDidUnload) and on process stop (via
- // RunStopHooks), controlled by the event mask.
+ // Hooks fire on target lifecycle events. Each hook must explicitly specify
+ // which triggers it responds to via --on-load, --on-unload, and/or --on-stop.
+ // All trigger types are coequal, meaning none is privileged or implied by default.
class Hook : public UserID {
public:
Hook(const Hook &rhs);
@@ -1670,11 +1669,9 @@ class Target : public std::enable_shared_from_this<Target>,
enum class HookKind : uint32_t { CommandBased = 0, ScriptBased };
- /// Event mask bits controlling when this hook fires.
- // FIXME: Add more event types as needed, e.g.:
- // kProcessExited (fires when the process exits).
- // kProcessDetached (fires when the debugger detaches).
- enum EventMask : uint32_t {
+ /// Individual trigger bits. Combine with OR to form a trigger mask.
+ // FIXME: Add kProcessExit, kProcessDetach, etc. as needed.
+ enum TriggerBit : uint32_t {
kModulesLoaded = (1u << 0),
kModulesUnloaded = (1u << 1),
kProcessStop = (1u << 2),
@@ -1682,14 +1679,14 @@ class Target : public std::enable_shared_from_this<Target>,
lldb::TargetSP &GetTarget() { return m_target_sp; }
- bool IsActive() { return m_active; }
- void SetIsActive(bool is_active) { m_active = is_active; }
+ bool IsEnabled() { return m_enabled; }
+ void SetIsEnabled(bool enabled) { m_enabled = enabled; }
- uint32_t GetEventMask() const { return m_event_mask; }
- void SetEventMask(uint32_t mask) { m_event_mask = mask; }
- void AddEvent(uint32_t event) { m_event_mask |= event; }
- void RemoveEvent(uint32_t event) { m_event_mask &= ~event; }
- bool FiresOn(uint32_t event) const { return m_event_mask & event; }
+ uint32_t GetTriggerMask() const { return m_trigger_mask; }
+ void SetTriggerMask(uint32_t mask) { m_trigger_mask = mask; }
+ void AddTrigger(uint32_t trigger) { m_trigger_mask |= trigger; }
+ void RemoveTrigger(uint32_t trigger) { m_trigger_mask &= ~trigger; }
+ bool FiresOn(uint32_t trigger) const { return m_trigger_mask & trigger; }
// Filter fields
@@ -1705,42 +1702,49 @@ class Target : public std::enable_shared_from_this<Target>,
void SetThreadSpecifier(ThreadSpec *specifier);
ThreadSpec *GetThreadSpecifier() { return m_thread_spec_up.get(); }
- void SetAutoContinue(bool auto_continue) {
- m_auto_continue = auto_continue;
- }
- bool GetAutoContinue() const { return m_auto_continue; }
-
void SetRunAtInitialStop(bool at_initial_stop) {
m_at_initial_stop = at_initial_stop;
}
bool GetRunAtInitialStop() const { return m_at_initial_stop; }
+ // Reaction settings
+
+ void SetAutoContinue(bool auto_continue) {
+ m_auto_continue = auto_continue;
+ }
+ bool GetAutoContinue() const { return m_auto_continue; }
+
void SetSuppressOutput(bool suppress_output) {
m_suppress_output = suppress_output;
}
bool GetSuppressOutput() const { return m_suppress_output; }
- // Event handler methods
+ // Event handler methods (default no-ops)
- virtual void HandleModuleLoaded(lldb::StreamSP output) = 0;
- virtual void HandleModuleUnloaded(lldb::StreamSP output) = 0;
+ virtual void HandleModuleLoaded(lldb::StreamSP output) {}
+ virtual void HandleModuleUnloaded(lldb::StreamSP output) {}
/// Called when the process stops. Returns a StopHookResult indicating
/// whether the process should remain stopped or continue.
virtual StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
- lldb::StreamSP output) = 0;
+ lldb::StreamSP output) {
+ return StopHook::StopHookResult::NoPreference;
+ }
virtual void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
protected:
lldb::TargetSP m_target_sp;
- bool m_active = true;
- uint32_t m_event_mask = kModulesLoaded; // Default: fire on load only
+ bool m_enabled = true;
+ uint32_t m_trigger_mask = 0; // No default, triggers must be explicit.
+ // Filters
lldb::SymbolContextSpecifierSP m_sc_specifier_sp;
std::unique_ptr<ThreadSpec> m_thread_spec_up;
- bool m_auto_continue = false;
bool m_at_initial_stop = true;
+
+ // Reaction settings
+ bool m_auto_continue = false;
bool m_suppress_output = false;
Hook(lldb::TargetSP target_sp, lldb::user_id_t uid);
@@ -1812,9 +1816,9 @@ class Target : public std::enable_shared_from_this<Target>,
HookSP GetHookByID(lldb::user_id_t uid);
- bool SetHookActiveStateByID(lldb::user_id_t uid, bool active_state);
+ bool SetHookEnabledStateByID(lldb::user_id_t uid, bool enabled);
- void SetAllHooksActiveState(bool active_state);
+ void SetAllHooksEnabledState(bool enabled);
size_t GetNumHooks() const { return m_hooks.size(); }
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index b2c13ace935b9..245a84dd4bcf2 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5318,201 +5318,15 @@ class CommandObjectTargetHookAdd : public CommandObjectParsed,
m_use_one_liner = true;
m_one_liner.push_back(std::string(option_arg));
break;
+ case 'L':
+ m_on_load = true;
+ break;
case 'u':
m_on_unload = true;
break;
case 'S':
m_on_stop = true;
break;
- default:
- llvm_unreachable("unhandled option");
- }
- return error;
- }
-
- void OptionParsingStarting(ExecutionContext *execution_context) override {
- m_use_one_liner = false;
- m_one_liner.clear();
- m_on_unload = false;
- m_on_stop = false;
- }
-
- std::vector<std::string> m_one_liner;
- bool m_use_one_liner = false;
- bool m_on_unload = false;
- bool m_on_stop = false;
- };
-
- CommandObjectTargetHookAdd(CommandInterpreter &interpreter)
- : CommandObjectParsed(
- interpreter, "target hook add",
- "Add a hook to be executed on target lifecycle events.",
- "target hook add"),
- IOHandlerDelegateMultiline("DONE",
- IOHandlerDelegate::Completion::LLDBCommand),
- m_python_class_options("scripted hook", false, 'P') {
- SetHelpLong(R"help(
-Hooks fire on target lifecycle events. By default, hooks fire when modules are
-loaded into the target. Use --on-unload (-u) to also fire on module unload, and
---on-stop (-S) to also fire when the process stops.
-
-Command-based hooks:
-
- target hook add -o "script print('module loaded')"
- target hook add -u -o "script print('module event')"
- target hook add -S -o "bt"
- target hook add -u -S -o "script print('all events')"
-
-Use 'target hook add-filter' to add stop-event filters to an existing hook:
-
- target hook add -S -o "bt"
- target hook add-filter -s mylib.so -n main 1
-
-Python-based hooks:
-
- target hook add -P mymodule.MyHook
- target hook add -u -S -P mymodule.MyHook
-
-The Python class should implement:
-
- class MyHook:
- def __init__(self, target, extra_args, internal_dict):
- self.target = target
- def handle_module_loaded(self, stream): # required
- pass
- def handle_module_unloaded(self, stream): # optional
- pass
- def handle_stop(self, exe_ctx, stream): # optional, return bool
- return True # True = should_stop
-
-handle_module_loaded is required. handle_module_unloaded is only called when
---on-unload is specified. handle_stop is only called when --on-stop is
-specified and should return True to stop or False to continue.
-)help");
- m_all_options.Append(&m_python_class_options,
- LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_2);
- m_all_options.Append(&m_options);
- m_all_options.Finalize();
- }
-
- ~CommandObjectTargetHookAdd() override = default;
-
- Options *GetOptions() override { return &m_all_options; }
-
-protected:
- void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
- if (interactive) {
- if (lldb::LockableStreamFileSP output_sp =
- io_handler.GetOutputStreamFileSP()) {
- LockedStreamFile locked_stream = output_sp->Lock();
- locked_stream.PutCString(
- "Enter your hook command(s). Type 'DONE' to end.\n");
- }
- }
- }
-
- void IOHandlerInputComplete(IOHandler &io_handler,
- std::string &line) override {
- if (m_hook_sp) {
- if (line.empty()) {
- if (lldb::LockableStreamFileSP error_sp =
- io_handler.GetErrorStreamFileSP()) {
- LockedStreamFile locked_stream = error_sp->Lock();
- locked_stream.Printf("error: hook #%" PRIu64
- " aborted, no commands.\n",
- m_hook_sp->GetID());
- }
- GetTarget().UndoCreateHook(m_hook_sp->GetID());
- } else {
- auto *hook = static_cast<Target::HookCommandLine *>(m_hook_sp.get());
- hook->SetActionFromString(line);
- if (lldb::LockableStreamFileSP output_sp =
- io_handler.GetOutputStreamFileSP()) {
- LockedStreamFile locked_stream = output_sp->Lock();
- locked_stream.Printf("Hook #%" PRIu64 " added.\n",
- m_hook_sp->GetID());
- }
- }
- m_hook_sp.reset();
- }
- io_handler.SetIsDone(true);
- }
-
- void DoExecute(Args &command, CommandReturnObject &result) override {
- m_hook_sp.reset();
- Target &target = GetTarget();
-
- Target::Hook::HookKind hook_kind;
- if (m_python_class_options.GetName().empty())
- hook_kind = Target::Hook::HookKind::CommandBased;
- else
- hook_kind = Target::Hook::HookKind::ScriptBased;
-
- Target::HookSP new_hook_sp = target.CreateHook(hook_kind);
-
- // Build event mask.
- uint32_t event_mask = Target::Hook::kModulesLoaded;
- if (m_options.m_on_unload)
- event_mask |= Target::Hook::kModulesUnloaded;
- if (m_options.m_on_stop)
- event_mask |= Target::Hook::kProcessStop;
- new_hook_sp->SetEventMask(event_mask);
-
- if (m_options.m_use_one_liner) {
- auto *hook = static_cast<Target::HookCommandLine *>(new_hook_sp.get());
- hook->SetActionFromStrings(m_options.m_one_liner);
- result.AppendMessageWithFormat("Hook #%" PRIu64 " added.\n",
- new_hook_sp->GetID());
- } else if (!m_python_class_options.GetName().empty()) {
- auto *hook = static_cast<Target::HookScripted *>(new_hook_sp.get());
- Status callback_error =
- hook->SetScriptCallback(m_python_class_options.GetName(),
- m_python_class_options.GetStructuredData());
- if (callback_error.Fail()) {
- result.AppendErrorWithFormat("error: couldn't add hook: %s\n",
- callback_error.AsCString());
- target.UndoCreateHook(new_hook_sp->GetID());
- return;
- }
- result.AppendMessageWithFormat("Hook #%" PRIu64 " added.\n",
- new_hook_sp->GetID());
- } else {
- m_hook_sp = new_hook_sp;
- m_interpreter.GetLLDBCommandsFromIOHandler("> ", // prompt
- *this); // delegate
- }
- result.SetStatus(eReturnStatusSuccessFinishNoResult);
- }
-
-private:
- CommandOptions m_options;
- OptionGroupPythonClassWithDict m_python_class_options;
- OptionGroupOptions m_all_options;
- Target::HookSP m_hook_sp;
-};
-
-#pragma mark CommandObjectTargetHookAddFilter
-
-#define LLDB_OPTIONS_target_hook_add_filter
-#include "CommandOptions.inc"
-
-class CommandObjectTargetHookAddFilter : public CommandObjectParsed {
-public:
- class CommandOptions : public Options {
- public:
- CommandOptions() = default;
- ~CommandOptions() override = default;
-
- llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
- return llvm::ArrayRef(g_target_hook_add_filter_options);
- }
-
- Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
- ExecutionContext *execution_context) override {
- Status error;
- const int short_option =
- g_target_hook_add_filter_options[option_idx].short_option;
- switch (short_option) {
case 's':
m_module_name = std::string(option_arg);
m_sym_ctx_specified = true;
@@ -5606,6 +5420,11 @@ class CommandObjectTargetHookAddFilter : public CommandObjectParsed {
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
+ m_use_one_liner = false;
+ m_one_liner.clear();
+ m_on_load = false;
+ m_on_unload = false;
+ m_on_stop = false;
m_sym_ctx_specified = false;
m_thread_specified = false;
m_module_name.clear();
@@ -5622,6 +5441,13 @@ class CommandObjectTargetHookAddFilter : public CommandObjectParsed {
m_at_initial_stop = true;
}
+ std::vector<std::string> m_one_liner;
+ bool m_use_one_liner = false;
+ bool m_on_load = false;
+ bool m_on_unload = false;
+ bool m_on_stop = false;
+
+ // Filter options (for stop trigger).
bool m_sym_ctx_specified = false;
bool m_thread_specified = false;
std::string m_module_name;
@@ -5638,60 +5464,128 @@ class CommandObjectTargetHookAddFilter : public CommandObjectParsed {
bool m_at_initial_stop = true;
};
- CommandObjectTargetHookAddFilter(CommandInterpreter &interpreter)
+ CommandObjectTargetHookAdd(CommandInterpreter &interpreter)
: CommandObjectParsed(
- interpreter, "target hook add-filter",
- "Add stop-event filters to an existing hook.",
- "target hook add-filter [<filter-options>] <hook-id>") {
+ interpreter, "target hook add",
+ "Add a hook to be executed on target lifecycle events.",
+ "target hook add"),
+ IOHandlerDelegateMultiline("DONE",
+ IOHandlerDelegate::Completion::LLDBCommand),
+ m_python_class_options("scripted hook", false, 'P') {
SetHelpLong(R"help(
-Adds stop-event filters to an existing hook. Filters control when the hook's
-stop handler fires based on the current execution context (module, function,
-source file/line, thread, etc.).
-
-If the hook does not already fire on process stop, the stop event is
-automatically added to its event mask.
+Hooks fire on target lifecycle events. At least one trigger must be specified:
+--on-load (-L), --on-unload (-u), or --on-stop (-S). Multiple triggers can be
+combined.
-Examples:
+Command-based hooks:
+ target hook add -L -o "script print('module loaded')"
+ target hook add -L -u -o "script print('module event')"
target hook add -S -o "bt"
- target hook add-filter -s mylib.so 1
+ target hook add -L -u -S -o "script print('all events')"
- target hook add -S -o "bt"
- target hook add-filter -n main -G true 1
+Stop-event filters can be specified inline:
+
+ target hook add -S -s mylib.so -n main -o "bt"
+ target hook add -S -G true -o "thread info"
+
+Python-based hooks:
+
+ target hook add -L -P mymodule.MyHook
+ target hook add -L -u -S -P mymodule.MyHook
+
+The Python class should implement:
+
+ class MyHook:
+ def __init__(self, target, extra_args, internal_dict):
+ self.target = target
+ def handle_module_loaded(self, stream): # for --on-load
+ pass
+ def handle_module_unloaded(self, stream): # for --on-unload
+ pass
+ def handle_stop(self, exe_ctx, stream): # for --on-stop, return bool
+ return True # True = should_stop
+
+Each handler is only called for the triggers that were specified.
)help");
- AddSimpleArgumentList(eArgTypeStopHookID);
+ m_all_options.Append(&m_python_class_options,
+ LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_2);
+ m_all_options.Append(&m_options);
+ m_all_options.Finalize();
}
- ~CommandObjectTargetHookAddFilter() override = default;
+ ~CommandObjectTargetHookAdd() override = default;
- Options *GetOptions() override { return &m_options; }
+ Options *GetOptions() override { return &m_all_options; }
protected:
+ void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
+ if (interactive) {
+ if (lldb::LockableStreamFileSP output_sp =
+ io_handler.GetOutputStreamFileSP()) {
+ LockedStreamFile locked_stream = output_sp->Lock();
+ locked_stream.PutCString(
+ "Enter your hook command(s). Type 'DONE' to end.\n");
+ }
+ }
+ }
+
+ void IOHandlerInputComplete(IOHandler &io_handler,
+ std::string &line) override {
+ if (m_hook_sp) {
+ if (line.empty()) {
+ if (lldb::LockableStreamFileSP error_sp =
+ io_handler.GetErrorStreamFileSP()) {
+ LockedStreamFile locked_stream = error_sp->Lock();
+ locked_stream.Printf("error: hook #%" PRIu64
+ " aborted, no commands.\n",
+ m_hook_sp->GetID());
+ }
+ GetTarget().UndoCreateHook(m_hook_sp->GetID());
+ } else {
+ auto *hook = static_cast<Target::HookCommandLine *>(m_hook_sp.get());
+ hook->SetActionFromString(line);
+ if (lldb::LockableStreamFileSP output_sp =
+ io_handler.GetOutputStreamFileSP()) {
+ LockedStreamFile locked_stream = output_sp->Lock();
+ locked_stream.Printf("Hook #%" PRIu64 " added.\n",
+ m_hook_sp->GetID());
+ }
+ }
+ m_hook_sp.reset();
+ }
+ io_handler.SetIsDone(true);
+ }
+
void DoExecute(Args &command, CommandReturnObject &result) override {
+ m_hook_sp.reset();
Target &target = GetTarget();
- if (command.GetArgumentCount() != 1) {
- result.AppendError("exactly one hook id is required.");
+ // At least one trigger must be specified.
+ if (!m_options.m_on_load && !m_options.m_on_unload &&
+ !m_options.m_on_stop) {
+ result.AppendError("at least one trigger must be specified: "
+ "--on-load (-L), --on-unload (-u), or --on-stop (-S)");
return;
}
- lldb::user_id_t hook_id;
- if (!llvm::to_integer(command.GetArgumentAtIndex(0), hook_id)) {
- result.AppendErrorWithFormat("invalid hook id: \"%s\".\n",
- command.GetArgumentAtIndex(0));
- return;
- }
+ Target::Hook::HookKind hook_kind;
+ if (m_python_class_options.GetName().empty())
+ hook_kind = Target::Hook::HookKind::CommandBased;
+ else
+ hook_kind = Target::Hook::HookKind::ScriptBased;
- Target::HookSP hook_sp = target.GetHookByID(hook_id);
- if (!hook_sp) {
- result.AppendErrorWithFormat("unknown hook id: \"%" PRIu64 "\".\n",
- hook_id);
- return;
- }
+ Target::HookSP new_hook_sp = target.CreateHook(hook_kind);
- // Automatically add the stop event if not already present.
- if (!hook_sp->FiresOn(Target::Hook::kProcessStop))
- hook_sp->AddEvent(Target::Hook::kProcessStop);
+ // Build trigger mask from explicit flags.
+ uint32_t trigger_mask = 0;
+ if (m_options.m_on_load)
+ trigger_mask |= Target::Hook::kModulesLoaded;
+ if (m_options.m_on_unload)
+ trigger_mask |= Target::Hook::kModulesUnloaded;
+ if (m_options.m_on_stop)
+ trigger_mask |= Target::Hook::kProcessStop;
+ new_hook_sp->SetTriggerMask(trigger_mask);
// Set up symbol context specifier if filter options were provided.
if (m_options.m_sym_ctx_specified) {
@@ -5726,7 +5620,7 @@ automatically added to its event mask.
m_options.m_function_name.c_str(),
SymbolContextSpecifier::eFunctionSpecified);
- hook_sp->SetSCSpecifier(specifier_up.release());
+ new_hook_sp->SetSCSpecifier(specifier_up.release());
}
// Set up thread specifier.
@@ -5745,19 +5639,43 @@ automatically added to its event mask.
if (!m_options.m_queue_name.empty())
thread_spec->SetQueueName(m_options.m_queue_name.c_str());
- hook_sp->SetThreadSpecifier(thread_spec);
+ new_hook_sp->SetThreadSpecifier(thread_spec);
}
- hook_sp->SetAutoContinue(m_options.m_auto_continue);
- hook_sp->SetRunAtInitialStop(m_options.m_at_initial_stop);
+ new_hook_sp->SetAutoContinue(m_options.m_auto_continue);
+ new_hook_sp->SetRunAtInitialStop(m_options.m_at_initial_stop);
- result.AppendMessageWithFormat("Filter added to hook #%" PRIu64 ".\n",
- hook_id);
+ if (m_options.m_use_one_liner) {
+ auto *hook = static_cast<Target::HookCommandLine *>(new_hook_sp.get());
+ hook->SetActionFromStrings(m_options.m_one_liner);
+ result.AppendMessageWithFormat("Hook #%" PRIu64 " added.\n",
+ new_hook_sp->GetID());
+ } else if (!m_python_class_options.GetName().empty()) {
+ auto *hook = static_cast<Target::HookScripted *>(new_hook_sp.get());
+ Status callback_error =
+ hook->SetScriptCallback(m_python_class_options.GetName(),
+ m_python_class_options.GetStructuredData());
+ if (callback_error.Fail()) {
+ result.AppendErrorWithFormat("error: couldn't add hook: %s\n",
+ callback_error.AsCString());
+ target.UndoCreateHook(new_hook_sp->GetID());
+ return;
+ }
+ result.AppendMessageWithFormat("Hook #%" PRIu64 " added.\n",
+ new_hook_sp->GetID());
+ } else {
+ m_hook_sp = new_hook_sp;
+ m_interpreter.GetLLDBCommandsFromIOHandler("> ", // prompt
+ *this); // delegate
+ }
result.SetStatus(eReturnStatusSuccessFinishNoResult);
}
private:
CommandOptions m_options;
+ OptionGroupPythonClassWithDict m_python_class_options;
+ OptionGroupOptions m_all_options;
+ Target::HookSP m_hook_sp;
};
#pragma mark CommandObjectTargetHookDelete
@@ -5815,42 +5733,43 @@ class CommandObjectTargetHookEnableDisable : public CommandObjectParsed {
void DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetTarget();
- // Check if the first argument is an event type name for per-event
- // toggling: module-loaded, module-unloaded, stop-hook.
- uint32_t event_type = 0;
+ // Check if the first argument is a trigger name for per-trigger
+ // toggling: load, unload, stop.
+ uint32_t trigger_type = 0;
size_t id_start_idx = 0;
if (command.GetArgumentCount() > 0) {
llvm::StringRef first_arg = command.GetArgumentAtIndex(0);
- if (first_arg == "module-loaded") {
- event_type = Target::Hook::kModulesLoaded;
+ if (first_arg == "load") {
+ trigger_type = Target::Hook::kModulesLoaded;
id_start_idx = 1;
- } else if (first_arg == "module-unloaded") {
- event_type = Target::Hook::kModulesUnloaded;
+ } else if (first_arg == "unload") {
+ trigger_type = Target::Hook::kModulesUnloaded;
id_start_idx = 1;
- } else if (first_arg == "stop-hook") {
- event_type = Target::Hook::kProcessStop;
+ } else if (first_arg == "stop") {
+ trigger_type = Target::Hook::kProcessStop;
id_start_idx = 1;
}
}
- // If no hook IDs given (after possibly consuming event type), apply to all.
+ // If no hook IDs given (after possibly consuming trigger name), apply to
+ // all.
if (command.GetArgumentCount() == id_start_idx) {
- if (event_type) {
- // Per-event toggle on all hooks.
+ if (trigger_type) {
+ // Per-trigger toggle on all hooks.
size_t num_hooks = target.GetNumHooks();
for (size_t i = 0; i < num_hooks; i++) {
Target::HookSP hook_sp = target.GetHookAtIndex(i);
if (!hook_sp)
continue;
if (m_enable)
- hook_sp->AddEvent(event_type);
+ hook_sp->AddTrigger(trigger_type);
else
- hook_sp->RemoveEvent(event_type);
+ hook_sp->RemoveTrigger(trigger_type);
}
} else {
// Whole-hook toggle on all hooks.
- target.SetAllHooksActiveState(m_enable);
+ target.SetAllHooksEnabledState(m_enable);
}
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return;
@@ -5865,8 +5784,8 @@ class CommandObjectTargetHookEnableDisable : public CommandObjectParsed {
return;
}
- if (event_type) {
- // Per-event toggle on a specific hook.
+ if (trigger_type) {
+ // Per-trigger toggle on a specific hook.
Target::HookSP hook_sp = target.GetHookByID(user_id);
if (!hook_sp) {
result.AppendErrorWithFormat("unknown hook id: \"%s\".\n",
@@ -5874,12 +5793,12 @@ class CommandObjectTargetHookEnableDisable : public CommandObjectParsed {
return;
}
if (m_enable)
- hook_sp->AddEvent(event_type);
+ hook_sp->AddTrigger(trigger_type);
else
- hook_sp->RemoveEvent(event_type);
+ hook_sp->RemoveTrigger(trigger_type);
} else {
// Whole-hook toggle.
- if (!target.SetHookActiveStateByID(user_id, m_enable)) {
+ if (!target.SetHookEnabledStateByID(user_id, m_enable)) {
result.AppendErrorWithFormat("unknown hook id: \"%s\".\n",
command.GetArgumentAtIndex(i));
return;
@@ -5932,21 +5851,18 @@ class CommandObjectMultiwordTargetHooks : public CommandObjectMultiword {
"target hook <subcommand> [<subcommand-options>]") {
LoadSubCommand(
"add", CommandObjectSP(new CommandObjectTargetHookAdd(interpreter)));
- LoadSubCommand(
- "add-filter",
- CommandObjectSP(new CommandObjectTargetHookAddFilter(interpreter)));
LoadSubCommand("delete", CommandObjectSP(new CommandObjectTargetHookDelete(
interpreter)));
LoadSubCommand("disable",
CommandObjectSP(new CommandObjectTargetHookEnableDisable(
interpreter, false, "target hook disable",
- "Disable a hook or a specific event on a hook.",
- "target hook disable [<event-type>] [<id> ...]")));
+ "Disable a hook or a specific trigger on a hook.",
+ "target hook disable [<trigger>] [<id> ...]")));
LoadSubCommand("enable",
CommandObjectSP(new CommandObjectTargetHookEnableDisable(
interpreter, true, "target hook enable",
- "Enable a hook or a specific event on a hook.",
- "target hook enable [<event-type>] [<id> ...]")));
+ "Enable a hook or a specific trigger on a hook.",
+ "target hook enable [<trigger>] [<id> ...]")));
LoadSubCommand(
"list", CommandObjectSP(new CommandObjectTargetHookList(interpreter)));
}
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index debc8837f20f0..76550f820bc5a 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1907,72 +1907,72 @@ let Command = "target hook add" in {
Arg<"OneLiner">,
Desc<"Add a command for the hook. Can be specified more than "
"once, and commands will be run in the order they appear.">;
+ def target_hook_add_on_load
+ : Option<"on-load", "L">,
+ Desc<"Fire the hook when modules are ${L}oaded.">;
def target_hook_add_on_unload
: Option<"on-unload", "u">,
- Desc<"Also fire the hook when modules are ${u}nloaded.">;
+ Desc<"Fire the hook when modules are ${u}nloaded.">;
def target_hook_add_on_stop
: Option<"on-stop", "S">,
- Desc<"Also fire the hook when the process ${S}tops.">;
-}
-
-let Command = "target hook add_filter" in {
- def target_hook_add_filter_shlib
+ Desc<"Fire the hook when the process ${S}tops.">;
+ def target_hook_add_shlib
: Option<"shlib", "s">,
Arg<"ShlibName">,
Completion<"Module">,
Desc<"Set the module within which the stop handler is to be run.">;
- def target_hook_add_filter_thread_index
+ def target_hook_add_thread_index
: Option<"thread-index", "x">,
Arg<"ThreadIndex">,
Desc<"The stop handler is run only for the thread whose inde${x} "
"matches this argument.">;
- def target_hook_add_filter_thread_id
+ def target_hook_add_thread_id
: Option<"thread-id", "t">,
Arg<"ThreadID">,
Desc<"The stop handler is run only for the ${t}hread whose TID "
"matches this argument.">;
- def target_hook_add_filter_thread_name
+ def target_hook_add_thread_name
: Option<"thread-name", "T">,
Arg<"ThreadName">,
Desc<"The stop handler is run only for the ${T}hread whose thread "
"name matches this argument.">;
- def target_hook_add_filter_queue_name
+ def target_hook_add_queue_name
: Option<"queue-name", "q">,
Arg<"QueueName">,
Desc<"The stop handler is run only for threads in the ${q}ueue whose "
"name is given by this argument.">;
- def target_hook_add_filter_file
+ def target_hook_add_file
: Option<"file", "f">,
Arg<"Filename">,
Desc<"Specify the source ${f}ile within which the stop handler is "
"to be run.">,
Completion<"SourceFile">;
- def target_hook_add_filter_start_line
+ def target_hook_add_start_line
: Option<"start-line", "l">,
Arg<"LineNum">,
Desc<"Set the start of the ${l}ine range for which the stop handler "
"is to be run.">;
- def target_hook_add_filter_end_line
+ def target_hook_add_end_line
: Option<"end-line", "e">,
Arg<"LineNum">,
Desc<"Set the ${e}nd of the line range for which the stop handler "
"is to be run.">;
- def target_hook_add_filter_classname
+ def target_hook_add_classname
: Option<"classname", "c">,
Arg<"ClassName">,
Desc<"Specify the ${c}lass within which the stop handler is to "
"be run.">;
- def target_hook_add_filter_name
+ def target_hook_add_name
: Option<"name", "n">,
Arg<"FunctionName">,
Desc<"Set the function ${n}ame within which the stop handler will "
"be run.">,
Completion<"Symbol">;
- def target_hook_add_filter_auto_continue
+ def target_hook_add_auto_continue
: Option<"auto-continue", "G">,
Arg<"Boolean">,
Desc<"The hook will auto-continue after running its stop handler.">;
- def target_hook_add_filter_at_initial_stop
+ def target_hook_add_at_initial_stop
: Option<"at-initial-stop", "I">,
Arg<"Boolean">,
Desc<"Whether the stop handler will trigger when lldb ${I}nitially "
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 7313d47c42e05..1e4299c9fd6f1 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3159,7 +3159,7 @@ bool Target::RunStopHooks(bool at_initial_stop) {
// Also collect unified hooks that fire on process stop.
std::vector<HookSP> active_unified_hooks;
for (auto &[_, hook] : m_hooks) {
- if (hook->IsActive() && hook->FiresOn(Hook::kProcessStop) &&
+ if (hook->IsEnabled() && hook->FiresOn(Hook::kProcessStop) &&
(!at_initial_stop || hook->GetRunAtInitialStop()))
active_unified_hooks.push_back(hook);
}
@@ -4318,10 +4318,11 @@ Target::Hook::Hook(lldb::TargetSP target_sp, lldb::user_id_t uid)
: UserID(uid), m_target_sp(std::move(target_sp)) {}
Target::Hook::Hook(const Hook &rhs)
- : UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), m_active(rhs.m_active),
- m_event_mask(rhs.m_event_mask), m_sc_specifier_sp(rhs.m_sc_specifier_sp),
- m_auto_continue(rhs.m_auto_continue),
+ : UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp),
+ m_enabled(rhs.m_enabled), m_trigger_mask(rhs.m_trigger_mask),
+ m_sc_specifier_sp(rhs.m_sc_specifier_sp),
m_at_initial_stop(rhs.m_at_initial_stop),
+ m_auto_continue(rhs.m_auto_continue),
m_suppress_output(rhs.m_suppress_output) {
if (rhs.m_thread_spec_up)
m_thread_spec_up = std::make_unique<ThreadSpec>(*rhs.m_thread_spec_up);
@@ -4358,24 +4359,26 @@ void Target::Hook::GetDescription(Stream &s,
return;
s.IndentMore();
s.Indent();
- s.Printf("State: %s\n", m_active ? "enabled" : "disabled");
+ s.Printf("State: %s\n", m_enabled ? "enabled" : "disabled");
- if (m_event_mask != kModulesLoaded) {
+ {
std::string fires_on;
- if (m_event_mask & kModulesLoaded)
+ if (m_trigger_mask & kModulesLoaded)
fires_on += "load";
- if (m_event_mask & kModulesUnloaded) {
+ if (m_trigger_mask & kModulesUnloaded) {
if (!fires_on.empty())
fires_on += ", ";
fires_on += "unload";
}
- if (m_event_mask & kProcessStop) {
+ if (m_trigger_mask & kProcessStop) {
if (!fires_on.empty())
fires_on += ", ";
fires_on += "stop";
}
- s.Indent();
- s.Printf("Fires on: %s\n", fires_on.c_str());
+ if (!fires_on.empty()) {
+ s.Indent();
+ s.Printf("Triggers: %s\n", fires_on.c_str());
+ }
}
if (m_auto_continue)
@@ -4675,31 +4678,31 @@ Target::HookSP Target::GetHookAtIndex(size_t index) {
return iter->second;
}
-bool Target::SetHookActiveStateByID(lldb::user_id_t uid, bool active_state) {
+bool Target::SetHookEnabledStateByID(lldb::user_id_t uid, bool enabled) {
auto iter = m_hooks.find(uid);
if (iter == m_hooks.end())
return false;
- iter->second->SetIsActive(active_state);
+ iter->second->SetIsEnabled(enabled);
return true;
}
-void Target::SetAllHooksActiveState(bool active_state) {
+void Target::SetAllHooksEnabledState(bool enabled) {
for (auto &[_, hook] : m_hooks)
- hook->SetIsActive(active_state);
+ hook->SetIsEnabled(enabled);
}
void Target::RunModuleHooks(bool is_load) {
if (m_hooks.empty())
return;
- uint32_t event = is_load ? Hook::kModulesLoaded : Hook::kModulesUnloaded;
+ uint32_t trigger = is_load ? Hook::kModulesLoaded : Hook::kModulesUnloaded;
StreamSP output_sp = m_debugger.GetAsyncOutputStream();
for (auto &[_, hook_sp] : m_hooks) {
- if (!hook_sp->IsActive())
+ if (!hook_sp->IsEnabled())
continue;
- if (!hook_sp->FiresOn(event))
+ if (!hook_sp->FiresOn(trigger))
continue;
if (is_load)
hook_sp->HandleModuleLoaded(output_sp);
diff --git a/lldb/test/Shell/Commands/command-module-hook-fire.test b/lldb/test/Shell/Commands/command-module-hook-fire.test
index 28ccd853bc803..fd5a113a40569 100644
--- a/lldb/test/Shell/Commands/command-module-hook-fire.test
+++ b/lldb/test/Shell/Commands/command-module-hook-fire.test
@@ -2,7 +2,7 @@
#
# RUN: %clang_host -g %S/Inputs/main.c -o %t
# RUN: %lldb -b -o 'file %t' \
-# RUN: -o 'target hook add -o "script print(\"HOOK_FIRED\")"' \
+# RUN: -o 'target hook add -L -o "script print(\"HOOK_FIRED\")"' \
# RUN: -o 'target modules add %t' \
# RUN: 2>&1 | FileCheck %s
diff --git a/lldb/test/Shell/Commands/command-module-hook.test b/lldb/test/Shell/Commands/command-module-hook.test
index 58676881d902a..3d4c80db36ce2 100644
--- a/lldb/test/Shell/Commands/command-module-hook.test
+++ b/lldb/test/Shell/Commands/command-module-hook.test
@@ -1,13 +1,13 @@
-# Test hook add/list/delete/enable/disable/add-filter commands.
+# Test hook add/list/delete/enable/disable commands.
#
# RUN: %lldb -b -s %s 2>&1 | FileCheck %s
# Adding hooks assigns incrementing IDs.
-target hook add -o "script print('hook1')"
+target hook add -L -o "script print('hook1')"
# CHECK: Hook #1 added.
-target hook add -o "script print('hook2')"
+target hook add -L -o "script print('hook2')"
# CHECK: Hook #2 added.
-target hook add -o "script print('hook3')"
+target hook add -L -o "script print('hook3')"
# CHECK: Hook #3 added.
# List all hooks.
@@ -15,14 +15,17 @@ target hook list
# CHECK: (lldb) target hook list
# CHECK: Hook: 1
# CHECK: State: enabled
+# CHECK: Triggers: load
# CHECK: Commands:
# CHECK: script print('hook1')
# CHECK: Hook: 2
# CHECK: State: enabled
+# CHECK: Triggers: load
# CHECK: Commands:
# CHECK: script print('hook2')
# CHECK: Hook: 3
# CHECK: State: enabled
+# CHECK: Triggers: load
# CHECK: Commands:
# CHECK: script print('hook3')
@@ -57,7 +60,7 @@ target hook list
# CHECK: Hook: 3
# New hook gets a new ID (not reused).
-target hook add -o "script print('hook4')"
+target hook add -L -o "script print('hook4')"
# CHECK: Hook #4 added.
target hook list
# CHECK: (lldb) target hook list
@@ -72,24 +75,24 @@ target hook list
# CHECK: (lldb) target hook list
# CHECK: No hooks.
-# Add a hook with --on-unload and verify the description.
-target hook add -u -o "script print('load+unload')"
+# Add a hook with --on-load and --on-unload.
+target hook add -L -u -o "script print('load+unload')"
# CHECK: Hook #5 added.
target hook list
# CHECK: (lldb) target hook list
# CHECK: Hook: 5
# CHECK: State: enabled
-# CHECK: Fires on: load, unload
+# CHECK: Triggers: load, unload
# CHECK: Commands:
# CHECK: script print('load+unload')
# Add a hook with multiple one-liner commands.
-target hook add -o "script print('first')" -o "script print('second')"
+target hook add -L -o "script print('first')" -o "script print('second')"
# CHECK: Hook #6 added.
target hook list
# CHECK: Hook: 6
# CHECK: State: enabled
-# CHECK-NOT: Fires on:
+# CHECK: Triggers: load
# CHECK: Commands:
# CHECK: script print('first')
# CHECK: script print('second')
@@ -112,7 +115,7 @@ target hook list
# CHECK: Hook: 6
# CHECK: State: enabled
-# Add a hook with --on-stop.
+# Add a hook with --on-stop only.
target hook delete
target hook add -S -o "bt"
# CHECK: Hook #7 added.
@@ -120,42 +123,43 @@ target hook list
# CHECK: (lldb) target hook list
# CHECK: Hook: 7
# CHECK: State: enabled
-# CHECK: Fires on: load, stop
+# CHECK: Triggers: stop
# CHECK: Commands:
# CHECK: bt
-# Add a hook with both --on-unload and --on-stop.
-target hook add -u -S -o "script print('all events')"
+# Add a hook with all triggers.
+target hook add -L -u -S -o "script print('all events')"
# CHECK: Hook #8 added.
target hook list
# CHECK: Hook: 8
# CHECK: State: enabled
-# CHECK: Fires on: load, unload, stop
+# CHECK: Triggers: load, unload, stop
-# Add a filter to hook #7 using add-filter.
-target hook add-filter -s mylib.so 7
-# CHECK: Filter added to hook #7.
+# Add a hook with --on-stop and inline filter.
+target hook delete
+target hook add -S -s mylib.so -o "bt"
+# CHECK: Hook #9 added.
target hook list
# CHECK: (lldb) target hook list
-# CHECK: Hook: 7
+# CHECK: Hook: 9
# CHECK: State: enabled
-# CHECK: Fires on: load, stop
+# CHECK: Triggers: stop
# CHECK: Specifier:
# CHECK: Module: mylib.so
-# Per-event disable: remove stop from hook #7.
-target hook disable stop-hook 7
+# Per-trigger disable: remove stop from hook #9.
+target hook disable stop 9
target hook list
# CHECK: (lldb) target hook list
-# CHECK: Hook: 7
+# CHECK: Hook: 9
# CHECK: State: enabled
-# CHECK-NOT: Fires on: load, stop
+# CHECK-NOT: Triggers: stop
# CHECK: Specifier:
-# Per-event enable: re-add stop to hook #7.
-target hook enable stop-hook 7
+# Per-trigger enable: re-add stop to hook #9.
+target hook enable stop 9
target hook list
# CHECK: (lldb) target hook list
-# CHECK: Hook: 7
+# CHECK: Hook: 9
# CHECK: State: enabled
-# CHECK: Fires on: load, stop
+# CHECK: Triggers: stop
>From 7ca328635417a4d9e8de2e622fa3ce8dee2e08e7 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Thu, 19 Mar 2026 13:46:33 -0700
Subject: [PATCH 10/12] format
---
lldb/include/lldb/Target/Target.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 6c92fe12f2d35..84037ff5c2afb 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1661,7 +1661,8 @@ class Target : public std::enable_shared_from_this<Target>,
//
// Hooks fire on target lifecycle events. Each hook must explicitly specify
// which triggers it responds to via --on-load, --on-unload, and/or --on-stop.
- // All trigger types are coequal, meaning none is privileged or implied by default.
+ // All trigger types are coequal, meaning none is privileged or implied by
+ // default.
class Hook : public UserID {
public:
Hook(const Hook &rhs);
>From 0bab024d5a0aad8cafb1841f97f360aeff46723e Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 31 Mar 2026 14:20:40 -0700
Subject: [PATCH 11/12] Separate command-based and Python class flows in target
hook add
---
.../Interfaces/ScriptedModuleHookInterface.h | 34 -------
lldb/include/lldb/Target/Target.h | 27 ++++--
lldb/source/Commands/CommandObjectTarget.cpp | 89 ++++++++++++-------
lldb/source/Commands/Options.td | 6 ++
.../Interfaces/ScriptedHookPythonInterface.h | 5 +-
.../ScriptedModuleHookPythonInterface.cpp | 63 -------------
.../ScriptedModuleHookPythonInterface.h | 51 -----------
7 files changed, 86 insertions(+), 189 deletions(-)
delete mode 100644 lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
delete mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
delete mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
deleted file mode 100644
index e5a95384cac56..0000000000000
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h
+++ /dev/null
@@ -1,34 +0,0 @@
-//===-- ScriptedModuleHookInterface.h ---------------------------*- C++ -*-===//
-//
-// 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_INTERPRETER_INTERFACES_SCRIPTEDMODULEHOOKINTERFACE_H
-#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDMODULEHOOKINTERFACE_H
-
-#include "lldb/lldb-private.h"
-
-#include "ScriptedInterface.h"
-
-namespace lldb_private {
-class ScriptedModuleHookInterface : public ScriptedInterface {
-public:
- virtual llvm::Expected<StructuredData::GenericSP>
- CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
- const StructuredDataImpl &args_sp) = 0;
-
- /// Called when modules are loaded into the target. Unlike stop hooks,
- /// module hooks do not control whether the process should stop.
- virtual void HandleModuleLoaded(lldb::StreamSP &output_sp) {}
-
- /// Called when modules are unloaded from the target. Optional for
- /// scripted hooks; if not implemented, the hook silently does nothing
- /// on unload.
- virtual void HandleModuleUnloaded(lldb::StreamSP &output_sp) {}
-};
-} // namespace lldb_private
-
-#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDMODULEHOOKINTERFACE_H
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 84037ff5c2afb..cf5a9e34b0241 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1659,10 +1659,17 @@ class Target : public std::enable_shared_from_this<Target>,
// Target Hooks
//
- // Hooks fire on target lifecycle events. Each hook must explicitly specify
- // which triggers it responds to via --on-load, --on-unload, and/or --on-stop.
- // All trigger types are coequal, meaning none is privileged or implied by
- // default.
+ // Hooks fire on target lifecycle events. There are two flows:
+ //
+ // Command-based hooks: the user specifies which triggers the hook responds
+ // to (--on-load, --on-unload, --on-stop) and provides a list of commands.
+ // All commands run for every trigger the hook is signed up for.
+ //
+ // Python class hooks: the user provides a Python class name and optional
+ // extra_args (-k key -v value). The class controls which events it
+ // handles by implementing the corresponding callback methods
+ // (handle_module_loaded, handle_module_unloaded, handle_stop).
+ // Triggers are set automatically based on which methods exist.
class Hook : public UserID {
public:
Hook(const Hook &rhs);
@@ -1670,7 +1677,7 @@ class Target : public std::enable_shared_from_this<Target>,
enum class HookKind : uint32_t { CommandBased = 0, ScriptBased };
- /// Individual trigger bits. Combine with OR to form a trigger mask.
+ /// Individual trigger bits. Combine with bitwise OR to form a trigger mask.
// FIXME: Add kProcessExit, kProcessDetach, etc. as needed.
enum TriggerBit : uint32_t {
kModulesLoaded = (1u << 0),
@@ -1683,10 +1690,20 @@ class Target : public std::enable_shared_from_this<Target>,
bool IsEnabled() { return m_enabled; }
void SetIsEnabled(bool enabled) { m_enabled = enabled; }
+ /// Return the bitmask of triggers this hook responds to.
+ /// Each bit corresponds to a TriggerBit value.
uint32_t GetTriggerMask() const { return m_trigger_mask; }
+
+ /// Replace the trigger mask. \a mask is a bitwise OR of TriggerBit values.
void SetTriggerMask(uint32_t mask) { m_trigger_mask = mask; }
+
+ /// Add a trigger to the mask. \a trigger is a single TriggerBit value.
void AddTrigger(uint32_t trigger) { m_trigger_mask |= trigger; }
+
+ /// Remove a trigger from the mask. \a trigger is a single TriggerBit value.
void RemoveTrigger(uint32_t trigger) { m_trigger_mask &= ~trigger; }
+
+ /// Return true if this hook fires on the given trigger.
bool FiresOn(uint32_t trigger) const { return m_trigger_mask & trigger; }
// Filter fields
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 245a84dd4bcf2..694c56145eee8 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5473,41 +5473,54 @@ class CommandObjectTargetHookAdd : public CommandObjectParsed,
IOHandlerDelegate::Completion::LLDBCommand),
m_python_class_options("scripted hook", false, 'P') {
SetHelpLong(R"help(
-Hooks fire on target lifecycle events. At least one trigger must be specified:
---on-load (-L), --on-unload (-u), or --on-stop (-S). Multiple triggers can be
-combined.
-
Command-based hooks:
-
+--------------------
+ Specify which triggers the hook responds to with --on-load (-L),
+ --on-unload (-u), and/or --on-stop (-S). At least one trigger is required.
+ Provide commands with --one-liner (-o), or omit -o to enter an interactive
+ command editor. All commands run for every trigger the hook is signed up
+ for; there is no per-trigger command list.
+
+ Examples:
target hook add -L -o "script print('module loaded')"
target hook add -L -u -o "script print('module event')"
target hook add -S -o "bt"
target hook add -L -u -S -o "script print('all events')"
-
-Stop-event filters can be specified inline:
-
target hook add -S -s mylib.so -n main -o "bt"
target hook add -S -G true -o "thread info"
-Python-based hooks:
+Python class hooks:
+-------------------
+ Provide a Python class with --python-class (-P). The class controls which
+ events it handles by implementing the corresponding methods; you do not
+ specify triggers on the command line. Use -k <key> -v <value> to pass
+ extra_args to the class constructor.
- target hook add -L -P mymodule.MyHook
- target hook add -L -u -S -P mymodule.MyHook
+ Examples:
+ target hook add -P mymodule.MyHook
+ target hook add -P mymodule.MyHook -k verbose -v true
-The Python class should implement:
+ The Python class should implement at least one of these methods:
class MyHook:
def __init__(self, target, extra_args, internal_dict):
self.target = target
- def handle_module_loaded(self, stream): # for --on-load
+ def handle_module_loaded(self, stream):
pass
- def handle_module_unloaded(self, stream): # for --on-unload
+ def handle_module_unloaded(self, stream):
pass
- def handle_stop(self, exe_ctx, stream): # for --on-stop, return bool
- return True # True = should_stop
+ def handle_stop(self, exe_ctx, stream):
+ return True # True = should_stop, False = continue
-Each handler is only called for the triggers that were specified.
+Filter options:
+---------------
+ Filters (-s, -f, -l, -e, -c, -n, -x, -t, -T, -q) restrict when the hook
+ fires. They apply to both command-based and Python class hooks.
)help");
+ // Python class options (-P, -k, -v) are placed in Set 2 (dst_mask).
+ // src_mask must cover Set 1 | Set 2 to match the internal usage masks of
+ // OptionGroupPythonClassWithDict (class=Set1, key/value=Set2).
+ // Since -o, -L, -u, -S are Group<1> only, the parser prevents mixing.
m_all_options.Append(&m_python_class_options,
LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_2);
m_all_options.Append(&m_options);
@@ -5561,31 +5574,39 @@ Each handler is only called for the triggers that were specified.
m_hook_sp.reset();
Target &target = GetTarget();
- // At least one trigger must be specified.
- if (!m_options.m_on_load && !m_options.m_on_unload &&
+ bool is_python_class = !m_python_class_options.GetName().empty();
+
+ // Command-based hooks require at least one explicit trigger.
+ if (!is_python_class && !m_options.m_on_load && !m_options.m_on_unload &&
!m_options.m_on_stop) {
result.AppendError("at least one trigger must be specified: "
"--on-load (-L), --on-unload (-u), or --on-stop (-S)");
return;
}
- Target::Hook::HookKind hook_kind;
- if (m_python_class_options.GetName().empty())
- hook_kind = Target::Hook::HookKind::CommandBased;
- else
- hook_kind = Target::Hook::HookKind::ScriptBased;
+ Target::Hook::HookKind hook_kind =
+ is_python_class ? Target::Hook::HookKind::ScriptBased
+ : Target::Hook::HookKind::CommandBased;
Target::HookSP new_hook_sp = target.CreateHook(hook_kind);
- // Build trigger mask from explicit flags.
- uint32_t trigger_mask = 0;
- if (m_options.m_on_load)
- trigger_mask |= Target::Hook::kModulesLoaded;
- if (m_options.m_on_unload)
- trigger_mask |= Target::Hook::kModulesUnloaded;
- if (m_options.m_on_stop)
- trigger_mask |= Target::Hook::kProcessStop;
- new_hook_sp->SetTriggerMask(trigger_mask);
+ if (is_python_class) {
+ // Python class hooks respond to all triggers; the class controls
+ // behavior by which callback methods it implements.
+ new_hook_sp->SetTriggerMask(Target::Hook::kModulesLoaded |
+ Target::Hook::kModulesUnloaded |
+ Target::Hook::kProcessStop);
+ } else {
+ // Build trigger mask from explicit command-line flags.
+ uint32_t trigger_mask = 0;
+ if (m_options.m_on_load)
+ trigger_mask |= Target::Hook::kModulesLoaded;
+ if (m_options.m_on_unload)
+ trigger_mask |= Target::Hook::kModulesUnloaded;
+ if (m_options.m_on_stop)
+ trigger_mask |= Target::Hook::kProcessStop;
+ new_hook_sp->SetTriggerMask(trigger_mask);
+ }
// Set up symbol context specifier if filter options were provided.
if (m_options.m_sym_ctx_specified) {
@@ -5656,7 +5677,7 @@ Each handler is only called for the triggers that were specified.
hook->SetScriptCallback(m_python_class_options.GetName(),
m_python_class_options.GetStructuredData());
if (callback_error.Fail()) {
- result.AppendErrorWithFormat("error: couldn't add hook: %s\n",
+ result.AppendErrorWithFormat("Couldn't add hook: %s",
callback_error.AsCString());
target.UndoCreateHook(new_hook_sp->GetID());
return;
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 76550f820bc5a..90d4064880392 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1902,20 +1902,26 @@ let Command = "target stop_hook list" in {
}
let Command = "target hook add" in {
+ // Command-based options (Group 1 only -- mutually exclusive with -P).
def target_hook_add_one_liner
: Option<"one-liner", "o">,
+ Group<1>,
Arg<"OneLiner">,
Desc<"Add a command for the hook. Can be specified more than "
"once, and commands will be run in the order they appear.">;
def target_hook_add_on_load
: Option<"on-load", "L">,
+ Group<1>,
Desc<"Fire the hook when modules are ${L}oaded.">;
def target_hook_add_on_unload
: Option<"on-unload", "u">,
+ Group<1>,
Desc<"Fire the hook when modules are ${u}nloaded.">;
def target_hook_add_on_stop
: Option<"on-stop", "S">,
+ Group<1>,
Desc<"Fire the hook when the process ${S}tops.">;
+ // Shared options (both command-based and Python class hooks).
def target_hook_add_shlib
: Option<"shlib", "s">,
Arg<"ShlibName">,
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
index 9e8b8f7605992..3480945c0da91 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
@@ -24,10 +24,11 @@ class ScriptedHookPythonInterface : public ScriptedHookInterface,
CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
const StructuredDataImpl &args_sp) override;
+ /// A hook class must implement at least one callback. All three are
+ /// individually optional; hooks that implement none will simply never fire.
llvm::SmallVector<AbstractMethodRequirement>
GetAbstractMethodRequirements() const override {
- return llvm::SmallVector<AbstractMethodRequirement>(
- {{"handle_module_loaded", 1}});
+ return {};
}
void HandleModuleLoaded(lldb::StreamSP &output_sp) override;
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
deleted file mode 100644
index 1147aaf6cbd5c..0000000000000
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-//===-- ScriptedModuleHookPythonInterface.cpp -----------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "lldb/Core/PluginManager.h"
-#include "lldb/Utility/Log.h"
-#include "lldb/lldb-enumerations.h"
-
-#include "../SWIGPythonBridge.h"
-#include "../ScriptInterpreterPythonImpl.h"
-#include "../lldb-python.h"
-#include "ScriptedModuleHookPythonInterface.h"
-
-using namespace lldb;
-using namespace lldb_private;
-using namespace lldb_private::python;
-
-ScriptedModuleHookPythonInterface::ScriptedModuleHookPythonInterface(
- ScriptInterpreterPythonImpl &interpreter)
- : ScriptedModuleHookInterface(), ScriptedPythonInterface(interpreter) {}
-
-llvm::Expected<StructuredData::GenericSP>
-ScriptedModuleHookPythonInterface::CreatePluginObject(
- llvm::StringRef class_name, lldb::TargetSP target_sp,
- const StructuredDataImpl &args_sp) {
- return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr,
- target_sp, args_sp);
-}
-
-void ScriptedModuleHookPythonInterface::HandleModuleLoaded(
- lldb::StreamSP &output_sp) {
- Status error;
- // We pass only the output stream to Python. The Python class can access
- // self.target (set during __init__) to query loaded modules if needed.
- Dispatch("handle_module_loaded", error, output_sp);
-}
-
-void ScriptedModuleHookPythonInterface::HandleModuleUnloaded(
- lldb::StreamSP &output_sp) {
- Status error;
- // Optional method. If the Python class does not implement
- // handle_module_unloaded, the dispatch will fail silently.
- Dispatch("handle_module_unloaded", error, output_sp);
-}
-
-void ScriptedModuleHookPythonInterface::Initialize() {
- const std::vector<llvm::StringRef> ci_usages = {
- "target hook add -P <script-name> [-k key -v value ...]"};
- const std::vector<llvm::StringRef> api_usages = {};
- PluginManager::RegisterPlugin(
- GetPluginNameStatic(),
- llvm::StringRef("Perform actions whenever modules are loaded into the "
- "target."),
- CreateInstance, eScriptLanguagePython, {ci_usages, api_usages});
-}
-
-void ScriptedModuleHookPythonInterface::Terminate() {
- PluginManager::UnregisterPlugin(CreateInstance);
-}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
deleted file mode 100644
index 3fdb392352b3b..0000000000000
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedModuleHookPythonInterface.h
+++ /dev/null
@@ -1,51 +0,0 @@
-//===-- ScriptedModuleHookPythonInterface.h ---------------------*- C++ -*-===//
-//
-// 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_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDMODULEHOOKPYTHONINTERFACE_H
-#define LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDMODULEHOOKPYTHONINTERFACE_H
-
-#include "lldb/Interpreter/Interfaces/ScriptedModuleHookInterface.h"
-
-#include "ScriptedPythonInterface.h"
-
-namespace lldb_private {
-class ScriptedModuleHookPythonInterface : public ScriptedModuleHookInterface,
- public ScriptedPythonInterface,
- public PluginInterface {
-public:
- ScriptedModuleHookPythonInterface(ScriptInterpreterPythonImpl &interpreter);
-
- llvm::Expected<StructuredData::GenericSP>
- CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
- const StructuredDataImpl &args_sp) override;
-
- llvm::SmallVector<AbstractMethodRequirement>
- GetAbstractMethodRequirements() const override {
- return llvm::SmallVector<AbstractMethodRequirement>(
- {{"handle_module_loaded", 1}});
- }
-
- void HandleModuleLoaded(lldb::StreamSP &output_sp) override;
-
- /// Optional: only called if the Python class implements
- /// handle_module_unloaded. Silently does nothing otherwise.
- void HandleModuleUnloaded(lldb::StreamSP &output_sp) override;
-
- static void Initialize();
-
- static void Terminate();
-
- static llvm::StringRef GetPluginNameStatic() {
- return "ScriptedModuleHookPythonInterface";
- }
-
- llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
-};
-} // namespace lldb_private
-
-#endif // LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDMODULEHOOKPYTHONINTERFACE_H
>From ce0823aa1fb6754cea03c30cc4df8877e35de418 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 8 Apr 2026 15:47:19 -0700
Subject: [PATCH 12/12] Addressed all feedback
---
.../Interfaces/ScriptedHookInterface.h | 15 ++
lldb/include/lldb/Target/Target.h | 40 +--
lldb/source/Commands/CommandObjectTarget.cpp | 250 ++++++++++++------
lldb/source/Commands/Options.td | 13 +
.../ScriptedHookPythonInterface.cpp | 26 ++
.../Interfaces/ScriptedHookPythonInterface.h | 6 +-
lldb/source/Target/Target.cpp | 114 ++++----
.../Shell/Commands/command-module-hook.test | 12 +-
.../Inputs/stop-hook-unified-1.lldbinit | 1 +
.../StopHook/stop-hook-unified.test | 48 ++++
10 files changed, 380 insertions(+), 145 deletions(-)
create mode 100644 lldb/test/Shell/ExecControl/StopHook/Inputs/stop-hook-unified-1.lldbinit
create mode 100644 lldb/test/Shell/ExecControl/StopHook/stop-hook-unified.test
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
index 1ea76e2ab875f..0c1a370d5baac 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
@@ -16,10 +16,25 @@
namespace lldb_private {
class ScriptedHookInterface : public ScriptedInterface {
public:
+ /// Describes which hook callback methods the Python class implements.
+ struct SupportedHookMethods {
+ bool handle_module_loaded = false;
+ bool handle_module_unloaded = false;
+ bool handle_stop = false;
+
+ bool any() const {
+ return handle_module_loaded || handle_module_unloaded || handle_stop;
+ }
+ };
+
virtual llvm::Expected<StructuredData::GenericSP>
CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
const StructuredDataImpl &args_sp) = 0;
+ /// Check which hook callback methods the Python class implements.
+ /// Called after CreatePluginObject to determine the trigger mask.
+ virtual SupportedHookMethods GetSupportedMethods() { return {}; }
+
/// Called when modules are loaded into the target.
virtual void HandleModuleLoaded(lldb::StreamSP &output_sp) {}
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index cf5a9e34b0241..e1249e0878c87 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1666,10 +1666,11 @@ class Target : public std::enable_shared_from_this<Target>,
// All commands run for every trigger the hook is signed up for.
//
// Python class hooks: the user provides a Python class name and optional
- // extra_args (-k key -v value). The class controls which events it
- // handles by implementing the corresponding callback methods
- // (handle_module_loaded, handle_module_unloaded, handle_stop).
- // Triggers are set automatically based on which methods exist.
+ // extra_args that will be passed to the hook init method (-k key -v value).
+ // The class controls which events it handles by implementing the
+ // corresponding callback methods (handle_module_loaded,
+ // handle_module_unloaded, handle_stop). Triggers are set automatically
+ // based on which methods exist.
class Hook : public UserID {
public:
Hook(const Hook &rhs);
@@ -1677,6 +1678,8 @@ class Target : public std::enable_shared_from_this<Target>,
enum class HookKind : uint32_t { CommandBased = 0, ScriptBased };
+ HookKind GetHookKind() const { return m_kind; }
+
/// Individual trigger bits. Combine with bitwise OR to form a trigger mask.
// FIXME: Add kProcessExit, kProcessDetach, etc. as needed.
enum TriggerBit : uint32_t {
@@ -1694,15 +1697,6 @@ class Target : public std::enable_shared_from_this<Target>,
/// Each bit corresponds to a TriggerBit value.
uint32_t GetTriggerMask() const { return m_trigger_mask; }
- /// Replace the trigger mask. \a mask is a bitwise OR of TriggerBit values.
- void SetTriggerMask(uint32_t mask) { m_trigger_mask = mask; }
-
- /// Add a trigger to the mask. \a trigger is a single TriggerBit value.
- void AddTrigger(uint32_t trigger) { m_trigger_mask |= trigger; }
-
- /// Remove a trigger from the mask. \a trigger is a single TriggerBit value.
- void RemoveTrigger(uint32_t trigger) { m_trigger_mask &= ~trigger; }
-
/// Return true if this hook fires on the given trigger.
bool FiresOn(uint32_t trigger) const { return m_trigger_mask & trigger; }
@@ -1752,7 +1746,12 @@ class Target : public std::enable_shared_from_this<Target>,
virtual void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
protected:
+ /// Print the filter portion of the description (AutoContinue, Specifier,
+ /// ThreadSpec). Called by subclass GetDescription after printing the
+ /// hook-specific content (commands or class).
+ void GetFilterDescription(Stream &s, lldb::DescriptionLevel level) const;
lldb::TargetSP m_target_sp;
+ HookKind m_kind;
bool m_enabled = true;
uint32_t m_trigger_mask = 0; // No default, triggers must be explicit.
@@ -1765,13 +1764,22 @@ class Target : public std::enable_shared_from_this<Target>,
bool m_auto_continue = false;
bool m_suppress_output = false;
- Hook(lldb::TargetSP target_sp, lldb::user_id_t uid);
+ Hook(lldb::TargetSP target_sp, lldb::user_id_t uid, HookKind kind);
};
class HookCommandLine : public Hook {
public:
~HookCommandLine() override = default;
+ /// Replace the trigger mask. \a mask is a bitwise OR of TriggerBit values.
+ void SetTriggerMask(uint32_t mask) { m_trigger_mask = mask; }
+
+ /// Add a trigger to the mask. \a trigger is a single TriggerBit value.
+ void AddTrigger(uint32_t trigger) { m_trigger_mask |= trigger; }
+
+ /// Remove a trigger from the mask. \a trigger is a single TriggerBit value.
+ void RemoveTrigger(uint32_t trigger) { m_trigger_mask &= ~trigger; }
+
/// Return the list of commands that this hook runs.
StringList &GetCommands() { return m_commands; }
@@ -1791,7 +1799,7 @@ class Target : public std::enable_shared_from_this<Target>,
StringList m_commands;
HookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
- : Hook(target_sp, uid) {}
+ : Hook(target_sp, uid, HookKind::CommandBased) {}
friend class Target;
};
@@ -1815,7 +1823,7 @@ class Target : public std::enable_shared_from_this<Target>,
lldb::ScriptedHookInterfaceSP m_interface_sp;
HookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
- : Hook(target_sp, uid) {}
+ : Hook(target_sp, uid, HookKind::ScriptBased) {}
friend class Target;
};
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 694c56145eee8..6426797eaec3b 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5590,14 +5590,10 @@ Filter options:
Target::HookSP new_hook_sp = target.CreateHook(hook_kind);
- if (is_python_class) {
- // Python class hooks respond to all triggers; the class controls
- // behavior by which callback methods it implements.
- new_hook_sp->SetTriggerMask(Target::Hook::kModulesLoaded |
- Target::Hook::kModulesUnloaded |
- Target::Hook::kProcessStop);
- } else {
+ if (!is_python_class) {
// Build trigger mask from explicit command-line flags.
+ auto *cmd_hook =
+ static_cast<Target::HookCommandLine *>(new_hook_sp.get());
uint32_t trigger_mask = 0;
if (m_options.m_on_load)
trigger_mask |= Target::Hook::kModulesLoaded;
@@ -5605,8 +5601,10 @@ Filter options:
trigger_mask |= Target::Hook::kModulesUnloaded;
if (m_options.m_on_stop)
trigger_mask |= Target::Hook::kProcessStop;
- new_hook_sp->SetTriggerMask(trigger_mask);
+ cmd_hook->SetTriggerMask(trigger_mask);
}
+ // Python class hooks: triggers are computed in SetScriptCallback based
+ // on which callback methods the class implements.
// Set up symbol context specifier if filter options were provided.
if (m_options.m_sym_ctx_specified) {
@@ -5677,7 +5675,7 @@ Filter options:
hook->SetScriptCallback(m_python_class_options.GetName(),
m_python_class_options.GetStructuredData());
if (callback_error.Fail()) {
- result.AppendErrorWithFormat("Couldn't add hook: %s",
+ result.AppendErrorWithFormat("couldn't add hook: %s",
callback_error.AsCString());
target.UndoCreateHook(new_hook_sp->GetID());
return;
@@ -5723,12 +5721,12 @@ class CommandObjectTargetHookDelete : public CommandObjectParsed {
for (size_t i = 0; i < command.GetArgumentCount(); i++) {
lldb::user_id_t user_id;
if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) {
- result.AppendErrorWithFormat("invalid hook id: \"%s\".\n",
+ result.AppendErrorWithFormat("invalid hook id: \"%s\"",
command.GetArgumentAtIndex(i));
return;
}
if (!target.RemoveHookByID(user_id)) {
- result.AppendErrorWithFormat("unknown hook id: \"%s\".\n",
+ result.AppendErrorWithFormat("unknown hook id: \"%s\"",
command.GetArgumentAtIndex(i));
return;
}
@@ -5754,76 +5752,24 @@ class CommandObjectTargetHookEnableDisable : public CommandObjectParsed {
void DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetTarget();
- // Check if the first argument is a trigger name for per-trigger
- // toggling: load, unload, stop.
- uint32_t trigger_type = 0;
- size_t id_start_idx = 0;
-
- if (command.GetArgumentCount() > 0) {
- llvm::StringRef first_arg = command.GetArgumentAtIndex(0);
- if (first_arg == "load") {
- trigger_type = Target::Hook::kModulesLoaded;
- id_start_idx = 1;
- } else if (first_arg == "unload") {
- trigger_type = Target::Hook::kModulesUnloaded;
- id_start_idx = 1;
- } else if (first_arg == "stop") {
- trigger_type = Target::Hook::kProcessStop;
- id_start_idx = 1;
- }
- }
-
- // If no hook IDs given (after possibly consuming trigger name), apply to
- // all.
- if (command.GetArgumentCount() == id_start_idx) {
- if (trigger_type) {
- // Per-trigger toggle on all hooks.
- size_t num_hooks = target.GetNumHooks();
- for (size_t i = 0; i < num_hooks; i++) {
- Target::HookSP hook_sp = target.GetHookAtIndex(i);
- if (!hook_sp)
- continue;
- if (m_enable)
- hook_sp->AddTrigger(trigger_type);
- else
- hook_sp->RemoveTrigger(trigger_type);
- }
- } else {
- // Whole-hook toggle on all hooks.
- target.SetAllHooksEnabledState(m_enable);
- }
+ // No IDs = apply to all hooks.
+ if (command.GetArgumentCount() == 0) {
+ target.SetAllHooksEnabledState(m_enable);
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return;
}
- // Process hook IDs.
- for (size_t i = id_start_idx; i < command.GetArgumentCount(); i++) {
+ for (size_t i = 0; i < command.GetArgumentCount(); i++) {
lldb::user_id_t user_id;
if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) {
- result.AppendErrorWithFormat("invalid hook id: \"%s\".\n",
+ result.AppendErrorWithFormat("invalid hook id: \"%s\"",
command.GetArgumentAtIndex(i));
return;
}
-
- if (trigger_type) {
- // Per-trigger toggle on a specific hook.
- Target::HookSP hook_sp = target.GetHookByID(user_id);
- if (!hook_sp) {
- result.AppendErrorWithFormat("unknown hook id: \"%s\".\n",
- command.GetArgumentAtIndex(i));
- return;
- }
- if (m_enable)
- hook_sp->AddTrigger(trigger_type);
- else
- hook_sp->RemoveTrigger(trigger_type);
- } else {
- // Whole-hook toggle.
- if (!target.SetHookEnabledStateByID(user_id, m_enable)) {
- result.AppendErrorWithFormat("unknown hook id: \"%s\".\n",
- command.GetArgumentAtIndex(i));
- return;
- }
+ if (!target.SetHookEnabledStateByID(user_id, m_enable)) {
+ result.AppendErrorWithFormat("unknown hook id: \"%s\"",
+ command.GetArgumentAtIndex(i));
+ return;
}
}
result.SetStatus(eReturnStatusSuccessFinishNoResult);
@@ -5833,6 +5779,158 @@ class CommandObjectTargetHookEnableDisable : public CommandObjectParsed {
bool m_enable;
};
+#pragma mark CommandObjectTargetHookModify
+
+#define LLDB_OPTIONS_target_hook_modify
+#include "CommandOptions.inc"
+
+/// Modify trigger settings on a hook. Only valid for command-based hooks;
+/// scripted hooks derive their triggers from the class methods.
+class CommandObjectTargetHookModify : public CommandObjectParsed {
+public:
+ class CommandOptions : public Options {
+ public:
+ CommandOptions() = default;
+ ~CommandOptions() override = default;
+
+ llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+ return llvm::ArrayRef(g_target_hook_modify_options);
+ }
+
+ Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+ ExecutionContext *execution_context) override {
+ Status error;
+ const int short_option =
+ g_target_hook_modify_options[option_idx].short_option;
+ switch (short_option) {
+ case 'e':
+ m_enable_trigger = option_arg.str();
+ break;
+ case 'd':
+ m_disable_trigger = option_arg.str();
+ break;
+ default:
+ llvm_unreachable("unhandled option");
+ }
+ return error;
+ }
+
+ void OptionParsingStarting(ExecutionContext *execution_context) override {
+ m_enable_trigger.clear();
+ m_disable_trigger.clear();
+ }
+
+ std::string m_enable_trigger;
+ std::string m_disable_trigger;
+ };
+
+ CommandObjectTargetHookModify(CommandInterpreter &interpreter)
+ : CommandObjectParsed(interpreter, "target hook modify",
+ "Modify trigger settings on a hook.",
+ "target hook modify [--enable-trigger <name>] "
+ "[--disable-trigger <name>] [<id>]") {
+ AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatOptional);
+ SetHelpLong(R"help(
+Modify trigger settings on command-based hooks. Scripted hooks derive their
+triggers from the class methods and cannot be modified.
+
+If no hook ID is given, the last added hook is modified.
+
+Valid trigger names: load, unload, stop.
+
+Examples:
+ target hook modify --enable-trigger stop 1
+ target hook modify --disable-trigger load 1
+ target hook modify --enable-trigger stop (modifies last added hook)
+)help");
+ }
+
+ ~CommandObjectTargetHookModify() override = default;
+
+ Options *GetOptions() override { return &m_options; }
+
+protected:
+ static uint32_t ParseTriggerName(llvm::StringRef name) {
+ if (name == "load")
+ return Target::Hook::kModulesLoaded;
+ if (name == "unload")
+ return Target::Hook::kModulesUnloaded;
+ if (name == "stop")
+ return Target::Hook::kProcessStop;
+ return 0;
+ }
+
+ void DoExecute(Args &command, CommandReturnObject &result) override {
+ Target &target = GetTarget();
+
+ if (m_options.m_enable_trigger.empty() &&
+ m_options.m_disable_trigger.empty()) {
+ result.AppendError("at least one of --enable-trigger or "
+ "--disable-trigger must be specified");
+ return;
+ }
+
+ // Resolve the hook ID. Default to last added if not specified.
+ Target::HookSP hook_sp;
+ if (command.GetArgumentCount() == 0) {
+ size_t num_hooks = target.GetNumHooks();
+ if (num_hooks == 0) {
+ result.AppendError("no hooks exist");
+ return;
+ }
+ hook_sp = target.GetHookAtIndex(num_hooks - 1);
+ } else {
+ lldb::user_id_t user_id;
+ if (!llvm::to_integer(command.GetArgumentAtIndex(0), user_id)) {
+ result.AppendErrorWithFormat("invalid hook id: \"%s\"",
+ command.GetArgumentAtIndex(0));
+ return;
+ }
+ hook_sp = target.GetHookByID(user_id);
+ if (!hook_sp) {
+ result.AppendErrorWithFormat("unknown hook id: \"%s\"",
+ command.GetArgumentAtIndex(0));
+ return;
+ }
+ }
+
+ // Reject trigger modification on scripted hooks.
+ if (hook_sp->GetHookKind() != Target::Hook::HookKind::CommandBased) {
+ result.AppendError("cannot modify triggers on a scripted hook; "
+ "triggers are determined by the class methods");
+ return;
+ }
+ auto *cmd_hook = static_cast<Target::HookCommandLine *>(hook_sp.get());
+
+ if (!m_options.m_enable_trigger.empty()) {
+ uint32_t trigger = ParseTriggerName(m_options.m_enable_trigger);
+ if (!trigger) {
+ result.AppendErrorWithFormat("unknown trigger name: \"%s\". "
+ "Valid names: load, unload, stop",
+ m_options.m_enable_trigger.c_str());
+ return;
+ }
+ cmd_hook->AddTrigger(trigger);
+ }
+
+ if (!m_options.m_disable_trigger.empty()) {
+ uint32_t trigger = ParseTriggerName(m_options.m_disable_trigger);
+ if (!trigger) {
+ result.AppendErrorWithFormat("unknown trigger name: \"%s\". "
+ "Valid names: load, unload, stop",
+ m_options.m_disable_trigger.c_str());
+ return;
+ }
+ cmd_hook->RemoveTrigger(trigger);
+ }
+
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ }
+
+private:
+ CommandOptions m_options;
+};
+
#pragma mark CommandObjectTargetHookList
class CommandObjectTargetHookList : public CommandObjectParsed {
@@ -5877,15 +5975,15 @@ class CommandObjectMultiwordTargetHooks : public CommandObjectMultiword {
LoadSubCommand("disable",
CommandObjectSP(new CommandObjectTargetHookEnableDisable(
interpreter, false, "target hook disable",
- "Disable a hook or a specific trigger on a hook.",
- "target hook disable [<trigger>] [<id> ...]")));
+ "Disable a hook.", "target hook disable [<id> ...]")));
LoadSubCommand("enable",
CommandObjectSP(new CommandObjectTargetHookEnableDisable(
interpreter, true, "target hook enable",
- "Enable a hook or a specific trigger on a hook.",
- "target hook enable [<trigger>] [<id> ...]")));
+ "Enable a hook.", "target hook enable [<id> ...]")));
LoadSubCommand(
"list", CommandObjectSP(new CommandObjectTargetHookList(interpreter)));
+ LoadSubCommand("modify", CommandObjectSP(new CommandObjectTargetHookModify(
+ interpreter)));
}
~CommandObjectMultiwordTargetHooks() override = default;
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 90d4064880392..98ac2134b44c7 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1985,6 +1985,19 @@ let Command = "target hook add" in {
"gains control of the process. Defaults to true.">;
}
+let Command = "target hook modify" in {
+ def target_hook_modify_enable_trigger
+ : Option<"enable-trigger", "e">,
+ Arg<"Name">,
+ Desc<"Enable a trigger on the hook. Valid trigger names: load, "
+ "unload, stop.">;
+ def target_hook_modify_disable_trigger
+ : Option<"disable-trigger", "d">,
+ Arg<"Name">,
+ Desc<"Disable a trigger on the hook. Valid trigger names: load, "
+ "unload, stop.">;
+}
+
let Command = "thread backtrace" in {
def thread_backtrace_count : Option<"count", "c">,
Group<1>,
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
index 78ca14c99bb46..5963a1093a3d6 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
@@ -24,6 +24,32 @@ ScriptedHookPythonInterface::ScriptedHookPythonInterface(
ScriptInterpreterPythonImpl &interpreter)
: ScriptedHookInterface(), ScriptedPythonInterface(interpreter) {}
+ScriptedHookInterface::SupportedHookMethods
+ScriptedHookPythonInterface::GetSupportedMethods() {
+ SupportedHookMethods methods;
+ // Qualify through ScriptedPythonInterface to resolve the diamond
+ // inheritance (both ScriptedHookInterface and ScriptedPythonInterface
+ // inherit ScriptedInterface which owns m_object_instance_sp).
+ auto &obj_sp = ScriptedPythonInterface::m_object_instance_sp;
+ if (!obj_sp)
+ return methods;
+
+ using Locker = ScriptInterpreterPythonImpl::Locker;
+ Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
+ Locker::FreeLock);
+
+ PythonObject implementor(PyRefType::Borrowed, (PyObject *)obj_sp->GetValue());
+ if (!implementor.IsValid())
+ return methods;
+
+ methods.handle_module_loaded =
+ implementor.HasAttribute("handle_module_loaded");
+ methods.handle_module_unloaded =
+ implementor.HasAttribute("handle_module_unloaded");
+ methods.handle_stop = implementor.HasAttribute("handle_stop");
+ return methods;
+}
+
llvm::Expected<StructuredData::GenericSP>
ScriptedHookPythonInterface::CreatePluginObject(
llvm::StringRef class_name, lldb::TargetSP target_sp,
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
index 3480945c0da91..9ea5bcd821805 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
@@ -25,12 +25,16 @@ class ScriptedHookPythonInterface : public ScriptedHookInterface,
const StructuredDataImpl &args_sp) override;
/// A hook class must implement at least one callback. All three are
- /// individually optional; hooks that implement none will simply never fire.
+ /// individually optional; hooks that implement none will be rejected
+ /// at creation time.
llvm::SmallVector<AbstractMethodRequirement>
GetAbstractMethodRequirements() const override {
return {};
}
+ /// Check which of the three hook methods the Python class implements.
+ SupportedHookMethods GetSupportedMethods() override;
+
void HandleModuleLoaded(lldb::StreamSP &output_sp) override;
void HandleModuleUnloaded(lldb::StreamSP &output_sp) override;
llvm::Expected<bool> HandleStop(ExecutionContext &exe_ctx,
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 1e4299c9fd6f1..63680054050b9 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4314,11 +4314,11 @@ void Target::StopHookScripted::GetSubclassDescription(
// Hook
-Target::Hook::Hook(lldb::TargetSP target_sp, lldb::user_id_t uid)
- : UserID(uid), m_target_sp(std::move(target_sp)) {}
+Target::Hook::Hook(lldb::TargetSP target_sp, lldb::user_id_t uid, HookKind kind)
+ : UserID(uid), m_target_sp(std::move(target_sp)), m_kind(kind) {}
Target::Hook::Hook(const Hook &rhs)
- : UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp),
+ : UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), m_kind(rhs.m_kind),
m_enabled(rhs.m_enabled), m_trigger_mask(rhs.m_trigger_mask),
m_sc_specifier_sp(rhs.m_sc_specifier_sp),
m_at_initial_stop(rhs.m_at_initial_stop),
@@ -4380,6 +4380,14 @@ void Target::Hook::GetDescription(Stream &s,
s.Printf("Triggers: %s\n", fires_on.c_str());
}
}
+ // Subclasses add their content (commands or class) then call
+ // GetFilterDescription to print filters.
+ s.IndentLess();
+}
+
+void Target::Hook::GetFilterDescription(Stream &s,
+ lldb::DescriptionLevel level) const {
+ s.IndentMore();
if (m_auto_continue)
s.Indent("AutoContinue on\n");
@@ -4416,6 +4424,7 @@ void Target::HookCommandLine::GetDescription(
return;
}
+ // Commands come after the header (ID, State, Triggers) but before filters.
s.IndentMore();
s.Indent("Commands: \n");
s.IndentMore();
@@ -4425,6 +4434,8 @@ void Target::HookCommandLine::GetDescription(
}
s.IndentLess();
s.IndentLess();
+
+ GetFilterDescription(s, level);
}
// HookCommandLine
@@ -4540,6 +4551,21 @@ Status Target::HookScripted::SetScriptCallback(
"ScriptedHook::%s () - ERROR: %s", __FUNCTION__,
"Failed to create valid script object");
+ // Determine which triggers the class supports by checking which callback
+ // methods it implements.
+ auto methods = m_interface_sp->GetSupportedMethods();
+ if (!methods.any())
+ return Status::FromErrorString(
+ "hook class implements none of the expected methods "
+ "(handle_module_loaded, handle_module_unloaded, handle_stop)");
+
+ if (methods.handle_module_loaded)
+ m_trigger_mask |= kModulesLoaded;
+ if (methods.handle_module_unloaded)
+ m_trigger_mask |= kModulesUnloaded;
+ if (methods.handle_stop)
+ m_trigger_mask |= kProcessStop;
+
return {};
}
@@ -4549,8 +4575,7 @@ void Target::HookScripted::HandleModuleLoaded(StreamSP output_sp) {
StreamSP stream = std::make_shared<StreamString>();
m_interface_sp->HandleModuleLoaded(stream);
- output_sp->PutCString(
- reinterpret_cast<StreamString *>(stream.get())->GetData());
+ output_sp->PutCString(static_cast<StreamString *>(stream.get())->GetData());
}
void Target::HookScripted::HandleModuleUnloaded(StreamSP output_sp) {
@@ -4559,8 +4584,7 @@ void Target::HookScripted::HandleModuleUnloaded(StreamSP output_sp) {
StreamSP stream = std::make_shared<StreamString>();
m_interface_sp->HandleModuleUnloaded(stream);
- output_sp->PutCString(
- reinterpret_cast<StreamString *>(stream.get())->GetData());
+ output_sp->PutCString(static_cast<StreamString *>(stream.get())->GetData());
}
Target::StopHook::StopHookResult
@@ -4574,8 +4598,7 @@ Target::HookScripted::HandleStop(ExecutionContext &exc_ctx,
lldb::StreamSP stream = std::make_shared<lldb_private::StreamString>();
auto should_stop_or_err = m_interface_sp->HandleStop(exc_ctx, stream);
- output_sp->PutCString(
- reinterpret_cast<StreamString *>(stream.get())->GetData());
+ output_sp->PutCString(static_cast<StreamString *>(stream.get())->GetData());
if (!should_stop_or_err)
return StopHook::StopHookResult::KeepStopped;
@@ -4591,45 +4614,35 @@ void Target::HookScripted::GetDescription(Stream &s,
return;
}
+ // Class and args come after the header (ID, State, Triggers) but before
+ // filters.
s.IndentMore();
- s.Indent("Class:");
+ s.Indent("Class: ");
s.Printf("%s\n", m_class_name.c_str());
- if (!m_extra_args.IsValid()) {
- s.IndentLess();
- return;
- }
- StructuredData::ObjectSP object_sp = m_extra_args.GetObjectSP();
- if (!object_sp || !object_sp->IsValid()) {
- s.IndentLess();
- return;
- }
-
- StructuredData::Dictionary *as_dict = object_sp->GetAsDictionary();
- if (!as_dict || !as_dict->IsValid()) {
- s.IndentLess();
- return;
- }
+ if (m_extra_args.IsValid()) {
+ StructuredData::ObjectSP object_sp = m_extra_args.GetObjectSP();
+ if (object_sp && object_sp->IsValid()) {
+ StructuredData::Dictionary *as_dict = object_sp->GetAsDictionary();
+ if (as_dict && as_dict->IsValid() && as_dict->GetSize() > 0) {
+ s.Indent("Args:\n");
+ s.IndentMore();
+
+ auto print_one_element = [&s](llvm::StringRef key,
+ StructuredData::Object *object) {
+ s.Indent();
+ s.Format("{0} : {1}\n", key, object->GetStringValue());
+ return true;
+ };
- uint32_t num_keys = as_dict->GetSize();
- if (num_keys == 0) {
- s.IndentLess();
- return;
+ as_dict->ForEach(print_one_element);
+ s.IndentLess();
+ }
+ }
}
-
- s.Indent("Args:\n");
- s.IndentMore();
-
- auto print_one_element = [&s](llvm::StringRef key,
- StructuredData::Object *object) {
- s.Indent();
- s.Format("{0} : {1}\n", key, object->GetStringValue());
- return true;
- };
-
- as_dict->ForEach(print_one_element);
- s.IndentLess();
s.IndentLess();
+
+ GetFilterDescription(s, level);
}
// Hook management methods
@@ -4697,13 +4710,20 @@ void Target::RunModuleHooks(bool is_load) {
uint32_t trigger = is_load ? Hook::kModulesLoaded : Hook::kModulesUnloaded;
+ // Copy active hooks into a local vector before iterating, in case a
+ // callback modifies m_hooks (same pattern as RunStopHooks).
+ std::vector<HookSP> active_hooks;
+ for (auto &[_, hook_sp] : m_hooks) {
+ if (hook_sp->IsEnabled() && hook_sp->FiresOn(trigger))
+ active_hooks.push_back(hook_sp);
+ }
+
+ if (active_hooks.empty())
+ return;
+
StreamSP output_sp = m_debugger.GetAsyncOutputStream();
- for (auto &[_, hook_sp] : m_hooks) {
- if (!hook_sp->IsEnabled())
- continue;
- if (!hook_sp->FiresOn(trigger))
- continue;
+ for (auto &hook_sp : active_hooks) {
if (is_load)
hook_sp->HandleModuleLoaded(output_sp);
else
diff --git a/lldb/test/Shell/Commands/command-module-hook.test b/lldb/test/Shell/Commands/command-module-hook.test
index 3d4c80db36ce2..0448f8f2aba55 100644
--- a/lldb/test/Shell/Commands/command-module-hook.test
+++ b/lldb/test/Shell/Commands/command-module-hook.test
@@ -144,20 +144,22 @@ target hook list
# CHECK: Hook: 9
# CHECK: State: enabled
# CHECK: Triggers: stop
+# CHECK: Commands:
+# CHECK: bt
# CHECK: Specifier:
# CHECK: Module: mylib.so
-# Per-trigger disable: remove stop from hook #9.
-target hook disable stop 9
+# Per-trigger disable: remove stop from hook #9 using modify.
+target hook modify --disable-trigger stop 9
target hook list
# CHECK: (lldb) target hook list
# CHECK: Hook: 9
# CHECK: State: enabled
# CHECK-NOT: Triggers: stop
-# CHECK: Specifier:
+# CHECK: Commands:
-# Per-trigger enable: re-add stop to hook #9.
-target hook enable stop 9
+# Per-trigger enable: re-add stop to hook #9 using modify.
+target hook modify --enable-trigger stop 9
target hook list
# CHECK: (lldb) target hook list
# CHECK: Hook: 9
diff --git a/lldb/test/Shell/ExecControl/StopHook/Inputs/stop-hook-unified-1.lldbinit b/lldb/test/Shell/ExecControl/StopHook/Inputs/stop-hook-unified-1.lldbinit
new file mode 100644
index 0000000000000..1de4c2964ad31
--- /dev/null
+++ b/lldb/test/Shell/ExecControl/StopHook/Inputs/stop-hook-unified-1.lldbinit
@@ -0,0 +1 @@
+target hook add -S -n b -o "expr ptr"
diff --git a/lldb/test/Shell/ExecControl/StopHook/stop-hook-unified.test b/lldb/test/Shell/ExecControl/StopHook/stop-hook-unified.test
new file mode 100644
index 0000000000000..f456be671cf0e
--- /dev/null
+++ b/lldb/test/Shell/ExecControl/StopHook/stop-hook-unified.test
@@ -0,0 +1,48 @@
+# Test that stop-hooks added via "target hook add -S" work the same as
+# "target stop-hook add".
+#
+# RUN: %clang_host %p/Inputs/stop-hook.c -g -o %t
+# RUN: %lldb -b -s %p/Inputs/stop-hook-unified-1.lldbinit -s %s -f %t \
+# RUN: | FileCheck %s
+# UNSUPPORTED: system-windows
+
+break set -f stop-hook.c -p "// Set breakpoint here to test target stop-hook"
+break set -f stop-hook.c -p "// Another breakpoint which is outside of the stop-hook range"
+target hook list
+
+# CHECK: Hook: 1
+# CHECK-NEXT: State: enabled
+# CHECK-NEXT: Triggers: stop
+# CHECK-NEXT: Commands:
+# CHECK-NEXT: expr ptr
+# CHECK-NEXT: Specifier:
+# CHECK-NEXT: Function: b.
+
+run
+# Stopping inside of the stop hook range
+# CHECK: (lldb) run
+# CHECK-NEXT: (void *) ${{.*}} = 0x
+
+thread step-over
+# Stepping inside of the stop hook range
+# CHECK: (lldb) thread step-over
+# CHECK-NEXT: (void *) ${{.*}} = 0x
+# CHECK: ->{{.*}} // We should stop here after stepping.
+
+process continue
+# Stopping outside of the stop hook range
+# CHECK: (lldb) process continue
+# CHECK-NOT: (void *)
+# CHECK: ->{{.*}} // Another breakpoint which is outside of the stop-hook range.
+
+thread step-over
+# Stepping inside of the stop hook range
+# CHECK: (lldb) thread step-over
+# CHECK-NOT: (void *)
+
+settings set auto-confirm true
+target hook delete
+
+target hook list
+# CHECK: (lldb) target hook list
+# CHECK: No hooks.
More information about the lldb-commits
mailing list