[Lldb-commits] [lldb] [LLDB] Add module hook implementation (PR #185465)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Apr 7 16:45:16 PDT 2026
================
@@ -5328,6 +5328,606 @@ class CommandObjectMultiwordTargetStopHooks : public CommandObjectMultiword {
~CommandObjectMultiwordTargetStopHooks() override = default;
};
+#pragma mark CommandObjectTargetHookAdd
+
+#define LLDB_OPTIONS_target_hook_add
+#include "CommandOptions.inc"
+
+class CommandObjectTargetHookAdd : 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_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_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 'L':
+ m_on_load = true;
+ break;
+ case 'u':
+ m_on_unload = true;
+ break;
+ case 'S':
+ m_on_stop = true;
+ break;
+ 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_use_one_liner = false;
+ m_one_liner.clear();
+ m_on_load = false;
+ m_on_unload = false;
+ m_on_stop = false;
+ m_sym_ctx_specified = false;
+ m_thread_specified = false;
+ m_module_name.clear();
+ 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;
+ }
+
+ std::vector<std::string> m_one_liner;
+ bool m_use_one_liner = false;
+ bool m_on_load = false;
+ bool m_on_unload = false;
+ bool m_on_stop = false;
+
+ // Filter options (for stop trigger).
+ bool m_sym_ctx_specified = false;
+ bool m_thread_specified = false;
+ std::string m_module_name;
+ 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;
+ };
+
+ CommandObjectTargetHookAdd(CommandInterpreter &interpreter)
+ : CommandObjectParsed(
+ interpreter, "target hook add",
+ "Add a hook to be executed on target lifecycle events.",
+ "target hook add"),
+ IOHandlerDelegateMultiline("DONE",
+ IOHandlerDelegate::Completion::LLDBCommand),
+ m_python_class_options("scripted hook", false, 'P') {
+ SetHelpLong(R"help(
+Command-based hooks:
+--------------------
+ Specify which triggers the hook responds to with --on-load (-L),
+ --on-unload (-u), and/or --on-stop (-S). At least one trigger is required.
+ Provide commands with --one-liner (-o), or omit -o to enter an interactive
+ command editor. All commands run for every trigger the hook is signed up
+ for; there is no per-trigger command list.
+
+ Examples:
+ target hook add -L -o "script print('module loaded')"
+ target hook add -L -u -o "script print('module event')"
+ target hook add -S -o "bt"
+ target hook add -L -u -S -o "script print('all events')"
+ target hook add -S -s mylib.so -n main -o "bt"
+ target hook add -S -G true -o "thread info"
+
+Python class hooks:
+-------------------
+ Provide a Python class with --python-class (-P). The class controls which
+ events it handles by implementing the corresponding methods; you do not
+ specify triggers on the command line. Use -k <key> -v <value> to pass
+ extra_args to the class constructor.
+
+ Examples:
+ target hook add -P mymodule.MyHook
+ target hook add -P mymodule.MyHook -k verbose -v true
+
+ The Python class should implement at least one of these methods:
+
+ class MyHook:
+ def __init__(self, target, extra_args, internal_dict):
+ self.target = target
+ def handle_module_loaded(self, stream):
+ pass
+ def handle_module_unloaded(self, stream):
+ pass
+ def handle_stop(self, exe_ctx, stream):
+ return True # True = should_stop, False = continue
+
+Filter options:
+---------------
+ Filters (-s, -f, -l, -e, -c, -n, -x, -t, -T, -q) restrict when the hook
+ fires. They apply to both command-based and Python class hooks.
+)help");
+ // Python class options (-P, -k, -v) are placed in Set 2 (dst_mask).
+ // src_mask must cover Set 1 | Set 2 to match the internal usage masks of
+ // OptionGroupPythonClassWithDict (class=Set1, key/value=Set2).
+ // Since -o, -L, -u, -S are Group<1> only, the parser prevents mixing.
+ m_all_options.Append(&m_python_class_options,
+ LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_2);
+ m_all_options.Append(&m_options);
+ m_all_options.Finalize();
+ }
+
+ ~CommandObjectTargetHookAdd() override = default;
+
+ Options *GetOptions() override { return &m_all_options; }
+
+protected:
+ void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
+ if (interactive) {
+ if (lldb::LockableStreamFileSP output_sp =
+ io_handler.GetOutputStreamFileSP()) {
+ LockedStreamFile locked_stream = output_sp->Lock();
+ locked_stream.PutCString(
+ "Enter your hook command(s). Type 'DONE' to end.\n");
+ }
+ }
+ }
+
+ void IOHandlerInputComplete(IOHandler &io_handler,
+ std::string &line) override {
+ if (m_hook_sp) {
+ if (line.empty()) {
+ if (lldb::LockableStreamFileSP error_sp =
+ io_handler.GetErrorStreamFileSP()) {
+ LockedStreamFile locked_stream = error_sp->Lock();
+ locked_stream.Printf("error: hook #%" PRIu64
+ " aborted, no commands.\n",
+ m_hook_sp->GetID());
+ }
+ GetTarget().UndoCreateHook(m_hook_sp->GetID());
+ } else {
+ auto *hook = static_cast<Target::HookCommandLine *>(m_hook_sp.get());
+ hook->SetActionFromString(line);
+ if (lldb::LockableStreamFileSP output_sp =
+ io_handler.GetOutputStreamFileSP()) {
+ LockedStreamFile locked_stream = output_sp->Lock();
+ locked_stream.Printf("Hook #%" PRIu64 " added.\n",
+ m_hook_sp->GetID());
+ }
+ }
+ m_hook_sp.reset();
+ }
+ io_handler.SetIsDone(true);
+ }
+
+ void DoExecute(Args &command, CommandReturnObject &result) override {
+ m_hook_sp.reset();
+ Target &target = GetTarget();
+
+ bool is_python_class = !m_python_class_options.GetName().empty();
+
+ // Command-based hooks require at least one explicit trigger.
+ if (!is_python_class && !m_options.m_on_load && !m_options.m_on_unload &&
+ !m_options.m_on_stop) {
+ result.AppendError("at least one trigger must be specified: "
+ "--on-load (-L), --on-unload (-u), or --on-stop (-S)");
+ return;
+ }
+
+ Target::Hook::HookKind hook_kind =
+ is_python_class ? Target::Hook::HookKind::ScriptBased
+ : Target::Hook::HookKind::CommandBased;
+
+ Target::HookSP new_hook_sp = target.CreateHook(hook_kind);
+
+ if (is_python_class) {
+ // Python class hooks respond to all triggers; the class controls
+ // behavior by which callback methods it implements.
+ new_hook_sp->SetTriggerMask(Target::Hook::kModulesLoaded |
----------------
jimingham wrote:
I think this provides a confusing UI. If I add a class that does modules loaded and modules unloaded and look at the description, it will tell me that it fires on stop. That would be disconcerting.
I don't think it would be hard to go from the class to the triggers it supports, since that's just looking up methods in the Python class.
https://github.com/llvm/llvm-project/pull/185465
More information about the lldb-commits
mailing list