From 969ed3d10f2bed3ef9c4d53114594827fefecf68 Mon Sep 17 00:00:00 2001 From: Caroline Tice Date: Mon, 2 May 2011 20:41:46 +0000 Subject: [PATCH] This patch captures and serializes all output being written by the command line driver, including the lldb prompt being output by editline, the asynchronous process output & error messages, and asynchronous messages written by target stop-hooks. As part of this it introduces a new Stream class, StreamAsynchronousIO. A StreamAsynchronousIO object is created with a broadcaster, who will eventually broadcast the stream's data for a listener to handle, and an event type indicating what type of event the broadcaster will broadcast. When the Write method is called on a StreamAsynchronousIO object, the data is appended to an internal string. When the Flush method is called on a StreamAsynchronousIO object, it broadcasts it's data string and clears the string. Anything in lldb-core that needs to generate asynchronous output for the end-user should use the StreamAsynchronousIO objects. I have also added a new notification type for InputReaders, to let them know that a asynchronous output has been written. This is to allow the input readers to, for example, refresh their prompts and lines, if desired. I added the case statements to all the input readers to catch this notification, but I haven't added any code for handling them yet (except to the IOChannel input reader). llvm-svn: 130721 --- lldb/include/lldb/API/SBCommandInterpreter.h | 4 +- lldb/include/lldb/API/SBDebugger.h | 3 + lldb/include/lldb/Core/Debugger.h | 3 + lldb/include/lldb/Core/Event.h | 3 + lldb/include/lldb/Core/StreamAsynchronousIO.h | 42 +++++ .../lldb/Interpreter/CommandInterpreter.h | 4 +- lldb/include/lldb/lldb-enumerations.h | 1 + lldb/lldb.xcodeproj/project.pbxproj | 12 +- lldb/source/API/SBDebugger.cpp | 12 ++ .../CommandObjectBreakpointCommand.cpp | 3 + .../source/Commands/CommandObjectCommands.cpp | 5 +- .../Commands/CommandObjectExpression.cpp | 3 + lldb/source/Commands/CommandObjectTarget.cpp | 3 + lldb/source/Core/Debugger.cpp | 14 ++ lldb/source/Core/Event.cpp | 7 + lldb/source/Core/InputReader.cpp | 3 + lldb/source/Core/StreamAsynchronousIO.cpp | 52 ++++++ .../source/Interpreter/CommandInterpreter.cpp | 11 +- .../Interpreter/ScriptInterpreterPython.cpp | 6 + lldb/source/Target/Process.cpp | 3 + lldb/source/Target/Target.cpp | 12 +- lldb/tools/driver/Driver.cpp | 84 ++++++--- lldb/tools/driver/Driver.h | 2 + lldb/tools/driver/IOChannel.cpp | 160 +++++++++--------- lldb/tools/driver/IOChannel.h | 18 +- 25 files changed, 351 insertions(+), 119 deletions(-) create mode 100644 lldb/include/lldb/Core/StreamAsynchronousIO.h create mode 100644 lldb/source/Core/StreamAsynchronousIO.cpp diff --git a/lldb/include/lldb/API/SBCommandInterpreter.h b/lldb/include/lldb/API/SBCommandInterpreter.h index 0ce9d4a588c0d..c3bda08247893 100644 --- a/lldb/include/lldb/API/SBCommandInterpreter.h +++ b/lldb/include/lldb/API/SBCommandInterpreter.h @@ -21,7 +21,9 @@ class SBCommandInterpreter { eBroadcastBitThreadShouldExit = (1 << 0), eBroadcastBitResetPrompt = (1 << 1), - eBroadcastBitQuitCommandReceived = (1 << 2) // User entered quit + eBroadcastBitQuitCommandReceived = (1 << 2), // User entered quit + eBroadcastBitAsynchronousOutputData = (1 << 3), + eBroadcastBitAsynchronousErrorData = (1 << 4) }; SBCommandInterpreter (const lldb::SBCommandInterpreter &rhs); diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h index a1dd5834c6ca3..64253be358213 100644 --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -163,6 +163,9 @@ class SBDebugger void PushInputReader (lldb::SBInputReader &reader); + void + NotifyTopInputReader (lldb::InputReaderAction notification); + const char * GetInstanceName (); diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index 6d7a29a39748b..bdee600560c2e 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -388,6 +388,9 @@ class Debugger : bool PopInputReader (const lldb::InputReaderSP& reader_sp); + void + NotifyTopInputReader (lldb::InputReaderAction notification); + static lldb::DebuggerSP FindDebuggerWithID (lldb::user_id_t id); diff --git a/lldb/include/lldb/Core/Event.h b/lldb/include/lldb/Core/Event.h index dd6988daaba10..0fb6c67b08f1e 100644 --- a/lldb/include/lldb/Core/Event.h +++ b/lldb/include/lldb/Core/Event.h @@ -87,6 +87,9 @@ class EventDataBytes : public EventData void SetBytes (const void *src, size_t src_len); + + void + SwapBytes (std::string &new_bytes); void SetBytesFromCString (const char *cstr); diff --git a/lldb/include/lldb/Core/StreamAsynchronousIO.h b/lldb/include/lldb/Core/StreamAsynchronousIO.h new file mode 100644 index 0000000000000..9dd798ce6d508 --- /dev/null +++ b/lldb/include/lldb/Core/StreamAsynchronousIO.h @@ -0,0 +1,42 @@ +//===-- StreamAsynchronousIO.h -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_StreamAsynchronousIO_h_ +#define liblldb_StreamAsynchronousIO_h_ + +#include + +#include "lldb/Core/Stream.h" +#include "lldb/Core/StreamString.h" + +namespace lldb_private { + +class StreamAsynchronousIO : + public Stream +{ +public: + StreamAsynchronousIO (Broadcaster &broadcaster, uint32_t broadcast_event_type); + + virtual ~StreamAsynchronousIO (); + + virtual void + Flush (); + + virtual int + Write (const void *src, size_t src_len); + + +private: + Broadcaster &m_broadcaster; + uint32_t m_broadcast_event_type; + StreamString m_accumulated_data; +}; + +} // namespace lldb_private +#endif // #ifndef liblldb_StreamAsynchronousIO_h diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index 2f03c061a9b51..5567f0c4e476e 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -35,7 +35,9 @@ class CommandInterpreter : public Broadcaster { eBroadcastBitThreadShouldExit = (1 << 0), eBroadcastBitResetPrompt = (1 << 1), - eBroadcastBitQuitCommandReceived = (1 << 2) // User entered quit + eBroadcastBitQuitCommandReceived = (1 << 2), // User entered quit + eBroadcastBitAsynchronousOutputData = (1 << 3), + eBroadcastBitAsynchronousErrorData = (1 << 4) }; void diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index a3388b8873ac4..f37b83293eb71 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -265,6 +265,7 @@ namespace lldb { typedef enum InputReaderAction { eInputReaderActivate, // reader is newly pushed onto the reader stack + eInputReaderAsynchronousOutputWritten, // an async output event occurred; the reader may want to do something eInputReaderReactivate, // reader is on top of the stack again after another reader was popped off eInputReaderDeactivate, // another reader was pushed on the stack eInputReaderGotToken, // reader got one of its tokens (granularity) diff --git a/lldb/lldb.xcodeproj/project.pbxproj b/lldb/lldb.xcodeproj/project.pbxproj index 9932d37019c45..2976fe0956264 100644 --- a/lldb/lldb.xcodeproj/project.pbxproj +++ b/lldb/lldb.xcodeproj/project.pbxproj @@ -433,6 +433,8 @@ 9A357673116E7B6400E8ED2F /* SBStringList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9A357672116E7B6400E8ED2F /* SBStringList.cpp */; }; 9A3576A8116E9AB700E8ED2F /* SBHostOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A3576A7116E9AB700E8ED2F /* SBHostOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9A3576AA116E9AC700E8ED2F /* SBHostOS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9A3576A9116E9AC700E8ED2F /* SBHostOS.cpp */; }; + 9A4F35101368A51A00823F52 /* StreamAsynchronousIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9A4F350F1368A51A00823F52 /* StreamAsynchronousIO.cpp */; }; + 9A4F35121368A54100823F52 /* StreamAsynchronousIO.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A4F35111368A54100823F52 /* StreamAsynchronousIO.h */; }; 9AA69DA61188F52100D753A0 /* PseudoTerminal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2682F16A115EDA0D00CCFF99 /* PseudoTerminal.cpp */; }; 9AA69DAF118A023300D753A0 /* SBInputReader.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AA69DAE118A023300D753A0 /* SBInputReader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9AA69DB1118A024600D753A0 /* SBInputReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9AA69DB0118A024600D753A0 /* SBInputReader.cpp */; }; @@ -1192,6 +1194,8 @@ 9A4633DA11F65D8600955CE1 /* UserSettingsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UserSettingsController.h; path = include/lldb/Core/UserSettingsController.h; sourceTree = ""; }; 9A4633DC11F65D9A00955CE1 /* UserSettingsController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = UserSettingsController.cpp; path = source/Core/UserSettingsController.cpp; sourceTree = ""; }; 9A48A3A7124AAA5A00922451 /* python-extensions.swig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "python-extensions.swig"; sourceTree = ""; }; + 9A4F350F1368A51A00823F52 /* StreamAsynchronousIO.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StreamAsynchronousIO.cpp; path = source/Core/StreamAsynchronousIO.cpp; sourceTree = ""; }; + 9A4F35111368A54100823F52 /* StreamAsynchronousIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StreamAsynchronousIO.h; path = include/lldb/Core/StreamAsynchronousIO.h; sourceTree = ""; }; 9A633FE7112DCE3C001A7E43 /* SBFrame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SBFrame.cpp; path = source/API/SBFrame.cpp; sourceTree = ""; }; 9A633FE8112DCE3C001A7E43 /* SBFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBFrame.h; path = include/lldb/API/SBFrame.h; sourceTree = ""; }; 9A82010B10FFB49800182560 /* ScriptInterpreter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScriptInterpreter.cpp; path = source/Interpreter/ScriptInterpreter.cpp; sourceTree = ""; }; @@ -1327,7 +1331,6 @@ 26BC7E7510F1B85900F91463 /* lldb-log.cpp */, 26BC7C2A10F1B3BC00F91463 /* lldb-private.h */, 26217932133BCB850083B112 /* lldb-private-enumerations.h */, - 26BC7C2810F1B3BC00F91463 /* lldb-private-interfaces.h */, 26BC7D5D10F1B77400F91463 /* lldb-private-log.h */, 26217930133BC8640083B112 /* lldb-private-types.h */, 262D3190111B4341004E6F88 /* API */, @@ -1977,6 +1980,8 @@ 26BC7D7810F1B77400F91463 /* STLUtils.h */, 26BC7D7910F1B77400F91463 /* Stream.h */, 26BC7E9110F1B85900F91463 /* Stream.cpp */, + 9A4F35111368A54100823F52 /* StreamAsynchronousIO.h */, + 9A4F350F1368A51A00823F52 /* StreamAsynchronousIO.cpp */, 26BC7D7A10F1B77400F91463 /* StreamFile.h */, 26BC7E9210F1B85900F91463 /* StreamFile.cpp */, 26BC7D7B10F1B77400F91463 /* StreamString.h */, @@ -2176,6 +2181,7 @@ 26BC7DBE10F1B78200F91463 /* Expression */ = { isa = PBXGroup; children = ( + 26BC7C2810F1B3BC00F91463 /* lldb-private-interfaces.h */, 49D7072611B5AD03001AD875 /* ClangASTSource.h */, 49D7072811B5AD11001AD875 /* ClangASTSource.cpp */, 26BC7DC010F1B79500F91463 /* ClangExpression.h */, @@ -2677,6 +2683,7 @@ 2671A0CE134825F6003A87BB /* ConnectionMachPort.h in Headers */, 4CABA9DD134A8BA800539BDD /* ValueObjectMemory.h in Headers */, 4CD0BD0D134BFAB600CB44D4 /* ValueObjectDynamicValue.h in Headers */, + 9A4F35121368A54100823F52 /* StreamAsynchronousIO.h in Headers */, 4C2FAE2F135E3A70001EDE44 /* SharedCluster.h in Headers */, 2692BA16136610C100F9E14D /* UnwindAssemblyInstEmulation.h in Headers */, ); @@ -3246,6 +3253,7 @@ 26A7A035135E6E4200FB369E /* NamedOptionValue.cpp in Sources */, 9A22A161135E30370024DDC3 /* EmulateInstructionARM.cpp in Sources */, 9A22A163135E30370024DDC3 /* EmulationStateARM.cpp in Sources */, + 9A4F35101368A51A00823F52 /* StreamAsynchronousIO.cpp in Sources */, 2630BFAF1365F3220070C534 /* ArchDefaultUnwindPlan.cpp in Sources */, 2630BFB01365F3220070C534 /* ArchVolatileRegs.cpp in Sources */, 2692BA15136610C100F9E14D /* UnwindAssemblyInstEmulation.cpp in Sources */, @@ -3336,7 +3344,7 @@ __STDC_LIMIT_MACROS, LLDB_CONFIGURATION_DEBUG, ); - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_VERSION = 4.2; GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp index 84b13ab641e9a..04f15cbba3fc9 100644 --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -639,6 +639,18 @@ SBDebugger::PushInputReader (SBInputReader &reader) } } +void +SBDebugger::NotifyTopInputReader (InputReaderAction notification) +{ + LogSP log(GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); + + if (log) + log->Printf ("SBDebugger(%p)::NotifyTopInputReader (%d)", m_opaque_sp.get(), notification); + + if (m_opaque_sp) + m_opaque_sp->NotifyTopInputReader (notification); +} + void SBDebugger::reset (const DebuggerSP &debugger_sp) { diff --git a/lldb/source/Commands/CommandObjectBreakpointCommand.cpp b/lldb/source/Commands/CommandObjectBreakpointCommand.cpp index a188baebdeb55..16466481fcf6f 100644 --- a/lldb/source/Commands/CommandObjectBreakpointCommand.cpp +++ b/lldb/source/Commands/CommandObjectBreakpointCommand.cpp @@ -466,6 +466,9 @@ CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback } break; + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderGotToken: if (bytes && bytes_len && baton) { diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp index 142f29d79e776..9d31cf7f1f9ca 100644 --- a/lldb/source/Commands/CommandObjectCommands.cpp +++ b/lldb/source/Commands/CommandObjectCommands.cpp @@ -975,7 +975,10 @@ CommandObjectCommandsAddRegex::InputReaderCallback (void *baton, case eInputReaderDeactivate: break; - + + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderGotToken: while (bytes_len > 0 && (bytes[bytes_len-1] == '\r' || bytes[bytes_len-1] == '\n')) --bytes_len; diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp index ed7824bd82325..fa578f4998794 100644 --- a/lldb/source/Commands/CommandObjectExpression.cpp +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -205,6 +205,9 @@ CommandObjectExpression::MultiLineExpressionCallback case eInputReaderDeactivate: break; + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderGotToken: ++cmd_object_expr->m_expr_line_count; if (bytes && bytes_len) diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index 958aa2cf047b9..f7f873cd8f34b 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -1032,6 +1032,9 @@ class CommandObjectTargetStopHookAdd : public CommandObject } break; + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderGotToken: if (bytes && bytes_len && baton) { diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index f1a64b9820903..f328c626fd104 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -422,6 +422,20 @@ Debugger::CleanUpInputReaders () } } +void +Debugger::NotifyTopInputReader (InputReaderAction notification) +{ + InputReaderSP reader_sp (GetCurrentInputReader()); + if (reader_sp) + { + reader_sp->Notify (notification); + + // Flush out any input readers that are done. + while (CheckIfTopInputReaderIsDone ()) + /* Do nothing. */; + } +} + void Debugger::WriteToDefaultReader (const char *bytes, size_t bytes_len) { diff --git a/lldb/source/Core/Event.cpp b/lldb/source/Core/Event.cpp index 8b0ac16e2b600..ac122858c71d1 100644 --- a/lldb/source/Core/Event.cpp +++ b/lldb/source/Core/Event.cpp @@ -198,3 +198,10 @@ EventDataBytes::GetEventDataFromEvent (const Event *event_ptr) return NULL; } +void +EventDataBytes::SwapBytes (std::string &new_bytes) +{ + m_bytes.swap (new_bytes); +} + + diff --git a/lldb/source/Core/InputReader.cpp b/lldb/source/Core/InputReader.cpp index fc94bed083422..26f7f842314f9 100644 --- a/lldb/source/Core/InputReader.cpp +++ b/lldb/source/Core/InputReader.cpp @@ -327,6 +327,9 @@ InputReader::Notify (InputReaderAction notification) m_active = false; break; + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderInterrupt: case eInputReaderEndOfFile: break; diff --git a/lldb/source/Core/StreamAsynchronousIO.cpp b/lldb/source/Core/StreamAsynchronousIO.cpp new file mode 100644 index 0000000000000..84659c6e16ba5 --- /dev/null +++ b/lldb/source/Core/StreamAsynchronousIO.cpp @@ -0,0 +1,52 @@ +//===-- StreamBroadcast.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include + +#include "lldb/lldb-private.h" +#include "lldb/Core/Broadcaster.h" +#include "lldb/Core/Event.h" +#include "lldb/Core/StreamAsynchronousIO.h" + +using namespace lldb; +using namespace lldb_private; + + +StreamAsynchronousIO::StreamAsynchronousIO (Broadcaster &broadcaster, uint32_t broadcast_event_type) : + Stream (0, 4, eByteOrderBig), + m_broadcaster (broadcaster), + m_broadcast_event_type (broadcast_event_type), + m_accumulated_data () +{ +} + +StreamAsynchronousIO::~StreamAsynchronousIO () +{ +} + +void +StreamAsynchronousIO::Flush () +{ + if (m_accumulated_data.GetSize() > 0) + { + std::auto_ptr data_bytes_ap (new EventDataBytes); + // Let's swap the bytes to avoid LARGE string copies. + data_bytes_ap->SwapBytes (m_accumulated_data.GetString()); + EventSP new_event_sp (new Event (m_broadcast_event_type, data_bytes_ap.release())); + m_broadcaster.BroadcastEvent (new_event_sp); + m_accumulated_data.Clear(); + } +} + +int +StreamAsynchronousIO::Write (const void *s, size_t length) +{ + m_accumulated_data.Write (s, length); + return length; +} diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index a36d9c299e8cb..16e5f3b75afed 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -1226,7 +1226,10 @@ CommandInterpreter::GetConfirmationInputReaderCallback out_file.Flush (); } break; - + + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderGotToken: if (bytes_len == 0) { @@ -1642,6 +1645,12 @@ CommandInterpreter::HandleCommands (const StringList &commands, } } + if (result.GetImmediateOutputStream()) + result.GetImmediateOutputStream()->Flush(); + + if (result.GetImmediateErrorStream()) + result.GetImmediateErrorStream()->Flush(); + // N.B. Can't depend on DidChangeProcessState, because the state coming into the command execution // could be running (for instance in Breakpoint Commands. // So we check the return value to see if it is has running in it. diff --git a/lldb/source/Interpreter/ScriptInterpreterPython.cpp b/lldb/source/Interpreter/ScriptInterpreterPython.cpp index 10b9792f009ff..47302be0ee4aa 100644 --- a/lldb/source/Interpreter/ScriptInterpreterPython.cpp +++ b/lldb/source/Interpreter/ScriptInterpreterPython.cpp @@ -639,6 +639,9 @@ ScriptInterpreterPython::InputReaderCallback script_interpreter->EnterSession (); break; + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderInterrupt: ::write (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor(), "raise KeyboardInterrupt\n", 24); break; @@ -1047,6 +1050,9 @@ ScriptInterpreterPython::GenerateBreakpointOptionsCommandCallback } break; + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderGotToken: { std::string temp_string (bytes, bytes_len); diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 8e96c716e74f9..7bdccdaf4d092 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -3183,6 +3183,9 @@ Process::ProcessInputReaderCallback (void *baton, case eInputReaderReactivate: break; + case eInputReaderAsynchronousOutputWritten: + break; + case eInputReaderGotToken: { Error error; diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index 11804798490ea..a023bf5b7641d 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -20,6 +20,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Event.h" #include "lldb/Core/Log.h" +#include "lldb/Core/StreamAsynchronousIO.h" #include "lldb/Core/StreamString.h" #include "lldb/Core/Timer.h" #include "lldb/Core/ValueObject.h" @@ -1104,8 +1105,12 @@ Target::RunStopHooks () if (num_exe_ctx == 0) return; - result.SetImmediateOutputFile (m_debugger.GetOutputFile().GetStream()); - result.SetImmediateErrorFile (m_debugger.GetErrorFile().GetStream()); + StreamSP output_stream (new StreamAsynchronousIO (m_debugger.GetCommandInterpreter(), + CommandInterpreter::eBroadcastBitAsynchronousOutputData)); + StreamSP error_stream (new StreamAsynchronousIO (m_debugger.GetCommandInterpreter(), + CommandInterpreter::eBroadcastBitAsynchronousErrorData)); + result.SetImmediateOutputStream (output_stream); + result.SetImmediateErrorStream (error_stream); bool keep_going = true; bool hooks_ran = false; @@ -1176,6 +1181,9 @@ Target::RunStopHooks () } if (hooks_ran) result.AppendMessage ("\n** End Stop Hooks **\n"); + + result.GetImmediateOutputStream()->Flush(); + result.GetImmediateErrorStream()->Flush(); } //-------------------------------------------------------------- diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index 83e42fe2e0c26..5107708e8a20d 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -658,7 +658,7 @@ Driver::GetProcessSTDOUT () size_t total_bytes = 0; while ((len = m_debugger.GetSelectedTarget().GetProcess().GetSTDOUT (stdio_buffer, sizeof (stdio_buffer))) > 0) { - m_io_channel_ap->OutWrite (stdio_buffer, len); + m_io_channel_ap->OutWrite (stdio_buffer, len, ASYNC); total_bytes += len; } return total_bytes; @@ -673,7 +673,7 @@ Driver::GetProcessSTDERR () size_t total_bytes = 0; while ((len = m_debugger.GetSelectedTarget().GetProcess().GetSTDERR (stdio_buffer, sizeof (stdio_buffer))) > 0) { - m_io_channel_ap->ErrWrite (stdio_buffer, len); + m_io_channel_ap->ErrWrite (stdio_buffer, len, ASYNC); total_bytes += len; } return total_bytes; @@ -755,24 +755,20 @@ Driver::HandleProcessEvent (const SBEvent &event) { // The process has stdout available, get it and write it out to the // appropriate place. - if (GetProcessSTDOUT ()) - m_io_channel_ap->RefreshPrompt(); + GetProcessSTDOUT (); } else if (event_type & SBProcess::eBroadcastBitSTDERR) { // The process has stderr available, get it and write it out to the // appropriate place. - if (GetProcessSTDERR ()) - m_io_channel_ap->RefreshPrompt(); + GetProcessSTDERR (); } else if (event_type & SBProcess::eBroadcastBitStateChanged) { // Drain all stout and stderr so we don't see any output come after // we print our prompts - if (GetProcessSTDOUT () - || GetProcessSTDERR ()) - m_io_channel_ap->RefreshPrompt(); - + GetProcessSTDOUT (); + GetProcessSTDERR (); // Something changed in the process; get the event and report the process's current status and location to // the user. StateType event_state = SBProcess::GetStateFromEvent (event); @@ -795,7 +791,7 @@ Driver::HandleProcessEvent (const SBEvent &event) char message[1024]; int message_len = ::snprintf (message, sizeof(message), "Process %d %s\n", process.GetProcessID(), m_debugger.StateAsCString (event_state)); - m_io_channel_ap->OutWrite(message, message_len); + m_io_channel_ap->OutWrite(message, message_len, ASYNC); } break; @@ -807,9 +803,8 @@ Driver::HandleProcessEvent (const SBEvent &event) { SBCommandReturnObject result; m_debugger.GetCommandInterpreter().HandleCommand("process status", result, false); - m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize()); - m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize()); - m_io_channel_ap->RefreshPrompt(); + m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize(), ASYNC); + m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize(), ASYNC); } break; @@ -823,17 +818,15 @@ Driver::HandleProcessEvent (const SBEvent &event) char message[1024]; int message_len = ::snprintf (message, sizeof(message), "Process %d stopped and was programmatically restarted.\n", process.GetProcessID()); - m_io_channel_ap->OutWrite(message, message_len); - m_io_channel_ap->RefreshPrompt (); + m_io_channel_ap->OutWrite(message, message_len, ASYNC); } else { SBCommandReturnObject result; UpdateSelectedThread (); m_debugger.GetCommandInterpreter().HandleCommand("process status", result, false); - m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize()); - m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize()); - m_io_channel_ap->RefreshPrompt (); + m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize(), ASYNC); + m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize(), ASYNC); } break; } @@ -935,11 +928,16 @@ Driver::EditLineInputReaderCallback case eInputReaderDeactivate: break; + + case eInputReaderAsynchronousOutputWritten: + if (driver->m_io_channel_ap.get() != NULL) + driver->m_io_channel_ap->RefreshPrompt(); + break; case eInputReaderInterrupt: if (driver->m_io_channel_ap.get() != NULL) { - driver->m_io_channel_ap->OutWrite ("^C\n", 3); + driver->m_io_channel_ap->OutWrite ("^C\n", 3, NO_ASYNC); driver->m_io_channel_ap->RefreshPrompt(); } break; @@ -947,7 +945,7 @@ Driver::EditLineInputReaderCallback case eInputReaderEndOfFile: if (driver->m_io_channel_ap.get() != NULL) { - driver->m_io_channel_ap->OutWrite ("^D\n", 3); + driver->m_io_channel_ap->OutWrite ("^D\n", 3, NO_ASYNC); driver->m_io_channel_ap->RefreshPrompt (); } write (driver->m_editline_pty.GetMasterFileDescriptor(), "quit\n", 5); @@ -996,6 +994,36 @@ Driver::MainLoop () } } + lldb_utility::PseudoTerminal editline_output_pty; + FILE *editline_output_slave_fh = NULL; + + if (editline_output_pty.OpenFirstAvailableMaster (O_RDWR|O_NOCTTY, error_str, sizeof (error_str)) == false) + { + ::fprintf (stderr, "error: failed to open output pseudo terminal : %s", error_str); + exit(1); + } + else + { + const char *output_slave_name = editline_output_pty.GetSlaveName (error_str, sizeof(error_str)); + if (output_slave_name == NULL) + { + ::fprintf (stderr, "error: failed to get slave name for output pseudo terminal : %s", error_str); + exit(2); + } + else + { + editline_output_slave_fh = ::fopen (output_slave_name, "r+"); + if (editline_output_slave_fh == NULL) + { + SBError error; + error.SetErrorToErrno(); + ::fprintf (stderr, "error: failed to get open slave for output pseudo terminal : %s", + error.GetCString()); + exit(3); + } + ::setbuf (editline_output_slave_fh, NULL); + } + } // struct termios stdin_termios; @@ -1038,7 +1066,19 @@ Driver::MainLoop () // SBCommandInterpreter sb_interpreter = m_debugger.GetCommandInterpreter(); - m_io_channel_ap.reset (new IOChannel(m_editline_slave_fh, stdout, stderr, this)); + m_io_channel_ap.reset (new IOChannel(m_editline_slave_fh, editline_output_slave_fh, stdout, stderr, this)); + + SBCommunication out_comm_2("driver.editline_output"); + out_comm_2.SetCloseOnEOF (false); + out_comm_2.AdoptFileDesriptor (editline_output_pty.GetMasterFileDescriptor(), false); + out_comm_2.SetReadThreadBytesReceivedCallback (IOChannel::LibeditOutputBytesReceived, m_io_channel_ap.get()); + + if (out_comm_2.ReadThreadStart () == false) + { + ::fprintf (stderr, "error: failed to start libedit output read thread"); + exit (5); + } + struct winsize window_size; if (isatty (STDIN_FILENO) diff --git a/lldb/tools/driver/Driver.h b/lldb/tools/driver/Driver.h index ffeef8951c938..1c09e63a77cc2 100644 --- a/lldb/tools/driver/Driver.h +++ b/lldb/tools/driver/Driver.h @@ -23,6 +23,8 @@ #include "lldb/API/SBError.h" #include "lldb/API/SBInputReader.h" +#define ASYNC true +#define NO_ASYNC false class IOChannel; diff --git a/lldb/tools/driver/IOChannel.cpp b/lldb/tools/driver/IOChannel.cpp index 9bf2b8e583fd1..6c2c3d9cc9832 100644 --- a/lldb/tools/driver/IOChannel.cpp +++ b/lldb/tools/driver/IOChannel.cpp @@ -29,10 +29,6 @@ typedef std::map PromptMap; const char *g_default_prompt = "(lldb) "; PromptMap g_prompt_map; -#define NSEC_PER_USEC 1000ull -#define USEC_PER_SEC 1000000ull -#define NSEC_PER_SEC 1000000000ull - static const char* el_prompt(EditLine *el) { @@ -100,16 +96,16 @@ IOChannel::HandleCompletion (EditLine *e, int ch) const char *comment = "\nAvailable completions:"; int num_elements = num_completions + 1; - OutWrite(comment, strlen (comment)); + OutWrite(comment, strlen (comment), NO_ASYNC); if (num_completions < page_size) { for (int i = 1; i < num_elements; i++) { completion_str = completions.GetStringAtIndex(i); - OutWrite("\n\t", 2); - OutWrite(completion_str, strlen (completion_str)); + OutWrite("\n\t", 2, NO_ASYNC); + OutWrite(completion_str, strlen (completion_str), NO_ASYNC); } - OutWrite ("\n", 1); + OutWrite ("\n", 1, NO_ASYNC); } else { @@ -124,17 +120,17 @@ IOChannel::HandleCompletion (EditLine *e, int ch) for (; cur_pos < endpoint; cur_pos++) { completion_str = completions.GetStringAtIndex(cur_pos); - OutWrite("\n\t", 2); - OutWrite(completion_str, strlen (completion_str)); + OutWrite("\n\t", 2, NO_ASYNC); + OutWrite(completion_str, strlen (completion_str), NO_ASYNC); } if (cur_pos >= num_elements) { - OutWrite("\n", 1); + OutWrite("\n", 1, NO_ASYNC); break; } - OutWrite("\nMore (Y/n/a): ", strlen ("\nMore (Y/n/a): ")); + OutWrite("\nMore (Y/n/a): ", strlen ("\nMore (Y/n/a): "), NO_ASYNC); reply = 'n'; got_char = el_getc(m_edit_line, &reply); if (got_char == -1 || reply == 'n') @@ -154,8 +150,9 @@ IOChannel::HandleCompletion (EditLine *e, int ch) IOChannel::IOChannel ( - FILE *in, - FILE *out, + FILE *editline_in, + FILE *editline_out, + FILE *out, FILE *err, Driver *driver ) : @@ -169,10 +166,12 @@ IOChannel::IOChannel m_err_file (err), m_command_queue (), m_completion_key ("\t"), - m_edit_line (::el_init (SBHostOS::GetProgramFileSpec().GetFilename(), in, out, err)), + m_edit_line (::el_init (SBHostOS::GetProgramFileSpec().GetFilename(), editline_in, editline_out, editline_out)), m_history (history_init()), m_history_event(), - m_getting_command (false) + m_getting_command (false), + m_expecting_prompt (false), + m_prompt_str () { assert (m_edit_line); ::el_set (m_edit_line, EL_PROMPT, el_prompt); @@ -252,6 +251,36 @@ IOChannel::HistorySaveLoad (bool save) } } +void +IOChannel::LibeditOutputBytesReceived (void *baton, const void *src, size_t src_len) +{ + // Make this a member variable. + // static std::string prompt_str; + IOChannel *io_channel = (IOChannel *) baton; + const char *bytes = (const char *) src; + + if (io_channel->IsGettingCommand() && io_channel->m_expecting_prompt) + { + io_channel->m_prompt_str.append (bytes, src_len); + // Log this to make sure the prompt is really what you think it is. + if (io_channel->m_prompt_str.find (el_prompt(io_channel->m_edit_line)) == 0) + { + io_channel->m_expecting_prompt = false; + io_channel->OutWrite (io_channel->m_prompt_str.c_str(), + io_channel->m_prompt_str.size(), NO_ASYNC); + io_channel->m_prompt_str.clear(); + } + else + assert (io_channel->m_prompt_str.find (el_prompt(io_channel->m_edit_line)) == std::string::npos); + } + else + { + if (io_channel->m_prompt_str.size() > 0) + io_channel->m_prompt_str.clear(); + io_channel->OutWrite (bytes, src_len, NO_ASYNC); + } +} + bool IOChannel::LibeditGetInput (std::string &new_line) { @@ -262,12 +291,7 @@ IOChannel::LibeditGetInput (std::string &new_line) // Set boolean indicating whether or not el_gets is trying to get input (i.e. whether or not to attempt // to refresh the prompt after writing data). SetGettingCommand (true); - - // Get the current time just before calling el_gets; this is used by OutWrite, ErrWrite, and RefreshPrompt - // to make sure they have given el_gets enough time to write the prompt before they attempt to write - // anything. - - ::gettimeofday (&m_enter_elgets_time, NULL); + m_expecting_prompt = true; // Call el_gets to prompt the user and read the user's input. const char *line = ::el_gets (m_edit_line, &line_len); @@ -318,7 +342,9 @@ IOChannel::Run () listener.StartListeningForEvents (interpreter_broadcaster, SBCommandInterpreter::eBroadcastBitResetPrompt | SBCommandInterpreter::eBroadcastBitThreadShouldExit | - SBCommandInterpreter::eBroadcastBitQuitCommandReceived); + SBCommandInterpreter::eBroadcastBitQuitCommandReceived | + SBCommandInterpreter::eBroadcastBitAsynchronousOutputData | + SBCommandInterpreter::eBroadcastBitAsynchronousErrorData); listener.StartListeningForEvents (*this, IOChannel::eBroadcastBitThreadShouldExit); @@ -392,6 +418,18 @@ IOChannel::Run () case SBCommandInterpreter::eBroadcastBitQuitCommandReceived: done = true; break; + case SBCommandInterpreter::eBroadcastBitAsynchronousErrorData: + { + const char *data = SBEvent::GetCStringFromEvent (event); + ErrWrite (data, strlen(data), ASYNC); + } + break; + case SBCommandInterpreter::eBroadcastBitAsynchronousOutputData: + { + const char *data = SBEvent::GetCStringFromEvent (event); + OutWrite (data, strlen(data), ASYNC); + } + break; } } else if (event.BroadcasterMatchesPtr (this)) @@ -448,60 +486,41 @@ IOChannel::RefreshPrompt () if (! IsGettingCommand()) return; - // Compare the current time versus the last time el_gets was called. If less than 40 milliseconds - // (40,0000 microseconds or 40,000,0000 nanoseconds) have elapsed, wait 40,0000 microseconds, to ensure el_gets had - // time to finish writing the prompt before we start writing here. - - if (ElapsedNanoSecondsSinceEnteringElGets() < (40 * 1000 * 1000)) - usleep (40 * 1000); - - // Use the mutex to make sure OutWrite, ErrWrite and Refresh prompt do not interfere with - // each other's output. + // If we haven't finished writing the prompt, there's no need to refresh it. + if (m_expecting_prompt) + return; - IOLocker locker (m_output_mutex); ::el_set (m_edit_line, EL_REFRESH); } void -IOChannel::OutWrite (const char *buffer, size_t len) +IOChannel::OutWrite (const char *buffer, size_t len, bool asynchronous) { if (len == 0) return; - // Compare the current time versus the last time el_gets was called. If less than - // 10000 microseconds (10000000 nanoseconds) have elapsed, wait 10000 microseconds, to ensure el_gets had time - // to finish writing the prompt before we start writing here. - - if (ElapsedNanoSecondsSinceEnteringElGets() < 10000000) - usleep (10000); - - { - // Use the mutex to make sure OutWrite, ErrWrite and Refresh prompt do not interfere with - // each other's output. - IOLocker locker (m_output_mutex); - ::fwrite (buffer, 1, len, m_out_file); - } + // Use the mutex to make sure OutWrite and ErrWrite do not interfere with each other's output. + IOLocker locker (m_output_mutex); + if (asynchronous) + ::fwrite ("\n", 1, 1, m_out_file); + ::fwrite (buffer, 1, len, m_out_file); + if (asynchronous) + m_driver->GetDebugger().NotifyTopInputReader (eInputReaderAsynchronousOutputWritten); } void -IOChannel::ErrWrite (const char *buffer, size_t len) +IOChannel::ErrWrite (const char *buffer, size_t len, bool asynchronous) { if (len == 0) return; - // Compare the current time versus the last time el_gets was called. If less than - // 10000 microseconds (10000000 nanoseconds) have elapsed, wait 10000 microseconds, to ensure el_gets had time - // to finish writing the prompt before we start writing here. - - if (ElapsedNanoSecondsSinceEnteringElGets() < 10000000) - usleep (10000); - - { - // Use the mutex to make sure OutWrite, ErrWrite and Refresh prompt do not interfere with - // each other's output. - IOLocker locker (m_output_mutex); - ::fwrite (buffer, 1, len, m_err_file); - } + // Use the mutex to make sure OutWrite and ErrWrite do not interfere with each other's output. + IOLocker locker (m_output_mutex); + if (asynchronous) + ::fwrite ("\n", 1, 1, m_err_file); + ::fwrite (buffer, 1, len, m_err_file); + if (asynchronous) + m_driver->GetDebugger().NotifyTopInputReader (eInputReaderAsynchronousOutputWritten); } void @@ -551,25 +570,6 @@ IOChannel::SetGettingCommand (bool new_value) m_getting_command = new_value; } -uint64_t -IOChannel::Nanoseconds (const struct timeval &time_val) const -{ - uint64_t nanoseconds = time_val.tv_sec * NSEC_PER_SEC + time_val.tv_usec * NSEC_PER_USEC; - - return nanoseconds; -} - -uint64_t -IOChannel::ElapsedNanoSecondsSinceEnteringElGets () -{ - if (! IsGettingCommand()) - return 0; - - struct timeval current_time; - ::gettimeofday (¤t_time, NULL); - return (Nanoseconds (current_time) - Nanoseconds (m_enter_elgets_time)); -} - IOLocker::IOLocker (pthread_mutex_t &mutex) : m_mutex_ptr (&mutex) { diff --git a/lldb/tools/driver/IOChannel.h b/lldb/tools/driver/IOChannel.h index 188e2a20975c9..886de1b5ba1f1 100644 --- a/lldb/tools/driver/IOChannel.h +++ b/lldb/tools/driver/IOChannel.h @@ -35,7 +35,8 @@ class IOChannel : public lldb::SBBroadcaster eAllEventBits = 0xffffffff }; - IOChannel (FILE *in, + IOChannel (FILE *editline_in, + FILE *editline_out, FILE *out, FILE *err, Driver *driver = NULL); @@ -56,13 +57,16 @@ class IOChannel : public lldb::SBBroadcaster Run (); void - OutWrite (const char *buffer, size_t len); + OutWrite (const char *buffer, size_t len, bool asynchronous); void - ErrWrite (const char *buffer, size_t len); + ErrWrite (const char *buffer, size_t len, bool asynchronous); bool LibeditGetInput (std::string &); + + static void + LibeditOutputBytesReceived (void *baton, const void *src,size_t src_len); void SetPrompt (); @@ -99,12 +103,6 @@ class IOChannel : public lldb::SBBroadcaster void SetGettingCommand (bool new_value); - uint64_t - Nanoseconds (const struct timeval &time_val) const; - - uint64_t - ElapsedNanoSecondsSinceEnteringElGets (); - private: pthread_mutex_t m_output_mutex; @@ -122,6 +120,8 @@ class IOChannel : public lldb::SBBroadcaster History *m_history; HistEvent m_history_event; bool m_getting_command; + bool m_expecting_prompt; + std::string m_prompt_str; // for accumlating the prompt as it gets written out by editline void HistorySaveLoad (bool save);