diff --git a/lldb/include/lldb/API/SBCommandInterpreter.h b/lldb/include/lldb/API/SBCommandInterpreter.h --- a/lldb/include/lldb/API/SBCommandInterpreter.h +++ b/lldb/include/lldb/API/SBCommandInterpreter.h @@ -241,7 +241,16 @@ lldb::SBStringList &matches, lldb::SBStringList &descriptions); + /// Returns whether an interrupt flag was raised either by the SBDebugger - + /// when the function is not running on the RunCommandInterpreter thread, or + /// by SBCommandInterpreter::InterruptCommand if it is. If your code is doing + /// interruptible work, check this API periodically, and interrupt if it + /// returns true. bool WasInterrupted() const; + + /// Interrupts the command currently executing in the RunCommandInterpreter + /// thread. + bool InterruptCommand(); // Catch commands before they execute by registering a callback that will get // called when the command gets executed. This allows GUI or command line diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -197,6 +197,10 @@ lldb::SBCommandInterpreter GetCommandInterpreter(); void HandleCommand(const char *command); + + void RequestInterrupt(); + void CancelInterruptRequest(); + bool InterruptRequested(); lldb::SBListener GetListener(); diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -370,6 +370,53 @@ bool IsHandlingEvents() const { return m_event_handler_thread.IsJoinable(); } Status RunREPL(lldb::LanguageType language, const char *repl_options); + + /// Interruption in LLDB: + /// + /// This is a voluntary interruption mechanism, not preemptive. Parts of lldb + /// that do work that can be safely interrupted call + /// Debugger::InterruptRequested and if that returns true, they should return + /// at a safe point, shortcutting the rest of the work they were to do. + /// + /// lldb clients can both offer a CommandInterpreter (through + /// RunCommandInterpreter) and use the SB API's for their own purposes, so it + /// is convenient to separate "interrupting the CommandInterpreter execution" + /// and interrupting the work it is doing with the SB API's. So there are two + /// ways to cause an interrupt: + /// * CommandInterpreter::InterruptCommand: Interrupts the command currently + /// running in the command interpreter IOHandler thread + /// * Debugger::RequestInterrupt: Interrupts are active on anything but the + /// CommandInterpreter thread till CancelInterruptRequest is called. + /// + /// Since the two checks are mutually exclusive, however, it's also convenient + /// to have just one function to check the interrupt state. + + + /// Bump the "interrupt requested" flag on the debugger to support + /// cooperative interruption. If this is non-zero, InterruptRequested will + /// return true. Interruptible operations are expected to query this + /// flag periodically, and interrupt what they were doing if it is true. + /// + /// \param[in] message + /// The new value of the InterruptRequested flag. + /// + void RequestInterrupt(); + + /// Decrement the "interrupt requested" flag. This is queried by + /// InterruptRequested, and any interruptible operations will be interrupted + /// while this flag is raised. + /// + void CancelInterruptRequest(); + + /// This is the correct way to query the state of Interruption. + /// If you are on the RunCommandInterpreter thread, it will check the + /// command interpreter state, and if it is on another thread it will + /// check the debugger Interrupt Request state. + /// + /// \return + /// A boolean value, if true an interruptible operation should interrupt + /// itself. + bool InterruptRequested(); // This is for use in the command interpreter, when you either want the // selected target, or if no target is present you want to prime the dummy @@ -505,13 +552,19 @@ bool PopIOHandler(const lldb::IOHandlerSP &reader_sp); - bool HasIOHandlerThread(); + bool HasIOHandlerThread() const; bool StartIOHandlerThread(); void StopIOHandlerThread(); + + // Sets the IOHandler thread to the new_thread, and returns + // the previous IOHandler thread. + const HostThread SetIOHandlerThread(HostThread &new_thread); void JoinIOHandlerThread(); + + bool IsIOHandlerThreadCurrentThread() const; lldb::thread_result_t IOHandlerThread(); @@ -590,6 +643,9 @@ lldb::ListenerSP m_forward_listener_sp; llvm::once_flag m_clear_once; lldb::TargetSP m_dummy_target_sp; + uint32_t m_interrupt_requested = 0; + std::vector m_interrupt_stack; + std::recursive_mutex m_interrupt_mutex; // Events for m_sync_broadcaster enum { diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -354,7 +354,7 @@ bool HandleCommand(const char *command_line, LazyBool add_to_history, CommandReturnObject &result); - bool WasInterrupted() const; + bool InterruptCommand(); /// Execute a list of commands in sequence. /// @@ -639,6 +639,10 @@ protected: friend class Debugger; + // This checks just the RunCommandInterpreter interruption state. It is only + // meant to be used in Debugger::InterruptRequested + bool WasInterrupted() const; + // IOHandlerDelegate functions void IOHandlerInputComplete(IOHandler &io_handler, std::string &line) override; @@ -701,7 +705,6 @@ void StartHandlingCommand(); void FinishHandlingCommand(); - bool InterruptCommand(); Debugger &m_debugger; // The debugger session that this interpreter is // associated with diff --git a/lldb/source/API/SBCommandInterpreter.cpp b/lldb/source/API/SBCommandInterpreter.cpp --- a/lldb/source/API/SBCommandInterpreter.cpp +++ b/lldb/source/API/SBCommandInterpreter.cpp @@ -141,7 +141,13 @@ bool SBCommandInterpreter::WasInterrupted() const { LLDB_INSTRUMENT_VA(this); - return (IsValid() ? m_opaque_ptr->WasInterrupted() : false); + return (IsValid() ? m_opaque_ptr->GetDebugger().InterruptRequested() : false); +} + +bool SBCommandInterpreter::InterruptCommand() { + LLDB_INSTRUMENT_VA(this); + + return (IsValid() ? m_opaque_ptr->InterruptCommand() : false); } const char *SBCommandInterpreter::GetIOHandlerControlSequence(char ch) { diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -1690,3 +1690,24 @@ LLDB_INSTRUMENT_VA(this, error, trace_description_file); return SBTrace::LoadTraceFromFile(error, *this, trace_description_file); } + +void SBDebugger::RequestInterrupt() { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_sp) + m_opaque_sp->RequestInterrupt(); +} + void SBDebugger::CancelInterruptRequest() { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_sp) + m_opaque_sp->CancelInterruptRequest(); +} + +bool SBDebugger::InterruptRequested() { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_sp) + return m_opaque_sp->InterruptRequested(); + return false; +} diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -2004,7 +2004,7 @@ result.GetOutputStream().EOL(); result.GetOutputStream().EOL(); } - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; num_dumped++; DumpModuleSymtab(m_interpreter, result.GetOutputStream(), @@ -2031,7 +2031,7 @@ result.GetOutputStream().EOL(); result.GetOutputStream().EOL(); } - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; num_dumped++; DumpModuleSymtab(m_interpreter, result.GetOutputStream(), @@ -2092,7 +2092,7 @@ result.GetOutputStream().Format("Dumping sections for {0} modules.\n", num_modules); for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; num_dumped++; DumpModuleSections( @@ -2110,7 +2110,7 @@ FindModulesByName(target, arg_cstr, module_list, true); if (num_matches > 0) { for (size_t i = 0; i < num_matches; ++i) { - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; Module *module = module_list.GetModulePointerAtIndex(i); if (module) { @@ -2224,7 +2224,7 @@ result.GetOutputStream().Format("Dumping clang ast for {0} modules.\n", num_modules); for (ModuleSP module_sp : module_list.ModulesNoLocking()) { - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; if (SymbolFile *sf = module_sp->GetSymbolFile()) sf->DumpClangAST(result.GetOutputStream()); @@ -2249,7 +2249,7 @@ } for (size_t i = 0; i < num_matches; ++i) { - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; Module *m = module_list.GetModulePointerAtIndex(i); if (SymbolFile *sf = m->GetSymbolFile()) @@ -2298,7 +2298,7 @@ result.GetOutputStream().Format( "Dumping debug symbols for {0} modules.\n", num_modules); for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; if (DumpModuleSymbolFile(result.GetOutputStream(), module_sp.get())) num_dumped++; @@ -2314,7 +2314,7 @@ FindModulesByName(target, arg_cstr, module_list, true); if (num_matches > 0) { for (size_t i = 0; i < num_matches; ++i) { - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; Module *module = module_list.GetModulePointerAtIndex(i); if (module) { @@ -2381,7 +2381,7 @@ if (target_modules.GetSize() > 0) { uint32_t num_dumped = 0; for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { - if (m_interpreter.WasInterrupted()) + if (GetDebugger().InterruptRequested()) break; if (DumpCompileUnitLineTable( m_interpreter, result.GetOutputStream(), module_sp.get(), diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -1198,6 +1198,29 @@ return std::make_shared(*this, false, GetUseColor()); } +void Debugger::RequestInterrupt() { + std::lock_guard guard(m_interrupt_mutex); + m_interrupt_requested++; +} + +void Debugger::CancelInterruptRequest() { + std::lock_guard guard(m_interrupt_mutex); + if (m_interrupt_requested > 0) + m_interrupt_requested--; +} + +bool Debugger::InterruptRequested() { + // This is the one we should call internally. This will return true either + // if there's a debugger interrupt and we aren't on the IOHandler thread, + // or if we are on the IOHandler thread and there's a CommandInterpreter + // interrupt. + if (!IsIOHandlerThreadCurrentThread()) { + std::lock_guard guard(m_interrupt_mutex); + return m_interrupt_requested != 0; + } else + return GetCommandInterpreter().WasInterrupted(); +} + size_t Debugger::GetNumDebuggers() { if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); @@ -1943,7 +1966,15 @@ data->Dump(stream.get()); } -bool Debugger::HasIOHandlerThread() { return m_io_handler_thread.IsJoinable(); } +bool Debugger::HasIOHandlerThread() const { + return m_io_handler_thread.IsJoinable(); +} + +const HostThread Debugger::SetIOHandlerThread(HostThread &new_thread) { + HostThread old_host = m_io_handler_thread; + m_io_handler_thread = new_thread; + return old_host; +} bool Debugger::StartIOHandlerThread() { if (!m_io_handler_thread.IsJoinable()) { @@ -1975,6 +2006,12 @@ } } +bool Debugger::IsIOHandlerThreadCurrentThread() const { + if (!HasIOHandlerThread()) + return false; + return m_io_handler_thread.EqualsThread(Host::GetCurrentThread()); +} + Target &Debugger::GetSelectedOrDummyTarget(bool prefer_dummy) { if (!prefer_dummy) { if (TargetSP target = m_target_list.GetSelectedTarget()) diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -3022,6 +3022,9 @@ } bool CommandInterpreter::WasInterrupted() const { + if (!m_debugger.IsIOHandlerThreadCurrentThread()) + return false; + bool was_interrupted = (m_command_state == CommandHandlingState::eInterrupted); lldbassert(!was_interrupted || m_iohandler_nesting_level > 0); @@ -3359,7 +3362,13 @@ if (options.GetSpawnThread()) { m_debugger.StartIOHandlerThread(); } else { + // If the current thread is not managed by a host thread, we won't detect + // that this IS the CommandInterpreter IOHandler thread, so make it so: + HostThread new_io_handler_thread(Host::GetCurrentThread()); + HostThread old_io_handler_thread + = m_debugger.SetIOHandlerThread(new_io_handler_thread); m_debugger.RunIOHandlers(); + m_debugger.SetIOHandlerThread(old_io_handler_thread); if (options.GetAutoHandleEvents()) m_debugger.StopEventHandlerThread(); diff --git a/lldb/test/API/python_api/was_interrupted/Makefile b/lldb/test/API/python_api/was_interrupted/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/python_api/was_interrupted/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c +CFLAGS_EXTRAS := -std=c99 + +include Makefile.rules diff --git a/lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py b/lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py @@ -0,0 +1,332 @@ +""" +Test SBDebugger.InterruptRequested and SBCommandInterpreter.WasInterrupted. +""" + + + +import lldb +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.lldbtest import * +import threading +import os + +class TestDebuggerInterruption(TestBase): + """This test runs a command that blocks on a lock, and once it gets + the lock, it spins calling WasInterrupted. + We use the lock to coordinate this execution with a controller thread, + so it can know the command is executing before trying to interrupt it. + We also use a barrier to coordinate the whole command runner with the + controlling thread, and the command also waits on an event, which we + use to kick it out of its loop in case of error, making the test + more resilient in case of errors. + The command's first argument is either 'interp' or 'debugger', to test + InterruptRequested and WasInterrupted respectively. + The command has two modes, interrupt and check, the former is the one that + waits for an interrupt. Then latter just returns whether an interrupt was + requested. We use the latter to make sure we took down the flag correctly.""" + + NO_DEBUG_INFO_TESTCASE = True + + class CommandRunner(threading.Thread): + """This class is for running a command, and for making a thread to run the command on. + It gets passed the test it is working on behalf of, and most of the important + objects come from the test. """ + def __init__(self, test): + super().__init__() + self.test = test + + def rendevous(self): + # We smuggle out lock and event to the runner thread using thread local data: + import interruptible + interruptible.local_data = interruptible.LockContainer(self.test.lock, self.test.event) + # If we are going to run in a locking mode, wait for the barrier so everything is set + # up on the other side: + if self.test.lock: + self.test.lock.acquire() + self.test.barrier.wait() + + + class DirectCommandRunner(CommandRunner): + """"This version runs a single command using HandleCommand.""" + def __init__(self, test, command): + super().__init__(test) + self.command = command + + def run(self): + self.rendevous() + self.test.dbg.GetCommandInterpreter().HandleCommand(self.command, self.test.result) + + if self.test.lock: + self.test.lock.release() + + class CommandInterpreterRunner(CommandRunner): + """This version runs the CommandInterpreter and feeds the command to it.""" + def __init__(self, test): + super().__init__(test) + + def run(self): + self.rendevous() + + test = self.test + + # We will use files for debugger input and output: + + # First write down the command: + with open(test.getBuildArtifact(test.in_filename), "w") as f: + f.write(f"{test.command}\n") + + # Now set the debugger's stdout & stdin to our files, and run + # the CommandInterpreter: + with open(test.out_filename, "w") as outf, open(test.in_filename, "r") as inf: + outsbf = lldb.SBFile(outf.fileno(), "w", False) + orig_outf = test.dbg.GetOutputFile() + error = test.dbg.SetOutputFile(outsbf) + test.assertSuccess(error, "Could not set outfile") + + insbf = lldb.SBFile(inf.fileno(), "r", False) + orig_inf = test.dbg.GetOutputFile() + error = test.dbg.SetInputFile(insbf) + test.assertSuccess(error, "Could not set infile") + + options = lldb.SBCommandInterpreterRunOptions() + options.SetPrintResults(True) + options.SetEchoCommands(False) + + test.dbg.RunCommandInterpreter(True, False, options, 0, False, False) + test.dbg.GetOutputFile().Flush() + + error = test.dbg.SetOutputFile(orig_outf) + test.assertSuccess(error, "Restored outfile") + test.dbg.SetInputFile(orig_inf) + test.assertSuccess(error, "Restored infile") + + def command_setup(self, args): + """Insert our command, if needed. Then set up the locking if needed. + Then return the command to run.""" + + self.interp = self.dbg.GetCommandInterpreter() + self.command_name = "interruptible_command" + self.cmd_result = lldb.SBCommandReturnObject() + + if not "check" in args: + self.lock = threading.Lock() + self.event = threading.Event() + self.barrier = threading.Barrier(2, timeout=10) + else: + self.lock = None + self.event = None + self.barrier = None + + if not self.interp.UserCommandExists(self.command_name): + # Make the command we're going to use - it spins calling WasInterrupted: + cmd_filename = "interruptible" + cmd_filename = os.path.join(self.getSourceDir(), "interruptible.py") + self.runCmd(f"command script import {cmd_filename}") + cmd_string = f"command script add {self.command_name} --class interruptible.WelcomeCommand" + self.runCmd(cmd_string) + + if len(args) == 0: + command = self.command_name + else: + command = self.command_name + " " + args + return command + + def run_single_command(self, command): + # Now start up a thread to run the command: + self.runner = TestDebuggerInterruption.DirectCommandRunner(self, command) + self.runner.start() + + def start_command_interp(self): + self.runner = TestDebuggerInterruption.CommandInterpreterRunner(self) + self.runner.start() + + def check_text(self, result_text, interrupted): + if interrupted: + self.assertIn("Command was interrupted", result_text, + "Got the interrupted message") + else: + self.assertIn("Command was not interrupted", result_text, + "Got the not interrupted message") + + def gather_output(self): + # Now wait for the interrupt to interrupt the command: + self.runner.join(10.0) + finished = not self.runner.is_alive() + # Don't leave the runner thread stranded if the interrupt didn't work. + if not finished: + self.event.set() + self.runner.join(10.0) + + self.assertTrue(finished, "We did finish the command") + + def check_result(self, interrupted = True): + self.gather_output() + self.check_text(self.result.GetOutput(), interrupted) + + def check_result_output(self, interrupted = True): + self.gather_output() + buffer = "" + # Okay, now open the file for reading, and read. + with open(self.out_filename, "r") as f: + buffer = f.read() + + self.assertNotEqual(len(buffer), 0, "No command data") + self.check_text(buffer, interrupted) + + def debugger_interrupt_test(self, use_interrupt_requested): + """Test that debugger interruption interrupts a command + running directly through HandleCommand. + If use_interrupt_requested is true, we'll check that API, + otherwise we'll check WasInterrupted. They should both do + the same thing.""" + + if use_interrupt_requested: + command = self.command_setup("debugger") + else: + command = self.command_setup("interp") + + self.result = lldb.SBCommandReturnObject() + self.run_single_command(command) + + # Now raise the debugger interrupt flag. It will also interrupt the command: + self.barrier.wait() + self.lock.acquire() + self.dbg.RequestInterrupt() + def cleanup(): + self.dbg.CancelInterruptRequest() + self.addTearDownHook(cleanup) + + # Check that the command was indeed interrupted: + self.assertTrue(self.result.Succeeded(), "Our command succeeded") + result_output = self.result.GetOutput() + self.check_result(True) + + # Do it again to make sure that the flag has stayed up: + command = self.command_setup("debugger") + self.run_single_command(command) + + # Now raise the debugger interrupt flag. It will also interrupt the command: + self.barrier.wait() + self.lock.acquire() + + # Again check that we were indeed interrupted: + self.assertTrue(self.result.Succeeded(), "Our command succeeded") + result_output = self.result.GetOutput() + self.check_result(True) + + # Now take down the flag, and make sure that we aren't interrupted: + self.dbg.CancelInterruptRequest() + + # Now make sure that we really did take down the flag: + command = self.command_setup("debugger check") + self.run_single_command(command) + result_output = self.result.GetOutput() + self.check_result(False) + + def test_debugger_interrupt_use_dbg(self): + self.debugger_interrupt_test(True) + + def test_debugger_interrupt_use_interp(self): + self.debugger_interrupt_test(False) + + def test_interp_doesnt_interrupt_debugger(self): + """Test that interpreter interruption does not interrupt a command + running directly through HandleCommand. + If use_interrupt_requested is true, we'll check that API, + otherwise we'll check WasInterrupted. They should both do + the same thing.""" + + command = self.command_setup("debugger poll") + + self.result = lldb.SBCommandReturnObject() + self.run_single_command(command) + + self.dbg.GetCommandInterpreter().InterruptCommand() + + # Now raise the debugger interrupt flag. It will also interrupt the command: + self.barrier.wait() + self.lock.acquire() + + # Check that the command was indeed interrupted: + self.assertTrue(self.result.Succeeded(), "Our command succeeded") + result_output = self.result.GetOutput() + self.check_result(False) + + + def interruptible_command_test(self, use_interrupt_requested): + """Test that interpreter interruption interrupts a command + running in the RunCommandInterpreter loop. + If use_interrupt_requested is true, we'll check that API, + otherwise we'll check WasInterrupted. They should both do + the same thing.""" + + self.out_filename = self.getBuildArtifact("output") + self.in_filename = self.getBuildArtifact("input") + # We're going to overwrite the input file, but we + # don't want data accumulating in the output file. + + if os.path.exists(self.out_filename): + os.unlink(self.out_filename) + + # You should be able to use either check method interchangeably: + if use_interrupt_requested: + self.command = self.command_setup("debugger immediate") + "\n" + else: + self.command = self.command_setup("interp immediate") + "\n" + + self.start_command_interp() + + self.barrier.wait() + + # Now give the interpreter a chance to run this command up + # to the lock point + self.lock.acquire() + + sent_interrupt = self.dbg.GetCommandInterpreter().InterruptCommand() + self.assertTrue(sent_interrupt, "Did send command interrupt.") + self.check_result_output(True) + + os.unlink(self.out_filename) + + # Now send the check command, and make sure the flag is now down. + self.command = self.command_setup("interp check") + "\n" + self.start_command_interp() + + self.check_result_output(False) + + def test_interruptible_command_check_dbg(self): + self.interruptible_command_test(True) + + def test_interruptible_command_check_interp(self): + self.interruptible_command_test(False) + + def test_debugger_doesnt_interrupt_command(self): + """Test that debugger interruption doesn't interrupt a command + running in the RunCommandInterpreter loop.""" + + self.out_filename = self.getBuildArtifact("output") + self.in_filename = self.getBuildArtifact("input") + # We're going to overwrite the input file, but we + # don't want data accumulating in the output file. + + if os.path.exists(self.out_filename): + os.unlink(self.out_filename) + + self.command = self.command_setup("interp immediate poll") + "\n" + + self.start_command_interp() + + self.barrier.wait() + + self.dbg.RequestInterrupt() + def cleanup(): + self.dbg.CancelInterruptRequest() + self.addTearDownHook(cleanup) + # Now give the interpreter a chance to run this command up + # to the lock point + self.lock.acquire() + + self.check_result_output(False) + + os.unlink(self.out_filename) + diff --git a/lldb/test/API/python_api/was_interrupted/interruptible.py b/lldb/test/API/python_api/was_interrupted/interruptible.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/python_api/was_interrupted/interruptible.py @@ -0,0 +1,93 @@ +import lldb +import threading + +local_data = None + +class LockContainer(threading.local): + def __init__(self, lock, event): + self.lock = lock + self.event = event + +class WelcomeCommand(object): + + def __init__(self, debugger, session_dict): + return + + def get_short_help(self): + return "A command that waits for an interrupt before returning." + + def check_was_interrupted(self, debugger, use_interpreter): + if use_interpreter: + self.was_interrupted = debugger.GetCommandInterpreter().WasInterrupted() + else: + self.was_interrupted = debugger.InterruptRequested() + if local_data.event: + self.was_canceled = local_data.event.is_set() + + def __call__(self, debugger, args, exe_ctx, result): + """Command arguments: + {interp/debugger} - Whether to use SBCommandInterpreter::WasInterrupted + of SBDebugger::InterruptRequested(). + check - Don't do the locking, just check if an interrupt was requested. + If check is not provided, we'll do the lock and then check. + poll - Should we poll once after the lock or spin waiting for the + interruption to happen. + + For the interrupt cases, the command fetches a lock and event source + that have been set in the local data. It locks the lock (giving the + runner time to set the interrupt flag. Then waits for the interrupt. + if it finds it, it returns "Command was interrupted". If it gets an + event before seeing the interrupt it returns "Command was not interrupted." + For the "poll" case, it waits on the lock, then checks once. + For the "check" case, it doesn't wait, but just returns whether there was + an interrupt in force or not.""" + + # The current behavior of CommandInterpreter::PrintCommandOutput is + # to not print the output of an interrupted command. I need that output + # so I force it to come out synchronously here: + if "immediate" in args: + result.SetImmediateOutputFile(debugger.GetOutputFile()) + result.SetImmediateErrorFile(debugger.GetErrorFile()) + + if local_data == None: + result.SetError("local data was not set.") + result.SetStatus(lldb.eReturnStatusFailed) + return + + use_interpreter = "interp" in args + if not use_interpreter: + if not "debugger" in args: + result.SetError("Must pass either 'interp' or 'debugger'") + result.SetStatus(lldb.eReturnStatusFailed) + return + + self.was_interrupted = False + self.was_canceled = False + + if "check" in args: + self.check_was_interrupted(debugger, use_interpreter) + if self.was_interrupted: + result.Print("Command was interrupted") + else: + result.Print("Command was not interrupted") + else: + lock = local_data.lock + lock.release() + + if "poll" in args: + self.check_was_interrupted(debugger, use_interpreter) + else: + while not self.was_interrupted and not self.was_canceled: + self.check_was_interrupted(debugger, use_interpreter) + + if self.was_interrupted: + result.Print("Command was interrupted") + else: + result.Print("Command was not interrupted") + + if self.was_canceled: + result.Print("Command was canceled") + result.SetStatus(lldb.eReturnStatusSuccessFinishResult) + return True + + diff --git a/lldb/test/API/python_api/was_interrupted/main.c b/lldb/test/API/python_api/was_interrupted/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/python_api/was_interrupted/main.c @@ -0,0 +1,11 @@ +#include + +int global_test_var = 10; + +int +main() +{ + int test_var = 10; + printf ("Set a breakpoint here: %d.\n", test_var); + return global_test_var; +}