https://github.com/youngd007 updated https://github.com/llvm/llvm-project/pull/186821
>From e8c50279dc5a900f76c230a5963350458ea5ac89 Mon Sep 17 00:00:00 2001 From: David Young <[email protected]> Date: Mon, 16 Mar 2026 08:30:49 -0700 Subject: [PATCH] [lldb] Fix 'process signal' when the process is stopped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Process::Signal() only works when the inferior is running because SendAsyncSignal() relies on interrupting the running process and replacing the continue packet. When the process is already stopped (e.g. at a breakpoint), SendAsyncSignal() fails because there is no continue loop to interrupt. Fix by falling back to Host::Kill() + Process::Resume() when Signal() fails on a stopped process. This queues the signal via kill(), then resumes the process so ptrace intercepts the pending signal — giving the debugger a chance to handle it according to the current 'process handle' settings, consistent with the running-process behavior. --- lldb/source/Commands/CommandObjectProcess.cpp | 16 ++++++ .../functionalities/signal/TestSendSignal.py | 56 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp index b0adbf8156bed..eac01a5c5bd5c 100644 --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -18,6 +18,7 @@ #include "lldb/Breakpoint/BreakpointSite.h" #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Host/Host.h" #include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandOptionArgumentTable.h" @@ -1185,6 +1186,21 @@ class CommandObjectProcessSignal : public CommandObjectParsed { Status error(process->Signal(signo)); if (error.Success()) { result.SetStatus(eReturnStatusSuccessFinishResult); + } else if (StateIsStoppedState(process->GetState(), true)) { + // Signal() only works when the process is running (it interrupts + // the process and replaces the continue packet with a + // continue-with-signal packet). When the process is stopped, we + // queue the signal via kill() and resume the process. ptrace will + // intercept the pending signal, giving the debugger a chance to + // handle it according to the current 'process handle' settings. + Host::Kill(process->GetID(), signo); + error = process->Resume(); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return; + } + result.AppendErrorWithFormat("Failed to send signal %i: %s\n", signo, + error.AsCString()); } else { result.AppendErrorWithFormat("Failed to send signal %i: %s\n", signo, error.AsCString()); diff --git a/lldb/test/API/functionalities/signal/TestSendSignal.py b/lldb/test/API/functionalities/signal/TestSendSignal.py index 5b6a50a461cdf..81c50bd962011 100644 --- a/lldb/test/API/functionalities/signal/TestSendSignal.py +++ b/lldb/test/API/functionalities/signal/TestSendSignal.py @@ -86,6 +86,62 @@ def test_with_run_command(self): self.match("statistics dump", [r'"signals": \[', r'"SIGUSR1": 1']) + @expectedFailureNetBSD(bugnumber="llvm.org/pr43959") + @skipIfWindows # Windows does not support signals + def test_send_signal_while_stopped(self): + """Test that 'process signal SIGUSR1' works when the process is stopped.""" + self.build() + exe = self.getBuildArtifact("a.out") + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + breakpoint = target.BreakpointCreateByLocation("main.c", self.line) + self.assertTrue( + breakpoint and breakpoint.GetNumLocations() == 1, VALID_BREAKPOINT + ) + + process_listener = lldb.SBListener("signal_test_listener") + launch_info = target.GetLaunchInfo() + launch_info.SetWorkingDirectory(self.get_process_working_directory()) + launch_info.SetListener(process_listener) + error = lldb.SBError() + process = target.Launch(launch_info, error) + self.assertTrue(process, PROCESS_IS_VALID) + + # Configure SIGUSR1 to stop and pass through to the inferior. + self.runCmd("process handle -n False -p True -s True SIGUSR1") + + thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint) + self.assertTrue(thread.IsValid(), "We hit the first breakpoint.") + + # The process is now stopped. Disable the breakpoint so we don't hit + # it again. + breakpoint.SetEnabled(False) + + self.setAsync(True) + + # Send a signal while the process is stopped. This should resume the + # process with the signal, matching GDB's behavior. + self.runCmd("process signal SIGUSR1") + + self.match_state(process_listener, lldb.eStateRunning) + self.match_state(process_listener, lldb.eStateStopped) + + # Verify the process stopped because of SIGUSR1. + threads = lldbutil.get_stopped_threads(process, lldb.eStopReasonSignal) + self.assertEqual(len(threads), 1, "One thread stopped for a signal.") + thread = threads[0] + + self.assertGreaterEqual( + thread.GetStopReasonDataCount(), 1, "There was data in the event." + ) + self.assertEqual( + thread.GetStopReasonDataAtIndex(0), + lldbutil.get_signal_number("SIGUSR1"), + "The stop signal was SIGUSR1", + ) + def match_state(self, process_listener, expected_state): num_seconds = 5 broadcaster = self.process().GetBroadcaster() _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
