aboutsummaryrefslogtreecommitdiff
path: root/lldb
diff options
context:
space:
mode:
Diffstat (limited to 'lldb')
-rw-r--r--lldb/cmake/modules/FindLuaAndSwig.cmake1
-rw-r--r--lldb/include/lldb/Target/Target.h81
-rw-r--r--lldb/source/Commands/CommandCompletions.cpp4
-rw-r--r--lldb/source/Commands/CommandObjectBreakpoint.cpp4
-rw-r--r--lldb/source/Commands/CommandObjectTarget.cpp63
-rw-r--r--lldb/source/Commands/Options.td10
-rw-r--r--lldb/source/Host/common/File.cpp23
-rw-r--r--lldb/source/Host/common/Socket.cpp4
-rw-r--r--lldb/source/Target/Target.cpp77
-rw-r--r--lldb/test/API/lua_api/TestLuaAPI.py4
-rw-r--r--lldb/test/Shell/ExecControl/StopHook/stop-hook-list.test70
11 files changed, 258 insertions, 83 deletions
diff --git a/lldb/cmake/modules/FindLuaAndSwig.cmake b/lldb/cmake/modules/FindLuaAndSwig.cmake
index 33fadb2..c5df29e 100644
--- a/lldb/cmake/modules/FindLuaAndSwig.cmake
+++ b/lldb/cmake/modules/FindLuaAndSwig.cmake
@@ -34,6 +34,7 @@ else()
FOUND_VAR
LUAANDSWIG_FOUND
REQUIRED_VARS
+ LUA_EXECUTABLE
LUA_LIBRARIES
LUA_INCLUDE_DIR
LUA_VERSION_MINOR
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index f4a0923..c375df2 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1356,7 +1356,11 @@ public:
StopHook(const StopHook &rhs);
virtual ~StopHook() = default;
- enum class StopHookKind : uint32_t { CommandBased = 0, ScriptBased };
+ enum class StopHookKind : uint32_t {
+ CommandBased = 0,
+ ScriptBased,
+ CodeBased,
+ };
enum class StopHookResult : uint32_t {
KeepStopped = 0,
RequestContinue,
@@ -1403,6 +1407,12 @@ public:
bool GetRunAtInitialStop() const { return m_at_initial_stop; }
+ void SetSuppressOutput(bool suppress_output) {
+ m_suppress_output = suppress_output;
+ }
+
+ bool GetSuppressOutput() const { return m_suppress_output; }
+
void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
virtual void GetSubclassDescription(Stream &s,
lldb::DescriptionLevel level) const = 0;
@@ -1414,6 +1424,7 @@ public:
bool m_active = true;
bool m_auto_continue = false;
bool m_at_initial_stop = true;
+ bool m_suppress_output = false;
StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
};
@@ -1433,8 +1444,8 @@ public:
private:
StringList m_commands;
- // Use CreateStopHook to make a new empty stop hook. The GetCommandPointer
- // and fill it with commands, and SetSpecifier to set the specifier shared
+ // Use CreateStopHook to make a new empty stop hook. Use SetActionFromString
+ // to fill it with commands, and SetSpecifier to set the specifier shared
// pointer (can be null, that will match anything.)
StopHookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
: StopHook(target_sp, uid) {}
@@ -1460,19 +1471,56 @@ public:
StructuredDataImpl m_extra_args;
lldb::ScriptedStopHookInterfaceSP m_interface_sp;
- /// Use CreateStopHook to make a new empty stop hook. The GetCommandPointer
- /// and fill it with commands, and SetSpecifier to set the specifier shared
- /// pointer (can be null, that will match anything.)
+ /// Use CreateStopHook to make a new empty stop hook. Use SetScriptCallback
+ /// to set the script to execute, and SetSpecifier to set the specifier
+ /// shared pointer (can be null, that will match anything.)
StopHookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
: StopHook(target_sp, uid) {}
friend class Target;
};
+ class StopHookCoded : public StopHook {
+ public:
+ ~StopHookCoded() override = default;
+
+ using HandleStopCallback = StopHookResult(ExecutionContext &exc_ctx,
+ lldb::StreamSP output);
+
+ void SetCallback(llvm::StringRef name, HandleStopCallback *callback) {
+ m_name = name;
+ m_callback = callback;
+ }
+
+ StopHookResult HandleStop(ExecutionContext &exc_ctx,
+ lldb::StreamSP output) override {
+ return m_callback(exc_ctx, output);
+ }
+
+ void GetSubclassDescription(Stream &s,
+ lldb::DescriptionLevel level) const override {
+ s.Indent();
+ s.Printf("%s (built-in)\n", m_name.c_str());
+ }
+
+ private:
+ std::string m_name;
+ HandleStopCallback *m_callback;
+
+ /// Use CreateStopHook to make a new empty stop hook. Use SetCallback to set
+ /// the callback to execute, and SetSpecifier to set the specifier shared
+ /// pointer (can be null, that will match anything.)
+ StopHookCoded(lldb::TargetSP target_sp, lldb::user_id_t uid)
+ : StopHook(target_sp, uid) {}
+ friend class Target;
+ };
+
+ void RegisterInternalStopHooks();
+
typedef std::shared_ptr<StopHook> StopHookSP;
/// Add an empty stop hook to the Target's stop hook list, and returns a
- /// shared pointer to it in new_hook. Returns the id of the new hook.
- StopHookSP CreateStopHook(StopHook::StopHookKind kind);
+ /// shared pointer to the new hook.
+ StopHookSP CreateStopHook(StopHook::StopHookKind kind, bool internal = false);
/// If you tried to create a stop hook, and that failed, call this to
/// remove the stop hook, as it will also reset the stop hook counter.
@@ -1484,8 +1532,6 @@ public:
// control over the process for the first time.
bool RunStopHooks(bool at_initial_stop = false);
- size_t GetStopHookSize();
-
bool SetSuppresStopHooks(bool suppress) {
bool old_value = m_suppress_stop_hooks;
m_suppress_stop_hooks = suppress;
@@ -1504,19 +1550,7 @@ public:
void SetAllStopHooksActiveState(bool active_state);
- size_t GetNumStopHooks() const { return m_stop_hooks.size(); }
-
- StopHookSP GetStopHookAtIndex(size_t index) {
- if (index >= GetNumStopHooks())
- return StopHookSP();
- StopHookCollection::iterator pos = m_stop_hooks.begin();
-
- while (index > 0) {
- pos++;
- index--;
- }
- return (*pos).second;
- }
+ const std::vector<StopHookSP> GetStopHooks(bool internal = false) const;
lldb::PlatformSP GetPlatform() { return m_platform_sp; }
@@ -1656,6 +1690,7 @@ protected:
typedef std::map<lldb::user_id_t, StopHookSP> StopHookCollection;
StopHookCollection m_stop_hooks;
lldb::user_id_t m_stop_hook_next_id;
+ std::vector<StopHookSP> m_internal_stop_hooks;
uint32_t m_latest_stop_hook_id; /// This records the last natural stop at
/// which we ran a stop-hook.
bool m_valid;
diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp
index b2fc893..c60d303 100644
--- a/lldb/source/Commands/CommandCompletions.cpp
+++ b/lldb/source/Commands/CommandCompletions.cpp
@@ -777,13 +777,11 @@ void CommandCompletions::StopHookIDs(CommandInterpreter &interpreter,
if (!target_sp)
return;
- const size_t num = target_sp->GetNumStopHooks();
- for (size_t idx = 0; idx < num; ++idx) {
+ for (auto &stophook_sp : target_sp->GetStopHooks()) {
StreamString strm;
// The value 11 is an offset to make the completion description looks
// neater.
strm.SetIndentLevel(11);
- const Target::StopHookSP stophook_sp = target_sp->GetStopHookAtIndex(idx);
stophook_sp->GetDescription(strm, lldb::eDescriptionLevelInitial);
request.TryCompleteCurrentArg(std::to_string(stophook_sp->GetID()),
strm.GetString());
diff --git a/lldb/source/Commands/CommandObjectBreakpoint.cpp b/lldb/source/Commands/CommandObjectBreakpoint.cpp
index de0a7e7..5a55126 100644
--- a/lldb/source/Commands/CommandObjectBreakpoint.cpp
+++ b/lldb/source/Commands/CommandObjectBreakpoint.cpp
@@ -1114,9 +1114,7 @@ public:
CommandObjectBreakpointList(CommandInterpreter &interpreter)
: CommandObjectParsed(
interpreter, "breakpoint list",
- "List some or all breakpoints at configurable levels of detail.",
- nullptr) {
- CommandArgumentData bp_id_arg;
+ "List some or all breakpoints at configurable levels of detail.") {
// Define the first (and only) variant of this arg.
AddSimpleArgumentList(eArgTypeBreakpointID, eArgRepeatOptional);
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index c59d028..8de6521 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5223,33 +5223,72 @@ private:
#pragma mark CommandObjectTargetStopHookList
// CommandObjectTargetStopHookList
+#define LLDB_OPTIONS_target_stop_hook_list
+#include "CommandOptions.inc"
class CommandObjectTargetStopHookList : public CommandObjectParsed {
public:
CommandObjectTargetStopHookList(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "target stop-hook list",
- "List all stop-hooks.", "target stop-hook list") {}
+ "List all stop-hooks.") {}
~CommandObjectTargetStopHookList() override = default;
+ Options *GetOptions() override { return &m_options; }
+
+ class CommandOptions : public Options {
+ public:
+ CommandOptions() = default;
+ ~CommandOptions() override = default;
+
+ Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+ ExecutionContext *execution_context) override {
+ Status error;
+ const int short_option = m_getopt_table[option_idx].val;
+
+ switch (short_option) {
+ case 'i':
+ m_internal = true;
+ break;
+ default:
+ llvm_unreachable("Unimplemented option");
+ }
+
+ return error;
+ }
+
+ void OptionParsingStarting(ExecutionContext *execution_context) override {
+ m_internal = false;
+ }
+
+ llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+ return llvm::ArrayRef(g_target_stop_hook_list_options);
+ }
+
+ // Instance variables to hold the values for command options.
+ bool m_internal = false;
+ };
+
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetTarget();
- size_t num_hooks = target.GetNumStopHooks();
- if (num_hooks == 0) {
- result.GetOutputStream().PutCString("No stop hooks.\n");
- } else {
- for (size_t i = 0; i < num_hooks; i++) {
- Target::StopHookSP this_hook = target.GetStopHookAtIndex(i);
- if (i > 0)
- result.GetOutputStream().PutCString("\n");
- this_hook->GetDescription(result.GetOutputStream(),
- eDescriptionLevelFull);
- }
+ bool printed_hook = false;
+ for (auto &hook : target.GetStopHooks(m_options.m_internal)) {
+ if (printed_hook)
+ result.GetOutputStream().PutCString("\n");
+ hook->GetDescription(result.GetOutputStream(), eDescriptionLevelFull);
+ printed_hook = true;
}
+
+ if (!printed_hook)
+ result.GetOutputStream().PutCString("No stop hooks.\n");
+
result.SetStatus(eReturnStatusSuccessFinishResult);
}
+
+private:
+ CommandOptions m_options;
};
#pragma mark CommandObjectMultiwordTargetStopHooks
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index a9f054e..ed06131 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -77,7 +77,7 @@ let Command = "breakpoint list" in {
// FIXME: We need to add an "internal" command, and then add this sort of
// thing to it. But I need to see it for now, and don't want to wait.
def blist_internal : Option<"internal", "i">,
- Desc<"Show debugger ${i}nternal breakpoints">;
+ Desc<"Show debugger ${i}nternal breakpoints.">;
def blist_brief : Option<"brief", "b">,
Group<1>,
Desc<"Give a ${b}rief description of the breakpoint (no "
@@ -1686,7 +1686,7 @@ let Command = "target modules lookup" in {
"match, if a best match is available.">;
}
-let Command = "target stop hook add" in {
+let Command = "target stop_hook add" in {
def target_stop_hook_add_one_liner
: Option<"one-liner", "o">,
GroupRange<1, 3>,
@@ -1762,6 +1762,12 @@ let Command = "target stop hook add" in {
"Defaults to true.">;
}
+let Command = "target stop_hook list" in {
+ def target_stop_hook_list_internal
+ : Option<"internal", "i">,
+ Desc<"Show debugger ${i}nternal stop hooks.">;
+}
+
let Command = "thread backtrace" in {
def thread_backtrace_count : Option<"count", "c">,
Group<1>,
diff --git a/lldb/source/Host/common/File.cpp b/lldb/source/Host/common/File.cpp
index 1272f13..65b75bd 100644
--- a/lldb/source/Host/common/File.cpp
+++ b/lldb/source/Host/common/File.cpp
@@ -81,18 +81,17 @@ File::GetStreamOpenModeFromOptions(File::OpenOptions options) {
Expected<File::OpenOptions> File::GetOptionsFromMode(llvm::StringRef mode) {
OpenOptions opts =
llvm::StringSwitch<OpenOptions>(mode)
- .Cases("r", "rb", eOpenOptionReadOnly)
- .Cases("w", "wb", eOpenOptionWriteOnly)
- .Cases("a", "ab",
- eOpenOptionWriteOnly | eOpenOptionAppend |
- eOpenOptionCanCreate)
- .Cases("r+", "rb+", "r+b", eOpenOptionReadWrite)
- .Cases("w+", "wb+", "w+b",
- eOpenOptionReadWrite | eOpenOptionCanCreate |
- eOpenOptionTruncate)
- .Cases("a+", "ab+", "a+b",
- eOpenOptionReadWrite | eOpenOptionAppend |
- eOpenOptionCanCreate)
+ .Cases({"r", "rb"}, eOpenOptionReadOnly)
+ .Cases({"w", "wb"}, eOpenOptionWriteOnly)
+ .Cases({"a", "ab"}, eOpenOptionWriteOnly | eOpenOptionAppend |
+ eOpenOptionCanCreate)
+ .Cases({"r+", "rb+", "r+b"}, eOpenOptionReadWrite)
+ .Cases({"w+", "wb+", "w+b"}, eOpenOptionReadWrite |
+ eOpenOptionCanCreate |
+ eOpenOptionTruncate)
+ .Cases({"a+", "ab+", "a+b"}, eOpenOptionReadWrite |
+ eOpenOptionAppend |
+ eOpenOptionCanCreate)
.Default(eOpenOptionInvalid);
if (opts != eOpenOptionInvalid)
return opts;
diff --git a/lldb/source/Host/common/Socket.cpp b/lldb/source/Host/common/Socket.cpp
index bc3d849..eb333f7 100644
--- a/lldb/source/Host/common/Socket.cpp
+++ b/lldb/source/Host/common/Socket.cpp
@@ -500,13 +500,13 @@ Socket::GetProtocolAndMode(llvm::StringRef scheme) {
return llvm::StringSwitch<std::optional<ProtocolModePair>>(scheme)
.Case("listen", ProtocolModePair{SocketProtocol::ProtocolTcp,
SocketMode::ModeAccept})
- .Cases("accept", "unix-accept",
+ .Cases({"accept", "unix-accept"},
ProtocolModePair{SocketProtocol::ProtocolUnixDomain,
SocketMode::ModeAccept})
.Case("unix-abstract-accept",
ProtocolModePair{SocketProtocol::ProtocolUnixAbstract,
SocketMode::ModeAccept})
- .Cases("connect", "tcp-connect", "connection",
+ .Cases({"connect", "tcp-connect", "connection"},
ProtocolModePair{SocketProtocol::ProtocolTcp,
SocketMode::ModeConnect})
.Case("udp", ProtocolModePair{SocketProtocol::ProtocolTcp,
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index e224a12..d070c3d 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -183,8 +183,8 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch,
m_watchpoint_list(), m_process_sp(), m_search_filter_sp(),
m_image_search_paths(ImageSearchPathsChanged, this),
m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0),
- m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false),
- m_is_dummy_target(is_dummy_target),
+ m_internal_stop_hooks(), m_latest_stop_hook_id(0), m_valid(true),
+ m_suppress_stop_hooks(false), m_is_dummy_target(is_dummy_target),
m_target_unique_id(g_target_unique_id++),
m_frame_recognizer_manager_up(
std::make_unique<StackFrameRecognizerManager>()) {
@@ -217,6 +217,7 @@ Target::~Target() {
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;
for (const auto &breakpoint_sp : target.m_breakpoint_list.Breakpoints()) {
if (breakpoint_sp->IsInternal())
@@ -383,6 +384,7 @@ void Target::Destroy() {
m_image_search_paths.Clear(notify);
m_stop_hooks.clear();
m_stop_hook_next_id = 0;
+ m_internal_stop_hooks.clear();
m_suppress_stop_hooks = false;
m_repl_map.clear();
Args signal_args;
@@ -3041,8 +3043,9 @@ SourceManager &Target::GetSourceManager() {
return *m_source_manager_up;
}
-Target::StopHookSP Target::CreateStopHook(StopHook::StopHookKind kind) {
- lldb::user_id_t new_uid = ++m_stop_hook_next_id;
+Target::StopHookSP Target::CreateStopHook(StopHook::StopHookKind kind,
+ bool internal) {
+ user_id_t new_uid = (internal ? LLDB_INVALID_UID : ++m_stop_hook_next_id);
Target::StopHookSP stop_hook_sp;
switch (kind) {
case StopHook::StopHookKind::CommandBased:
@@ -3051,8 +3054,14 @@ Target::StopHookSP Target::CreateStopHook(StopHook::StopHookKind kind) {
case StopHook::StopHookKind::ScriptBased:
stop_hook_sp.reset(new StopHookScripted(shared_from_this(), new_uid));
break;
+ case StopHook::StopHookKind::CodeBased:
+ stop_hook_sp.reset(new StopHookCoded(shared_from_this(), new_uid));
+ break;
}
- m_stop_hooks[new_uid] = stop_hook_sp;
+ if (internal)
+ m_internal_stop_hooks.push_back(stop_hook_sp);
+ else
+ m_stop_hooks[new_uid] = stop_hook_sp;
return stop_hook_sp;
}
@@ -3098,6 +3107,23 @@ void Target::SetAllStopHooksActiveState(bool active_state) {
}
}
+// FIXME: Ideally we would like to return a `const &` (const reference) instead
+// of creating copy here, but that is not possible due to different container
+// types. In C++20, we should be able to use `std::ranges::views::values` to
+// adapt the key-pair entries in the `std::map` (behind `StopHookCollection`)
+// to avoid creating the copy.
+const std::vector<Target::StopHookSP>
+Target::GetStopHooks(bool internal) const {
+ if (internal)
+ return m_internal_stop_hooks;
+
+ std::vector<StopHookSP> stop_hooks;
+ for (auto &[_, hook] : m_stop_hooks)
+ stop_hooks.push_back(hook);
+
+ return stop_hooks;
+}
+
bool Target::RunStopHooks(bool at_initial_stop) {
if (m_suppress_stop_hooks)
return false;
@@ -3111,16 +3137,20 @@ bool Target::RunStopHooks(bool at_initial_stop) {
if (!(state == eStateStopped || state == eStateAttaching))
return false;
- if (m_stop_hooks.empty())
- return false;
+ auto is_active = [at_initial_stop](StopHookSP hook) {
+ bool should_run_now = (!at_initial_stop || hook->GetRunAtInitialStop());
+ return hook->IsActive() && should_run_now;
+ };
- bool no_active_hooks =
- llvm::none_of(m_stop_hooks, [at_initial_stop](auto &p) {
- bool should_run_now =
- !at_initial_stop || p.second->GetRunAtInitialStop();
- return p.second->IsActive() && should_run_now;
- });
- if (no_active_hooks)
+ // Create list of active internal and user stop hooks.
+ std::vector<StopHookSP> active_hooks;
+ llvm::copy_if(m_internal_stop_hooks, std::back_inserter(active_hooks),
+ is_active);
+ for (auto &[_, hook] : m_stop_hooks) {
+ if (is_active(hook))
+ active_hooks.push_back(hook);
+ }
+ if (active_hooks.empty())
return false;
// Make sure we check that we are not stopped because of us running a user
@@ -3169,24 +3199,21 @@ bool Target::RunStopHooks(bool at_initial_stop) {
StreamSP output_sp = m_debugger.GetAsyncOutputStream();
auto on_exit = llvm::make_scope_exit([output_sp] { output_sp->Flush(); });
- bool print_hook_header = (m_stop_hooks.size() != 1);
- bool print_thread_header = (num_exe_ctx != 1);
+ size_t num_hooks_with_output = llvm::count_if(
+ active_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;
bool requested_continue = false;
- for (auto stop_entry : m_stop_hooks) {
- StopHookSP cur_hook_sp = stop_entry.second;
- if (!cur_hook_sp->IsActive())
- continue;
- if (at_initial_stop && !cur_hook_sp->GetRunAtInitialStop())
- continue;
-
+ for (auto cur_hook_sp : active_hooks) {
bool any_thread_matched = false;
for (auto exc_ctx : exc_ctx_with_reasons) {
if (!cur_hook_sp->ExecutionContextPasses(exc_ctx))
continue;
- if (print_hook_header && !any_thread_matched) {
+ 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)
@@ -3197,7 +3224,7 @@ bool Target::RunStopHooks(bool at_initial_stop) {
any_thread_matched = true;
}
- if (print_thread_header)
+ if (print_thread_header && !suppress_output)
output_sp->Printf("-- Thread %d\n",
exc_ctx.GetThreadPtr()->GetIndexID());
diff --git a/lldb/test/API/lua_api/TestLuaAPI.py b/lldb/test/API/lua_api/TestLuaAPI.py
index 4ac795d..e78ed9d 100644
--- a/lldb/test/API/lua_api/TestLuaAPI.py
+++ b/lldb/test/API/lua_api/TestLuaAPI.py
@@ -158,7 +158,9 @@ class TestLuaAPI(TestBase):
return tests
def test_lua_api(self):
- if "LUA_EXECUTABLE" not in os.environ or len(os.environ["LUA_EXECUTABLE"]) == 0:
+ if "LUA_EXECUTABLE" not in os.environ or not os.path.exists(
+ os.environ["LUA_EXECUTABLE"]
+ ):
self.skipTest("Lua API tests could not find Lua executable.")
return
lua_executable = os.environ["LUA_EXECUTABLE"]
diff --git a/lldb/test/Shell/ExecControl/StopHook/stop-hook-list.test b/lldb/test/Shell/ExecControl/StopHook/stop-hook-list.test
new file mode 100644
index 0000000..42d0a67
--- /dev/null
+++ b/lldb/test/Shell/ExecControl/StopHook/stop-hook-list.test
@@ -0,0 +1,70 @@
+# Test stop hook user ID assignment, ordering, and printing.
+#
+# RUN: %lldb -b -s %s | FileCheck %s
+
+# Create some stop hooks
+target stop-hook add -o 'print "Hello"'
+target stop-hook add -o 'print "world,"'
+target stop-hook add -o 'print "nice"'
+target stop-hook add -o 'print "weather"'
+target stop-hook add -o 'print "today!"'
+
+# Print hooks
+target stop-hook list
+
+# CHECK: (lldb) target stop-hook list
+# CHECK: Hook: 1
+# CHECK: "Hello"
+# CHECK: Hook: 2
+# CHECK: "world,"
+# CHECK: Hook: 3
+# CHECK: "nice"
+# CHECK: Hook: 4
+# CHECK: "weather"
+# CHECK: Hook: 5
+# CHECK: "today!"
+
+# Delete last hook, then add new one
+target stop-hook delete 5
+target stop-hook add -o 'print "Sunshine,"'
+
+# Stop hook gets new user ID (it is not reused)
+# CHECK: (lldb) target stop-hook add -o 'print "Sunshine,"'
+# CHECK: Stop hook #6 added.
+
+target stop-hook list
+# CHECK: (lldb) target stop-hook list
+# CHECK: Hook: 4
+# CHECK-NOT: Hook: 5
+# CHECK: Hook: 6
+
+# Add a few more hooks
+target stop-hook add -o 'print "rain,"'
+target stop-hook add -o 'print "and wind!"'
+target stop-hook add -o 'print "It is all okay!"'
+# CHECK: Stop hook #7 added.
+# CHECK: Stop hook #8 added.
+# CHECK: Stop hook #9 added.
+
+# Delete a few hooks
+target stop-hook delete 1
+target stop-hook delete 3
+target stop-hook delete 7
+target stop-hook delete 9
+
+# Check that the list is still well-ordered
+target stop-hook list
+# CHECK: (lldb) target stop-hook list
+# CHECK-NOT: Hook: 1
+# CHECK: Hook: 2
+# CHECK: "world,"
+# CHECK-NOT: Hook: 3
+# CHECK: Hook: 4
+# CHECK: "weather"
+# CHECK-NOT: Hook: 5
+# CHECK: Hook: 6
+# CHECK: "Sunshine,"
+# CHECK-NOT: Hook: 7
+# CHECK: Hook: 8
+# CHECK: "and wind!"
+# CHECK-NOT: Hook: 9