diff options
Diffstat (limited to 'lldb/source/Core')
-rw-r--r-- | lldb/source/Core/Broadcaster.cpp | 6 | ||||
-rw-r--r-- | lldb/source/Core/CMakeLists.txt | 4 | ||||
-rw-r--r-- | lldb/source/Core/Debugger.cpp | 896 | ||||
-rw-r--r-- | lldb/source/Core/Disassembler.cpp | 13 | ||||
-rw-r--r-- | lldb/source/Core/IOHandler.cpp | 5221 | ||||
-rw-r--r-- | lldb/source/Core/InputReader.cpp | 387 | ||||
-rw-r--r-- | lldb/source/Core/InputReaderEZ.cpp | 91 | ||||
-rw-r--r-- | lldb/source/Core/InputReaderStack.cpp | 80 | ||||
-rw-r--r-- | lldb/source/Core/SourceManager.cpp | 50 | ||||
-rw-r--r-- | lldb/source/Core/StreamAsynchronousIO.cpp | 9 | ||||
-rw-r--r-- | lldb/source/Core/StringList.cpp | 91 | ||||
-rw-r--r-- | lldb/source/Core/ValueObject.cpp | 3 | ||||
-rw-r--r-- | lldb/source/Core/ValueObjectChild.cpp | 3 |
13 files changed, 6051 insertions, 803 deletions
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 |