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,20 @@ 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. + /// + /// \return + /// \b true if there was a command in progress to recieve the interrupt. + /// \b false if there's no command currently in flight. + 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 @@ -371,6 +371,48 @@ 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" count on the debugger to support + /// cooperative interruption. If this is non-zero, InterruptRequested will + /// return true. Interruptible operations are expected to query the + /// InterruptRequested API periodically, and interrupt what they were doing + /// if it returns \b true. + /// + void RequestInterrupt(); + + /// Decrement the "interrupt requested" counter. + 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 \b 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 @@ -512,13 +554,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. + HostThread SetIOHandlerThread(HostThread &new_thread); void JoinIOHandlerThread(); + + bool IsIOHandlerThreadCurrentThread() const; lldb::thread_result_t IOHandlerThread(); @@ -602,6 +650,9 @@ lldb_private::DebuggerDestroyCallback m_destroy_callback = nullptr; void *m_destroy_callback_baton = nullptr; + uint32_t m_interrupt_requested = 0; ///< Tracks interrupt requests + std::mutex m_interrupt_mutex; + // Events for m_sync_broadcaster enum { eBroadcastBitEventThreadIsListening = (1 << 0), 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 @@ -355,7 +355,7 @@ CommandReturnObject &result, bool force_repeat_command = false); - bool WasInterrupted() const; + bool InterruptCommand(); /// Execute a list of commands in sequence. /// @@ -640,6 +640,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; @@ -702,7 +706,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 @@ -1699,3 +1699,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/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp --- a/lldb/source/API/SBFrame.cpp +++ b/lldb/source/API/SBFrame.cpp @@ -16,6 +16,7 @@ #include "Utils.h" #include "lldb/Core/Address.h" +#include "lldb/Core/Debugger.h" #include "lldb/Core/StreamFile.h" #include "lldb/Core/ValueObjectRegister.h" #include "lldb/Core/ValueObjectVariable.h" @@ -806,6 +807,7 @@ if (stop_locker.TryLock(&process->GetRunLock())) { frame = exe_ctx.GetFramePtr(); if (frame) { + Debugger &dbg = process->GetTarget().GetDebugger(); VariableList *variable_list = nullptr; Status var_error; variable_list = frame->GetVariableList(true, &var_error); @@ -815,6 +817,11 @@ const size_t num_variables = variable_list->GetSize(); if (num_variables) { for (const VariableSP &variable_sp : *variable_list) { + if (dbg.InterruptRequested()) { + Log *log = GetLog(LLDBLog::Host); + LLDB_LOG(log, "Interrupted SBFrame::GetVariables"); + return {}; + } if (variable_sp) { bool add_variable = false; switch (variable_sp->GetScope()) { 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/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -228,7 +228,7 @@ thread->GetIndexID()); return false; } - if (m_options.m_extended_backtrace) { + if (m_options.m_extended_backtrace && !GetDebugger().InterruptRequested()) { DoExtendedBacktrace(thread, result); } 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 @@ -1230,6 +1230,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; + } + 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); @@ -1981,7 +2004,15 @@ data->Dump(stream.get()); } -bool Debugger::HasIOHandlerThread() { return m_io_handler_thread.IsJoinable(); } +bool Debugger::HasIOHandlerThread() const { + return m_io_handler_thread.IsJoinable(); +} + +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()) { @@ -2013,6 +2044,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 @@ -1886,8 +1886,8 @@ LLDB_LOGF(log, "Processing command: %s", command_line); LLDB_SCOPED_TIMERF("Processing command: %s.", command_line); - if (WasInterrupted()) { - result.AppendError("interrupted"); + if (GetDebugger().InterruptRequested()) { + result.AppendError("... Interrupted"); return false; } @@ -2555,7 +2555,7 @@ m_debugger.SetAsyncExecution(false); } - for (size_t idx = 0; idx < num_lines && !WasInterrupted(); idx++) { + for (size_t idx = 0; idx < num_lines; idx++) { const char *cmd = commands.GetStringAtIndex(idx); if (cmd[0] == '\0') continue; @@ -3035,6 +3035,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); @@ -3048,7 +3051,8 @@ lldb::StreamFileSP stream = is_stdout ? io_handler.GetOutputStreamFileSP() : io_handler.GetErrorStreamFileSP(); // Split the output into lines and poll for interrupt requests - while (!str.empty() && !WasInterrupted()) { + bool had_output = !str.empty(); + while (!str.empty()) { llvm::StringRef line; std::tie(line, str) = str.split('\n'); { @@ -3059,7 +3063,7 @@ } std::lock_guard guard(io_handler.GetOutputMutex()); - if (!str.empty()) + if (had_output && GetDebugger().InterruptRequested()) stream->Printf("\n... Interrupted.\n"); stream->Flush(); } @@ -3372,7 +3376,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/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -9,6 +9,7 @@ #include "lldb/Target/StackFrameList.h" #include "lldb/Breakpoint/Breakpoint.h" #include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Debugger.h" #include "lldb/Core/SourceManager.h" #include "lldb/Core/StreamFile.h" #include "lldb/Symbol/Block.h" @@ -470,7 +471,15 @@ } StackFrameSP unwind_frame_sp; + Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger(); do { + // Check for interruption here when building the frames - this is the + // expensive part, Dump later on is cheap. + if (dbg.InterruptRequested()) { + Log *log = GetLog(LLDBLog::Host); + LLDB_LOG(log, "Interrupted %s", __FUNCTION__); + break; + } uint32_t idx = m_concrete_frames_fetched++; lldb::addr_t pc = LLDB_INVALID_ADDRESS; lldb::addr_t cfa = LLDB_INVALID_ADDRESS; 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,330 @@ +""" +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 starts up, rendevous with the test thread + using threading barriers, then checks whether it has been interrupted. + + 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 barriers and event to the runner thread using thread local data: + import interruptible + interruptible.local_data = interruptible.BarrierContainer(self.test.before_interrupt_barrier, + self.test.after_interrupt_barrier, + self.test.event) + + 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() + result = self.test.dbg.GetCommandInterpreter().HandleCommand(self.command, self.test.result) + if self.test.result_barrier: + self.test.result_barrier.wait() + + 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 event and barriers 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.event = threading.Event() + self.result_barrier = threading.Barrier(2, timeout=10) + self.before_interrupt_barrier = threading.Barrier(2, timeout=10) + self.after_interrupt_barrier = threading.Barrier(2, timeout=10) + else: + self.event = None + self.result_barrier = None + self.before_interrupt_barrier = None + self.after_interrupt_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.result.Clear() + 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) + + # Okay now wait till the command has gotten started to issue the interrupt: + self.before_interrupt_barrier.wait() + # I'm going to do it twice here to test that it works as a counter: + self.dbg.RequestInterrupt() + self.dbg.RequestInterrupt() + + def cleanup(): + self.dbg.CancelInterruptRequest() + self.addTearDownHook(cleanup) + # Okay, now set both sides going: + self.after_interrupt_barrier.wait() + + # Check that the command was indeed interrupted. First rendevous + # after the runner thread had a chance to execute the command: + self.result_barrier.wait() + 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 counter is counting: + self.dbg.CancelInterruptRequest() + command = self.command_setup("debugger") + self.run_single_command(command) + + # This time we won't even get to run the command, since HandleCommand + # checks for the interrupt state on entry, so we don't wait on the command + # barriers. + self.result_barrier.wait() + + # Again check that we were + self.assertFalse(self.result.Succeeded(), "Our command was not allowed to run") + error_output = self.result.GetError() + self.assertIn("... Interrupted", error_output, "Command was cut short by interrupt") + + # 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) + + # Now raise the debugger interrupt flag. It will also interrupt the command: + self.before_interrupt_barrier.wait() + self.dbg.GetCommandInterpreter().InterruptCommand() + self.after_interrupt_barrier.wait() + + # Check that the command was indeed interrupted: + self.result_barrier.wait() + 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") + "\n" + else: + self.command = self.command_setup("interp") + "\n" + + self.start_command_interp() + + # Now give the interpreter a chance to run this command up + # to the first barrier + self.before_interrupt_barrier.wait() + # Then issue the interrupt: + sent_interrupt = self.dbg.GetCommandInterpreter().InterruptCommand() + self.assertTrue(sent_interrupt, "Did send command interrupt.") + # Now give the command a chance to finish: + self.after_interrupt_barrier.wait() + + 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 poll") + "\n" + + self.start_command_interp() + + self.before_interrupt_barrier.wait() + self.dbg.RequestInterrupt() + def cleanup(): + self.dbg.CancelInterruptRequest() + self.addTearDownHook(cleanup) + self.after_interrupt_barrier.wait() + + 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,90 @@ +import lldb +import threading + +local_data = None + +class BarrierContainer(threading.local): + def __init__(self, before_interrupt_barrier, after_interrupt_barrier, event): + self.event = event + self.before_interrupt_barrier = before_interrupt_barrier + self.after_interrupt_barrier = after_interrupt_barrier + +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 rendevous, 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 rendevous or spin waiting for the + interruption to happen. + + For the interrupt cases, the command waits serially on the barriers + passed to it in local data, giving the test runner a chance to set the + interrupt. Once the barriers are passed, it waits for the interrupt + or the event. + If it finds an interrupt, 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 rendevous, then checks once. + For the "check" case, it doesn't wait, but just returns whether there was + an interrupt in force or not.""" + + 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: + # Wait here to rendevous in the test before it sets the interrupt. + local_data.before_interrupt_barrier.wait() + # Now the test will set the interrupt, and we can continue: + local_data.after_interrupt_barrier.wait() + + 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; +}