aboutsummaryrefslogtreecommitdiff
path: root/tools/driver/IOChannel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/driver/IOChannel.cpp')
-rw-r--r--tools/driver/IOChannel.cpp647
1 files changed, 647 insertions, 0 deletions
diff --git a/tools/driver/IOChannel.cpp b/tools/driver/IOChannel.cpp
new file mode 100644
index 000000000000..7adf2e4e85e9
--- /dev/null
+++ b/tools/driver/IOChannel.cpp
@@ -0,0 +1,647 @@
+//===-- IOChannel.cpp -------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "IOChannel.h"
+
+#include <map>
+
+#include "lldb/API/SBCommandInterpreter.h"
+#include "lldb/API/SBDebugger.h"
+#include "lldb/API/SBError.h"
+#include "lldb/API/SBEvent.h"
+#include "lldb/API/SBFileSpec.h"
+#include "lldb/API/SBHostOS.h"
+#include "lldb/API/SBListener.h"
+#include "lldb/API/SBStringList.h"
+
+#include <string.h>
+#include <limits.h>
+
+using namespace lldb;
+
+typedef std::map<EditLine *, std::string> PromptMap;
+const char *g_default_prompt = "(lldb) ";
+PromptMap g_prompt_map;
+
+// Printing the following string causes libedit to back up to the beginning of the line & blank it out.
+const char undo_prompt_string[4] = { (char) 13, (char) 27, (char) 91, (char) 75};
+
+static const char*
+el_prompt(EditLine *el)
+{
+ PromptMap::const_iterator pos = g_prompt_map.find (el);
+ if (pos == g_prompt_map.end())
+ return g_default_prompt;
+ return pos->second.c_str();
+}
+
+const char *
+IOChannel::GetPrompt ()
+{
+ PromptMap::const_iterator pos = g_prompt_map.find (m_edit_line);
+ if (pos == g_prompt_map.end())
+ return g_default_prompt;
+ return pos->second.c_str();
+}
+
+bool
+IOChannel::EditLineHasCharacters ()
+{
+ const LineInfo *line_info = el_line(m_edit_line);
+ if (line_info)
+ {
+ // Sometimes we get called after the user has submitted the line, but before editline has
+ // cleared the buffer. In that case the cursor will be pointing at the newline. That's
+ // equivalent to having no characters on the line, since it has already been submitted.
+ if (*line_info->cursor == '\n')
+ return false;
+ else
+ return line_info->cursor != line_info->buffer;
+ }
+ else
+ return false;
+}
+
+
+void
+IOChannel::EraseCharsBeforeCursor ()
+{
+ const LineInfo *line_info = el_line(m_edit_line);
+ el_deletestr(m_edit_line, line_info->cursor - line_info->buffer);
+}
+
+unsigned char
+IOChannel::ElCompletionFn (EditLine *e, int ch)
+{
+ IOChannel *io_channel;
+ if (el_get(e, EL_CLIENTDATA, &io_channel) == 0)
+ {
+ return io_channel->HandleCompletion (e, ch);
+ }
+ else
+ {
+ return CC_ERROR;
+ }
+}
+
+void
+IOChannel::ElResize()
+{
+ el_resize(m_edit_line);
+}
+
+unsigned char
+IOChannel::HandleCompletion (EditLine *e, int ch)
+{
+ assert (e == m_edit_line);
+
+ const LineInfo *line_info = el_line(m_edit_line);
+ SBStringList completions;
+ int page_size = 40;
+
+ int num_completions = m_driver->GetDebugger().GetCommandInterpreter().HandleCompletion (line_info->buffer,
+ line_info->cursor,
+ line_info->lastchar,
+ 0,
+ -1,
+ completions);
+
+ if (num_completions == -1)
+ {
+ el_insertstr (m_edit_line, m_completion_key);
+ return CC_REDISPLAY;
+ }
+ else if (num_completions == -2)
+ {
+ el_deletestr (m_edit_line, line_info->cursor - line_info->buffer);
+ el_insertstr (m_edit_line, completions.GetStringAtIndex(0));
+ return CC_REDISPLAY;
+ }
+
+ // If we get a longer match display that first.
+ const char *completion_str = completions.GetStringAtIndex(0);
+ if (completion_str != NULL && *completion_str != '\0')
+ {
+ el_insertstr (m_edit_line, completion_str);
+ return CC_REDISPLAY;
+ }
+
+ if (num_completions > 1)
+ {
+ const char *comment = "\nAvailable completions:";
+
+ int num_elements = num_completions + 1;
+ OutWrite(comment, strlen (comment), NO_ASYNC);
+ if (num_completions < page_size)
+ {
+ for (int i = 1; i < num_elements; i++)
+ {
+ completion_str = completions.GetStringAtIndex(i);
+ OutWrite("\n\t", 2, NO_ASYNC);
+ OutWrite(completion_str, strlen (completion_str), NO_ASYNC);
+ }
+ OutWrite ("\n", 1, NO_ASYNC);
+ }
+ else
+ {
+ int cur_pos = 1;
+ char reply;
+ int got_char;
+ while (cur_pos < num_elements)
+ {
+ int endpoint = cur_pos + page_size;
+ if (endpoint > num_elements)
+ endpoint = num_elements;
+ for (; cur_pos < endpoint; cur_pos++)
+ {
+ completion_str = completions.GetStringAtIndex(cur_pos);
+ OutWrite("\n\t", 2, NO_ASYNC);
+ OutWrite(completion_str, strlen (completion_str), NO_ASYNC);
+ }
+
+ if (cur_pos >= num_elements)
+ {
+ OutWrite("\n", 1, NO_ASYNC);
+ break;
+ }
+
+ OutWrite("\nMore (Y/n/a): ", strlen ("\nMore (Y/n/a): "), NO_ASYNC);
+ reply = 'n';
+ got_char = el_getc(m_edit_line, &reply);
+ if (got_char == -1 || reply == 'n')
+ break;
+ if (reply == 'a')
+ page_size = num_elements - cur_pos;
+ }
+ }
+
+ }
+
+ if (num_completions == 0)
+ return CC_REFRESH_BEEP;
+ else
+ return CC_REDISPLAY;
+}
+
+IOChannel::IOChannel
+(
+ FILE *editline_in,
+ FILE *editline_out,
+ FILE *out,
+ FILE *err,
+ Driver *driver
+) :
+ SBBroadcaster ("IOChannel"),
+ m_output_mutex (),
+ m_enter_elgets_time (),
+ m_driver (driver),
+ m_read_thread (LLDB_INVALID_HOST_THREAD),
+ m_read_thread_should_exit (false),
+ m_out_file (out),
+ m_err_file (err),
+ m_command_queue (),
+ m_completion_key ("\t"),
+ m_edit_line (::el_init (SBHostOS::GetProgramFileSpec().GetFilename(), editline_in, editline_out, editline_out)),
+ m_history (history_init()),
+ m_history_event(),
+ m_getting_command (false),
+ m_expecting_prompt (false),
+ m_prompt_str (),
+ m_refresh_request_pending (false)
+{
+ assert (m_edit_line);
+ ::el_set (m_edit_line, EL_PROMPT, el_prompt);
+ ::el_set (m_edit_line, EL_EDITOR, "emacs");
+ ::el_set (m_edit_line, EL_HIST, history, m_history);
+
+ el_set (m_edit_line, EL_ADDFN, "lldb_complete",
+ "LLDB completion function",
+ IOChannel::ElCompletionFn);
+ el_set (m_edit_line, EL_BIND, m_completion_key, "lldb_complete", NULL);
+ el_set (m_edit_line, EL_BIND, "^r", "em-inc-search-prev", NULL); // Cycle through backwards search, entering string
+ el_set (m_edit_line, EL_BIND, "^w", "ed-delete-prev-word", NULL); // Delete previous word, behave like bash does.
+ el_set (m_edit_line, EL_BIND, "\e[3~", "ed-delete-next-char", NULL); // Fix the delete key.
+ el_set (m_edit_line, EL_CLIENTDATA, this);
+
+ // Source $PWD/.editrc then $HOME/.editrc
+ ::el_source (m_edit_line, NULL);
+
+ assert (m_history);
+ ::history (m_history, &m_history_event, H_SETSIZE, 800);
+ ::history (m_history, &m_history_event, H_SETUNIQUE, 1);
+ // Load history
+ HistorySaveLoad (false);
+
+ // Set up mutex to make sure OutErr, OutWrite and RefreshPrompt do not interfere
+ // with each other when writing.
+
+ int error;
+ ::pthread_mutexattr_t attr;
+ error = ::pthread_mutexattr_init (&attr);
+ assert (error == 0);
+ error = ::pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
+ assert (error == 0);
+ error = ::pthread_mutex_init (&m_output_mutex, &attr);
+ assert (error == 0);
+ error = ::pthread_mutexattr_destroy (&attr);
+ assert (error == 0);
+
+ // Initialize time that ::el_gets was last called.
+
+ m_enter_elgets_time.tv_sec = 0;
+ m_enter_elgets_time.tv_usec = 0;
+}
+
+IOChannel::~IOChannel ()
+{
+ // Save history
+ HistorySaveLoad (true);
+
+ if (m_history != NULL)
+ {
+ ::history_end (m_history);
+ m_history = NULL;
+ }
+
+ if (m_edit_line != NULL)
+ {
+ ::el_end (m_edit_line);
+ m_edit_line = NULL;
+ }
+
+ ::pthread_mutex_destroy (&m_output_mutex);
+}
+
+void
+IOChannel::HistorySaveLoad (bool save)
+{
+ if (m_history != NULL)
+ {
+ char history_path[PATH_MAX];
+ ::snprintf (history_path, sizeof(history_path), "~/.%s-history", SBHostOS::GetProgramFileSpec().GetFilename());
+ if ((size_t)SBFileSpec::ResolvePath (history_path, history_path, sizeof(history_path)) < sizeof(history_path) - 1)
+ {
+ const char *path_ptr = history_path;
+ if (save)
+ ::history (m_history, &m_history_event, H_SAVE, path_ptr);
+ else
+ ::history (m_history, &m_history_event, H_LOAD, path_ptr);
+ }
+ }
+}
+
+void
+IOChannel::LibeditOutputBytesReceived (void *baton, const void *src, size_t src_len)
+{
+ // Make this a member variable.
+ // static std::string prompt_str;
+ IOChannel *io_channel = (IOChannel *) baton;
+ IOLocker locker (io_channel->m_output_mutex);
+ const char *bytes = (const char *) src;
+
+ if (io_channel->IsGettingCommand() && io_channel->m_expecting_prompt)
+ {
+ io_channel->m_prompt_str.append (bytes, src_len);
+ // Log this to make sure the prompt is really what you think it is.
+ if (io_channel->m_prompt_str.find (el_prompt(io_channel->m_edit_line)) == 0)
+ {
+ io_channel->m_expecting_prompt = false;
+ io_channel->m_refresh_request_pending = false;
+ io_channel->OutWrite (io_channel->m_prompt_str.c_str(),
+ io_channel->m_prompt_str.size(), NO_ASYNC);
+ io_channel->m_prompt_str.clear();
+ }
+ }
+ else
+ {
+ if (io_channel->m_prompt_str.size() > 0)
+ io_channel->m_prompt_str.clear();
+ std::string tmp_str (bytes, src_len);
+ if (tmp_str.find (el_prompt (io_channel->m_edit_line)) == 0)
+ io_channel->m_refresh_request_pending = false;
+ io_channel->OutWrite (bytes, src_len, NO_ASYNC);
+ }
+}
+
+IOChannel::LibeditGetInputResult
+IOChannel::LibeditGetInput (std::string &new_line)
+{
+ IOChannel::LibeditGetInputResult retval = IOChannel::eLibeditGetInputResultUnknown;
+ if (m_edit_line != NULL)
+ {
+ int line_len = 0;
+
+ // Set boolean indicating whether or not el_gets is trying to get input (i.e. whether or not to attempt
+ // to refresh the prompt after writing data).
+ SetGettingCommand (true);
+ m_expecting_prompt = true;
+
+ // Call el_gets to prompt the user and read the user's input.
+ const char *line = ::el_gets (m_edit_line, &line_len);
+
+ // Re-set the boolean indicating whether or not el_gets is trying to get input.
+ SetGettingCommand (false);
+
+ if (line)
+ {
+ retval = IOChannel::eLibeditGetInputValid;
+ // strip any newlines off the end of the string...
+ while (line_len > 0 && (line[line_len - 1] == '\n' || line[line_len - 1] == '\r'))
+ --line_len;
+ if (line_len > 0)
+ {
+ ::history (m_history, &m_history_event, H_ENTER, line);
+ new_line.assign (line, line_len); // Omit the newline
+ }
+ else
+ {
+ retval = IOChannel::eLibeditGetInputEmpty;
+ // Someone just hit ENTER, return the empty string
+ new_line.clear();
+ }
+ // Return true to indicate success even if a string is empty
+ return retval;
+ }
+ else
+ {
+ retval = (line_len == 0 ? IOChannel::eLibeditGetInputEOF : IOChannel::eLibeditGetInputResultError);
+ }
+ }
+ // Return false to indicate failure. This can happen when the file handle
+ // is closed (EOF).
+ new_line.clear();
+ return retval;
+}
+
+void *
+IOChannel::IOReadThread (void *ptr)
+{
+ IOChannel *myself = static_cast<IOChannel *> (ptr);
+ myself->Run();
+ return NULL;
+}
+
+void
+IOChannel::Run ()
+{
+ SBListener listener("IOChannel::Run");
+ std::string new_line;
+
+ SBBroadcaster interpreter_broadcaster (m_driver->GetDebugger().GetCommandInterpreter().GetBroadcaster());
+ listener.StartListeningForEvents (interpreter_broadcaster,
+ SBCommandInterpreter::eBroadcastBitResetPrompt |
+ SBCommandInterpreter::eBroadcastBitThreadShouldExit |
+ SBCommandInterpreter::eBroadcastBitQuitCommandReceived);
+
+ listener.StartListeningForEvents (*this,
+ IOChannel::eBroadcastBitThreadShouldExit);
+
+ listener.StartListeningForEvents (*m_driver,
+ Driver::eBroadcastBitReadyForInput |
+ Driver::eBroadcastBitThreadShouldExit);
+
+ // Let anyone know that the IO channel is up and listening and ready for events
+ BroadcastEventByType (eBroadcastBitThreadDidStart);
+ bool done = false;
+ while (!done)
+ {
+ SBEvent event;
+
+ listener.WaitForEvent (UINT32_MAX, event);
+ if (!event.IsValid())
+ continue;
+
+ const uint32_t event_type = event.GetType();
+
+ if (event.GetBroadcaster().IsValid())
+ {
+ if (event.BroadcasterMatchesPtr (m_driver))
+ {
+ if (event_type & Driver::eBroadcastBitReadyForInput)
+ {
+ std::string line;
+
+ if (CommandQueueIsEmpty())
+ {
+ IOChannel::LibeditGetInputResult getline_result = LibeditGetInput(line);
+ if (getline_result == IOChannel::eLibeditGetInputEOF)
+ {
+ // EOF occurred
+ // pretend that a quit was typed so the user gets a potential
+ // chance to confirm
+ line.assign("quit");
+ }
+ else if (getline_result == IOChannel::eLibeditGetInputResultError || getline_result == IOChannel::eLibeditGetInputResultUnknown)
+ {
+ // some random error occurred, exit and don't ask because the state might be corrupt
+ done = true;
+ continue;
+ }
+ }
+ else
+ {
+ GetCommandFromQueue (line);
+ }
+
+ // TO BE DONE: FIGURE OUT WHICH COMMANDS SHOULD NOT BE REPEATED IF USER PRESSES PLAIN 'RETURN'
+ // AND TAKE CARE OF THAT HERE.
+
+ SBEvent line_event(IOChannel::eBroadcastBitHasUserInput,
+ line.c_str(),
+ line.size());
+ BroadcastEvent (line_event);
+ }
+ else if (event_type & Driver::eBroadcastBitThreadShouldExit)
+ {
+ done = true;
+ continue;
+ }
+ }
+ else if (event.BroadcasterMatchesRef (interpreter_broadcaster))
+ {
+ switch (event_type)
+ {
+ case SBCommandInterpreter::eBroadcastBitResetPrompt:
+ {
+ const char *new_prompt = SBEvent::GetCStringFromEvent (event);
+ if (new_prompt)
+ g_prompt_map[m_edit_line] = new_prompt;
+ }
+ break;
+
+ case SBCommandInterpreter::eBroadcastBitThreadShouldExit:
+ case SBCommandInterpreter::eBroadcastBitQuitCommandReceived:
+ done = true;
+ break;
+ }
+ }
+ else if (event.BroadcasterMatchesPtr (this))
+ {
+ if (event_type & IOChannel::eBroadcastBitThreadShouldExit)
+ {
+ done = true;
+ continue;
+ }
+ }
+ }
+ }
+ BroadcastEventByType (IOChannel::eBroadcastBitThreadDidExit);
+ m_driver = NULL;
+ m_read_thread = 0;
+}
+
+bool
+IOChannel::Start ()
+{
+ if (IS_VALID_LLDB_HOST_THREAD(m_read_thread))
+ return true;
+
+ m_read_thread = SBHostOS::ThreadCreate ("<lldb.driver.commandline_io>", IOChannel::IOReadThread, this,
+ NULL);
+
+ return (IS_VALID_LLDB_HOST_THREAD(m_read_thread));
+}
+
+bool
+IOChannel::Stop ()
+{
+ if (!IS_VALID_LLDB_HOST_THREAD(m_read_thread))
+ return true;
+
+ BroadcastEventByType (eBroadcastBitThreadShouldExit);
+
+ // Don't call Host::ThreadCancel since el_gets won't respond to this
+ // function call -- the thread will just die and all local variables in
+ // IOChannel::Run() won't get destructed down which is bad since there is
+ // a local listener holding onto broadcasters... To ensure proper shutdown,
+ // a ^D (control-D) sequence (0x04) should be written to other end of the
+ // the "in" file handle that was passed into the contructor as closing the
+ // file handle doesn't seem to make el_gets() exit....
+ return SBHostOS::ThreadJoin (m_read_thread, NULL, NULL);
+}
+
+void
+IOChannel::RefreshPrompt ()
+{
+ // If we are not in the middle of getting input from the user, there is no need to
+ // refresh the prompt.
+ IOLocker locker (m_output_mutex);
+ if (! IsGettingCommand())
+ return;
+
+ // If we haven't finished writing the prompt, there's no need to refresh it.
+ if (m_expecting_prompt)
+ return;
+
+ if (m_refresh_request_pending)
+ return;
+
+ ::el_set (m_edit_line, EL_REFRESH);
+ m_refresh_request_pending = true;
+}
+
+void
+IOChannel::OutWrite (const char *buffer, size_t len, bool asynchronous)
+{
+ if (len == 0 || buffer == NULL)
+ return;
+
+ // We're in the process of exiting -- IOChannel::Run() has already completed
+ // and set m_driver to NULL - it is time for us to leave now. We might not
+ // print the final ^D to stdout in this case. We need to do some re-work on
+ // how the I/O streams are managed at some point.
+ if (m_driver == NULL)
+ {
+ return;
+ }
+
+ // Use the mutex to make sure OutWrite and ErrWrite do not interfere with each other's output.
+ IOLocker locker (m_output_mutex);
+ if (m_driver->EditlineReaderIsTop() && asynchronous)
+ ::fwrite (undo_prompt_string, 1, 4, m_out_file);
+ ::fwrite (buffer, 1, len, m_out_file);
+ if (asynchronous)
+ m_driver->GetDebugger().NotifyTopInputReader (eInputReaderAsynchronousOutputWritten);
+}
+
+void
+IOChannel::ErrWrite (const char *buffer, size_t len, bool asynchronous)
+{
+ if (len == 0 || buffer == NULL)
+ return;
+
+ // Use the mutex to make sure OutWrite and ErrWrite do not interfere with each other's output.
+ IOLocker locker (m_output_mutex);
+ if (asynchronous)
+ ::fwrite (undo_prompt_string, 1, 4, m_err_file);
+ ::fwrite (buffer, 1, len, m_err_file);
+ if (asynchronous)
+ m_driver->GetDebugger().NotifyTopInputReader (eInputReaderAsynchronousOutputWritten);
+}
+
+void
+IOChannel::AddCommandToQueue (const char *command)
+{
+ m_command_queue.push (std::string(command));
+}
+
+bool
+IOChannel::GetCommandFromQueue (std::string &cmd)
+{
+ if (m_command_queue.empty())
+ return false;
+ cmd.swap(m_command_queue.front());
+ m_command_queue.pop ();
+ return true;
+}
+
+int
+IOChannel::CommandQueueSize () const
+{
+ return m_command_queue.size();
+}
+
+void
+IOChannel::ClearCommandQueue ()
+{
+ while (!m_command_queue.empty())
+ m_command_queue.pop();
+}
+
+bool
+IOChannel::CommandQueueIsEmpty () const
+{
+ return m_command_queue.empty();
+}
+
+bool
+IOChannel::IsGettingCommand () const
+{
+ return m_getting_command;
+}
+
+void
+IOChannel::SetGettingCommand (bool new_value)
+{
+ m_getting_command = new_value;
+}
+
+IOLocker::IOLocker (pthread_mutex_t &mutex) :
+ m_mutex_ptr (&mutex)
+{
+ if (m_mutex_ptr)
+ ::pthread_mutex_lock (m_mutex_ptr);
+
+}
+
+IOLocker::~IOLocker ()
+{
+ if (m_mutex_ptr)
+ ::pthread_mutex_unlock (m_mutex_ptr);
+}