// 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" #if !defined(PATH_MAX) #define PATH_MAX 4096 #endif 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."); }