| // 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 <cstdlib> |
| #include <errno.h> |
| #ifdef _WIN32 |
| #include <fcntl.h> |
| #include <io.h> |
| #endif |
| #include <mutex> |
| #include <stdio.h> |
| #include <string> |
| #include <vector> |
| |
| #include "lldb/API/SBBreakpoint.h" |
| #include "lldb/API/SBBroadcaster.h" |
| #include "lldb/API/SBCommandInterpreter.h" |
| #include "lldb/API/SBCommandReturnObject.h" |
| #include "lldb/API/SBDebugger.h" |
| #include "lldb/API/SBEvent.h" |
| #include "lldb/API/SBFileSpec.h" |
| #include "lldb/API/SBListener.h" |
| #include "lldb/API/SBProcess.h" |
| #include "lldb/API/SBTarget.h" |
| |
| #include "common.h" |
| |
| #if !defined(PATH_MAX) |
| #define PATH_MAX 4096 |
| #endif |
| |
| using namespace lldb; |
| |
| void test(SBDebugger &dbg, std::vector<std::string> 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. |
| // The test sets LLDB_TEST_SOURCE_DIR to the directory containing this |
| // test's support files (see TestMultithreaded.py). |
| const char *source_dir = getenv("LLDB_TEST_SOURCE_DIR"); |
| if (!source_dir) |
| throw Exception("LLDB_TEST_SOURCE_DIR is not set"); |
| 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(std::string("Couldn't import ") + path); |
| |
| 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]; |
| #ifdef _WIN32 |
| int pipe_result = _pipe(to_lldb_des, 4096, _O_TEXT); |
| FILE *fh_lldb_in = _fdopen(to_lldb_des[0], "r"); |
| FILE *fh_to_lldb = _fdopen(to_lldb_des[1], "w"); |
| #else |
| 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"); |
| #endif |
| |
| // We need to reset the handle before destroying the debugger |
| // or the same deadlock will stall exiting: |
| class Cleanup { |
| public: |
| Cleanup(SBDebugger dbg, FILE *read_end, FILE *write_end) |
| : m_dbg(dbg), m_read_end(read_end), m_write_end(write_end) { |
| m_file = m_dbg.GetInputFileHandle(); |
| } |
| ~Cleanup() { |
| fclose(m_write_end); |
| // Normally driver.cpp would do this, but we must do it now so that |
| // any in-progress reads from the input handle are finished before |
| // we close it. |
| SBDebugger::Destroy(m_dbg); |
| fclose(m_read_end); |
| } |
| |
| private: |
| FILE *m_file; |
| SBDebugger m_dbg; |
| FILE *m_read_end; |
| FILE *m_write_end; |
| }; |
| Cleanup cleanup(dbg, fh_lldb_in, fh_to_lldb); |
| |
| 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."); |
| } |