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

Reply via email to