https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/195959
>From 4b6a5d530e3bdacf25673e890912ff9de855759b Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Tue, 5 May 2026 15:58:21 -0700 Subject: [PATCH 1/3] [lldb] Handle SIGINT via the MainLoop signal thread (on POSIX) The driver's async SIGINT handler called SBDebugger::DispatchInputInterrupt directly, which is not async-signal-safe and can lead to a crash. Register SIGINT with the existing signal-thread MainLoop instead so DispatchInputInterrupt runs in normal thread context. The callback temporarily installs SIG_DFL for the duration of DispatchInputInterrupt, preserving the "second Ctrl-C force-exits" escape hatch users rely on when the debugger is unresponsive. Windows keeps the existing code path. rdar://158218595 --- lldb/tools/driver/Driver.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index d47d3daf1c3fc..4e25df66fd19b 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -651,11 +651,10 @@ void Driver::UpdateWindowSize() { } } -void sigint_handler(int signo) { #ifdef _WIN32 +void sigint_handler(int signo) { // Restore handler as it is not persistent on Windows. signal(SIGINT, sigint_handler); -#endif static std::atomic_flag g_interrupt_sent = ATOMIC_FLAG_INIT; if (g_driver != nullptr) { @@ -668,6 +667,7 @@ void sigint_handler(int signo) { _exit(signo); } +#endif static void printHelp(LLDBOptTable &table, llvm::StringRef tool_name) { std::string usage_str = tool_name.str() + " [options]"; @@ -781,15 +781,41 @@ int main(int argc, char const *argv[]) { // Setup LLDB signal handlers once the debugger has been initialized. SBDebugger::PrintDiagnosticsOnError(); - // FIXME: Migrate the SIGINT handler to be handled by the signal loop below. +#ifdef _WIN32 signal(SIGINT, sigint_handler); -#if !defined(_WIN32) +#else signal(SIGPIPE, SIG_IGN); // Handle signals in a MainLoop running on a separate thread. MainLoop signal_loop; Status signal_status; + auto sigint_handler = signal_loop.RegisterSignal( + SIGINT, + [&](MainLoopBase &) { + // Temporarily restore the default disposition so that a second SIGINT + // delivered while DispatchInputInterrupt is running hard-terminates + // the process. This preserves the "double Ctrl-C to force exit" + // escape hatch users rely on when the debugger is unresponsive. + struct sigaction old_action; + struct sigaction new_action = {}; + new_action.sa_handler = SIG_DFL; + sigemptyset(&new_action.sa_mask); + + int ret = sigaction(SIGINT, &new_action, &old_action); + UNUSED_IF_ASSERT_DISABLED(ret); + assert(ret == 0 && "sigaction failed"); + + if (g_driver) + g_driver->GetDebugger().DispatchInputInterrupt(); + + ret = sigaction(SIGINT, &old_action, nullptr); + UNUSED_IF_ASSERT_DISABLED(ret); + assert(ret == 0 && "sigaction failed"); + }, + signal_status); + assert(sigint_handler && signal_status.Success()); + auto sigwinch_handler = signal_loop.RegisterSignal( SIGWINCH, [&](MainLoopBase &) { >From c2593d2485d5a42dc3397633986114b38d465144 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Tue, 5 May 2026 20:15:07 -0700 Subject: [PATCH 2/3] Install a no-op SIGURG handler --- lldb/tools/driver/Driver.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index 4e25df66fd19b..c1462911011e1 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -43,6 +43,9 @@ #include <clocale> #include <csignal> #include <future> +#ifndef _WIN32 +#include <pthread.h> +#endif #include <string> #include <thread> #include <utility> @@ -786,13 +789,25 @@ int main(int argc, char const *argv[]) { #else signal(SIGPIPE, SIG_IGN); + // Install a no-op handler for SIGURG so the signal thread can use + // pthread_kill(main_tid, SIGURG) to interrupt blocking syscalls (e.g. + // Python's time.sleep or input()) on the main thread without causing the + // process to terminate or re-entering the SIGINT callback. + struct sigaction wakeup_action = {}; + wakeup_action.sa_handler = [](int) {}; + sigemptyset(&wakeup_action.sa_mask); + sigaction(SIGURG, &wakeup_action, nullptr); + + // Capture the main thread's id so the signal thread can target it. + pthread_t main_thread = pthread_self(); + // Handle signals in a MainLoop running on a separate thread. MainLoop signal_loop; Status signal_status; auto sigint_handler = signal_loop.RegisterSignal( SIGINT, - [&](MainLoopBase &) { + [&, main_thread](MainLoopBase &) { // Temporarily restore the default disposition so that a second SIGINT // delivered while DispatchInputInterrupt is running hard-terminates // the process. This preserves the "double Ctrl-C to force exit" @@ -812,6 +827,12 @@ int main(int argc, char const *argv[]) { ret = sigaction(SIGINT, &old_action, nullptr); UNUSED_IF_ASSERT_DISABLED(ret); assert(ret == 0 && "sigaction failed"); + + // Wake the main thread so any blocking syscall (e.g. the Python REPL + // waiting on input or sleeping) returns with EINTR. This lets Python + // observe the pending interrupt queued by DispatchInputInterrupt and + // raise KeyboardInterrupt. + pthread_kill(main_thread, SIGURG); }, signal_status); assert(sigint_handler && signal_status.Success()); >From 6a4942dc6529cb3063e9a9845b1a0f8254660c48 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Thu, 7 May 2026 10:41:10 -0700 Subject: [PATCH 3/3] Go with Pavel's suggestion to use SIGINT --- lldb/tools/driver/Driver.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index c1462911011e1..95c1c6601479e 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -789,18 +789,13 @@ int main(int argc, char const *argv[]) { #else signal(SIGPIPE, SIG_IGN); - // Install a no-op handler for SIGURG so the signal thread can use - // pthread_kill(main_tid, SIGURG) to interrupt blocking syscalls (e.g. - // Python's time.sleep or input()) on the main thread without causing the - // process to terminate or re-entering the SIGINT callback. - struct sigaction wakeup_action = {}; - wakeup_action.sa_handler = [](int) {}; - sigemptyset(&wakeup_action.sa_mask); - sigaction(SIGURG, &wakeup_action, nullptr); - // Capture the main thread's id so the signal thread can target it. pthread_t main_thread = pthread_self(); + // Set when the signal thread sends itself a SIGINT to wake the main thread. + // The next SIGINT callback invocation observes this flag and skips the work. + std::atomic<bool> skip_next_sigint{false}; + // Handle signals in a MainLoop running on a separate thread. MainLoop signal_loop; Status signal_status; @@ -808,6 +803,11 @@ int main(int argc, char const *argv[]) { auto sigint_handler = signal_loop.RegisterSignal( SIGINT, [&, main_thread](MainLoopBase &) { + // Skip the self-sent wakeup SIGINT queued at the end of the previous + // invocation. + if (skip_next_sigint.exchange(false)) + return; + // Temporarily restore the default disposition so that a second SIGINT // delivered while DispatchInputInterrupt is running hard-terminates // the process. This preserves the "double Ctrl-C to force exit" @@ -831,8 +831,10 @@ int main(int argc, char const *argv[]) { // Wake the main thread so any blocking syscall (e.g. the Python REPL // waiting on input or sleeping) returns with EINTR. This lets Python // observe the pending interrupt queued by DispatchInputInterrupt and - // raise KeyboardInterrupt. - pthread_kill(main_thread, SIGURG); + // raise KeyboardInterrupt. Flag the resulting callback invocation so + // it's skipped rather than re-running DispatchInputInterrupt. + skip_next_sigint.store(true); + pthread_kill(main_thread, SIGINT); }, signal_status); assert(sigint_handler && signal_status.Success()); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
