Command Interpreter mechanism.
Differential Revision: https://reviews.llvm.org/D145136
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
lldb::SBCommandInterpreter GetCommandInterpreter();
void HandleCommand(const char *command);
+
+ void RequestInterrupt();
+ void CancelInterruptRequest();
+ bool InterruptRequested();
lldb::SBListener GetListener();
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
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();
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),
CommandReturnObject &result,
bool force_repeat_command = false);
- bool WasInterrupted() const;
+ bool InterruptCommand();
/// Execute a list of commands in sequence.
///
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;
void StartHandlingCommand();
void FinishHandlingCommand();
- bool InterruptCommand();
Debugger &m_debugger; // The debugger session that this interpreter is
// associated with
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) {
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;
+}
#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"
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);
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()) {
result.GetOutputStream().EOL();
result.GetOutputStream().EOL();
}
- if (m_interpreter.WasInterrupted())
+ if (GetDebugger().InterruptRequested())
break;
num_dumped++;
DumpModuleSymtab(m_interpreter, result.GetOutputStream(),
result.GetOutputStream().EOL();
result.GetOutputStream().EOL();
}
- if (m_interpreter.WasInterrupted())
+ if (GetDebugger().InterruptRequested())
break;
num_dumped++;
DumpModuleSymtab(m_interpreter, result.GetOutputStream(),
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(
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) {
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());
}
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())
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++;
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) {
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(),
thread->GetIndexID());
return false;
}
- if (m_options.m_extended_backtrace) {
+ if (m_options.m_extended_backtrace && !GetDebugger().InterruptRequested()) {
DoExtendedBacktrace(thread, result);
}
return std::make_shared<StreamAsynchronousIO>(*this, false, GetUseColor());
}
+void Debugger::RequestInterrupt() {
+ std::lock_guard<std::mutex> guard(m_interrupt_mutex);
+ m_interrupt_requested++;
+}
+
+void Debugger::CancelInterruptRequest() {
+ std::lock_guard<std::mutex> 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<std::mutex> 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<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr);
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()) {
}
}
+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())
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;
}
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;
}
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);
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');
{
}
std::lock_guard<std::recursive_mutex> guard(io_handler.GetOutputMutex());
- if (!str.empty())
+ if (had_output && GetDebugger().InterruptRequested())
stream->Printf("\n... Interrupted.\n");
stream->Flush();
}
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();
#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"
}
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;
--- /dev/null
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -std=c99
+
+include Makefile.rules
--- /dev/null
+"""
+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)
+
--- /dev/null
+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
+
+
--- /dev/null
+#include <stdio.h>
+
+int global_test_var = 10;
+
+int
+main()
+{
+ int test_var = 10;
+ printf ("Set a breakpoint here: %d.\n", test_var);
+ return global_test_var;
+}