Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -45,6 +45,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatAdapters.h" +#include #include #include #include @@ -1050,8 +1051,18 @@ } bool ScriptInterpreterPythonImpl::Interrupt() { + // PyErr_SetInterrupt was introduced in 3.2. +#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2) || (PY_MAJOR_VERSION > 3) + // Tell Python that it should pretend to have received a SIGINT. + PyErr_SetInterrupt(); + // PyErr_SetInterrupt has no way to return an error so we can only pretend the + // signal got successfully handled and return true. + // Python 3.10 introduces PyErr_SetInterruptEx that could return an error, but + // the error handling is limited to checking the arguments which would be + // just our (hardcoded) input signal code SIGINT, so that's not useful at all. + return true; +#else Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_SCRIPT)); - if (IsExecutingPython()) { PyThreadState *state = PyThreadState_GET(); if (!state) @@ -1071,6 +1082,7 @@ "ScriptInterpreterPythonImpl::Interrupt() python code not running, " "can't interrupt"); return false; +#endif } bool ScriptInterpreterPythonImpl::ExecuteOneLineWithReturn( @@ -3275,6 +3287,19 @@ "lldb.embedded_interpreter; from " "lldb.embedded_interpreter import run_python_interpreter; " "from lldb.embedded_interpreter import run_one_line"); + + // Setup a default SIGINT signal handler that works the same way as the + // normal Python REPL signal handler which raises a KeyboardInterrupt. + // Also make sure to not pollute the user's REPL with the signal module nor + // our utility function. + PyRun_SimpleString( + "def lldb_setup_sigint_handler():\n" + " import signal;\n" + " def signal_handler(sig, frame):\n" + " raise KeyboardInterrupt()\n" + " signal.signal(signal.SIGINT, signal_handler);\n" + "lldb_setup_sigint_handler();\n" + "del lldb_setup_sigint_handler\n"); } void ScriptInterpreterPythonImpl::AddToSysPath(AddLocation location, Index: lldb/test/API/iohandler/sigint/TestIOHandlerPythonREPLSigint.py =================================================================== --- /dev/null +++ lldb/test/API/iohandler/sigint/TestIOHandlerPythonREPLSigint.py @@ -0,0 +1,34 @@ +""" +Test sending SIGINT to the embedded Python REPL. +""" + +import os + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test.lldbpexpect import PExpectTest + +class TestCase(PExpectTest): + + mydir = TestBase.compute_mydir(__file__) + + # PExpect uses many timeouts internally and doesn't play well + # under ASAN on a loaded machine.. + @skipIfAsan + def test(self): + self.launch() + + # Start the embedded Python REPL via the 'script' command. + self.child.send("script -l python --\n") + # Wait for the Python REPL prompt. + self.child.expect(">>>") + # Send SIGINT to the LLDB process. + self.child.sendintr() + # This should get transformed to a KeyboardInterrupt which is the same + # behaviour as the standalone Python REPL. + self.child.expect("KeyboardInterrupt") + # Send EOF to quit the Python REPL. + self.child.sendeof() + + self.quit()