From 76644944606ed28affc01d7638b99372ccac86f7 Mon Sep 17 00:00:00 2001 From: Jim Ingham Date: Tue, 23 Sep 2025 13:44:33 -0700 Subject: [PATCH 1/4] Fix a crash when a stop hook deletes itself in its callback. --- lldb/source/Target/Target.cpp | 8 ++++- .../target/stop-hooks/TestStopHookScripted.py | 33 +++++++++++++++++++ .../commands/target/stop-hooks/stop_hook.py | 24 ++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index fa98c24606492..e94fe854f8157 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3171,7 +3171,13 @@ bool Target::RunStopHooks(bool at_initial_stop) { bool should_stop = false; bool requested_continue = false; - for (auto stop_entry : m_stop_hooks) { + // A stop hook might get deleted while running stop hooks. + // We have to decide what that means. We will follow the rule that deleting + // a stop hook while processing these stop hooks will delete it for FUTURE + // stops but not this stop. The easiest way to do that is to copy the + // stop hooks and iterate over the copy. + StopHookCollection stop_hooks_copy = m_stop_hooks; + for (auto stop_entry : stop_hooks_copy) { StopHookSP cur_hook_sp = stop_entry.second; if (!cur_hook_sp->IsActive()) continue; diff --git a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py index 954cac1592435..c0e3c2a8fcf31 100644 --- a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py +++ b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py @@ -48,6 +48,39 @@ def test_bad_handler(self): "Got the right error", ) + def test_self_deleting(self): + """Test that we can handle a stop hook that deletes itself""" + self.script_setup() + # Run to the first breakpoint before setting the stop hook + # so we don't have to figure out where it showed up in the new + # target. + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "Stop here first", self.main_source_file + ) + + # Now add our stop hook and register it: + result = lldb.SBCommandReturnObject() + command = "target stop-hook add -P stop_hook.self_deleting_stop" + self.interp.HandleCommand(command, result) + self.assertCommandReturn(result, f"Added my stop hook: {result.GetError()}") + + result_str = result.GetOutput() + p = re.compile("Stop hook #([0-9]+) added.") + m = p.match(result_str) + current_stop_hook_id = m.group(1) + command = "command script add -o -f stop_hook.handle_stop_hook_id handle_id" + self.interp.HandleCommand(command, result) + self.assertCommandReturn(result, "Added my command") + + command = f"handle_id {current_stop_hook_id}" + self.interp.HandleCommand(command, result) + self.assertCommandReturn(result, "Registered my stop ID") + + # Now step the process and make sure the stop hook was deleted. + thread.StepOver() + self.interp.HandleCommand("target stop-hook list", result) + self.assertEqual(result.GetOutput().rstrip(), "No stop hooks.", "Deleted hook") + def test_stop_hooks_scripted(self): """Test that a scripted stop hook works with no specifiers""" self.stop_hooks_scripted(5, "-I false") diff --git a/lldb/test/API/commands/target/stop-hooks/stop_hook.py b/lldb/test/API/commands/target/stop-hooks/stop_hook.py index cb7a4337c40d4..142c4ab4c5d89 100644 --- a/lldb/test/API/commands/target/stop-hooks/stop_hook.py +++ b/lldb/test/API/commands/target/stop-hooks/stop_hook.py @@ -48,3 +48,27 @@ def handle_stop(self): class no_handle_stop: def __init__(self, target, extra_args, dict): print("I am okay") + +class self_deleting_stop: + def __init__(self, target, extra_args, dict): + self.target = target + + def handle_stop(self, exe_ctx, stream): + interp = exe_ctx.target.debugger.GetCommandInterpreter() + result = lldb.SBCommandReturnObject() + interp.HandleCommand("handle_id", result) + id_str = result.GetOutput().rstrip() + + command = f"target stop-hook delete {id_str}" + interp.HandleCommand(command, result) + +stop_hook_id = 0 +def handle_stop_hook_id(debugger, command, exe_ctx, result, extra_args): + global stop_hook_id + if command == "": + result.AppendMessage(str(stop_hook_id)) + else: + stop_hook_id = int(command) + + + From 95cec34938436d5eef25c73ce71da1bdddeef387 Mon Sep 17 00:00:00 2001 From: Jim Ingham Date: Tue, 23 Sep 2025 17:20:21 -0700 Subject: [PATCH 2/4] formatting --- lldb/source/Target/Target.cpp | 2 +- .../commands/target/stop-hooks/TestStopHookScripted.py | 4 ++-- lldb/test/API/commands/target/stop-hooks/stop_hook.py | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index e94fe854f8157..baf28c6de0ec7 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3171,7 +3171,7 @@ bool Target::RunStopHooks(bool at_initial_stop) { bool should_stop = false; bool requested_continue = false; - // A stop hook might get deleted while running stop hooks. + // A stop hook might get deleted while running stop hooks. // We have to decide what that means. We will follow the rule that deleting // a stop hook while processing these stop hooks will delete it for FUTURE // stops but not this stop. The easiest way to do that is to copy the diff --git a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py index c0e3c2a8fcf31..8e91781b87a39 100644 --- a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py +++ b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py @@ -63,7 +63,7 @@ def test_self_deleting(self): command = "target stop-hook add -P stop_hook.self_deleting_stop" self.interp.HandleCommand(command, result) self.assertCommandReturn(result, f"Added my stop hook: {result.GetError()}") - + result_str = result.GetOutput() p = re.compile("Stop hook #([0-9]+) added.") m = p.match(result_str) @@ -80,7 +80,7 @@ def test_self_deleting(self): thread.StepOver() self.interp.HandleCommand("target stop-hook list", result) self.assertEqual(result.GetOutput().rstrip(), "No stop hooks.", "Deleted hook") - + def test_stop_hooks_scripted(self): """Test that a scripted stop hook works with no specifiers""" self.stop_hooks_scripted(5, "-I false") diff --git a/lldb/test/API/commands/target/stop-hooks/stop_hook.py b/lldb/test/API/commands/target/stop-hooks/stop_hook.py index 142c4ab4c5d89..a41190baeadf2 100644 --- a/lldb/test/API/commands/target/stop-hooks/stop_hook.py +++ b/lldb/test/API/commands/target/stop-hooks/stop_hook.py @@ -49,6 +49,7 @@ class no_handle_stop: def __init__(self, target, extra_args, dict): print("I am okay") + class self_deleting_stop: def __init__(self, target, extra_args, dict): self.target = target @@ -58,17 +59,17 @@ def handle_stop(self, exe_ctx, stream): result = lldb.SBCommandReturnObject() interp.HandleCommand("handle_id", result) id_str = result.GetOutput().rstrip() - + command = f"target stop-hook delete {id_str}" interp.HandleCommand(command, result) + stop_hook_id = 0 + + def handle_stop_hook_id(debugger, command, exe_ctx, result, extra_args): global stop_hook_id if command == "": result.AppendMessage(str(stop_hook_id)) else: stop_hook_id = int(command) - - - From 73ac3a038abd0c451b354b93018f56ddb1038c11 Mon Sep 17 00:00:00 2001 From: Jim Ingham Date: Wed, 24 Sep 2025 16:06:04 -0700 Subject: [PATCH 3/4] Document the one-stop-hook-deletes-another behavior. --- lldb/source/Commands/CommandObjectTarget.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index 940be42d1b6e3..e3f169ab4fa10 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -5117,6 +5117,15 @@ class CommandObjectTargetStopHookDelete : public CommandObjectParsed { : CommandObjectParsed(interpreter, "target stop-hook delete", "Delete a stop-hook.", "target stop-hook delete []") { + SetHelpLong( + R"( +Deletes the stop hook by index. + +At any given stop, all enabled stop hooks that pass the stop filter will +get a chance to run. That means if one stop-hook deletes another stop hook +while executing, the deleted stop hook will still fire for the stop at which +it was deleted. + )"); AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar); } From 32c6dfed39fd9a4bcefba771f1f01d6921310713 Mon Sep 17 00:00:00 2001 From: Jim Ingham Date: Fri, 7 Nov 2025 09:23:09 -0800 Subject: [PATCH 4/4] formatting --- lldb/source/Target/Target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index 6a448b911831e..edc2cfbf0e401 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3211,7 +3211,7 @@ bool Target::RunStopHooks(bool at_initial_stop) { // We have to decide what that means. We will follow the rule that deleting // a stop hook while processing these stop hooks will delete it for FUTURE // stops but not this stop. Fortunately, copying the m_stop_hooks to the - // active_hooks list before iterating over the hooks has this effect. + // active_hooks list before iterating over the hooks has this effect. for (auto cur_hook_sp : active_hooks) { bool any_thread_matched = false; for (auto exc_ctx : exc_ctx_with_reasons) {