diff options
Diffstat (limited to 'lldb/source')
57 files changed, 8967 insertions, 3696 deletions
diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt index 560167e2853..a626f614d0b 100644 --- a/lldb/source/API/CMakeLists.txt +++ b/lldb/source/API/CMakeLists.txt @@ -21,7 +21,6 @@ add_lldb_library(lldbAPI SBFrame.cpp SBFunction.cpp SBHostOS.cpp - SBInputReader.cpp SBInstruction.cpp SBInstructionList.cpp SBLineEntry.cpp diff --git a/lldb/source/API/SBCommandInterpreter.cpp b/lldb/source/API/SBCommandInterpreter.cpp index ac77e2e4112..f1faa13ba98 100644 --- a/lldb/source/API/SBCommandInterpreter.cpp +++ b/lldb/source/API/SBCommandInterpreter.cpp @@ -107,6 +107,22 @@ SBCommandInterpreter::AliasExists (const char *cmd) return false; } +bool +SBCommandInterpreter::IsActive () +{ + if (m_opaque_ptr) + return m_opaque_ptr->IsActive (); + return false; +} + +const char * +SBCommandInterpreter::GetIOHandlerControlSequence(char ch) +{ + if (m_opaque_ptr) + return m_opaque_ptr->GetDebugger().GetTopIOHandlerControlSequence (ch).GetCString(); + return NULL; +} + lldb::ReturnStatus SBCommandInterpreter::HandleCommand (const char *command_line, SBCommandReturnObject &result, bool add_to_history) { diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp index 10c0b7dea20..8d6887a6c28 100644 --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -20,7 +20,6 @@ #include "lldb/API/SBError.h" #include "lldb/API/SBEvent.h" #include "lldb/API/SBFrame.h" -#include "lldb/API/SBInputReader.h" #include "lldb/API/SBProcess.h" #include "lldb/API/SBSourceManager.h" #include "lldb/API/SBStream.h" @@ -37,6 +36,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/State.h" +#include "lldb/Core/StreamFile.h" #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/Host/DynamicLibrary.h" #include "lldb/Interpreter/Args.h" @@ -49,6 +49,29 @@ using namespace lldb; using namespace lldb_private; +SBInputReader::SBInputReader() +{ +} +SBInputReader::~SBInputReader() +{ +} + +SBError +SBInputReader::Initialize(lldb::SBDebugger& sb_debugger, unsigned long (*)(void*, lldb::SBInputReader*, lldb::InputReaderAction, char const*, unsigned long), void*, lldb::InputReaderGranularity, char const*, char const*, bool) +{ + return SBError(); +} + +void +SBInputReader::SetIsDone(bool) +{ +} +bool +SBInputReader::IsActive() const +{ + return false; +} + static lldb::DynamicLibrarySP LoadPlugin (const lldb::DebuggerSP &debugger_sp, const FileSpec& spec, Error& error) { @@ -111,7 +134,7 @@ SBDebugger::Clear () log->Printf ("SBDebugger(%p)::Clear ()", m_opaque_sp.get()); if (m_opaque_sp) - m_opaque_sp->CleanUpInputReaders (); + m_opaque_sp->ClearIOHandlers (); m_opaque_sp.reset(); } @@ -309,7 +332,11 @@ FILE * SBDebugger::GetInputFileHandle () { if (m_opaque_sp) - return m_opaque_sp->GetInputFile().GetStream(); + { + StreamFileSP stream_file_sp (m_opaque_sp->GetInputFile()); + if (stream_file_sp) + return stream_file_sp->GetFile().GetStream(); + } return NULL; } @@ -317,7 +344,11 @@ FILE * SBDebugger::GetOutputFileHandle () { if (m_opaque_sp) - return m_opaque_sp->GetOutputFile().GetStream(); + { + StreamFileSP stream_file_sp (m_opaque_sp->GetOutputFile()); + if (stream_file_sp) + return stream_file_sp->GetFile().GetStream(); + } return NULL; } @@ -325,7 +356,12 @@ FILE * SBDebugger::GetErrorFileHandle () { if (m_opaque_sp) - return m_opaque_sp->GetErrorFile().GetStream(); + if (m_opaque_sp) + { + StreamFileSP stream_file_sp (m_opaque_sp->GetErrorFile()); + if (stream_file_sp) + return stream_file_sp->GetFile().GetStream(); + } return NULL; } @@ -885,17 +921,17 @@ SBDebugger::DispatchInput (void* baton, const void *data, size_t data_len) void SBDebugger::DispatchInput (const void *data, size_t data_len) { - Log *log(GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); - - if (log) - log->Printf ("SBDebugger(%p)::DispatchInput (data=\"%.*s\", size_t=%" PRIu64 ")", - m_opaque_sp.get(), - (int) data_len, - (const char *) data, - (uint64_t)data_len); - - if (m_opaque_sp) - m_opaque_sp->DispatchInput ((const char *) data, data_len); +// Log *log(GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); +// +// if (log) +// log->Printf ("SBDebugger(%p)::DispatchInput (data=\"%.*s\", size_t=%" PRIu64 ")", +// m_opaque_sp.get(), +// (int) data_len, +// (const char *) data, +// (uint64_t)data_len); +// +// if (m_opaque_sp) +// m_opaque_sp->DispatchInput ((const char *) data, data_len); } void @@ -911,54 +947,18 @@ SBDebugger::DispatchInputEndOfFile () if (m_opaque_sp) m_opaque_sp->DispatchInputEndOfFile (); } - -bool -SBDebugger::InputReaderIsTopReader (const lldb::SBInputReader &reader) -{ - Log *log(GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); - - if (log) - log->Printf ("SBDebugger(%p)::InputReaderIsTopReader (SBInputReader(%p))", m_opaque_sp.get(), &reader); - - if (m_opaque_sp && reader.IsValid()) - { - InputReaderSP reader_sp (*reader); - return m_opaque_sp->InputReaderIsTopReader (reader_sp); - } - - return false; -} - void SBDebugger::PushInputReader (SBInputReader &reader) { - Log *log(GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); - - if (log) - log->Printf ("SBDebugger(%p)::PushInputReader (SBInputReader(%p))", m_opaque_sp.get(), &reader); - - if (m_opaque_sp && reader.IsValid()) - { - TargetSP target_sp (m_opaque_sp->GetSelectedTarget()); - Mutex::Locker api_locker; - if (target_sp) - api_locker.Lock(target_sp->GetAPIMutex()); - InputReaderSP reader_sp(*reader); - m_opaque_sp->PushInputReader (reader_sp); - } } void -SBDebugger::NotifyTopInputReader (InputReaderAction notification) +SBDebugger::RunCommandInterpreter (bool auto_handle_events, + bool spawn_thread) { - Log *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); + m_opaque_sp->GetCommandInterpreter().RunCommandInterpreter(auto_handle_events, spawn_thread); } void @@ -1050,7 +1050,7 @@ SBDebugger::GetInternalVariableValue (const char *var_name, const char *debugger if (!value_str.empty()) { StringList string_list; - string_list.SplitIntoLines(value_str.c_str(), value_str.size()); + string_list.SplitIntoLines(value_str); return SBStringList(&string_list); } } diff --git a/lldb/source/API/SBInputReader.cpp b/lldb/source/API/SBInputReader.cpp deleted file mode 100644 index 82b75c869f0..00000000000 --- a/lldb/source/API/SBInputReader.cpp +++ /dev/null @@ -1,216 +0,0 @@ -//===-- SBInputReader.cpp ---------------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - - -#include "lldb/lldb-enumerations.h" - -#include "lldb/API/SBDebugger.h" -#include "lldb/API/SBError.h" -#include "lldb/API/SBInputReader.h" -#include "lldb/API/SBStream.h" -#include "lldb/API/SBStringList.h" -#include "lldb/Core/InputReader.h" -#include "lldb/Core/Log.h" - - -using namespace lldb; -using namespace lldb_private; - -SBInputReader::SBInputReader () : - m_opaque_sp (), - m_callback_function (NULL), - m_callback_baton (NULL) - -{ -} - -SBInputReader::SBInputReader (const lldb::InputReaderSP &reader_sp) : - m_opaque_sp (reader_sp) -{ - Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); - - if (log) - log->Printf ("SBInputReader::SBInputReader (reader_sp=%p) => SBInputReader(%p)", reader_sp.get(), - m_opaque_sp.get()); -} - -SBInputReader::SBInputReader (const SBInputReader &rhs) : - m_opaque_sp (rhs.m_opaque_sp) -{ - Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); - - if (log) - log->Printf("SBInputReader::SBInputReader (rhs.sp=%p) => SBInputReader(%p)", - rhs.m_opaque_sp.get(), m_opaque_sp.get()); -} - -SBInputReader::~SBInputReader () -{ -} - -size_t -SBInputReader::PrivateCallback -( - void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len -) -{ - SBInputReader *sb_reader = (SBInputReader *)baton; - return sb_reader->m_callback_function (sb_reader->m_callback_baton, - sb_reader, - notification, - bytes, - bytes_len); -} - -SBError -SBInputReader::Initialize -( - SBDebugger &debugger, - Callback callback_function, - void *callback_baton, - lldb::InputReaderGranularity granularity, - const char *end_token, - const char *prompt, - bool echo -) -{ - Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); - - if (log) - log->Printf("SBInputReader(%p)::Initialize (SBDebugger(%p), callback_function=%p, callback_baton=%p, " - "granularity=%s, end_token=\"%s\", prompt=\"%s\", echo=%i)", - m_opaque_sp.get(), - debugger.get(), - callback_function, - callback_baton, - InputReader::GranularityAsCString (granularity), end_token, prompt, - echo); - - SBError sb_error; - m_opaque_sp.reset (new InputReader (debugger.ref())); - - m_callback_function = callback_function; - m_callback_baton = callback_baton; - - if (m_opaque_sp) - { - sb_error.SetError (m_opaque_sp->Initialize (SBInputReader::PrivateCallback, - this, - granularity, - end_token, - prompt, - echo)); - } - - if (sb_error.Fail()) - { - m_opaque_sp.reset (); - m_callback_function = NULL; - m_callback_baton = NULL; - } - - if (log) - { - SBStream sstr; - sb_error.GetDescription (sstr); - log->Printf ("SBInputReader(%p)::Initialize (...) => SBError(%p): %s", m_opaque_sp.get(), - sb_error.get(), sstr.GetData()); - } - - return sb_error; -} - -bool -SBInputReader::IsValid () const -{ - return (m_opaque_sp.get() != NULL); -} - -const SBInputReader & -SBInputReader::operator = (const SBInputReader &rhs) -{ - if (this != &rhs) - m_opaque_sp = rhs.m_opaque_sp; - return *this; -} - -InputReader * -SBInputReader::operator->() const -{ - return m_opaque_sp.get(); -} - -lldb::InputReaderSP & -SBInputReader::operator *() -{ - return m_opaque_sp; -} - -const lldb::InputReaderSP & -SBInputReader::operator *() const -{ - return m_opaque_sp; -} - -InputReader * -SBInputReader::get() const -{ - return m_opaque_sp.get(); -} - -InputReader & -SBInputReader::ref() const -{ - assert (m_opaque_sp.get()); - return *m_opaque_sp; -} - -bool -SBInputReader::IsDone () const -{ - if (m_opaque_sp) - return m_opaque_sp->IsDone(); - else - return true; -} - -void -SBInputReader::SetIsDone (bool value) -{ - if (m_opaque_sp) - m_opaque_sp->SetIsDone (value); -} - -bool -SBInputReader::IsActive () const -{ - Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); - - bool ret_value = false; - if (m_opaque_sp) - ret_value = m_opaque_sp->IsActive(); - - if (log) - log->Printf ("SBInputReader(%p)::IsActive () => %i", m_opaque_sp.get(), ret_value); - - return ret_value; -} - -InputReaderGranularity -SBInputReader::GetGranularity () -{ - if (m_opaque_sp) - return m_opaque_sp->GetGranularity(); - else - return eInputReaderGranularityInvalid; -} diff --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt index 1f287d57243..cee8146bee2 100644 --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -10,6 +10,7 @@ add_lldb_library(lldbCommands CommandObjectDisassemble.cpp CommandObjectExpression.cpp CommandObjectFrame.cpp + CommandObjectGUI.cpp CommandObjectHelp.cpp CommandObjectLog.cpp CommandObjectMemory.cpp diff --git a/lldb/source/Commands/CommandObjectBreakpointCommand.cpp b/lldb/source/Commands/CommandObjectBreakpointCommand.cpp index e540461dada..532d6cedc83 100644 --- a/lldb/source/Commands/CommandObjectBreakpointCommand.cpp +++ b/lldb/source/Commands/CommandObjectBreakpointCommand.cpp @@ -16,6 +16,7 @@ #include "CommandObjectBreakpointCommand.h" #include "CommandObjectBreakpoint.h" +#include "lldb/Core/IOHandler.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Target.h" @@ -34,7 +35,9 @@ using namespace lldb_private; //------------------------------------------------------------------------- -class CommandObjectBreakpointCommandAdd : public CommandObjectParsed +class CommandObjectBreakpointCommandAdd : + public CommandObjectParsed, + public IOHandlerDelegateMultiline { public: @@ -43,6 +46,7 @@ public: "add", "Add a set of commands to a breakpoint, to be executed whenever the breakpoint is hit.", NULL), + IOHandlerDelegateMultiline ("DONE", IOHandlerDelegate::Completion::LLDBCommand), m_options (interpreter) { SetHelpLong ( @@ -207,42 +211,47 @@ one command per line.\n" ); return &m_options; } - void - CollectDataForBreakpointCommandCallback (BreakpointOptions *bp_options, - CommandReturnObject &result) + virtual void + IOHandlerActivated (IOHandler &io_handler) { - InputReaderSP reader_sp (new InputReader(m_interpreter.GetDebugger())); - std::unique_ptr<BreakpointOptions::CommandData> data_ap(new BreakpointOptions::CommandData()); - if (reader_sp && data_ap.get()) + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) { - BatonSP baton_sp (new BreakpointOptions::CommandBaton (data_ap.release())); - bp_options->SetCallback (BreakpointOptionsCallbackFunction, baton_sp); - - Error err (reader_sp->Initialize (CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback, - bp_options, // baton - eInputReaderGranularityLine, // token size, to pass to callback function - "DONE", // end token - "> ", // prompt - true)); // echo input - if (err.Success()) - { - m_interpreter.GetDebugger().PushInputReader (reader_sp); - result.SetStatus (eReturnStatusSuccessFinishNoResult); - } - else - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - } + output_sp->PutCString(g_reader_instructions); + output_sp->Flush(); } - else + } + + + virtual void + IOHandlerInputComplete (IOHandler &io_handler, std::string &line) + { + io_handler.SetIsDone(true); + + BreakpointOptions *bp_options = (BreakpointOptions *) io_handler.GetUserData(); + if (bp_options) { - result.AppendError("out of memory"); - result.SetStatus (eReturnStatusFailed); + std::unique_ptr<BreakpointOptions::CommandData> data_ap(new BreakpointOptions::CommandData()); + if (data_ap.get()) + { + data_ap->user_source.SplitIntoLines (line.c_str(), line.size()); + BatonSP baton_sp (new BreakpointOptions::CommandBaton (data_ap.release())); + bp_options->SetCallback (BreakpointOptionsCallbackFunction, baton_sp); + } } } + void + CollectDataForBreakpointCommandCallback (BreakpointOptions *bp_options, + CommandReturnObject &result) + { + m_interpreter.GetLLDBCommandsFromIOHandler ("> ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + bp_options); // Baton for the "io_handler" that will be passed back into our IOHandlerDelegate functions + } + /// Set a one-liner as the callback for the breakpoint. void SetBreakpointCommandCallback (BreakpointOptions *bp_options, @@ -262,93 +271,6 @@ one command per line.\n" ); return; } - - static size_t - GenerateBreakpointCommandCallback (void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - - switch (notification) - { - case eInputReaderActivate: - if (!batch_mode) - { - out_stream->Printf ("%s\n", g_reader_instructions); - if (reader.GetPrompt()) - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderReactivate: - if (reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderGotToken: - if (bytes && bytes_len && baton) - { - BreakpointOptions *bp_options = (BreakpointOptions *) baton; - if (bp_options) - { - Baton *bp_options_baton = bp_options->GetBaton(); - if (bp_options_baton) - ((BreakpointOptions::CommandData *)bp_options_baton->m_data)->user_source.AppendString (bytes, bytes_len); - } - } - if (!reader.IsDone() && reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - break; - - case eInputReaderInterrupt: - { - // Finish, and cancel the breakpoint command. - reader.SetIsDone (true); - BreakpointOptions *bp_options = (BreakpointOptions *) baton; - if (bp_options) - { - Baton *bp_options_baton = bp_options->GetBaton (); - if (bp_options_baton) - { - ((BreakpointOptions::CommandData *) bp_options_baton->m_data)->user_source.Clear(); - ((BreakpointOptions::CommandData *) bp_options_baton->m_data)->script_source.clear(); - } - } - if (!batch_mode) - { - out_stream->Printf ("Warning: No command attached to breakpoint.\n"); - out_stream->Flush(); - } - } - break; - - case eInputReaderEndOfFile: - reader.SetIsDone (true); - break; - - case eInputReaderDone: - break; - } - - return bytes_len; - } static bool BreakpointOptionsCallbackFunction (void *baton, @@ -623,7 +545,7 @@ private: }; const char * -CommandObjectBreakpointCommandAdd::g_reader_instructions = "Enter your debugger command(s). Type 'DONE' to end."; +CommandObjectBreakpointCommandAdd::g_reader_instructions = "Enter your debugger command(s). Type 'DONE' to end.\n"; // FIXME: "script-type" needs to have its contents determined dynamically, so somebody can add a new scripting // language to lldb and have it pickable here without having to change this enumeration by hand and rebuild lldb proper. diff --git a/lldb/source/Commands/CommandObjectBreakpointCommand.h b/lldb/source/Commands/CommandObjectBreakpointCommand.h index afedb7602cd..e9179077951 100644 --- a/lldb/source/Commands/CommandObjectBreakpointCommand.h +++ b/lldb/source/Commands/CommandObjectBreakpointCommand.h @@ -19,7 +19,6 @@ #include "lldb/lldb-types.h" #include "lldb/Interpreter/Options.h" -#include "lldb/Core/InputReader.h" #include "lldb/Interpreter/CommandObject.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/CommandObjectMultiword.h" diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp index 6824ead0d9e..9cd89b4870b 100644 --- a/lldb/source/Commands/CommandObjectCommands.cpp +++ b/lldb/source/Commands/CommandObjectCommands.cpp @@ -18,8 +18,7 @@ // Project includes #include "lldb/Core/Debugger.h" -#include "lldb/Core/InputReader.h" -#include "lldb/Core/InputReaderEZ.h" +#include "lldb/Core/IOHandler.h" #include "lldb/Core/StringList.h" #include "lldb/Interpreter/Args.h" #include "lldb/Interpreter/CommandHistory.h" @@ -379,7 +378,8 @@ protected: { const char *filename = command.GetArgumentAtIndex(0); - result.AppendMessageWithFormat ("Executing commands in '%s'.\n", filename); + if (!m_interpreter.GetDebugger().GetCommandInterpreter().GetBatchCommandMode()) + result.AppendMessageWithFormat ("Executing commands in '%s'.\n", filename); FileSpec cmd_file (filename, true); ExecutionContext *exe_ctx = NULL; // Just use the default context. @@ -423,7 +423,7 @@ CommandObjectCommandsSource::CommandOptions::g_option_table[] = static const char *g_python_command_instructions = "Enter your Python command(s). Type 'DONE' to end.\n" "You must define a Python function with this signature:\n" - "def my_command_impl(debugger, args, result, internal_dict):"; + "def my_command_impl(debugger, args, result, internal_dict):\n"; class CommandObjectCommandsAlias : public CommandObjectRaw @@ -856,7 +856,9 @@ protected: //------------------------------------------------------------------------- #pragma mark CommandObjectCommandsAddRegex -class CommandObjectCommandsAddRegex : public CommandObjectParsed +class CommandObjectCommandsAddRegex : + public CommandObjectParsed, + public IOHandlerDelegate { public: CommandObjectCommandsAddRegex (CommandInterpreter &interpreter) : @@ -864,6 +866,7 @@ public: "command regex", "Allow the user to create a regular expression command.", "command regex <cmd-name> [s/<regex>/<subst>/ ...]"), + IOHandlerDelegate(IOHandlerDelegate::Completion::LLDBCommand), m_options (interpreter) { SetHelpLong( @@ -899,6 +902,97 @@ public: protected: + + virtual void + IOHandlerActivated (IOHandler &io_handler) + { + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) + { + output_sp->PutCString("Enter one of more sed substitution commands in the form: 's/<regex>/<subst>/'.\nTerminate the substitution list with an empty line.\n"); + output_sp->Flush(); + } + } + + virtual void + IOHandlerInputComplete (IOHandler &io_handler, std::string &data) + { + io_handler.SetIsDone(true); + if (m_regex_cmd_ap.get()) + { + StringList lines; + if (lines.SplitIntoLines (data)) + { + const size_t num_lines = lines.GetSize(); + bool check_only = false; + for (size_t i=0; i<num_lines; ++i) + { + printf ("regex[%zu] = %s\n", i, lines[i].c_str()); + llvm::StringRef bytes_strref (lines[i]); + Error error = AppendRegexSubstitution (bytes_strref, check_only); + if (error.Fail()) + { + if (!m_interpreter.GetDebugger().GetCommandInterpreter().GetBatchCommandMode()) + { + StreamSP out_stream = m_interpreter.GetDebugger().GetAsyncOutputStream(); + out_stream->Printf("error: %s\n", error.AsCString()); + } + } + } + } + if (m_regex_cmd_ap->HasRegexEntries()) + { + CommandObjectSP cmd_sp (m_regex_cmd_ap.release()); + m_interpreter.AddCommand(cmd_sp->GetCommandName(), cmd_sp, true); + } + } + } + + virtual LineStatus + IOHandlerLinesUpdated (IOHandler &io_handler, + StringList &lines, + uint32_t line_idx, + Error &error) + { + if (line_idx == UINT32_MAX) + { + // Return true to indicate we are done getting lines (this + // is a "fake" line - the real terminating blank line was + // removed during a previous call with the code below) + error.Clear(); + return LineStatus::Done; + } + else + { + const size_t num_lines = lines.GetSize(); + if (line_idx + 1 == num_lines) + { + // The last line was edited, if this line is empty, then we are done + // getting our multiple lines. + if (lines[line_idx].empty()) + { + // Remove the last empty line from "lines" so it doesn't appear + // in our final expression and return true to indicate we are done + // getting lines + lines.PopBack(); + return LineStatus::Done; + } + } + // Check the current line to make sure it is formatted correctly + bool check_only = true; + llvm::StringRef regex_sed(lines[line_idx]); + error = AppendRegexSubstitution (regex_sed, check_only); + if (error.Fail()) + { + return LineStatus::Error; + } + else + { + return LineStatus::Success; + } + } + } + bool DoExecute (Args& command, CommandReturnObject &result) { @@ -920,21 +1014,18 @@ protected: if (argc == 1) { - InputReaderSP reader_sp (new InputReader(m_interpreter.GetDebugger())); - if (reader_sp) + Debugger &debugger = m_interpreter.GetDebugger(); + const bool multiple_lines = true; // Get multiple lines + IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger, + "lldb", // Name of input reader for history + "\033[K> ", // Prompt and clear line + multiple_lines, + *this)); + + if (io_handler_sp) { - error =reader_sp->Initialize (CommandObjectCommandsAddRegex::InputReaderCallback, - this, // baton - eInputReaderGranularityLine, // token size, to pass to callback function - NULL, // end token - "> ", // prompt - true); // echo input - if (error.Success()) - { - m_interpreter.GetDebugger().PushInputReader (reader_sp); - result.SetStatus (eReturnStatusSuccessFinishNoResult); - return true; - } + debugger.PushIOHandler(io_handler_sp); + result.SetStatus (eReturnStatusSuccessFinishNoResult); } } else @@ -942,7 +1033,8 @@ protected: for (size_t arg_idx = 1; arg_idx < argc; ++arg_idx) { llvm::StringRef arg_strref (command.GetArgumentAtIndex(arg_idx)); - error = AppendRegexSubstitution (arg_strref); + bool check_only = false; + error = AppendRegexSubstitution (arg_strref, check_only); if (error.Fail()) break; } @@ -963,7 +1055,7 @@ protected: } Error - AppendRegexSubstitution (const llvm::StringRef ®ex_sed) + AppendRegexSubstitution (const llvm::StringRef ®ex_sed, bool check_only) { Error error; @@ -1053,10 +1145,14 @@ protected: regex_sed.data()); return error; } - std::string regex(regex_sed.substr(first_separator_char_pos + 1, second_separator_char_pos - first_separator_char_pos - 1)); - std::string subst(regex_sed.substr(second_separator_char_pos + 1, third_separator_char_pos - second_separator_char_pos - 1)); - m_regex_cmd_ap->AddRegexCommand (regex.c_str(), - subst.c_str()); + + if (check_only == false) + { + std::string regex(regex_sed.substr(first_separator_char_pos + 1, second_separator_char_pos - first_separator_char_pos - 1)); + std::string subst(regex_sed.substr(second_separator_char_pos + 1, third_separator_char_pos - second_separator_char_pos - 1)); + m_regex_cmd_ap->AddRegexCommand (regex.c_str(), + subst.c_str()); + } return error; } @@ -1073,89 +1169,6 @@ protected: } } - void - InputReaderDidCancel() - { - m_regex_cmd_ap.reset(); - } - - static size_t - InputReaderCallback (void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len) - { - CommandObjectCommandsAddRegex *add_regex_cmd = (CommandObjectCommandsAddRegex *) baton; - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - - switch (notification) - { - case eInputReaderActivate: - if (!batch_mode) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream (); - out_stream->Printf("%s\n", "Enter regular expressions in the form 's/<regex>/<subst>/' and terminate with an empty line:"); - out_stream->Flush(); - } - break; - case eInputReaderReactivate: - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderGotToken: - while (bytes_len > 0 && (bytes[bytes_len-1] == '\r' || bytes[bytes_len-1] == '\n')) - --bytes_len; - if (bytes_len == 0) - reader.SetIsDone(true); - else if (bytes) - { - llvm::StringRef bytes_strref (bytes, bytes_len); - Error error (add_regex_cmd->AppendRegexSubstitution (bytes_strref)); - if (error.Fail()) - { - if (!batch_mode) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - out_stream->Printf("error: %s\n", error.AsCString()); - out_stream->Flush(); - } - add_regex_cmd->InputReaderDidCancel (); - reader.SetIsDone (true); - } - } - break; - - case eInputReaderInterrupt: - { - reader.SetIsDone (true); - if (!batch_mode) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - out_stream->PutCString("Regular expression command creations was cancelled.\n"); - out_stream->Flush(); - } - add_regex_cmd->InputReaderDidCancel (); - } - break; - - case eInputReaderEndOfFile: - reader.SetIsDone (true); - break; - - case eInputReaderDone: - add_regex_cmd->AddRegexCommandToInterpreter(); - break; - } - - return bytes_len; - } - private: std::unique_ptr<CommandObjectRegexCommand> m_regex_cmd_ap; @@ -1526,7 +1539,9 @@ CommandObjectCommandsScriptImport::CommandOptions::g_option_table[] = // CommandObjectCommandsScriptAdd //------------------------------------------------------------------------- -class CommandObjectCommandsScriptAdd : public CommandObjectParsed +class CommandObjectCommandsScriptAdd : + public CommandObjectParsed, + public IOHandlerDelegateMultiline { public: CommandObjectCommandsScriptAdd(CommandInterpreter &interpreter) : @@ -1534,6 +1549,7 @@ public: "command script add", "Add a scripted function as an LLDB command.", NULL), + IOHandlerDelegateMultiline ("DONE"), m_options (interpreter) { CommandArgumentEntry arg1; @@ -1567,7 +1583,7 @@ protected: public: CommandOptions (CommandInterpreter &interpreter) : - Options (interpreter) + Options (interpreter) { } @@ -1586,7 +1602,7 @@ protected: m_funct_name = std::string(option_arg); break; case 's': - m_synchronous = (ScriptedCommandSynchronicity) Args::StringToOptionEnum(option_arg, g_option_table[option_idx].enum_values, 0, error); + m_synchronicity = (ScriptedCommandSynchronicity) Args::StringToOptionEnum(option_arg, g_option_table[option_idx].enum_values, 0, error); if (!error.Success()) error.SetErrorStringWithFormat ("unrecognized value for synchronicity '%s'", option_arg); break; @@ -1602,7 +1618,7 @@ protected: OptionParsingStarting () { m_funct_name = ""; - m_synchronous = eScriptedCommandSynchronicitySynchronous; + m_synchronicity = eScriptedCommandSynchronicitySynchronous; } const OptionDefinition* @@ -1618,128 +1634,81 @@ protected: // Instance variables to hold the values for command options. std::string m_funct_name; - ScriptedCommandSynchronicity m_synchronous; + ScriptedCommandSynchronicity m_synchronicity; }; -private: - class PythonAliasReader : public InputReaderEZ + virtual void + IOHandlerActivated (IOHandler &io_handler) { - private: - CommandInterpreter& m_interpreter; - std::string m_cmd_name; - ScriptedCommandSynchronicity m_synchronous; - StringList m_user_input; - DISALLOW_COPY_AND_ASSIGN (PythonAliasReader); - public: - PythonAliasReader(Debugger& debugger, - CommandInterpreter& interpreter, - std::string cmd_name, - ScriptedCommandSynchronicity synch) : - InputReaderEZ(debugger), - m_interpreter(interpreter), - m_cmd_name(cmd_name), - m_synchronous(synch), - m_user_input() - {} - - virtual - ~PythonAliasReader() + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) { + output_sp->PutCString(g_python_command_instructions); + output_sp->Flush(); } + } + + + virtual void + IOHandlerInputComplete (IOHandler &io_handler, std::string &data) + { + StreamFileSP error_sp = io_handler.GetErrorStreamFile(); - virtual void ActivateHandler(HandlerData& data) + ScriptInterpreter *interpreter = m_interpreter.GetScriptInterpreter(); + if (interpreter) { - StreamSP out_stream = data.GetOutStream(); - bool batch_mode = data.GetBatchMode(); - if (!batch_mode) - { - out_stream->Printf ("%s\n", g_python_command_instructions); - if (data.reader.GetPrompt()) - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); - } - } - virtual void ReactivateHandler(HandlerData& data) - { - StreamSP out_stream = data.GetOutStream(); - bool batch_mode = data.GetBatchMode(); - if (data.reader.GetPrompt() && !batch_mode) + StringList lines; + lines.SplitIntoLines(data); + if (lines.GetSize() > 0) { - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); - } - } - virtual void GotTokenHandler(HandlerData& data) - { - StreamSP out_stream = data.GetOutStream(); - bool batch_mode = data.GetBatchMode(); - if (data.bytes && data.bytes_len) - { - m_user_input.AppendString(data.bytes, data.bytes_len); - } - if (!data.reader.IsDone() && data.reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); + std::string funct_name_str; + if (interpreter->GenerateScriptAliasFunction (lines, funct_name_str)) + { + if (funct_name_str.empty()) + { + error_sp->Printf ("error: unable to obtain a function name, didn't add python command.\n"); + error_sp->Flush(); + } + else + { + // everything should be fine now, let's add this alias + + CommandObjectSP command_obj_sp(new CommandObjectPythonFunction (m_interpreter, + m_cmd_name, + funct_name_str.c_str(), + m_synchronicity)); + + if (!m_interpreter.AddUserCommand(m_cmd_name, command_obj_sp, true)) + { + error_sp->Printf ("error: unable to add selected command, didn't add python command.\n"); + error_sp->Flush(); + } + } + } + else + { + error_sp->Printf ("error: unable to create function, didn't add python command.\n"); + error_sp->Flush(); + } } - } - virtual void InterruptHandler(HandlerData& data) - { - StreamSP out_stream = data.GetOutStream(); - bool batch_mode = data.GetBatchMode(); - data.reader.SetIsDone (true); - if (!batch_mode) + else { - out_stream->Printf ("Warning: No script attached.\n"); - out_stream->Flush(); + error_sp->Printf ("error: empty function, didn't add python command.\n"); + error_sp->Flush(); } } - virtual void EOFHandler(HandlerData& data) - { - data.reader.SetIsDone (true); - } - virtual void DoneHandler(HandlerData& data) + else { - StreamSP out_stream = data.GetOutStream(); - - ScriptInterpreter *interpreter = data.reader.GetDebugger().GetCommandInterpreter().GetScriptInterpreter(); - if (!interpreter) - { - out_stream->Printf ("Script interpreter missing: no script attached.\n"); - out_stream->Flush(); - return; - } - std::string funct_name_str; - if (!interpreter->GenerateScriptAliasFunction (m_user_input, - funct_name_str)) - { - out_stream->Printf ("Unable to create function: no script attached.\n"); - out_stream->Flush(); - return; - } - if (funct_name_str.empty()) - { - out_stream->Printf ("Unable to obtain a function name: no script attached.\n"); - out_stream->Flush(); - return; - } - // everything should be fine now, let's add this alias - - CommandObjectSP command_obj_sp(new CommandObjectPythonFunction(m_interpreter, - m_cmd_name, - funct_name_str.c_str(), - m_synchronous)); - - if (!m_interpreter.AddUserCommand(m_cmd_name, command_obj_sp, true)) - { - out_stream->Printf ("Unable to add selected command: no script attached.\n"); - out_stream->Flush(); - return; - } + error_sp->Printf ("error: script interpreter missing, didn't add python command.\n"); + error_sp->Flush(); } - }; - + + io_handler.SetIsDone(true); + + + } + protected: bool DoExecute (Args& command, CommandReturnObject &result) @@ -1761,45 +1730,24 @@ protected: return false; } - std::string cmd_name = command.GetArgumentAtIndex(0); + // Store the command name and synchronicity in case we get multi-line input + m_cmd_name = command.GetArgumentAtIndex(0); + m_synchronicity = m_options.m_synchronicity; if (m_options.m_funct_name.empty()) { - InputReaderSP reader_sp (new PythonAliasReader (m_interpreter.GetDebugger(), - m_interpreter, - cmd_name, - m_options.m_synchronous)); - - if (reader_sp) - { - - InputReaderEZ::InitializationParameters ipr; - - Error err (reader_sp->Initialize (ipr.SetBaton(NULL).SetPrompt(" "))); - if (err.Success()) - { - m_interpreter.GetDebugger().PushInputReader (reader_sp); - result.SetStatus (eReturnStatusSuccessFinishNoResult); - } - else - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - } - } - else - { - result.AppendError("out of memory"); - result.SetStatus (eReturnStatusFailed); - } + m_interpreter.GetPythonCommandsFromIOHandler (" ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + NULL); // Baton for the "io_handler" that will be passed back into our IOHandlerDelegate functions } else { CommandObjectSP new_cmd(new CommandObjectPythonFunction(m_interpreter, - cmd_name, + m_cmd_name, m_options.m_funct_name, - m_options.m_synchronous)); - if (m_interpreter.AddUserCommand(cmd_name, new_cmd, true)) + m_synchronicity)); + if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) { result.SetStatus (eReturnStatusSuccessFinishNoResult); } @@ -1815,6 +1763,8 @@ protected: } CommandOptions m_options; + std::string m_cmd_name; + ScriptedCommandSynchronicity m_synchronicity; }; static OptionEnumValueElement g_script_synchro_type[] = diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp index 18d144c57a1..b3651640ec0 100644 --- a/lldb/source/Commands/CommandObjectExpression.cpp +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -17,7 +17,6 @@ // Project includes #include "lldb/Interpreter/Args.h" #include "lldb/Core/Value.h" -#include "lldb/Core/InputReader.h" #include "lldb/Core/ValueObjectVariable.h" #include "lldb/DataFormatters/ValueObjectPrinter.h" #include "lldb/Expression/ClangExpressionVariable.h" @@ -197,6 +196,7 @@ CommandObjectExpression::CommandObjectExpression (CommandInterpreter &interprete "Evaluate a C/ObjC/C++ expression in the current program context, using user defined variables and variables currently in scope.", NULL, eFlagProcessMustBePaused | eFlagTryTargetAPILock), + IOHandlerDelegate (IOHandlerDelegate::Completion::Expression), m_option_group (interpreter), m_format_options (eFormatDefault), m_command_options (), @@ -254,87 +254,6 @@ CommandObjectExpression::GetOptions () return &m_option_group; } -size_t -CommandObjectExpression::MultiLineExpressionCallback -( - void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len -) -{ - CommandObjectExpression *cmd_object_expr = (CommandObjectExpression *) baton; - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - - switch (notification) - { - case eInputReaderActivate: - if (!batch_mode) - { - StreamSP async_strm_sp(reader.GetDebugger().GetAsyncOutputStream()); - if (async_strm_sp) - { - async_strm_sp->PutCString("Enter expressions, then terminate with an empty line to evaluate:\n"); - async_strm_sp->Flush(); - } - } - // Fall through - case eInputReaderReactivate: - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderGotToken: - ++cmd_object_expr->m_expr_line_count; - if (bytes && bytes_len) - { - cmd_object_expr->m_expr_lines.append (bytes, bytes_len + 1); - } - - if (bytes_len == 0) - reader.SetIsDone(true); - break; - - case eInputReaderInterrupt: - cmd_object_expr->m_expr_lines.clear(); - reader.SetIsDone (true); - if (!batch_mode) - { - StreamSP async_strm_sp (reader.GetDebugger().GetAsyncOutputStream()); - if (async_strm_sp) - { - async_strm_sp->PutCString("Expression evaluation cancelled.\n"); - async_strm_sp->Flush(); - } - } - break; - - case eInputReaderEndOfFile: - reader.SetIsDone (true); - break; - - case eInputReaderDone: - if (cmd_object_expr->m_expr_lines.size() > 0) - { - StreamSP output_stream = reader.GetDebugger().GetAsyncOutputStream(); - StreamSP error_stream = reader.GetDebugger().GetAsyncErrorStream(); - cmd_object_expr->EvaluateExpression (cmd_object_expr->m_expr_lines.c_str(), - output_stream.get(), - error_stream.get()); - output_stream->Flush(); - error_stream->Flush(); - } - break; - } - - return bytes_len; -} - bool CommandObjectExpression::EvaluateExpression ( @@ -445,6 +364,60 @@ CommandObjectExpression::EvaluateExpression return true; } +void +CommandObjectExpression::IOHandlerActivated (IOHandler &io_handler) +{ + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) + { + output_sp->PutCString("Enter expressions, then terminate with an empty line to evaluate:\n"); + output_sp->Flush(); + } +} + + +void +CommandObjectExpression::IOHandlerInputComplete (IOHandler &io_handler, std::string &line) +{ + io_handler.SetIsDone(true); +// StreamSP output_stream = io_handler.GetDebugger().GetAsyncOutputStream(); +// StreamSP error_stream = io_handler.GetDebugger().GetAsyncErrorStream(); + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + StreamFileSP error_sp(io_handler.GetErrorStreamFile()); + + EvaluateExpression (line.c_str(), + output_sp.get(), + error_sp.get()); + if (output_sp) + output_sp->Flush(); + if (error_sp) + error_sp->Flush(); +} + +LineStatus +CommandObjectExpression::IOHandlerLinesUpdated (IOHandler &io_handler, + StringList &lines, + uint32_t line_idx, + Error &error) +{ + if (line_idx == UINT32_MAX) + { + // Remove the last line from "lines" so it doesn't appear + // in our final expression + lines.PopBack(); + error.Clear(); + return LineStatus::Done; + } + else if (line_idx + 1 == lines.GetSize()) + { + // The last line was edited, if this line is empty, then we are done + // getting our multiple lines. + if (lines[line_idx].empty()) + return LineStatus::Done; + } + return LineStatus::Success; +} + bool CommandObjectExpression::DoExecute ( @@ -461,31 +434,14 @@ CommandObjectExpression::DoExecute m_expr_lines.clear(); m_expr_line_count = 0; - InputReaderSP reader_sp (new InputReader(m_interpreter.GetDebugger())); - if (reader_sp) - { - Error err (reader_sp->Initialize (CommandObjectExpression::MultiLineExpressionCallback, - this, // baton - eInputReaderGranularityLine, // token size, to pass to callback function - NULL, // end token - NULL, // prompt - true)); // echo input - if (err.Success()) - { - m_interpreter.GetDebugger().PushInputReader (reader_sp); - result.SetStatus (eReturnStatusSuccessFinishNoResult); - } - else - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - } - } - else - { - result.AppendError("out of memory"); - result.SetStatus (eReturnStatusFailed); - } + Debugger &debugger = GetCommandInterpreter().GetDebugger(); + const bool multiple_lines = true; // Get multiple lines + IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger, + "lldb-expr", // Name of input reader for history + NULL, // No prompt + multiple_lines, + *this)); + debugger.PushIOHandler(io_handler_sp); return result.Succeeded(); } diff --git a/lldb/source/Commands/CommandObjectExpression.h b/lldb/source/Commands/CommandObjectExpression.h index e0703a22a4c..4b5162fc67f 100644 --- a/lldb/source/Commands/CommandObjectExpression.h +++ b/lldb/source/Commands/CommandObjectExpression.h @@ -14,6 +14,7 @@ // C++ Includes // Other libraries and framework includes // Project includes +#include "lldb/Core/IOHandler.h" #include "lldb/Interpreter/CommandObject.h" #include "lldb/Interpreter/OptionGroupFormat.h" #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" @@ -21,7 +22,9 @@ namespace lldb_private { -class CommandObjectExpression : public CommandObjectRaw +class CommandObjectExpression : + public CommandObjectRaw, + public IOHandlerDelegate { public: @@ -71,17 +74,26 @@ public: GetOptions (); protected: + + //------------------------------------------------------------------ + // IOHandler::Delegate functions + //------------------------------------------------------------------ + virtual void + IOHandlerActivated (IOHandler &io_handler); + + virtual void + IOHandlerInputComplete (IOHandler &io_handler, + std::string &line); + + virtual LineStatus + IOHandlerLinesUpdated (IOHandler &io_handler, + StringList &lines, + uint32_t line_idx, + Error &error); virtual bool DoExecute (const char *command, CommandReturnObject &result); - static size_t - MultiLineExpressionCallback (void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len); - bool EvaluateExpression (const char *expr, Stream *output_stream, diff --git a/lldb/source/Commands/CommandObjectGUI.cpp b/lldb/source/Commands/CommandObjectGUI.cpp new file mode 100644 index 00000000000..0fe6cdcd8b0 --- /dev/null +++ b/lldb/source/Commands/CommandObjectGUI.cpp @@ -0,0 +1,56 @@ +//===-- CommandObjectGUI.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/lldb-python.h" + +#include "CommandObjectGUI.h" + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" + +using namespace lldb; +using namespace lldb_private; + +//------------------------------------------------------------------------- +// CommandObjectGUI +//------------------------------------------------------------------------- + +CommandObjectGUI::CommandObjectGUI (CommandInterpreter &interpreter) : + CommandObjectParsed (interpreter, "gui", "Switch into the curses based GUI mode.", "gui") +{ +} + +CommandObjectGUI::~CommandObjectGUI () +{ +} + +bool +CommandObjectGUI::DoExecute (Args& args, CommandReturnObject &result) +{ + if (args.GetArgumentCount() == 0) + { + Debugger &debugger = m_interpreter.GetDebugger(); + IOHandlerSP io_handler_sp (new IOHandlerCursesGUI (debugger)); + if (io_handler_sp) + debugger.PushIOHandler(io_handler_sp); + result.SetStatus (eReturnStatusSuccessFinishResult); + } + else + { + result.AppendError("the gui command takes no arguments."); + result.SetStatus (eReturnStatusFailed); + } + return true; +} + diff --git a/lldb/source/Commands/CommandObjectGUI.h b/lldb/source/Commands/CommandObjectGUI.h new file mode 100644 index 00000000000..72ddb961c26 --- /dev/null +++ b/lldb/source/Commands/CommandObjectGUI.h @@ -0,0 +1,43 @@ +//===-- CommandObjectGUI.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_CommandObjectGUI_h_ +#define liblldb_CommandObjectGUI_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/Interpreter/CommandObject.h" + +namespace lldb_private { + +//------------------------------------------------------------------------- +// CommandObjectGUI +//------------------------------------------------------------------------- + +class CommandObjectGUI : public CommandObjectParsed +{ +public: + + CommandObjectGUI (CommandInterpreter &interpreter); + + virtual + ~CommandObjectGUI (); + +protected: + virtual bool + DoExecute (Args& args, + CommandReturnObject &result); + +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectGUI_h_ diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp index 7c72ab8c347..49a392286c6 100644 --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -532,37 +532,36 @@ protected: if (error.Success()) { + ListenerSP listener_sp (new Listener("lldb.CommandObjectProcessAttach.DoExecute.attach.hijack")); + m_options.attach_info.SetHijackListener(listener_sp); + process->HijackProcessEvents(listener_sp.get()); error = process->Attach (m_options.attach_info); if (error.Success()) { result.SetStatus (eReturnStatusSuccessContinuingNoResult); - } - else - { - result.AppendErrorWithFormat ("attach failed: %s\n", error.AsCString()); - result.SetStatus (eReturnStatusFailed); - return false; - } - // If we're synchronous, wait for the stopped event and report that. - // Otherwise just return. - // FIXME: in the async case it will now be possible to get to the command - // interpreter with a state eStateAttaching. Make sure we handle that correctly. - StateType state = process->WaitForProcessToStop (NULL); - - result.SetDidChangeProcessState (true); + StateType state = process->WaitForProcessToStop (NULL, NULL, false, listener_sp.get()); - if (state == eStateStopped) - { - result.AppendMessageWithFormat ("Process %" PRIu64 " %s\n", process->GetID(), StateAsCString (state)); - result.SetStatus (eReturnStatusSuccessFinishNoResult); + process->RestoreProcessEvents(); + + result.SetDidChangeProcessState (true); + + if (state == eStateStopped) + { + result.AppendMessageWithFormat ("Process %" PRIu64 " %s\n", process->GetID(), StateAsCString (state)); + result.SetStatus (eReturnStatusSuccessFinishNoResult); + } + else + { + result.AppendError ("attach failed: process did not stop (no such process or permission problem?)"); + process->Destroy(); + result.SetStatus (eReturnStatusFailed); + } } else { - result.AppendError ("attach failed: process did not stop (no such process or permission problem?)"); - process->Destroy(); + result.AppendErrorWithFormat ("attach failed: %s\n", error.AsCString()); result.SetStatus (eReturnStatusFailed); - return false; } } } @@ -1087,7 +1086,7 @@ protected: if (process) { - error = process->ConnectRemote (&process->GetTarget().GetDebugger().GetOutputStream(), remote_url); + error = process->ConnectRemote (process->GetTarget().GetDebugger().GetOutputFile().get(), remote_url); if (error.Fail()) { diff --git a/lldb/source/Commands/CommandObjectQuit.cpp b/lldb/source/Commands/CommandObjectQuit.cpp index d04ecdd9885..ffe2a924072 100644 --- a/lldb/source/Commands/CommandObjectQuit.cpp +++ b/lldb/source/Commands/CommandObjectQuit.cpp @@ -92,7 +92,8 @@ CommandObjectQuit::DoExecute (Args& command, CommandReturnObject &result) return false; } } - m_interpreter.BroadcastEvent (CommandInterpreter::eBroadcastBitQuitCommandReceived); + const uint32_t event_type = CommandInterpreter::eBroadcastBitQuitCommandReceived; + m_interpreter.BroadcastEvent (event_type); result.SetStatus (eReturnStatusQuit); return true; } diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index ff6a7235378..7a46d1cb782 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -19,7 +19,7 @@ // Project includes #include "lldb/Interpreter/Args.h" #include "lldb/Core/Debugger.h" -#include "lldb/Core/InputReader.h" +#include "lldb/Core/IOHandler.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/Section.h" @@ -4759,7 +4759,9 @@ private: // CommandObjectTargetStopHookAdd //------------------------------------------------------------------------- -class CommandObjectTargetStopHookAdd : public CommandObjectParsed +class CommandObjectTargetStopHookAdd : + public CommandObjectParsed, + public IOHandlerDelegateMultiline { public: @@ -4926,9 +4928,10 @@ public: CommandObjectTargetStopHookAdd (CommandInterpreter &interpreter) : CommandObjectParsed (interpreter, - "target stop-hook add ", + "target stop-hook add", "Add a hook to be executed when the target stops.", "target stop-hook add"), + IOHandlerDelegateMultiline ("DONE", IOHandlerDelegate::Completion::LLDBCommand), m_options (interpreter) { } @@ -4937,102 +4940,61 @@ public: { } - static size_t - ReadCommandsCallbackFunction (void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len) +protected: + + virtual void + IOHandlerActivated (IOHandler &io_handler) { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - Target::StopHook *new_stop_hook = ((Target::StopHook *) baton); - static bool got_interrupted; - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - - switch (notification) + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) { - case eInputReaderActivate: - if (!batch_mode) - { - out_stream->Printf ("%s\n", "Enter your stop hook command(s). Type 'DONE' to end."); - if (reader.GetPrompt()) - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - got_interrupted = false; - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderReactivate: - if (reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - got_interrupted = false; - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderGotToken: - if (bytes && bytes_len && baton) + output_sp->PutCString("Enter your stop hook command(s). Type 'DONE' to end.\n"); + output_sp->Flush(); + } + } + + + virtual void + IOHandlerInputComplete (IOHandler &io_handler, std::string &line) + { + if (m_stop_hook_sp) + { + if (line.empty()) { - StringList *commands = new_stop_hook->GetCommandPointer(); - if (commands) + StreamFileSP error_sp(io_handler.GetErrorStreamFile()); + if (error_sp) { - commands->AppendString (bytes, bytes_len); + error_sp->Printf("error: stop hook #%" PRIu64 " aborted, no commands.\n", m_stop_hook_sp->GetID()); + error_sp->Flush(); } + Target *target = m_interpreter.GetDebugger().GetSelectedTarget().get(); + if (target) + target->RemoveStopHookByID(m_stop_hook_sp->GetID()); } - if (!reader.IsDone() && reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - break; - - case eInputReaderInterrupt: + else { - // Finish, and cancel the stop hook. - new_stop_hook->GetTarget()->RemoveStopHookByID(new_stop_hook->GetID()); - if (!batch_mode) + m_stop_hook_sp->GetCommandPointer()->SplitIntoLines(line); + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) { - out_stream->Printf ("Stop hook cancelled.\n"); - out_stream->Flush(); + output_sp->Printf("Stop hook #%" PRIu64 " added.\n", m_stop_hook_sp->GetID()); + output_sp->Flush(); } - - reader.SetIsDone (true); } - got_interrupted = true; - break; - - case eInputReaderEndOfFile: - reader.SetIsDone (true); - break; - - case eInputReaderDone: - if (!got_interrupted && !batch_mode) - { - out_stream->Printf ("Stop hook #%" PRIu64 " added.\n", new_stop_hook->GetID()); - out_stream->Flush(); - } - break; + m_stop_hook_sp.reset(); } - - return bytes_len; + io_handler.SetIsDone(true); } - -protected: + bool DoExecute (Args& command, CommandReturnObject &result) { + m_stop_hook_sp.reset(); + Target *target = m_interpreter.GetDebugger().GetSelectedTarget().get(); if (target) { - Target::StopHookSP new_hook_sp; - target->AddStopHook (new_hook_sp); + Target::StopHookSP new_hook_sp = target->CreateStopHook(); // First step, make the specifier. std::unique_ptr<SymbolContextSpecifier> specifier_ap; @@ -5105,31 +5067,12 @@ protected: } else { - // Otherwise gather up the command list, we'll push an input reader and suck the data from that directly into - // the new stop hook's command string. - InputReaderSP reader_sp (new InputReader(m_interpreter.GetDebugger())); - if (!reader_sp) - { - result.AppendError("out of memory\n"); - result.SetStatus (eReturnStatusFailed); - target->RemoveStopHookByID (new_hook_sp->GetID()); - return false; - } - - Error err (reader_sp->Initialize (CommandObjectTargetStopHookAdd::ReadCommandsCallbackFunction, - new_hook_sp.get(), // baton - eInputReaderGranularityLine, // token size, to pass to callback function - "DONE", // end token - "> ", // prompt - true)); // echo input - if (!err.Success()) - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - target->RemoveStopHookByID (new_hook_sp->GetID()); - return false; - } - m_interpreter.GetDebugger().PushInputReader (reader_sp); + m_stop_hook_sp = new_hook_sp; + m_interpreter.GetLLDBCommandsFromIOHandler ("> ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + NULL); // Baton for the "io_handler" that will be passed back into our IOHandlerDelegate functions + } result.SetStatus (eReturnStatusSuccessFinishNoResult); } @@ -5143,6 +5086,7 @@ protected: } private: CommandOptions m_options; + Target::StopHookSP m_stop_hook_sp; }; OptionDefinition diff --git a/lldb/source/Commands/CommandObjectType.cpp b/lldb/source/Commands/CommandObjectType.cpp index 2aa77c6729c..caf5429084e 100644 --- a/lldb/source/Commands/CommandObjectType.cpp +++ b/lldb/source/Commands/CommandObjectType.cpp @@ -19,7 +19,7 @@ #include "lldb/Core/ConstString.h" #include "lldb/Core/Debugger.h" -#include "lldb/Core/InputReaderEZ.h" +#include "lldb/Core/IOHandler.h" #include "lldb/Core/RegularExpression.h" #include "lldb/Core/State.h" #include "lldb/Core/StringList.h" @@ -42,7 +42,6 @@ public: TypeSummaryImpl::Flags m_flags; StringList m_target_types; - StringList m_user_source; bool m_regex; @@ -74,7 +73,6 @@ public: bool m_skip_references; bool m_cascade; bool m_regex; - StringList m_user_source; StringList m_target_types; std::string m_category; @@ -88,7 +86,6 @@ public: m_skip_references(sref), m_cascade(casc), m_regex(regx), - m_user_source(), m_target_types(), m_category(catg) { @@ -100,7 +97,9 @@ public: -class CommandObjectTypeSummaryAdd : public CommandObjectParsed +class CommandObjectTypeSummaryAdd : + public CommandObjectParsed, + public IOHandlerDelegateMultiline { private: @@ -153,10 +152,6 @@ private: return &m_options; } - void - CollectPythonScript(ScriptAddOptions *options, - CommandReturnObject &result); - bool Execute_ScriptSummary (Args& command, CommandReturnObject &result); @@ -178,6 +173,146 @@ public: { } + virtual void + IOHandlerActivated (IOHandler &io_handler) + { + static const char *g_summary_addreader_instructions = "Enter your Python command(s). Type 'DONE' to end.\n" + "def function (valobj,internal_dict):\n" + " \"\"\"valobj: an SBValue which you want to provide a summary for\n" + " internal_dict: an LLDB support object not to be used\"\"\""; + + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) + { + output_sp->PutCString(g_summary_addreader_instructions); + output_sp->Flush(); + } + } + + + virtual void + IOHandlerInputComplete (IOHandler &io_handler, std::string &data) + { + StreamFileSP error_sp = io_handler.GetErrorStreamFile(); + + ScriptInterpreter *interpreter = m_interpreter.GetScriptInterpreter(); + if (interpreter) + { + StringList lines; + lines.SplitIntoLines(data); + if (lines.GetSize() > 0) + { + ScriptAddOptions *options_ptr = ((ScriptAddOptions*)io_handler.GetUserData()); + if (options_ptr) + { + ScriptAddOptions::SharedPointer options(options_ptr); // this will ensure that we get rid of the pointer when going out of scope + + ScriptInterpreter *interpreter = m_interpreter.GetScriptInterpreter(); + if (interpreter) + { + std::string funct_name_str; + if (interpreter->GenerateTypeScriptFunction (lines, funct_name_str)) + { + if (funct_name_str.empty()) + { + error_sp->Printf ("unable to obtain a valid function name from the script interpreter.\n"); + error_sp->Flush(); + } + else + { + // now I have a valid function name, let's add this as script for every type in the list + + TypeSummaryImplSP script_format; + script_format.reset(new ScriptSummaryFormat(options->m_flags, + funct_name_str.c_str(), + lines.CopyList(" ").c_str())); + + Error error; + + for (size_t i = 0; i < options->m_target_types.GetSize(); i++) + { + const char *type_name = options->m_target_types.GetStringAtIndex(i); + CommandObjectTypeSummaryAdd::AddSummary(ConstString(type_name), + script_format, + (options->m_regex ? CommandObjectTypeSummaryAdd::eRegexSummary : CommandObjectTypeSummaryAdd::eRegularSummary), + options->m_category, + &error); + if (error.Fail()) + { + error_sp->Printf ("error: %s", error.AsCString()); + error_sp->Flush(); + } + } + + if (options->m_name) + { + CommandObjectTypeSummaryAdd::AddSummary (options->m_name, + script_format, + CommandObjectTypeSummaryAdd::eNamedSummary, + options->m_category, + &error); + if (error.Fail()) + { + CommandObjectTypeSummaryAdd::AddSummary (options->m_name, + script_format, + CommandObjectTypeSummaryAdd::eNamedSummary, + options->m_category, + &error); + if (error.Fail()) + { + error_sp->Printf ("error: %s", error.AsCString()); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: %s", error.AsCString()); + error_sp->Flush(); + } + } + else + { + if (error.AsCString()) + { + error_sp->Printf ("error: %s", error.AsCString()); + error_sp->Flush(); + } + } + } + } + else + { + error_sp->Printf ("error: unable to generate a function.\n"); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: no script interpreter.\n"); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: internal synchronization information missing or invalid.\n"); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: empty function, didn't add python command.\n"); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: script interpreter missing, didn't add python command.\n"); + error_sp->Flush(); + } + + io_handler.SetIsDone(true); + } + static bool AddSummary(ConstString type_name, lldb::TypeSummaryImplSP entry, @@ -190,7 +325,19 @@ protected: }; -class CommandObjectTypeSynthAdd : public CommandObjectParsed +static const char *g_synth_addreader_instructions = "Enter your Python command(s). Type 'DONE' to end.\n" +"You must define a Python class with these methods:\n" +" def __init__(self, valobj, dict):\n" +" def num_children(self):\n" +" def get_child_at_index(self, index):\n" +" def get_child_index(self, name):\n" +" def update(self):\n" +" '''Optional'''\n" +"class synthProvider:\n"; + +class CommandObjectTypeSynthAdd : + public CommandObjectParsed, + public IOHandlerDelegateMultiline { private: @@ -200,7 +347,7 @@ private: public: CommandOptions (CommandInterpreter &interpreter) : - Options (interpreter) + Options (interpreter) { } @@ -296,9 +443,6 @@ private: return &m_options; } - void - CollectPythonScript (SynthAddOptions *options, - CommandReturnObject &result); bool Execute_HandwritePython (Args& command, CommandReturnObject &result); @@ -307,8 +451,137 @@ private: protected: bool - DoExecute (Args& command, CommandReturnObject &result); + DoExecute (Args& command, CommandReturnObject &result) + { + if (m_options.handwrite_python) + return Execute_HandwritePython(command, result); + else if (m_options.is_class_based) + return Execute_PythonClass(command, result); + else + { + result.AppendError("must either provide a children list, a Python class name, or use -P and type a Python class line-by-line"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + virtual void + IOHandlerActivated (IOHandler &io_handler) + { + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) + { + output_sp->PutCString(g_synth_addreader_instructions); + output_sp->Flush(); + } + } + + virtual void + IOHandlerInputComplete (IOHandler &io_handler, std::string &data) + { + StreamFileSP error_sp = io_handler.GetErrorStreamFile(); + + ScriptInterpreter *interpreter = m_interpreter.GetScriptInterpreter(); + if (interpreter) + { + StringList lines; + lines.SplitIntoLines(data); + if (lines.GetSize() > 0) + { + SynthAddOptions *options_ptr = ((SynthAddOptions*)io_handler.GetUserData()); + if (options_ptr) + { + SynthAddOptions::SharedPointer options(options_ptr); // this will ensure that we get rid of the pointer when going out of scope + + ScriptInterpreter *interpreter = m_interpreter.GetScriptInterpreter(); + if (interpreter) + { + std::string class_name_str; + if (interpreter->GenerateTypeSynthClass (lines, class_name_str)) + { + if (class_name_str.empty()) + { + error_sp->Printf ("error: unable to obtain a proper name for the class.\n"); + error_sp->Flush(); + } + else + { + // everything should be fine now, let's add the synth provider class + + SyntheticChildrenSP synth_provider; + synth_provider.reset(new ScriptedSyntheticChildren(SyntheticChildren::Flags().SetCascades(options->m_cascade). + SetSkipPointers(options->m_skip_pointers). + SetSkipReferences(options->m_skip_references), + class_name_str.c_str())); + + + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory(ConstString(options->m_category.c_str()), category); + + Error error; + + for (size_t i = 0; i < options->m_target_types.GetSize(); i++) + { + const char *type_name = options->m_target_types.GetStringAtIndex(i); + ConstString const_type_name(type_name); + if (const_type_name) + { + if (!CommandObjectTypeSynthAdd::AddSynth(const_type_name, + synth_provider, + options->m_regex ? CommandObjectTypeSynthAdd::eRegexSynth : CommandObjectTypeSynthAdd::eRegularSynth, + options->m_category, + &error)) + { + error_sp->Printf("error: %s\n", error.AsCString()); + error_sp->Flush(); + break; + } + } + else + { + error_sp->Printf ("error: invalid type name.\n"); + error_sp->Flush(); + break; + } + } + } + } + else + { + error_sp->Printf ("error: unable to generate a class.\n"); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: no script interpreter.\n"); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: internal synchronization data missing.\n"); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: empty function, didn't add python command.\n"); + error_sp->Flush(); + } + } + else + { + error_sp->Printf ("error: script interpreter missing, didn't add python command.\n"); + error_sp->Flush(); + } + + io_handler.SetIsDone(true); + + + } + public: enum SynthFormatType @@ -1102,176 +1375,6 @@ CommandObjectTypeFormatList::CommandOptions::g_option_table[] = // CommandObjectTypeSummaryAdd //------------------------------------------------------------------------- -static const char *g_summary_addreader_instructions = "Enter your Python command(s). Type 'DONE' to end.\n" - "def function (valobj,internal_dict):\n" - " \"\"\"valobj: an SBValue which you want to provide a summary for\n" - " internal_dict: an LLDB support object not to be used\"\"\""; - -class TypeScriptAddInputReader : public InputReaderEZ -{ -private: - DISALLOW_COPY_AND_ASSIGN (TypeScriptAddInputReader); -public: - TypeScriptAddInputReader(Debugger& debugger) : - InputReaderEZ(debugger) - {} - - virtual - ~TypeScriptAddInputReader() - { - } - - virtual void ActivateHandler(HandlerData& data) - { - StreamSP out_stream = data.reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = data.reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - if (!batch_mode) - { - out_stream->Printf ("%s\n", g_summary_addreader_instructions); - if (data.reader.GetPrompt()) - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); - } - } - - virtual void ReactivateHandler(HandlerData& data) - { - StreamSP out_stream = data.reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = data.reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - if (data.reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); - } - } - virtual void GotTokenHandler(HandlerData& data) - { - StreamSP out_stream = data.reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = data.reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - if (data.bytes && data.bytes_len && data.baton) - { - ((ScriptAddOptions*)data.baton)->m_user_source.AppendString(data.bytes, data.bytes_len); - } - if (!data.reader.IsDone() && data.reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); - } - } - virtual void InterruptHandler(HandlerData& data) - { - StreamSP out_stream = data.reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = data.reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - data.reader.SetIsDone (true); - if (!batch_mode) - { - out_stream->Printf ("Warning: No command attached to breakpoint.\n"); - out_stream->Flush(); - } - } - virtual void EOFHandler(HandlerData& data) - { - data.reader.SetIsDone (true); - } - virtual void DoneHandler(HandlerData& data) - { - StreamSP out_stream = data.reader.GetDebugger().GetAsyncOutputStream(); - ScriptAddOptions *options_ptr = ((ScriptAddOptions*)data.baton); - if (!options_ptr) - { - out_stream->Printf ("internal synchronization information missing or invalid.\n"); - out_stream->Flush(); - return; - } - - ScriptAddOptions::SharedPointer options(options_ptr); // this will ensure that we get rid of the pointer when going out of scope - - ScriptInterpreter *interpreter = data.reader.GetDebugger().GetCommandInterpreter().GetScriptInterpreter(); - if (!interpreter) - { - out_stream->Printf ("no script interpreter.\n"); - out_stream->Flush(); - return; - } - std::string funct_name_str; - if (!interpreter->GenerateTypeScriptFunction (options->m_user_source, - funct_name_str)) - { - out_stream->Printf ("unable to generate a function.\n"); - out_stream->Flush(); - return; - } - if (funct_name_str.empty()) - { - out_stream->Printf ("unable to obtain a valid function name from the script interpreter.\n"); - out_stream->Flush(); - return; - } - // now I have a valid function name, let's add this as script for every type in the list - - TypeSummaryImplSP script_format; - script_format.reset(new ScriptSummaryFormat(options->m_flags, - funct_name_str.c_str(), - options->m_user_source.CopyList(" ").c_str())); - - Error error; - - for (size_t i = 0; i < options->m_target_types.GetSize(); i++) - { - const char *type_name = options->m_target_types.GetStringAtIndex(i); - CommandObjectTypeSummaryAdd::AddSummary(ConstString(type_name), - script_format, - (options->m_regex ? CommandObjectTypeSummaryAdd::eRegexSummary : CommandObjectTypeSummaryAdd::eRegularSummary), - options->m_category, - &error); - if (error.Fail()) - { - out_stream->Printf ("%s", error.AsCString()); - out_stream->Flush(); - return; - } - } - - if (options->m_name) - { - CommandObjectTypeSummaryAdd::AddSummary (options->m_name, - script_format, - CommandObjectTypeSummaryAdd::eNamedSummary, - options->m_category, - &error); - if (error.Fail()) - { - CommandObjectTypeSummaryAdd::AddSummary (options->m_name, - script_format, - CommandObjectTypeSummaryAdd::eNamedSummary, - options->m_category, - &error); - if (error.Fail()) - { - out_stream->Printf ("%s", error.AsCString()); - out_stream->Flush(); - return; - } - } - else - { - out_stream->Printf ("%s", error.AsCString()); - out_stream->Flush(); - return; - } - } - else - { - if (error.AsCString()) - { - out_stream->PutCString (error.AsCString()); - out_stream->Flush(); - } - return; - } - } -}; - #endif // #ifndef LLDB_DISABLE_PYTHON Error @@ -1352,35 +1455,9 @@ CommandObjectTypeSummaryAdd::CommandOptions::OptionParsingStarting () m_category = "default"; } + + #ifndef LLDB_DISABLE_PYTHON -void -CommandObjectTypeSummaryAdd::CollectPythonScript (ScriptAddOptions *options, - CommandReturnObject &result) -{ - InputReaderSP reader_sp (new TypeScriptAddInputReader(m_interpreter.GetDebugger())); - if (reader_sp && options) - { - - InputReaderEZ::InitializationParameters ipr; - - Error err (reader_sp->Initialize (ipr.SetBaton(options).SetPrompt(" "))); - if (err.Success()) - { - m_interpreter.GetDebugger().PushInputReader (reader_sp); - result.SetStatus (eReturnStatusSuccessFinishNoResult); - } - else - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - } - } - else - { - result.AppendError("out of memory"); - result.SetStatus (eReturnStatusFailed); - } -} bool CommandObjectTypeSummaryAdd::Execute_ScriptSummary (Args& command, CommandReturnObject &result) @@ -1406,7 +1483,7 @@ CommandObjectTypeSummaryAdd::Execute_ScriptSummary (Args& command, CommandReturn return false; } - std::string code = (" " + m_options.m_python_function + "(valobj,internal_dict)"); + std::string code = (" " + m_options.m_python_function + "(valobj,internal_dict)"); script_format.reset(new ScriptSummaryFormat(m_options.m_flags, funct_name, @@ -1445,14 +1522,15 @@ CommandObjectTypeSummaryAdd::Execute_ScriptSummary (Args& command, CommandReturn return false; } - std::string code = " " + m_options.m_python_script; + std::string code = " " + m_options.m_python_script; script_format.reset(new ScriptSummaryFormat(m_options.m_flags, funct_name_str.c_str(), code.c_str())); } - else // use an InputReader to grab Python code from the user - { + else + { + // Use an IOHandler to grab Python code from the user ScriptAddOptions *options = new ScriptAddOptions(m_options.m_flags, m_options.m_regex, m_options.m_name, @@ -1471,7 +1549,12 @@ CommandObjectTypeSummaryAdd::Execute_ScriptSummary (Args& command, CommandReturn } } - CollectPythonScript(options,result); + m_interpreter.GetPythonCommandsFromIOHandler (" ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + options); // Baton for the "io_handler" that will be passed back into our IOHandlerDelegate functions + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); } @@ -1603,6 +1686,7 @@ CommandObjectTypeSummaryAdd::CommandObjectTypeSummaryAdd (CommandInterpreter &in "type summary add", "Add a new summary style for a type.", NULL), + IOHandlerDelegateMultiline ("DONE"), m_options (interpreter) { CommandArgumentEntry type_arg; @@ -3647,193 +3731,6 @@ CommandObjectTypeSynthClear::CommandOptions::g_option_table[] = }; -//------------------------------------------------------------------------- -// TypeSynthAddInputReader -//------------------------------------------------------------------------- - -static const char *g_synth_addreader_instructions = "Enter your Python command(s). Type 'DONE' to end.\n" - "You must define a Python class with these methods:\n" - " def __init__(self, valobj, dict):\n" - " def num_children(self):\n" - " def get_child_at_index(self, index):\n" - " def get_child_index(self, name):\n" - "Optionally, you can also define a method:\n" - " def update(self):\n" - "if your synthetic provider is holding on to any per-object state variables (currently, this is not implemented because of the way LLDB handles instances of SBValue and you should not rely on object persistence and per-object state)\n" - "class synthProvider:"; - -class TypeSynthAddInputReader : public InputReaderEZ -{ -public: - TypeSynthAddInputReader(Debugger& debugger) : - InputReaderEZ(debugger) - {} - - virtual - ~TypeSynthAddInputReader() - { - } - - virtual void ActivateHandler(HandlerData& data) - { - StreamSP out_stream = data.GetOutStream(); - bool batch_mode = data.GetBatchMode(); - if (!batch_mode) - { - out_stream->Printf ("%s\n", g_synth_addreader_instructions); - if (data.reader.GetPrompt()) - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); - } - } - - virtual void ReactivateHandler(HandlerData& data) - { - StreamSP out_stream = data.GetOutStream(); - bool batch_mode = data.GetBatchMode(); - if (data.reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); - } - } - virtual void GotTokenHandler(HandlerData& data) - { - StreamSP out_stream = data.GetOutStream(); - bool batch_mode = data.GetBatchMode(); - if (data.bytes && data.bytes_len && data.baton) - { - ((SynthAddOptions*)data.baton)->m_user_source.AppendString(data.bytes, data.bytes_len); - } - if (!data.reader.IsDone() && data.reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", data.reader.GetPrompt()); - out_stream->Flush(); - } - } - virtual void InterruptHandler(HandlerData& data) - { - StreamSP out_stream = data.GetOutStream(); - bool batch_mode = data.GetBatchMode(); - data.reader.SetIsDone (true); - if (!batch_mode) - { - out_stream->Printf ("Warning: No command attached to breakpoint.\n"); - out_stream->Flush(); - } - } - virtual void EOFHandler(HandlerData& data) - { - data.reader.SetIsDone (true); - } - virtual void DoneHandler(HandlerData& data) - { - StreamSP out_stream = data.GetOutStream(); - SynthAddOptions *options_ptr = ((SynthAddOptions*)data.baton); - if (!options_ptr) - { - out_stream->Printf ("internal synchronization data missing.\n"); - out_stream->Flush(); - return; - } - - SynthAddOptions::SharedPointer options(options_ptr); // this will ensure that we get rid of the pointer when going out of scope - - ScriptInterpreter *interpreter = data.reader.GetDebugger().GetCommandInterpreter().GetScriptInterpreter(); - if (!interpreter) - { - out_stream->Printf ("no script interpreter.\n"); - out_stream->Flush(); - return; - } - std::string class_name_str; - if (!interpreter->GenerateTypeSynthClass (options->m_user_source, - class_name_str)) - { - out_stream->Printf ("unable to generate a class.\n"); - out_stream->Flush(); - return; - } - if (class_name_str.empty()) - { - out_stream->Printf ("unable to obtain a proper name for the class.\n"); - out_stream->Flush(); - return; - } - - // everything should be fine now, let's add the synth provider class - - SyntheticChildrenSP synth_provider; - synth_provider.reset(new ScriptedSyntheticChildren(SyntheticChildren::Flags().SetCascades(options->m_cascade). - SetSkipPointers(options->m_skip_pointers). - SetSkipReferences(options->m_skip_references), - class_name_str.c_str())); - - - lldb::TypeCategoryImplSP category; - DataVisualization::Categories::GetCategory(ConstString(options->m_category.c_str()), category); - - Error error; - - for (size_t i = 0; i < options->m_target_types.GetSize(); i++) - { - const char *type_name = options->m_target_types.GetStringAtIndex(i); - ConstString typeCS(type_name); - if (typeCS) - { - if (!CommandObjectTypeSynthAdd::AddSynth(typeCS, - synth_provider, - options->m_regex ? CommandObjectTypeSynthAdd::eRegexSynth : CommandObjectTypeSynthAdd::eRegularSynth, - options->m_category, - &error)) - { - out_stream->Printf("%s\n", error.AsCString()); - out_stream->Flush(); - return; - } - } - else - { - out_stream->Printf ("invalid type name.\n"); - out_stream->Flush(); - return; - } - } - } - -private: - DISALLOW_COPY_AND_ASSIGN (TypeSynthAddInputReader); -}; - -void -CommandObjectTypeSynthAdd::CollectPythonScript (SynthAddOptions *options, - CommandReturnObject &result) -{ - InputReaderSP reader_sp (new TypeSynthAddInputReader(m_interpreter.GetDebugger())); - if (reader_sp && options) - { - - InputReaderEZ::InitializationParameters ipr; - - Error err (reader_sp->Initialize (ipr.SetBaton(options).SetPrompt(" "))); - if (err.Success()) - { - m_interpreter.GetDebugger().PushInputReader (reader_sp); - result.SetStatus (eReturnStatusSuccessFinishNoResult); - } - else - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - } - } - else - { - result.AppendError("out of memory"); - result.SetStatus (eReturnStatusFailed); - } -} - bool CommandObjectTypeSynthAdd::Execute_HandwritePython (Args& command, CommandReturnObject &result) { @@ -3858,7 +3755,11 @@ CommandObjectTypeSynthAdd::Execute_HandwritePython (Args& command, CommandReturn } } - CollectPythonScript(options,result); + m_interpreter.GetPythonCommandsFromIOHandler (" ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + options); // Baton for the "io_handler" that will be passed back into our IOHandlerDelegate functions + result.SetStatus(eReturnStatusSuccessFinishNoResult); return result.Succeeded(); } @@ -3937,6 +3838,7 @@ CommandObjectTypeSynthAdd::CommandObjectTypeSynthAdd (CommandInterpreter &interp "type synthetic add", "Add a new synthetic provider for a type.", NULL), + IOHandlerDelegateMultiline ("DONE"), m_options (interpreter) { CommandArgumentEntry type_arg; @@ -4006,21 +3908,6 @@ CommandObjectTypeSynthAdd::AddSynth(ConstString type_name, return true; } } - -bool -CommandObjectTypeSynthAdd::DoExecute (Args& command, CommandReturnObject &result) -{ - if (m_options.handwrite_python) - return Execute_HandwritePython(command, result); - else if (m_options.is_class_based) - return Execute_PythonClass(command, result); - else - { - result.AppendError("must either provide a children list, a Python class name, or use -P and type a Python class line-by-line"); - result.SetStatus(eReturnStatusFailed); - return false; - } -} OptionDefinition CommandObjectTypeSynthAdd::CommandOptions::g_option_table[] = diff --git a/lldb/source/Commands/CommandObjectWatchpointCommand.cpp b/lldb/source/Commands/CommandObjectWatchpointCommand.cpp index e19216d74fc..0083ff140e5 100644 --- a/lldb/source/Commands/CommandObjectWatchpointCommand.cpp +++ b/lldb/source/Commands/CommandObjectWatchpointCommand.cpp @@ -16,6 +16,7 @@ #include "CommandObjectWatchpointCommand.h" #include "CommandObjectWatchpoint.h" +#include "lldb/Core/IOHandler.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Target.h" @@ -34,7 +35,9 @@ using namespace lldb_private; //------------------------------------------------------------------------- -class CommandObjectWatchpointCommandAdd : public CommandObjectParsed +class CommandObjectWatchpointCommandAdd : + public CommandObjectParsed, + public IOHandlerDelegateMultiline { public: @@ -43,6 +46,7 @@ public: "add", "Add a set of commands to a watchpoint, to be executed whenever the watchpoint is hit.", NULL), + IOHandlerDelegateMultiline("DONE", IOHandlerDelegate::Completion::LLDBCommand), m_options (interpreter) { SetHelpLong ( @@ -185,40 +189,45 @@ but do NOT enter more than one command per line. \n" ); return &m_options; } - void - CollectDataForWatchpointCommandCallback (WatchpointOptions *wp_options, - CommandReturnObject &result) + virtual void + IOHandlerActivated (IOHandler &io_handler) { - InputReaderSP reader_sp (new InputReader(m_interpreter.GetDebugger())); - std::unique_ptr<WatchpointOptions::CommandData> data_ap(new WatchpointOptions::CommandData()); - if (reader_sp && data_ap.get()) + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) { - BatonSP baton_sp (new WatchpointOptions::CommandBaton (data_ap.release())); - wp_options->SetCallback (WatchpointOptionsCallbackFunction, baton_sp); - - Error err (reader_sp->Initialize (CommandObjectWatchpointCommandAdd::GenerateWatchpointCommandCallback, - wp_options, // callback_data - eInputReaderGranularityLine, // token size, to pass to callback function - "DONE", // end token - "> ", // prompt - true)); // echo input - if (err.Success()) - { - m_interpreter.GetDebugger().PushInputReader (reader_sp); - result.SetStatus (eReturnStatusSuccessFinishNoResult); - } - else - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - } + output_sp->PutCString("Enter your debugger command(s). Type 'DONE' to end.\n"); + output_sp->Flush(); } - else + } + + + virtual void + IOHandlerInputComplete (IOHandler &io_handler, std::string &line) + { + io_handler.SetIsDone(true); + + // The WatchpointOptions object is owned by the watchpoint or watchpoint location + WatchpointOptions *wp_options = (WatchpointOptions *) io_handler.GetUserData(); + if (wp_options) { - result.AppendError("out of memory"); - result.SetStatus (eReturnStatusFailed); + std::unique_ptr<WatchpointOptions::CommandData> data_ap(new WatchpointOptions::CommandData()); + if (data_ap.get()) + { + data_ap->user_source.SplitIntoLines(line); + BatonSP baton_sp (new WatchpointOptions::CommandBaton (data_ap.release())); + wp_options->SetCallback (WatchpointOptionsCallbackFunction, baton_sp); + } } + } + void + CollectDataForWatchpointCommandCallback (WatchpointOptions *wp_options, + CommandReturnObject &result) + { + m_interpreter.GetLLDBCommandsFromIOHandler ("> ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + wp_options); // Baton for the "io_handler" that will be passed back into our IOHandlerDelegate functions } /// Set a one-liner as the callback for the watchpoint. @@ -240,93 +249,6 @@ but do NOT enter more than one command per line. \n" ); return; } - - static size_t - GenerateWatchpointCommandCallback (void *callback_data, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - - switch (notification) - { - case eInputReaderActivate: - if (!batch_mode) - { - out_stream->Printf ("%s\n", g_reader_instructions); - if (reader.GetPrompt()) - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderReactivate: - if (reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderGotToken: - if (bytes && bytes_len && callback_data) - { - WatchpointOptions *wp_options = (WatchpointOptions *) callback_data; - if (wp_options) - { - Baton *wp_options_baton = wp_options->GetBaton(); - if (wp_options_baton) - ((WatchpointOptions::CommandData *)wp_options_baton->m_data)->user_source.AppendString (bytes, bytes_len); - } - } - if (!reader.IsDone() && reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush(); - } - break; - - case eInputReaderInterrupt: - { - // Finish, and cancel the watchpoint command. - reader.SetIsDone (true); - WatchpointOptions *wp_options = (WatchpointOptions *) callback_data; - if (wp_options) - { - Baton *wp_options_baton = wp_options->GetBaton (); - if (wp_options_baton) - { - ((WatchpointOptions::CommandData *) wp_options_baton->m_data)->user_source.Clear(); - ((WatchpointOptions::CommandData *) wp_options_baton->m_data)->script_source.clear(); - } - } - if (!batch_mode) - { - out_stream->Printf ("Warning: No command attached to watchpoint.\n"); - out_stream->Flush(); - } - } - break; - - case eInputReaderEndOfFile: - reader.SetIsDone (true); - break; - - case eInputReaderDone: - break; - } - - return bytes_len; - } static bool WatchpointOptionsCallbackFunction (void *baton, @@ -579,12 +501,8 @@ protected: private: CommandOptions m_options; - static const char *g_reader_instructions; - }; -const char * -CommandObjectWatchpointCommandAdd::g_reader_instructions = "Enter your debugger command(s). Type 'DONE' to end."; // FIXME: "script-type" needs to have its contents determined dynamically, so somebody can add a new scripting // language to lldb and have it pickable here without having to change this enumeration by hand and rebuild lldb proper. diff --git a/lldb/source/Commands/CommandObjectWatchpointCommand.h b/lldb/source/Commands/CommandObjectWatchpointCommand.h index c2faf7187db..3bc9b3537db 100644 --- a/lldb/source/Commands/CommandObjectWatchpointCommand.h +++ b/lldb/source/Commands/CommandObjectWatchpointCommand.h @@ -19,9 +19,6 @@ #include "lldb/lldb-types.h" #include "lldb/Interpreter/Options.h" -#include "lldb/Core/InputReader.h" -#include "lldb/Interpreter/CommandObject.h" -#include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/CommandObjectMultiword.h" diff --git a/lldb/source/Core/Broadcaster.cpp b/lldb/source/Core/Broadcaster.cpp index 5af7497c8da..d56e47c9df9 100644 --- a/lldb/source/Core/Broadcaster.cpp +++ b/lldb/source/Core/Broadcaster.cpp @@ -323,8 +323,10 @@ Broadcaster::RestoreBroadcaster () listener->m_name.c_str(), listener); } - m_hijacking_listeners.pop_back(); - m_hijacking_masks.pop_back(); + if (!m_hijacking_listeners.empty()) + m_hijacking_listeners.pop_back(); + if (!m_hijacking_masks.empty()) + m_hijacking_masks.pop_back(); } ConstString & diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt index eeca58ca0b9..16730ac46b2 100644 --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -28,9 +28,7 @@ add_lldb_library(lldbCore FileLineResolver.cpp FileSpecList.cpp History.cpp - InputReader.cpp - InputReaderEZ.cpp - InputReaderStack.cpp + IOHandler.cpp Language.cpp Listener.cpp Log.cpp diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index b57c6051a96..867e1de68e5 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -18,13 +18,13 @@ #include "lldb/lldb-private.h" #include "lldb/Core/ConnectionFileDescriptor.h" -#include "lldb/Core/InputReader.h" #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/RegisterValue.h" #include "lldb/Core/State.h" #include "lldb/Core/StreamAsynchronousIO.h" #include "lldb/Core/StreamCallback.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/StreamString.h" #include "lldb/Core/Timer.h" #include "lldb/Core/ValueObject.h" @@ -180,6 +180,7 @@ Debugger::SetPropertyValue (const ExecutionContext *exe_ctx, std::string str = lldb_utility::ansi::FormatAnsiTerminalCodes (new_prompt, GetUseColor()); if (str.length()) new_prompt = str.c_str(); + GetCommandInterpreter().UpdatePrompt(new_prompt); EventSP prompt_change_event_sp (new Event(CommandInterpreter::eBroadcastBitResetPrompt, new EventDataBytes (new_prompt))); GetCommandInterpreter().BroadcastEvent (prompt_change_event_sp); } @@ -196,12 +197,16 @@ Debugger::SetPropertyValue (const ExecutionContext *exe_ctx, StreamString feedback_stream; if (!target_sp->LoadScriptingResources(errors,&feedback_stream)) { - for (auto error : errors) + StreamFileSP stream_sp (GetErrorFile()); + if (stream_sp) { - GetErrorStream().Printf("%s\n",error.AsCString()); + for (auto error : errors) + { + stream_sp->Printf("%s\n",error.AsCString()); + } + if (feedback_stream.GetSize()) + stream_sp->Printf("%s",feedback_stream.GetData()); } - if (feedback_stream.GetSize()) - GetErrorStream().Printf("%s",feedback_stream.GetData()); } } } @@ -246,8 +251,7 @@ Debugger::SetPrompt(const char *p) std::string str = lldb_utility::ansi::FormatAnsiTerminalCodes (new_prompt, GetUseColor()); if (str.length()) new_prompt = str.c_str(); - EventSP prompt_change_event_sp (new Event(CommandInterpreter::eBroadcastBitResetPrompt, new EventDataBytes (new_prompt)));; - GetCommandInterpreter().BroadcastEvent (prompt_change_event_sp); + GetCommandInterpreter().UpdatePrompt(new_prompt); } const char * @@ -611,10 +615,9 @@ Debugger::FindTargetWithProcess (Process *process) Debugger::Debugger (lldb::LogOutputCallback log_callback, void *baton) : UserID (g_unique_id++), Properties(OptionValuePropertiesSP(new OptionValueProperties())), - m_input_comm("debugger.input"), - m_input_file (), - m_output_file (), - m_error_file (), + m_input_file_sp (new StreamFile (stdin, false)), + m_output_file_sp (new StreamFile (stdout, false)), + m_error_file_sp (new StreamFile (stderr, false)), m_terminal_state (), m_target_list (*this), m_platform_list (), @@ -623,8 +626,11 @@ Debugger::Debugger (lldb::LogOutputCallback log_callback, void *baton) : m_source_file_cache(), m_command_interpreter_ap (new CommandInterpreter (*this, eScriptLanguageDefault, false)), m_input_reader_stack (), - m_input_reader_data (), - m_instance_name() + m_instance_name (), + m_loaded_plugins (), + m_event_handler_thread (LLDB_INVALID_HOST_THREAD), + m_io_handler_thread (LLDB_INVALID_HOST_THREAD), + m_event_handler_thread_alive(false) { char instance_cstr[256]; snprintf(instance_cstr, sizeof(instance_cstr), "debugger_%d", (int)GetID()); @@ -667,7 +673,9 @@ Debugger::~Debugger () void Debugger::Clear() { - CleanUpInputReaders(); + ClearIOHandlers(); + StopIOHandlerThread(); + StopEventHandlerThread(); m_listener.Clear(); int num_targets = m_target_list.GetNumTargets(); for (int i = 0; i < num_targets; i++) @@ -686,23 +694,21 @@ Debugger::Clear() // Close the input file _before_ we close the input read communications class // as it does NOT own the input file, our m_input_file does. m_terminal_state.Clear(); - GetInputFile().Close (); - // Now that we have closed m_input_file, we can now tell our input communication - // class to close down. Its read thread should quickly exit after we close - // the input file handle above. - m_input_comm.Clear (); + if (m_input_file_sp) + m_input_file_sp->GetFile().Close (); } bool Debugger::GetCloseInputOnEOF () const { - return m_input_comm.GetCloseOnEOF(); +// return m_input_comm.GetCloseOnEOF(); + return false; } void Debugger::SetCloseInputOnEOF (bool b) { - m_input_comm.SetCloseOnEOF(b); +// m_input_comm.SetCloseOnEOF(b); } bool @@ -721,37 +727,28 @@ Debugger::SetAsyncExecution (bool async_execution) void Debugger::SetInputFileHandle (FILE *fh, bool tranfer_ownership) { - File &in_file = GetInputFile(); - in_file.SetStream (fh, tranfer_ownership); + if (m_input_file_sp) + m_input_file_sp->GetFile().SetStream (fh, tranfer_ownership); + else + m_input_file_sp.reset (new StreamFile (fh, tranfer_ownership)); + + File &in_file = m_input_file_sp->GetFile(); if (in_file.IsValid() == false) in_file.SetStream (stdin, true); - // Disconnect from any old connection if we had one - m_input_comm.Disconnect (); - // Pass false as the second argument to ConnectionFileDescriptor below because - // our "in_file" above will already take ownership if requested and we don't - // want to objects trying to own and close a file descriptor. - m_input_comm.SetConnection (new ConnectionFileDescriptor (in_file.GetDescriptor(), false)); - m_input_comm.SetReadThreadBytesReceivedCallback (Debugger::DispatchInputCallback, this); - // Save away the terminal state if that is relevant, so that we can restore it in RestoreInputState. SaveInputTerminalState (); - - Error error; - if (m_input_comm.StartReadThread (&error) == false) - { - File &err_file = GetErrorFile(); - - err_file.Printf ("error: failed to main input read thread: %s", error.AsCString() ? error.AsCString() : "unkown error"); - exit(1); - } } void Debugger::SetOutputFileHandle (FILE *fh, bool tranfer_ownership) { - File &out_file = GetOutputFile(); - out_file.SetStream (fh, tranfer_ownership); + if (m_output_file_sp) + m_output_file_sp->GetFile().SetStream (fh, tranfer_ownership); + else + m_output_file_sp.reset (new StreamFile (fh, tranfer_ownership)); + + File &out_file = m_output_file_sp->GetFile(); if (out_file.IsValid() == false) out_file.SetStream (stdout, false); @@ -766,8 +763,12 @@ Debugger::SetOutputFileHandle (FILE *fh, bool tranfer_ownership) void Debugger::SetErrorFileHandle (FILE *fh, bool tranfer_ownership) { - File &err_file = GetErrorFile(); - err_file.SetStream (fh, tranfer_ownership); + if (m_error_file_sp) + m_error_file_sp->GetFile().SetStream (fh, tranfer_ownership); + else + m_error_file_sp.reset (new StreamFile (fh, tranfer_ownership)); + + File &err_file = m_error_file_sp->GetFile(); if (err_file.IsValid() == false) err_file.SetStream (stderr, false); } @@ -775,9 +776,12 @@ Debugger::SetErrorFileHandle (FILE *fh, bool tranfer_ownership) void Debugger::SaveInputTerminalState () { - File &in_file = GetInputFile(); - if (in_file.GetDescriptor() != File::kInvalidDescriptor) - m_terminal_state.Save(in_file.GetDescriptor(), true); + if (m_input_file_sp) + { + File &in_file = m_input_file_sp->GetFile(); + if (in_file.GetDescriptor() != File::kInvalidDescriptor) + m_terminal_state.Save(in_file.GetDescriptor(), true); + } } void @@ -812,245 +816,211 @@ Debugger::GetSelectedExecutionContext () return exe_ctx; } -InputReaderSP -Debugger::GetCurrentInputReader () -{ - InputReaderSP reader_sp; - - if (!m_input_reader_stack.IsEmpty()) - { - // Clear any finished readers from the stack - while (CheckIfTopInputReaderIsDone()) ; - - if (!m_input_reader_stack.IsEmpty()) - reader_sp = m_input_reader_stack.Top(); - } - - return reader_sp; -} - -void -Debugger::DispatchInputCallback (void *baton, const void *bytes, size_t bytes_len) -{ - if (bytes_len > 0) - ((Debugger *)baton)->DispatchInput ((char *)bytes, bytes_len); - else - ((Debugger *)baton)->DispatchInputEndOfFile (); -} - - -void -Debugger::DispatchInput (const char *bytes, size_t bytes_len) -{ - if (bytes == NULL || bytes_len == 0) - return; - - WriteToDefaultReader (bytes, bytes_len); -} - void Debugger::DispatchInputInterrupt () { - m_input_reader_data.clear(); - - InputReaderSP reader_sp (GetCurrentInputReader ()); + Mutex::Locker locker (m_input_reader_stack.GetMutex()); + IOHandlerSP reader_sp (m_input_reader_stack.Top()); if (reader_sp) - { - reader_sp->Notify (eInputReaderInterrupt); - - // If notifying the reader of the interrupt finished the reader, we should pop it off the stack. - while (CheckIfTopInputReaderIsDone ()) ; - } + reader_sp->Interrupt(); } void Debugger::DispatchInputEndOfFile () { - m_input_reader_data.clear(); - - InputReaderSP reader_sp (GetCurrentInputReader ()); + Mutex::Locker locker (m_input_reader_stack.GetMutex()); + IOHandlerSP reader_sp (m_input_reader_stack.Top()); if (reader_sp) - { - reader_sp->Notify (eInputReaderEndOfFile); - - // If notifying the reader of the end-of-file finished the reader, we should pop it off the stack. - while (CheckIfTopInputReaderIsDone ()) ; - } + reader_sp->GotEOF(); } void -Debugger::CleanUpInputReaders () +Debugger::ClearIOHandlers () { - m_input_reader_data.clear(); - // The bottom input reader should be the main debugger input reader. We do not want to close that one here. + Mutex::Locker locker (m_input_reader_stack.GetMutex()); while (m_input_reader_stack.GetSize() > 1) { - InputReaderSP reader_sp (GetCurrentInputReader ()); + IOHandlerSP reader_sp (m_input_reader_stack.Top()); if (reader_sp) { - reader_sp->Notify (eInputReaderEndOfFile); - reader_sp->SetIsDone (true); + m_input_reader_stack.Pop(); + reader_sp->SetIsDone(true); + reader_sp->Interrupt(); } } } void -Debugger::NotifyTopInputReader (InputReaderAction notification) +Debugger::ExecuteIOHanders() { - InputReaderSP reader_sp (GetCurrentInputReader()); - if (reader_sp) - { - reader_sp->Notify (notification); + + while (1) + { + IOHandlerSP reader_sp(m_input_reader_stack.Top()); + if (!reader_sp) + break; - // Flush out any input readers that are done. - while (CheckIfTopInputReaderIsDone ()) - /* Do nothing. */; + reader_sp->Activate(); + reader_sp->Run(); + reader_sp->Deactivate(); + + // Remove all input readers that are done from the top of the stack + while (1) + { + IOHandlerSP top_reader_sp = m_input_reader_stack.Top(); + if (top_reader_sp && top_reader_sp->GetIsDone()) + m_input_reader_stack.Pop(); + else + break; + } } + ClearIOHandlers(); } bool -Debugger::InputReaderIsTopReader (const InputReaderSP& reader_sp) +Debugger::IsTopIOHandler (const lldb::IOHandlerSP& reader_sp) { - InputReaderSP top_reader_sp (GetCurrentInputReader()); + return m_input_reader_stack.IsTop (reader_sp); +} + - return (reader_sp.get() == top_reader_sp.get()); +ConstString +Debugger::GetTopIOHandlerControlSequence(char ch) +{ + return m_input_reader_stack.GetTopIOHandlerControlSequence (ch); } - void -Debugger::WriteToDefaultReader (const char *bytes, size_t bytes_len) +Debugger::RunIOHandler (const IOHandlerSP& reader_sp) { - if (bytes && bytes_len) - m_input_reader_data.append (bytes, bytes_len); - - if (m_input_reader_data.empty()) - return; + Mutex::Locker locker (m_input_reader_stack.GetMutex()); + PushIOHandler (reader_sp); + reader_sp->Activate(); + reader_sp->Run(); + PopIOHandler (reader_sp); +} - while (!m_input_reader_stack.IsEmpty() && !m_input_reader_data.empty()) +void +Debugger::AdoptTopIOHandlerFilesIfInvalid (StreamFileSP &in, StreamFileSP &out, StreamFileSP &err) +{ + // Before an IOHandler runs, it must have in/out/err streams. + // This function is called when one ore more of the streams + // are NULL. We use the top input reader's in/out/err streams, + // or fall back to the debugger file handles, or we fall back + // onto stdin/stdout/stderr as a last resort. + + Mutex::Locker locker (m_input_reader_stack.GetMutex()); + IOHandlerSP top_reader_sp (m_input_reader_stack.Top()); + // If no STDIN has been set, then set it appropriately + if (!in) { - // Get the input reader from the top of the stack - InputReaderSP reader_sp (GetCurrentInputReader ()); - if (!reader_sp) - break; - - size_t bytes_handled = reader_sp->HandleRawBytes (m_input_reader_data.c_str(), - m_input_reader_data.size()); - if (bytes_handled) - { - m_input_reader_data.erase (0, bytes_handled); - } + if (top_reader_sp) + in = top_reader_sp->GetInputStreamFile(); else - { - // No bytes were handled, we might not have reached our - // granularity, just return and wait for more data - break; - } + in = GetInputFile(); + + // If there is nothing, use stdin + if (!in) + in = StreamFileSP(new StreamFile(stdin, false)); + } + // If no STDOUT has been set, then set it appropriately + if (!out) + { + if (top_reader_sp) + out = top_reader_sp->GetOutputStreamFile(); + else + out = GetOutputFile(); + + // If there is nothing, use stdout + if (!out) + out = StreamFileSP(new StreamFile(stdout, false)); + } + // If no STDERR has been set, then set it appropriately + if (!err) + { + if (top_reader_sp) + err = top_reader_sp->GetErrorStreamFile(); + else + err = GetErrorFile(); + + // If there is nothing, use stderr + if (!err) + err = StreamFileSP(new StreamFile(stdout, false)); + } - - // Flush out any input readers that are done. - while (CheckIfTopInputReaderIsDone ()) - /* Do nothing. */; - } void -Debugger::PushInputReader (const InputReaderSP& reader_sp) +Debugger::PushIOHandler (const IOHandlerSP& reader_sp) { if (!reader_sp) return; - // Deactivate the old top reader - InputReaderSP top_reader_sp (GetCurrentInputReader ()); + // Got the current top input reader... + IOHandlerSP top_reader_sp (m_input_reader_stack.Top()); - if (top_reader_sp) - top_reader_sp->Notify (eInputReaderDeactivate); - + // Push our new input reader m_input_reader_stack.Push (reader_sp); - reader_sp->Notify (eInputReaderActivate); - ActivateInputReader (reader_sp); + + // Interrupt the top input reader to it will exit its Run() function + // and let this new input reader take over + if (top_reader_sp) + top_reader_sp->Deactivate(); } bool -Debugger::PopInputReader (const InputReaderSP& pop_reader_sp) +Debugger::PopIOHandler (const IOHandlerSP& pop_reader_sp) { bool result = false; + + Mutex::Locker locker (m_input_reader_stack.GetMutex()); // The reader on the stop of the stack is done, so let the next // read on the stack referesh its prompt and if there is one... if (!m_input_reader_stack.IsEmpty()) { - // Cannot call GetCurrentInputReader here, as that would cause an infinite loop. - InputReaderSP reader_sp(m_input_reader_stack.Top()); + IOHandlerSP reader_sp(m_input_reader_stack.Top()); if (!pop_reader_sp || pop_reader_sp.get() == reader_sp.get()) { + reader_sp->Deactivate(); m_input_reader_stack.Pop (); - reader_sp->Notify (eInputReaderDeactivate); - reader_sp->Notify (eInputReaderDone); - result = true; + + reader_sp = m_input_reader_stack.Top(); + if (reader_sp) + reader_sp->Activate(); - if (!m_input_reader_stack.IsEmpty()) - { - reader_sp = m_input_reader_stack.Top(); - if (reader_sp) - { - ActivateInputReader (reader_sp); - reader_sp->Notify (eInputReaderReactivate); - } - } + result = true; } } return result; } bool -Debugger::CheckIfTopInputReaderIsDone () +Debugger::HideTopIOHandler() { - bool result = false; - if (!m_input_reader_stack.IsEmpty()) + Mutex::Locker locker; + + if (locker.TryLock(m_input_reader_stack.GetMutex())) { - // Cannot call GetCurrentInputReader here, as that would cause an infinite loop. - InputReaderSP reader_sp(m_input_reader_stack.Top()); - - if (reader_sp && reader_sp->IsDone()) - { - result = true; - PopInputReader (reader_sp); - } + IOHandlerSP reader_sp(m_input_reader_stack.Top()); + if (reader_sp) + reader_sp->Hide(); + return true; } - return result; + return false; } void -Debugger::ActivateInputReader (const InputReaderSP &reader_sp) +Debugger::RefreshTopIOHandler() { - int input_fd = m_input_file.GetFile().GetDescriptor(); - - if (input_fd >= 0) - { - Terminal tty(input_fd); - - tty.SetEcho(reader_sp->GetEcho()); - - switch (reader_sp->GetGranularity()) - { - case eInputReaderGranularityByte: - case eInputReaderGranularityWord: - tty.SetCanonical (false); - break; - - case eInputReaderGranularityLine: - case eInputReaderGranularityAll: - tty.SetCanonical (true); - break; - - default: - break; - } - } + IOHandlerSP reader_sp(m_input_reader_stack.Top()); + if (reader_sp) + reader_sp->Refresh(); } + StreamSP Debugger::GetAsyncOutputStream () { @@ -2624,7 +2594,7 @@ Debugger::EnableLog (const char *channel, const char **categories, const char *l } else if (log_file == NULL || *log_file == '\0') { - log_stream_sp.reset(new StreamFile(GetOutputFile().GetDescriptor(), false)); + log_stream_sp = GetOutputFile(); } else { @@ -2680,3 +2650,513 @@ Debugger::GetSourceManager () } + +// This function handles events that were broadcast by the process. +void +Debugger::HandleBreakpointEvent (const EventSP &event_sp) +{ + using namespace lldb; + const uint32_t event_type = Breakpoint::BreakpointEventData::GetBreakpointEventTypeFromEvent (event_sp); + +// if (event_type & eBreakpointEventTypeAdded +// || event_type & eBreakpointEventTypeRemoved +// || event_type & eBreakpointEventTypeEnabled +// || event_type & eBreakpointEventTypeDisabled +// || event_type & eBreakpointEventTypeCommandChanged +// || event_type & eBreakpointEventTypeConditionChanged +// || event_type & eBreakpointEventTypeIgnoreChanged +// || event_type & eBreakpointEventTypeLocationsResolved) +// { +// // Don't do anything about these events, since the breakpoint commands already echo these actions. +// } +// + if (event_type & eBreakpointEventTypeLocationsAdded) + { + uint32_t num_new_locations = Breakpoint::BreakpointEventData::GetNumBreakpointLocationsFromEvent(event_sp); + if (num_new_locations > 0) + { + BreakpointSP breakpoint = Breakpoint::BreakpointEventData::GetBreakpointFromEvent(event_sp); + StreamFileSP output_sp (GetOutputFile()); + if (output_sp) + { + output_sp->Printf("%d location%s added to breakpoint %d\n", + num_new_locations, + num_new_locations == 1 ? "" : "s", + breakpoint->GetID()); + RefreshTopIOHandler(); + } + } + } +// else if (event_type & eBreakpointEventTypeLocationsRemoved) +// { +// // These locations just get disabled, not sure it is worth spamming folks about this on the command line. +// } +// else if (event_type & eBreakpointEventTypeLocationsResolved) +// { +// // This might be an interesting thing to note, but I'm going to leave it quiet for now, it just looked noisy. +// } +} + +size_t +Debugger::GetProcessSTDOUT (Process *process, Stream *stream) +{ + size_t total_bytes = 0; + if (stream == NULL) + stream = GetOutputFile().get(); + + if (stream) + { + // The process has stuff waiting for stdout; get it and write it out to the appropriate place. + if (process == NULL) + { + TargetSP target_sp = GetTargetList().GetSelectedTarget(); + if (target_sp) + process = target_sp->GetProcessSP().get(); + } + if (process) + { + Error error; + size_t len; + char stdio_buffer[1024]; + while ((len = process->GetSTDOUT (stdio_buffer, sizeof (stdio_buffer), error)) > 0) + { + stream->Write(stdio_buffer, len); + total_bytes += len; + } + } + stream->Flush(); + } + return total_bytes; +} + +size_t +Debugger::GetProcessSTDERR (Process *process, Stream *stream) +{ + size_t total_bytes = 0; + if (stream == NULL) + stream = GetOutputFile().get(); + + if (stream) + { + // The process has stuff waiting for stderr; get it and write it out to the appropriate place. + if (process == NULL) + { + TargetSP target_sp = GetTargetList().GetSelectedTarget(); + if (target_sp) + process = target_sp->GetProcessSP().get(); + } + if (process) + { + Error error; + size_t len; + char stdio_buffer[1024]; + while ((len = process->GetSTDERR (stdio_buffer, sizeof (stdio_buffer), error)) > 0) + { + stream->Write(stdio_buffer, len); + total_bytes += len; + } + } + stream->Flush(); + } + return total_bytes; +} + +// This function handles events that were broadcast by the process. +void +Debugger::HandleProcessEvent (const EventSP &event_sp) +{ + using namespace lldb; + const uint32_t event_type = event_sp->GetType(); + ProcessSP process_sp = Process::ProcessEventData::GetProcessFromEvent(event_sp.get()); + + const bool gui_enabled = IsForwardingEvents(); + bool top_io_handler_hid = false; + if (gui_enabled == false) + top_io_handler_hid = HideTopIOHandler(); + + assert (process_sp); + + if (event_type & Process::eBroadcastBitSTDOUT) + { + // The process has stdout available, get it and write it out to the + // appropriate place. + if (top_io_handler_hid) + GetProcessSTDOUT (process_sp.get(), NULL); + } + else if (event_type & Process::eBroadcastBitSTDERR) + { + // The process has stderr available, get it and write it out to the + // appropriate place. + if (top_io_handler_hid) + GetProcessSTDERR (process_sp.get(), NULL); + } + else if (event_type & Process::eBroadcastBitStateChanged) + { + // Drain all stout and stderr so we don't see any output come after + // we print our prompts + if (top_io_handler_hid) + { + StreamFileSP stream_sp (GetOutputFile()); + GetProcessSTDOUT (process_sp.get(), stream_sp.get()); + GetProcessSTDERR (process_sp.get(), NULL); + // Something changed in the process; get the event and report the process's current status and location to + // the user. + StateType event_state = Process::ProcessEventData::GetStateFromEvent (event_sp.get()); + if (event_state == eStateInvalid) + return; + + switch (event_state) + { + case eStateInvalid: + case eStateUnloaded: + case eStateConnected: + case eStateAttaching: + case eStateLaunching: + case eStateStepping: + case eStateDetached: + { + stream_sp->Printf("Process %" PRIu64 " %s\n", + process_sp->GetID(), + StateAsCString (event_state)); + } + break; + + case eStateRunning: + // Don't be chatty when we run... + break; + + case eStateExited: + process_sp->GetStatus(*stream_sp); + break; + + case eStateStopped: + case eStateCrashed: + case eStateSuspended: + // Make sure the program hasn't been auto-restarted: + if (Process::ProcessEventData::GetRestartedFromEvent (event_sp.get())) + { + size_t num_reasons = Process::ProcessEventData::GetNumRestartedReasons(event_sp.get()); + if (num_reasons > 0) + { + // FIXME: Do we want to report this, or would that just be annoyingly chatty? + if (num_reasons == 1) + { + const char *reason = Process::ProcessEventData::GetRestartedReasonAtIndex (event_sp.get(), 0); + stream_sp->Printf("Process %" PRIu64 " stopped and restarted: %s\n", + process_sp->GetID(), + reason ? reason : "<UNKNOWN REASON>"); + } + else + { + stream_sp->Printf("Process %" PRIu64 " stopped and restarted, reasons:\n", + process_sp->GetID()); + + + for (size_t i = 0; i < num_reasons; i++) + { + const char *reason = Process::ProcessEventData::GetRestartedReasonAtIndex (event_sp.get(), i); + stream_sp->Printf("\t%s\n", reason ? reason : "<UNKNOWN REASON>"); + } + } + } + } + else + { + // Lock the thread list so it doesn't change on us + ThreadList &thread_list = process_sp->GetThreadList(); + Mutex::Locker locker (thread_list.GetMutex()); + + ThreadSP curr_thread (thread_list.GetSelectedThread()); + ThreadSP thread; + StopReason curr_thread_stop_reason = eStopReasonInvalid; + if (curr_thread) + curr_thread_stop_reason = curr_thread->GetStopReason(); + if (!curr_thread->IsValid() || + curr_thread_stop_reason == eStopReasonInvalid || + curr_thread_stop_reason == eStopReasonNone) + { + // Prefer a thread that has just completed its plan over another thread as current thread. + ThreadSP plan_thread; + ThreadSP other_thread; + const size_t num_threads = thread_list.GetSize(); + size_t i; + for (i = 0; i < num_threads; ++i) + { + thread = thread_list.GetThreadAtIndex(i); + StopReason thread_stop_reason = thread->GetStopReason(); + switch (thread_stop_reason) + { + case eStopReasonInvalid: + case eStopReasonNone: + break; + + case eStopReasonTrace: + case eStopReasonBreakpoint: + case eStopReasonWatchpoint: + case eStopReasonSignal: + case eStopReasonException: + case eStopReasonExec: + case eStopReasonThreadExiting: + if (!other_thread) + other_thread = thread; + break; + case eStopReasonPlanComplete: + if (!plan_thread) + plan_thread = thread; + break; + } + } + if (plan_thread) + thread_list.SetSelectedThreadByID (plan_thread->GetID()); + else if (other_thread) + thread_list.SetSelectedThreadByID (other_thread->GetID()); + else + { + if (curr_thread->IsValid()) + thread = curr_thread; + else + thread = thread_list.GetThreadAtIndex(0); + + if (thread) + thread_list.SetSelectedThreadByID (thread->GetID()); + } + } + + if (GetTargetList().GetSelectedTarget().get() == &process_sp->GetTarget()) + { + const bool only_threads_with_stop_reason = true; + const uint32_t start_frame = 0; + const uint32_t num_frames = 1; + const uint32_t num_frames_with_source = 1; + process_sp->GetStatus(*stream_sp); + process_sp->GetThreadStatus (*stream_sp, + only_threads_with_stop_reason, + start_frame, + num_frames, + num_frames_with_source); + } + else + { + uint32_t target_idx = GetTargetList().GetIndexOfTarget(process_sp->GetTarget().shared_from_this()); + if (target_idx != UINT32_MAX) + stream_sp->Printf ("Target %d: (", target_idx); + else + stream_sp->Printf ("Target <unknown index>: ("); + process_sp->GetTarget().Dump (stream_sp.get(), eDescriptionLevelBrief); + stream_sp->Printf (") stopped.\n"); + } + } + break; + } + } + } + + if (top_io_handler_hid) + RefreshTopIOHandler(); +} + +void +Debugger::HandleThreadEvent (const EventSP &event_sp) +{ + // At present the only thread event we handle is the Frame Changed event, + // and all we do for that is just reprint the thread status for that thread. + using namespace lldb; + const uint32_t event_type = event_sp->GetType(); + if (event_type == Thread::eBroadcastBitStackChanged || + event_type == Thread::eBroadcastBitThreadSelected ) + { + ThreadSP thread_sp (Thread::ThreadEventData::GetThreadFromEvent (event_sp.get())); + if (thread_sp) + { + HideTopIOHandler(); + StreamFileSP stream_sp (GetOutputFile()); + thread_sp->GetStatus(*stream_sp, 0, 1, 1); + RefreshTopIOHandler(); + } + } +} + +bool +Debugger::IsForwardingEvents () +{ + return (bool)m_forward_listener_sp; +} + +void +Debugger::EnableForwardEvents (const ListenerSP &listener_sp) +{ + m_forward_listener_sp = listener_sp; +} + +void +Debugger::CancelForwardEvents (const ListenerSP &listener_sp) +{ + m_forward_listener_sp.reset(); +} + + +void +Debugger::DefaultEventHandler() +{ + Listener& listener(GetListener()); + ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass()); + ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); + ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass()); + BroadcastEventSpec target_event_spec (broadcaster_class_target, + Target::eBroadcastBitBreakpointChanged); + + BroadcastEventSpec process_event_spec (broadcaster_class_process, + Process::eBroadcastBitStateChanged | + Process::eBroadcastBitSTDOUT | + Process::eBroadcastBitSTDERR); + + BroadcastEventSpec thread_event_spec (broadcaster_class_thread, + Thread::eBroadcastBitStackChanged | + Thread::eBroadcastBitThreadSelected ); + + listener.StartListeningForEventSpec (*this, target_event_spec); + listener.StartListeningForEventSpec (*this, process_event_spec); + listener.StartListeningForEventSpec (*this, thread_event_spec); + listener.StartListeningForEvents (m_command_interpreter_ap.get(), + CommandInterpreter::eBroadcastBitQuitCommandReceived | + CommandInterpreter::eBroadcastBitAsynchronousOutputData | + CommandInterpreter::eBroadcastBitAsynchronousErrorData ); + + bool done = false; + while (!done) + { +// Mutex::Locker locker; +// if (locker.TryLock(m_input_reader_stack.GetMutex())) +// { +// if (m_input_reader_stack.IsEmpty()) +// break; +// } +// + EventSP event_sp; + if (listener.WaitForEvent(NULL, event_sp)) + { + if (event_sp) + { + Broadcaster *broadcaster = event_sp->GetBroadcaster(); + if (broadcaster) + { + uint32_t event_type = event_sp->GetType(); + ConstString broadcaster_class (broadcaster->GetBroadcasterClass()); + if (broadcaster_class == broadcaster_class_process) + { + HandleProcessEvent (event_sp); + } + else if (broadcaster_class == broadcaster_class_target) + { + if (Breakpoint::BreakpointEventData::GetEventDataFromEvent(event_sp.get())) + { + HandleBreakpointEvent (event_sp); + } + } + else if (broadcaster_class == broadcaster_class_thread) + { + HandleThreadEvent (event_sp); + } + else if (broadcaster == m_command_interpreter_ap.get()) + { + if (event_type & CommandInterpreter::eBroadcastBitQuitCommandReceived) + { + done = true; + } + else if (event_type & CommandInterpreter::eBroadcastBitAsynchronousErrorData) + { + const char *data = reinterpret_cast<const char *>(EventDataBytes::GetBytesFromEvent (event_sp.get())); + if (data && data[0]) + { + StreamFileSP error_sp (GetErrorFile()); + if (error_sp) + { + HideTopIOHandler(); + error_sp->PutCString(data); + error_sp->Flush(); + RefreshTopIOHandler(); + } + } + } + else if (event_type & CommandInterpreter::eBroadcastBitAsynchronousOutputData) + { + const char *data = reinterpret_cast<const char *>(EventDataBytes::GetBytesFromEvent (event_sp.get())); + if (data && data[0]) + { + StreamFileSP output_sp (GetOutputFile()); + if (output_sp) + { + HideTopIOHandler(); + output_sp->PutCString(data); + output_sp->Flush(); + RefreshTopIOHandler(); + } + } + } + } + } + + if (m_forward_listener_sp) + m_forward_listener_sp->AddEvent(event_sp); + } + } + } +} + +lldb::thread_result_t +Debugger::EventHandlerThread (lldb::thread_arg_t arg) +{ + ((Debugger *)arg)->DefaultEventHandler(); + return NULL; +} + +bool +Debugger::StartEventHandlerThread() +{ + if (!IS_VALID_LLDB_HOST_THREAD(m_event_handler_thread)) + m_event_handler_thread = Host::ThreadCreate("lldb.debugger.event-handler", EventHandlerThread, this, NULL); + return IS_VALID_LLDB_HOST_THREAD(m_event_handler_thread); +} + +void +Debugger::StopEventHandlerThread() +{ + if (IS_VALID_LLDB_HOST_THREAD(m_event_handler_thread)) + { + GetCommandInterpreter().BroadcastEvent(CommandInterpreter::eBroadcastBitQuitCommandReceived); + Host::ThreadJoin(m_event_handler_thread, NULL, NULL); + m_event_handler_thread = LLDB_INVALID_HOST_THREAD; + } +} + + +lldb::thread_result_t +Debugger::IOHandlerThread (lldb::thread_arg_t arg) +{ + Debugger *debugger = (Debugger *)arg; + debugger->ExecuteIOHanders(); + debugger->StopEventHandlerThread(); + return NULL; +} + +bool +Debugger::StartIOHandlerThread() +{ + if (!IS_VALID_LLDB_HOST_THREAD(m_io_handler_thread)) + m_io_handler_thread = Host::ThreadCreate("lldb.debugger.io-handler", IOHandlerThread, this, NULL); + return IS_VALID_LLDB_HOST_THREAD(m_io_handler_thread); +} + +void +Debugger::StopIOHandlerThread() +{ + if (IS_VALID_LLDB_HOST_THREAD(m_io_handler_thread)) + { + if (m_input_file_sp) + m_input_file_sp->GetFile().Close(); + Host::ThreadJoin(m_io_handler_thread, NULL, NULL); + m_io_handler_thread = LLDB_INVALID_HOST_THREAD; + } +} + + diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp index 9bcd6668d0b..1d2b8cf04c3 100644 --- a/lldb/source/Core/Disassembler.cpp +++ b/lldb/source/Core/Disassembler.cpp @@ -1045,10 +1045,8 @@ InstructionList::GetIndexOfNextBranchInstruction(uint32_t start) const } uint32_t -InstructionList::GetIndexOfInstructionAtLoadAddress (lldb::addr_t load_addr, Target &target) +InstructionList::GetIndexOfInstructionAtAddress (const Address &address) { - Address address; - address.SetLoadAddress(load_addr, &target); size_t num_instructions = m_instructions.size(); uint32_t index = UINT32_MAX; for (size_t i = 0; i < num_instructions; i++) @@ -1062,6 +1060,15 @@ InstructionList::GetIndexOfInstructionAtLoadAddress (lldb::addr_t load_addr, Tar return index; } + +uint32_t +InstructionList::GetIndexOfInstructionAtLoadAddress (lldb::addr_t load_addr, Target &target) +{ + Address address; + address.SetLoadAddress(load_addr, &target); + return GetIndexOfInstructionAtAddress(address); +} + size_t Disassembler::ParseInstructions (const ExecutionContext *exe_ctx, const AddressRange &range, diff --git a/lldb/source/Core/IOHandler.cpp b/lldb/source/Core/IOHandler.cpp new file mode 100644 index 00000000000..2976beeeb40 --- /dev/null +++ b/lldb/source/Core/IOHandler.cpp @@ -0,0 +1,5221 @@ +//===-- IOHandler.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#include "lldb/lldb-python.h" + +#include <stdio.h> /* ioctl, TIOCGWINSZ */ +#include <sys/ioctl.h> /* ioctl, TIOCGWINSZ */ + + +#include <string> + +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/IOHandler.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/State.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Core/ValueObjectRegister.h" +#include "lldb/Host/Editline.h" +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/ThreadPlan.h" + +#include <ncurses.h> +#include <panel.h> + +using namespace lldb; +using namespace lldb_private; + +IOHandler::IOHandler (Debugger &debugger) : + IOHandler (debugger, + StreamFileSP(), // Adopt STDIN from top input reader + StreamFileSP(), // Adopt STDOUT from top input reader + StreamFileSP()) // Adopt STDERR from top input reader +{ +} + + +IOHandler::IOHandler (Debugger &debugger, + const lldb::StreamFileSP &input_sp, + const lldb::StreamFileSP &output_sp, + const lldb::StreamFileSP &error_sp) : + m_debugger (debugger), + m_input_sp (input_sp), + m_output_sp (output_sp), + m_error_sp (error_sp), + m_user_data (NULL), + m_done (false), + m_active (false) +{ + // If any files are not specified, then adopt them from the top input reader. + if (!m_input_sp || !m_output_sp || !m_error_sp) + debugger.AdoptTopIOHandlerFilesIfInvalid (m_input_sp, + m_output_sp, + m_error_sp); +} + +IOHandler::~IOHandler() +{ +} + + +int +IOHandler::GetInputFD() +{ + if (m_input_sp) + return m_input_sp->GetFile().GetDescriptor(); + return -1; +} + +int +IOHandler::GetOutputFD() +{ + if (m_output_sp) + return m_output_sp->GetFile().GetDescriptor(); + return -1; +} + +int +IOHandler::GetErrorFD() +{ + if (m_error_sp) + return m_error_sp->GetFile().GetDescriptor(); + return -1; +} + +FILE * +IOHandler::GetInputFILE() +{ + if (m_input_sp) + return m_input_sp->GetFile().GetStream(); + return NULL; +} + +FILE * +IOHandler::GetOutputFILE() +{ + if (m_output_sp) + return m_output_sp->GetFile().GetStream(); + return NULL; +} + +FILE * +IOHandler::GetErrorFILE() +{ + if (m_error_sp) + return m_error_sp->GetFile().GetStream(); + return NULL; +} + +StreamFileSP & +IOHandler::GetInputStreamFile() +{ + return m_input_sp; +} + +StreamFileSP & +IOHandler::GetOutputStreamFile() +{ + return m_output_sp; +} + + +StreamFileSP & +IOHandler::GetErrorStreamFile() +{ + return m_error_sp; +} + + +IOHandlerConfirm::IOHandlerConfirm (Debugger &debugger, + const char *prompt, + bool default_response) : + IOHandlerEditline(debugger, + NULL, // NULL editline_name means no history loaded/saved + NULL, + false, // Multi-line + *this), + m_default_response (default_response), + m_user_response (default_response) +{ + StreamString prompt_stream; + prompt_stream.PutCString(prompt); + if (m_default_response) + prompt_stream.Printf(": [Y/n] "); + else + prompt_stream.Printf(": [y/N] "); + + SetPrompt (prompt_stream.GetString().c_str()); + +} + + +IOHandlerConfirm::~IOHandlerConfirm () +{ +} + +int +IOHandlerConfirm::IOHandlerComplete (IOHandler &io_handler, + const char *current_line, + const char *cursor, + const char *last_char, + int skip_first_n_matches, + int max_matches, + StringList &matches) +{ + if (current_line == cursor) + { + if (m_default_response) + { + matches.AppendString("y"); + } + else + { + matches.AppendString("n"); + } + } + return matches.GetSize(); +} + +void +IOHandlerConfirm::IOHandlerInputComplete (IOHandler &io_handler, std::string &line) +{ + if (line.empty()) + { + // User just hit enter, set the response to the default + m_user_response = m_default_response; + io_handler.SetIsDone(true); + return; + } + + if (line.size() == 1) + { + switch (line[0]) + { + case 'y': + case 'Y': + m_user_response = true; + io_handler.SetIsDone(true); + return; + case 'n': + case 'N': + m_user_response = false; + io_handler.SetIsDone(true); + return; + default: + break; + } + } + + if (line == "yes" || line == "YES" || line == "Yes") + { + m_user_response = true; + io_handler.SetIsDone(true); + } + else if (line == "no" || line == "NO" || line == "No") + { + m_user_response = false; + io_handler.SetIsDone(true); + } +} + +int +IOHandlerDelegate::IOHandlerComplete (IOHandler &io_handler, + const char *current_line, + const char *cursor, + const char *last_char, + int skip_first_n_matches, + int max_matches, + StringList &matches) +{ + switch (m_completion) + { + case Completion::None: + break; + + case Completion::LLDBCommand: + return io_handler.GetDebugger().GetCommandInterpreter().HandleCompletion (current_line, + cursor, + last_char, + skip_first_n_matches, + max_matches, + matches); + + case Completion::Expression: + { + bool word_complete = false; + const char *word_start = cursor; + if (cursor > current_line) + --word_start; + while (word_start > current_line && !isspace(*word_start)) + --word_start; + CommandCompletions::InvokeCommonCompletionCallbacks (io_handler.GetDebugger().GetCommandInterpreter(), + CommandCompletions::eVariablePathCompletion, + word_start, + skip_first_n_matches, + max_matches, + NULL, + word_complete, + matches); + + size_t num_matches = matches.GetSize(); + if (num_matches > 0) + { + std::string common_prefix; + matches.LongestCommonPrefix (common_prefix); + const size_t partial_name_len = strlen(word_start); + + // If we matched a unique single command, add a space... + // Only do this if the completer told us this was a complete word, however... + if (num_matches == 1 && word_complete) + { + common_prefix.push_back(' '); + } + common_prefix.erase (0, partial_name_len); + matches.InsertStringAtIndex(0, std::move(common_prefix)); + } + return num_matches; + } + break; + } + + + return 0; +} + + +IOHandlerEditline::IOHandlerEditline (Debugger &debugger, + const char *editline_name, // Used for saving history files + const char *prompt, + bool multi_line, + IOHandlerDelegate &delegate) : + IOHandlerEditline(debugger, + StreamFileSP(), // Inherit input from top input reader + StreamFileSP(), // Inherit output from top input reader + StreamFileSP(), // Inherit error from top input reader + editline_name, // Used for saving history files + prompt, + multi_line, + delegate) +{ +} + +IOHandlerEditline::IOHandlerEditline (Debugger &debugger, + const lldb::StreamFileSP &input_sp, + const lldb::StreamFileSP &output_sp, + const lldb::StreamFileSP &error_sp, + const char *editline_name, // Used for saving history files + const char *prompt, + bool multi_line, + IOHandlerDelegate &delegate) : + IOHandler (debugger, input_sp, output_sp, error_sp), + m_editline_ap (), + m_delegate (delegate), + m_prompt (), + m_multi_line (multi_line), + m_interactive (false) +{ + SetPrompt(prompt); + + const int in_fd = GetInputFD(); + struct winsize window_size; + bool use_editline = false; + if (isatty (in_fd)) + { + m_interactive = true; + if (::ioctl (in_fd, TIOCGWINSZ, &window_size) == 0) + { + if (window_size.ws_col > 0) + use_editline = true; + } + } + + if (use_editline) + { + m_editline_ap.reset(new Editline (editline_name, + prompt ? prompt : "", + GetInputFILE (), + GetOutputFILE (), + GetErrorFILE ())); + m_editline_ap->SetLineCompleteCallback (LineCompletedCallback, this); + m_editline_ap->SetAutoCompleteCallback (AutoCompleteCallback, this); + } + +} + +IOHandlerEditline::~IOHandlerEditline () +{ + m_editline_ap.reset(); +} + + +bool +IOHandlerEditline::GetLine (std::string &line) +{ + if (m_editline_ap) + { + return m_editline_ap->GetLine(line).Success(); + } + else + { + line.clear(); + + FILE *in = GetInputFILE(); + if (in) + { + if (m_interactive) + { + const char *prompt = GetPrompt(); + if (prompt && prompt[0]) + { + FILE *out = GetOutputFILE(); + if (out) + { + ::fprintf(out, "%s", prompt); + ::fflush(out); + } + } + } + char buffer[256]; + bool done = false; + while (!done) + { + if (fgets(buffer, sizeof(buffer), in) == NULL) + done = true; + else + { + size_t buffer_len = strlen(buffer); + assert (buffer[buffer_len] == '\0'); + char last_char = buffer[buffer_len-1]; + if (last_char == '\r' || last_char == '\n') + { + done = true; + // Strip trailing newlines + while (last_char == '\r' || last_char == '\n') + { + --buffer_len; + if (buffer_len == 0) + break; + last_char = buffer[buffer_len-1]; + } + } + line.append(buffer, buffer_len); + } + } + } + else + { + // No more input file, we are done... + SetIsDone(true); + } + return !line.empty(); + } +} + + +LineStatus +IOHandlerEditline::LineCompletedCallback (Editline *editline, + StringList &lines, + uint32_t line_idx, + Error &error, + void *baton) +{ + IOHandlerEditline *editline_reader = (IOHandlerEditline *) baton; + return editline_reader->m_delegate.IOHandlerLinesUpdated(*editline_reader, lines, line_idx, error); +} + +int +IOHandlerEditline::AutoCompleteCallback (const char *current_line, + const char *cursor, + const char *last_char, + int skip_first_n_matches, + int max_matches, + StringList &matches, + void *baton) +{ + IOHandlerEditline *editline_reader = (IOHandlerEditline *) baton; + if (editline_reader) + return editline_reader->m_delegate.IOHandlerComplete (*editline_reader, + current_line, + cursor, + last_char, + skip_first_n_matches, + max_matches, + matches); + return 0; +} + +const char * +IOHandlerEditline::GetPrompt () +{ + if (m_editline_ap) + return m_editline_ap->GetPrompt (); + else if (m_prompt.empty()) + return NULL; + return m_prompt.c_str(); +} + +bool +IOHandlerEditline::SetPrompt (const char *p) +{ + if (p && p[0]) + m_prompt = p; + else + m_prompt.clear(); + if (m_editline_ap) + m_editline_ap->SetPrompt (m_prompt.empty() ? NULL : m_prompt.c_str()); + return true; +} + +bool +IOHandlerEditline::GetLines (StringList &lines) +{ + bool success = false; + if (m_editline_ap) + { + std::string end_token; + success = m_editline_ap->GetLines(end_token, lines).Success(); + } + else + { + LineStatus lines_status = LineStatus::Success; + + while (lines_status == LineStatus::Success) + { + std::string line; + if (GetLine(line)) + { + lines.AppendString(line); + Error error; + lines_status = m_delegate.IOHandlerLinesUpdated(*this, lines, lines.GetSize() - 1, error); + } + else + { + lines_status = LineStatus::Done; + } + } + success = lines.GetSize() > 0; + } + return success; +} + +// Each IOHandler gets to run until it is done. It should read data +// from the "in" and place output into "out" and "err and return +// when done. +void +IOHandlerEditline::Run () +{ + std::string line; + while (IsActive()) + { + if (m_multi_line) + { + StringList lines; + if (GetLines (lines)) + { + line = lines.CopyList(); + m_delegate.IOHandlerInputComplete(*this, line); + } + else + { + m_done = true; + } + } + else + { + if (GetLine(line)) + { + m_delegate.IOHandlerInputComplete(*this, line); + } + else + { + m_done = true; + } + } + } +} + +void +IOHandlerEditline::Hide () +{ + if (m_editline_ap && m_editline_ap->GettingLine()) + m_editline_ap->Hide(); +} + + +void +IOHandlerEditline::Refresh () +{ + if (m_editline_ap && m_editline_ap->GettingLine()) + m_editline_ap->Refresh(); + else + { + const char *prompt = GetPrompt(); + if (prompt && prompt[0]) + { + FILE *out = GetOutputFILE(); + if (out) + { + ::fprintf(out, "%s", prompt); + ::fflush(out); + } + } + } +} + +void +IOHandlerEditline::Interrupt () +{ + if (m_editline_ap) + m_editline_ap->Interrupt(); +} + +void +IOHandlerEditline::GotEOF() +{ + if (m_editline_ap) + m_editline_ap->Interrupt(); +} + +#include "lldb/Core/ValueObject.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/StackFrame.h" + +#define KEY_RETURN 10 +#define KEY_ESCAPE 27 + +namespace curses +{ + class Menu; + class MenuDelegate; + class Window; + class WindowDelegate; + typedef std::shared_ptr<Menu> MenuSP; + typedef std::shared_ptr<MenuDelegate> MenuDelegateSP; + typedef std::shared_ptr<Window> WindowSP; + typedef std::shared_ptr<WindowDelegate> WindowDelegateSP; + typedef std::vector<MenuSP> Menus; + typedef std::vector<WindowSP> Windows; + typedef std::vector<WindowDelegateSP> WindowDelegates; + +#if 0 +type summary add -s "x=${var.x}, y=${var.y}" curses::Point +type summary add -s "w=${var.width}, h=${var.height}" curses::Size +type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect +#endif + struct Point + { + int x; + int y; + + Point (int _x = 0, int _y = 0) : + x(_x), + y(_y) + { + } + + void + Clear () + { + x = 0; + y = 0; + } + + Point & + operator += (const Point &rhs) + { + x += rhs.x; + y += rhs.y; + return *this; + } + + void + Dump () + { + printf ("(x=%i, y=%i)\n", x, y); + } + + }; + + bool operator == (const Point &lhs, const Point &rhs) + { + return lhs.x == rhs.x && lhs.y == rhs.y; + } + bool operator != (const Point &lhs, const Point &rhs) + { + return lhs.x != rhs.x || lhs.y != rhs.y; + } + + struct Size + { + int width; + int height; + Size (int w = 0, int h = 0) : + width (w), + height (h) + { + } + + void + Clear () + { + width = 0; + height = 0; + } + + void + Dump () + { + printf ("(w=%i, h=%i)\n", width, height); + } + + }; + + bool operator == (const Size &lhs, const Size &rhs) + { + return lhs.width == rhs.width && lhs.height == rhs.height; + } + bool operator != (const Size &lhs, const Size &rhs) + { + return lhs.width != rhs.width || lhs.height != rhs.height; + } + + struct Rect + { + Point origin; + Size size; + + Rect () : + origin(), + size() + { + } + + Rect (const Point &p, const Size &s) : + origin (p), + size (s) + { + } + + void + Clear () + { + origin.Clear(); + size.Clear(); + } + + void + Dump () + { + printf ("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width, size.height); + } + + void + Inset (int w, int h) + { + if (size.width > w*2) + size.width -= w*2; + origin.x += w; + + if (size.height > h*2) + size.height -= h*2; + origin.y += h; + } + // Return a status bar rectangle which is the last line of + // this rectangle. This rectangle will be modified to not + // include the status bar area. + Rect + MakeStatusBar () + { + Rect status_bar; + if (size.height > 1) + { + status_bar.origin.x = origin.x; + status_bar.origin.y = size.height; + status_bar.size.width = size.width; + status_bar.size.height = 1; + --size.height; + } + return status_bar; + } + + // Return a menubar rectangle which is the first line of + // this rectangle. This rectangle will be modified to not + // include the menubar area. + Rect + MakeMenuBar () + { + Rect menubar; + if (size.height > 1) + { + menubar.origin.x = origin.x; + menubar.origin.y = origin.y; + menubar.size.width = size.width; + menubar.size.height = 1; + ++origin.y; + --size.height; + } + return menubar; + } + + void + HorizontalSplitPercentage (float top_percentage, Rect &top, Rect &bottom) const + { + float top_height = top_percentage * size.height; + HorizontalSplit (top_height, top, bottom); + } + + void + HorizontalSplit (int top_height, Rect &top, Rect &bottom) const + { + top = *this; + if (top_height < size.height) + { + top.size.height = top_height; + bottom.origin.x = origin.x; + bottom.origin.y = origin.y + top.size.height; + bottom.size.width = size.width; + bottom.size.height = size.height - top.size.height; + } + else + { + bottom.Clear(); + } + } + + void + VerticalSplitPercentage (float left_percentage, Rect &left, Rect &right) const + { + float left_width = left_percentage * size.width; + VerticalSplit (left_width, left, right); + } + + + void + VerticalSplit (int left_width, Rect &left, Rect &right) const + { + left = *this; + if (left_width < size.width) + { + left.size.width = left_width; + right.origin.x = origin.x + left.size.width; + right.origin.y = origin.y; + right.size.width = size.width - left.size.width; + right.size.height = size.height; + } + else + { + right.Clear(); + } + } + }; + + bool operator == (const Rect &lhs, const Rect &rhs) + { + return lhs.origin == rhs.origin && lhs.size == rhs.size; + } + bool operator != (const Rect &lhs, const Rect &rhs) + { + return lhs.origin != rhs.origin || lhs.size != rhs.size; + } + + enum HandleCharResult + { + eKeyNotHandled = 0, + eKeyHandled = 1, + eQuitApplication = 2 + }; + + enum class MenuActionResult + { + Handled, + NotHandled, + Quit // Exit all menus and quit + }; + + struct KeyHelp + { + int ch; + const char *description; + }; + + class WindowDelegate + { + public: + virtual + ~WindowDelegate() + { + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + return false; // Drawing not handled + } + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int key) + { + return eKeyNotHandled; + } + + virtual const char * + WindowDelegateGetHelpText () + { + return NULL; + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + return NULL; + } + }; + + class HelpDialogDelegate : + public WindowDelegate + { + public: + HelpDialogDelegate (const char *text, KeyHelp *key_help_array); + + virtual + ~HelpDialogDelegate(); + + virtual bool + WindowDelegateDraw (Window &window, bool force); + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int key); + + size_t + GetNumLines() const + { + return m_text.GetSize(); + } + + size_t + GetMaxLineLength () const + { + return m_text.GetMaxStringLength(); + } + + protected: + StringList m_text; + int m_first_visible_line; + }; + + + class Window + { + public: + + Window (const char *name) : + m_name (name), + m_window (NULL), + m_panel (NULL), + m_parent (NULL), + m_subwindows (), + m_delegate_sp (), + m_curr_active_window_idx (UINT32_MAX), + m_prev_active_window_idx (UINT32_MAX), + m_delete (false), + m_needs_update (true), + m_can_activate (true), + m_is_subwin (false) + { + } + + Window (const char *name, WINDOW *w, bool del = true) : + m_name (name), + m_window (NULL), + m_panel (NULL), + m_parent (NULL), + m_subwindows (), + m_delegate_sp (), + m_curr_active_window_idx (UINT32_MAX), + m_prev_active_window_idx (UINT32_MAX), + m_delete (del), + m_needs_update (true), + m_can_activate (true), + m_is_subwin (false) + { + if (w) + Reset(w); + } + + Window (const char *name, const Rect &bounds) : + m_name (name), + m_window (NULL), + m_parent (NULL), + m_subwindows (), + m_delegate_sp (), + m_curr_active_window_idx (UINT32_MAX), + m_prev_active_window_idx (UINT32_MAX), + m_delete (true), + m_needs_update (true), + m_can_activate (true), + m_is_subwin (false) + { + Reset (::newwin (bounds.size.height, bounds.size.width, bounds.origin.y, bounds.origin.y)); + } + + virtual + ~Window () + { + RemoveSubWindows (); + Reset (); + } + + void + Reset (WINDOW *w = NULL, bool del = true) + { + if (m_window == w) + return; + + if (m_panel) + { + ::del_panel (m_panel); + m_panel = NULL; + } + if (m_window && m_delete) + { + ::delwin (m_window); + m_window = NULL; + m_delete = false; + } + if (w) + { + m_window = w; + m_panel = ::new_panel (m_window); + m_delete = del; + } + } + + void AttributeOn (attr_t attr) { ::wattron (m_window, attr); } + void AttributeOff (attr_t attr) { ::wattroff (m_window, attr); } + void Box (chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { ::box(m_window, v_char, h_char); } + void Clear () { ::wclear (m_window); } + void Erase () { ::werase (m_window); } + Rect GetBounds () { return Rect (GetParentOrigin(), GetSize()); } // Get the rectangle in our parent window + int GetChar () { return ::wgetch (m_window); } + int GetCursorX () { return getcurx (m_window); } + int GetCursorY () { return getcury (m_window); } + Rect GetFrame () { return Rect (Point(), GetSize()); } // Get our rectangle in our own coordinate system + Point GetParentOrigin() { return Point (GetParentX(), GetParentY()); } + Size GetSize() { return Size (GetWidth(), GetHeight()); } + int GetParentX () { return getparx (m_window); } + int GetParentY () { return getpary (m_window); } + int GetMaxX() { return getmaxx (m_window); } + int GetMaxY() { return getmaxy (m_window); } + int GetWidth() { return GetMaxX(); } + int GetHeight() { return GetMaxY(); } + void MoveCursor (int x, int y) { ::wmove (m_window, y, x); } + void MoveWindow (int x, int y) { MoveWindow(Point(x,y)); } + void Resize (int w, int h) { ::wresize(m_window, h, w); } + void Resize (const Size &size) { ::wresize(m_window, size.height, size.width); } + void PutChar (int ch) { ::waddch (m_window, ch); } + void PutCString (const char *s, int len = -1) { ::waddnstr (m_window, s, len); } + void Refresh () { ::wrefresh (m_window); } + void DeferredRefresh () + { + // We are using panels, so we don't need to call this... + //::wnoutrefresh(m_window); + } + void SetBackground (int color_pair_idx) { ::wbkgd (m_window,COLOR_PAIR(color_pair_idx)); } + void UnderlineOn () { AttributeOn(A_UNDERLINE); } + void UnderlineOff () { AttributeOff(A_UNDERLINE); } + + void PutCStringTruncated (const char *s, int right_pad) + { + int bytes_left = GetWidth() - GetCursorX(); + if (bytes_left > right_pad) + { + bytes_left -= right_pad; + ::waddnstr (m_window, s, bytes_left); + } + } + + void + MoveWindow (const Point &origin) + { + const bool moving_window = origin != GetParentOrigin(); + if (m_is_subwin && moving_window) + { + // Can't move subwindows, must delete and re-create + Size size = GetSize(); + Reset (::subwin (m_parent->m_window, + size.height, + size.width, + origin.y, + origin.x), true); + } + else + { + ::mvwin (m_window, origin.y, origin.x); + } + } + + void + SetBounds (const Rect &bounds) + { + const bool moving_window = bounds.origin != GetParentOrigin(); + if (m_is_subwin && moving_window) + { + // Can't move subwindows, must delete and re-create + Reset (::subwin (m_parent->m_window, + bounds.size.height, + bounds.size.width, + bounds.origin.y, + bounds.origin.x), true); + } + else + { + if (moving_window) + MoveWindow(bounds.origin); + Resize (bounds.size); + } + } + + void + Printf (const char *format, ...) __attribute__ ((format (printf, 2, 3))) + { + va_list args; + va_start (args, format); + vwprintw(m_window, format, args); + va_end (args); + } + + void + Touch () + { + ::touchwin (m_window); + if (m_parent) + m_parent->Touch(); + } + + WindowSP + CreateSubWindow (const char *name, const Rect &bounds, bool make_active) + { + WindowSP subwindow_sp; + if (m_window) + { + subwindow_sp.reset(new Window(name, ::subwin (m_window, + bounds.size.height, + bounds.size.width, + bounds.origin.y, + bounds.origin.x), true)); + subwindow_sp->m_is_subwin = true; + } + else + { + subwindow_sp.reset(new Window(name, ::newwin (bounds.size.height, + bounds.size.width, + bounds.origin.y, + bounds.origin.x), true)); + subwindow_sp->m_is_subwin = false; + } + subwindow_sp->m_parent = this; + if (make_active) + { + m_prev_active_window_idx = m_curr_active_window_idx; + m_curr_active_window_idx = m_subwindows.size(); + } + m_subwindows.push_back(subwindow_sp); + ::top_panel (subwindow_sp->m_panel); + m_needs_update = true; + return subwindow_sp; + } + + bool + RemoveSubWindow (Window *window) + { + Windows::iterator pos, end = m_subwindows.end(); + size_t i = 0; + for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) + { + if ((*pos).get() == window) + { + if (m_prev_active_window_idx == i) + m_prev_active_window_idx = UINT32_MAX; + else if (m_prev_active_window_idx != UINT32_MAX && m_prev_active_window_idx > i) + --m_prev_active_window_idx; + + if (m_curr_active_window_idx == i) + m_curr_active_window_idx = UINT32_MAX; + else if (m_curr_active_window_idx != UINT32_MAX && m_curr_active_window_idx > i) + --m_curr_active_window_idx; + window->Erase(); + m_subwindows.erase(pos); + m_needs_update = true; + if (m_parent) + m_parent->Touch(); + else + ::touchwin (stdscr); + return true; + } + } + return false; + } + + WindowSP + FindSubWindow (const char *name) + { + Windows::iterator pos, end = m_subwindows.end(); + size_t i = 0; + for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) + { + if ((*pos)->m_name.compare(name) == 0) + return *pos; + } + return WindowSP(); + } + + void + RemoveSubWindows () + { + m_curr_active_window_idx = UINT32_MAX; + m_prev_active_window_idx = UINT32_MAX; + for (Windows::iterator pos = m_subwindows.begin(); + pos != m_subwindows.end(); + pos = m_subwindows.erase(pos)) + { + (*pos)->Erase(); + } + if (m_parent) + m_parent->Touch(); + else + ::touchwin (stdscr); + } + + WINDOW * + get() + { + return m_window; + } + + operator WINDOW *() + { + return m_window; + } + + //---------------------------------------------------------------------- + // Window drawing utilities + //---------------------------------------------------------------------- + void + DrawTitleBox (const char *title, const char *bottom_message = NULL) + { + attr_t attr = 0; + if (IsActive()) + attr = A_BOLD | COLOR_PAIR(2); + else + attr = 0; + if (attr) + AttributeOn(attr); + + Box(); + MoveCursor(3, 0); + + if (title && title[0]) + { + PutChar ('<'); + PutCString (title); + PutChar ('>'); + } + + if (bottom_message && bottom_message[0]) + { + int bottom_message_length = strlen(bottom_message); + int x = GetWidth() - 3 - (bottom_message_length + 2); + + if (x > 0) + { + MoveCursor (x, GetHeight() - 1); + PutChar ('['); + PutCString(bottom_message); + PutChar (']'); + } + else + { + MoveCursor (1, GetHeight() - 1); + PutChar ('['); + PutCStringTruncated (bottom_message, 1); + } + } + if (attr) + AttributeOff(attr); + + } + + virtual void + Draw (bool force) + { + if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw (*this, force)) + return; + + for (auto &subwindow_sp : m_subwindows) + subwindow_sp->Draw(force); + } + + bool + CreateHelpSubwindow () + { + if (m_delegate_sp) + { + const char *text = m_delegate_sp->WindowDelegateGetHelpText (); + KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp (); + if ((text && text[0]) || key_help) + { + std::auto_ptr<HelpDialogDelegate> help_delegate_ap(new HelpDialogDelegate(text, key_help)); + const size_t num_lines = help_delegate_ap->GetNumLines(); + const size_t max_length = help_delegate_ap->GetMaxLineLength(); + Rect bounds = GetBounds(); + bounds.Inset(1, 1); + if (max_length + 4 < bounds.size.width) + { + bounds.origin.x += (bounds.size.width - max_length + 4)/2; + bounds.size.width = max_length + 4; + } + else + { + if (bounds.size.width > 100) + { + const int inset_w = bounds.size.width / 4; + bounds.origin.x += inset_w; + bounds.size.width -= 2*inset_w; + } + } + + if (num_lines + 2 < bounds.size.height) + { + bounds.origin.y += (bounds.size.height - num_lines + 2)/2; + bounds.size.height = num_lines + 2; + } + else + { + if (bounds.size.height > 100) + { + const int inset_h = bounds.size.height / 4; + bounds.origin.y += inset_h; + bounds.size.height -= 2*inset_h; + } + } + WindowSP help_window_sp = GetParent()->CreateSubWindow("Help", bounds, true); + help_window_sp->SetDelegate(WindowDelegateSP(help_delegate_ap.release())); + return true; + } + } + return false; + } + + virtual HandleCharResult + HandleChar (int key) + { + // Always check the active window first + HandleCharResult result = eKeyNotHandled; + WindowSP active_window_sp = GetActiveWindow (); + if (active_window_sp) + { + result = active_window_sp->HandleChar (key); + if (result != eKeyNotHandled) + return result; + } + + if (m_delegate_sp) + { + result = m_delegate_sp->WindowDelegateHandleChar (*this, key); + if (result != eKeyNotHandled) + return result; + } + + // Then check for any windows that want any keys + // that weren't handled. This is typically only + // for a menubar. + // Make a copy of the subwindows in case any HandleChar() + // functions muck with the subwindows. If we don't do this, + // we can crash when iterating over the subwindows. + Windows subwindows (m_subwindows); + for (auto subwindow_sp : subwindows) + { + if (subwindow_sp->m_can_activate == false) + { + HandleCharResult result = subwindow_sp->HandleChar(key); + if (result != eKeyNotHandled) + return result; + } + } + + return eKeyNotHandled; + } + + bool + SetActiveWindow (Window *window) + { + const size_t num_subwindows = m_subwindows.size(); + for (size_t i=0; i<num_subwindows; ++i) + { + if (m_subwindows[i].get() == window) + { + m_prev_active_window_idx = m_curr_active_window_idx; + ::top_panel (window->m_panel); + m_curr_active_window_idx = i; + return true; + } + } + return false; + } + + WindowSP + GetActiveWindow () + { + if (!m_subwindows.empty()) + { + if (m_curr_active_window_idx >= m_subwindows.size()) + { + if (m_prev_active_window_idx < m_subwindows.size()) + { + m_curr_active_window_idx = m_prev_active_window_idx; + m_prev_active_window_idx = UINT32_MAX; + } + else if (IsActive()) + { + m_prev_active_window_idx = UINT32_MAX; + m_curr_active_window_idx = UINT32_MAX; + + // Find first window that wants to be active if this window is active + const size_t num_subwindows = m_subwindows.size(); + for (size_t i=0; i<num_subwindows; ++i) + { + if (m_subwindows[i]->GetCanBeActive()) + { + m_curr_active_window_idx = i; + break; + } + } + } + } + + if (m_curr_active_window_idx < m_subwindows.size()) + return m_subwindows[m_curr_active_window_idx]; + } + return WindowSP(); + } + + bool + GetCanBeActive () const + { + return m_can_activate; + } + + void + SetCanBeActive (bool b) + { + m_can_activate = b; + } + + const WindowDelegateSP & + GetDelegate () const + { + return m_delegate_sp; + } + + void + SetDelegate (const WindowDelegateSP &delegate_sp) + { + m_delegate_sp = delegate_sp; + } + + Window * + GetParent () const + { + return m_parent; + } + + bool + IsActive () const + { + if (m_parent) + return m_parent->GetActiveWindow().get() == this; + else + return true; // Top level window is always active + } + + void + SelectNextWindowAsActive () + { + // Move active focus to next window + const size_t num_subwindows = m_subwindows.size(); + if (m_curr_active_window_idx == UINT32_MAX) + { + uint32_t idx = 0; + for (auto subwindow_sp : m_subwindows) + { + if (subwindow_sp->GetCanBeActive()) + { + m_curr_active_window_idx = idx; + break; + } + ++idx; + } + } + else if (m_curr_active_window_idx + 1 < num_subwindows) + { + bool handled = false; + m_prev_active_window_idx = m_curr_active_window_idx; + for (size_t idx=m_curr_active_window_idx + 1; idx<num_subwindows; ++idx) + { + if (m_subwindows[idx]->GetCanBeActive()) + { + m_curr_active_window_idx = idx; + handled = true; + break; + } + } + if (!handled) + { + for (size_t idx=0; idx<=m_prev_active_window_idx; ++idx) + { + if (m_subwindows[idx]->GetCanBeActive()) + { + m_curr_active_window_idx = idx; + break; + } + } + } + } + else + { + m_prev_active_window_idx = m_curr_active_window_idx; + for (size_t idx=0; idx<num_subwindows; ++idx) + { + if (m_subwindows[idx]->GetCanBeActive()) + { + m_curr_active_window_idx = idx; + break; + } + } + } + } + + const char * + GetName () const + { + return m_name.c_str(); + } + protected: + std::string m_name; + WINDOW *m_window; + PANEL *m_panel; + Window *m_parent; + Windows m_subwindows; + WindowDelegateSP m_delegate_sp; + uint32_t m_curr_active_window_idx; + uint32_t m_prev_active_window_idx; + bool m_delete; + bool m_needs_update; + bool m_can_activate; + bool m_is_subwin; + + private: + DISALLOW_COPY_AND_ASSIGN(Window); + }; + + class MenuDelegate + { + public: + virtual ~MenuDelegate() {} + + virtual MenuActionResult + MenuDelegateAction (Menu &menu) = 0; + }; + + class Menu : public WindowDelegate + { + public: + enum class Type + { + Invalid, + Bar, + Item, + Separator + }; + + // Menubar or separator constructor + Menu (Type type); + + // Menuitem constructor + Menu (const char *name, + const char *key_name, + int key_value, + uint64_t identifier); + + virtual ~ + Menu () + { + } + + const MenuDelegateSP & + GetDelegate () const + { + return m_delegate_sp; + } + + void + SetDelegate (const MenuDelegateSP &delegate_sp) + { + m_delegate_sp = delegate_sp; + } + + void + RecalculateNameLengths(); + + void + AddSubmenu (const MenuSP &menu_sp); + + int + DrawAndRunMenu (Window &window); + + void + DrawMenuTitle (Window &window, bool highlight); + + virtual bool + WindowDelegateDraw (Window &window, bool force); + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int key); + + MenuActionResult + ActionPrivate (Menu &menu) + { + MenuActionResult result = MenuActionResult::NotHandled; + if (m_delegate_sp) + { + result = m_delegate_sp->MenuDelegateAction (menu); + if (result != MenuActionResult::NotHandled) + return result; + } + else if (m_parent) + { + result = m_parent->ActionPrivate(menu); + if (result != MenuActionResult::NotHandled) + return result; + } + return m_canned_result; + } + + MenuActionResult + Action () + { + // Call the recursive action so it can try to handle it + // with the menu delegate, and if not, try our parent menu + return ActionPrivate (*this); + } + + void + SetCannedResult (MenuActionResult result) + { + m_canned_result = result; + } + + Menus & + GetSubmenus() + { + return m_submenus; + } + + const Menus & + GetSubmenus() const + { + return m_submenus; + } + + int + GetSelectedSubmenuIndex () const + { + return m_selected; + } + + void + SetSelectedSubmenuIndex (int idx) + { + m_selected = idx; + } + + Type + GetType () const + { + return m_type; + } + + int + GetStartingColumn() const + { + return m_start_col; + } + + void + SetStartingColumn(int col) + { + m_start_col = col; + } + + int + GetKeyValue() const + { + return m_key_value; + } + + void + SetKeyValue(int key_value) + { + m_key_value = key_value; + } + + std::string & + GetName() + { + return m_name; + } + + std::string & + GetKeyName() + { + return m_key_name; + } + + int + GetDrawWidth () const + { + return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; + } + + + uint64_t + GetIdentifier() const + { + return m_identifier; + } + + void + SetIdentifier (uint64_t identifier) + { + m_identifier = identifier; + } + + protected: + std::string m_name; + std::string m_key_name; + uint64_t m_identifier; + Type m_type; + int m_key_value; + int m_start_col; + int m_max_submenu_name_length; + int m_max_submenu_key_name_length; + int m_selected; + Menu *m_parent; + Menus m_submenus; + WindowSP m_menu_window_sp; + MenuActionResult m_canned_result; + MenuDelegateSP m_delegate_sp; + }; + + // Menubar or separator constructor + Menu::Menu (Type type) : + m_name (), + m_key_name (), + m_identifier (0), + m_type (type), + m_key_value (0), + m_start_col (0), + m_max_submenu_name_length (0), + m_max_submenu_key_name_length (0), + m_selected (0), + m_parent (NULL), + m_submenus (), + m_canned_result (MenuActionResult::NotHandled), + m_delegate_sp() + { + } + + // Menuitem constructor + Menu::Menu (const char *name, + const char *key_name, + int key_value, + uint64_t identifier) : + m_name (), + m_key_name (), + m_identifier (identifier), + m_type (Type::Invalid), + m_key_value (key_value), + m_start_col (0), + m_max_submenu_name_length (0), + m_max_submenu_key_name_length (0), + m_selected (0), + m_parent (NULL), + m_submenus (), + m_canned_result (MenuActionResult::NotHandled), + m_delegate_sp() + { + if (name && name[0]) + { + m_name = name; + m_type = Type::Item; + if (key_name && key_name[0]) + m_key_name = key_name; + } + else + { + m_type = Type::Separator; + } + } + + void + Menu::RecalculateNameLengths() + { + m_max_submenu_name_length = 0; + m_max_submenu_key_name_length = 0; + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + for (size_t i=0; i<num_submenus; ++i) + { + Menu *submenu = submenus[i].get(); + if (m_max_submenu_name_length < submenu->m_name.size()) + m_max_submenu_name_length = submenu->m_name.size(); + if (m_max_submenu_key_name_length < submenu->m_key_name.size()) + m_max_submenu_key_name_length = submenu->m_key_name.size(); + } + } + + void + Menu::AddSubmenu (const MenuSP &menu_sp) + { + menu_sp->m_parent = this; + if (m_max_submenu_name_length < menu_sp->m_name.size()) + m_max_submenu_name_length = menu_sp->m_name.size(); + if (m_max_submenu_key_name_length < menu_sp->m_key_name.size()) + m_max_submenu_key_name_length = menu_sp->m_key_name.size(); + m_submenus.push_back(menu_sp); + } + + void + Menu::DrawMenuTitle (Window &window, bool highlight) + { + if (m_type == Type::Separator) + { + window.MoveCursor(0, window.GetCursorY()); + window.PutChar(ACS_LTEE); + int width = window.GetWidth(); + if (width > 2) + { + width -= 2; + for (size_t i=0; i< width; ++i) + window.PutChar(ACS_HLINE); + } + window.PutChar(ACS_RTEE); + } + else + { + const int shortcut_key = m_key_value; + bool underlined_shortcut = false; + const attr_t hilgight_attr = A_REVERSE; + if (highlight) + window.AttributeOn(hilgight_attr); + if (isprint(shortcut_key)) + { + size_t lower_pos = m_name.find(tolower(shortcut_key)); + size_t upper_pos = m_name.find(toupper(shortcut_key)); + const char *name = m_name.c_str(); + size_t pos = std::min<size_t>(lower_pos, upper_pos); + if (pos != std::string::npos) + { + underlined_shortcut = true; + if (pos > 0) + { + window.PutCString(name, pos); + name += pos; + } + const attr_t shortcut_attr = A_UNDERLINE|A_BOLD; + window.AttributeOn (shortcut_attr); + window.PutChar(name[0]); + window.AttributeOff(shortcut_attr); + name++; + if (name[0]) + window.PutCString(name); + } + } + + if (!underlined_shortcut) + { + window.PutCString(m_name.c_str()); + } + + if (highlight) + window.AttributeOff(hilgight_attr); + + if (m_key_name.empty()) + { + if (!underlined_shortcut && isprint(m_key_value)) + { + window.AttributeOn (COLOR_PAIR(3)); + window.Printf (" (%c)", m_key_value); + window.AttributeOff (COLOR_PAIR(3)); + } + } + else + { + window.AttributeOn (COLOR_PAIR(3)); + window.Printf (" (%s)", m_key_name.c_str()); + window.AttributeOff (COLOR_PAIR(3)); + } + } + } + + bool + Menu::WindowDelegateDraw (Window &window, bool force) + { + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + const int selected_idx = GetSelectedSubmenuIndex(); + Menu::Type menu_type = GetType (); + switch (menu_type) + { + case Menu::Type::Bar: + { + window.SetBackground(2); + window.MoveCursor(0, 0); + for (size_t i=0; i<num_submenus; ++i) + { + Menu *menu = submenus[i].get(); + if (i > 0) + window.PutChar(' '); + menu->SetStartingColumn (window.GetCursorX()); + window.PutCString("| "); + menu->DrawMenuTitle (window, false); + } + window.PutCString(" |"); + window.DeferredRefresh(); + } + break; + + case Menu::Type::Item: + { + int y = 1; + int x = 3; + // Draw the menu + int cursor_x = 0; + int cursor_y = 0; + window.Erase(); + window.SetBackground(2); + window.Box(); + for (size_t i=0; i<num_submenus; ++i) + { + const bool is_selected = i == selected_idx; + window.MoveCursor(x, y + i); + if (is_selected) + { + // Remember where we want the cursor to be + cursor_x = x-1; + cursor_y = y+i; + } + submenus[i]->DrawMenuTitle (window, is_selected); + } + window.MoveCursor(cursor_x, cursor_y); + window.DeferredRefresh(); + } + break; + + default: + case Menu::Type::Separator: + break; + } + return true; // Drawing handled... + } + + HandleCharResult + Menu::WindowDelegateHandleChar (Window &window, int key) + { + HandleCharResult result = eKeyNotHandled; + + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + const int selected_idx = GetSelectedSubmenuIndex(); + Menu::Type menu_type = GetType (); + if (menu_type == Menu::Type::Bar) + { + MenuSP run_menu_sp; + switch (key) + { + case KEY_DOWN: + case KEY_UP: + // Show last menu or first menu + if (selected_idx < num_submenus) + run_menu_sp = submenus[selected_idx]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + break; + + case KEY_RIGHT: + { + ++m_selected; + if (m_selected >= num_submenus) + m_selected = 0; + if (m_selected < num_submenus) + run_menu_sp = submenus[m_selected]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + } + break; + + case KEY_LEFT: + { + --m_selected; + if (m_selected < 0) + m_selected = num_submenus - 1; + if (m_selected < num_submenus) + run_menu_sp = submenus[m_selected]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + } + break; + + default: + for (size_t i=0; i<num_submenus; ++i) + { + if (submenus[i]->GetKeyValue() == key) + { + SetSelectedSubmenuIndex(i); + run_menu_sp = submenus[i]; + result = eKeyHandled; + break; + } + } + break; + } + + if (run_menu_sp) + { + // Run the action on this menu in case we need to populate the + // menu with dynamic content and also in case check marks, and + // any other menu decorations need to be caclulated + if (run_menu_sp->Action() == MenuActionResult::Quit) + return eQuitApplication; + + Rect menu_bounds; + menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); + menu_bounds.origin.y = 1; + menu_bounds.size.width = run_menu_sp->GetDrawWidth(); + menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; + if (m_menu_window_sp) + window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); + + m_menu_window_sp = window.GetParent()->CreateSubWindow (run_menu_sp->GetName().c_str(), + menu_bounds, + true); + m_menu_window_sp->SetDelegate (run_menu_sp); + } + } + else if (menu_type == Menu::Type::Item) + { + switch (key) + { + case KEY_DOWN: + if (m_submenus.size() > 1) + { + const int start_select = m_selected; + while (++m_selected != start_select) + { + if (m_selected >= num_submenus) + m_selected = 0; + if (m_submenus[m_selected]->GetType() == Type::Separator) + continue; + else + break; + } + return eKeyHandled; + } + break; + + case KEY_UP: + if (m_submenus.size() > 1) + { + const int start_select = m_selected; + while (--m_selected != start_select) + { + if (m_selected < 0) + m_selected = num_submenus - 1; + if (m_submenus[m_selected]->GetType() == Type::Separator) + continue; + else + break; + } + return eKeyHandled; + } + break; + + case KEY_RETURN: + if (selected_idx < num_submenus) + { + if (submenus[selected_idx]->Action() == MenuActionResult::Quit) + return eQuitApplication; + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + } + break; + + case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in case other chars are entered for escaped sequences + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + + default: + { + bool handled = false; + for (size_t i=0; i<num_submenus; ++i) + { + Menu *menu = submenus[i].get(); + if (menu->GetKeyValue() == key) + { + handled = true; + SetSelectedSubmenuIndex(i); + window.GetParent()->RemoveSubWindow(&window); + if (menu->Action() == MenuActionResult::Quit) + return eQuitApplication; + return eKeyHandled; + } + } + } + break; + + } + } + else if (menu_type == Menu::Type::Separator) + { + + } + return result; + } + + + class Application + { + public: + Application (FILE *in, FILE *out) : + m_window_sp(), + m_screen (NULL), + m_in (in), + m_out (out) + { + + } + + ~Application () + { + m_window_delegates.clear(); + m_window_sp.reset(); + if (m_screen) + { + ::delscreen(m_screen); + m_screen = NULL; + } + } + + void + Initialize () + { + ::setlocale(LC_ALL, ""); + ::setlocale(LC_CTYPE, ""); +#if 0 + ::initscr(); +#else + m_screen = ::newterm(NULL, m_out, m_in); +#endif + ::start_color(); + ::curs_set(0); + ::noecho(); + ::keypad(stdscr,TRUE); + } + + void + Terminate () + { + ::endwin(); + } + + void + Run (Debugger &debugger) + { + bool done = false; + int delay_in_tenths_of_a_second = 1; + + // Alas the threading model in curses is a bit lame so we need to + // resort to polling every 0.5 seconds. We could poll for stdin + // ourselves and then pass the keys down but then we need to + // translate all of the escape sequences ourselves. So we resort to + // polling for input because we need to receive async process events + // while in this loop. + + halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths of seconds seconds when calling Window::GetChar() + + ListenerSP listener_sp (new Listener ("lldb.IOHandler.curses.Application")); + ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass()); + ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); + ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass()); + debugger.EnableForwardEvents (listener_sp); + + bool update = true; +#if defined(__APPLE__) + std::deque<int> escape_chars; +#endif + + while (!done) + { + if (update) + { + m_window_sp->Draw(false); + // All windows should be calling Window::DeferredRefresh() instead + // of Window::Refresh() so we can do a single update and avoid + // any screen blinking + update_panels(); + + // Cursor hiding isn't working on MacOSX, so hide it in the top left corner + m_window_sp->MoveCursor(0, 0); + + doupdate(); + update = false; + } + +#if defined(__APPLE__) + // Terminal.app doesn't map its function keys correctly, F1-F4 default to: + // \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if possible + int ch; + if (escape_chars.empty()) + ch = m_window_sp->GetChar(); + else + { + ch = escape_chars.front(); + escape_chars.pop_front(); + } + if (ch == KEY_ESCAPE) + { + int ch2 = m_window_sp->GetChar(); + if (ch2 == 'O') + { + int ch3 = m_window_sp->GetChar(); + switch (ch3) + { + case 'P': ch = KEY_F(1); break; + case 'Q': ch = KEY_F(2); break; + case 'R': ch = KEY_F(3); break; + case 'S': ch = KEY_F(4); break; + default: + escape_chars.push_back(ch2); + if (ch3 != -1) + escape_chars.push_back(ch3); + break; + } + } + else if (ch2 != -1) + escape_chars.push_back(ch2); + } +#else + int ch = m_window_sp->GetChar(); + +#endif + if (ch == -1) + { + if (feof(m_in) || ferror(m_in)) + { + done = true; + } + else + { + // Just a timeout from using halfdelay(), check for events + EventSP event_sp; + while (listener_sp->PeekAtNextEvent()) + { + listener_sp->GetNextEvent(event_sp); + + if (event_sp) + { + Broadcaster *broadcaster = event_sp->GetBroadcaster(); + if (broadcaster) + { + //uint32_t event_type = event_sp->GetType(); + ConstString broadcaster_class (broadcaster->GetBroadcasterClass()); + if (broadcaster_class == broadcaster_class_process) + { + update = true; + continue; // Don't get any key, just update our view + } + } + } + } + } + } + else + { + HandleCharResult key_result = m_window_sp->HandleChar(ch); + switch (key_result) + { + case eKeyHandled: + update = true; + break; + case eKeyNotHandled: + break; + case eQuitApplication: + done = true; + break; + } + } + } + + debugger.CancelForwardEvents (listener_sp); + + } + + WindowSP & + GetMainWindow () + { + if (!m_window_sp) + m_window_sp.reset (new Window ("main", stdscr, false)); + return m_window_sp; + } + + WindowDelegates & + GetWindowDelegates () + { + return m_window_delegates; + } + + protected: + WindowSP m_window_sp; + WindowDelegates m_window_delegates; + SCREEN *m_screen; + FILE *m_in; + FILE *m_out; + }; + + +} // namespace curses + + +using namespace curses; + +struct Row +{ + ValueObjectSP valobj; + Row *parent; + int row_idx; + int x; + int y; + bool might_have_children; + bool expanded; + bool calculated_children; + std::vector<Row> children; + + Row (const ValueObjectSP &v, Row *p) : + valobj (v), + parent (p), + row_idx(0), + x(1), + y(1), + might_have_children (v ? v->MightHaveChildren() : false), + expanded (false), + calculated_children (false), + children() + { + } + + size_t + GetDepth () const + { + if (parent) + return 1 + parent->GetDepth(); + return 0; + } + + void + Expand() + { + expanded = true; + if (!calculated_children) + { + calculated_children = true; + if (valobj) + { + const size_t num_children = valobj->GetNumChildren(); + for (size_t i=0; i<num_children; ++i) + { + children.push_back(Row (valobj->GetChildAtIndex(i, true), this)); + } + } + } + } + + void + Unexpand () + { + expanded = false; + } + + void + DrawTree (Window &window) + { + if (parent) + parent->DrawTreeForChild (window, this, 0); + + if (might_have_children) + { + // It we can get UTF8 characters to work we should try to use the "symbol" + // UTF8 string below +// const char *symbol = ""; +// if (row.expanded) +// symbol = "\xe2\x96\xbd "; +// else +// symbol = "\xe2\x96\xb7 "; +// window.PutCString (symbol); + + // The ACS_DARROW and ACS_RARROW don't look very nice they are just a + // 'v' or '>' character... +// if (expanded) +// window.PutChar (ACS_DARROW); +// else +// window.PutChar (ACS_RARROW); + // Since we can't find any good looking right arrow/down arrow + // symbols, just use a diamond... + window.PutChar (ACS_DIAMOND); + window.PutChar (ACS_HLINE); + } + } + + void + DrawTreeForChild (Window &window, Row *child, uint32_t reverse_depth) + { + if (parent) + parent->DrawTreeForChild (window, this, reverse_depth + 1); + + if (&children.back() == child) + { + // Last child + if (reverse_depth == 0) + { + window.PutChar (ACS_LLCORNER); + window.PutChar (ACS_HLINE); + } + else + { + window.PutChar (' '); + window.PutChar (' '); + } + } + else + { + if (reverse_depth == 0) + { + window.PutChar (ACS_LTEE); + window.PutChar (ACS_HLINE); + } + else + { + window.PutChar (ACS_VLINE); + window.PutChar (' '); + } + } + } +}; + +struct DisplayOptions +{ + bool show_types; +}; + +class TreeItem; + +class TreeDelegate +{ +public: + TreeDelegate() {} + virtual ~TreeDelegate() {} + virtual void TreeDelegateDrawTreeItem (TreeItem &item, Window &window) = 0; + virtual void TreeDelegateGenerateChildren (TreeItem &item) = 0; + virtual bool TreeDelegateItemSelected (TreeItem &item) = 0; // Return true if we need to update views +}; +typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; + +class TreeItem +{ +public: + + TreeItem (TreeItem *parent, TreeDelegate &delegate, bool might_have_children) : + m_parent (parent), + m_delegate (delegate), + m_identifier (0), + m_row_idx (-1), + m_children (), + m_might_have_children (might_have_children), + m_is_expanded (false) + { + } + + TreeItem & + operator=(const TreeItem &rhs) + { + if (this != &rhs) + { + m_parent = rhs.m_parent; + m_delegate = rhs.m_delegate; + m_identifier = rhs.m_identifier; + m_row_idx = rhs.m_row_idx; + m_children = rhs.m_children; + m_might_have_children = rhs.m_might_have_children; + m_is_expanded = rhs.m_is_expanded; + } + return *this; + } + + size_t + GetDepth () const + { + if (m_parent) + return 1 + m_parent->GetDepth(); + return 0; + } + + int + GetRowIndex () const + { + return m_row_idx; + } + + void + ClearChildren () + { + m_children.clear(); + } + + void + Resize (size_t n, const TreeItem &t) + { + m_children.resize(n, t); + } + + TreeItem & + operator [](size_t i) + { + return m_children[i]; + } + + void + SetRowIndex (int row_idx) + { + m_row_idx = row_idx; + } + + size_t + GetNumChildren () + { + m_delegate.TreeDelegateGenerateChildren (*this); + return m_children.size(); + } + + void + ItemWasSelected () + { + m_delegate.TreeDelegateItemSelected(*this); + } + void + CalculateRowIndexes (int &row_idx) + { + SetRowIndex(row_idx); + ++row_idx; + + // The root item must calculate its children + if (m_parent == NULL) + GetNumChildren(); + + const bool expanded = IsExpanded(); + for (auto &item : m_children) + { + if (expanded) + item.CalculateRowIndexes(row_idx); + else + item.SetRowIndex(-1); + } + } + + TreeItem * + GetParent () + { + return m_parent; + } + + bool + IsExpanded () const + { + return m_is_expanded; + } + + void + Expand() + { + m_is_expanded = true; + } + + void + Unexpand () + { + m_is_expanded = false; + } + + bool + Draw (Window &window, + const int first_visible_row, + const uint32_t selected_row_idx, + int &row_idx, + int &num_rows_left) + { + if (num_rows_left <= 0) + return false; + + if (m_row_idx >= first_visible_row) + { + window.MoveCursor(2, row_idx + 1); + + if (m_parent) + m_parent->DrawTreeForChild (window, this, 0); + + if (m_might_have_children) + { + // It we can get UTF8 characters to work we should try to use the "symbol" + // UTF8 string below + // const char *symbol = ""; + // if (row.expanded) + // symbol = "\xe2\x96\xbd "; + // else + // symbol = "\xe2\x96\xb7 "; + // window.PutCString (symbol); + + // The ACS_DARROW and ACS_RARROW don't look very nice they are just a + // 'v' or '>' character... + // if (expanded) + // window.PutChar (ACS_DARROW); + // else + // window.PutChar (ACS_RARROW); + // Since we can't find any good looking right arrow/down arrow + // symbols, just use a diamond... + window.PutChar (ACS_DIAMOND); + window.PutChar (ACS_HLINE); + } + bool highlight = (selected_row_idx == m_row_idx) && window.IsActive(); + + if (highlight) + window.AttributeOn(A_REVERSE); + + m_delegate.TreeDelegateDrawTreeItem(*this, window); + + if (highlight) + window.AttributeOff(A_REVERSE); + ++row_idx; + --num_rows_left; + } + + if (num_rows_left <= 0) + return false; // We are done drawing... + + if (IsExpanded()) + { + for (auto &item : m_children) + { + // If we displayed all the rows and item.Draw() returns + // false we are done drawing and can exit this for loop + if (item.Draw(window, first_visible_row, selected_row_idx, row_idx, num_rows_left) == false) + break; + } + } + return num_rows_left >= 0; // Return true if not done drawing yet + } + + void + DrawTreeForChild (Window &window, TreeItem *child, uint32_t reverse_depth) + { + if (m_parent) + m_parent->DrawTreeForChild (window, this, reverse_depth + 1); + + if (&m_children.back() == child) + { + // Last child + if (reverse_depth == 0) + { + window.PutChar (ACS_LLCORNER); + window.PutChar (ACS_HLINE); + } + else + { + window.PutChar (' '); + window.PutChar (' '); + } + } + else + { + if (reverse_depth == 0) + { + window.PutChar (ACS_LTEE); + window.PutChar (ACS_HLINE); + } + else + { + window.PutChar (ACS_VLINE); + window.PutChar (' '); + } + } + } + + TreeItem * + GetItemForRowIndex (uint32_t row_idx) + { + if (m_row_idx == row_idx) + return this; + if (m_children.empty()) + return NULL; + if (m_children.back().m_row_idx < row_idx) + return NULL; + if (IsExpanded()) + { + for (auto &item : m_children) + { + TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); + if (selected_item_ptr) + return selected_item_ptr; + } + } + return NULL; + } + +// void * +// GetUserData() const +// { +// return m_user_data; +// } +// +// void +// SetUserData (void *user_data) +// { +// m_user_data = user_data; +// } + uint64_t + GetIdentifier() const + { + return m_identifier; + } + + void + SetIdentifier (uint64_t identifier) + { + m_identifier = identifier; + } + + +protected: + TreeItem *m_parent; + TreeDelegate &m_delegate; + //void *m_user_data; + uint64_t m_identifier; + int m_row_idx; // Zero based visible row index, -1 if not visible or for the root item + std::vector<TreeItem> m_children; + bool m_might_have_children; + bool m_is_expanded; + +}; + +class TreeWindowDelegate : public WindowDelegate +{ +public: + TreeWindowDelegate (Debugger &debugger, const TreeDelegateSP &delegate_sp) : + m_debugger (debugger), + m_delegate_sp (delegate_sp), + m_root (NULL, *delegate_sp, true), + m_selected_item (NULL), + m_num_rows (0), + m_selected_row_idx (0), + m_first_visible_row (0), + m_min_x (0), + m_min_y (0), + m_max_x (0), + m_max_y (0) + { + } + + int + NumVisibleRows () const + { + return m_max_y - m_min_y; + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx (m_debugger.GetCommandInterpreter().GetExecutionContext()); + Process *process = exe_ctx.GetProcessPtr(); + + bool display_content = false; + if (process) + { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) + { + // We are stopped, so it is ok to + display_content = true; + } + else if (StateIsRunningState(state)) + { + return true; // Don't do any updating when we are running + } + } + + m_min_x = 2; + m_min_y = 1; + m_max_x = window.GetWidth() - 1; + m_max_y = window.GetHeight() - 1; + + window.Erase(); + window.DrawTitleBox (window.GetName()); + + if (display_content) + { + const int num_visible_rows = NumVisibleRows(); + m_num_rows = 0; + m_root.CalculateRowIndexes(m_num_rows); + + // If we unexpanded while having something selected our + // total number of rows is less than the num visible rows, + // then make sure we show all the rows by setting the first + // visible row accordingly. + if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + int row_idx = 0; + int num_rows_left = num_visible_rows; + m_root.Draw (window, m_first_visible_row, m_selected_row_idx, row_idx, num_rows_left); + // Get the selected row + m_selected_item = m_root.GetItemForRowIndex (m_selected_row_idx); + } + else + { + m_selected_item = NULL; + } + + window.DeferredRefresh(); + + + return true; // Drawing handled + } + + + virtual const char * + WindowDelegateGetHelpText () + { + return "Thread window keyboard shortcuts:"; + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + static curses::KeyHelp g_source_view_key_help[] = { + { KEY_UP, "Select previous item" }, + { KEY_DOWN, "Select next item" }, + { KEY_RIGHT, "Expand the selected item" }, + { KEY_LEFT, "Unexpand the selected item or select parent if not expanded" }, + { KEY_PPAGE, "Page up" }, + { KEY_NPAGE, "Page down" }, + { 'h', "Show help dialog" }, + { ' ', "Toggle item expansion" }, + { ',', "Page up" }, + { '.', "Page down" }, + { '\0', NULL } + }; + return g_source_view_key_help; + } + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int c) + { + switch(c) + { + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_row > 0) + { + if (m_first_visible_row > m_max_y) + m_first_visible_row -= m_max_y; + else + m_first_visible_row = 0; + m_selected_row_idx = m_first_visible_row; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + if (m_num_rows > m_max_y) + { + if (m_first_visible_row + m_max_y < m_num_rows) + { + m_first_visible_row += m_max_y; + m_selected_row_idx = m_first_visible_row; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_row_idx > 0) + { + --m_selected_row_idx; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + return eKeyHandled; + case KEY_DOWN: + if (m_selected_row_idx + 1 < m_num_rows) + { + ++m_selected_row_idx; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + return eKeyHandled; + + case KEY_RIGHT: + if (m_selected_item) + { + if (!m_selected_item->IsExpanded()) + m_selected_item->Expand(); + } + return eKeyHandled; + + case KEY_LEFT: + if (m_selected_item) + { + if (m_selected_item->IsExpanded()) + m_selected_item->Unexpand(); + else if (m_selected_item->GetParent()) + { + m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + } + return eKeyHandled; + + case ' ': + // Toggle expansion state when SPACE is pressed + if (m_selected_item) + { + if (m_selected_item->IsExpanded()) + m_selected_item->Unexpand(); + else + m_selected_item->Expand(); + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow (); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + Debugger &m_debugger; + TreeDelegateSP m_delegate_sp; + TreeItem m_root; + TreeItem *m_selected_item; + int m_num_rows; + int m_selected_row_idx; + int m_first_visible_row; + int m_min_x; + int m_min_y; + int m_max_x; + int m_max_y; + +}; + +class FrameTreeDelegate : public TreeDelegate +{ +public: + FrameTreeDelegate (const ThreadSP &thread_sp) : + TreeDelegate(), + m_thread_wp() + { + if (thread_sp) + m_thread_wp = thread_sp; + } + + virtual ~FrameTreeDelegate() + { + } + + virtual void + TreeDelegateDrawTreeItem (TreeItem &item, Window &window) + { + ThreadSP thread_sp = m_thread_wp.lock(); + if (thread_sp) + { + const uint64_t frame_idx = item.GetIdentifier(); + StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(frame_idx); + if (frame_sp) + { + StreamString strm; + const SymbolContext &sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + ExecutionContext exe_ctx (frame_sp); + //const char *frame_format = "frame #${frame.index}: ${module.file.basename}{`${function.name}${function.pc-offset}}}"; + const char *frame_format = "frame #${frame.index}: {${function.name}${function.pc-offset}}}"; + if (Debugger::FormatPrompt (frame_format, &sc, &exe_ctx, NULL, strm)) + { + int right_pad = 1; + window.PutCStringTruncated(strm.GetString().c_str(), right_pad); + } + } + } + } + virtual void + TreeDelegateGenerateChildren (TreeItem &item) + { + // No children for frames yet... + } + + virtual bool + TreeDelegateItemSelected (TreeItem &item) + { + ThreadSP thread_sp = m_thread_wp.lock(); + if (thread_sp) + { + const uint64_t frame_idx = item.GetIdentifier(); + thread_sp->SetSelectedFrameByIndex(frame_idx); + return true; + } + return false; + } + void + SetThread (ThreadSP thread_sp) + { + m_thread_wp = thread_sp; + } + +protected: + ThreadWP m_thread_wp; +}; + +class ThreadTreeDelegate : public TreeDelegate +{ +public: + ThreadTreeDelegate (Debugger &debugger) : + TreeDelegate(), + m_debugger (debugger), + m_thread_wp (), + m_tid (LLDB_INVALID_THREAD_ID), + m_stop_id (UINT32_MAX) + { + } + + virtual + ~ThreadTreeDelegate() + { + } + + virtual void + TreeDelegateDrawTreeItem (TreeItem &item, Window &window) + { + ThreadSP thread_sp = m_thread_wp.lock(); + if (thread_sp) + { + StreamString strm; + ExecutionContext exe_ctx (thread_sp); + const char *format = "thread #${thread.index}: tid = ${thread.id}{, stop reason = ${thread.stop-reason}}"; + if (Debugger::FormatPrompt (format, NULL, &exe_ctx, NULL, strm)) + { + int right_pad = 1; + window.PutCStringTruncated(strm.GetString().c_str(), right_pad); + } + } + } + virtual void + TreeDelegateGenerateChildren (TreeItem &item) + { + TargetSP target_sp (m_debugger.GetSelectedTarget()); + if (target_sp) + { + ProcessSP process_sp = target_sp->GetProcessSP(); + if (process_sp && process_sp->IsAlive()) + { + StateType state = process_sp->GetState(); + if (StateIsStoppedState(state, true)) + { + ThreadSP thread_sp = process_sp->GetThreadList().GetSelectedThread(); + if (thread_sp) + { + if (m_stop_id == process_sp->GetStopID() && thread_sp->GetID() == m_tid) + return; // Children are already up to date + if (m_frame_delegate_sp) + m_frame_delegate_sp->SetThread(thread_sp); + else + { + // Always expand the thread item the first time we show it + item.Expand(); + m_frame_delegate_sp.reset (new FrameTreeDelegate(thread_sp)); + } + + m_stop_id = process_sp->GetStopID(); + m_thread_wp = thread_sp; + m_tid = thread_sp->GetID(); + + TreeItem t (&item, *m_frame_delegate_sp, false); + size_t num_frames = thread_sp->GetStackFrameCount(); + item.Resize (num_frames, t); + for (size_t i=0; i<num_frames; ++i) + { + item[i].SetIdentifier(i); + } + } + return; + } + } + } + item.ClearChildren(); + } + + virtual bool + TreeDelegateItemSelected (TreeItem &item) + { + ThreadSP thread_sp = m_thread_wp.lock(); + if (thread_sp) + { + ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); + Mutex::Locker locker (thread_list.GetMutex()); + ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); + if (selected_thread_sp->GetID() != thread_sp->GetID()) + { + thread_list.SetSelectedThreadByID(thread_sp->GetID()); + return true; + } + } + return false; + } + +protected: + Debugger &m_debugger; + ThreadWP m_thread_wp; + std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; + lldb::user_id_t m_tid; + uint32_t m_stop_id; +}; + +class ValueObjectListDelegate : public WindowDelegate +{ +public: + ValueObjectListDelegate () : + m_valobj_list (), + m_rows (), + m_selected_row (NULL), + m_selected_row_idx (0), + m_first_visible_row (0), + m_num_rows (0), + m_max_x (0), + m_max_y (0) + { + } + + ValueObjectListDelegate (ValueObjectList &valobj_list) : + m_valobj_list (valobj_list), + m_rows (), + m_selected_row (NULL), + m_selected_row_idx (0), + m_first_visible_row (0), + m_num_rows (0), + m_max_x (0), + m_max_y (0) + { + SetValues (valobj_list); + } + + virtual + ~ValueObjectListDelegate() + { + } + + void + SetValues (ValueObjectList &valobj_list) + { + m_selected_row = NULL; + m_selected_row_idx = 0; + m_first_visible_row = 0; + m_num_rows = 0; + m_rows.clear(); + m_valobj_list = valobj_list; + const size_t num_values = m_valobj_list.GetSize(); + for (size_t i=0; i<num_values; ++i) + m_rows.push_back(Row(m_valobj_list.GetValueObjectAtIndex(i), NULL)); + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + m_num_rows = 0; + m_min_x = 2; + m_min_y = 1; + m_max_x = window.GetWidth() - 1; + m_max_y = window.GetHeight() - 1; + + window.Erase(); + window.DrawTitleBox (window.GetName()); + + const int num_visible_rows = NumVisibleRows(); + const int num_rows = CalculateTotalNumberRows (m_rows); + + // If we unexpanded while having something selected our + // total number of rows is less than the num visible rows, + // then make sure we show all the rows by setting the first + // visible row accordingly. + if (m_first_visible_row > 0 && num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + DisplayRows (window, m_rows, g_options); + + window.DeferredRefresh(); + + // Get the selected row + m_selected_row = GetRowForRowIndex (m_selected_row_idx); + // Keep the cursor on the selected row so the highlight and the cursor + // are always on the same line + if (m_selected_row) + window.MoveCursor (m_selected_row->x, + m_selected_row->y); + + return true; // Drawing handled + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + static curses::KeyHelp g_source_view_key_help[] = { + { KEY_UP, "Select previous item" }, + { KEY_DOWN, "Select next item" }, + { KEY_RIGHT, "Expand selected item" }, + { KEY_LEFT, "Unexpand selected item or select parent if not expanded" }, + { KEY_PPAGE, "Page up" }, + { KEY_NPAGE, "Page down" }, + { 'A', "Format as annotated address" }, + { 'b', "Format as binary" }, + { 'B', "Format as hex bytes with ASCII" }, + { 'c', "Format as character" }, + { 'd', "Format as a signed integer" }, + { 'D', "Format selected value using the default format for the type" }, + { 'f', "Format as float" }, + { 'h', "Show help dialog" }, + { 'i', "Format as instructions" }, + { 'o', "Format as octal" }, + { 'p', "Format as pointer" }, + { 's', "Format as C string" }, + { 't', "Toggle showing/hiding type names" }, + { 'u', "Format as an unsigned integer" }, + { 'x', "Format as hex" }, + { 'X', "Format as uppercase hex" }, + { ' ', "Toggle item expansion" }, + { ',', "Page up" }, + { '.', "Page down" }, + { '\0', NULL } + }; + return g_source_view_key_help; + } + + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int c) + { + switch(c) + { + case 'x': + case 'X': + case 'o': + case 's': + case 'u': + case 'd': + case 'D': + case 'i': + case 'A': + case 'p': + case 'c': + case 'b': + case 'B': + case 'f': + // Change the format for the currently selected item + if (m_selected_row) + m_selected_row->valobj->SetFormat (FormatForChar (c)); + return eKeyHandled; + + case 't': + // Toggle showing type names + g_options.show_types = !g_options.show_types; + return eKeyHandled; + + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_row > 0) + { + if (m_first_visible_row > m_max_y) + m_first_visible_row -= m_max_y; + else + m_first_visible_row = 0; + m_selected_row_idx = m_first_visible_row; + } + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + if (m_num_rows > m_max_y) + { + if (m_first_visible_row + m_max_y < m_num_rows) + { + m_first_visible_row += m_max_y; + m_selected_row_idx = m_first_visible_row; + } + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_row_idx > 0) + --m_selected_row_idx; + return eKeyHandled; + case KEY_DOWN: + if (m_selected_row_idx + 1 < m_num_rows) + ++m_selected_row_idx; + return eKeyHandled; + + case KEY_RIGHT: + if (m_selected_row) + { + if (!m_selected_row->expanded) + m_selected_row->Expand(); + } + return eKeyHandled; + + case KEY_LEFT: + if (m_selected_row) + { + if (m_selected_row->expanded) + m_selected_row->Unexpand(); + else if (m_selected_row->parent) + m_selected_row_idx = m_selected_row->parent->row_idx; + } + return eKeyHandled; + + case ' ': + // Toggle expansion state when SPACE is pressed + if (m_selected_row) + { + if (m_selected_row->expanded) + m_selected_row->Unexpand(); + else + m_selected_row->Expand(); + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow (); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + ValueObjectList m_valobj_list; + std::vector<Row> m_rows; + Row *m_selected_row; + uint32_t m_selected_row_idx; + uint32_t m_first_visible_row; + uint32_t m_num_rows; + int m_min_x; + int m_min_y; + int m_max_x; + int m_max_y; + + static Format + FormatForChar (int c) + { + switch (c) + { + case 'x': return eFormatHex; + case 'X': return eFormatHexUppercase; + case 'o': return eFormatOctal; + case 's': return eFormatCString; + case 'u': return eFormatUnsigned; + case 'd': return eFormatDecimal; + case 'D': return eFormatDefault; + case 'i': return eFormatInstruction; + case 'A': return eFormatAddressInfo; + case 'p': return eFormatPointer; + case 'c': return eFormatChar; + case 'b': return eFormatBinary; + case 'B': return eFormatBytesWithASCII; + case 'f': return eFormatFloat; + } + return eFormatDefault; + } + + bool + DisplayRowObject (Window &window, + Row &row, + DisplayOptions &options, + bool highlight, + bool last_child) + { + ValueObject *valobj = row.valobj.get(); + + if (valobj == NULL) + return false; + + const char *type_name = options.show_types ? valobj->GetTypeName().GetCString() : NULL; + const char *name = valobj->GetName().GetCString(); + const char *value = valobj->GetValueAsCString (); + const char *summary = valobj->GetSummaryAsCString (); + + window.MoveCursor (row.x, row.y); + + row.DrawTree (window); + + if (highlight) + window.AttributeOn(A_REVERSE); + + if (type_name && type_name[0]) + window.Printf ("(%s) ", type_name); + + if (name && name[0]) + window.PutCString(name); + + attr_t changd_attr = 0; + if (valobj->GetValueDidChange()) + changd_attr = COLOR_PAIR(5) | A_BOLD; + + if (value && value[0]) + { + window.PutCString(" = "); + if (changd_attr) + window.AttributeOn(changd_attr); + window.PutCString (value); + if (changd_attr) + window.AttributeOff(changd_attr); + } + + if (summary && summary[0]) + { + window.PutChar(' '); + if (changd_attr) + window.AttributeOn(changd_attr); + window.PutCString(summary); + if (changd_attr) + window.AttributeOff(changd_attr); + } + + if (highlight) + window.AttributeOff (A_REVERSE); + + return true; + } + void + DisplayRows (Window &window, + std::vector<Row> &rows, + DisplayOptions &options) + { + // > 0x25B7 + // \/ 0x25BD + + bool window_is_active = window.IsActive(); + for (auto &row : rows) + { + const bool last_child = row.parent && &rows[rows.size()-1] == &row; + // Save the row index in each Row structure + row.row_idx = m_num_rows; + if ((m_num_rows >= m_first_visible_row) && + ((m_num_rows - m_first_visible_row) < NumVisibleRows())) + { + row.x = m_min_x; + row.y = m_num_rows - m_first_visible_row + 1; + if (DisplayRowObject (window, + row, + options, + window_is_active && m_num_rows == m_selected_row_idx, + last_child)) + { + ++m_num_rows; + } + else + { + row.x = 0; + row.y = 0; + } + } + else + { + row.x = 0; + row.y = 0; + ++m_num_rows; + } + + if (row.expanded && !row.children.empty()) + { + DisplayRows (window, + row.children, + options); + } + } + } + + int + CalculateTotalNumberRows (const std::vector<Row> &rows) + { + int row_count = 0; + for (const auto &row : rows) + { + ++row_count; + if (row.expanded) + row_count += CalculateTotalNumberRows(row.children); + } + return row_count; + } + static Row * + GetRowForRowIndexImpl (std::vector<Row> &rows, size_t &row_index) + { + for (auto &row : rows) + { + if (row_index == 0) + return &row; + else + { + --row_index; + if (row.expanded && !row.children.empty()) + { + Row *result = GetRowForRowIndexImpl (row.children, row_index); + if (result) + return result; + } + } + } + return NULL; + } + + Row * + GetRowForRowIndex (size_t row_index) + { + return GetRowForRowIndexImpl (m_rows, row_index); + } + + int + NumVisibleRows () const + { + return m_max_y - m_min_y; + } + + static DisplayOptions g_options; +}; + +class FrameVariablesWindowDelegate : public ValueObjectListDelegate +{ +public: + FrameVariablesWindowDelegate (Debugger &debugger) : + ValueObjectListDelegate (), + m_debugger (debugger), + m_frame_block (NULL) + { + } + + virtual + ~FrameVariablesWindowDelegate() + { + } + + virtual const char * + WindowDelegateGetHelpText () + { + return "Frame variable window keyboard shortcuts:"; + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx (m_debugger.GetCommandInterpreter().GetExecutionContext()); + Process *process = exe_ctx.GetProcessPtr(); + Block *frame_block = NULL; + StackFrame *frame = NULL; + + if (process) + { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) + { + frame = exe_ctx.GetFramePtr(); + if (frame) + frame_block = frame->GetFrameBlock (); + } + else if (StateIsRunningState(state)) + { + return true; // Don't do any updating when we are running + } + } + + ValueObjectList local_values; + if (frame_block) + { + // Only update the variables if they have changed + if (m_frame_block != frame_block) + { + m_frame_block = frame_block; + + VariableList *locals = frame->GetVariableList(true); + if (locals) + { + const DynamicValueType use_dynamic = eDynamicDontRunTarget; + const size_t num_locals = locals->GetSize(); + for (size_t i=0; i<num_locals; ++i) + local_values.Append(frame->GetValueObjectForFrameVariable (locals->GetVariableAtIndex(i), use_dynamic)); + // Update the values + SetValues(local_values); + } + } + } + else + { + m_frame_block = NULL; + // Update the values with an empty list if there is no frame + SetValues(local_values); + } + + return ValueObjectListDelegate::WindowDelegateDraw (window, force); + + } + +protected: + Debugger &m_debugger; + Block *m_frame_block; +}; + + +class RegistersWindowDelegate : public ValueObjectListDelegate +{ +public: + RegistersWindowDelegate (Debugger &debugger) : + ValueObjectListDelegate (), + m_debugger (debugger) + { + } + + virtual + ~RegistersWindowDelegate() + { + } + + virtual const char * + WindowDelegateGetHelpText () + { + return "Register window keyboard shortcuts:"; + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx (m_debugger.GetCommandInterpreter().GetExecutionContext()); + StackFrame *frame = exe_ctx.GetFramePtr(); + + ValueObjectList value_list; + if (frame) + { + if (frame->GetStackID() != m_stack_id) + { + m_stack_id = frame->GetStackID(); + RegisterContextSP reg_ctx (frame->GetRegisterContext()); + if (reg_ctx) + { + const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); + for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) + { + value_list.Append(ValueObjectRegisterSet::Create (frame, reg_ctx, set_idx)); + } + } + SetValues(value_list); + } + } + else + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + return true; // Don't do any updating if we are running + else + { + // Update the values with an empty list if there + // is no process or the process isn't alive anymore + SetValues(value_list); + } + } + return ValueObjectListDelegate::WindowDelegateDraw (window, force); + } + +protected: + Debugger &m_debugger; + StackID m_stack_id; +}; + +static const char * +CursesKeyToCString (int ch) +{ + static char g_desc[32]; + if (ch >= KEY_F0 && ch < KEY_F0 + 64) + { + snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); + return g_desc; + } + switch (ch) + { + case KEY_DOWN: return "down"; + case KEY_UP: return "up"; + case KEY_LEFT: return "left"; + case KEY_RIGHT: return "right"; + case KEY_HOME: return "home"; + case KEY_BACKSPACE: return "backspace"; + case KEY_DL: return "delete-line"; + case KEY_IL: return "insert-line"; + case KEY_DC: return "delete-char"; + case KEY_IC: return "insert-char"; + case KEY_CLEAR: return "clear"; + case KEY_EOS: return "clear-to-eos"; + case KEY_EOL: return "clear-to-eol"; + case KEY_SF: return "scroll-forward"; + case KEY_SR: return "scroll-backward"; + case KEY_NPAGE: return "page-down"; + case KEY_PPAGE: return "page-up"; + case KEY_STAB: return "set-tab"; + case KEY_CTAB: return "clear-tab"; + case KEY_CATAB: return "clear-all-tabs"; + case KEY_ENTER: return "enter"; + case KEY_PRINT: return "print"; + case KEY_LL: return "lower-left key"; + case KEY_A1: return "upper left of keypad"; + case KEY_A3: return "upper right of keypad"; + case KEY_B2: return "center of keypad"; + case KEY_C1: return "lower left of keypad"; + case KEY_C3: return "lower right of keypad"; + case KEY_BTAB: return "back-tab key"; + case KEY_BEG: return "begin key"; + case KEY_CANCEL: return "cancel key"; + case KEY_CLOSE: return "close key"; + case KEY_COMMAND: return "command key"; + case KEY_COPY: return "copy key"; + case KEY_CREATE: return "create key"; + case KEY_END: return "end key"; + case KEY_EXIT: return "exit key"; + case KEY_FIND: return "find key"; + case KEY_HELP: return "help key"; + case KEY_MARK: return "mark key"; + case KEY_MESSAGE: return "message key"; + case KEY_MOVE: return "move key"; + case KEY_NEXT: return "next key"; + case KEY_OPEN: return "open key"; + case KEY_OPTIONS: return "options key"; + case KEY_PREVIOUS: return "previous key"; + case KEY_REDO: return "redo key"; + case KEY_REFERENCE: return "reference key"; + case KEY_REFRESH: return "refresh key"; + case KEY_REPLACE: return "replace key"; + case KEY_RESTART: return "restart key"; + case KEY_RESUME: return "resume key"; + case KEY_SAVE: return "save key"; + case KEY_SBEG: return "shifted begin key"; + case KEY_SCANCEL: return "shifted cancel key"; + case KEY_SCOMMAND: return "shifted command key"; + case KEY_SCOPY: return "shifted copy key"; + case KEY_SCREATE: return "shifted create key"; + case KEY_SDC: return "shifted delete-character key"; + case KEY_SDL: return "shifted delete-line key"; + case KEY_SELECT: return "select key"; + case KEY_SEND: return "shifted end key"; + case KEY_SEOL: return "shifted clear-to-end-of-line key"; + case KEY_SEXIT: return "shifted exit key"; + case KEY_SFIND: return "shifted find key"; + case KEY_SHELP: return "shifted help key"; + case KEY_SHOME: return "shifted home key"; + case KEY_SIC: return "shifted insert-character key"; + case KEY_SLEFT: return "shifted left-arrow key"; + case KEY_SMESSAGE: return "shifted message key"; + case KEY_SMOVE: return "shifted move key"; + case KEY_SNEXT: return "shifted next key"; + case KEY_SOPTIONS: return "shifted options key"; + case KEY_SPREVIOUS: return "shifted previous key"; + case KEY_SPRINT: return "shifted print key"; + case KEY_SREDO: return "shifted redo key"; + case KEY_SREPLACE: return "shifted replace key"; + case KEY_SRIGHT: return "shifted right-arrow key"; + case KEY_SRSUME: return "shifted resume key"; + case KEY_SSAVE: return "shifted save key"; + case KEY_SSUSPEND: return "shifted suspend key"; + case KEY_SUNDO: return "shifted undo key"; + case KEY_SUSPEND: return "suspend key"; + case KEY_UNDO: return "undo key"; + case KEY_MOUSE: return "Mouse event has occurred"; + case KEY_RESIZE: return "Terminal resize event"; + case KEY_EVENT: return "We were interrupted by an event"; + case KEY_RETURN: return "return"; + case ' ': return "space"; + case KEY_ESCAPE: return "escape"; + default: + if (isprint(ch)) + snprintf(g_desc, sizeof(g_desc), "%c", ch); + else + snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); + return g_desc; + } + return NULL; +} + +HelpDialogDelegate::HelpDialogDelegate (const char *text, KeyHelp *key_help_array) : + m_text (), + m_first_visible_line (0) +{ + if (text && text[0]) + { + m_text.SplitIntoLines(text); + m_text.AppendString(""); + } + if (key_help_array) + { + for (KeyHelp *key = key_help_array; key->ch; ++key) + { + StreamString key_description; + key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), key->description); + m_text.AppendString(std::move(key_description.GetString())); + } + } +} + +HelpDialogDelegate::~HelpDialogDelegate() +{ +} + +bool +HelpDialogDelegate::WindowDelegateDraw (Window &window, bool force) +{ + window.Erase(); + const int window_height = window.GetHeight(); + int x = 2; + int y = 1; + const int min_y = y; + const int max_y = window_height - 1 - y; + const int num_visible_lines = max_y - min_y + 1; + const size_t num_lines = m_text.GetSize(); + const char *bottom_message; + if (num_lines <= num_visible_lines) + bottom_message = "Press any key to exit"; + else + bottom_message = "Use arrows to scroll, any other key to exit"; + window.DrawTitleBox(window.GetName(), bottom_message); + while (y <= max_y) + { + window.MoveCursor(x, y); + window.PutCStringTruncated(m_text.GetStringAtIndex(m_first_visible_line + y - min_y), 1); + ++y; + } + return true; +} + +HandleCharResult +HelpDialogDelegate::WindowDelegateHandleChar (Window &window, int key) +{ + bool done = false; + const size_t num_lines = m_text.GetSize(); + const size_t num_visible_lines = window.GetHeight() - 2; + + if (num_lines <= num_visible_lines) + { + done = true; + // If we have all lines visible and don't need scrolling, then any + // key press will cause us to exit + } + else + { + switch (key) + { + case KEY_UP: + if (m_first_visible_line > 0) + --m_first_visible_line; + break; + + case KEY_DOWN: + if (m_first_visible_line + num_visible_lines < num_lines) + ++m_first_visible_line; + break; + + case KEY_PPAGE: + case ',': + if (m_first_visible_line > 0) + { + if (m_first_visible_line >= num_visible_lines) + m_first_visible_line -= num_visible_lines; + else + m_first_visible_line = 0; + } + break; + case KEY_NPAGE: + case '.': + if (m_first_visible_line + num_visible_lines < num_lines) + { + m_first_visible_line += num_visible_lines; + if (m_first_visible_line > num_lines) + m_first_visible_line = num_lines - num_visible_lines; + } + break; + default: + done = true; + break; + } + } + if (done) + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; +} + +class ApplicationDelegate : + public WindowDelegate, + public MenuDelegate +{ +public: + enum { + eMenuID_LLDB = 1, + eMenuID_LLDBAbout, + eMenuID_LLDBExit, + + eMenuID_Target, + eMenuID_TargetCreate, + eMenuID_TargetDelete, + + eMenuID_Process, + eMenuID_ProcessAttach, + eMenuID_ProcessDetach, + eMenuID_ProcessLaunch, + eMenuID_ProcessContinue, + eMenuID_ProcessHalt, + eMenuID_ProcessKill, + + eMenuID_Thread, + eMenuID_ThreadStepIn, + eMenuID_ThreadStepOver, + eMenuID_ThreadStepOut, + + eMenuID_View, + eMenuID_ViewBacktrace, + eMenuID_ViewRegisters, + eMenuID_ViewSource, + eMenuID_ViewVariables, + + eMenuID_Help, + eMenuID_HelpGUIHelp + }; + + ApplicationDelegate (Application &app, Debugger &debugger) : + WindowDelegate (), + MenuDelegate (), + m_app (app), + m_debugger (debugger) + { + } + + virtual + ~ApplicationDelegate () + { + } + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + return false; // Drawing not handled, let standard window drawing happen + } + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int key) + { + if (key == '\t') + { + window.SelectNextWindowAsActive(); + return eKeyHandled; + } + return eKeyNotHandled; + } + + virtual MenuActionResult + MenuDelegateAction (Menu &menu) + { + switch (menu.GetIdentifier()) + { + case eMenuID_ThreadStepIn: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + exe_ctx.GetThreadRef().StepIn(true, true); + } + } + return MenuActionResult::Handled; + + case eMenuID_ThreadStepOut: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + exe_ctx.GetThreadRef().StepOut(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ThreadStepOver: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + exe_ctx.GetThreadRef().StepOver(true); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessContinue: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + process->Resume(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessKill: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Destroy(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessHalt: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Halt(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessDetach: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Detach(false); + } + } + return MenuActionResult::Handled; + + case eMenuID_Process: + { + // Populate the menu with all of the threads if the process is stopped when + // the Process menu gets selected and is about to display its submenu. + Menus &submenus = menu.GetSubmenus(); + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + { + if (submenus.size() == 7) + menu.AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); + else if (submenus.size() > 8) + submenus.erase (submenus.begin() + 8, submenus.end()); + + ThreadList &threads = process->GetThreadList(); + Mutex::Locker locker (threads.GetMutex()); + size_t num_threads = threads.GetSize(); + for (size_t i=0; i<num_threads; ++i) + { + ThreadSP thread_sp = threads.GetThreadAtIndex(i); + char menu_char = '\0'; + if (i < 9) + menu_char = '1' + i; + StreamString thread_menu_title; + thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); + const char *thread_name = thread_sp->GetName(); + if (thread_name && thread_name[0]) + thread_menu_title.Printf (" %s", thread_name); + else + { + const char *queue_name = thread_sp->GetQueueName(); + if (queue_name && queue_name[0]) + thread_menu_title.Printf (" %s", queue_name); + } + menu.AddSubmenu (MenuSP (new Menu(thread_menu_title.GetString().c_str(), NULL, menu_char, thread_sp->GetID()))); + } + } + else if (submenus.size() > 7) + { + // Remove the separator and any other thread submenu items + // that were previously added + submenus.erase (submenus.begin() + 7, submenus.end()); + } + // Since we are adding and removing items we need to recalculate the name lengths + menu.RecalculateNameLengths(); + } + return MenuActionResult::Handled; + + case eMenuID_ViewVariables: + { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (variables_window_sp) + { + const Rect variables_bounds = variables_window_sp->GetBounds(); + + main_window_sp->RemoveSubWindow(variables_window_sp.get()); + + if (registers_window_sp) + { + // We have a registers window, so give all the area back to the registers window + Rect registers_bounds = variables_bounds; + registers_bounds.size.width = source_bounds.size.width; + registers_window_sp->SetBounds(registers_bounds); + } + else + { + // We have no registers window showing so give the bottom + // area back to the source view + source_window_sp->Resize (source_bounds.size.width, + source_bounds.size.height + variables_bounds.size.height); + } + } + else + { + Rect new_variables_rect; + if (registers_window_sp) + { + // We have a registers window so split the area of the registers + // window into two columns where the left hand side will be the + // variables and the right hand side will be the registers + const Rect variables_bounds = registers_window_sp->GetBounds(); + Rect new_registers_rect; + variables_bounds.VerticalSplitPercentage (0.50, new_variables_rect, new_registers_rect); + registers_window_sp->SetBounds (new_registers_rect); + } + else + { + // No variables window, grab the bottom part of the source window + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage (0.70, new_source_rect, new_variables_rect); + source_window_sp->SetBounds (new_source_rect); + } + WindowSP new_window_sp = main_window_sp->CreateSubWindow ("Variables", + new_variables_rect, + false); + new_window_sp->SetDelegate (WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + + case eMenuID_ViewRegisters: + { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (registers_window_sp) + { + if (variables_window_sp) + { + const Rect variables_bounds = variables_window_sp->GetBounds(); + + // We have a variables window, so give all the area back to the variables window + variables_window_sp->Resize (variables_bounds.size.width + registers_window_sp->GetWidth(), + variables_bounds.size.height); + } + else + { + // We have no variables window showing so give the bottom + // area back to the source view + source_window_sp->Resize (source_bounds.size.width, + source_bounds.size.height + registers_window_sp->GetHeight()); + } + main_window_sp->RemoveSubWindow(registers_window_sp.get()); + } + else + { + Rect new_regs_rect; + if (variables_window_sp) + { + // We have a variables window, split it into two columns + // where the left hand side will be the variables and the + // right hand side will be the registers + const Rect variables_bounds = variables_window_sp->GetBounds(); + Rect new_vars_rect; + variables_bounds.VerticalSplitPercentage (0.50, new_vars_rect, new_regs_rect); + variables_window_sp->SetBounds (new_vars_rect); + } + else + { + // No registers window, grab the bottom part of the source window + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage (0.70, new_source_rect, new_regs_rect); + source_window_sp->SetBounds (new_source_rect); + } + WindowSP new_window_sp = main_window_sp->CreateSubWindow ("Registers", + new_regs_rect, + false); + new_window_sp->SetDelegate (WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + + case eMenuID_HelpGUIHelp: + return MenuActionResult::Handled; + + default: + break; + } + + return MenuActionResult::NotHandled; + } +protected: + Application &m_app; + Debugger &m_debugger; +}; + + +class StatusBarWindowDelegate : public WindowDelegate +{ +public: + StatusBarWindowDelegate (Debugger &debugger) : + m_debugger (debugger) + { + } + + virtual + ~StatusBarWindowDelegate () + { + } + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + Thread *thread = exe_ctx.GetThreadPtr(); + StackFrame *frame = exe_ctx.GetFramePtr(); + window.Erase(); + window.SetBackground(2); + window.MoveCursor (0, 0); + if (process) + { + const StateType state = process->GetState(); + window.Printf ("Process: %5" PRIu64 " %10s", process->GetID(), StateAsCString(state)); + + if (StateIsStoppedState(state, true)) + { + window.MoveCursor (40, 0); + if (thread) + window.Printf ("Thread: 0x%4.4" PRIx64, thread->GetID()); + + window.MoveCursor (60, 0); + if (frame) + window.Printf ("Frame: %3u PC = 0x%16.16" PRIx64, frame->GetFrameIndex(), frame->GetFrameCodeAddress().GetOpcodeLoadAddress (exe_ctx.GetTargetPtr())); + } + else if (state == eStateExited) + { + const char *exit_desc = process->GetExitDescription(); + const int exit_status = process->GetExitStatus(); + if (exit_desc && exit_desc[0]) + window.Printf (" with status = %i (%s)", exit_status, exit_desc); + else + window.Printf (" with status = %i", exit_status); + } + } + window.DeferredRefresh(); + return true; + } + +protected: + Debugger &m_debugger; +}; + +class SourceFileWindowDelegate : public WindowDelegate +{ +public: + SourceFileWindowDelegate (Debugger &debugger) : + WindowDelegate (), + m_debugger (debugger), + m_sc (), + m_file_sp (), + m_disassembly_scope (NULL), + m_disassembly_sp (), + m_disassembly_range (), + m_line_width (4), + m_selected_line (0), + m_pc_line (0), + m_stop_id (0), + m_frame_idx (UINT32_MAX), + m_first_visible_line (0), + m_min_x (0), + m_min_y (0), + m_max_x (0), + m_max_y (0) + { + } + + + virtual + ~SourceFileWindowDelegate() + { + } + + void + Update (const SymbolContext &sc) + { + m_sc = sc; + } + + uint32_t + NumVisibleLines () const + { + return m_max_y - m_min_y; + } + + virtual const char * + WindowDelegateGetHelpText () + { + return "Source/Disassembly window keyboard shortcuts:"; + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + static curses::KeyHelp g_source_view_key_help[] = { + { KEY_RETURN, "Run to selected line with one shot breakpoint" }, + { KEY_UP, "Select previous source line" }, + { KEY_DOWN, "Select next source line" }, + { KEY_PPAGE, "Page up" }, + { KEY_NPAGE, "Page down" }, + { 'b', "Set breakpoint on selected source/disassembly line" }, + { 'c', "Continue process" }, + { 'd', "Detach and resume process" }, + { 'D', "Detach with process suspended" }, + { 'h', "Show help dialog" }, + { 'k', "Kill process" }, + { 'n', "Step over (source line)" }, + { 'N', "Step over (single instruction)" }, + { 'o', "Step out" }, + { 's', "Step in (source line)" }, + { 'S', "Step in (single instruction)" }, + { ',', "Page up" }, + { '.', "Page down" }, + { '\0', NULL } + }; + return g_source_view_key_help; + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + Thread *thread = NULL; + + bool update_location = false; + if (process) + { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) + { + // We are stopped, so it is ok to + update_location = true; + } + } + + m_min_x = 1; + m_min_y = 1; + m_max_x = window.GetMaxX()-1; + m_max_y = window.GetMaxY()-1; + + const uint32_t num_visible_lines = NumVisibleLines(); + StackFrameSP frame_sp; + bool set_selected_line_to_pc = false; + + + if (update_location) + { + + const bool process_alive = process ? process->IsAlive() : false; + bool thread_changed = false; + if (process_alive) + { + thread = exe_ctx.GetThreadPtr(); + if (thread) + { + frame_sp = thread->GetSelectedFrame(); + auto tid = thread->GetID(); + thread_changed = tid != m_tid; + m_tid = tid; + } + else + { + if (m_tid != LLDB_INVALID_THREAD_ID) + { + thread_changed = true; + m_tid = LLDB_INVALID_THREAD_ID; + } + } + } + const uint32_t stop_id = process ? process->GetStopID() : 0; + const bool stop_id_changed = stop_id != m_stop_id; + bool frame_changed = false; + m_stop_id = stop_id; + if (frame_sp) + { + m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + const uint32_t frame_idx = frame_sp->GetFrameIndex(); + frame_changed = frame_idx != m_frame_idx; + m_frame_idx = frame_idx; + } + else + { + m_sc.Clear(true); + frame_changed = m_frame_idx != UINT32_MAX; + m_frame_idx = UINT32_MAX; + } + + const bool context_changed = thread_changed || frame_changed || stop_id_changed; + + if (process_alive) + { + if (m_sc.line_entry.IsValid()) + { + m_pc_line = m_sc.line_entry.line; + if (m_pc_line != UINT32_MAX) + --m_pc_line; // Convert to zero based line number... + // Update the selected line if the stop ID changed... + if (context_changed) + m_selected_line = m_pc_line; + + if (m_file_sp && m_file_sp->FileSpecMatches(m_sc.line_entry.file)) + { + // Same file, nothing to do, we should either have the + // lines or not (source file missing) + if (m_selected_line >= m_first_visible_line) + { + if (m_selected_line >= m_first_visible_line + num_visible_lines) + m_first_visible_line = m_selected_line - 10; + } + else + { + if (m_selected_line > 10) + m_first_visible_line = m_selected_line - 10; + else + m_first_visible_line = 0; + } + } + else + { + // File changed, set selected line to the line with the PC + m_selected_line = m_pc_line; + m_file_sp = m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); + if (m_file_sp) + { + const size_t num_lines = m_file_sp->GetNumLines(); + int m_line_width = 1; + for (size_t n = num_lines; n >= 10; n = n / 10) + ++m_line_width; + + snprintf (m_line_format, sizeof(m_line_format), " %%%iu ", m_line_width); + if (num_lines < num_visible_lines || m_selected_line < num_visible_lines) + m_first_visible_line = 0; + else + m_first_visible_line = m_selected_line - 10; + } + } + } + else + { + m_file_sp.reset(); + } + + if (!m_file_sp || m_file_sp->GetNumLines() == 0) + { + // Show disassembly + bool prefer_file_cache = false; + if (m_sc.function) + { + if (m_disassembly_scope != m_sc.function) + { + m_disassembly_scope = m_sc.function; + m_disassembly_sp = m_sc.function->GetInstructions (exe_ctx, NULL, prefer_file_cache); + if (m_disassembly_sp) + { + set_selected_line_to_pc = true; + m_disassembly_range = m_sc.function->GetAddressRange(); + } + else + { + m_disassembly_range.Clear(); + } + } + else + { + set_selected_line_to_pc = context_changed; + } + } + else if (m_sc.symbol) + { + if (m_disassembly_scope != m_sc.symbol) + { + m_disassembly_scope = m_sc.symbol; + m_disassembly_sp = m_sc.symbol->GetInstructions (exe_ctx, NULL, prefer_file_cache); + if (m_disassembly_sp) + { + set_selected_line_to_pc = true; + m_disassembly_range.GetBaseAddress() = m_sc.symbol->GetAddress(); + m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); + } + else + { + m_disassembly_range.Clear(); + } + } + else + { + set_selected_line_to_pc = context_changed; + } + } + } + } + else + { + m_pc_line = UINT32_MAX; + } + } + + + window.Erase(); + window.DrawTitleBox ("Sources"); + + + Target *target = exe_ctx.GetTargetPtr(); + const size_t num_source_lines = GetNumSourceLines(); + if (num_source_lines > 0) + { + // Display source + BreakpointLines bp_lines; + if (target) + { + BreakpointList &bp_list = target->GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx=0; bp_idx<num_bps; ++bp_idx) + { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx=0; bp_loc_idx<num_bps_locs; ++bp_loc_idx) + { + BreakpointLocationSP bp_loc_sp = bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry (bp_loc_line_entry)) + { + if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) + { + bp_lines.insert(bp_loc_line_entry.line); + } + } + } + } + } + + + const attr_t selected_highlight_attr = A_REVERSE; + const attr_t pc_highlight_attr = COLOR_PAIR(1); + + for (int i=0; i<num_visible_lines; ++i) + { + const uint32_t curr_line = m_first_visible_line + i; + if (curr_line < num_source_lines) + { + const int line_y = 1+i; + window.MoveCursor(1, line_y); + const bool is_pc_line = curr_line == m_pc_line; + const bool line_is_selected = m_selected_line == curr_line; + // Highlight the line as the PC line first, then if the selected line + // isn't the same as the PC line, highlight it differently + attr_t highlight_attr = 0; + attr_t bp_attr = 0; + if (is_pc_line) + highlight_attr = pc_highlight_attr; + else if (line_is_selected) + highlight_attr = selected_highlight_attr; + + if (bp_lines.find(curr_line+1) != bp_lines.end()) + bp_attr = COLOR_PAIR(2); + + if (bp_attr) + window.AttributeOn(bp_attr); + + window.Printf (m_line_format, curr_line + 1); + + if (bp_attr) + window.AttributeOff(bp_attr); + + window.PutChar(ACS_VLINE); + // Mark the line with the PC with a diamond + if (is_pc_line) + window.PutChar(ACS_DIAMOND); + else + window.PutChar(' '); + + if (highlight_attr) + window.AttributeOn(highlight_attr); + const uint32_t line_len = m_file_sp->GetLineLength(curr_line + 1, false); + if (line_len > 0) + window.PutCString(m_file_sp->PeekLineData(curr_line + 1), line_len); + + if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) + { + StopInfoSP stop_info_sp; + if (thread) + stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp) + { + const char *stop_description = stop_info_sp->GetDescription(); + if (stop_description && stop_description[0]) + { + size_t stop_description_len = strlen(stop_description); + int desc_x = window.GetWidth() - stop_description_len - 16; + window.Printf ("%*s", desc_x - window.GetCursorX(), ""); + //window.MoveCursor(window.GetWidth() - stop_description_len - 15, line_y); + window.Printf ("<<< Thread %u: %s ", thread->GetIndexID(), stop_description); + } + } + else + { + window.Printf ("%*s", window.GetWidth() - window.GetCursorX() - 1, ""); + } + } + if (highlight_attr) + window.AttributeOff(highlight_attr); + + } + else + { + break; + } + } + } + else + { + size_t num_disassembly_lines = GetNumDisassemblyLines(); + if (num_disassembly_lines > 0) + { + // Display disassembly + BreakpointAddrs bp_file_addrs; + Target *target = exe_ctx.GetTargetPtr(); + if (target) + { + BreakpointList &bp_list = target->GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx=0; bp_idx<num_bps; ++bp_idx) + { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx=0; bp_loc_idx<num_bps_locs; ++bp_loc_idx) + { + BreakpointLocationSP bp_loc_sp = bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + const lldb::addr_t file_addr = bp_loc_sp->GetAddress().GetFileAddress(); + if (file_addr != LLDB_INVALID_ADDRESS) + { + if (m_disassembly_range.ContainsFileAddress(file_addr)) + bp_file_addrs.insert(file_addr); + } + } + } + } + + + const attr_t selected_highlight_attr = A_REVERSE; + const attr_t pc_highlight_attr = COLOR_PAIR(1); + + StreamString strm; + + InstructionList &insts = m_disassembly_sp->GetInstructionList(); + Address pc_address; + + if (frame_sp) + pc_address = frame_sp->GetFrameCodeAddress(); + const uint32_t pc_idx = pc_address.IsValid() ? insts.GetIndexOfInstructionAtAddress (pc_address) : UINT32_MAX; + if (set_selected_line_to_pc) + { + m_selected_line = pc_idx; + } + + const uint32_t non_visible_pc_offset = (num_visible_lines / 5); + if (m_first_visible_line >= num_disassembly_lines) + m_first_visible_line = 0; + + if (pc_idx < num_disassembly_lines) + { + if (pc_idx < m_first_visible_line || + pc_idx >= m_first_visible_line + num_visible_lines) + m_first_visible_line = pc_idx - non_visible_pc_offset; + } + + for (size_t i=0; i<num_visible_lines; ++i) + { + const uint32_t inst_idx = m_first_visible_line + i; + Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); + if (!inst) + break; + + window.MoveCursor(1, i+1); + const bool is_pc_line = frame_sp && inst_idx == pc_idx; + const bool line_is_selected = m_selected_line == inst_idx; + // Highlight the line as the PC line first, then if the selected line + // isn't the same as the PC line, highlight it differently + attr_t highlight_attr = 0; + attr_t bp_attr = 0; + if (is_pc_line) + highlight_attr = pc_highlight_attr; + else if (line_is_selected) + highlight_attr = selected_highlight_attr; + + if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != bp_file_addrs.end()) + bp_attr = COLOR_PAIR(2); + + if (bp_attr) + window.AttributeOn(bp_attr); + + window.Printf (" 0x%16.16llx ", inst->GetAddress().GetLoadAddress(target)); + + if (bp_attr) + window.AttributeOff(bp_attr); + + window.PutChar(ACS_VLINE); + // Mark the line with the PC with a diamond + if (is_pc_line) + window.PutChar(ACS_DIAMOND); + else + window.PutChar(' '); + + if (highlight_attr) + window.AttributeOn(highlight_attr); + + const char *mnemonic = inst->GetMnemonic(&exe_ctx); + const char *operands = inst->GetOperands(&exe_ctx); + const char *comment = inst->GetComment(&exe_ctx); + + if (mnemonic && mnemonic[0] == '\0') + mnemonic = NULL; + if (operands && operands[0] == '\0') + operands = NULL; + if (comment && comment[0] == '\0') + comment = NULL; + + strm.Clear(); + + if (mnemonic && operands && comment) + strm.Printf ("%-8s %-25s ; %s", mnemonic, operands, comment); + else if (mnemonic && operands) + strm.Printf ("%-8s %s", mnemonic, operands); + else if (mnemonic) + strm.Printf ("%s", mnemonic); + + int right_pad = 1; + window.PutCStringTruncated(strm.GetString().c_str(), right_pad); + + if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) + { + StopInfoSP stop_info_sp; + if (thread) + stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp) + { + const char *stop_description = stop_info_sp->GetDescription(); + if (stop_description && stop_description[0]) + { + size_t stop_description_len = strlen(stop_description); + int desc_x = window.GetWidth() - stop_description_len - 16; + window.Printf ("%*s", desc_x - window.GetCursorX(), ""); + //window.MoveCursor(window.GetWidth() - stop_description_len - 15, line_y); + window.Printf ("<<< Thread %u: %s ", thread->GetIndexID(), stop_description); + } + } + else + { + window.Printf ("%*s", window.GetWidth() - window.GetCursorX() - 1, ""); + } + } + if (highlight_attr) + window.AttributeOff(highlight_attr); + } + } + } + window.DeferredRefresh(); + return true; // Drawing handled + } + + size_t + GetNumLines () + { + size_t num_lines = GetNumSourceLines(); + if (num_lines == 0) + num_lines = GetNumDisassemblyLines(); + return num_lines; + } + + size_t + GetNumSourceLines () const + { + if (m_file_sp) + return m_file_sp->GetNumLines(); + return 0; + } + size_t + GetNumDisassemblyLines () const + { + if (m_disassembly_sp) + return m_disassembly_sp->GetInstructionList().GetSize(); + return 0; + } + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int c) + { + const uint32_t num_visible_lines = NumVisibleLines(); + const size_t num_lines = GetNumLines (); + + switch (c) + { + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_line > num_visible_lines) + m_first_visible_line -= num_visible_lines; + else + m_first_visible_line = 0; + m_selected_line = m_first_visible_line; + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + { + if (m_first_visible_line + num_visible_lines < num_lines) + m_first_visible_line += num_visible_lines; + else if (num_lines < num_visible_lines) + m_first_visible_line = 0; + else + m_first_visible_line = num_lines - num_visible_lines; + m_selected_line = m_first_visible_line; + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_line > 0) + { + m_selected_line--; + if (m_first_visible_line > m_selected_line) + m_first_visible_line = m_selected_line; + } + return eKeyHandled; + + case KEY_DOWN: + if (m_selected_line + 1 < num_lines) + { + m_selected_line++; + if (m_first_visible_line + num_visible_lines < m_selected_line) + m_first_visible_line++; + } + return eKeyHandled; + + case '\r': + case '\n': + case KEY_ENTER: + // Set a breakpoint and run to the line using a one shot breakpoint + if (GetNumSourceLines() > 0) + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) + { + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (NULL, // Don't limit the breakpoint to certain modules + m_file_sp->GetFileSpec(), // Source file + m_selected_line + 1, // Source line number (m_selected_line is zero based) + eLazyBoolCalculate, // Check inlines using global setting + eLazyBoolCalculate, // Skip prologue using global setting, + false, // internal + false); // request_hardware + // Make breakpoint one shot + bp_sp->GetOptions()->SetOneShot(true); + exe_ctx.GetProcessRef().Resume(); + } + } + else if (m_selected_line < GetNumDisassemblyLines()) + { + const Instruction *inst = m_disassembly_sp->GetInstructionList().GetInstructionAtIndex(m_selected_line).get(); + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasTargetScope()) + { + Address addr = inst->GetAddress(); + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (addr, // lldb_private::Address + false, // internal + false); // request_hardware + // Make breakpoint one shot + bp_sp->GetOptions()->SetOneShot(true); + exe_ctx.GetProcessRef().Resume(); + } + } + return eKeyHandled; + + case 'b': // 'b' == toggle breakpoint on currently selected line + if (m_selected_line < GetNumSourceLines()) + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasTargetScope()) + { + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (NULL, // Don't limit the breakpoint to certain modules + m_file_sp->GetFileSpec(), // Source file + m_selected_line + 1, // Source line number (m_selected_line is zero based) + eLazyBoolCalculate, // Check inlines using global setting + eLazyBoolCalculate, // Skip prologue using global setting, + false, // internal + false); // request_hardware + } + } + else if (m_selected_line < GetNumDisassemblyLines()) + { + const Instruction *inst = m_disassembly_sp->GetInstructionList().GetInstructionAtIndex(m_selected_line).get(); + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasTargetScope()) + { + Address addr = inst->GetAddress(); + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (addr, // lldb_private::Address + false, // internal + false); // request_hardware + } + } + return eKeyHandled; + + case 'd': // 'd' == detach and let run + case 'D': // 'D' == detach and keep stopped + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Detach(c == 'D'); + } + return eKeyHandled; + + case 'k': + // 'k' == kill + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Destroy(); + } + return eKeyHandled; + + case 'c': + // 'c' == continue + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Resume(); + } + return eKeyHandled; + + case 'o': + // 'o' == step out + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) + { + exe_ctx.GetThreadRef().StepOut(); + } + } + return eKeyHandled; + case 'n': // 'n' == step over + case 'N': // 'N' == step over instruction + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) + { + bool source_step = (c == 'n'); + exe_ctx.GetThreadRef().StepOver(source_step); + } + } + return eKeyHandled; + case 's': // 's' == step into + case 'S': // 'S' == step into instruction + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) + { + bool source_step = (c == 's'); + bool avoid_code_without_debug_info = true; + exe_ctx.GetThreadRef().StepIn(source_step, avoid_code_without_debug_info); + } + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow (); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + typedef std::set<uint32_t> BreakpointLines; + typedef std::set<lldb::addr_t> BreakpointAddrs; + + Debugger &m_debugger; + SymbolContext m_sc; + SourceManager::FileSP m_file_sp; + SymbolContextScope *m_disassembly_scope; + lldb::DisassemblerSP m_disassembly_sp; + AddressRange m_disassembly_range; + lldb::user_id_t m_tid; + char m_line_format[8]; + int m_line_width; + uint32_t m_selected_line; // The selected line + uint32_t m_pc_line; // The line with the PC + uint32_t m_stop_id; + uint32_t m_frame_idx; + int m_first_visible_line; + int m_min_x; + int m_min_y; + int m_max_x; + int m_max_y; + +}; + +DisplayOptions ValueObjectListDelegate::g_options = { true }; + +IOHandlerCursesGUI::IOHandlerCursesGUI (Debugger &debugger) : + IOHandler (debugger) +{ +} + +void +IOHandlerCursesGUI::Activate () +{ + IOHandler::Activate(); + if (!m_app_ap) + { + m_app_ap.reset (new Application (GetInputFILE(), GetOutputFILE())); + + + // This is both a window and a menu delegate + std::shared_ptr<ApplicationDelegate> app_delegate_sp(new ApplicationDelegate(*m_app_ap, m_debugger)); + + MenuDelegateSP app_menu_delegate_sp = std::static_pointer_cast<MenuDelegate>(app_delegate_sp); + MenuSP lldb_menu_sp(new Menu("LLDB" , "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); + MenuSP exit_menuitem_sp(new Menu("Exit", NULL, 'x', ApplicationDelegate::eMenuID_LLDBExit)); + exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); + lldb_menu_sp->AddSubmenu (MenuSP (new Menu("About LLDB", NULL, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); + lldb_menu_sp->AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); + lldb_menu_sp->AddSubmenu (exit_menuitem_sp); + + MenuSP target_menu_sp(new Menu("Target" ,"F2", KEY_F(2), ApplicationDelegate::eMenuID_Target)); + target_menu_sp->AddSubmenu (MenuSP (new Menu("Create", NULL, 'c', ApplicationDelegate::eMenuID_TargetCreate))); + target_menu_sp->AddSubmenu (MenuSP (new Menu("Delete", NULL, 'd', ApplicationDelegate::eMenuID_TargetDelete))); + + MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), ApplicationDelegate::eMenuID_Process)); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Attach" , NULL, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Detach" , NULL, 'd', ApplicationDelegate::eMenuID_ProcessDetach))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Launch" , NULL, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); + process_menu_sp->AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Continue", NULL, 'c', ApplicationDelegate::eMenuID_ProcessContinue))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Halt" , NULL, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Kill" , NULL, 'k', ApplicationDelegate::eMenuID_ProcessKill))); + + MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), ApplicationDelegate::eMenuID_Thread)); + thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step In" , NULL, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); + thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step Over", NULL, 'v', ApplicationDelegate::eMenuID_ThreadStepOver))); + thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step Out" , NULL, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); + + MenuSP view_menu_sp(new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); + view_menu_sp->AddSubmenu (MenuSP (new Menu("Backtrace", NULL, 'b', ApplicationDelegate::eMenuID_ViewBacktrace))); + view_menu_sp->AddSubmenu (MenuSP (new Menu("Registers", NULL, 'r', ApplicationDelegate::eMenuID_ViewRegisters))); + view_menu_sp->AddSubmenu (MenuSP (new Menu("Source" , NULL, 's', ApplicationDelegate::eMenuID_ViewSource))); + view_menu_sp->AddSubmenu (MenuSP (new Menu("Variables", NULL, 'v', ApplicationDelegate::eMenuID_ViewVariables))); + + MenuSP help_menu_sp(new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); + help_menu_sp->AddSubmenu (MenuSP (new Menu("GUI Help", NULL, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); + + m_app_ap->Initialize(); + WindowSP &main_window_sp = m_app_ap->GetMainWindow(); + + MenuSP menubar_sp(new Menu(Menu::Type::Bar)); + menubar_sp->AddSubmenu (lldb_menu_sp); + menubar_sp->AddSubmenu (target_menu_sp); + menubar_sp->AddSubmenu (process_menu_sp); + menubar_sp->AddSubmenu (thread_menu_sp); + menubar_sp->AddSubmenu (view_menu_sp); + menubar_sp->AddSubmenu (help_menu_sp); + menubar_sp->SetDelegate(app_menu_delegate_sp); + + Rect content_bounds = main_window_sp->GetFrame(); + Rect menubar_bounds = content_bounds.MakeMenuBar(); + Rect status_bounds = content_bounds.MakeStatusBar(); + Rect source_bounds; + Rect variables_bounds; + Rect threads_bounds; + Rect source_variables_bounds; + content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, threads_bounds); + source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, variables_bounds); + + WindowSP menubar_window_sp = main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); + // Let the menubar get keys if the active window doesn't handle the + // keys that are typed so it can respond to menubar key presses. + menubar_window_sp->SetCanBeActive(false); // Don't let the menubar become the active window + menubar_window_sp->SetDelegate(menubar_sp); + + WindowSP source_window_sp (main_window_sp->CreateSubWindow("Source", + source_bounds, + true)); + WindowSP variables_window_sp (main_window_sp->CreateSubWindow("Variables", + variables_bounds, + false)); + WindowSP threads_window_sp (main_window_sp->CreateSubWindow("Threads", + threads_bounds, + false)); + WindowSP status_window_sp (main_window_sp->CreateSubWindow("Status", + status_bounds, + false)); + status_window_sp->SetCanBeActive(false); // Don't let the status bar become the active window + main_window_sp->SetDelegate (std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); + source_window_sp->SetDelegate (WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); + variables_window_sp->SetDelegate (WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + TreeDelegateSP thread_delegate_sp (new ThreadTreeDelegate(m_debugger)); + threads_window_sp->SetDelegate (WindowDelegateSP(new TreeWindowDelegate(m_debugger, thread_delegate_sp))); + status_window_sp->SetDelegate (WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); + + init_pair (1, COLOR_WHITE , COLOR_BLUE ); + init_pair (2, COLOR_BLACK , COLOR_WHITE ); + init_pair (3, COLOR_MAGENTA , COLOR_WHITE ); + init_pair (4, COLOR_MAGENTA , COLOR_BLACK ); + init_pair (5, COLOR_RED , COLOR_BLACK ); + + } +} + +void +IOHandlerCursesGUI::Deactivate () +{ + m_app_ap->Terminate(); +} + +void +IOHandlerCursesGUI::Run () +{ + m_app_ap->Run(m_debugger); + SetIsDone(true); +} + + +IOHandlerCursesGUI::~IOHandlerCursesGUI () +{ + +} + +void +IOHandlerCursesGUI::Hide () +{ +} + + +void +IOHandlerCursesGUI::Refresh () +{ +} + + +void +IOHandlerCursesGUI::Interrupt () +{ +} + + +void +IOHandlerCursesGUI::GotEOF() +{ +} + diff --git a/lldb/source/Core/InputReader.cpp b/lldb/source/Core/InputReader.cpp deleted file mode 100644 index cbaa671bcba..00000000000 --- a/lldb/source/Core/InputReader.cpp +++ /dev/null @@ -1,387 +0,0 @@ -//===-- InputReader.cpp -----------------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "lldb/lldb-python.h" - -#include <string> - -#include "lldb/Core/InputReader.h" -#include "lldb/Core/Debugger.h" -#include "lldb/Interpreter/CommandInterpreter.h" - -using namespace lldb; -using namespace lldb_private; - -InputReader::InputReader (Debugger &debugger) : - m_debugger (debugger), - m_callback (NULL), - m_callback_baton (NULL), - m_end_token (), - m_granularity (eInputReaderGranularityInvalid), - m_done (true), - m_echo (true), - m_active (false), - m_reader_done (false), - m_user_input(), - m_save_user_input(false) -{ -} - -InputReader::~InputReader () -{ -} - -Error -InputReader::Initialize -( - Callback callback, - void *baton, - lldb::InputReaderGranularity granularity, - const char *end_token, - const char *prompt, - bool echo -) -{ - Error err; - m_callback = callback; - m_callback_baton = baton, - m_granularity = granularity; - if (end_token != NULL) - m_end_token = end_token; - if (prompt != NULL) - m_prompt = prompt; - m_done = true; - m_echo = echo; - - if (m_granularity == eInputReaderGranularityInvalid) - { - err.SetErrorString ("Invalid read token size: Reader must be initialized with a token size other than 'eInputReaderGranularityInvalid'."); - } - else - if (end_token != NULL && granularity != eInputReaderGranularityInvalid) - { - if (granularity == eInputReaderGranularityByte) - { - // Check to see if end_token is longer than one byte. - - if (strlen (end_token) > 1) - { - err.SetErrorString ("Invalid end token: End token cannot be larger than specified token size (byte)."); - } - } - else if (granularity == eInputReaderGranularityWord) - { - // Check to see if m_end_token contains any white space (i.e. is multiple words). - - const char *white_space = " \t\n"; - size_t pos = m_end_token.find_first_of (white_space); - if (pos != std::string::npos) - { - err.SetErrorString ("Invalid end token: End token cannot be larger than specified token size (word)."); - } - } - else - { - // Check to see if m_end_token contains any newlines; cannot handle multi-line end tokens. - - size_t pos = m_end_token.find_first_of ('\n'); - if (pos != std::string::npos) - { - err.SetErrorString ("Invalid end token: End token cannot contain a newline."); - } - } - } - - m_done = err.Fail(); - - return err; -} - -size_t -InputReader::HandleRawBytes (const char *bytes, size_t bytes_len) -{ - const char *end_token = NULL; - - if (m_end_token.empty() == false) - { - end_token = ::strstr (bytes, m_end_token.c_str()); - if (end_token >= bytes + bytes_len) - end_token = NULL; - } - - const char *p = bytes; - const char *end = bytes + bytes_len; - - switch (m_granularity) - { - case eInputReaderGranularityInvalid: - break; - - case eInputReaderGranularityByte: - while (p < end) - { - if (end_token == p) - { - p += m_end_token.size(); - SetIsDone(true); - break; - } - - if (m_callback (m_callback_baton, *this, eInputReaderGotToken, p, 1) == 0) - break; - ++p; - if (IsDone()) - break; - } - // Return how many bytes were handled. - return p - bytes; - break; - - - case eInputReaderGranularityWord: - { - char quote = '\0'; - const char *word_start = NULL; - bool send_word = false; - for (; p < end; ++p, send_word = false) - { - if (end_token && end_token == p) - { - m_end_token.size(); - SetIsDone(true); - break; - } - - const char ch = *p; - if (isspace(ch) && (!quote || (quote == ch && p[-1] != '\\'))) - { - // We have a space character or the terminating quote - send_word = word_start != NULL; - quote = '\0'; - } - else if (quote) - { - // We are in the middle of a quoted character - continue; - } - else if (ch == '"' || ch == '\'' || ch == '`') - quote = ch; - else if (word_start == NULL) - { - // We have the first character in a word - word_start = p; - } - - if (send_word) - { - const size_t word_len = p - word_start; - size_t bytes_handled = m_callback (m_callback_baton, - *this, - eInputReaderGotToken, - word_start, - word_len); - - if (bytes_handled != word_len) - return word_start - bytes + bytes_handled; - - if (IsDone()) - return p - bytes; - } - } - } - break; - - - case eInputReaderGranularityLine: - { - const char *line_start = bytes; - const char *end_line = NULL; - while (p < end) - { - const char ch = *p; - if (ch == '\n' || ch == '\r') - { - size_t line_length = p - line_start; - // Now skip the newline character - ++p; - // Skip a complete DOS newline if we run into one - if (ch == 0xd && p < end && *p == 0xa) - ++p; - - if (line_start <= end_token && end_token < line_start + line_length) - { - SetIsDone(true); - m_callback (m_callback_baton, - *this, - eInputReaderGotToken, - line_start, - end_token - line_start); - - return p - bytes; - } - - size_t bytes_handled = m_callback (m_callback_baton, - *this, - eInputReaderGotToken, - line_start, - line_length); - - end_line = p; - - if (bytes_handled != line_length) - { - // The input reader wasn't able to handle all the data - return line_start - bytes + bytes_handled; - } - - - if (IsDone()) - return p - bytes; - - line_start = p; - } - else - { - ++p; - } - } - - if (end_line) - return end_line - bytes; - } - break; - - - case eInputReaderGranularityAll: - { - // Nothing should be handle unless we see our end token - if (end_token) - { - size_t length = end_token - bytes; - size_t bytes_handled = m_callback (m_callback_baton, - *this, - eInputReaderGotToken, - bytes, - length); - m_done = true; - - p += bytes_handled + m_end_token.size(); - - // Consume any white space, such as newlines, beyond the end token - - while (p < end && isspace(*p)) - ++p; - - if (bytes_handled != length) - return bytes_handled; - else - { - return p - bytes; - //return bytes_handled + m_end_token.size(); - } - } - return 0; - } - break; - } - return 0; -} - -const char * -InputReader::GetPrompt () const -{ - if (!m_prompt.empty()) - return m_prompt.c_str(); - else - return NULL; -} - -void -InputReader::RefreshPrompt () -{ - if (m_debugger.GetCommandInterpreter().GetBatchCommandMode()) - return; - - if (!m_prompt.empty()) - { - File &out_file = m_debugger.GetOutputFile(); - if (out_file.IsValid()) - { - out_file.Printf ("%s", m_prompt.c_str()); - out_file.Flush(); - } - } -} - -void -InputReader::Notify (InputReaderAction notification) -{ - switch (notification) - { - case eInputReaderActivate: - case eInputReaderReactivate: - m_active = true; - m_reader_done.SetValue(false, eBroadcastAlways); - break; - - case eInputReaderDeactivate: - case eInputReaderDone: - m_active = false; - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderInterrupt: - case eInputReaderEndOfFile: - break; - - case eInputReaderGotToken: - return; // We don't notify the tokens here, it is done in HandleRawBytes - } - if (m_callback) - m_callback (m_callback_baton, *this, notification, NULL, 0); - if (notification == eInputReaderDone) - m_reader_done.SetValue(true, eBroadcastAlways); -} - -void -InputReader::WaitOnReaderIsDone () -{ - m_reader_done.WaitForValueEqualTo (true); -} - -const char * -InputReader::GranularityAsCString (lldb::InputReaderGranularity granularity) -{ - switch (granularity) - { - case eInputReaderGranularityInvalid: return "invalid"; - case eInputReaderGranularityByte: return "byte"; - case eInputReaderGranularityWord: return "word"; - case eInputReaderGranularityLine: return "line"; - case eInputReaderGranularityAll: return "all"; - } - - static char unknown_state_string[64]; - snprintf(unknown_state_string, sizeof (unknown_state_string), "InputReaderGranularity = %i", granularity); - return unknown_state_string; -} - -bool -InputReader::HandlerData::GetBatchMode() -{ - return reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); -} - -lldb::StreamSP -InputReader::HandlerData::GetOutStream() -{ - return reader.GetDebugger().GetAsyncOutputStream(); -} diff --git a/lldb/source/Core/InputReaderEZ.cpp b/lldb/source/Core/InputReaderEZ.cpp deleted file mode 100644 index 7a865bdc509..00000000000 --- a/lldb/source/Core/InputReaderEZ.cpp +++ /dev/null @@ -1,91 +0,0 @@ -//===-- InputReaderEZ.cpp ---------------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "lldb/Core/InputReaderEZ.h" - -using namespace lldb; -using namespace lldb_private; - -size_t -InputReaderEZ::Callback_Impl(void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len) - -{ - HandlerData hand_data(reader, - bytes, - bytes_len, - baton); - - switch (notification) - { - case eInputReaderActivate: - reader.ActivateHandler(hand_data); - break; - case eInputReaderDeactivate: - reader.DeactivateHandler(hand_data); - break; - case eInputReaderReactivate: - reader.ReactivateHandler(hand_data); - break; - case eInputReaderAsynchronousOutputWritten: - reader.AsynchronousOutputWrittenHandler(hand_data); - break; - case eInputReaderGotToken: - { - if (reader.GetSaveUserInput()) - reader.GetUserInput().AppendString(bytes, bytes_len); - reader.GotTokenHandler(hand_data); - } - break; - case eInputReaderInterrupt: - reader.InterruptHandler(hand_data); - break; - case eInputReaderEndOfFile: - reader.EOFHandler(hand_data); - break; - case eInputReaderDone: - reader.DoneHandler(hand_data); - break; - } - return bytes_len; -} - -Error -InputReaderEZ::Initialize(void* baton, - lldb::InputReaderGranularity token_size, - const char* end_token, - const char *prompt, - bool echo) -{ - return InputReader::Initialize(Callback_Impl, - baton, - token_size, - end_token, - prompt, - echo); -} - -Error -InputReaderEZ::Initialize(InitializationParameters& params) -{ - Error ret = Initialize(params.m_baton, - params.m_token_size, - params.m_end_token, - params.m_prompt, - params.m_echo); - m_save_user_input = params.m_save_user_input; - return ret; -} - -InputReaderEZ::~InputReaderEZ () -{ -} diff --git a/lldb/source/Core/InputReaderStack.cpp b/lldb/source/Core/InputReaderStack.cpp deleted file mode 100644 index 764ea26550f..00000000000 --- a/lldb/source/Core/InputReaderStack.cpp +++ /dev/null @@ -1,80 +0,0 @@ -//===-- InputReaderStack.cpp ------------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "lldb/Core/InputReaderStack.h" - -// C Includes -// C++ Includes -// Other libraries and framework includes -// Project includes - - -using namespace lldb; -using namespace lldb_private; - -InputReaderStack::InputReaderStack () : - m_input_readers (), - m_input_readers_mutex (Mutex::eMutexTypeRecursive) -{ -} - -InputReaderStack::~InputReaderStack () -{ -} - -size_t -InputReaderStack::GetSize () const -{ - Mutex::Locker locker (m_input_readers_mutex); - return m_input_readers.size(); -} - -void -InputReaderStack::Push (const lldb::InputReaderSP& reader_sp) -{ - if (reader_sp) - { - Mutex::Locker locker (m_input_readers_mutex); - m_input_readers.push (reader_sp); - } -} - -bool -InputReaderStack::IsEmpty () const -{ - Mutex::Locker locker (m_input_readers_mutex); - return m_input_readers.empty(); -} - -InputReaderSP -InputReaderStack::Top () -{ - InputReaderSP input_reader_sp; - { - Mutex::Locker locker (m_input_readers_mutex); - if (!m_input_readers.empty()) - input_reader_sp = m_input_readers.top(); - } - - return input_reader_sp; -} - -void -InputReaderStack::Pop () -{ - Mutex::Locker locker (m_input_readers_mutex); - if (!m_input_readers.empty()) - m_input_readers.pop(); -} - -Mutex & -InputReaderStack::GetStackMutex () -{ - return m_input_readers_mutex; -} diff --git a/lldb/source/Core/SourceManager.cpp b/lldb/source/Core/SourceManager.cpp index 940034625c0..0a6a80401d8 100644 --- a/lldb/source/Core/SourceManager.cpp +++ b/lldb/source/Core/SourceManager.cpp @@ -432,6 +432,56 @@ SourceManager::File::GetLineOffset (uint32_t line) return UINT32_MAX; } +uint32_t +SourceManager::File::GetNumLines () +{ + CalculateLineOffsets(); + return m_offsets.size(); +} + +const char * +SourceManager::File::PeekLineData (uint32_t line) +{ + if (!LineIsValid(line)) + return NULL; + + size_t line_offset = GetLineOffset (line); + if (line_offset < m_data_sp->GetByteSize()) + return (const char *)m_data_sp->GetBytes() + line_offset; + return NULL; +} + +uint32_t +SourceManager::File::GetLineLength (uint32_t line, bool include_newline_chars) +{ + if (!LineIsValid(line)) + return false; + + size_t start_offset = GetLineOffset (line); + size_t end_offset = GetLineOffset (line + 1); + if (end_offset == UINT32_MAX) + end_offset = m_data_sp->GetByteSize(); + + if (end_offset > start_offset) + { + uint32_t length = end_offset - start_offset; + if (include_newline_chars == false) + { + const char *line_start = (const char *)m_data_sp->GetBytes() + start_offset; + while (length > 0) + { + const char last_char = line_start[length-1]; + if ((last_char == '\r') || (last_char == '\n')) + --length; + else + break; + } + } + return length; + } + return 0; +} + bool SourceManager::File::LineIsValid (uint32_t line) { diff --git a/lldb/source/Core/StreamAsynchronousIO.cpp b/lldb/source/Core/StreamAsynchronousIO.cpp index b9e5cdfde72..257982ab8b2 100644 --- a/lldb/source/Core/StreamAsynchronousIO.cpp +++ b/lldb/source/Core/StreamAsynchronousIO.cpp @@ -28,25 +28,26 @@ StreamAsynchronousIO::StreamAsynchronousIO (Broadcaster &broadcaster, uint32_t b StreamAsynchronousIO::~StreamAsynchronousIO () { + // Flush when we destroy to make sure we display the data + Flush(); } void StreamAsynchronousIO::Flush () { - if (m_accumulated_data.GetSize() > 0) + if (!m_accumulated_data.empty()) { std::unique_ptr<EventDataBytes> data_bytes_ap (new EventDataBytes); // Let's swap the bytes to avoid LARGE string copies. - data_bytes_ap->SwapBytes (m_accumulated_data.GetString()); + data_bytes_ap->SwapBytes (m_accumulated_data); EventSP new_event_sp (new Event (m_broadcast_event_type, data_bytes_ap.release())); m_broadcaster.BroadcastEvent (new_event_sp); - m_accumulated_data.Clear(); } } size_t StreamAsynchronousIO::Write (const void *s, size_t length) { - m_accumulated_data.Write (s, length); + m_accumulated_data.append ((const char *)s, length); return length; } diff --git a/lldb/source/Core/StringList.cpp b/lldb/source/Core/StringList.cpp index 99497511678..d2fa8cfb4a8 100644 --- a/lldb/source/Core/StringList.cpp +++ b/lldb/source/Core/StringList.cpp @@ -56,6 +56,12 @@ StringList::AppendString (const std::string &s) } void +StringList::AppendString (std::string &&s) +{ + m_strings.push_back (s); +} + +void StringList::AppendString (const char *str, size_t str_len) { if (str) @@ -93,6 +99,20 @@ StringList::GetSize () const return m_strings.size(); } +size_t +StringList::GetMaxStringLength () const +{ + size_t max_length = 0; + for (const auto &s : m_strings) + { + const size_t len = s.size(); + if (max_length < len) + max_length = len; + } + return max_length; +} + + const char * StringList::GetStringAtIndex (size_t idx) const { @@ -126,37 +146,39 @@ StringList::Clear () void StringList::LongestCommonPrefix (std::string &common_prefix) { - //arg_sstr_collection::iterator pos, end = m_args.end(); - size_t pos = 0; - size_t end = m_strings.size(); + const size_t num_strings = m_strings.size(); - if (pos == end) + if (num_strings == 0) + { common_prefix.clear(); + } else - common_prefix = m_strings[pos]; - - for (++pos; pos != end; ++pos) { - size_t new_size = strlen (m_strings[pos].c_str()); + common_prefix = m_strings.front(); - // First trim common_prefix if it is longer than the current element: - if (common_prefix.size() > new_size) - common_prefix.erase (new_size); + for (size_t idx = 1; idx < num_strings; ++idx) + { + std::string &curr_string = m_strings[idx]; + size_t new_size = curr_string.size(); - // Then trim it at the first disparity: + // First trim common_prefix if it is longer than the current element: + if (common_prefix.size() > new_size) + common_prefix.erase (new_size); - for (size_t i = 0; i < common_prefix.size(); i++) - { - if (m_strings[pos][i] != common_prefix[i]) + // Then trim it at the first disparity: + for (size_t i = 0; i < common_prefix.size(); i++) { - common_prefix.erase(i); - break; + if (curr_string[i] != common_prefix[i]) + { + common_prefix.erase(i); + break; + } } - } - // If we've emptied the common prefix, we're done. - if (common_prefix.empty()) - break; + // If we've emptied the common prefix, we're done. + if (common_prefix.empty()) + break; + } } } @@ -173,6 +195,24 @@ StringList::InsertStringAtIndex (size_t idx, const char *str) } void +StringList::InsertStringAtIndex (size_t idx, const std::string &str) +{ + if (idx < m_strings.size()) + m_strings.insert (m_strings.begin() + idx, str); + else + m_strings.push_back (str); +} + +void +StringList::InsertStringAtIndex (size_t idx, std::string &&str) +{ + if (idx < m_strings.size()) + m_strings.insert (m_strings.begin() + idx, str); + else + m_strings.push_back (str); +} + +void StringList::DeleteStringAtIndex (size_t idx) { if (idx < m_strings.size()) @@ -180,6 +220,12 @@ StringList::DeleteStringAtIndex (size_t idx) } size_t +StringList::SplitIntoLines (const std::string &lines) +{ + return SplitIntoLines (lines.c_str(), lines.size()); +} + +size_t StringList::SplitIntoLines (const char *lines, size_t len) { const size_t orig_size = m_strings.size(); @@ -231,8 +277,7 @@ StringList::RemoveBlankLines () } std::string -StringList::CopyList(const char* item_preamble, - const char* items_sep) +StringList::CopyList(const char* item_preamble, const char* items_sep) const { StreamString strm; for (size_t i = 0; i < GetSize(); i++) diff --git a/lldb/source/Core/ValueObject.cpp b/lldb/source/Core/ValueObject.cpp index 93a5f934b06..cf93c86d0b5 100644 --- a/lldb/source/Core/ValueObject.cpp +++ b/lldb/source/Core/ValueObject.cpp @@ -3721,7 +3721,8 @@ ValueObject::EvaluationPoint::SyncWithProcessState() { // Start with the target, if it is NULL, then we're obviously not going to get any further: - ExecutionContext exe_ctx(m_exe_ctx_ref.Lock()); + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx(m_exe_ctx_ref.Lock(thread_and_frame_only_if_stopped)); if (exe_ctx.GetTargetPtr() == NULL) return false; diff --git a/lldb/source/Core/ValueObjectChild.cpp b/lldb/source/Core/ValueObjectChild.cpp index 4547c58d429..ccf87cd15b2 100644 --- a/lldb/source/Core/ValueObjectChild.cpp +++ b/lldb/source/Core/ValueObjectChild.cpp @@ -206,7 +206,8 @@ ValueObjectChild::UpdateValue () if (m_error.Success()) { - ExecutionContext exe_ctx (GetExecutionContextRef().Lock()); + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx (GetExecutionContextRef().Lock(thread_and_frame_only_if_stopped)); if (GetClangType().GetTypeInfo() & ClangASTType::eTypeHasValue) m_error = m_value.GetValueAsData (&exe_ctx, m_data, 0, GetModule().get()); else diff --git a/lldb/source/DataFormatters/LibCxxUnorderedMap.cpp b/lldb/source/DataFormatters/LibCxxUnorderedMap.cpp index b73d3bf852e..05b41d0de0c 100644 --- a/lldb/source/DataFormatters/LibCxxUnorderedMap.cpp +++ b/lldb/source/DataFormatters/LibCxxUnorderedMap.cpp @@ -84,7 +84,8 @@ lldb_private::formatters::LibcxxStdUnorderedMapSyntheticFrontEnd::GetChildAtInde stream.Printf("[%zu]",idx); DataExtractor data; val_hash.first->GetData(data); - ExecutionContext exe_ctx = val_hash.first->GetExecutionContextRef().Lock(); + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx = val_hash.first->GetExecutionContextRef().Lock(thread_and_frame_only_if_stopped); return val_hash.first->CreateValueObjectFromData(stream.GetData(), data, exe_ctx, diff --git a/lldb/source/Expression/ClangExpressionParser.cpp b/lldb/source/Expression/ClangExpressionParser.cpp index 59f81958127..615f29fd0c7 100644 --- a/lldb/source/Expression/ClangExpressionParser.cpp +++ b/lldb/source/Expression/ClangExpressionParser.cpp @@ -16,6 +16,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Disassembler.h" #include "lldb/Core/Stream.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/StreamString.h" #include "lldb/Expression/ClangASTSource.h" #include "lldb/Expression/ClangExpression.h" @@ -505,7 +506,7 @@ ClangExpressionParser::PrepareForExecution (lldb::addr_t &func_addr, Stream *error_stream = NULL; Target *target = exe_ctx.GetTargetPtr(); if (target) - error_stream = &target->GetDebugger().GetErrorStream(); + error_stream = target->GetDebugger().GetErrorFile().get(); IRForTarget ir_for_target(decl_map, m_expr.NeedsVariableResolution(), diff --git a/lldb/source/Host/common/CMakeLists.txt b/lldb/source/Host/common/CMakeLists.txt index 443b983cb6d..a17a2abaa7a 100644 --- a/lldb/source/Host/common/CMakeLists.txt +++ b/lldb/source/Host/common/CMakeLists.txt @@ -3,6 +3,7 @@ set(LLVM_NO_RTTI 1) add_lldb_library(lldbHostCommon Condition.cpp DynamicLibrary.cpp + Editline.cpp File.cpp FileSpec.cpp Host.cpp diff --git a/lldb/source/Host/common/Editline.cpp b/lldb/source/Host/common/Editline.cpp new file mode 100644 index 00000000000..53fa64103ab --- /dev/null +++ b/lldb/source/Host/common/Editline.cpp @@ -0,0 +1,671 @@ +//===-- Editline.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#include "lldb/Host/Editline.h" + +#include "lldb/Core/Error.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Core/StringList.h" +#include "lldb/Host/Host.h" + +#include <limits.h> + +using namespace lldb; +using namespace lldb_private; + +static const char k_prompt_escape_char = '\1'; + +Editline::Editline (const char *prog, // prog can't be NULL + const char *prompt, // can be NULL for no prompt + FILE *fin, + FILE *fout, + FILE *ferr) : + m_editline (NULL), + m_history (NULL), + m_history_event (), + m_program (), + m_prompt (), + m_lines_prompt (), + m_getc_buffer (), + m_getc_mutex (Mutex::eMutexTypeNormal), + m_getc_cond (), +// m_gets_mutex (Mutex::eMutexTypeNormal), + m_completion_callback (NULL), + m_completion_callback_baton (NULL), + m_line_complete_callback (NULL), + m_line_complete_callback_baton (NULL), + m_lines_command (Command::None), + m_lines_curr_line (0), + m_lines_max_line (0), + m_prompt_with_line_numbers (false), + m_getting_line (false), + m_got_eof (false), + m_interrupted (false) +{ + if (prog && prog[0]) + { + m_program = prog; + m_editline = ::el_init(prog, fin, fout, ferr); + m_history = ::history_init(); + } + else + { + m_editline = ::el_init("lldb-tmp", fin, fout, ferr); + } + if (prompt && prompt[0]) + SetPrompt (prompt); + + //::el_set (m_editline, EL_BIND, "^[[A", NULL); // Print binding for up arrow key + //::el_set (m_editline, EL_BIND, "^[[B", NULL); // Print binding for up down key + + assert (m_editline); + ::el_set (m_editline, EL_CLIENTDATA, this); + ::el_set (m_editline, EL_PROMPT_ESC, GetPromptCallback, k_prompt_escape_char); + ::el_set (m_editline, EL_EDITOR, "emacs"); + if (m_history) + { + ::el_set (m_editline, EL_HIST, history, m_history); + } + ::el_set (m_editline, EL_ADDFN, "lldb-complete", "Editline completion function", Editline::CallbackComplete); + ::el_set (m_editline, EL_ADDFN, "lldb-edit-prev-line", "Editline edit prev line", Editline::CallbackEditPrevLine); + ::el_set (m_editline, EL_ADDFN, "lldb-edit-next-line", "Editline edit next line", Editline::CallbackEditNextLine); + + ::el_set (m_editline, EL_BIND, "^r", "em-inc-search-prev", NULL); // Cycle through backwards search, entering string + ::el_set (m_editline, EL_BIND, "^w", "ed-delete-prev-word", NULL); // Delete previous word, behave like bash does. + ::el_set (m_editline, EL_BIND, "\033[3~", "ed-delete-next-char", NULL); // Fix the delete key. + ::el_set (m_editline, EL_BIND, "\t", "lldb-complete", NULL); // Bind TAB to be autocompelte + + // Source $PWD/.editrc then $HOME/.editrc + ::el_source (m_editline, NULL); + + if (m_history) + { + ::history (m_history, &m_history_event, H_SETSIZE, 800); + ::history (m_history, &m_history_event, H_SETUNIQUE, 1); + } + + // Always read through our callback function so we don't read + // stuff we aren't supposed to. This also stops the extra echoing + // that can happen when you have more input than editline can handle + // at once. + SetGetCharCallback(GetCharFromInputFileCallback); + + LoadHistory(); +} + +Editline::~Editline() +{ + SaveHistory(); + + if (m_history) + { + ::history_end (m_history); + m_history = NULL; + } + + // Disable edit mode to stop the terminal from flushing all input + // during the call to el_end() since we expect to have multiple editline + // instances in this program. + ::el_set (m_editline, EL_EDITMODE, 0); + + ::el_end(m_editline); + m_editline = NULL; +} + +void +Editline::SetGetCharCallback (GetCharCallbackType callback) +{ + ::el_set (m_editline, EL_GETCFN, callback); +} + +FileSpec +Editline::GetHistoryFile() +{ + char history_path[PATH_MAX]; + ::snprintf (history_path, sizeof(history_path), "~/.%s-history", m_program.c_str()); + return FileSpec(history_path, true); +} + +bool +Editline::LoadHistory () +{ + if (m_history) + { + FileSpec history_file(GetHistoryFile()); + if (history_file.Exists()) + ::history (m_history, &m_history_event, H_LOAD, history_file.GetPath().c_str()); + return true; + } + return false; +} + +bool +Editline::SaveHistory () +{ + if (m_history) + { + std::string history_path = GetHistoryFile().GetPath(); + ::history (m_history, &m_history_event, H_SAVE, history_path.c_str()); + return true; + } + return false; +} + + +Error +Editline::PrivateGetLine(std::string &line) +{ + Error error; + if (m_interrupted) + { + error.SetErrorString("interrupted"); + return error; + } + + line.clear(); + if (m_editline != NULL) + { + int line_len = 0; + const char *line_cstr = NULL; + // Call el_gets to prompt the user and read the user's input. +// { +// // Make sure we know when we are in el_gets() by using a mutex +// Mutex::Locker locker (m_gets_mutex); + line_cstr = ::el_gets (m_editline, &line_len); +// } + + static int save_errno = (line_len < 0) ? errno : 0; + + if (save_errno != 0) + { + error.SetError(save_errno, eErrorTypePOSIX); + } + else if (line_cstr) + { + // Decrement the length so we don't have newline characters in "line" for when + // we assign the cstr into the std::string + while (line_len > 0 && + (line_cstr[line_len - 1] == '\n' || + line_cstr[line_len - 1] == '\r')) + --line_len; + + if (line_len > 0) + { + // We didn't strip the newlines, we just adjusted the length, and + // we want to add the history item with the newlines + if (m_history) + ::history (m_history, &m_history_event, H_ENTER, line_cstr); + + // Copy the part of the c string that we want (removing the newline chars) + line.assign(line_cstr, line_len); + } + } + } + else + { + error.SetErrorString("the EditLine instance has been deleted"); + } + return error; +} + + +Error +Editline::GetLine(std::string &line) +{ + Error error; + line.clear(); + + // Set arrow key bindings for up and down arrows for single line + // mode where up and down arrows do prev/next history + ::el_set (m_editline, EL_BIND, "^[[A", "ed-prev-history", NULL); // Map up arrow + ::el_set (m_editline, EL_BIND, "^[[B", "ed-next-history", NULL); // Map down arrow + m_interrupted = false; + + if (!m_got_eof) + { + if (m_getting_line) + { + error.SetErrorString("already getting a line"); + return error; + } + if (m_lines_curr_line > 0) + { + error.SetErrorString("already getting lines"); + return error; + } + m_getting_line = true; + error = PrivateGetLine(line); + m_getting_line = false; + } + + if (m_got_eof && line.empty()) + { + // Only set the error if we didn't get an error back from PrivateGetLine() + if (error.Success()) + error.SetErrorString("end of file"); + } + + return error; +} + +size_t +Editline::Push (const char *bytes, size_t len) +{ + if (m_editline) + { + // Must NULL terminate the string for el_push() so we stick it + // into a std::string first + ::el_push(m_editline, std::string (bytes, len).c_str()); + return len; + } + return 0; +} + + +Error +Editline::GetLines(const std::string &end_line, StringList &lines) +{ + Error error; + if (m_getting_line) + { + error.SetErrorString("already getting a line"); + return error; + } + if (m_lines_curr_line > 0) + { + error.SetErrorString("already getting lines"); + return error; + } + + // Set arrow key bindings for up and down arrows for multiple line + // mode where up and down arrows do edit prev/next line + ::el_set (m_editline, EL_BIND, "^[[A", "lldb-edit-prev-line", NULL); // Map up arrow + ::el_set (m_editline, EL_BIND, "^[[B", "lldb-edit-next-line", NULL); // Map down arrow + ::el_set (m_editline, EL_BIND, "^b", "ed-prev-history", NULL); + ::el_set (m_editline, EL_BIND, "^n", "ed-next-history", NULL); + m_interrupted = false; + + LineStatus line_status = LineStatus::Success; + + lines.Clear(); + + FILE *out_file = GetOutputFile(); + FILE *err_file = GetErrorFile(); + m_lines_curr_line = 1; + while (line_status != LineStatus::Done) + { + const uint32_t line_idx = m_lines_curr_line-1; + if (line_idx >= lines.GetSize()) + lines.SetSize(m_lines_curr_line); + m_lines_max_line = lines.GetSize(); + m_lines_command = Command::None; + assert(line_idx < m_lines_max_line); + std::string &line = lines[line_idx]; + error = PrivateGetLine(line); + if (error.Fail()) + { + line_status = LineStatus::Error; + } + else + { + switch (m_lines_command) + { + case Command::None: + if (m_line_complete_callback) + { + line_status = m_line_complete_callback (this, + lines, + line_idx, + error, + m_line_complete_callback_baton); + } + else if (line == end_line) + { + line_status = LineStatus::Done; + } + + if (line_status == LineStatus::Success) + { + ++m_lines_curr_line; + // If we already have content for the next line because + // we were editing previous lines, then populate the line + // with the appropriate contents + if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty()) + ::el_push (m_editline, lines[line_idx+1].c_str()); + } + else if (line_status == LineStatus::Error) + { + // Clear to end of line ("ESC[K"), then print the error, + // then go to the next line ("\n") and then move cursor up + // two lines ("ESC[2A"). + fprintf (err_file, "\033[Kerror: %s\n\033[2A", error.AsCString()); + } + break; + case Command::EditPrevLine: + if (m_lines_curr_line > 1) + { + //::fprintf (out_file, "\033[1A\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); // Make cursor go up a line and clear that line + ::fprintf (out_file, "\033[1A\033[1000D\033[2K"); + if (!lines[line_idx-1].empty()) + ::el_push (m_editline, lines[line_idx-1].c_str()); + --m_lines_curr_line; + } + break; + case Command::EditNextLine: + // Allow the down arrow to create a new line + ++m_lines_curr_line; + //::fprintf (out_file, "\033[1B\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); + ::fprintf (out_file, "\033[1B\033[1000D\033[2K"); + if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty()) + ::el_push (m_editline, lines[line_idx+1].c_str()); + break; + } + } + } + m_lines_curr_line = 0; + m_lines_command = Command::None; + + // If we have a callback, call it one more time to let the + // user know the lines are complete + if (m_line_complete_callback) + m_line_complete_callback (this, + lines, + UINT32_MAX, + error, + m_line_complete_callback_baton); + + return error; +} + +unsigned char +Editline::HandleCompletion (int ch) +{ + if (m_completion_callback == NULL) + return CC_ERROR; + + const LineInfo *line_info = ::el_line(m_editline); + StringList completions; + int page_size = 40; + + const int num_completions = m_completion_callback (line_info->buffer, + line_info->cursor, + line_info->lastchar, + 0, // Don't skip any matches (start at match zero) + -1, // Get all the matches + completions, + m_completion_callback_baton); + + FILE *out_file = GetOutputFile(); + +// if (num_completions == -1) +// { +// ::el_insertstr (m_editline, m_completion_key); +// return CC_REDISPLAY; +// } +// else + if (num_completions == -2) + { + // Replace the entire line with the first string... + ::el_deletestr (m_editline, line_info->cursor - line_info->buffer); + ::el_insertstr (m_editline, completions.GetStringAtIndex(0)); + return CC_REDISPLAY; + } + + // If we get a longer match display that first. + const char *completion_str = completions.GetStringAtIndex(0); + if (completion_str != NULL && *completion_str != '\0') + { + el_insertstr (m_editline, completion_str); + return CC_REDISPLAY; + } + + if (num_completions > 1) + { + int num_elements = num_completions + 1; + ::fprintf (out_file, "\nAvailable completions:"); + if (num_completions < page_size) + { + for (int i = 1; i < num_elements; i++) + { + completion_str = completions.GetStringAtIndex(i); + ::fprintf (out_file, "\n\t%s", completion_str); + } + ::fprintf (out_file, "\n"); + } + else + { + int cur_pos = 1; + char reply; + int got_char; + while (cur_pos < num_elements) + { + int endpoint = cur_pos + page_size; + if (endpoint > num_elements) + endpoint = num_elements; + for (; cur_pos < endpoint; cur_pos++) + { + completion_str = completions.GetStringAtIndex(cur_pos); + ::fprintf (out_file, "\n\t%s", completion_str); + } + + if (cur_pos >= num_elements) + { + ::fprintf (out_file, "\n"); + break; + } + + ::fprintf (out_file, "\nMore (Y/n/a): "); + reply = 'n'; + got_char = el_getc(m_editline, &reply); + if (got_char == -1 || reply == 'n') + break; + if (reply == 'a') + page_size = num_elements - cur_pos; + } + } + + } + + if (num_completions == 0) + return CC_REFRESH_BEEP; + else + return CC_REDISPLAY; +} + +Editline * +Editline::GetClientData (::EditLine *e) +{ + Editline *editline = NULL; + if (e && ::el_get(e, EL_CLIENTDATA, &editline) == 0) + return editline; + return NULL; +} + +FILE * +Editline::GetInputFile () +{ + return GetFilePointer (m_editline, 0); +} + +FILE * +Editline::GetOutputFile () +{ + return GetFilePointer (m_editline, 1); +} + +FILE * +Editline::GetErrorFile () +{ + return GetFilePointer (m_editline, 2); +} + +const char * +Editline::GetPrompt() +{ + if (m_prompt_with_line_numbers && m_lines_curr_line > 0) + { + StreamString strm; + strm.Printf("%3u: ", m_lines_curr_line); + m_lines_prompt = std::move(strm.GetString()); + return m_lines_prompt.c_str(); + } + else + { + return m_prompt.c_str(); + } +} + +void +Editline::SetPrompt (const char *p) +{ + if (p && p[0]) + m_prompt = p; + else + m_prompt.clear(); + size_t start_pos = 0; + size_t escape_pos; + while ((escape_pos = m_prompt.find('\033', start_pos)) != std::string::npos) + { + m_prompt.insert(escape_pos, 1, k_prompt_escape_char); + start_pos += 2; + } +} + +FILE * +Editline::GetFilePointer (::EditLine *e, int fd) +{ + FILE *file_ptr = NULL; + if (e && ::el_get(e, EL_GETFP, fd, &file_ptr) == 0) + return file_ptr; + return NULL; +} + +unsigned char +Editline::CallbackEditPrevLine (::EditLine *e, int ch) +{ + Editline *editline = GetClientData (e); + if (editline->m_lines_curr_line > 1) + { + editline->m_lines_command = Command::EditPrevLine; + return CC_NEWLINE; + } + return CC_ERROR; +} +unsigned char +Editline::CallbackEditNextLine (::EditLine *e, int ch) +{ + Editline *editline = GetClientData (e); + if (editline->m_lines_curr_line < editline->m_lines_max_line) + { + editline->m_lines_command = Command::EditNextLine; + return CC_NEWLINE; + } + return CC_ERROR; +} + +unsigned char +Editline::CallbackComplete (::EditLine *e, int ch) +{ + Editline *editline = GetClientData (e); + if (editline) + return editline->HandleCompletion (ch); + return CC_ERROR; +} + +const char * +Editline::GetPromptCallback (::EditLine *e) +{ + Editline *editline = GetClientData (e); + if (editline) + return editline->GetPrompt(); + return ""; +} + +size_t +Editline::SetInputBuffer (const char *c, size_t len) +{ + if (c && len > 0) + { + Mutex::Locker locker(m_getc_mutex); + SetGetCharCallback(GetCharInputBufferCallback); + m_getc_buffer.append(c, len); + m_getc_cond.Broadcast(); + } + return len; +} + +int +Editline::GetChar (char *c) +{ + Mutex::Locker locker(m_getc_mutex); + if (m_getc_buffer.empty()) + m_getc_cond.Wait(m_getc_mutex); + if (m_getc_buffer.empty()) + return 0; + *c = m_getc_buffer[0]; + m_getc_buffer.erase(0,1); + return 1; +} + +int +Editline::GetCharInputBufferCallback (EditLine *e, char *c) +{ + Editline *editline = GetClientData (e); + if (editline) + return editline->GetChar(c); + return 0; +} + +int +Editline::GetCharFromInputFileCallback (EditLine *e, char *c) +{ + Editline *editline = GetClientData (e); + if (editline && editline->m_got_eof == false) + { + char ch = ::fgetc(editline->GetInputFile()); + if (ch == '\x04' || ch == EOF) + { + editline->m_got_eof = true; + } + else + { + *c = ch; + return 1; + } + } + return 0; +} + +void +Editline::Hide () +{ + FILE *out_file = GetOutputFile(); + if (out_file) + { + const LineInfo *line_info = ::el_line(m_editline); + if (line_info) + ::fprintf (out_file, "\033[%uD\033[K", (uint32_t)(strlen(GetPrompt()) + line_info->cursor - line_info->buffer)); + } +} + + +void +Editline::Refresh() +{ + ::el_set (m_editline, EL_REFRESH); +} + +void +Editline::Interrupt () +{ + m_interrupted = true; + if (m_getting_line || m_lines_curr_line > 0) + el_insertstr(m_editline, "\n"); // True to force the line to complete itself so we get exit from el_gets() +} diff --git a/lldb/source/Host/common/File.cpp b/lldb/source/Host/common/File.cpp index bbd11858aab..ab61b393c54 100644 --- a/lldb/source/Host/common/File.cpp +++ b/lldb/source/Host/common/File.cpp @@ -76,8 +76,9 @@ FILE * File::kInvalidStream = NULL; File::File(const char *path, uint32_t options, uint32_t permissions) : m_descriptor (kInvalidDescriptor), m_stream (kInvalidStream), - m_options (0), - m_owned (false) + m_options (), + m_own_stream (false), + m_own_descriptor (false) { Open (path, options, permissions); } @@ -88,7 +89,8 @@ File::File (const FileSpec& filespec, m_descriptor (kInvalidDescriptor), m_stream (kInvalidStream), m_options (0), - m_owned (false) + m_own_stream (false), + m_own_descriptor (false) { if (filespec) { @@ -100,7 +102,8 @@ File::File (const File &rhs) : m_descriptor (kInvalidDescriptor), m_stream (kInvalidStream), m_options (0), - m_owned (false) + m_own_stream (false), + m_own_descriptor (false) { Duplicate (rhs); } @@ -141,7 +144,7 @@ File::SetDescriptor (int fd, bool transfer_ownership) if (IsValid()) Close(); m_descriptor = fd; - m_owned = transfer_ownership; + m_own_descriptor = transfer_ownership; } @@ -155,10 +158,31 @@ File::GetStream () const char *mode = GetStreamOpenModeFromOptions (m_options); if (mode) { + if (!m_own_descriptor) + { + // We must duplicate the file descriptor if we don't own it because + // when you call fdopen, the stream will own the fd +#ifdef _WIN32 + m_descriptor = ::_dup(GetDescriptor()); +#else + m_descriptor = ::fcntl(GetDescriptor(), F_DUPFD); +#endif + m_own_descriptor = true; + } + do { m_stream = ::fdopen (m_descriptor, mode); } while (m_stream == NULL && errno == EINTR); + + // If we got a stream, then we own the stream and should no + // longer own the descriptor because fclose() will close it for us + + if (m_stream) + { + m_own_stream = true; + m_own_descriptor = false; + } } } } @@ -172,7 +196,7 @@ File::SetStream (FILE *fh, bool transfer_ownership) if (IsValid()) Close(); m_stream = fh; - m_owned = transfer_ownership; + m_own_stream = transfer_ownership; } Error @@ -194,7 +218,7 @@ File::Duplicate (const File &rhs) else { m_options = rhs.m_options; - m_owned = true; + m_own_descriptor = true; } } else @@ -272,7 +296,10 @@ File::Open (const char *path, uint32_t options, uint32_t permissions) if (!DescriptorIsValid()) error.SetErrorToErrno(); else - m_owned = true; + { + m_own_descriptor = true; + m_options = options; + } return error; } @@ -328,27 +355,22 @@ Error File::Close () { Error error; - if (IsValid ()) + if (StreamIsValid() && m_own_stream) { - if (m_owned) - { - if (StreamIsValid()) - { - if (::fclose (m_stream) == EOF) - error.SetErrorToErrno(); - } - - if (DescriptorIsValid()) - { - if (::close (m_descriptor) != 0) - error.SetErrorToErrno(); - } - } - m_descriptor = kInvalidDescriptor; - m_stream = kInvalidStream; - m_options = 0; - m_owned = false; + if (::fclose (m_stream) == EOF) + error.SetErrorToErrno(); + } + + if (DescriptorIsValid() && m_own_descriptor) + { + if (::close (m_descriptor) != 0) + error.SetErrorToErrno(); } + m_descriptor = kInvalidDescriptor; + m_stream = kInvalidStream; + m_options = 0; + m_own_stream = false; + m_own_descriptor = false; return error; } diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index 53642db940d..7fe42a84a41 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -22,6 +22,7 @@ #include "../Commands/CommandObjectDisassemble.h" #include "../Commands/CommandObjectExpression.h" #include "../Commands/CommandObjectFrame.h" +#include "../Commands/CommandObjectGUI.h" #include "../Commands/CommandObjectHelp.h" #include "../Commands/CommandObjectLog.h" #include "../Commands/CommandObjectMemory.h" @@ -42,11 +43,12 @@ #include "lldb/Core/Debugger.h" -#include "lldb/Core/InputReader.h" #include "lldb/Core/Log.h" #include "lldb/Core/Stream.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/Timer.h" +#include "lldb/Host/Editline.h" #include "lldb/Host/Host.h" #include "lldb/Interpreter/Args.h" @@ -99,11 +101,13 @@ CommandInterpreter::CommandInterpreter ) : Broadcaster (&debugger, "lldb.command-interpreter"), Properties(OptionValuePropertiesSP(new OptionValueProperties(ConstString("interpreter")))), + IOHandlerDelegate (IOHandlerDelegate::Completion::LLDBCommand), m_debugger (debugger), m_synchronous_execution (synchronous_execution), m_skip_lldbinit_files (false), m_skip_app_init_files (false), m_script_interpreter_ap (), + m_command_io_handler_sp (), m_comment_char ('#'), m_batch_command_mode (false), m_truncation_warning(eNoTruncation), @@ -376,6 +380,7 @@ CommandInterpreter::LoadCommandDictionary () m_command_dict["disassemble"] = CommandObjectSP (new CommandObjectDisassemble (*this)); m_command_dict["expression"]= CommandObjectSP (new CommandObjectExpression (*this)); m_command_dict["frame"] = CommandObjectSP (new CommandObjectMultiwordFrame (*this)); + m_command_dict["gui"] = CommandObjectSP (new CommandObjectGUI (*this)); m_command_dict["help"] = CommandObjectSP (new CommandObjectHelp (*this)); m_command_dict["log"] = CommandObjectSP (new CommandObjectLog (*this)); m_command_dict["memory"] = CommandObjectSP (new CommandObjectMemory (*this)); @@ -2093,96 +2098,15 @@ CommandInterpreter::~CommandInterpreter () { } -const char * -CommandInterpreter::GetPrompt () -{ - return m_debugger.GetPrompt(); -} - void -CommandInterpreter::SetPrompt (const char *new_prompt) +CommandInterpreter::UpdatePrompt (const char *new_prompt) { - m_debugger.SetPrompt (new_prompt); + EventSP prompt_change_event_sp (new Event(eBroadcastBitResetPrompt, new EventDataBytes (new_prompt)));; + BroadcastEvent (prompt_change_event_sp); + if (m_command_io_handler_sp) + m_command_io_handler_sp->SetPrompt(new_prompt); } -size_t -CommandInterpreter::GetConfirmationInputReaderCallback -( - void *baton, - InputReader &reader, - lldb::InputReaderAction action, - const char *bytes, - size_t bytes_len -) -{ - File &out_file = reader.GetDebugger().GetOutputFile(); - bool *response_ptr = (bool *) baton; - - switch (action) - { - case eInputReaderActivate: - if (out_file.IsValid()) - { - if (reader.GetPrompt()) - { - out_file.Printf ("%s", reader.GetPrompt()); - out_file.Flush (); - } - } - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderReactivate: - if (out_file.IsValid() && reader.GetPrompt()) - { - out_file.Printf ("%s", reader.GetPrompt()); - out_file.Flush (); - } - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderGotToken: - if (bytes_len == 0) - { - reader.SetIsDone(true); - } - else if (bytes[0] == 'y' || bytes[0] == 'Y') - { - *response_ptr = true; - reader.SetIsDone(true); - } - else if (bytes[0] == 'n' || bytes[0] == 'N') - { - *response_ptr = false; - reader.SetIsDone(true); - } - else - { - if (out_file.IsValid() && !reader.IsDone() && reader.GetPrompt()) - { - out_file.Printf ("Please answer \"y\" or \"n\".\n%s", reader.GetPrompt()); - out_file.Flush (); - } - } - break; - - case eInputReaderInterrupt: - case eInputReaderEndOfFile: - *response_ptr = false; // Assume ^C or ^D means cancel the proposed action - reader.SetIsDone (true); - break; - - case eInputReaderDone: - break; - } - - return bytes_len; - -} bool CommandInterpreter::Confirm (const char *message, bool default_answer) @@ -2190,31 +2114,13 @@ CommandInterpreter::Confirm (const char *message, bool default_answer) // Check AutoConfirm first: if (m_debugger.GetAutoConfirm()) return default_answer; - - InputReaderSP reader_sp (new InputReader(GetDebugger())); - bool response = default_answer; - if (reader_sp) - { - std::string prompt(message); - prompt.append(": ["); - if (default_answer) - prompt.append ("Y/n] "); - else - prompt.append ("y/N] "); - - Error err (reader_sp->Initialize (CommandInterpreter::GetConfirmationInputReaderCallback, - &response, // baton - eInputReaderGranularityLine, // token size, to pass to callback function - NULL, // end token - prompt.c_str(), // prompt - true)); // echo input - if (err.Success()) - { - GetDebugger().PushInputReader (reader_sp); - } - reader_sp->WaitOnReaderIsDone(); - } - return response; + + IOHandlerConfirm *confirm = new IOHandlerConfirm(m_debugger, + message, + default_answer); + IOHandlerSP io_handler_sp (confirm); + m_debugger.RunIOHandler (io_handler_sp); + return confirm->GetResponse(); } OptionArgVectorSP @@ -2490,7 +2396,9 @@ CommandInterpreter::SourceInitFile (bool in_cwd, CommandReturnObject &result) bool echo_commands = false; bool print_results = false; + const bool saved_batch = SetBatchCommandMode (true); HandleCommandsFromFile (init_file, exe_ctx, stop_on_continue, stop_on_error, echo_commands, print_results, eLazyBoolNo, result); + SetBatchCommandMode (saved_batch); } else { @@ -2553,8 +2461,8 @@ CommandInterpreter::HandleCommands (const StringList &commands, if (echo_commands) { result.AppendMessageWithFormat ("%s %s\n", - GetPrompt(), - cmd); + m_debugger.GetPrompt(), + cmd); } CommandReturnObject tmp_result; @@ -2650,6 +2558,33 @@ CommandInterpreter::HandleCommandsFromFile (FileSpec &cmd_file, { if (cmd_file.Exists()) { + StreamFileSP input_file_sp (new StreamFile()); + + std::string cmd_file_path = cmd_file.GetPath(); + Error error = input_file_sp->GetFile().Open(cmd_file_path.c_str(), File::eOpenOptionRead); + + if (error.Success()) + { + Debugger &debugger = GetDebugger(); + + IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger, + input_file_sp, + debugger.GetOutputFile(), + debugger.GetErrorFile(), + NULL, // Pass in NULL for "editline_name" so no history is saved, or written + m_debugger.GetPrompt(), + false, // Not multi-line + *this)); + m_debugger.RunIOHandler(io_handler_sp); + result.SetStatus (eReturnStatusSuccessFinishNoResult); + } + else + { + result.AppendErrorWithFormat ("error: an error occurred read file '%s': %s\n", cmd_file_path.c_str(), error.AsCString()); + result.SetStatus (eReturnStatusFailed); + } + +#if 0 bool success; StringList commands; success = commands.ReadFileLines(cmd_file); @@ -2662,6 +2597,7 @@ CommandInterpreter::HandleCommandsFromFile (FileSpec &cmd_file, m_command_source_depth++; HandleCommands (commands, context, stop_on_continue, stop_on_error, echo_command, print_result, add_to_history, result); m_command_source_depth--; +#endif } else { @@ -2901,7 +2837,6 @@ CommandInterpreter::FindCommandsForApropos (const char *search_word, StringList } } - void CommandInterpreter::UpdateExecutionContext (ExecutionContext *override_context) { @@ -2915,3 +2850,160 @@ CommandInterpreter::UpdateExecutionContext (ExecutionContext *override_context) m_exe_ctx_ref.SetTargetPtr (m_debugger.GetSelectedTarget().get(), adopt_selected); } } + + +size_t +CommandInterpreter::GetProcessOutput () +{ + // The process has stuff waiting for stderr; get it and write it out to the appropriate place. + char stdio_buffer[1024]; + size_t len; + size_t total_bytes = 0; + Error error; + TargetSP target_sp (m_debugger.GetTargetList().GetSelectedTarget()); + if (target_sp) + { + ProcessSP process_sp (target_sp->GetProcessSP()); + if (process_sp) + { + while ((len = process_sp->GetSTDOUT (stdio_buffer, sizeof (stdio_buffer), error)) > 0) + { + size_t bytes_written = len; + m_debugger.GetOutputFile()->Write (stdio_buffer, bytes_written); + total_bytes += len; + } + while ((len = process_sp->GetSTDERR (stdio_buffer, sizeof (stdio_buffer), error)) > 0) + { + size_t bytes_written = len; + m_debugger.GetErrorFile()->Write (stdio_buffer, bytes_written); + total_bytes += len; + } + } + } + return total_bytes; +} + +void +CommandInterpreter::IOHandlerInputComplete (IOHandler &io_handler, std::string &line) +{ + lldb_private::CommandReturnObject result; + HandleCommand(line.c_str(), eLazyBoolCalculate, result); + + // Display any STDOUT/STDERR _prior_ to emitting the command result text + GetProcessOutput (); + + // Now emit the command output text from the command we just executed + const char *output = result.GetOutputData(); + if (output && output[0]) + io_handler.GetOutputStreamFile()->PutCString(output); + + // Now emit the command error text from the command we just executed + const char *error = result.GetErrorData(); + if (error && error[0]) + io_handler.GetErrorStreamFile()->PutCString(error); + + switch (result.GetStatus()) + { + case eReturnStatusInvalid: + case eReturnStatusSuccessFinishNoResult: + case eReturnStatusSuccessFinishResult: + case eReturnStatusSuccessContinuingNoResult: + case eReturnStatusSuccessContinuingResult: + case eReturnStatusStarted: + case eReturnStatusFailed: + break; + + case eReturnStatusQuit: + io_handler.SetIsDone(true); + break; + } +} + +void +CommandInterpreter::GetLLDBCommandsFromIOHandler (const char *prompt, + IOHandlerDelegate &delegate, + bool asynchronously, + void *baton) +{ + Debugger &debugger = GetDebugger(); + IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger, + "lldb", // Name of input reader for history + prompt, // Prompt + true, // Get multiple lines + delegate)); // IOHandlerDelegate + + if (io_handler_sp) + { + io_handler_sp->SetUserData (baton); + if (asynchronously) + debugger.PushIOHandler(io_handler_sp); + else + debugger.RunIOHandler(io_handler_sp); + } + +} + + +void +CommandInterpreter::GetPythonCommandsFromIOHandler (const char *prompt, + IOHandlerDelegate &delegate, + bool asynchronously, + void *baton) +{ + Debugger &debugger = GetDebugger(); + IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger, + "lldb-python", // Name of input reader for history + prompt, // Prompt + true, // Get multiple lines + delegate)); // IOHandlerDelegate + + if (io_handler_sp) + { + io_handler_sp->SetUserData (baton); + if (asynchronously) + debugger.PushIOHandler(io_handler_sp); + else + debugger.RunIOHandler(io_handler_sp); + } + +} + +bool +CommandInterpreter::IsActive () +{ + return m_debugger.IsTopIOHandler (m_command_io_handler_sp); +} + +void +CommandInterpreter::RunCommandInterpreter(bool auto_handle_events, + bool spawn_thread) +{ + const bool multiple_lines = false; // Only get one line at a time + if (!m_command_io_handler_sp) + m_command_io_handler_sp.reset(new IOHandlerEditline (m_debugger, + m_debugger.GetInputFile(), + m_debugger.GetOutputFile(), + m_debugger.GetErrorFile(), + "lldb", + m_debugger.GetPrompt(), + multiple_lines, + *this)); + m_debugger.PushIOHandler(m_command_io_handler_sp); + + if (auto_handle_events) + m_debugger.StartEventHandlerThread(); + + if (spawn_thread) + { + m_debugger.StartIOHandlerThread(); + } + else + { + m_debugger.ExecuteIOHanders(); + + if (auto_handle_events) + m_debugger.StopEventHandlerThread(); + } + +} + diff --git a/lldb/source/Interpreter/PythonDataObjects.cpp b/lldb/source/Interpreter/PythonDataObjects.cpp index 1e2bd239119..01f2754a2cc 100644 --- a/lldb/source/Interpreter/PythonDataObjects.cpp +++ b/lldb/source/Interpreter/PythonDataObjects.cpp @@ -97,7 +97,7 @@ PythonString::PythonString (PyObject *py_obj) : PythonString::PythonString (const PythonObject &object) : PythonObject() { - Reset(object.GetPythonObject()); // Use "Reset()" to ensure that py_obj is a string + Reset(object.get()); // Use "Reset()" to ensure that py_obj is a string } PythonString::PythonString (const lldb::ScriptInterpreterObjectSP &script_object_sp) : @@ -166,7 +166,7 @@ PythonInteger::PythonInteger (PyObject *py_obj) : PythonInteger::PythonInteger (const PythonObject &object) : PythonObject() { - Reset(object.GetPythonObject()); // Use "Reset()" to ensure that py_obj is a integer type + Reset(object.get()); // Use "Reset()" to ensure that py_obj is a integer type } PythonInteger::PythonInteger (const lldb::ScriptInterpreterObjectSP &script_object_sp) : @@ -223,8 +223,8 @@ PythonInteger::SetInteger (int64_t value) // PythonList //---------------------------------------------------------------------- -PythonList::PythonList () : - PythonObject(PyList_New(0)) +PythonList::PythonList (bool create_empty) : + PythonObject(create_empty ? PyList_New(0) : NULL) { } @@ -243,7 +243,7 @@ PythonList::PythonList (PyObject *py_obj) : PythonList::PythonList (const PythonObject &object) : PythonObject() { - Reset(object.GetPythonObject()); // Use "Reset()" to ensure that py_obj is a list + Reset(object.get()); // Use "Reset()" to ensure that py_obj is a list } PythonList::PythonList (const lldb::ScriptInterpreterObjectSP &script_object_sp) : @@ -280,29 +280,29 @@ PythonList::GetItemAtIndex (uint32_t index) { if (m_py_obj) return PythonObject(PyList_GetItem(m_py_obj, index)); - return NULL; + return PythonObject(); } void PythonList::SetItemAtIndex (uint32_t index, const PythonObject & object) { if (m_py_obj && object) - PyList_SetItem(m_py_obj, index, object.GetPythonObject()); + PyList_SetItem(m_py_obj, index, object.get()); } void PythonList::AppendItem (const PythonObject &object) { if (m_py_obj && object) - PyList_Append(m_py_obj, object.GetPythonObject()); + PyList_Append(m_py_obj, object.get()); } //---------------------------------------------------------------------- // PythonDictionary //---------------------------------------------------------------------- -PythonDictionary::PythonDictionary () : - PythonObject(PyDict_New()) +PythonDictionary::PythonDictionary (bool create_empty) : +PythonObject(create_empty ? PyDict_New() : NULL) { } @@ -316,7 +316,7 @@ PythonDictionary::PythonDictionary (PyObject *py_obj) : PythonDictionary::PythonDictionary (const PythonObject &object) : PythonObject() { - Reset(object.GetPythonObject()); // Use "Reset()" to ensure that py_obj is a dictionary + Reset(object.get()); // Use "Reset()" to ensure that py_obj is a dictionary } PythonDictionary::PythonDictionary (const lldb::ScriptInterpreterObjectSP &script_object_sp) : @@ -356,7 +356,7 @@ PythonDictionary::GetItemForKey (const char *key) const PythonString python_key(key); return GetItemForKey(python_key); } - return NULL; + return PythonObject(); } @@ -364,7 +364,7 @@ PythonObject PythonDictionary::GetItemForKey (const PythonString &key) const { if (m_py_obj && key) - return PythonObject(PyDict_GetItem(m_py_obj, key.GetPythonObject())); + return PythonObject(PyDict_GetItem(m_py_obj, key.get())); return PythonObject(); } @@ -374,7 +374,7 @@ PythonDictionary::GetItemForKeyAsString (const PythonString &key, const char *fa { if (m_py_obj && key) { - PyObject *py_obj = PyDict_GetItem(m_py_obj, key.GetPythonObject()); + PyObject *py_obj = PyDict_GetItem(m_py_obj, key.get()); if (py_obj && PyString_Check(py_obj)) return PyString_AsString(py_obj); } @@ -386,7 +386,7 @@ PythonDictionary::GetItemForKeyAsInteger (const PythonString &key, int64_t fail_ { if (m_py_obj && key) { - PyObject *py_obj = PyDict_GetItem(m_py_obj, key.GetPythonObject()); + PyObject *py_obj = PyDict_GetItem(m_py_obj, key.get()); if (py_obj) { if (PyInt_Check(py_obj)) @@ -404,7 +404,7 @@ PythonDictionary::GetKeys () const { if (m_py_obj) return PythonList(PyDict_Keys(m_py_obj)); - return PythonList(); + return PythonList(true); } PythonString @@ -431,7 +431,7 @@ PythonDictionary::GetValueAtPosition (uint32_t pos) const Py_ssize_t pos_iter = 0; if (!m_py_obj) - return NULL; + return PythonObject(); while (PyDict_Next(m_py_obj, &pos_iter, &key, &value)) { if (pos-- == 0) @@ -441,10 +441,17 @@ PythonDictionary::GetValueAtPosition (uint32_t pos) const } void +PythonDictionary::SetItemForKey (const PythonString &key, PyObject *value) +{ + if (m_py_obj && key && value) + PyDict_SetItem(m_py_obj, key.get(), value); +} + +void PythonDictionary::SetItemForKey (const PythonString &key, const PythonObject &value) { if (m_py_obj && key && value) - PyDict_SetItem(m_py_obj, key.GetPythonObject(), value.GetPythonObject()); + PyDict_SetItem(m_py_obj, key.get(), value.get()); } #endif diff --git a/lldb/source/Interpreter/ScriptInterpreterNone.cpp b/lldb/source/Interpreter/ScriptInterpreterNone.cpp index 6a4411494c4..e33480d1a6d 100644 --- a/lldb/source/Interpreter/ScriptInterpreterNone.cpp +++ b/lldb/source/Interpreter/ScriptInterpreterNone.cpp @@ -11,6 +11,7 @@ #include "lldb/Interpreter/ScriptInterpreterNone.h" #include "lldb/Core/Stream.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/StringList.h" #include "lldb/Core/Debugger.h" #include "lldb/Interpreter/CommandInterpreter.h" @@ -30,14 +31,14 @@ ScriptInterpreterNone::~ScriptInterpreterNone () bool ScriptInterpreterNone::ExecuteOneLine (const char *command, CommandReturnObject *, const ExecuteScriptOptions&) { - m_interpreter.GetDebugger().GetErrorStream().PutCString ("error: there is no embedded script interpreter in this mode.\n"); + m_interpreter.GetDebugger().GetErrorFile()->PutCString ("error: there is no embedded script interpreter in this mode.\n"); return false; } void ScriptInterpreterNone::ExecuteInterpreterLoop () { - m_interpreter.GetDebugger().GetErrorStream().PutCString ("error: there is no embedded script interpreter in this mode.\n"); + m_interpreter.GetDebugger().GetErrorFile()->PutCString ("error: there is no embedded script interpreter in this mode.\n"); } diff --git a/lldb/source/Interpreter/ScriptInterpreterPython.cpp b/lldb/source/Interpreter/ScriptInterpreterPython.cpp index fb60fedbe94..a2740cf96e1 100644 --- a/lldb/source/Interpreter/ScriptInterpreterPython.cpp +++ b/lldb/source/Interpreter/ScriptInterpreterPython.cpp @@ -27,11 +27,14 @@ #include "lldb/Breakpoint/BreakpointLocation.h" #include "lldb/Breakpoint/StoppointCallbackContext.h" #include "lldb/Breakpoint/WatchpointOptions.h" +#include "lldb/Core/Communication.h" +#include "lldb/Core/ConnectionFileDescriptor.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/Timer.h" #include "lldb/Host/Host.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/PythonDataObjects.h" #include "lldb/Target/Thread.h" using namespace lldb; @@ -66,22 +69,23 @@ _check_and_flush (FILE *stream) return fflush (stream) || prev_fail ? EOF : 0; } +static std::string +ReadPythonBacktrace (PyObject* py_backtrace); + ScriptInterpreterPython::Locker::Locker (ScriptInterpreterPython *py_interpreter, uint16_t on_entry, uint16_t on_leave, - FILE* wait_msg_handle) : + FILE *in, + FILE *out, + FILE *err) : ScriptInterpreterLocker (), m_teardown_session( (on_leave & TearDownSession) == TearDownSession ), - m_python_interpreter(py_interpreter), - m_tmp_fh(wait_msg_handle) + m_python_interpreter(py_interpreter) { - if (m_python_interpreter && !m_tmp_fh) - m_tmp_fh = (m_python_interpreter->m_dbg_stdout ? m_python_interpreter->m_dbg_stdout : stdout); - DoAcquireLock(); if ((on_entry & InitSession) == InitSession) { - if (DoInitSession((on_entry & InitGlobals) == InitGlobals) == false) + if (DoInitSession((on_entry & InitGlobals) == InitGlobals, in, out, err) == false) { // Don't teardown the session if we didn't init it. m_teardown_session = false; @@ -100,11 +104,11 @@ ScriptInterpreterPython::Locker::DoAcquireLock() } bool -ScriptInterpreterPython::Locker::DoInitSession(bool init_lldb_globals) +ScriptInterpreterPython::Locker::DoInitSession(bool init_lldb_globals, FILE *in, FILE *out, FILE *err) { if (!m_python_interpreter) return false; - return m_python_interpreter->EnterSession (init_lldb_globals); + return m_python_interpreter->EnterSession (init_lldb_globals, in, out, err); } bool @@ -133,268 +137,29 @@ ScriptInterpreterPython::Locker::~Locker() DoFreeLock(); } -ScriptInterpreterPython::PythonInputReaderManager::PythonInputReaderManager (ScriptInterpreterPython *interpreter) : -m_interpreter(interpreter), -m_debugger_sp(), -m_reader_sp(), -m_error(false) -{ - if (m_interpreter == NULL) - { - m_error = true; - return; - } - - m_debugger_sp = m_interpreter->GetCommandInterpreter().GetDebugger().shared_from_this(); - - if (!m_debugger_sp) - { - m_error = true; - return; - } - - m_reader_sp = InputReaderSP(new InputReader(*m_debugger_sp.get())); - - if (!m_reader_sp) - { - m_error = true; - return; - } - - Error error (m_reader_sp->Initialize (ScriptInterpreterPython::PythonInputReaderManager::InputReaderCallback, - m_interpreter, // baton - eInputReaderGranularityLine, // token size, to pass to callback function - NULL, // end token - NULL, // prompt - true)); // echo input - if (error.Fail()) - m_error = true; - else - { - m_debugger_sp->PushInputReader (m_reader_sp); - m_interpreter->m_embedded_thread_input_reader_sp = m_reader_sp; - } -} - -ScriptInterpreterPython::PythonInputReaderManager::~PythonInputReaderManager() -{ - // Nothing to do if either m_interpreter or m_reader_sp is invalid. - if (!m_interpreter || !m_reader_sp) - return; - - m_reader_sp->SetIsDone (true); - if (m_debugger_sp) - m_debugger_sp->PopInputReader(m_reader_sp); - - // Only mess with m_interpreter's counterpart if, indeed, they are the same object. - if (m_reader_sp.get() == m_interpreter->m_embedded_thread_input_reader_sp.get()) - { - m_interpreter->m_embedded_thread_pty.CloseSlaveFileDescriptor(); - m_interpreter->m_embedded_thread_input_reader_sp.reset(); - } -} - -size_t -ScriptInterpreterPython::PythonInputReaderManager::InputReaderCallback (void *baton, - InputReader &reader, - InputReaderAction notification, - const char *bytes, - size_t bytes_len) -{ - lldb::thread_t embedded_interpreter_thread; - Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_SCRIPT)); - - if (baton == NULL) - return 0; - - ScriptInterpreterPython *script_interpreter = (ScriptInterpreterPython *) baton; - - if (script_interpreter->m_script_lang != eScriptLanguagePython) - return 0; - - switch (notification) - { - case eInputReaderActivate: - { - // Save terminal settings if we can - int input_fd = reader.GetDebugger().GetInputFile().GetDescriptor(); - if (input_fd == File::kInvalidDescriptor) - input_fd = STDIN_FILENO; - - script_interpreter->SaveTerminalState(input_fd); - - char error_str[1024]; - if (script_interpreter->m_embedded_thread_pty.OpenFirstAvailableMaster (O_RDWR|O_NOCTTY, error_str, - sizeof(error_str))) - { - if (log) - log->Printf ("ScriptInterpreterPython::NonInteractiveInputReaderCallback, Activate, succeeded in opening master pty (fd = %d).", - script_interpreter->m_embedded_thread_pty.GetMasterFileDescriptor()); - { - StreamString run_string; - char error_str[1024]; - const char *pty_slave_name = script_interpreter->m_embedded_thread_pty.GetSlaveName (error_str, sizeof (error_str)); - if (pty_slave_name != NULL && PyThreadState_GetDict() != NULL) - { - ScriptInterpreterPython::Locker locker(script_interpreter, - ScriptInterpreterPython::Locker::AcquireLock | ScriptInterpreterPython::Locker::InitSession | ScriptInterpreterPython::Locker::InitGlobals, - ScriptInterpreterPython::Locker::FreeAcquiredLock); - run_string.Printf ("run_one_line (%s, 'save_stderr = sys.stderr')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - - run_string.Printf ("run_one_line (%s, 'sys.stderr = sys.stdout')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - - run_string.Printf ("run_one_line (%s, 'save_stdin = sys.stdin')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - - run_string.Printf ("run_one_line (%s, \"sys.stdin = open ('%s', 'r')\")", script_interpreter->m_dictionary_name.c_str(), - pty_slave_name); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - } - } - embedded_interpreter_thread = Host::ThreadCreate ("<lldb.script-interpreter.noninteractive-python>", - ScriptInterpreterPython::PythonInputReaderManager::RunPythonInputReader, - script_interpreter, NULL); - if (IS_VALID_LLDB_HOST_THREAD(embedded_interpreter_thread)) - { - if (log) - log->Printf ("ScriptInterpreterPython::NonInteractiveInputReaderCallback, Activate, succeeded in creating thread (thread_t = %p)", (void *)embedded_interpreter_thread); - Error detach_error; - Host::ThreadDetach (embedded_interpreter_thread, &detach_error); - } - else - { - if (log) - log->Printf ("ScriptInterpreterPython::NonInteractiveInputReaderCallback, Activate, failed in creating thread"); - reader.SetIsDone (true); - } - } - else - { - if (log) - log->Printf ("ScriptInterpreterPython::NonInteractiveInputReaderCallback, Activate, failed to open master pty "); - reader.SetIsDone (true); - } - } - break; - - case eInputReaderDeactivate: - // When another input reader is pushed, don't leave the session... - //script_interpreter->LeaveSession (); - break; - - case eInputReaderReactivate: -// { -// ScriptInterpreterPython::Locker locker(script_interpreter, -// ScriptInterpreterPython::Locker::AcquireLock | ScriptInterpreterPython::Locker::InitSession, -// ScriptInterpreterPython::Locker::FreeAcquiredLock); -// } - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderInterrupt: - { - PyThreadState* state = _PyThreadState_Current; - if (!state) - state = script_interpreter->m_command_thread_state; - if (state) - { - long tid = state->thread_id; - _PyThreadState_Current = state; - int num_threads = PyThreadState_SetAsyncExc(tid, PyExc_KeyboardInterrupt); - if (log) - log->Printf("ScriptInterpreterPython::NonInteractiveInputReaderCallback, eInputReaderInterrupt, tid = %ld, num_threads = %d, state = %p", - tid,num_threads,state); - } - else if (log) - log->Printf("ScriptInterpreterPython::NonInteractiveInputReaderCallback, eInputReaderInterrupt, state = NULL"); - } - break; - - case eInputReaderEndOfFile: - reader.SetIsDone(true); - break; - - case eInputReaderGotToken: - if (script_interpreter->m_embedded_thread_pty.GetMasterFileDescriptor() != -1) - { - if (log) - log->Printf ("ScriptInterpreterPython::NonInteractiveInputReaderCallback, GotToken, bytes='%s', byte_len = %zu", bytes, - bytes_len); - if (bytes && bytes_len) - ::write (script_interpreter->m_embedded_thread_pty.GetMasterFileDescriptor(), bytes, bytes_len); - ::write (script_interpreter->m_embedded_thread_pty.GetMasterFileDescriptor(), "\n", 1); - } - else - { - if (log) - log->Printf ("ScriptInterpreterPython::NonInteractiveInputReaderCallback, GotToken, bytes='%s', byte_len = %zu, Master File Descriptor is bad.", - bytes, - bytes_len); - reader.SetIsDone (true); - } - break; - - case eInputReaderDone: - { - StreamString run_string; - char error_str[1024]; - const char *pty_slave_name = script_interpreter->m_embedded_thread_pty.GetSlaveName (error_str, sizeof (error_str)); - if (pty_slave_name != NULL && PyThreadState_GetDict() != NULL) - { - ScriptInterpreterPython::Locker locker(script_interpreter, - ScriptInterpreterPython::Locker::AcquireLock, - ScriptInterpreterPython::Locker::FreeAcquiredLock); - run_string.Printf ("run_one_line (%s, 'sys.stdin = save_stdin; sys.stderr = save_stderr')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear(); - } - // Restore terminal settings if they were validly saved - if (log) - log->Printf ("ScriptInterpreterPython::NonInteractiveInputReaderCallback, Done, closing down input reader."); - - script_interpreter->RestoreTerminalState (); - - script_interpreter->m_embedded_thread_pty.CloseMasterFileDescriptor(); - } - break; - } - - return bytes_len; -} ScriptInterpreterPython::ScriptInterpreterPython (CommandInterpreter &interpreter) : ScriptInterpreter (interpreter, eScriptLanguagePython), - m_embedded_thread_pty (), - m_embedded_python_pty (), - m_embedded_thread_input_reader_sp (), - m_embedded_python_input_reader_sp (), - m_dbg_stdout (interpreter.GetDebugger().GetOutputFile().GetStream()), - m_new_sysout (NULL), - m_old_sysout (NULL), - m_old_syserr (NULL), - m_run_one_line (NULL), + IOHandlerDelegateMultiline("DONE"), + m_saved_stdin (), + m_saved_stdout (), + m_saved_stderr (), + m_main_module (), + m_lldb_module (), + m_session_dict (false), // Don't create an empty dictionary, leave it invalid + m_sys_module_dict (false), // Don't create an empty dictionary, leave it invalid + m_run_one_line_function (), + m_run_one_line_str_global (), m_dictionary_name (interpreter.GetDebugger().GetInstanceName().AsCString()), m_terminal_state (), + m_active_io_handler (eIOHandlerNone), m_session_is_active (false), + m_pty_slave_is_open (false), m_valid_session (true), m_command_thread_state (NULL) { - static int g_initialized = false; - - if (!g_initialized) - { - g_initialized = true; - ScriptInterpreterPython::InitializePrivate (); - } + ScriptInterpreterPython::InitializePrivate (); m_dictionary_name.append("_dict"); StreamString run_string; @@ -435,62 +200,116 @@ ScriptInterpreterPython::ScriptInterpreterPython (CommandInterpreter &interprete run_string.Printf ("run_one_line (%s, 'lldb.debugger_unique_id = %" PRIu64 "; pydoc.pager = pydoc.plainpager')", m_dictionary_name.c_str(), interpreter.GetDebugger().GetID()); PyRun_SimpleString (run_string.GetData()); - - if (m_dbg_stdout != NULL) - { - m_new_sysout = PyFile_FromFile (m_dbg_stdout, (char *) "", (char *) "w", _check_and_flush); - } - - // get the output file handle from the debugger (if any) - File& out_file = interpreter.GetDebugger().GetOutputFile(); - if (out_file.IsValid()) - ResetOutputFileHandle(out_file.GetStream()); } ScriptInterpreterPython::~ScriptInterpreterPython () { - Debugger &debugger = GetCommandInterpreter().GetDebugger(); +} - if (m_embedded_thread_input_reader_sp.get() != NULL) - { - m_embedded_thread_input_reader_sp->SetIsDone (true); - m_embedded_thread_pty.CloseSlaveFileDescriptor(); - const InputReaderSP reader_sp = m_embedded_thread_input_reader_sp; - debugger.PopInputReader (reader_sp); - m_embedded_thread_input_reader_sp.reset(); - } +void +ScriptInterpreterPython::IOHandlerActivated (IOHandler &io_handler) +{ + const char *instructions = NULL; - if (m_embedded_python_input_reader_sp.get() != NULL) + switch (m_active_io_handler) { - m_embedded_python_input_reader_sp->SetIsDone (true); - m_embedded_python_pty.CloseSlaveFileDescriptor(); - const InputReaderSP reader_sp = m_embedded_python_input_reader_sp; - debugger.PopInputReader (reader_sp); - m_embedded_python_input_reader_sp.reset(); + case eIOHandlerNone: + break; + case eIOHandlerBreakpoint: + instructions = R"(Enter your Python command(s). Type 'DONE' to end. +def function (frame, bp_loc, internal_dict): + """frame: the lldb.SBFrame for the location at which you stopped + bp_loc: an lldb.SBBreakpointLocation for the breakpoint location information + internal_dict: an LLDB support object not to be used""" +)"; + break; + case eIOHandlerWatchpoint: + instructions = "Enter your Python command(s). Type 'DONE' to end.\n"; + break; } - if (m_new_sysout) + if (instructions) { - Locker locker(this, - ScriptInterpreterPython::Locker::AcquireLock, - ScriptInterpreterPython::Locker::FreeLock); - Py_XDECREF ((PyObject*)m_new_sysout); + StreamFileSP output_sp(io_handler.GetOutputStreamFile()); + if (output_sp) + { + output_sp->PutCString(instructions); + output_sp->Flush(); + } } } void -ScriptInterpreterPython::ResetOutputFileHandle (FILE *fh) +ScriptInterpreterPython::IOHandlerInputComplete (IOHandler &io_handler, std::string &data) { - if (fh == NULL) - return; - - m_dbg_stdout = fh; + io_handler.SetIsDone(true); + bool batch_mode = m_interpreter.GetBatchCommandMode(); + + switch (m_active_io_handler) + { + case eIOHandlerNone: + break; + case eIOHandlerBreakpoint: + { + BreakpointOptions *bp_options = (BreakpointOptions *)io_handler.GetUserData(); + std::unique_ptr<BreakpointOptions::CommandData> data_ap(new BreakpointOptions::CommandData()); + if (data_ap.get()) + { + data_ap->user_source.SplitIntoLines(data); + + if (GenerateBreakpointCommandCallbackData (data_ap->user_source, data_ap->script_source)) + { + BatonSP baton_sp (new BreakpointOptions::CommandBaton (data_ap.release())); + bp_options->SetCallback (ScriptInterpreterPython::BreakpointCallbackFunction, baton_sp); + } + else if (!batch_mode) + { + StreamFileSP error_sp = io_handler.GetErrorStreamFile(); + if (error_sp) + { + error_sp->Printf ("Warning: No command attached to breakpoint.\n"); + error_sp->Flush(); + } + } + } + m_active_io_handler = eIOHandlerNone; + } + break; + case eIOHandlerWatchpoint: + { + WatchpointOptions *wp_options = (WatchpointOptions *)io_handler.GetUserData(); + std::unique_ptr<WatchpointOptions::CommandData> data_ap(new WatchpointOptions::CommandData()); + if (data_ap.get()) + { + data_ap->user_source.SplitIntoLines(data); + + if (GenerateWatchpointCommandCallbackData (data_ap->user_source, data_ap->script_source)) + { + BatonSP baton_sp (new WatchpointOptions::CommandBaton (data_ap.release())); + wp_options->SetCallback (ScriptInterpreterPython::WatchpointCallbackFunction, baton_sp); + } + else if (!batch_mode) + { + StreamFileSP error_sp = io_handler.GetErrorStreamFile(); + if (error_sp) + { + error_sp->Printf ("Warning: No command attached to breakpoint.\n"); + error_sp->Flush(); + } + } + } + m_active_io_handler = eIOHandlerNone; + } + break; + } - Locker locker(this, - ScriptInterpreterPython::Locker::AcquireLock, - ScriptInterpreterPython::Locker::FreeAcquiredLock); - m_new_sysout = PyFile_FromFile (m_dbg_stdout, (char *) "", (char *) "w", _check_and_flush); +} + + +void +ScriptInterpreterPython::ResetOutputFileHandle (FILE *fh) +{ } void @@ -529,15 +348,15 @@ ScriptInterpreterPython::LeaveSession () // When the current thread state is NULL, PyThreadState_Get() issues a fatal error. if (PyThreadState_GetDict()) { - PyObject *sysmod = PyImport_AddModule ("sys"); - PyObject *sysdict = PyModule_GetDict (sysmod); - - if (m_new_sysout && sysmod && sysdict) + PythonDictionary &sys_module_dict = GetSysModuleDictionary (); + if (sys_module_dict) { - if (m_old_sysout) - PyDict_SetItemString (sysdict, "stdout", (PyObject*)m_old_sysout); - if (m_old_syserr) - PyDict_SetItemString (sysdict, "stderr", (PyObject*)m_old_syserr); + if (m_saved_stdin) + sys_module_dict.SetItemForKey("stdin", m_saved_stdin); + if (m_saved_stdout) + sys_module_dict.SetItemForKey("stdout", m_saved_stdout); + if (m_saved_stderr) + sys_module_dict.SetItemForKey("stderr", m_saved_stderr); } } @@ -545,7 +364,10 @@ ScriptInterpreterPython::LeaveSession () } bool -ScriptInterpreterPython::EnterSession (bool init_lldb_globals) +ScriptInterpreterPython::EnterSession (bool init_lldb_globals, + FILE *in, + FILE *out, + FILE *err) { // If we have already entered the session, without having officially 'left' it, then there is no need to // 'enter' it again. @@ -578,26 +400,53 @@ ScriptInterpreterPython::EnterSession (bool init_lldb_globals) else { // If we aren't initing the globals, we should still always set the debugger (since that is always unique.) - run_string.Printf ( "run_one_line (%s, \"lldb.debugger_unique_id = %" PRIu64, m_dictionary_name.c_str(), GetCommandInterpreter().GetDebugger().GetID()); + run_string.Printf ( "run_one_line (%s, 'lldb.debugger_unique_id = %" PRIu64, m_dictionary_name.c_str(), GetCommandInterpreter().GetDebugger().GetID()); run_string.Printf ( "; lldb.debugger = lldb.SBDebugger.FindDebuggerWithID (%" PRIu64 ")", GetCommandInterpreter().GetDebugger().GetID()); - run_string.PutCString ("\")"); + run_string.PutCString ("')"); } PyRun_SimpleString (run_string.GetData()); run_string.Clear(); - PyObject *sysmod = PyImport_AddModule ("sys"); - PyObject *sysdict = PyModule_GetDict (sysmod); - - if (m_new_sysout && sysmod && sysdict) + PythonDictionary &sys_module_dict = GetSysModuleDictionary (); + if (sys_module_dict) { - m_old_sysout = PyDict_GetItemString(sysdict, "stdout"); - m_old_syserr = PyDict_GetItemString(sysdict, "stderr"); - if (m_new_sysout) + lldb::StreamFileSP in_sp; + lldb::StreamFileSP out_sp; + lldb::StreamFileSP err_sp; + if (in == NULL || out == NULL || err == NULL) + m_interpreter.GetDebugger().AdoptTopIOHandlerFilesIfInvalid (in_sp, out_sp, err_sp); + + if (in == NULL && in_sp) + in = in_sp->GetFile().GetStream(); + if (in) { - PyDict_SetItemString (sysdict, "stdout", (PyObject*)m_new_sysout); - PyDict_SetItemString (sysdict, "stderr", (PyObject*)m_new_sysout); + m_saved_stdin.Reset(sys_module_dict.GetItemForKey("stdin")); + + sys_module_dict.SetItemForKey ("stdin", PyFile_FromFile (in, (char *) "", (char *) "r", 0)); } + else + m_saved_stdin.Reset(); + + if (out == NULL && out_sp) + out = out_sp->GetFile().GetStream(); + if (out) + { + m_saved_stdout.Reset(sys_module_dict.GetItemForKey("stdout")); + sys_module_dict.SetItemForKey ("stdout", PyFile_FromFile (out, (char *) "", (char *) "w", 0)); + } + else + m_saved_stdout.Reset(); + + if (err == NULL && err_sp) + err = err_sp->GetFile().GetStream(); + if (err) + { + m_saved_stderr.Reset(sys_module_dict.GetItemForKey("stderr")); + sys_module_dict.SetItemForKey ("stderr", PyFile_FromFile (err, (char *) "", (char *) "w", 0)); + } + else + m_saved_stdout.Reset(); } if (PyErr_Occurred()) @@ -606,44 +455,42 @@ ScriptInterpreterPython::EnterSession (bool init_lldb_globals) return true; } -static PyObject* -FindSessionDictionary (const char* dict_name) +PythonObject & +ScriptInterpreterPython::GetMainModule () { - static std::map<ConstString,PyObject*> g_dict_map; - - ConstString dict(dict_name); - - std::map<ConstString,PyObject*>::iterator iter = g_dict_map.find(dict); - - if (iter != g_dict_map.end()) - return iter->second; - - PyObject *main_mod = PyImport_AddModule ("__main__"); - if (main_mod != NULL) + if (!m_main_module) + m_main_module.Reset(PyImport_AddModule ("__main__")); + return m_main_module; +} + +PythonDictionary & +ScriptInterpreterPython::GetSessionDictionary () +{ + if (!m_session_dict) { - PyObject *main_dict = PyModule_GetDict (main_mod); - if ((main_dict != NULL) - && PyDict_Check (main_dict)) + PythonObject &main_module = GetMainModule (); + if (main_module) { - // Go through the main dictionary looking for the correct python script interpreter dictionary - PyObject *key, *value; - Py_ssize_t pos = 0; - - while (PyDict_Next (main_dict, &pos, &key, &value)) + PythonDictionary main_dict(PyModule_GetDict (main_module.get())); + if (main_dict) { - // We have stolen references to the key and value objects in the dictionary; we need to increment - // them now so that Python's garbage collector doesn't collect them out from under us. - Py_INCREF (key); - Py_INCREF (value); - if (strcmp (PyString_AsString (key), dict_name) == 0) - { - g_dict_map[dict] = value; - return value; - } + m_session_dict = main_dict.GetItemForKey(m_dictionary_name.c_str()); } } } - return NULL; + return m_session_dict; +} + +PythonDictionary & +ScriptInterpreterPython::GetSysModuleDictionary () +{ + if (!m_sys_module_dict) + { + PyObject *sys_module = PyImport_AddModule ("sys"); + if (sys_module) + m_sys_module_dict.Reset(PyModule_GetDict (sys_module)); + } + return m_sys_module_dict; } static std::string @@ -665,82 +512,158 @@ GenerateUniqueName (const char* base_name_wanted, } bool +ScriptInterpreterPython::GetEmbeddedInterpreterModuleObjects () +{ + if (!m_run_one_line_function) + { + PyObject *module = PyImport_AddModule ("lldb.embedded_interpreter"); + if (module != NULL) + { + PythonDictionary module_dict (PyModule_GetDict (module)); + if (module_dict) + { + m_run_one_line_function = module_dict.GetItemForKey("run_one_line"); + m_run_one_line_str_global = module_dict.GetItemForKey("g_run_one_line_str"); + } + } + } + return (bool)m_run_one_line_function; +} + +static void +ReadThreadBytesReceived(void *baton, const void *src, size_t src_len) +{ + if (src && src_len) + { + Stream *strm = (Stream *)baton; + strm->Write(src, src_len); + strm->Flush(); + } +} + +bool ScriptInterpreterPython::ExecuteOneLine (const char *command, CommandReturnObject *result, const ExecuteScriptOptions &options) { if (!m_valid_session) return false; + + if (command && command[0]) + { + // We want to call run_one_line, passing in the dictionary and the command string. We cannot do this through + // PyRun_SimpleString here because the command string may contain escaped characters, and putting it inside + // another string to pass to PyRun_SimpleString messes up the escaping. So we use the following more complicated + // method to pass the command string directly down to Python. + Debugger &debugger = m_interpreter.GetDebugger(); - // We want to call run_one_line, passing in the dictionary and the command string. We cannot do this through - // PyRun_SimpleString here because the command string may contain escaped characters, and putting it inside - // another string to pass to PyRun_SimpleString messes up the escaping. So we use the following more complicated - // method to pass the command string directly down to Python. - - Locker locker(this, - ScriptInterpreterPython::Locker::AcquireLock | ScriptInterpreterPython::Locker::InitSession | (options.GetSetLLDBGlobals() ? ScriptInterpreterPython::Locker::InitGlobals : 0), - ScriptInterpreterPython::Locker::FreeAcquiredLock | ScriptInterpreterPython::Locker::TearDownSession); - - bool success = false; + StreamFileSP input_file_sp; + StreamFileSP output_file_sp; + StreamFileSP error_file_sp; + Communication output_comm ("lldb.ScriptInterpreterPython.ExecuteOneLine.comm"); + int pipe_fds[2] = { -1, -1 }; + + if (options.GetEnableIO()) + { + if (result) + { + input_file_sp = debugger.GetInputFile(); + // Set output to a temporary file so we can forward the results on to the result object + + int err = pipe(pipe_fds); + if (err == 0) + { + std::unique_ptr<ConnectionFileDescriptor> conn_ap(new ConnectionFileDescriptor(pipe_fds[0], false)); + if (conn_ap->IsConnected()) + { + output_comm.SetConnection(conn_ap.release()); + output_comm.SetReadThreadBytesReceivedCallback(ReadThreadBytesReceived, &result->GetOutputStream()); + output_comm.StartReadThread(); + FILE *outfile_handle = fdopen (pipe_fds[1], "w"); + output_file_sp.reset(new StreamFile(outfile_handle, true)); + error_file_sp = output_file_sp; + if (outfile_handle) + ::setbuf (outfile_handle, NULL); + + result->SetImmediateOutputFile(debugger.GetOutputFile()->GetFile().GetStream()); + result->SetImmediateErrorFile(debugger.GetErrorFile()->GetFile().GetStream()); + } + } + } + if (!input_file_sp || !output_file_sp || !error_file_sp) + debugger.AdoptTopIOHandlerFilesIfInvalid(input_file_sp, output_file_sp, error_file_sp); + } + else + { + input_file_sp.reset (new StreamFile ()); + input_file_sp->GetFile().Open("/dev/null", File::eOpenOptionRead); + output_file_sp.reset (new StreamFile ()); + output_file_sp->GetFile().Open("/dev/null", File::eOpenOptionWrite); + error_file_sp = output_file_sp; + } - if (command) - { + FILE *in_file = input_file_sp->GetFile().GetStream(); + FILE *out_file = output_file_sp->GetFile().GetStream(); + FILE *err_file = error_file_sp->GetFile().GetStream(); + Locker locker(this, + ScriptInterpreterPython::Locker::AcquireLock | + ScriptInterpreterPython::Locker::InitSession | + (options.GetSetLLDBGlobals() ? ScriptInterpreterPython::Locker::InitGlobals : 0), + ScriptInterpreterPython::Locker::FreeAcquiredLock | + ScriptInterpreterPython::Locker::TearDownSession, + in_file, + out_file, + err_file); + + bool success = false; + // Find the correct script interpreter dictionary in the main module. - PyObject *script_interpreter_dict = FindSessionDictionary(m_dictionary_name.c_str()); - if (script_interpreter_dict != NULL) + PythonDictionary &session_dict = GetSessionDictionary (); + if (session_dict) { - PyObject *pfunc = (PyObject*)m_run_one_line; - PyObject *pmod = PyImport_AddModule ("lldb.embedded_interpreter"); - if (pmod != NULL) + if (GetEmbeddedInterpreterModuleObjects ()) { - PyObject *pmod_dict = PyModule_GetDict (pmod); - if ((pmod_dict != NULL) - && PyDict_Check (pmod_dict)) + PyObject *pfunc = m_run_one_line_function.get(); + + if (pfunc && PyCallable_Check (pfunc)) { - if (!pfunc) + PythonObject pargs (Py_BuildValue("(Os)", session_dict.get(), command)); + if (pargs) { - PyObject *key, *value; - Py_ssize_t pos = 0; - - while (PyDict_Next (pmod_dict, &pos, &key, &value)) + PythonObject return_value; + { // scope for PythonInputReaderManager + //PythonInputReaderManager py_input(options.GetEnableIO() ? this : NULL); + return_value.Reset(PyObject_CallObject (pfunc, pargs.get())); + } + if (return_value) { - Py_INCREF (key); - Py_INCREF (value); - if (strcmp (PyString_AsString (key), "run_one_line") == 0) - { - pfunc = value; - break; - } + success = true; } - m_run_one_line = pfunc; - } - - if (pfunc && PyCallable_Check (pfunc)) - { - PyObject *pargs = Py_BuildValue("(Os)",script_interpreter_dict,command); - if (pargs != NULL) + else if (options.GetMaskoutErrors() && PyErr_Occurred ()) { - PyObject *pvalue = NULL; - { // scope for PythonInputReaderManager - PythonInputReaderManager py_input(options.GetEnableIO() ? this : NULL); - pvalue = PyObject_CallObject (pfunc, pargs); - } - Py_XDECREF (pargs); - if (pvalue != NULL) - { - Py_XDECREF (pvalue); - success = true; - } - else if (options.GetMaskoutErrors() && PyErr_Occurred ()) - { - PyErr_Print(); - PyErr_Clear(); - } + PyErr_Print(); + PyErr_Clear(); } } } } - Py_INCREF (script_interpreter_dict); } + // Flush our output and error file handles + ::fflush (out_file); + if (out_file != err_file) + ::fflush (err_file); + + if (pipe_fds[0] != -1) + { + // Close write end of pipe so our communication thread exits + output_comm.Disconnect(); + output_comm.StopReadThread(); + // Close the read end of the pipe and don't close the write end + // since we called fdopen on it and gave the ownership to the + // connection in "output_comm" + ::close (pipe_fds[0]); + } + + if (success) return true; @@ -755,155 +678,107 @@ ScriptInterpreterPython::ExecuteOneLine (const char *command, CommandReturnObjec return false; } -size_t -ScriptInterpreterPython::InputReaderCallback -( - void *baton, - InputReader &reader, - InputReaderAction notification, - const char *bytes, - size_t bytes_len -) -{ - lldb::thread_t embedded_interpreter_thread; - Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_SCRIPT)); - if (baton == NULL) - return 0; +class IOHandlerPythonInterpreter : + public IOHandler +{ +public: + + IOHandlerPythonInterpreter (Debugger &debugger, + ScriptInterpreterPython *python) : + IOHandler (debugger), + m_python(python) + { - ScriptInterpreterPython *script_interpreter = (ScriptInterpreterPython *) baton; + } - if (script_interpreter->m_script_lang != eScriptLanguagePython) - return 0; + virtual + ~IOHandlerPythonInterpreter() + { + + } - switch (notification) + virtual ConstString + GetControlSequence (char ch) { - case eInputReaderActivate: - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - if (!batch_mode) - { - out_stream->Printf ("Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.\n"); - out_stream->Flush(); - } - - // Save terminal settings if we can - int input_fd = reader.GetDebugger().GetInputFile().GetDescriptor(); - if (input_fd == File::kInvalidDescriptor) - input_fd = STDIN_FILENO; - - script_interpreter->SaveTerminalState(input_fd); - - { - ScriptInterpreterPython::Locker locker(script_interpreter, - ScriptInterpreterPython::Locker::AcquireLock | ScriptInterpreterPython::Locker::InitSession | ScriptInterpreterPython::Locker::InitGlobals, - ScriptInterpreterPython::Locker::FreeAcquiredLock); - } + if (ch == 'd') + return ConstString("quit()\n"); + return ConstString(); + } - char error_str[1024]; - if (script_interpreter->m_embedded_python_pty.OpenFirstAvailableMaster (O_RDWR|O_NOCTTY, error_str, - sizeof(error_str))) + virtual void + Run () + { + if (m_python) + { + int stdin_fd = GetInputFD(); + if (stdin_fd >= 0) { - if (log) - log->Printf ("ScriptInterpreterPython::InputReaderCallback, Activate, succeeded in opening master pty (fd = %d).", - script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor()); - embedded_interpreter_thread = Host::ThreadCreate ("<lldb.script-interpreter.embedded-python-loop>", - ScriptInterpreterPython::RunEmbeddedPythonInterpreter, - script_interpreter, NULL); - if (IS_VALID_LLDB_HOST_THREAD(embedded_interpreter_thread)) - { - if (log) - log->Printf ("ScriptInterpreterPython::InputReaderCallback, Activate, succeeded in creating thread (thread_t = %p)", (void *)embedded_interpreter_thread); - Error detach_error; - Host::ThreadDetach (embedded_interpreter_thread, &detach_error); - } - else + Terminal terminal(stdin_fd); + TerminalState terminal_state; + const bool is_a_tty = terminal.IsATerminal(); + + if (is_a_tty) { - if (log) - log->Printf ("ScriptInterpreterPython::InputReaderCallback, Activate, failed in creating thread"); - reader.SetIsDone (true); + terminal_state.Save (stdin_fd, false); + terminal.SetCanonical(false); + terminal.SetEcho(true); } - } - else - { - if (log) - log->Printf ("ScriptInterpreterPython::InputReaderCallback, Activate, failed to open master pty "); - reader.SetIsDone (true); + + ScriptInterpreterPython::Locker locker (m_python, + ScriptInterpreterPython::Locker::AcquireLock | + ScriptInterpreterPython::Locker::InitSession | + ScriptInterpreterPython::Locker::InitGlobals, + ScriptInterpreterPython::Locker::FreeAcquiredLock | + ScriptInterpreterPython::Locker::TearDownSession); + + // The following call drops into the embedded interpreter loop and stays there until the + // user chooses to exit from the Python interpreter. + // This embedded interpreter will, as any Python code that performs I/O, unlock the GIL before + // a system call that can hang, and lock it when the syscall has returned. + + // We need to surround the call to the embedded interpreter with calls to PyGILState_Ensure and + // PyGILState_Release (using the Locker above). This is because Python has a global lock which must be held whenever we want + // to touch any Python objects. Otherwise, if the user calls Python code, the interpreter state will be off, + // and things could hang (it's happened before). + + StreamString run_string; + run_string.Printf ("run_python_interpreter (%s)", m_python->GetDictionaryName ()); + PyRun_SimpleString (run_string.GetData()); + + if (is_a_tty) + terminal_state.Restore(); } } - break; - - case eInputReaderDeactivate: - // When another input reader is pushed, don't leave the session... - //script_interpreter->LeaveSession (); - break; + SetIsDone(true); + } - case eInputReaderReactivate: - { - ScriptInterpreterPython::Locker locker (script_interpreter, - ScriptInterpreterPython::Locker::AcquireLock | ScriptInterpreterPython::Locker::InitSession, - ScriptInterpreterPython::Locker::FreeAcquiredLock); - } - break; + virtual void + Hide () + { - case eInputReaderAsynchronousOutputWritten: - break; + } + + virtual void + Refresh () + { - case eInputReaderInterrupt: - ::write (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor(), "raise KeyboardInterrupt\n", 24); - break; + } + + virtual void + Interrupt () + { - case eInputReaderEndOfFile: - ::write (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor(), "quit()\n", 7); - break; - - case eInputReaderGotToken: - if (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor() != -1) - { - if (log) - log->Printf ("ScriptInterpreterPython::InputReaderCallback, GotToken, bytes='%s', byte_len = %zu", bytes, - bytes_len); - if (bytes && bytes_len) - { - if ((int) bytes[0] == 4) - ::write (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor(), "quit()", 6); - else - ::write (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor(), bytes, bytes_len); - } - ::write (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor(), "\n", 1); - } - else - { - if (log) - log->Printf ("ScriptInterpreterPython::InputReaderCallback, GotToken, bytes='%s', byte_len = %zu, Master File Descriptor is bad.", - bytes, - bytes_len); - reader.SetIsDone (true); - } - - break; + } + + virtual void + GotEOF() + { - case eInputReaderDone: - { - Locker locker(script_interpreter, - ScriptInterpreterPython::Locker::AcquireLock, - ScriptInterpreterPython::Locker::FreeAcquiredLock); - script_interpreter->LeaveSession (); - } - - // Restore terminal settings if they were validly saved - if (log) - log->Printf ("ScriptInterpreterPython::InputReaderCallback, Done, closing down input reader."); - - script_interpreter->RestoreTerminalState (); - - script_interpreter->m_embedded_python_pty.CloseMasterFileDescriptor(); - break; } - - return bytes_len; -} +protected: + ScriptInterpreterPython *m_python; +}; void @@ -918,24 +793,13 @@ ScriptInterpreterPython::ExecuteInterpreterLoop () // try to embed a running interpreter loop inside the already running Python interpreter loop, so we won't // do it. - if (!debugger.GetInputFile().IsValid()) + if (!debugger.GetInputFile()->GetFile().IsValid()) return; - InputReaderSP reader_sp (new InputReader(debugger)); - if (reader_sp) - { - Error error (reader_sp->Initialize (ScriptInterpreterPython::InputReaderCallback, - this, // baton - eInputReaderGranularityLine, // token size, to pass to callback function - NULL, // end token - NULL, // prompt - true)); // echo input - - if (error.Success()) - { - debugger.PushInputReader (reader_sp); - m_embedded_python_input_reader_sp = reader_sp; - } + IOHandlerSP io_handler_sp (new IOHandlerPythonInterpreter (debugger, this)); + if (io_handler_sp) + { + debugger.PushIOHandler(io_handler_sp); } } @@ -951,27 +815,21 @@ ScriptInterpreterPython::ExecuteOneLineWithReturn (const char *in_string, ScriptInterpreterPython::Locker::FreeAcquiredLock | ScriptInterpreterPython::Locker::TearDownSession); PyObject *py_return = NULL; - PyObject *mainmod = PyImport_AddModule ("__main__"); - PyObject *globals = PyModule_GetDict (mainmod); - PyObject *locals = NULL; + PythonObject &main_module = GetMainModule (); + PythonDictionary globals (PyModule_GetDict(main_module.get())); PyObject *py_error = NULL; bool ret_success = false; - bool should_decrement_locals = false; int success; - locals = FindSessionDictionary(m_dictionary_name.c_str()); + PythonDictionary locals = GetSessionDictionary (); - if (locals == NULL) + if (!locals) { - locals = PyObject_GetAttrString (globals, m_dictionary_name.c_str()); - should_decrement_locals = true; + locals = PyObject_GetAttrString (globals.get(), m_dictionary_name.c_str()); } - if (locals == NULL) - { + if (!locals) locals = globals; - should_decrement_locals = false; - } py_error = PyErr_Occurred(); if (py_error != NULL) @@ -980,22 +838,18 @@ ScriptInterpreterPython::ExecuteOneLineWithReturn (const char *in_string, if (in_string != NULL) { { // scope for PythonInputReaderManager - PythonInputReaderManager py_input(options.GetEnableIO() ? this : NULL); - py_return = PyRun_String (in_string, Py_eval_input, globals, locals); + //PythonInputReaderManager py_input(options.GetEnableIO() ? this : NULL); + py_return = PyRun_String (in_string, Py_eval_input, globals.get(), locals.get()); if (py_return == NULL) { py_error = PyErr_Occurred (); if (py_error != NULL) PyErr_Clear (); - py_return = PyRun_String (in_string, Py_single_input, globals, locals); + py_return = PyRun_String (in_string, Py_single_input, globals.get(), locals.get()); } } - if (locals != NULL - && should_decrement_locals) - Py_XDECREF (locals); - if (py_return != NULL) { switch (return_type) @@ -1115,35 +969,31 @@ ScriptInterpreterPython::ExecuteOneLineWithReturn (const char *in_string, return ret_success; } -bool +Error ScriptInterpreterPython::ExecuteMultipleLines (const char *in_string, const ExecuteScriptOptions &options) { - + Error error; Locker locker(this, ScriptInterpreterPython::Locker::AcquireLock | ScriptInterpreterPython::Locker::InitSession | (options.GetSetLLDBGlobals() ? ScriptInterpreterPython::Locker::InitGlobals : 0), ScriptInterpreterPython::Locker::FreeAcquiredLock | ScriptInterpreterPython::Locker::TearDownSession); bool success = false; - PyObject *py_return = NULL; - PyObject *mainmod = PyImport_AddModule ("__main__"); - PyObject *globals = PyModule_GetDict (mainmod); - PyObject *locals = NULL; + PythonObject return_value; + PythonObject &main_module = GetMainModule (); + PythonDictionary globals (PyModule_GetDict(main_module.get())); PyObject *py_error = NULL; - bool should_decrement_locals = false; - locals = FindSessionDictionary(m_dictionary_name.c_str()); + PythonDictionary locals = GetSessionDictionary (); - if (locals == NULL) + if (!locals) { - locals = PyObject_GetAttrString (globals, m_dictionary_name.c_str()); - should_decrement_locals = true; + locals = PyObject_GetAttrString (globals.get(), m_dictionary_name.c_str()); } - if (locals == NULL) + if (!locals) { locals = globals; - should_decrement_locals = false; } py_error = PyErr_Occurred(); @@ -1159,16 +1009,11 @@ ScriptInterpreterPython::ExecuteMultipleLines (const char *in_string, const Exec if (compiled_code) { { // scope for PythonInputReaderManager - PythonInputReaderManager py_input(options.GetEnableIO() ? this : NULL); - py_return = PyEval_EvalCode (compiled_code, globals, locals); + //PythonInputReaderManager py_input(options.GetEnableIO() ? this : NULL); + return_value.Reset(PyEval_EvalCode (compiled_code, globals.get(), locals.get())); } - if (py_return != NULL) - { + if (return_value) success = true; - Py_XDECREF (py_return); - } - if (locals && should_decrement_locals) - Py_XDECREF (locals); } } } @@ -1176,324 +1021,50 @@ ScriptInterpreterPython::ExecuteMultipleLines (const char *in_string, const Exec py_error = PyErr_Occurred (); if (py_error != NULL) { - success = false; - if (options.GetMaskoutErrors()) - { - if (PyErr_GivenExceptionMatches (py_error, PyExc_SyntaxError)) - PyErr_Print (); - PyErr_Clear(); - } - } - - return success; -} - -static const char *g_reader_instructions = "Enter your Python command(s). Type 'DONE' to end."; - -static const char *g_bkpt_command_reader_instructions = "Enter your Python command(s). Type 'DONE' to end.\n" - "def function(frame,bp_loc,internal_dict):\n" - " \"\"\"frame: the SBFrame for the location at which you stopped\n" - " bp_loc: an SBBreakpointLocation for the breakpoint location information\n" - " internal_dict: an LLDB support object not to be used\"\"\""; - -size_t -ScriptInterpreterPython::GenerateBreakpointOptionsCommandCallback -( - void *baton, - InputReader &reader, - InputReaderAction notification, - const char *bytes, - size_t bytes_len -) -{ - static StringList commands_in_progress; - - switch (notification) - { - case eInputReaderActivate: - { - - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - commands_in_progress.Clear(); - if (!batch_mode) - { - out_stream->Printf ("%s\n", g_bkpt_command_reader_instructions); - if (reader.GetPrompt()) - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush (); - } - } - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderReactivate: - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - if (reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush (); - } - } - break; - - case eInputReaderAsynchronousOutputWritten: - break; +// puts(in_string); +// _PyObject_Dump (py_error); +// PyErr_Print(); +// success = false; - case eInputReaderGotToken: - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - std::string temp_string (bytes, bytes_len); - commands_in_progress.AppendString (temp_string.c_str()); - if (!reader.IsDone() && reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush (); - } - } - break; - - case eInputReaderEndOfFile: - case eInputReaderInterrupt: - // Control-c (SIGINT) & control-d both mean finish & exit. - reader.SetIsDone(true); + PyObject *type = NULL; + PyObject *value = NULL; + PyObject *traceback = NULL; + PyErr_Fetch (&type,&value,&traceback); - // Control-c (SIGINT) ALSO means cancel; do NOT create a breakpoint command. - if (notification == eInputReaderInterrupt) - commands_in_progress.Clear(); + // get the backtrace + std::string bt = ReadPythonBacktrace(traceback); - // Fall through here... - - case eInputReaderDone: + if (value && value != Py_None) + error.SetErrorStringWithFormat("%s\n%s", PyString_AsString(PyObject_Str(value)),bt.c_str()); + else + error.SetErrorStringWithFormat("%s",bt.c_str()); + Py_XDECREF(type); + Py_XDECREF(value); + Py_XDECREF(traceback); + if (options.GetMaskoutErrors()) { - bool batch_mode = notification == eInputReaderDone ? - reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode() : - true; - BreakpointOptions *bp_options = (BreakpointOptions *)baton; - std::unique_ptr<BreakpointOptions::CommandData> data_ap(new BreakpointOptions::CommandData()); - data_ap->user_source.AppendList (commands_in_progress); - if (data_ap.get()) - { - ScriptInterpreter *interpreter = reader.GetDebugger().GetCommandInterpreter().GetScriptInterpreter(); - if (interpreter) - { - if (interpreter->GenerateBreakpointCommandCallbackData (data_ap->user_source, - data_ap->script_source)) - { - BatonSP baton_sp (new BreakpointOptions::CommandBaton (data_ap.release())); - bp_options->SetCallback (ScriptInterpreterPython::BreakpointCallbackFunction, baton_sp); - } - else if (!batch_mode) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - out_stream->Printf ("Warning: No command attached to breakpoint.\n"); - out_stream->Flush(); - } - } - else - { - if (!batch_mode) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - out_stream->Printf ("Warning: Unable to find script intepreter; no command attached to breakpoint.\n"); - out_stream->Flush(); - } - } - } + PyErr_Clear(); } - break; - } - return bytes_len; + return error; } -size_t -ScriptInterpreterPython::GenerateWatchpointOptionsCommandCallback -( - void *baton, - InputReader &reader, - InputReaderAction notification, - const char *bytes, - size_t bytes_len -) -{ - static StringList commands_in_progress; - - switch (notification) - { - case eInputReaderActivate: - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - - commands_in_progress.Clear(); - if (!batch_mode) - { - out_stream->Printf ("%s\n", g_reader_instructions); - if (reader.GetPrompt()) - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush (); - } - } - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderReactivate: - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - if (reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush (); - } - } - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderGotToken: - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - bool batch_mode = reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); - std::string temp_string (bytes, bytes_len); - commands_in_progress.AppendString (temp_string.c_str()); - if (!reader.IsDone() && reader.GetPrompt() && !batch_mode) - { - out_stream->Printf ("%s", reader.GetPrompt()); - out_stream->Flush (); - } - } - break; - - case eInputReaderEndOfFile: - case eInputReaderInterrupt: - // Control-c (SIGINT) & control-d both mean finish & exit. - reader.SetIsDone(true); - - // Control-c (SIGINT) ALSO means cancel; do NOT create a breakpoint command. - if (notification == eInputReaderInterrupt) - commands_in_progress.Clear(); - - // Fall through here... - - case eInputReaderDone: - { - bool batch_mode = notification == eInputReaderDone ? - reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode() : - true; - WatchpointOptions *wp_options = (WatchpointOptions *)baton; - std::unique_ptr<WatchpointOptions::CommandData> data_ap(new WatchpointOptions::CommandData()); - data_ap->user_source.AppendList (commands_in_progress); - if (data_ap.get()) - { - ScriptInterpreter *interpreter = reader.GetDebugger().GetCommandInterpreter().GetScriptInterpreter(); - if (interpreter) - { - if (interpreter->GenerateWatchpointCommandCallbackData (data_ap->user_source, - data_ap->script_source)) - { - BatonSP baton_sp (new WatchpointOptions::CommandBaton (data_ap.release())); - wp_options->SetCallback (ScriptInterpreterPython::WatchpointCallbackFunction, baton_sp); - } - else if (!batch_mode) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - out_stream->Printf ("Warning: No command attached to breakpoint.\n"); - out_stream->Flush(); - } - } - else - { - if (!batch_mode) - { - StreamSP out_stream = reader.GetDebugger().GetAsyncOutputStream(); - out_stream->Printf ("Warning: Unable to find script intepreter; no command attached to breakpoint.\n"); - out_stream->Flush(); - } - } - } - } - break; - - } - - return bytes_len; -} void ScriptInterpreterPython::CollectDataForBreakpointCommandCallback (BreakpointOptions *bp_options, CommandReturnObject &result) { - Debugger &debugger = GetCommandInterpreter().GetDebugger(); - - InputReaderSP reader_sp (new InputReader (debugger)); - - if (reader_sp) - { - Error err = reader_sp->Initialize ( - ScriptInterpreterPython::GenerateBreakpointOptionsCommandCallback, - bp_options, // baton - eInputReaderGranularityLine, // token size, for feeding data to callback function - "DONE", // end token - " ", // prompt - true); // echo input - - if (err.Success()) - debugger.PushInputReader (reader_sp); - else - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - } - } - else - { - result.AppendError("out of memory"); - result.SetStatus (eReturnStatusFailed); - } + m_active_io_handler = eIOHandlerBreakpoint; + m_interpreter.GetPythonCommandsFromIOHandler (" ", *this, true, bp_options); } void ScriptInterpreterPython::CollectDataForWatchpointCommandCallback (WatchpointOptions *wp_options, CommandReturnObject &result) { - Debugger &debugger = GetCommandInterpreter().GetDebugger(); - - InputReaderSP reader_sp (new InputReader (debugger)); - - if (reader_sp) - { - Error err = reader_sp->Initialize ( - ScriptInterpreterPython::GenerateWatchpointOptionsCommandCallback, - wp_options, // baton - eInputReaderGranularityLine, // token size, for feeding data to callback function - "DONE", // end token - "> ", // prompt - true); // echo input - - if (err.Success()) - debugger.PushInputReader (reader_sp); - else - { - result.AppendError (err.AsCString()); - result.SetStatus (eReturnStatusFailed); - } - } - else - { - result.AppendError("out of memory"); - result.SetStatus (eReturnStatusFailed); - } + m_active_io_handler = eIOHandlerWatchpoint; + m_interpreter.GetPythonCommandsFromIOHandler (" ", *this, true, wp_options); } // Set a Python one-liner as the callback for the breakpoint. @@ -1548,7 +1119,7 @@ ScriptInterpreterPython::ExportFunctionDefinitionToInterpreter (StringList &func // Convert StringList to one long, newline delimited, const char *. std::string function_def_string(function_def.CopyList()); - return ExecuteMultipleLines (function_def_string.c_str(), ScriptInterpreter::ExecuteScriptOptions().SetEnableIO(false)); + return ExecuteMultipleLines (function_def_string.c_str(), ScriptInterpreter::ExecuteScriptOptions().SetEnableIO(false)).Success(); } bool @@ -2140,7 +1711,7 @@ ScriptInterpreterPython::GetScriptedSummary (const char *python_function_name, { Timer scoped_timer ("g_swig_typescript_callback","g_swig_typescript_callback"); ret_val = g_swig_typescript_callback (python_function_name, - FindSessionDictionary(m_dictionary_name.c_str()), + GetSessionDictionary().get(), valobj, &new_callee, retval); @@ -2188,8 +1759,7 @@ ScriptInterpreterPython::BreakpointCallbackFunction if (!script_interpreter) return true; - if (python_function_name != NULL - && python_function_name[0] != '\0') + if (python_function_name && python_function_name[0]) { const StackFrameSP stop_frame_sp (exe_ctx.GetFrameSP()); BreakpointSP breakpoint_sp = target->GetBreakpointByID (break_id); @@ -2243,8 +1813,7 @@ ScriptInterpreterPython::WatchpointCallbackFunction if (!script_interpreter) return true; - if (python_function_name != NULL - && python_function_name[0] != '\0') + if (python_function_name && python_function_name[0]) { const StackFrameSP stop_frame_sp (exe_ctx.GetFrameSP()); WatchpointSP wp_sp = target->GetWatchpointList().FindByID (watch_id); @@ -2269,106 +1838,6 @@ ScriptInterpreterPython::WatchpointCallbackFunction return true; } -lldb::thread_result_t -ScriptInterpreterPython::RunEmbeddedPythonInterpreter (lldb::thread_arg_t baton) -{ - ScriptInterpreterPython *script_interpreter = (ScriptInterpreterPython *) baton; - - Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_SCRIPT)); - - if (log) - log->Printf ("%p ScriptInterpreterPython::RunEmbeddedPythonInterpreter () thread starting...", baton); - - char error_str[1024]; - const char *pty_slave_name = script_interpreter->m_embedded_python_pty.GetSlaveName (error_str, sizeof (error_str)); - - if (pty_slave_name != NULL) - { - StreamString run_string; - - // Ensure we have the GIL before running any Python code. - // Since we're only running a few one-liners and then dropping to the interpreter (which will release the GIL when needed), - // we can just release the GIL after finishing our work. - // If finer-grained locking is desirable, we can lock and unlock the GIL only when calling a python function. - Locker locker(script_interpreter, - ScriptInterpreterPython::Locker::AcquireLock | ScriptInterpreterPython::Locker::InitSession | ScriptInterpreterPython::Locker::InitGlobals, - ScriptInterpreterPython::Locker::FreeAcquiredLock | ScriptInterpreterPython::Locker::TearDownSession); - - run_string.Printf ("run_one_line (%s, 'save_stderr = sys.stderr')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - - run_string.Printf ("run_one_line (%s, 'sys.stderr = sys.stdout')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - - run_string.Printf ("run_one_line (%s, 'save_stdin = sys.stdin')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - - run_string.Printf ("run_one_line (%s, \"sys.stdin = open ('%s', 'r')\")", script_interpreter->m_dictionary_name.c_str(), - pty_slave_name); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - - // The following call drops into the embedded interpreter loop and stays there until the - // user chooses to exit from the Python interpreter. - // This embedded interpreter will, as any Python code that performs I/O, unlock the GIL before - // a system call that can hang, and lock it when the syscall has returned. - - // We need to surround the call to the embedded interpreter with calls to PyGILState_Ensure and - // PyGILState_Release (using the Locker above). This is because Python has a global lock which must be held whenever we want - // to touch any Python objects. Otherwise, if the user calls Python code, the interpreter state will be off, - // and things could hang (it's happened before). - - run_string.Printf ("run_python_interpreter (%s)", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear (); - - run_string.Printf ("run_one_line (%s, 'sys.stdin = save_stdin')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear(); - - run_string.Printf ("run_one_line (%s, 'sys.stderr = save_stderr')", script_interpreter->m_dictionary_name.c_str()); - PyRun_SimpleString (run_string.GetData()); - run_string.Clear(); - } - - if (script_interpreter->m_embedded_python_input_reader_sp) - script_interpreter->m_embedded_python_input_reader_sp->SetIsDone (true); - - script_interpreter->m_embedded_python_pty.CloseSlaveFileDescriptor(); - - log = lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_SCRIPT); - if (log) - log->Printf ("%p ScriptInterpreterPython::RunEmbeddedPythonInterpreter () thread exiting...", baton); - - - // Clean up the input reader and make the debugger pop it off the stack. - Debugger &debugger = script_interpreter->GetCommandInterpreter().GetDebugger(); - const InputReaderSP reader_sp = script_interpreter->m_embedded_python_input_reader_sp; - if (reader_sp) - { - debugger.PopInputReader (reader_sp); - script_interpreter->m_embedded_python_input_reader_sp.reset(); - } - - return NULL; -} - -lldb::thread_result_t -ScriptInterpreterPython::PythonInputReaderManager::RunPythonInputReader (lldb::thread_arg_t baton) -{ - ScriptInterpreterPython *script_interpreter = (ScriptInterpreterPython *) baton; - - const InputReaderSP reader_sp = script_interpreter->m_embedded_thread_input_reader_sp; - - if (reader_sp) - reader_sp->WaitOnReaderIsDone(); - - return NULL; -} - size_t ScriptInterpreterPython::CalculateNumChildren (const lldb::ScriptInterpreterObjectSP& implementor_sp) { @@ -2755,7 +2224,7 @@ ScriptInterpreterPython::LoadScriptingModule (const char* pathname, command_stream.Printf("if not (sys.path.__contains__('%s')):\n sys.path.insert(1,'%s');\n\n", directory.c_str(), directory.c_str()); - bool syspath_retval = ExecuteMultipleLines(command_stream.GetData(), ScriptInterpreter::ExecuteScriptOptions().SetEnableIO(false).SetSetLLDBGlobals(false)); + bool syspath_retval = ExecuteMultipleLines(command_stream.GetData(), ScriptInterpreter::ExecuteScriptOptions().SetEnableIO(false).SetSetLLDBGlobals(false)).Success(); if (!syspath_retval) { error.SetErrorString("Python sys.path handling failed"); @@ -2818,47 +2287,9 @@ ScriptInterpreterPython::LoadScriptingModule (const char* pathname, else command_stream.Printf("import %s",basename.c_str()); - bool import_retval = ExecuteMultipleLines(command_stream.GetData(), ScriptInterpreter::ExecuteScriptOptions().SetEnableIO(false).SetSetLLDBGlobals(false).SetMaskoutErrors(false)); - PyObject* py_error = PyErr_Occurred(); // per Python docs: "you do not need to Py_DECREF()" the return of this function - - if (py_error || !import_retval) // check for failure of the import - { - if (py_error) // if we have a Python error.. - { - PyObject *type = NULL,*value = NULL,*traceback = NULL; - PyErr_Fetch (&type,&value,&traceback); - - if (PyErr_GivenExceptionMatches (py_error, PyExc_ImportError)) // and it is an ImportError - { - if (value && value != Py_None) - error.SetErrorString(PyString_AsString(PyObject_Str(value))); - else - error.SetErrorString("ImportError raised by imported module"); - } - else // any other error - { - // get the backtrace - std::string bt = ReadPythonBacktrace(traceback); - - if (value && value != Py_None) - error.SetErrorStringWithFormat("Python error raised while importing module: %s - traceback: %s", PyString_AsString(PyObject_Str(value)),bt.c_str()); - else - error.SetErrorStringWithFormat("Python raised an error while importing module - traceback: %s",bt.c_str()); - } - - Py_XDECREF(type); - Py_XDECREF(value); - Py_XDECREF(traceback); - } - else // we failed but have no error to explain why - { - error.SetErrorString("unknown error while importing module"); - } - - // anyway, clear the error indicator and return false - PyErr_Clear(); + error = ExecuteMultipleLines(command_stream.GetData(), ScriptInterpreter::ExecuteScriptOptions().SetEnableIO(false).SetSetLLDBGlobals(false)); + if (error.Fail()) return false; - } // if we are here, everything worked // call __lldb_init_module(debugger,dict) @@ -2954,7 +2385,7 @@ ScriptInterpreterPython::RunScriptBasedCommand(const char* impl_function, // to set the asynchronous exception - not a desirable situation m_command_thread_state = _PyThreadState_Current; - PythonInputReaderManager py_input(this); + //PythonInputReaderManager py_input(this); ret_val = g_swig_call_command (impl_function, m_dictionary_name.c_str(), @@ -3059,6 +2490,13 @@ ScriptInterpreterPython::InitializeInterpreter (SWIGInitCallback swig_init_callb void ScriptInterpreterPython::InitializePrivate () { + static int g_initialized = false; + + if (g_initialized) + return; + + g_initialized = true; + Timer scoped_timer (__PRETTY_FUNCTION__, __PRETTY_FUNCTION__); // Python will muck with STDIN terminal state, so save off any current TTY diff --git a/lldb/source/Interpreter/embedded_interpreter.py b/lldb/source/Interpreter/embedded_interpreter.py index 06b70ebeb8b..bc0dd6d5ae4 100644 --- a/lldb/source/Interpreter/embedded_interpreter.py +++ b/lldb/source/Interpreter/embedded_interpreter.py @@ -1,103 +1,108 @@ -import readline +import __builtin__ import code +import lldb import sys import traceback -class SimpleREPL(code.InteractiveConsole): - def __init__(self, prompt, dict): - code.InteractiveConsole.__init__(self,dict) - self.prompt = prompt - self.loop_exit = False - self.dict = dict +try: + import readline + import rlcompleter +except ImportError: + have_readline = False +else: + have_readline = True + if 'libedit' in readline.__doc__: + readline.parse_and_bind('bind ^I rl_complete') + else: + readline.parse_and_bind('tab: complete') - def interact(self): - try: - sys.ps1 - except AttributeError: - sys.ps1 = ">>> " - try: - sys.ps2 - except AttributeError: - sys.ps2 = "... " +g_builtin_override_called = False - while not self.loop_exit: - try: - self.read_py_command() - except (SystemExit, EOFError): - # EOF while in Python just breaks out to top level. - self.write('\n') - self.loop_exit = True - break - except KeyboardInterrupt: - self.write("\nKeyboardInterrupt\n") - self.resetbuffer() - more = 0 - except: - traceback.print_exc() +class LLDBQuitter(object): + def __init__(self, name): + self.name = name + def __repr__(self): + self() + def __call__(self, code=None): + global g_builtin_override_called + g_builtin_override_called = True + raise SystemExit(-1) - def process_input (self, in_str): - # Canonicalize the format of the input string - temp_str = in_str - temp_str.strip(' \t') - words = temp_str.split() - temp_str = ('').join(words) +def setquit(): + '''Redefine builtin functions 'quit()' and 'exit()' to print a message and raise an EOFError exception.''' + # This function will be called prior to each interactive + # interpreter loop or each single line, so we set the global + # g_builtin_override_called to False so we know if a SystemExit + # is thrown, we can catch it and tell the difference between + # a call to "quit()" or "exit()" and something like + # "sys.exit(123)" + global g_builtin_override_called + g_builtin_override_called = False + __builtin__.quit = LLDBQuitter('quit') + __builtin__.exit = LLDBQuitter('exit') - # Check the input string to see if it was the quit - # command. If so, intercept it, so that it doesn't - # close stdin on us! - if (temp_str.lower() == "quit()" or temp_str.lower() == "exit()"): - self.loop_exit = True - in_str = "raise SystemExit " - return in_str +# When running one line, we might place the string to run in this string +# in case it would be hard to correctly escape a string's contents - def my_raw_input (self, prompt): - stream = sys.stdout - stream.write (prompt) - stream.flush () - try: - line = sys.stdin.readline() - except KeyboardInterrupt: - line = " \n" - except (SystemExit, EOFError): - line = "quit()\n" - if not line: - raise EOFError - if line[-1] == '\n': - line = line[:-1] - return line +g_run_one_line_str = None - def read_py_command(self): - # Read off a complete Python command. - more = 0 - while 1: - if more: - prompt = sys.ps2 - else: - prompt = sys.ps1 - line = self.my_raw_input(prompt) - # Can be None if sys.stdin was redefined - encoding = getattr(sys.stdin, "encoding", None) - if encoding and not isinstance(line, unicode): - line = line.decode(encoding) - line = self.process_input (line) - more = self.push(line) - if not more: - break - def one_line (self, input): - line = self.process_input (input) - more = self.push(line) - if more: - self.write ("Input not a complete line.\n") - self.resetbuffer() - more = 0 +def get_terminal_size(fd): + try: + import fcntl, termios, struct + hw = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) + except: + hw = (0,0) + return hw -def run_python_interpreter (dict): - # Pass in the dictionary, for continuity from one session to the next. - repl = SimpleREPL('>>> ', dict) - repl.interact() +def readfunc_stdio(prompt): + sys.stdout.write(prompt) + return sys.stdin.readline() -def run_one_line (dict, input_string): - repl = SimpleREPL ('', dict) - repl.one_line (input_string) +def run_python_interpreter (local_dict): + # Pass in the dictionary, for continuity from one session to the next. + setquit() + try: + fd = sys.stdin.fileno(); + interacted = False + if get_terminal_size(fd)[1] == 0: + try: + import termios + old = termios.tcgetattr(fd) + if old[3] & termios.ECHO: + # Need to turn off echoing and restore + new = termios.tcgetattr(fd) + new[3] = new[3] & ~termios.ECHO + try: + termios.tcsetattr(fd, termios.TCSADRAIN, new) + interacted = True + code.interact(banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.", readfunc=readfunc_stdio, local=local_dict) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old) + except: + pass + # Don't need to turn off echoing + if not interacted: + code.interact(banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.", readfunc=readfunc_stdio, local=local_dict) + else: + # We have a real interactive terminal + code.interact(banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.", local=local_dict) + except SystemExit as e: + global g_builtin_override_called + if not g_builtin_override_called: + print 'Script exited with %s' %(e) +def run_one_line (local_dict, input_string): + global g_run_one_line_str + setquit() + try: + repl = code.InteractiveConsole(local_dict); + if input_string: + repl.runsource (input_string) + elif g_run_one_line_str: + repl.runsource (g_run_one_line_str) + + except SystemExit as e: + global g_builtin_override_called + if not g_builtin_override_called: + print 'Script exited with %s' %(e) diff --git a/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp b/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp index 2876fd4c70d..dcee569943a 100644 --- a/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp +++ b/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp @@ -19,6 +19,7 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Core/Section.h" #include "lldb/Core/State.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Host/Symbols.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/RegisterContext.h" @@ -717,7 +718,7 @@ DynamicLoaderDarwinKernel::KextImageInfo::ReadMemoryModule (Process *process) { if (m_uuid != exe_module->GetUUID()) { - Stream *s = &process->GetTarget().GetDebugger().GetOutputStream(); + Stream *s = process->GetTarget().GetDebugger().GetOutputFile().get(); if (s) { s->Printf ("warning: Host-side kernel file has Mach-O UUID of %s but remote kernel has a UUID of %s -- a mismatched kernel file will result in a poor debugger experience.\n", @@ -765,7 +766,7 @@ DynamicLoaderDarwinKernel::KextImageInfo::LoadImageUsingMemoryModule (Process *p if (IsKernel() && uuid_is_valid && m_memory_module_sp.get()) { - Stream *s = &target.GetDebugger().GetOutputStream(); + Stream *s = target.GetDebugger().GetOutputFile().get(); if (s) { s->Printf ("Kernel UUID: %s\n", m_memory_module_sp->GetUUID().GetAsString().c_str()); @@ -834,7 +835,7 @@ DynamicLoaderDarwinKernel::KextImageInfo::LoadImageUsingMemoryModule (Process *p if (IsKernel() && !m_module_sp) { - Stream *s = &target.GetDebugger().GetOutputStream(); + Stream *s = target.GetDebugger().GetOutputFile().get(); if (s) { s->Printf ("WARNING: Unable to locate kernel binary on the debugger system.\n"); @@ -867,7 +868,7 @@ DynamicLoaderDarwinKernel::KextImageInfo::LoadImageUsingMemoryModule (Process *p if (!m_module_sp && !IsKernel() && m_uuid.IsValid() && !m_name.empty()) { - Stream *s = &target.GetDebugger().GetOutputStream(); + Stream *s = target.GetDebugger().GetOutputFile().get(); if (s) { s->Printf ("warning: Can't find binary/dSYM for %s (%s)\n", @@ -945,7 +946,7 @@ DynamicLoaderDarwinKernel::KextImageInfo::LoadImageUsingMemoryModule (Process *p if (is_loaded && m_module_sp && IsKernel()) { - Stream *s = &target.GetDebugger().GetOutputStream(); + Stream *s = target.GetDebugger().GetOutputFile().get(); if (s) { ObjectFile *kernel_object_file = m_module_sp->GetObjectFile(); @@ -1248,7 +1249,7 @@ DynamicLoaderDarwinKernel::ParseKextSummaries (const Address &kext_summary_addr, if (number_of_new_kexts_being_added == 0 && number_of_old_kexts_being_removed == 0) return true; - Stream *s = &m_process->GetTarget().GetDebugger().GetOutputStream(); + Stream *s = m_process->GetTarget().GetDebugger().GetOutputFile().get(); if (s && load_kexts) { if (number_of_new_kexts_being_added > 0 && number_of_old_kexts_being_removed > 0) diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp index 1bcbb4e8c45..9349d31dca6 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp @@ -22,6 +22,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Log.h" #include "lldb/Core/Module.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/Value.h" #include "lldb/Expression/ClangExpression.h" #include "lldb/Expression/ClangFunction.h" @@ -664,7 +665,7 @@ AppleObjCTrampolineHandler::AppleObjCTrampolineHandler (const ProcessSP &process // step through any method dispatches. Warn to that effect and get out of here. if (process_sp->CanJIT()) { - process_sp->GetTarget().GetDebugger().GetErrorStream().Printf("Could not find implementation lookup function \"%s\"" + process_sp->GetTarget().GetDebugger().GetErrorFile()->Printf ("Could not find implementation lookup function \"%s\"" " step in through ObjC method dispatch will not work.\n", get_impl_name.AsCString()); } diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp index 5e2d8c31349..09d11efea18 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp @@ -812,7 +812,12 @@ PlatformDarwin::Attach (ProcessAttachInfo &attach_info, process_sp = target->CreateProcess (listener, attach_info.GetProcessPluginName(), NULL); if (process_sp) + { + ListenerSP listener_sp (new Listener("lldb.PlatformDarwin.attach.hijack")); + attach_info.SetHijackListener(listener_sp); + process_sp->HijackProcessEvents(listener_sp.get()); error = process_sp->Attach (attach_info); + } } } else diff --git a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h index 6d80d0aa80e..7021aa9a65e 100644 --- a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h +++ b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h @@ -21,7 +21,6 @@ #include "lldb/Core/Broadcaster.h" #include "lldb/Core/ConstString.h" #include "lldb/Core/Error.h" -#include "lldb/Core/InputReader.h" #include "lldb/Core/StreamString.h" #include "lldb/Core/StringList.h" #include "lldb/Core/ThreadSafeValue.h" diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index 7ff7c632f48..f3a0e349da7 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -34,7 +34,6 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/ConnectionFileDescriptor.h" #include "lldb/Host/FileSpec.h" -#include "lldb/Core/InputReader.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/PluginManager.h" @@ -1073,27 +1072,6 @@ ProcessGDBRemote::DoAttachToProcessWithID (lldb::pid_t attach_pid, const Process return error; } -size_t -ProcessGDBRemote::AttachInputReaderCallback -( - void *baton, - InputReader *reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len -) -{ - if (notification == eInputReaderGotToken) - { - ProcessGDBRemote *gdb_process = (ProcessGDBRemote *)baton; - if (gdb_process->m_waiting_for_attach) - gdb_process->m_waiting_for_attach = false; - reader->SetIsDone(true); - return 1; - } - return 0; -} - Error ProcessGDBRemote::DoAttachToProcessWithName (const char *process_name, const ProcessAttachInfo &attach_info) { diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h index 9d5ca42366d..9331775bb89 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -21,7 +21,6 @@ #include "lldb/Core/Broadcaster.h" #include "lldb/Core/ConstString.h" #include "lldb/Core/Error.h" -#include "lldb/Core/InputReader.h" #include "lldb/Core/StreamString.h" #include "lldb/Core/StringList.h" #include "lldb/Core/ThreadSafeValue.h" @@ -378,13 +377,6 @@ protected: GetDispatchQueueNameForThread (lldb::addr_t thread_dispatch_qaddr, std::string &dispatch_queue_name); - static size_t - AttachInputReaderCallback (void *baton, - lldb_private::InputReader *reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len); - lldb_private::DynamicLoader * GetDynamicLoader (); diff --git a/lldb/source/Symbol/ClangASTType.cpp b/lldb/source/Symbol/ClangASTType.cpp index 47bc6829522..40f6462ee36 100644 --- a/lldb/source/Symbol/ClangASTType.cpp +++ b/lldb/source/Symbol/ClangASTType.cpp @@ -38,6 +38,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Scalar.h" #include "lldb/Core/Stream.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/StreamString.h" #include "lldb/Symbol/ClangASTContext.h" #include "lldb/Symbol/ClangExternalASTSourceCommon.h" diff --git a/lldb/source/Symbol/Function.cpp b/lldb/source/Symbol/Function.cpp index 31334a6df8d..e6d6c000bc9 100644 --- a/lldb/source/Symbol/Function.cpp +++ b/lldb/source/Symbol/Function.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "lldb/Symbol/Function.h" +#include "lldb/Core/Disassembler.h" #include "lldb/Core/Module.h" #include "lldb/Core/Section.h" #include "lldb/Host/Host.h" @@ -404,6 +405,43 @@ Function::CalculateSymbolContextFunction () return this; } +lldb::DisassemblerSP +Function::GetInstructions (const ExecutionContext &exe_ctx, + const char *flavor, + bool prefer_file_cache) +{ + ModuleSP module_sp (GetAddressRange().GetBaseAddress().GetModule()); + if (module_sp) + { + const bool prefer_file_cache = false; + return Disassembler::DisassembleRange (module_sp->GetArchitecture(), + NULL, + flavor, + exe_ctx, + GetAddressRange(), + prefer_file_cache); + } + return lldb::DisassemblerSP(); +} + +bool +Function::GetDisassembly (const ExecutionContext &exe_ctx, + const char *flavor, + bool prefer_file_cache, + Stream &strm) +{ + lldb::DisassemblerSP disassembler_sp = GetInstructions (exe_ctx, flavor, prefer_file_cache); + if (disassembler_sp) + { + const bool show_address = true; + const bool show_bytes = false; + disassembler_sp->GetInstructionList().Dump (&strm, show_address, show_bytes, &exe_ctx); + return true; + } + return false; +} + + //Symbol * //Function::CalculateSymbolContextSymbol () //{ diff --git a/lldb/source/Symbol/Symbol.cpp b/lldb/source/Symbol/Symbol.cpp index a881b6f3101..6311a329739 100644 --- a/lldb/source/Symbol/Symbol.cpp +++ b/lldb/source/Symbol/Symbol.cpp @@ -580,3 +580,40 @@ Symbol::ResolveReExportedSymbol (Target &target) } return NULL; } + + +lldb::DisassemblerSP +Symbol::GetInstructions (const ExecutionContext &exe_ctx, + const char *flavor, + bool prefer_file_cache) +{ + ModuleSP module_sp (m_addr_range.GetBaseAddress().GetModule()); + if (module_sp) + { + const bool prefer_file_cache = false; + return Disassembler::DisassembleRange (module_sp->GetArchitecture(), + NULL, + flavor, + exe_ctx, + m_addr_range, + prefer_file_cache); + } + return lldb::DisassemblerSP(); +} + +bool +Symbol::GetDisassembly (const ExecutionContext &exe_ctx, + const char *flavor, + bool prefer_file_cache, + Stream &strm) +{ + lldb::DisassemblerSP disassembler_sp = GetInstructions (exe_ctx, flavor, prefer_file_cache); + if (disassembler_sp) + { + const bool show_address = true; + const bool show_bytes = false; + disassembler_sp->GetInstructionList().Dump (&strm, show_address, show_bytes, &exe_ctx); + return true; + } + return false; +} diff --git a/lldb/source/Target/ExecutionContext.cpp b/lldb/source/Target/ExecutionContext.cpp index 7a8b60189bc..db4025f40c0 100644 --- a/lldb/source/Target/ExecutionContext.cpp +++ b/lldb/source/Target/ExecutionContext.cpp @@ -154,7 +154,7 @@ ExecutionContext::ExecutionContext (const ExecutionContextRef &exe_ctx_ref) : { } -ExecutionContext::ExecutionContext (const ExecutionContextRef *exe_ctx_ref_ptr) : +ExecutionContext::ExecutionContext (const ExecutionContextRef *exe_ctx_ref_ptr, bool thread_and_frame_only_if_stopped) : m_target_sp (), m_process_sp (), m_thread_sp (), @@ -164,8 +164,11 @@ ExecutionContext::ExecutionContext (const ExecutionContextRef *exe_ctx_ref_ptr) { m_target_sp = exe_ctx_ref_ptr->GetTargetSP(); m_process_sp = exe_ctx_ref_ptr->GetProcessSP(); - m_thread_sp = exe_ctx_ref_ptr->GetThreadSP(); - m_frame_sp = exe_ctx_ref_ptr->GetFrameSP(); + if (!thread_and_frame_only_if_stopped || (m_process_sp && StateIsStoppedState(m_process_sp->GetState(), true))) + { + m_thread_sp = exe_ctx_ref_ptr->GetThreadSP(); + m_frame_sp = exe_ctx_ref_ptr->GetFrameSP(); + } } } @@ -824,9 +827,9 @@ ExecutionContextRef::GetFrameSP () const } ExecutionContext -ExecutionContextRef::Lock () const +ExecutionContextRef::Lock (bool thread_and_frame_only_if_stopped) const { - return ExecutionContext(this); + return ExecutionContext(this, thread_and_frame_only_if_stopped); } diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp index dc80b4a54fc..c49499d029b 100644 --- a/lldb/source/Target/Platform.cpp +++ b/lldb/source/Target/Platform.cpp @@ -1039,6 +1039,8 @@ Platform::DebugProcess (ProcessLaunchInfo &launch_info, process_sp = Attach (attach_info, debugger, target, listener, error); if (process_sp) { + launch_info.SetHijackListener(attach_info.GetHijackListener()); + // Since we attached to the process, it will think it needs to detach // if the process object just goes away without an explicit call to // Process::Kill() or Process::Detach(), so let it know to kill the diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index a5ef629e5eb..799f35a3dea 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -18,15 +18,16 @@ #include "lldb/Core/Event.h" #include "lldb/Core/ConnectionFileDescriptor.h" #include "lldb/Core/Debugger.h" -#include "lldb/Core/InputReader.h" #include "lldb/Core/Log.h" #include "lldb/Core/Module.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/State.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Expression/ClangUserExpression.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Host/Host.h" +#include "lldb/Host/Terminal.h" #include "lldb/Target/ABI.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/OperatingSystem.h" @@ -1244,7 +1245,7 @@ Process::GetNextEvent (EventSP &event_sp) StateType -Process::WaitForProcessToStop (const TimeValue *timeout, lldb::EventSP *event_sp_ptr, bool wait_always) +Process::WaitForProcessToStop (const TimeValue *timeout, lldb::EventSP *event_sp_ptr, bool wait_always, Listener *hijack_listener) { // We can't just wait for a "stopped" event, because the stopped event may have restarted the target. // We have to actually check each event, and in the case of a stopped event check the restarted flag @@ -1273,7 +1274,7 @@ Process::WaitForProcessToStop (const TimeValue *timeout, lldb::EventSP *event_sp while (state != eStateInvalid) { EventSP event_sp; - state = WaitForStateChangedEvents (timeout, event_sp); + state = WaitForStateChangedEvents (timeout, event_sp, hijack_listener); if (event_sp_ptr && event_sp) *event_sp_ptr = event_sp; @@ -1283,12 +1284,22 @@ Process::WaitForProcessToStop (const TimeValue *timeout, lldb::EventSP *event_sp case eStateDetached: case eStateExited: case eStateUnloaded: + // We need to toggle the run lock as this won't get done in + // SetPublicState() if the process is hijacked. + if (hijack_listener) + m_public_run_lock.SetStopped(); return state; case eStateStopped: if (Process::ProcessEventData::GetRestartedFromEvent(event_sp.get())) continue; else + { + // We need to toggle the run lock as this won't get done in + // SetPublicState() if the process is hijacked. + if (hijack_listener) + m_public_run_lock.SetStopped(); return state; + } default: continue; } @@ -1301,7 +1312,8 @@ StateType Process::WaitForState ( const TimeValue *timeout, - const StateType *match_states, const uint32_t num_match_states + const StateType *match_states, + const uint32_t num_match_states ) { EventSP event_sp; @@ -1314,7 +1326,7 @@ Process::WaitForState if (state == eStateDetached || state == eStateExited) return state; - state = WaitForStateChangedEvents (timeout, event_sp); + state = WaitForStateChangedEvents (timeout, event_sp, NULL); for (i=0; i<num_match_states; ++i) { @@ -1360,18 +1372,22 @@ Process::RestorePrivateProcessEvents () } StateType -Process::WaitForStateChangedEvents (const TimeValue *timeout, EventSP &event_sp) +Process::WaitForStateChangedEvents (const TimeValue *timeout, EventSP &event_sp, Listener *hijack_listener) { Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS)); if (log) log->Printf ("Process::%s (timeout = %p, event_sp)...", __FUNCTION__, timeout); + Listener *listener = hijack_listener; + if (listener == NULL) + listener = &m_listener; + StateType state = eStateInvalid; - if (m_listener.WaitForEventForBroadcasterWithType (timeout, - this, - eBroadcastBitStateChanged | eBroadcastBitInterrupt, - event_sp)) + if (listener->WaitForEventForBroadcasterWithType (timeout, + this, + eBroadcastBitStateChanged | eBroadcastBitInterrupt, + event_sp)) { if (event_sp && event_sp->GetType() == eBroadcastBitStateChanged) state = Process::ProcessEventData::GetStateFromEvent(event_sp.get()); @@ -1509,6 +1525,7 @@ Process::SetExitStatus (int status, const char *cstr) DidExit (); SetPrivateState (eStateExited); + CancelWatchForSTDIN (true); return true; } @@ -2183,11 +2200,11 @@ Process::CreateBreakpointSite (const BreakpointLocationSP &owner, bool use_hardw load_addr = ResolveIndirectFunction (&symbol->GetAddress(), error); if (!error.Success() && show_error) { - m_target.GetDebugger().GetErrorFile().Printf ("warning: failed to resolve indirect function at 0x%" PRIx64 " for breakpoint %i.%i: %s\n", - symbol->GetAddress().GetLoadAddress(&m_target), - owner->GetBreakpoint().GetID(), - owner->GetID(), - error.AsCString() ? error.AsCString() : "unkown error"); + m_target.GetDebugger().GetErrorFile()->Printf ("warning: failed to resolve indirect function at 0x%" PRIx64 " for breakpoint %i.%i: %s\n", + symbol->GetAddress().GetLoadAddress(&m_target), + owner->GetBreakpoint().GetID(), + owner->GetID(), + error.AsCString() ? error.AsCString() : "unkown error"); return LLDB_INVALID_BREAK_ID; } Address resolved_address(load_addr); @@ -2231,11 +2248,11 @@ Process::CreateBreakpointSite (const BreakpointLocationSP &owner, bool use_hardw if (show_error) { // Report error for setting breakpoint... - m_target.GetDebugger().GetErrorFile().Printf ("warning: failed to set breakpoint site at 0x%" PRIx64 " for breakpoint %i.%i: %s\n", - load_addr, - owner->GetBreakpoint().GetID(), - owner->GetID(), - error.AsCString() ? error.AsCString() : "unkown error"); + m_target.GetDebugger().GetErrorFile()->Printf ("warning: failed to set breakpoint site at 0x%" PRIx64 " for breakpoint %i.%i: %s\n", + load_addr, + owner->GetBreakpoint().GetID(), + owner->GetID(), + error.AsCString() ? error.AsCString() : "unkown error"); } } } @@ -3756,8 +3773,6 @@ Process::Destroy () } m_stdio_communication.StopReadThread(); m_stdio_communication.Disconnect(); - if (m_process_input_reader && m_process_input_reader->IsActive()) - m_target.GetDebugger().PopInputReader (m_process_input_reader); if (m_process_input_reader) m_process_input_reader.reset(); @@ -4147,9 +4162,14 @@ Process::HandlePrivateEvent (EventSP &event_sp) } Process::ProcessEventData::SetUpdateStateOnRemoval(event_sp.get()); if (StateIsRunningState (new_state)) - PushProcessInputReader (); + { + // Only push the input handler if we aren't fowarding events, + // as this means the curses GUI is in use... + if (!GetTarget().GetDebugger().IsForwardingEvents()) + PushProcessIOHandler (); + } else if (!Process::ProcessEventData::GetRestartedFromEvent(event_sp.get())) - PopProcessInputReader (); + PopProcessIOHandler (); BroadcastEvent (event_sp); } @@ -4695,64 +4715,187 @@ Process::STDIOReadThreadBytesReceived (void *baton, const void *src, size_t src_ process->AppendSTDOUT (static_cast<const char *>(src), src_len); } -size_t -Process::ProcessInputReaderCallback (void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len) +void +Process::ResetProcessIOHandler () +{ + m_process_input_reader.reset(); +} + + +class IOHandlerProcessSTDIO : + public IOHandler { - Process *process = (Process *) baton; - - switch (notification) +public: + IOHandlerProcessSTDIO (Process *process, + int write_fd) : + IOHandler(process->GetTarget().GetDebugger()), + m_process (process), + m_read_file (), + m_write_file (write_fd, false), + m_pipe_read(), + m_pipe_write() + { + m_read_file.SetDescriptor(GetInputFD(), false); + } + + virtual + ~IOHandlerProcessSTDIO () { - case eInputReaderActivate: - break; - - case eInputReaderDeactivate: - break; - - case eInputReaderReactivate: - break; - - case eInputReaderAsynchronousOutputWritten: - break; - case eInputReaderGotToken: + } + + bool + OpenPipes () + { + if (m_pipe_read.IsValid() && m_pipe_write.IsValid()) + return true; + + int fds[2]; + int err = pipe(fds); + if (err == 0) { - Error error; - process->PutSTDIN (bytes, bytes_len, error); + m_pipe_read.SetDescriptor(fds[0], true); + m_pipe_write.SetDescriptor(fds[1], true); + return true; } - break; + return false; + } + + void + ClosePipes() + { + m_pipe_read.Close(); + m_pipe_write.Close(); + } + + // Each IOHandler gets to run until it is done. It should read data + // from the "in" and place output into "out" and "err and return + // when done. + virtual void + Run () + { + if (m_read_file.IsValid() && m_write_file.IsValid()) + { + SetIsDone(false); + if (OpenPipes()) + { + const int read_fd = m_read_file.GetDescriptor(); + const int pipe_read_fd = m_pipe_read.GetDescriptor(); + TerminalState terminal_state; + terminal_state.Save (read_fd, false); + Terminal terminal(read_fd); + terminal.SetCanonical(false); + terminal.SetEcho(false); + while (!GetIsDone()) + { + fd_set read_fdset; + FD_ZERO (&read_fdset); + FD_SET (read_fd, &read_fdset); + FD_SET (pipe_read_fd, &read_fdset); + const int nfds = std::max<int>(read_fd, pipe_read_fd) + 1; + int num_set_fds = select (nfds, &read_fdset, NULL, NULL, NULL); + if (num_set_fds < 0) + { + const int select_errno = errno; + + if (select_errno != EINTR) + SetIsDone(true); + } + else if (num_set_fds > 0) + { + char ch = 0; + size_t n; + if (FD_ISSET (read_fd, &read_fdset)) + { + n = 1; + if (m_read_file.Read(&ch, n).Success() && n == 1) + { + if (m_write_file.Write(&ch, n).Fail() || n != 1) + SetIsDone(true); + } + else + SetIsDone(true); + } + if (FD_ISSET (pipe_read_fd, &read_fdset)) + { + // Consume the interrupt byte + n = 1; + m_pipe_read.Read (&ch, n); + SetIsDone(true); + } + } + } + terminal_state.Restore(); + + } + else + SetIsDone(true); + } + else + SetIsDone(true); + } + + // Hide any characters that have been displayed so far so async + // output can be displayed. Refresh() will be called after the + // output has been displayed. + virtual void + Hide () + { - case eInputReaderInterrupt: - process->SendAsyncInterrupt(); - break; - - case eInputReaderEndOfFile: - process->AppendSTDOUT ("^D", 2); - break; + } + // Called when the async output has been received in order to update + // the input reader (refresh the prompt and redisplay any current + // line(s) that are being edited + virtual void + Refresh () + { - case eInputReaderDone: - break; + } + virtual void + Interrupt () + { + size_t n = 1; + char ch = 'q'; + m_pipe_write.Write (&ch, n); + } + + virtual void + GotEOF() + { } - return bytes_len; +protected: + Process *m_process; + File m_read_file; // Read from this file (usually actual STDIN for LLDB + File m_write_file; // Write to this file (usually the master pty for getting io to debuggee) + File m_pipe_read; + File m_pipe_write; + +}; + +void +Process::WatchForSTDIN (IOHandler &io_handler) +{ } void -Process::ResetProcessInputReader () -{ - m_process_input_reader.reset(); +Process::CancelWatchForSTDIN (bool exited) +{ + if (m_process_input_reader) + { + if (exited) + m_process_input_reader->SetIsDone(true); + m_process_input_reader->Interrupt(); + } } void -Process::SetSTDIOFileDescriptor (int file_descriptor) +Process::SetSTDIOFileDescriptor (int fd) { // First set up the Read Thread for reading/handling process I/O - std::unique_ptr<ConnectionFileDescriptor> conn_ap (new ConnectionFileDescriptor (file_descriptor, true)); + std::unique_ptr<ConnectionFileDescriptor> conn_ap (new ConnectionFileDescriptor (fd, true)); if (conn_ap.get()) { @@ -4765,70 +4908,37 @@ Process::SetSTDIOFileDescriptor (int file_descriptor) // Now read thread is set up, set up input reader. if (!m_process_input_reader.get()) - { - m_process_input_reader.reset (new InputReader(m_target.GetDebugger())); - Error err (m_process_input_reader->Initialize (Process::ProcessInputReaderCallback, - this, - eInputReaderGranularityByte, - NULL, - NULL, - false)); - - if (err.Fail()) - m_process_input_reader.reset(); - } + m_process_input_reader.reset (new IOHandlerProcessSTDIO (this, fd)); } } } void -Process::PushProcessInputReader () +Process::PushProcessIOHandler () { - if (m_process_input_reader && !m_process_input_reader->IsActive()) - m_target.GetDebugger().PushInputReader (m_process_input_reader); + IOHandlerSP io_handler_sp (m_process_input_reader); + if (io_handler_sp) + { + io_handler_sp->SetIsDone(false); + m_target.GetDebugger().PushIOHandler (io_handler_sp); + } } void -Process::PopProcessInputReader () +Process::PopProcessIOHandler () { - if (m_process_input_reader && m_process_input_reader->IsActive()) - m_target.GetDebugger().PopInputReader (m_process_input_reader); + IOHandlerSP io_handler_sp (m_process_input_reader); + if (io_handler_sp) + { + io_handler_sp->Interrupt(); + m_target.GetDebugger().PopIOHandler (io_handler_sp); + } } // The process needs to know about installed plug-ins void Process::SettingsInitialize () { -// static std::vector<OptionEnumValueElement> g_plugins; -// -// int i=0; -// const char *name; -// OptionEnumValueElement option_enum; -// while ((name = PluginManager::GetProcessPluginNameAtIndex (i)) != NULL) -// { -// if (name) -// { -// option_enum.value = i; -// option_enum.string_value = name; -// option_enum.usage = PluginManager::GetProcessPluginDescriptionAtIndex (i); -// g_plugins.push_back (option_enum); -// } -// ++i; -// } -// option_enum.value = 0; -// option_enum.string_value = NULL; -// option_enum.usage = NULL; -// g_plugins.push_back (option_enum); -// -// for (i=0; (name = SettingsController::instance_settings_table[i].var_name); ++i) -// { -// if (::strcmp (name, "plugin") == 0) -// { -// SettingsController::instance_settings_table[i].enum_values = &g_plugins[0]; -// break; -// } -// } -// Thread::SettingsInitialize (); } diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index c53c1e3c1c2..3a980251ef6 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -29,6 +29,7 @@ #include "lldb/Core/Section.h" #include "lldb/Core/SourceManager.h" #include "lldb/Core/State.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/StreamString.h" #include "lldb/Core/Timer.h" #include "lldb/Core/ValueObject.h" @@ -1006,11 +1007,11 @@ LoadScriptingResourceForModule (const ModuleSP &module_sp, Target *target) if (module_sp && !module_sp->LoadScriptingResourceInTarget(target, error, &feedback_stream)) { if (error.AsCString()) - target->GetDebugger().GetErrorStream().Printf("unable to load scripting data for module %s - error reported was %s\n", + target->GetDebugger().GetErrorFile()->Printf("unable to load scripting data for module %s - error reported was %s\n", module_sp->GetFileSpec().GetFileNameStrippingExtension().GetCString(), error.AsCString()); if (feedback_stream.GetSize()) - target->GetDebugger().GetOutputStream().Printf("%s\n", + target->GetDebugger().GetErrorFile()->Printf("%s\n", feedback_stream.GetData()); } } @@ -1998,13 +1999,13 @@ Target::GetSourceManager () } -lldb::user_id_t -Target::AddStopHook (Target::StopHookSP &new_hook_sp) +Target::StopHookSP +Target::CreateStopHook () { lldb::user_id_t new_uid = ++m_stop_hook_next_id; - new_hook_sp.reset (new StopHook(shared_from_this(), new_uid)); - m_stop_hooks[new_uid] = new_hook_sp; - return new_uid; + Target::StopHookSP stop_hook_sp (new StopHook(shared_from_this(), new_uid)); + m_stop_hooks[new_uid] = stop_hook_sp; + return stop_hook_sp; } bool @@ -2323,7 +2324,6 @@ Error Target::Launch (Listener &listener, ProcessLaunchInfo &launch_info) { Error error; - Error error2; StateType state = eStateInvalid; @@ -2399,28 +2399,34 @@ Target::Launch (Listener &listener, ProcessLaunchInfo &launch_info) { if (launch_info.GetFlags().Test(eLaunchFlagStopAtEntry) == false) { - StateType state = m_process_sp->WaitForProcessToStop (NULL, NULL, false); + ListenerSP hijack_listener_sp (launch_info.GetHijackListener()); + + StateType state = m_process_sp->WaitForProcessToStop (NULL, NULL, false, hijack_listener_sp.get()); if (state == eStateStopped) { - error = m_process_sp->Resume(); + if (!synchronous_execution) + m_process_sp->RestoreProcessEvents (); + + error = m_process_sp->PrivateResume(); + if (error.Success()) { if (synchronous_execution) { - state = m_process_sp->WaitForProcessToStop (NULL); + state = m_process_sp->WaitForProcessToStop (NULL, NULL, true, hijack_listener_sp.get()); const bool must_be_alive = false; // eStateExited is ok, so this must be false if (!StateIsStoppedState(state, must_be_alive)) { - error2.SetErrorStringWithFormat("process isn't stopped: %s", StateAsCString(state)); - return error2; + error.SetErrorStringWithFormat("process isn't stopped: %s", StateAsCString(state)); } } } else { + Error error2; error2.SetErrorStringWithFormat("process resume at entry point failed: %s", error.AsCString()); - return error2; + error = error2; } } else @@ -2428,11 +2434,13 @@ Target::Launch (Listener &listener, ProcessLaunchInfo &launch_info) error.SetErrorStringWithFormat ("initial process state wasn't stopped: %s", StateAsCString(state)); } } + m_process_sp->RestoreProcessEvents (); } else { + Error error2; error2.SetErrorStringWithFormat ("process launch failed: %s", error.AsCString()); - return error2; + error = error2; } return error; } diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index b9ae5c7a2a5..72de5b8a3cf 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -2065,3 +2065,125 @@ Thread::IsStillAtLastBreakpointHit () } return false; } + + +Error +Thread::StepIn (bool source_step, + bool avoid_code_without_debug_info) + +{ + Error error; + Process *process = GetProcess().get(); + if (StateIsStoppedState (process->GetState(), true)) + { + StackFrameSP frame_sp = GetStackFrameAtIndex (0); + ThreadPlanSP new_plan_sp; + const lldb::RunMode run_mode = eOnlyThisThread; + const bool abort_other_plans = false; + + if (source_step && frame_sp && frame_sp->HasDebugInformation ()) + { + SymbolContext sc(frame_sp->GetSymbolContext(eSymbolContextEverything)); + new_plan_sp = QueueThreadPlanForStepInRange (abort_other_plans, + sc.line_entry.range, + sc, + NULL, + run_mode, + avoid_code_without_debug_info); + } + else + { + new_plan_sp = QueueThreadPlanForStepSingleInstruction (false, + abort_other_plans, + run_mode); + } + + new_plan_sp->SetIsMasterPlan(true); + new_plan_sp->SetOkayToDiscard(false); + + // Why do we need to set the current thread by ID here??? + process->GetThreadList().SetSelectedThreadByID (GetID()); + error = process->Resume(); + } + else + { + error.SetErrorString("process not stopped"); + } + return error; +} + +Error +Thread::StepOver (bool source_step) + +{ + Error error; + Process *process = GetProcess().get(); + if (StateIsStoppedState (process->GetState(), true)) + { + StackFrameSP frame_sp = GetStackFrameAtIndex (0); + ThreadPlanSP new_plan_sp; + + const lldb::RunMode run_mode = eOnlyThisThread; + const bool abort_other_plans = false; + + if (source_step && frame_sp && frame_sp->HasDebugInformation ()) + { + SymbolContext sc(frame_sp->GetSymbolContext(eSymbolContextEverything)); + new_plan_sp = QueueThreadPlanForStepOverRange (abort_other_plans, + sc.line_entry.range, + sc, + run_mode); + } + else + { + new_plan_sp = QueueThreadPlanForStepSingleInstruction (true, + abort_other_plans, + run_mode); + } + + new_plan_sp->SetIsMasterPlan(true); + new_plan_sp->SetOkayToDiscard(false); + + // Why do we need to set the current thread by ID here??? + process->GetThreadList().SetSelectedThreadByID (GetID()); + error = process->Resume(); + } + else + { + error.SetErrorString("process not stopped"); + } + return error; +} + +Error +Thread::StepOut () +{ + Error error; + Process *process = GetProcess().get(); + if (StateIsStoppedState (process->GetState(), true)) + { + const bool first_instruction = false; + const bool stop_other_threads = false; + const bool abort_other_plans = false; + + ThreadPlanSP new_plan_sp(QueueThreadPlanForStepOut (abort_other_plans, + NULL, + first_instruction, + stop_other_threads, + eVoteYes, + eVoteNoOpinion, + 0)); + + new_plan_sp->SetIsMasterPlan(true); + new_plan_sp->SetOkayToDiscard(false); + + // Why do we need to set the current thread by ID here??? + process->GetThreadList().SetSelectedThreadByID (GetID()); + error = process->Resume(); + } + else + { + error.SetErrorString("process not stopped"); + } + return error; +}
\ No newline at end of file diff --git a/lldb/source/Target/ThreadPlanTracer.cpp b/lldb/source/Target/ThreadPlanTracer.cpp index 7f0fc58524e..d191170fcc0 100644 --- a/lldb/source/Target/ThreadPlanTracer.cpp +++ b/lldb/source/Target/ThreadPlanTracer.cpp @@ -23,6 +23,7 @@ #include "lldb/Core/Log.h" #include "lldb/Core/Module.h" #include "lldb/Core/State.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/Value.h" #include "lldb/Symbol/TypeList.h" #include "lldb/Target/RegisterContext.h" @@ -62,7 +63,7 @@ ThreadPlanTracer::GetLogStream () { TargetSP target_sp (m_thread.CalculateTarget()); if (target_sp) - return &target_sp->GetDebugger().GetOutputStream(); + return target_sp->GetDebugger().GetOutputFile().get(); } return NULL; } diff --git a/lldb/source/lldb.cpp b/lldb/source/lldb.cpp index 78e8208e091..4817e7f2b44 100644 --- a/lldb/source/lldb.cpp +++ b/lldb/source/lldb.cpp @@ -19,6 +19,7 @@ #include "lldb/Core/Timer.h" #include "lldb/Host/Host.h" #include "lldb/Host/Mutex.h" +#include "lldb/Interpreter/ScriptInterpreterPython.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" @@ -120,6 +121,7 @@ lldb_private::Initialize () SymbolFileDWARFDebugMap::Initialize(); ItaniumABILanguageRuntime::Initialize(); #ifndef LLDB_DISABLE_PYTHON + ScriptInterpreterPython::InitializePrivate(); OperatingSystemPython::Initialize(); #endif |

