[Lldb-commits] [lldb] [LLDB] Add module hook implementation (PR #185465)
Bar Soloveychik via lldb-commits
lldb-commits at lists.llvm.org
Mon Mar 9 10:27:16 PDT 2026
https://github.com/barsolo2000 created https://github.com/llvm/llvm-project/pull/185465
Ran:
`./bin/llvm-lit -v /home/barsolo/llvm-sand/external/llvm-project/lldb/test/Shell/Commands/command-module-hook*.test`
`./bin/llvm-lit -v /home/barsolo/llvm-sand/external/llvm-project/lldb/test/API/commands/target/module-hook/`
>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] [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
More information about the lldb-commits
mailing list