[LLDB] Add module hook implementation (#185465)
Introduce a unified target hook command system for LLDB that handles
multiple target lifecycle events (module load, module unload, process
stop) through a single Hook class.
The existing target stop-hook commands remain unchanged for now, meaning
stop-hooks can now be created through either target stop-hook add or
target hook add -S.
A future follow-up can alias target stop-hook to the unified system to
consolidate the two paths.
New commands:
- target hook add [-o cmd] [-P class] [-u] [-S] - create a hook (fires
on module load by default, optionally on unload/stop)
- target hook add-filter [filter-opts] <hook-id> - attach stop-event
filters (shlib, function, file/line, thread) to an existing hook
- target hook list / delete / enable / disable - manage hooks
- target hook {enable,disable} {module-loaded|module-unloaded|stop-hook}
<id> - per-event toggling
top-event filter options live on a separate add-filter command rather
than on target hook add, keeping the add command clean and making the
system safe for filters on other event types.
Ran the tests + Made sure stop tests are passing as well.
---------
Co-authored-by: Bar Soloveychik <barsolo@fb.com>
This commit is contained in:
parent
f70ed1f758
commit
b1c027b3aa
21 changed files with 2000 additions and 1 deletions
|
|
@ -0,0 +1,53 @@
|
|||
//===-- 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:
|
||||
/// Describes which hook callback methods the Python class implements.
|
||||
struct SupportedHookMethods {
|
||||
bool handle_module_loaded = false;
|
||||
bool handle_module_unloaded = false;
|
||||
bool handle_stop = false;
|
||||
|
||||
bool any() const {
|
||||
return handle_module_loaded || handle_module_unloaded || handle_stop;
|
||||
}
|
||||
};
|
||||
|
||||
virtual llvm::Expected<StructuredData::GenericSP>
|
||||
CreatePluginObject(llvm::StringRef class_name, lldb::TargetSP target_sp,
|
||||
const StructuredDataImpl &args_sp) = 0;
|
||||
|
||||
/// Check which hook callback methods the Python class implements.
|
||||
/// Called after CreatePluginObject to determine the trigger mask.
|
||||
virtual SupportedHookMethods GetSupportedMethods() { return {}; }
|
||||
|
||||
/// Called when modules are loaded into the target.
|
||||
virtual void HandleModuleLoaded(lldb::StreamSP &output_sp) {}
|
||||
|
||||
/// 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
|
||||
|
|
@ -562,6 +562,10 @@ public:
|
|||
return {};
|
||||
}
|
||||
|
||||
virtual lldb::ScriptedHookInterfaceSP CreateScriptedHookInterface() {
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual lldb::ScriptedBreakpointInterfaceSP
|
||||
CreateScriptedBreakpointInterface() {
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -1666,6 +1666,201 @@ public:
|
|||
|
||||
typedef std::shared_ptr<StopHook> StopHookSP;
|
||||
|
||||
// Target Hooks
|
||||
//
|
||||
// Hooks fire on target lifecycle events. There are two flows:
|
||||
//
|
||||
// Command-based hooks: the user specifies which triggers the hook responds
|
||||
// to (--on-load, --on-unload, --on-stop) and provides a list of commands.
|
||||
// All commands run for every trigger the hook is signed up for.
|
||||
//
|
||||
// Python class hooks: the user provides a Python class name and optional
|
||||
// extra_args that will be passed to the hook init method (-k key -v value).
|
||||
// The class controls which events it handles by implementing the
|
||||
// corresponding callback methods (handle_module_loaded,
|
||||
// handle_module_unloaded, handle_stop). Triggers are set automatically
|
||||
// based on which methods exist.
|
||||
class Hook : public UserID {
|
||||
public:
|
||||
Hook(const Hook &rhs);
|
||||
virtual ~Hook() = default;
|
||||
|
||||
enum class HookKind : uint32_t { CommandBased = 0, ScriptBased };
|
||||
|
||||
HookKind GetHookKind() const { return m_kind; }
|
||||
|
||||
/// Individual trigger bits. Combine with bitwise OR to form a trigger mask.
|
||||
// FIXME: Add kProcessExit, kProcessDetach, etc. as needed.
|
||||
enum TriggerBit : uint32_t {
|
||||
kModulesLoaded = (1u << 0),
|
||||
kModulesUnloaded = (1u << 1),
|
||||
kProcessStop = (1u << 2),
|
||||
};
|
||||
|
||||
lldb::TargetSP &GetTarget() { return m_target_sp; }
|
||||
|
||||
bool IsEnabled() { return m_enabled; }
|
||||
void SetIsEnabled(bool enabled) { m_enabled = enabled; }
|
||||
|
||||
/// Return the bitmask of triggers this hook responds to.
|
||||
/// Each bit corresponds to a TriggerBit value.
|
||||
uint32_t GetTriggerMask() const { return m_trigger_mask; }
|
||||
|
||||
/// Return true if this hook fires on the given trigger.
|
||||
bool FiresOn(uint32_t trigger) const { return m_trigger_mask & trigger; }
|
||||
|
||||
// Filter fields
|
||||
|
||||
/// 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 takes ownership.
|
||||
void SetThreadSpecifier(ThreadSpec *specifier);
|
||||
ThreadSpec *GetThreadSpecifier() { return m_thread_spec_up.get(); }
|
||||
|
||||
void SetRunAtInitialStop(bool at_initial_stop) {
|
||||
m_at_initial_stop = at_initial_stop;
|
||||
}
|
||||
bool GetRunAtInitialStop() const { return m_at_initial_stop; }
|
||||
|
||||
// Reaction settings
|
||||
|
||||
void SetAutoContinue(bool auto_continue) {
|
||||
m_auto_continue = auto_continue;
|
||||
}
|
||||
bool GetAutoContinue() const { return m_auto_continue; }
|
||||
|
||||
void SetSuppressOutput(bool suppress_output) {
|
||||
m_suppress_output = suppress_output;
|
||||
}
|
||||
bool GetSuppressOutput() const { return m_suppress_output; }
|
||||
|
||||
// Event handler methods (default no-ops)
|
||||
|
||||
virtual void HandleModuleLoaded(lldb::StreamSP output) {}
|
||||
virtual void HandleModuleUnloaded(lldb::StreamSP output) {}
|
||||
|
||||
/// Called when the process stops. Returns a StopHookResult indicating
|
||||
/// whether the process should remain stopped or continue.
|
||||
virtual StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
|
||||
lldb::StreamSP output) {
|
||||
return StopHook::StopHookResult::NoPreference;
|
||||
}
|
||||
|
||||
virtual void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
|
||||
|
||||
protected:
|
||||
/// Print the filter portion of the description (AutoContinue, Specifier,
|
||||
/// ThreadSpec). Called by subclass GetDescription after printing the
|
||||
/// hook-specific content (commands or class).
|
||||
void GetFilterDescription(Stream &s, lldb::DescriptionLevel level) const;
|
||||
lldb::TargetSP m_target_sp;
|
||||
HookKind m_kind;
|
||||
bool m_enabled = true;
|
||||
uint32_t m_trigger_mask = 0; // No default, triggers must be explicit.
|
||||
|
||||
// Filters
|
||||
lldb::SymbolContextSpecifierSP m_sc_specifier_sp;
|
||||
std::unique_ptr<ThreadSpec> m_thread_spec_up;
|
||||
bool m_at_initial_stop = true;
|
||||
|
||||
// Reaction settings
|
||||
bool m_auto_continue = false;
|
||||
bool m_suppress_output = false;
|
||||
|
||||
Hook(lldb::TargetSP target_sp, lldb::user_id_t uid, HookKind kind);
|
||||
};
|
||||
|
||||
class HookCommandLine : public Hook {
|
||||
public:
|
||||
~HookCommandLine() override = default;
|
||||
|
||||
/// Replace the trigger mask. \a mask is a bitwise OR of TriggerBit values.
|
||||
void SetTriggerMask(uint32_t mask) { m_trigger_mask = mask; }
|
||||
|
||||
/// Add a trigger to the mask. \a trigger is a single TriggerBit value.
|
||||
void AddTrigger(uint32_t trigger) { m_trigger_mask |= trigger; }
|
||||
|
||||
/// Remove a trigger from the mask. \a trigger is a single TriggerBit value.
|
||||
void RemoveTrigger(uint32_t trigger) { m_trigger_mask &= ~trigger; }
|
||||
|
||||
/// Return the list of commands that this hook runs.
|
||||
StringList &GetCommands() { return m_commands; }
|
||||
|
||||
/// 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 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,
|
||||
lldb::StreamSP output) override;
|
||||
|
||||
private:
|
||||
StringList m_commands;
|
||||
|
||||
HookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
|
||||
: Hook(target_sp, uid, HookKind::CommandBased) {}
|
||||
friend class Target;
|
||||
};
|
||||
|
||||
class HookScripted : public Hook {
|
||||
public:
|
||||
~HookScripted() override = default;
|
||||
|
||||
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,
|
||||
lldb::StreamSP output) override;
|
||||
|
||||
Status SetScriptCallback(std::string class_name,
|
||||
StructuredData::ObjectSP extra_args_sp);
|
||||
|
||||
private:
|
||||
std::string m_class_name;
|
||||
StructuredDataImpl m_extra_args;
|
||||
lldb::ScriptedHookInterfaceSP m_interface_sp;
|
||||
|
||||
HookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
|
||||
: Hook(target_sp, uid, HookKind::ScriptBased) {}
|
||||
friend class Target;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Hook> HookSP;
|
||||
|
||||
HookSP CreateHook(Hook::HookKind kind);
|
||||
|
||||
/// 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 UndoCreateHook(lldb::user_id_t uid);
|
||||
|
||||
bool RemoveHookByID(lldb::user_id_t uid);
|
||||
|
||||
void RemoveAllHooks();
|
||||
|
||||
HookSP GetHookByID(lldb::user_id_t uid);
|
||||
|
||||
bool SetHookEnabledStateByID(lldb::user_id_t uid, bool enabled);
|
||||
|
||||
void SetAllHooksEnabledState(bool enabled);
|
||||
|
||||
size_t GetNumHooks() const { return m_hooks.size(); }
|
||||
|
||||
HookSP GetHookAtIndex(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);
|
||||
|
|
@ -1859,6 +2054,10 @@ protected:
|
|||
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, 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
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ class ScriptedFrameInterface;
|
|||
class ScriptedFrameProviderInterface;
|
||||
class ScriptedMetadata;
|
||||
class ScriptedBreakpointInterface;
|
||||
class ScriptedHookInterface;
|
||||
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::ScriptedHookInterface>
|
||||
ScriptedHookInterfaceSP;
|
||||
typedef std::shared_ptr<lldb_private::ScriptedThreadInterface>
|
||||
ScriptedThreadInterfaceSP;
|
||||
typedef std::shared_ptr<lldb_private::ScriptedThreadPlanInterface>
|
||||
|
|
|
|||
|
|
@ -5290,6 +5290,708 @@ public:
|
|||
~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) {
|
||||
// Build trigger mask from explicit command-line flags.
|
||||
auto *cmd_hook =
|
||||
static_cast<Target::HookCommandLine *>(new_hook_sp.get());
|
||||
uint32_t trigger_mask = 0;
|
||||
if (m_options.m_on_load)
|
||||
trigger_mask |= Target::Hook::kModulesLoaded;
|
||||
if (m_options.m_on_unload)
|
||||
trigger_mask |= Target::Hook::kModulesUnloaded;
|
||||
if (m_options.m_on_stop)
|
||||
trigger_mask |= Target::Hook::kProcessStop;
|
||||
cmd_hook->SetTriggerMask(trigger_mask);
|
||||
}
|
||||
// Python class hooks: triggers are computed in SetScriptCallback based
|
||||
// on which callback methods the class implements.
|
||||
|
||||
// Set up symbol context specifier if filter options were provided.
|
||||
if (m_options.m_sym_ctx_specified) {
|
||||
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);
|
||||
|
||||
new_hook_sp->SetSCSpecifier(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());
|
||||
|
||||
new_hook_sp->SetThreadSpecifier(thread_spec);
|
||||
}
|
||||
|
||||
new_hook_sp->SetAutoContinue(m_options.m_auto_continue);
|
||||
new_hook_sp->SetRunAtInitialStop(m_options.m_at_initial_stop);
|
||||
|
||||
if (m_options.m_use_one_liner) {
|
||||
auto *hook = static_cast<Target::HookCommandLine *>(new_hook_sp.get());
|
||||
hook->SetActionFromStrings(m_options.m_one_liner);
|
||||
result.AppendMessageWithFormatv("Hook #{0} added.\n",
|
||||
new_hook_sp->GetID());
|
||||
} else if (!m_python_class_options.GetName().empty()) {
|
||||
auto *hook = static_cast<Target::HookScripted *>(new_hook_sp.get());
|
||||
Status callback_error =
|
||||
hook->SetScriptCallback(m_python_class_options.GetName(),
|
||||
m_python_class_options.GetStructuredData());
|
||||
if (callback_error.Fail()) {
|
||||
result.AppendErrorWithFormat("couldn't add hook: %s",
|
||||
callback_error.AsCString());
|
||||
target.UndoCreateHook(new_hook_sp->GetID());
|
||||
return;
|
||||
}
|
||||
result.AppendMessageWithFormatv("Hook #{0} added.\n",
|
||||
new_hook_sp->GetID());
|
||||
} else {
|
||||
m_hook_sp = new_hook_sp;
|
||||
m_interpreter.GetLLDBCommandsFromIOHandler("> ", // prompt
|
||||
*this); // delegate
|
||||
}
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
}
|
||||
|
||||
private:
|
||||
CommandOptions m_options;
|
||||
OptionGroupPythonClassWithDict m_python_class_options;
|
||||
OptionGroupOptions m_all_options;
|
||||
Target::HookSP m_hook_sp;
|
||||
};
|
||||
|
||||
#pragma mark CommandObjectTargetHookDelete
|
||||
|
||||
class CommandObjectTargetHookDelete : public CommandObjectParsed {
|
||||
public:
|
||||
CommandObjectTargetHookDelete(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(interpreter, "target hook delete", "Delete a hook.",
|
||||
"target hook delete [<id>]") {
|
||||
AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar);
|
||||
}
|
||||
|
||||
~CommandObjectTargetHookDelete() override = default;
|
||||
|
||||
protected:
|
||||
void DoExecute(Args &command, CommandReturnObject &result) override {
|
||||
Target &target = GetTarget();
|
||||
if (command.GetArgumentCount() == 0) {
|
||||
if (!m_interpreter.Confirm("Delete all hooks?", true)) {
|
||||
result.SetStatus(eReturnStatusFailed);
|
||||
return;
|
||||
}
|
||||
target.RemoveAllHooks();
|
||||
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 hook id: \"%s\"",
|
||||
command.GetArgumentAtIndex(i));
|
||||
return;
|
||||
}
|
||||
if (!target.RemoveHookByID(user_id)) {
|
||||
result.AppendErrorWithFormat("unknown hook id: \"%s\"",
|
||||
command.GetArgumentAtIndex(i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
}
|
||||
};
|
||||
|
||||
#pragma mark CommandObjectTargetHookEnableDisable
|
||||
|
||||
class CommandObjectTargetHookEnableDisable : public CommandObjectParsed {
|
||||
public:
|
||||
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);
|
||||
}
|
||||
|
||||
~CommandObjectTargetHookEnableDisable() override = default;
|
||||
|
||||
protected:
|
||||
void DoExecute(Args &command, CommandReturnObject &result) override {
|
||||
Target &target = GetTarget();
|
||||
|
||||
// No IDs = apply to all hooks.
|
||||
if (command.GetArgumentCount() == 0) {
|
||||
target.SetAllHooksEnabledState(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 hook id: \"%s\"",
|
||||
command.GetArgumentAtIndex(i));
|
||||
return;
|
||||
}
|
||||
if (!target.SetHookEnabledStateByID(user_id, m_enable)) {
|
||||
result.AppendErrorWithFormat("unknown hook id: \"%s\"",
|
||||
command.GetArgumentAtIndex(i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_enable;
|
||||
};
|
||||
|
||||
#pragma mark CommandObjectTargetHookModify
|
||||
|
||||
#define LLDB_OPTIONS_target_hook_modify
|
||||
#include "CommandOptions.inc"
|
||||
|
||||
/// Modify trigger settings on a hook. Only valid for command-based hooks;
|
||||
/// scripted hooks derive their triggers from the class methods.
|
||||
class CommandObjectTargetHookModify : public CommandObjectParsed {
|
||||
public:
|
||||
class CommandOptions : public Options {
|
||||
public:
|
||||
CommandOptions() = default;
|
||||
~CommandOptions() override = default;
|
||||
|
||||
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
||||
return llvm::ArrayRef(g_target_hook_modify_options);
|
||||
}
|
||||
|
||||
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
||||
ExecutionContext *execution_context) override {
|
||||
Status error;
|
||||
const int short_option =
|
||||
g_target_hook_modify_options[option_idx].short_option;
|
||||
switch (short_option) {
|
||||
case 'e':
|
||||
m_enable_trigger = option_arg.str();
|
||||
break;
|
||||
case 'd':
|
||||
m_disable_trigger = option_arg.str();
|
||||
break;
|
||||
default:
|
||||
llvm_unreachable("unhandled option");
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
||||
m_enable_trigger.clear();
|
||||
m_disable_trigger.clear();
|
||||
}
|
||||
|
||||
std::string m_enable_trigger;
|
||||
std::string m_disable_trigger;
|
||||
};
|
||||
|
||||
CommandObjectTargetHookModify(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(interpreter, "target hook modify",
|
||||
"Modify trigger settings on a hook.",
|
||||
"target hook modify [--enable-trigger <name>] "
|
||||
"[--disable-trigger <name>] [<id>]") {
|
||||
AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatOptional);
|
||||
SetHelpLong(R"help(
|
||||
Modify trigger settings on command-based hooks. Scripted hooks derive their
|
||||
triggers from the class methods and cannot be modified.
|
||||
|
||||
If no hook ID is given, the last added hook is modified.
|
||||
|
||||
Valid trigger names: load, unload, stop.
|
||||
|
||||
Examples:
|
||||
target hook modify --enable-trigger stop 1
|
||||
target hook modify --disable-trigger load 1
|
||||
target hook modify --enable-trigger stop (modifies last added hook)
|
||||
)help");
|
||||
}
|
||||
|
||||
~CommandObjectTargetHookModify() override = default;
|
||||
|
||||
Options *GetOptions() override { return &m_options; }
|
||||
|
||||
protected:
|
||||
static uint32_t ParseTriggerName(llvm::StringRef name) {
|
||||
if (name == "load")
|
||||
return Target::Hook::kModulesLoaded;
|
||||
if (name == "unload")
|
||||
return Target::Hook::kModulesUnloaded;
|
||||
if (name == "stop")
|
||||
return Target::Hook::kProcessStop;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DoExecute(Args &command, CommandReturnObject &result) override {
|
||||
Target &target = GetTarget();
|
||||
|
||||
if (m_options.m_enable_trigger.empty() &&
|
||||
m_options.m_disable_trigger.empty()) {
|
||||
result.AppendError("at least one of --enable-trigger or "
|
||||
"--disable-trigger must be specified");
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve the hook ID. Default to last added if not specified.
|
||||
Target::HookSP hook_sp;
|
||||
if (command.GetArgumentCount() == 0) {
|
||||
size_t num_hooks = target.GetNumHooks();
|
||||
if (num_hooks == 0) {
|
||||
result.AppendError("no hooks exist");
|
||||
return;
|
||||
}
|
||||
hook_sp = target.GetHookAtIndex(num_hooks - 1);
|
||||
} else {
|
||||
lldb::user_id_t user_id;
|
||||
if (!llvm::to_integer(command.GetArgumentAtIndex(0), user_id)) {
|
||||
result.AppendErrorWithFormat("invalid hook id: \"%s\"",
|
||||
command.GetArgumentAtIndex(0));
|
||||
return;
|
||||
}
|
||||
hook_sp = target.GetHookByID(user_id);
|
||||
if (!hook_sp) {
|
||||
result.AppendErrorWithFormat("unknown hook id: \"%s\"",
|
||||
command.GetArgumentAtIndex(0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Reject trigger modification on scripted hooks.
|
||||
if (hook_sp->GetHookKind() != Target::Hook::HookKind::CommandBased) {
|
||||
result.AppendError("cannot modify triggers on a scripted hook; "
|
||||
"triggers are determined by the class methods");
|
||||
return;
|
||||
}
|
||||
auto *cmd_hook = static_cast<Target::HookCommandLine *>(hook_sp.get());
|
||||
|
||||
if (!m_options.m_enable_trigger.empty()) {
|
||||
uint32_t trigger = ParseTriggerName(m_options.m_enable_trigger);
|
||||
if (!trigger) {
|
||||
result.AppendErrorWithFormat("unknown trigger name: \"%s\". "
|
||||
"Valid names: load, unload, stop",
|
||||
m_options.m_enable_trigger.c_str());
|
||||
return;
|
||||
}
|
||||
cmd_hook->AddTrigger(trigger);
|
||||
}
|
||||
|
||||
if (!m_options.m_disable_trigger.empty()) {
|
||||
uint32_t trigger = ParseTriggerName(m_options.m_disable_trigger);
|
||||
if (!trigger) {
|
||||
result.AppendErrorWithFormat("unknown trigger name: \"%s\". "
|
||||
"Valid names: load, unload, stop",
|
||||
m_options.m_disable_trigger.c_str());
|
||||
return;
|
||||
}
|
||||
cmd_hook->RemoveTrigger(trigger);
|
||||
}
|
||||
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
}
|
||||
|
||||
private:
|
||||
CommandOptions m_options;
|
||||
};
|
||||
|
||||
#pragma mark CommandObjectTargetHookList
|
||||
|
||||
class CommandObjectTargetHookList : public CommandObjectParsed {
|
||||
public:
|
||||
CommandObjectTargetHookList(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(interpreter, "target hook list", "List all hooks.",
|
||||
"target hook list") {}
|
||||
|
||||
~CommandObjectTargetHookList() override = default;
|
||||
|
||||
protected:
|
||||
void DoExecute(Args &command, CommandReturnObject &result) override {
|
||||
Target &target = GetTarget();
|
||||
size_t num_hooks = target.GetNumHooks();
|
||||
if (num_hooks == 0) {
|
||||
result.GetOutputStream().PutCString("No hooks.\n");
|
||||
} else {
|
||||
for (size_t i = 0; i < num_hooks; i++) {
|
||||
Target::HookSP hook_sp = target.GetHookAtIndex(i);
|
||||
if (hook_sp)
|
||||
hook_sp->GetDescription(result.GetOutputStream(),
|
||||
eDescriptionLevelFull);
|
||||
}
|
||||
}
|
||||
result.SetStatus(eReturnStatusSuccessFinishResult);
|
||||
}
|
||||
};
|
||||
|
||||
#pragma mark CommandObjectMultiwordTargetHooks
|
||||
|
||||
class CommandObjectMultiwordTargetHooks : public CommandObjectMultiword {
|
||||
public:
|
||||
CommandObjectMultiwordTargetHooks(CommandInterpreter &interpreter)
|
||||
: CommandObjectMultiword(
|
||||
interpreter, "target hook",
|
||||
"Commands for operating on target hooks.",
|
||||
"target hook <subcommand> [<subcommand-options>]") {
|
||||
LoadSubCommand(
|
||||
"add", CommandObjectSP(new CommandObjectTargetHookAdd(interpreter)));
|
||||
LoadSubCommand("delete", CommandObjectSP(new CommandObjectTargetHookDelete(
|
||||
interpreter)));
|
||||
LoadSubCommand("disable",
|
||||
CommandObjectSP(new CommandObjectTargetHookEnableDisable(
|
||||
interpreter, false, "target hook disable",
|
||||
"Disable a hook.", "target hook disable [<id> ...]")));
|
||||
LoadSubCommand("enable",
|
||||
CommandObjectSP(new CommandObjectTargetHookEnableDisable(
|
||||
interpreter, true, "target hook enable",
|
||||
"Enable a hook.", "target hook enable [<id> ...]")));
|
||||
LoadSubCommand(
|
||||
"list", CommandObjectSP(new CommandObjectTargetHookList(interpreter)));
|
||||
LoadSubCommand("modify", CommandObjectSP(new CommandObjectTargetHookModify(
|
||||
interpreter)));
|
||||
}
|
||||
|
||||
~CommandObjectMultiwordTargetHooks() override = default;
|
||||
};
|
||||
|
||||
#pragma mark CommandObjectTargetDumpTypesystem
|
||||
|
||||
/// Dumps the TypeSystem of the selected Target.
|
||||
|
|
@ -5583,6 +6285,8 @@ CommandObjectMultiwordTarget::CommandObjectMultiwordTarget(
|
|||
LoadSubCommand(
|
||||
"stop-hook",
|
||||
CommandObjectSP(new CommandObjectMultiwordTargetStopHooks(interpreter)));
|
||||
LoadSubCommand("hook", CommandObjectSP(new CommandObjectMultiwordTargetHooks(
|
||||
interpreter)));
|
||||
LoadSubCommand("modules",
|
||||
CommandObjectSP(new CommandObjectTargetModules(interpreter)));
|
||||
LoadSubCommand("symbols",
|
||||
|
|
|
|||
|
|
@ -1901,6 +1901,103 @@ let Command = "target stop_hook list" in {
|
|||
Desc<"Show debugger ${i}nternal stop hooks.">;
|
||||
}
|
||||
|
||||
let Command = "target hook add" in {
|
||||
// Command-based options (Group 1 only -- mutually exclusive with -P).
|
||||
def target_hook_add_one_liner
|
||||
: Option<"one-liner", "o">,
|
||||
Group<1>,
|
||||
Arg<"OneLiner">,
|
||||
Desc<"Add a command for the hook. Can be specified more than "
|
||||
"once, and commands will be run in the order they appear.">;
|
||||
def target_hook_add_on_load
|
||||
: Option<"on-load", "L">,
|
||||
Group<1>,
|
||||
Desc<"Fire the hook when modules are ${L}oaded.">;
|
||||
def target_hook_add_on_unload
|
||||
: Option<"on-unload", "u">,
|
||||
Group<1>,
|
||||
Desc<"Fire the hook when modules are ${u}nloaded.">;
|
||||
def target_hook_add_on_stop
|
||||
: Option<"on-stop", "S">,
|
||||
Group<1>,
|
||||
Desc<"Fire the hook when the process ${S}tops.">;
|
||||
// Shared options (both command-based and Python class hooks).
|
||||
def target_hook_add_shlib
|
||||
: Option<"shlib", "s">,
|
||||
Arg<"ShlibName">,
|
||||
Completion<"Module">,
|
||||
Desc<"Set the module within which the stop handler is to be run.">;
|
||||
def target_hook_add_thread_index
|
||||
: Option<"thread-index", "x">,
|
||||
Arg<"ThreadIndex">,
|
||||
Desc<"The stop handler is run only for the thread whose inde${x} "
|
||||
"matches this argument.">;
|
||||
def target_hook_add_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_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_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_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_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_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_classname
|
||||
: Option<"classname", "c">,
|
||||
Arg<"ClassName">,
|
||||
Desc<"Specify the ${c}lass within which the stop handler is to "
|
||||
"be run.">;
|
||||
def target_hook_add_name
|
||||
: Option<"name", "n">,
|
||||
Arg<"FunctionName">,
|
||||
Desc<"Set the function ${n}ame within which the stop handler will "
|
||||
"be run.">,
|
||||
Completion<"Symbol">;
|
||||
def target_hook_add_auto_continue
|
||||
: Option<"auto-continue", "G">,
|
||||
Arg<"Boolean">,
|
||||
Desc<"The hook will auto-continue after running its stop handler.">;
|
||||
def target_hook_add_at_initial_stop
|
||||
: Option<"at-initial-stop", "I">,
|
||||
Arg<"Boolean">,
|
||||
Desc<"Whether the stop handler will trigger when lldb ${I}nitially "
|
||||
"gains control of the process. Defaults to true.">;
|
||||
}
|
||||
|
||||
let Command = "target hook modify" in {
|
||||
def target_hook_modify_enable_trigger
|
||||
: Option<"enable-trigger", "e">,
|
||||
Arg<"Name">,
|
||||
Desc<"Enable a trigger on the hook. Valid trigger names: load, "
|
||||
"unload, stop.">;
|
||||
def target_hook_modify_disable_trigger
|
||||
: Option<"disable-trigger", "d">,
|
||||
Arg<"Name">,
|
||||
Desc<"Disable a trigger on the hook. Valid trigger names: load, "
|
||||
"unload, stop.">;
|
||||
}
|
||||
|
||||
let Command = "thread backtrace" in {
|
||||
def thread_backtrace_count : Option<"count", "c">,
|
||||
Group<1>,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ add_lldb_library(lldbPluginScriptInterpreterPython PLUGIN
|
|||
Interfaces/ScriptedProcessPythonInterface.cpp
|
||||
Interfaces/ScriptedPythonInterface.cpp
|
||||
Interfaces/ScriptedStopHookPythonInterface.cpp
|
||||
Interfaces/ScriptedHookPythonInterface.cpp
|
||||
Interfaces/ScriptedBreakpointPythonInterface.cpp
|
||||
Interfaces/ScriptedThreadPlanPythonInterface.cpp
|
||||
Interfaces/ScriptedThreadPythonInterface.cpp
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() {
|
|||
ScriptedPlatformPythonInterface::Initialize();
|
||||
ScriptedProcessPythonInterface::Initialize();
|
||||
ScriptedStopHookPythonInterface::Initialize();
|
||||
ScriptedHookPythonInterface::Initialize();
|
||||
ScriptedBreakpointPythonInterface::Initialize();
|
||||
ScriptedThreadPlanPythonInterface::Initialize();
|
||||
ScriptedFrameProviderPythonInterface::Initialize();
|
||||
|
|
@ -34,6 +35,7 @@ void ScriptInterpreterPythonInterfaces::Terminate() {
|
|||
ScriptedPlatformPythonInterface::Terminate();
|
||||
ScriptedProcessPythonInterface::Terminate();
|
||||
ScriptedStopHookPythonInterface::Terminate();
|
||||
ScriptedHookPythonInterface::Terminate();
|
||||
ScriptedBreakpointPythonInterface::Terminate();
|
||||
ScriptedThreadPlanPythonInterface::Terminate();
|
||||
ScriptedFrameProviderPythonInterface::Terminate();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "ScriptedBreakpointPythonInterface.h"
|
||||
#include "ScriptedFrameProviderPythonInterface.h"
|
||||
#include "ScriptedFramePythonInterface.h"
|
||||
#include "ScriptedHookPythonInterface.h"
|
||||
#include "ScriptedPlatformPythonInterface.h"
|
||||
#include "ScriptedProcessPythonInterface.h"
|
||||
#include "ScriptedStopHookPythonInterface.h"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
//===-- 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) {}
|
||||
|
||||
ScriptedHookInterface::SupportedHookMethods
|
||||
ScriptedHookPythonInterface::GetSupportedMethods() {
|
||||
SupportedHookMethods methods;
|
||||
// Qualify through ScriptedPythonInterface to resolve the diamond
|
||||
// inheritance (both ScriptedHookInterface and ScriptedPythonInterface
|
||||
// inherit ScriptedInterface which owns m_object_instance_sp).
|
||||
auto &obj_sp = ScriptedPythonInterface::m_object_instance_sp;
|
||||
if (!obj_sp)
|
||||
return methods;
|
||||
|
||||
using Locker = ScriptInterpreterPythonImpl::Locker;
|
||||
Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
|
||||
Locker::FreeLock);
|
||||
|
||||
PythonObject implementor(PyRefType::Borrowed, (PyObject *)obj_sp->GetValue());
|
||||
if (!implementor.IsValid())
|
||||
return methods;
|
||||
|
||||
methods.handle_module_loaded =
|
||||
implementor.HasAttribute("handle_module_loaded");
|
||||
methods.handle_module_unloaded =
|
||||
implementor.HasAttribute("handle_module_unloaded");
|
||||
methods.handle_stop = implementor.HasAttribute("handle_stop");
|
||||
return methods;
|
||||
}
|
||||
|
||||
llvm::Expected<StructuredData::GenericSP>
|
||||
ScriptedHookPythonInterface::CreatePluginObject(
|
||||
llvm::StringRef class_name, lldb::TargetSP target_sp,
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
//===-- 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;
|
||||
|
||||
/// A hook class must implement at least one callback. All three are
|
||||
/// individually optional; hooks that implement none will be rejected
|
||||
/// at creation time.
|
||||
llvm::SmallVector<AbstractMethodRequirement>
|
||||
GetAbstractMethodRequirements() const override {
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Check which of the three hook methods the Python class implements.
|
||||
SupportedHookMethods GetSupportedMethods() override;
|
||||
|
||||
void HandleModuleLoaded(lldb::StreamSP &output_sp) override;
|
||||
void HandleModuleUnloaded(lldb::StreamSP &output_sp) override;
|
||||
llvm::Expected<bool> HandleStop(ExecutionContext &exe_ctx,
|
||||
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
|
||||
|
|
@ -1525,6 +1525,11 @@ ScriptInterpreterPythonImpl::CreateScriptedStopHookInterface() {
|
|||
return std::make_shared<ScriptedStopHookPythonInterface>(*this);
|
||||
}
|
||||
|
||||
ScriptedHookInterfaceSP
|
||||
ScriptInterpreterPythonImpl::CreateScriptedHookInterface() {
|
||||
return std::make_shared<ScriptedHookPythonInterface>(*this);
|
||||
}
|
||||
|
||||
ScriptedBreakpointInterfaceSP
|
||||
ScriptInterpreterPythonImpl::CreateScriptedBreakpointInterface() {
|
||||
return std::make_shared<ScriptedBreakpointPythonInterface>(*this);
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ public:
|
|||
|
||||
lldb::ScriptedStopHookInterfaceSP CreateScriptedStopHookInterface() override;
|
||||
|
||||
lldb::ScriptedHookInterfaceSP CreateScriptedHookInterface() override;
|
||||
|
||||
lldb::ScriptedBreakpointInterfaceSP
|
||||
CreateScriptedBreakpointInterface() override;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
#include "lldb/Interpreter/CommandInterpreter.h"
|
||||
#include "lldb/Interpreter/CommandReturnObject.h"
|
||||
#include "lldb/Interpreter/Interfaces/ScriptedBreakpointInterface.h"
|
||||
#include "lldb/Interpreter/Interfaces/ScriptedHookInterface.h"
|
||||
#include "lldb/Interpreter/Interfaces/ScriptedStopHookInterface.h"
|
||||
#include "lldb/Interpreter/OptionGroupWatchpoint.h"
|
||||
#include "lldb/Interpreter/OptionValueEnumeration.h"
|
||||
|
|
@ -222,6 +223,8 @@ void Target::PrimeFromDummyTarget(Target &target) {
|
|||
m_stop_hooks = target.m_stop_hooks;
|
||||
m_stop_hook_next_id = target.m_stop_hook_next_id;
|
||||
m_internal_stop_hooks = target.m_internal_stop_hooks;
|
||||
m_hooks = target.m_hooks;
|
||||
m_hook_next_id = target.m_hook_next_id;
|
||||
|
||||
for (const auto &breakpoint_sp : target.m_breakpoint_list.Breakpoints()) {
|
||||
if (breakpoint_sp->IsInternal())
|
||||
|
|
@ -1859,6 +1862,7 @@ void Target::ModulesDidLoad(ModuleList &module_list) {
|
|||
if (m_process_sp) {
|
||||
m_process_sp->ModulesDidLoad(module_list);
|
||||
}
|
||||
RunModuleHooks(/*is_load=*/true);
|
||||
auto data_sp =
|
||||
std::make_shared<TargetEventData>(shared_from_this(), module_list);
|
||||
BroadcastEvent(eBroadcastBitModulesLoaded, data_sp);
|
||||
|
|
@ -1918,6 +1922,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3149,7 +3155,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->IsEnabled() && 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
|
||||
|
|
@ -3200,6 +3215,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;
|
||||
|
|
@ -3263,6 +3280,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) {
|
||||
|
|
@ -4249,6 +4313,427 @@ void Target::StopHookScripted::GetSubclassDescription(
|
|||
as_dict->ForEach(print_one_element);
|
||||
}
|
||||
|
||||
// Hook
|
||||
|
||||
Target::Hook::Hook(lldb::TargetSP target_sp, lldb::user_id_t uid, HookKind kind)
|
||||
: UserID(uid), m_target_sp(std::move(target_sp)), m_kind(kind) {}
|
||||
|
||||
Target::Hook::Hook(const Hook &rhs)
|
||||
: UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), m_kind(rhs.m_kind),
|
||||
m_enabled(rhs.m_enabled), m_trigger_mask(rhs.m_trigger_mask),
|
||||
m_sc_specifier_sp(rhs.m_sc_specifier_sp),
|
||||
m_at_initial_stop(rhs.m_at_initial_stop),
|
||||
m_auto_continue(rhs.m_auto_continue),
|
||||
m_suppress_output(rhs.m_suppress_output) {
|
||||
if (rhs.m_thread_spec_up)
|
||||
m_thread_spec_up = std::make_unique<ThreadSpec>(*rhs.m_thread_spec_up);
|
||||
}
|
||||
|
||||
void Target::Hook::SetSCSpecifier(SymbolContextSpecifier *specifier) {
|
||||
m_sc_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 = GetSCSpecifier();
|
||||
if (!specifier)
|
||||
return true;
|
||||
|
||||
bool will_run = true;
|
||||
if (exc_ctx.GetFramePtr())
|
||||
will_run = specifier->SymbolContextMatches(
|
||||
exc_ctx.GetFramePtr()->GetSymbolContext(eSymbolContextEverything));
|
||||
if (will_run && GetThreadSpecifier() != nullptr)
|
||||
will_run =
|
||||
GetThreadSpecifier()->ThreadPassesBasicTests(exc_ctx.GetThreadRef());
|
||||
|
||||
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_enabled ? "enabled" : "disabled");
|
||||
|
||||
{
|
||||
std::string fires_on;
|
||||
if (m_trigger_mask & kModulesLoaded)
|
||||
fires_on += "load";
|
||||
if (m_trigger_mask & kModulesUnloaded) {
|
||||
if (!fires_on.empty())
|
||||
fires_on += ", ";
|
||||
fires_on += "unload";
|
||||
}
|
||||
if (m_trigger_mask & kProcessStop) {
|
||||
if (!fires_on.empty())
|
||||
fires_on += ", ";
|
||||
fires_on += "stop";
|
||||
}
|
||||
if (!fires_on.empty()) {
|
||||
s.Indent();
|
||||
s.Printf("Triggers: %s\n", fires_on.c_str());
|
||||
}
|
||||
}
|
||||
// Subclasses add their content (commands or class) then call
|
||||
// GetFilterDescription to print filters.
|
||||
s.IndentLess();
|
||||
}
|
||||
|
||||
void Target::Hook::GetFilterDescription(Stream &s,
|
||||
lldb::DescriptionLevel level) const {
|
||||
s.IndentMore();
|
||||
|
||||
if (m_auto_continue)
|
||||
s.Indent("AutoContinue on\n");
|
||||
|
||||
if (m_sc_specifier_sp) {
|
||||
s.Indent();
|
||||
s.PutCString("Specifier:\n");
|
||||
s.IndentMore();
|
||||
m_sc_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();
|
||||
}
|
||||
|
||||
s.IndentLess();
|
||||
}
|
||||
|
||||
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));
|
||||
else
|
||||
s.Printf("%" PRIu64 " commands", (uint64_t)m_commands.GetSize());
|
||||
return;
|
||||
}
|
||||
|
||||
// Commands come after the header (ID, State, Triggers) but before filters.
|
||||
s.IndentMore();
|
||||
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();
|
||||
s.IndentLess();
|
||||
|
||||
GetFilterDescription(s, level);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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::HookCommandLine::HandleModuleUnloaded(StreamSP output_sp) {
|
||||
// Command-based hooks run the same commands on unload as on load.
|
||||
HandleModuleLoaded(output_sp);
|
||||
}
|
||||
|
||||
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::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->CreateScriptedHookInterface();
|
||||
if (!m_interface_sp)
|
||||
return Status::FromErrorStringWithFormat(
|
||||
"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);
|
||||
|
||||
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(
|
||||
"ScriptedHook::%s () - ERROR: %s", __FUNCTION__,
|
||||
"Failed to create valid script object");
|
||||
|
||||
// Determine which triggers the class supports by checking which callback
|
||||
// methods it implements.
|
||||
auto methods = m_interface_sp->GetSupportedMethods();
|
||||
if (!methods.any())
|
||||
return Status::FromErrorString(
|
||||
"hook class implements none of the expected methods "
|
||||
"(handle_module_loaded, handle_module_unloaded, handle_stop)");
|
||||
|
||||
if (methods.handle_module_loaded)
|
||||
m_trigger_mask |= kModulesLoaded;
|
||||
if (methods.handle_module_unloaded)
|
||||
m_trigger_mask |= kModulesUnloaded;
|
||||
if (methods.handle_stop)
|
||||
m_trigger_mask |= kProcessStop;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Target::HookScripted::HandleModuleLoaded(StreamSP output_sp) {
|
||||
if (!m_interface_sp)
|
||||
return;
|
||||
|
||||
StreamSP stream = std::make_shared<StreamString>();
|
||||
m_interface_sp->HandleModuleLoaded(stream);
|
||||
output_sp->PutCString(static_cast<StreamString *>(stream.get())->GetData());
|
||||
}
|
||||
|
||||
void Target::HookScripted::HandleModuleUnloaded(StreamSP output_sp) {
|
||||
if (!m_interface_sp)
|
||||
return;
|
||||
|
||||
StreamSP stream = std::make_shared<StreamString>();
|
||||
m_interface_sp->HandleModuleUnloaded(stream);
|
||||
output_sp->PutCString(static_cast<StreamString *>(stream.get())->GetData());
|
||||
}
|
||||
|
||||
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(static_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::GetDescription(Stream &s,
|
||||
lldb::DescriptionLevel level) const {
|
||||
Hook::GetDescription(s, level);
|
||||
if (level == eDescriptionLevelBrief) {
|
||||
s.PutCString(m_class_name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Class and args come after the header (ID, State, Triggers) but before
|
||||
// filters.
|
||||
s.IndentMore();
|
||||
s.Indent("Class: ");
|
||||
s.Printf("%s\n", m_class_name.c_str());
|
||||
|
||||
if (m_extra_args.IsValid()) {
|
||||
StructuredData::ObjectSP object_sp = m_extra_args.GetObjectSP();
|
||||
if (object_sp && object_sp->IsValid()) {
|
||||
StructuredData::Dictionary *as_dict = object_sp->GetAsDictionary();
|
||||
if (as_dict && as_dict->IsValid() && as_dict->GetSize() > 0) {
|
||||
s.Indent("Args:\n");
|
||||
s.IndentMore();
|
||||
|
||||
auto print_one_element = [&s](llvm::StringRef key,
|
||||
StructuredData::Object *object) {
|
||||
s.Indent();
|
||||
s.Format("{0} : {1}\n", key, object->GetStringValue());
|
||||
return true;
|
||||
};
|
||||
|
||||
as_dict->ForEach(print_one_element);
|
||||
s.IndentLess();
|
||||
}
|
||||
}
|
||||
}
|
||||
s.IndentLess();
|
||||
|
||||
GetFilterDescription(s, level);
|
||||
}
|
||||
|
||||
// Hook management methods
|
||||
|
||||
Target::HookSP Target::CreateHook(Hook::HookKind kind) {
|
||||
lldb::user_id_t new_uid = ++m_hook_next_id;
|
||||
HookSP hook_sp;
|
||||
switch (kind) {
|
||||
case Hook::HookKind::CommandBased:
|
||||
hook_sp.reset(new HookCommandLine(shared_from_this(), new_uid));
|
||||
break;
|
||||
case Hook::HookKind::ScriptBased:
|
||||
hook_sp.reset(new HookScripted(shared_from_this(), new_uid));
|
||||
break;
|
||||
}
|
||||
m_hooks[new_uid] = hook_sp;
|
||||
return hook_sp;
|
||||
}
|
||||
|
||||
void Target::UndoCreateHook(lldb::user_id_t uid) {
|
||||
if (!RemoveHookByID(uid))
|
||||
return;
|
||||
if (uid > 0)
|
||||
--m_hook_next_id;
|
||||
}
|
||||
|
||||
bool Target::RemoveHookByID(lldb::user_id_t uid) {
|
||||
size_t num_removed = m_hooks.erase(uid);
|
||||
return (num_removed != 0);
|
||||
}
|
||||
|
||||
void Target::RemoveAllHooks() { m_hooks.clear(); }
|
||||
|
||||
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::HookSP Target::GetHookAtIndex(size_t index) {
|
||||
if (index >= m_hooks.size())
|
||||
return {};
|
||||
auto iter = m_hooks.begin();
|
||||
std::advance(iter, index);
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
bool Target::SetHookEnabledStateByID(lldb::user_id_t uid, bool enabled) {
|
||||
auto iter = m_hooks.find(uid);
|
||||
if (iter == m_hooks.end())
|
||||
return false;
|
||||
iter->second->SetIsEnabled(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Target::SetAllHooksEnabledState(bool enabled) {
|
||||
for (auto &[_, hook] : m_hooks)
|
||||
hook->SetIsEnabled(enabled);
|
||||
}
|
||||
|
||||
void Target::RunModuleHooks(bool is_load) {
|
||||
if (m_hooks.empty())
|
||||
return;
|
||||
|
||||
uint32_t trigger = is_load ? Hook::kModulesLoaded : Hook::kModulesUnloaded;
|
||||
|
||||
// Copy active hooks into a local vector before iterating, in case a
|
||||
// callback modifies m_hooks (same pattern as RunStopHooks).
|
||||
std::vector<HookSP> active_hooks;
|
||||
for (auto &[_, hook_sp] : m_hooks) {
|
||||
if (hook_sp->IsEnabled() && hook_sp->FiresOn(trigger))
|
||||
active_hooks.push_back(hook_sp);
|
||||
}
|
||||
|
||||
if (active_hooks.empty())
|
||||
return;
|
||||
|
||||
StreamSP output_sp = m_debugger.GetAsyncOutputStream();
|
||||
|
||||
for (auto &hook_sp : active_hooks) {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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 hook delete -1",
|
||||
error=True,
|
||||
startstr='error: invalid hook id: "-1"',
|
||||
)
|
||||
self.expect(
|
||||
"target hook delete abcdfx",
|
||||
error=True,
|
||||
startstr='error: invalid hook id: "abcdfx"',
|
||||
)
|
||||
|
|
@ -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 hook disable -1",
|
||||
error=True,
|
||||
startstr='error: invalid hook id: "-1"',
|
||||
)
|
||||
self.expect(
|
||||
"target hook disable abcdfx",
|
||||
error=True,
|
||||
startstr='error: invalid hook id: "abcdfx"',
|
||||
)
|
||||
|
|
@ -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 hook enable -1",
|
||||
error=True,
|
||||
startstr='error: invalid hook id: "-1"',
|
||||
)
|
||||
self.expect(
|
||||
"target hook enable abcdfx",
|
||||
error=True,
|
||||
startstr='error: invalid hook id: "abcdfx"',
|
||||
)
|
||||
10
lldb/test/Shell/Commands/command-module-hook-fire.test
Normal file
10
lldb/test/Shell/Commands/command-module-hook-fire.test
Normal file
|
|
@ -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 hook add -L -o "script print(\"HOOK_FIRED\")"' \
|
||||
# RUN: -o 'target modules add %t' \
|
||||
# RUN: 2>&1 | FileCheck %s
|
||||
|
||||
# CHECK: Hook #1 added.
|
||||
# CHECK: HOOK_FIRED
|
||||
168
lldb/test/Shell/Commands/command-module-hook.test
Normal file
168
lldb/test/Shell/Commands/command-module-hook.test
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# Test hook add/list/delete/enable/disable commands.
|
||||
#
|
||||
# RUN: %lldb -b -s %s 2>&1 | FileCheck %s
|
||||
|
||||
# Adding hooks assigns incrementing IDs.
|
||||
target hook add -L -o "script print('hook1')"
|
||||
# CHECK: Hook #1 added.
|
||||
target hook add -L -o "script print('hook2')"
|
||||
# CHECK: Hook #2 added.
|
||||
target hook add -L -o "script print('hook3')"
|
||||
# CHECK: Hook #3 added.
|
||||
|
||||
# List all hooks.
|
||||
target hook list
|
||||
# CHECK: (lldb) target hook list
|
||||
# CHECK: Hook: 1
|
||||
# CHECK: State: enabled
|
||||
# CHECK: Triggers: load
|
||||
# CHECK: Commands:
|
||||
# CHECK: script print('hook1')
|
||||
# CHECK: Hook: 2
|
||||
# CHECK: State: enabled
|
||||
# CHECK: Triggers: load
|
||||
# CHECK: Commands:
|
||||
# CHECK: script print('hook2')
|
||||
# CHECK: Hook: 3
|
||||
# CHECK: State: enabled
|
||||
# CHECK: Triggers: load
|
||||
# CHECK: Commands:
|
||||
# CHECK: script print('hook3')
|
||||
|
||||
# Disable a hook.
|
||||
target hook disable 2
|
||||
target hook list
|
||||
# CHECK: (lldb) target 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 hook enable 2
|
||||
target hook list
|
||||
# CHECK: (lldb) target 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 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 hook add -L -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.
|
||||
settings set auto-confirm true
|
||||
target hook delete
|
||||
target hook list
|
||||
# CHECK: (lldb) target hook list
|
||||
# CHECK: No hooks.
|
||||
|
||||
# Add a hook with --on-load and --on-unload.
|
||||
target hook add -L -u -o "script print('load+unload')"
|
||||
# CHECK: Hook #5 added.
|
||||
target hook list
|
||||
# CHECK: (lldb) target hook list
|
||||
# CHECK: Hook: 5
|
||||
# CHECK: State: enabled
|
||||
# CHECK: Triggers: load, unload
|
||||
# CHECK: Commands:
|
||||
# CHECK: script print('load+unload')
|
||||
|
||||
# Add a hook with multiple one-liner commands.
|
||||
target hook add -L -o "script print('first')" -o "script print('second')"
|
||||
# CHECK: Hook #6 added.
|
||||
target hook list
|
||||
# CHECK: Hook: 6
|
||||
# CHECK: State: enabled
|
||||
# CHECK: Triggers: load
|
||||
# CHECK: Commands:
|
||||
# CHECK: script print('first')
|
||||
# CHECK: script print('second')
|
||||
|
||||
# Disable all hooks.
|
||||
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 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 only.
|
||||
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: Triggers: stop
|
||||
# CHECK: Commands:
|
||||
# CHECK: bt
|
||||
|
||||
# Add a hook with all triggers.
|
||||
target hook add -L -u -S -o "script print('all events')"
|
||||
# CHECK: Hook #8 added.
|
||||
target hook list
|
||||
# CHECK: Hook: 8
|
||||
# CHECK: State: enabled
|
||||
# CHECK: Triggers: load, unload, stop
|
||||
|
||||
# Add a hook with --on-stop and inline filter.
|
||||
target hook delete
|
||||
target hook add -S -s mylib.so -o "bt"
|
||||
# CHECK: Hook #9 added.
|
||||
target hook list
|
||||
# CHECK: (lldb) target hook list
|
||||
# CHECK: Hook: 9
|
||||
# CHECK: State: enabled
|
||||
# CHECK: Triggers: stop
|
||||
# CHECK: Commands:
|
||||
# CHECK: bt
|
||||
# CHECK: Specifier:
|
||||
# CHECK: Module: mylib.so
|
||||
|
||||
# Per-trigger disable: remove stop from hook #9 using modify.
|
||||
target hook modify --disable-trigger stop 9
|
||||
target hook list
|
||||
# CHECK: (lldb) target hook list
|
||||
# CHECK: Hook: 9
|
||||
# CHECK: State: enabled
|
||||
# CHECK-NOT: Triggers: stop
|
||||
# CHECK: Commands:
|
||||
|
||||
# Per-trigger enable: re-add stop to hook #9 using modify.
|
||||
target hook modify --enable-trigger stop 9
|
||||
target hook list
|
||||
# CHECK: (lldb) target hook list
|
||||
# CHECK: Hook: 9
|
||||
# CHECK: State: enabled
|
||||
# CHECK: Triggers: stop
|
||||
|
|
@ -0,0 +1 @@
|
|||
target hook add -S -n b -o "expr ptr"
|
||||
48
lldb/test/Shell/ExecControl/StopHook/stop-hook-unified.test
Normal file
48
lldb/test/Shell/ExecControl/StopHook/stop-hook-unified.test
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Test that stop-hooks added via "target hook add -S" work the same as
|
||||
# "target stop-hook add".
|
||||
#
|
||||
# RUN: %clang_host %p/Inputs/stop-hook.c -g -o %t
|
||||
# RUN: %lldb -b -s %p/Inputs/stop-hook-unified-1.lldbinit -s %s -f %t \
|
||||
# RUN: | FileCheck %s
|
||||
# UNSUPPORTED: system-windows
|
||||
|
||||
break set -f stop-hook.c -p "// Set breakpoint here to test target stop-hook"
|
||||
break set -f stop-hook.c -p "// Another breakpoint which is outside of the stop-hook range"
|
||||
target hook list
|
||||
|
||||
# CHECK: Hook: 1
|
||||
# CHECK-NEXT: State: enabled
|
||||
# CHECK-NEXT: Triggers: stop
|
||||
# CHECK-NEXT: Commands:
|
||||
# CHECK-NEXT: expr ptr
|
||||
# CHECK-NEXT: Specifier:
|
||||
# CHECK-NEXT: Function: b.
|
||||
|
||||
run
|
||||
# Stopping inside of the stop hook range
|
||||
# CHECK: (lldb) run
|
||||
# CHECK-NEXT: (void *) ${{.*}} = 0x
|
||||
|
||||
thread step-over
|
||||
# Stepping inside of the stop hook range
|
||||
# CHECK: (lldb) thread step-over
|
||||
# CHECK-NEXT: (void *) ${{.*}} = 0x
|
||||
# CHECK: ->{{.*}} // We should stop here after stepping.
|
||||
|
||||
process continue
|
||||
# Stopping outside of the stop hook range
|
||||
# CHECK: (lldb) process continue
|
||||
# CHECK-NOT: (void *)
|
||||
# CHECK: ->{{.*}} // Another breakpoint which is outside of the stop-hook range.
|
||||
|
||||
thread step-over
|
||||
# Stepping inside of the stop hook range
|
||||
# CHECK: (lldb) thread step-over
|
||||
# CHECK-NOT: (void *)
|
||||
|
||||
settings set auto-confirm true
|
||||
target hook delete
|
||||
|
||||
target hook list
|
||||
# CHECK: (lldb) target hook list
|
||||
# CHECK: No hooks.
|
||||
Loading…
Reference in a new issue