[Lldb-commits] [lldb] [LLDB] Add module hook implementation (PR #185465)
Bar Soloveychik via lldb-commits
lldb-commits at lists.llvm.org
Thu Mar 12 10:38:20 PDT 2026
https://github.com/barsolo2000 updated https://github.com/llvm/llvm-project/pull/185465
>From 69157a9d440450d35a792892d735c841d7a3f9a6 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 1/7] [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 557d73a415452..402af875c7eb5 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 4f5b022765f9e..bf5582d30921d 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1641,6 +1641,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);
@@ -1826,6 +1926,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 59ccf390dea31..b18705e0c6913 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5333,6 +5333,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.
@@ -5625,6 +5950,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 a4d72010d2c4c..8eee059b7c9c0 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 24228a62708fd..1c94c70fa4ccb 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1509,6 +1509,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 f3ee0d91a88d9..c29538ca48cc7 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/OptionValues.h"
@@ -1873,6 +1874,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);
@@ -1932,6 +1934,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);
}
}
@@ -4229,6 +4233,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 6a635e28f40cb535d8a3b7b063345eca88a49080 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 2/7] 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 b18705e0c6913..d62c227ee05f0 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5335,7 +5335,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,
@@ -5347,14 +5347,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;
@@ -5382,10 +5382,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') {
@@ -5393,15 +5393,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:
@@ -5520,9 +5520,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);
}
@@ -5602,9 +5602,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;
@@ -5632,9 +5632,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(
@@ -5643,13 +5643,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)));
@@ -5950,7 +5950,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 8eee059b7c9c0..50fec369330de 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 d2a0b5317ce81e395d880954b8bcbbe01784f047 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 3/7] 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 bf5582d30921d..39f198c19f608 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1643,8 +1643,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);
@@ -1683,10 +1684,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;
@@ -1700,15 +1701,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;
@@ -1723,6 +1724,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 d62c227ee05f0..a83ec3a47693b 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5401,7 +5401,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 fae314ad50e0f7b1687d504f4b1653e2aa6ccb64 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 4/7] 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 402af875c7eb5..07e24d7e82b55 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 39f198c19f608..329d9edafebd4 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1641,44 +1641,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);
@@ -1688,24 +1742,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);
@@ -1713,35 +1771,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);
@@ -1931,9 +1989,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 a83ec3a47693b..f8fd4ecbc68f2 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5333,13 +5333,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:
@@ -5347,14 +5347,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;
@@ -5363,6 +5363,9 @@ class CommandObjectTargetModuleHookAdd : public CommandObjectParsed,
case 'u':
m_on_unload = true;
break;
+ case 'S':
+ m_on_stop = true;
+ break;
default:
llvm_unreachable("unhandled option");
}
@@ -5373,48 +5376,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);
@@ -5422,7 +5437,7 @@ called when --on-unload is specified.
m_all_options.Finalize();
}
- ~CommandObjectTargetModuleHookAdd() override = default;
+ ~CommandObjectTargetHookAdd() override = default;
Options *GetOptions() override { return &m_all_options; }
@@ -5433,75 +5448,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
}
@@ -5512,27 +5530,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;
}
@@ -5540,12 +5826,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;
}
@@ -5554,40 +5840,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);
@@ -5597,26 +5935,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);
@@ -5626,36 +5963,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
@@ -5950,9 +6288,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 50fec369330de..12469f392badf 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 1c94c70fa4ccb..5221da03f3483 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1509,9 +1509,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 c29538ca48cc7..ef342f6cfa859 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/OptionValues.h"
@@ -3159,7 +3159,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
@@ -3210,6 +3219,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;
@@ -3273,6 +3284,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) {
@@ -4233,45 +4291,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)
@@ -4290,7 +4414,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;
@@ -4324,25 +4448,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);
@@ -4355,13 +4512,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;
@@ -4371,7 +4528,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;
@@ -4381,7 +4538,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);
@@ -4418,76 +4595,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 9d0181755560dfab1706d3235c5ae818a521c323 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 5/7] 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 efd53140a91164b53db72a080eec2c9ca26d73f2 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 6/7] 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 ef342f6cfa859..61c05dcef4b2f 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -222,6 +222,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 ace96e909351420389a4a0ae082cbfb6fe75f51e 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 7/7] 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 329d9edafebd4..889eb5360d029 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1655,6 +1655,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),
@@ -1672,17 +1675,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(); }
@@ -1711,17 +1716,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;
@@ -1735,11 +1738,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,
@@ -1757,8 +1764,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 f8fd4ecbc68f2..2adfc65f5a95e 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5768,7 +5768,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 61c05dcef4b2f..12a44ad51511d 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4300,7 +4300,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) {
@@ -4308,8 +4309,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) {
@@ -4317,13 +4318,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 =
@@ -4341,7 +4342,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)
@@ -4363,11 +4363,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();
}
@@ -4381,24 +4381,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));
@@ -4407,6 +4395,7 @@ void Target::HookCommandLine::GetSubclassDescription(
return;
}
+ s.IndentMore();
s.Indent("Commands: \n");
s.IndentMore();
for (uint32_t i = 0; i < m_commands.GetSize(); i++) {
@@ -4414,6 +4403,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) {
@@ -4560,28 +4562,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();
@@ -4595,6 +4608,7 @@ void Target::HookScripted::GetSubclassDescription(
as_dict->ForEach(print_one_element);
s.IndentLess();
+ s.IndentLess();
}
// Hook management methods
More information about the lldb-commits
mailing list