diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -1927,6 +1927,7 @@ header.startswith("SB") and header.endswith(".h"))] includes = '\n'.join(list) new_content = content.replace('%include_SB_APIs%', includes) + new_content = new_content.replace('%SOURCE_DIR%', self.getSourceDir()) src = os.path.join(self.getBuildDir(), source) with open(src, 'w') as f: f.write(new_content) 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 @@ -2307,6 +2307,8 @@ } CommandReturnObject tmp_result(m_debugger.GetUseColor()); + tmp_result.SetInteractive(result.GetInteractive()); + // If override_context is not NULL, pass no_context_switching = true for // HandleCommand() since we updated our context already. diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3286,6 +3286,7 @@ CommandReturnObject result(false); result.SetImmediateOutputStream(output_sp); + result.SetInteractive(false); Debugger &debugger = exc_ctx.GetTargetPtr()->GetDebugger(); CommandInterpreterRunOptions options; options.SetStopOnContinue(true); diff --git a/lldb/test/API/api/multithreaded/TestMultithreaded.py b/lldb/test/API/api/multithreaded/TestMultithreaded.py --- a/lldb/test/API/api/multithreaded/TestMultithreaded.py +++ b/lldb/test/API/api/multithreaded/TestMultithreaded.py @@ -23,9 +23,19 @@ self.generateSource('test_listener_event_description.cpp') self.generateSource('test_listener_event_process_state.cpp') self.generateSource('test_listener_resume.cpp') + self.generateSource('test_stop-hook.cpp') mydir = TestBase.compute_mydir(__file__) + @skipIfRemote + @skipIfNoSBHeaders + # clang-cl does not support throw or catch (llvm.org/pr24538) + @skipIfWindows + def test_python_stop_hook(self): + """Test that you can run a python command in a stop-hook when stdin is File based. """ + self.build_and_test('driver.cpp test_stop-hook.cpp', + 'test_python_stop_hook') + @skipIfRemote @skipIfNoSBHeaders # clang-cl does not support throw or catch (llvm.org/pr24538) diff --git a/lldb/test/API/api/multithreaded/some_cmd.py b/lldb/test/API/api/multithreaded/some_cmd.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/api/multithreaded/some_cmd.py @@ -0,0 +1,33 @@ +""" Test command for checking the Python commands can run in a stop-hook """ +import lldb + +did_run = False + +class SomeCommand: + def __init__(self, debugger, unused): + self.dbg = debugger + def __call__(self, debugger, command, exe_ctx, result): + global did_run + did_run = True + result.PutCString("some output\n") + + def get_short_help(self): + return "Test command - sets a variable." + +class OtherCommand: + def __init__(self, debugger, unused): + self.dbg = debugger + def __call__(self, debugger, command, exe_ctx, result): + global did_run + if did_run: + result.SetStatus(lldb.eReturnStatusSuccessFinishNoResult) + else: + result.SetStatus(lldb.eReturnStatusFailed) + + def get_short_help(self): + return "Test command - sets a variable." + +def __lldb_init_module(debugger, unused): + print("Adding command some-cmd and report-cmd") + debugger.HandleCommand("command script add -c some_cmd.SomeCommand some-cmd") + debugger.HandleCommand("command script add -c some_cmd.OtherCommand report-cmd") diff --git a/lldb/test/API/api/multithreaded/test_stop-hook.cpp.template b/lldb/test/API/api/multithreaded/test_stop-hook.cpp.template new file mode 100644 --- /dev/null +++ b/lldb/test/API/api/multithreaded/test_stop-hook.cpp.template @@ -0,0 +1,130 @@ +// LLDB C++ API Test: Verify that when the Debugger stdin +// is set to a FILE *, lldb can still successfully run a +// python command in a stop hook. + +#include +#include +#include +#include +#include + +%include_SB_APIs% + +#include "common.h" + +using namespace lldb; + +void test(SBDebugger &dbg, std::vector args) { + // The problem we had was that when the thread that was + // waiting on input went into the call to 'read' it had + // the file handle lock. Then when the python interpreter + // Initialized itself to run the python command, it tried + // to flush the file channel, and that deadlocked. + // This only happens when Async is true, since otherwise + // the process event is handled on the I/O read thread, + // which sidestepped the problem. + dbg.SetAsync(true); + + SBTarget target = dbg.CreateTarget(args.at(0).c_str()); + if (!target.IsValid()) + throw Exception("invalid target"); + + SBBreakpoint breakpoint = target.BreakpointCreateByName("next"); + if (!breakpoint.IsValid()) + throw Exception("invalid breakpoint"); + + SBCommandInterpreter interp = dbg.GetCommandInterpreter(); + SBCommandReturnObject result; + + // Bring in the python command. We actually add two commands, + // one that runs in the stop hook and sets a variable when it + // runs, and one that reports out the variable so we can ensure + // that we did indeed run the stop hook. + const char *source_dir = "%SOURCE_DIR%"; + SBFileSpec script_spec(source_dir); + script_spec.AppendPathComponent("some_cmd.py"); + char path[PATH_MAX]; + script_spec.GetPath(path, PATH_MAX); + + std::string import_command("command script import "); + import_command.append(path); + interp.HandleCommand(import_command.c_str(), result); + if (!result.Succeeded()) + throw Exception("Couldn't import %SOURCE_DIR%/some_cmd.py"); + + SBProcess process = target.LaunchSimple(nullptr, nullptr, nullptr); + if (!process.IsValid()) + throw Exception("Couldn't launch process."); + if (process.GetState() != lldb::eStateStopped) + throw Exception("Process was not stopped"); + + process.SetSelectedThreadByIndexID(0); + + // Now add the stop hook: + interp.HandleCommand("target stop-hook add -o some-cmd", result); + if (!result.Succeeded()) + throw Exception("Couldn't add a stop hook."); + + // Now switch the I/O over to a pipe, which will be handled by the + // NativeFile class: + int to_lldb_des[2]; + int pipe_result = pipe(to_lldb_des); + FILE *fh_lldb_in = fdopen(to_lldb_des[0], "r"); + FILE *fh_to_lldb = fdopen(to_lldb_des[1], "w"); + + // We need to reset the handle before destroying the debugger + // or the same deadlock will stall exiting: + class Cleanup { + public: + Cleanup(SBDebugger dbg, int filedes[2]) : m_dbg(dbg) { + m_file = m_dbg.GetInputFileHandle(); + m_filedes[0] = filedes[0]; + m_filedes[1] = filedes[1]; + } + ~Cleanup() { + m_dbg.SetInputFileHandle(m_file, false); + close(m_filedes[0]); + close(m_filedes[1]); + } + + private: + FILE *m_file; + SBDebugger m_dbg; + int m_filedes[2]; + }; + Cleanup cleanup(dbg, to_lldb_des); + + dbg.SetInputFileHandle(fh_lldb_in, false); + + // Now run the command interpreter. You have to pass true to + // start thread so we will run the I/O in a separate thread. + dbg.RunCommandInterpreter(false, true); + + // Now issue a stepi, and fetch the running and stopped events: + fprintf(fh_to_lldb, "thread step-inst\n"); + + SBEvent proc_event; + StateType state; + bool got_event; + + got_event = dbg.GetListener().WaitForEventForBroadcaster( + 100, process.GetBroadcaster(), proc_event); + if (!got_event) + throw Exception("Didn't get running event"); + state = SBProcess::GetStateFromEvent(proc_event); + if (state != eStateRunning) + throw Exception("Event wasn't a running event."); + + got_event = dbg.GetListener().WaitForEventForBroadcaster( + 100, process.GetBroadcaster(), proc_event); + if (!got_event) + throw Exception("Didn't get a stopped event"); + state = SBProcess::GetStateFromEvent(proc_event); + if (state != eStateStopped) + throw Exception("Event wasn't a stop event."); + + // At this point the stop hook should have run. Check that: + interp.HandleCommand("report-cmd", result); + if (!result.Succeeded()) + throw Exception("Didn't actually call stop hook."); +}