aboutsummaryrefslogtreecommitdiff
path: root/tools/driver
diff options
context:
space:
mode:
authorEd Maste <emaste@FreeBSD.org>2013-08-23 17:46:38 +0000
committerEd Maste <emaste@FreeBSD.org>2013-08-23 17:46:38 +0000
commitf034231a6a1fd5d6395206c1651de8cd9402cca3 (patch)
treef561dabc721ad515599172c16da3a4400b7f4aec /tools/driver
downloadsrc-f034231a6a1fd5d6395206c1651de8cd9402cca3.tar.gz
src-f034231a6a1fd5d6395206c1651de8cd9402cca3.zip
Import lldb as of SVN r188801
(A number of files not required for the FreeBSD build have been removed.) Sponsored by: DARPA, AFRL
Notes
Notes: svn path=/vendor/lldb/dist/; revision=254721
Diffstat (limited to 'tools/driver')
-rw-r--r--tools/driver/Driver.cpp1733
-rw-r--r--tools/driver/Driver.h201
-rw-r--r--tools/driver/IOChannel.cpp647
-rw-r--r--tools/driver/IOChannel.h174
4 files changed, 2755 insertions, 0 deletions
diff --git a/tools/driver/Driver.cpp b/tools/driver/Driver.cpp
new file mode 100644
index 000000000000..875adc22283a
--- /dev/null
+++ b/tools/driver/Driver.cpp
@@ -0,0 +1,1733 @@
+//===-- Driver.cpp ----------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Driver.h"
+
+#include <getopt.h>
+#include <libgen.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <inttypes.h>
+
+#include <string>
+
+#include "IOChannel.h"
+#include "lldb/API/SBBreakpoint.h"
+#include "lldb/API/SBCommandInterpreter.h"
+#include "lldb/API/SBCommandReturnObject.h"
+#include "lldb/API/SBCommunication.h"
+#include "lldb/API/SBDebugger.h"
+#include "lldb/API/SBEvent.h"
+#include "lldb/API/SBHostOS.h"
+#include "lldb/API/SBListener.h"
+#include "lldb/API/SBStream.h"
+#include "lldb/API/SBTarget.h"
+#include "lldb/API/SBThread.h"
+#include "lldb/API/SBProcess.h"
+
+using namespace lldb;
+
+static void reset_stdin_termios ();
+static bool g_old_stdin_termios_is_valid = false;
+static struct termios g_old_stdin_termios;
+
+static char *g_debugger_name = (char *) "";
+static Driver *g_driver = NULL;
+
+// In the Driver::MainLoop, we change the terminal settings. This function is
+// added as an atexit handler to make sure we clean them up.
+static void
+reset_stdin_termios ()
+{
+ if (g_old_stdin_termios_is_valid)
+ {
+ g_old_stdin_termios_is_valid = false;
+ ::tcsetattr (STDIN_FILENO, TCSANOW, &g_old_stdin_termios);
+ }
+}
+
+typedef struct
+{
+ uint32_t usage_mask; // Used to mark options that can be used together. If (1 << n & usage_mask) != 0
+ // then this option belongs to option set n.
+ bool required; // This option is required (in the current usage level)
+ const char * long_option; // Full name for this option.
+ int short_option; // Single character for this option.
+ int option_has_arg; // no_argument, required_argument or optional_argument
+ uint32_t completion_type; // Cookie the option class can use to do define the argument completion.
+ lldb::CommandArgumentType argument_type; // Type of argument this option takes
+ const char * usage_text; // Full text explaining what this options does and what (if any) argument to
+ // pass it.
+} OptionDefinition;
+
+#define LLDB_3_TO_5 LLDB_OPT_SET_3|LLDB_OPT_SET_4|LLDB_OPT_SET_5
+#define LLDB_4_TO_5 LLDB_OPT_SET_4|LLDB_OPT_SET_5
+
+static OptionDefinition g_options[] =
+{
+ { LLDB_OPT_SET_1, true , "help" , 'h', no_argument , 0, eArgTypeNone,
+ "Prints out the usage information for the LLDB debugger." },
+ { LLDB_OPT_SET_2, true , "version" , 'v', no_argument , 0, eArgTypeNone,
+ "Prints out the current version number of the LLDB debugger." },
+ { LLDB_OPT_SET_3, true , "arch" , 'a', required_argument, 0, eArgTypeArchitecture,
+ "Tells the debugger to use the specified architecture when starting and running the program. <architecture> must "
+ "be one of the architectures for which the program was compiled." },
+ { LLDB_OPT_SET_3, true , "file" , 'f', required_argument, 0, eArgTypeFilename,
+ "Tells the debugger to use the file <filename> as the program to be debugged." },
+ { LLDB_OPT_SET_3, false, "core" , 'c', required_argument, 0, eArgTypeFilename,
+ "Tells the debugger to use the fullpath to <path> as the core file." },
+ { LLDB_OPT_SET_4, true , "attach-name" , 'n', required_argument, 0, eArgTypeProcessName,
+ "Tells the debugger to attach to a process with the given name." },
+ { LLDB_OPT_SET_4, true , "wait-for" , 'w', no_argument , 0, eArgTypeNone,
+ "Tells the debugger to wait for a process with the given pid or name to launch before attaching." },
+ { LLDB_OPT_SET_5, true , "attach-pid" , 'p', required_argument, 0, eArgTypePid,
+ "Tells the debugger to attach to a process with the given pid." },
+ { LLDB_3_TO_5, false, "script-language", 'l', required_argument, 0, eArgTypeScriptLang,
+ "Tells the debugger to use the specified scripting language for user-defined scripts, rather than the default. "
+ "Valid scripting languages that can be specified include Python, Perl, Ruby and Tcl. Currently only the Python "
+ "extensions have been implemented." },
+ { LLDB_3_TO_5, false, "debug" , 'd', no_argument , 0, eArgTypeNone,
+ "Tells the debugger to print out extra information for debugging itself." },
+ { LLDB_3_TO_5, false, "source" , 's', required_argument, 0, eArgTypeFilename,
+ "Tells the debugger to read in and execute the file <file>, which should contain lldb commands." },
+ { LLDB_3_TO_5, false, "editor" , 'e', no_argument , 0, eArgTypeNone,
+ "Tells the debugger to open source files using the host's \"external editor\" mechanism." },
+ { LLDB_3_TO_5, false, "no-lldbinit" , 'x', no_argument , 0, eArgTypeNone,
+ "Do not automatically parse any '.lldbinit' files." },
+ { LLDB_3_TO_5, false, "no-use-colors" , 'o', no_argument , 0, eArgTypeNone,
+ "Do not use colors." },
+ { LLDB_OPT_SET_6, true , "python-path" , 'P', no_argument , 0, eArgTypeNone,
+ "Prints out the path to the lldb.py file for this version of lldb." },
+ { 0, false, NULL , 0 , 0 , 0, eArgTypeNone, NULL }
+};
+
+static const uint32_t last_option_set_with_args = 2;
+
+Driver::Driver () :
+ SBBroadcaster ("Driver"),
+ m_debugger (SBDebugger::Create(false)),
+ m_editline_pty (),
+ m_editline_slave_fh (NULL),
+ m_editline_reader (),
+ m_io_channel_ap (),
+ m_option_data (),
+ m_executing_user_command (false),
+ m_waiting_for_command (false),
+ m_done(false)
+{
+ // We want to be able to handle CTRL+D in the terminal to have it terminate
+ // certain input
+ m_debugger.SetCloseInputOnEOF (false);
+ g_debugger_name = (char *) m_debugger.GetInstanceName();
+ if (g_debugger_name == NULL)
+ g_debugger_name = (char *) "";
+ g_driver = this;
+}
+
+Driver::~Driver ()
+{
+ g_driver = NULL;
+ g_debugger_name = NULL;
+}
+
+void
+Driver::CloseIOChannelFile ()
+{
+ // Write an End of File sequence to the file descriptor to ensure any
+ // read functions can exit.
+ char eof_str[] = "\x04";
+ ::write (m_editline_pty.GetMasterFileDescriptor(), eof_str, strlen(eof_str));
+
+ m_editline_pty.CloseMasterFileDescriptor();
+
+ if (m_editline_slave_fh)
+ {
+ ::fclose (m_editline_slave_fh);
+ m_editline_slave_fh = NULL;
+ }
+}
+
+// This function takes INDENT, which tells how many spaces to output at the front
+// of each line; TEXT, which is the text that is to be output. It outputs the
+// text, on multiple lines if necessary, to RESULT, with INDENT spaces at the
+// front of each line. It breaks lines on spaces, tabs or newlines, shortening
+// the line if necessary to not break in the middle of a word. It assumes that
+// each output line should contain a maximum of OUTPUT_MAX_COLUMNS characters.
+
+void
+OutputFormattedUsageText (FILE *out, int indent, const char *text, int output_max_columns)
+{
+ int len = strlen (text);
+ std::string text_string (text);
+
+ // Force indentation to be reasonable.
+ if (indent >= output_max_columns)
+ indent = 0;
+
+ // Will it all fit on one line?
+
+ if (len + indent < output_max_columns)
+ // Output as a single line
+ fprintf (out, "%*s%s\n", indent, "", text);
+ else
+ {
+ // We need to break it up into multiple lines.
+ int text_width = output_max_columns - indent - 1;
+ int start = 0;
+ int end = start;
+ int final_end = len;
+ int sub_len;
+
+ while (end < final_end)
+ {
+ // Dont start the 'text' on a space, since we're already outputting the indentation.
+ while ((start < final_end) && (text[start] == ' '))
+ start++;
+
+ end = start + text_width;
+ if (end > final_end)
+ end = final_end;
+ else
+ {
+ // If we're not at the end of the text, make sure we break the line on white space.
+ while (end > start
+ && text[end] != ' ' && text[end] != '\t' && text[end] != '\n')
+ end--;
+ }
+ sub_len = end - start;
+ std::string substring = text_string.substr (start, sub_len);
+ fprintf (out, "%*s%s\n", indent, "", substring.c_str());
+ start = end + 1;
+ }
+ }
+}
+
+void
+ShowUsage (FILE *out, OptionDefinition *option_table, Driver::OptionData data)
+{
+ uint32_t screen_width = 80;
+ uint32_t indent_level = 0;
+ const char *name = "lldb";
+
+ fprintf (out, "\nUsage:\n\n");
+
+ indent_level += 2;
+
+
+ // First, show each usage level set of options, e.g. <cmd> [options-for-level-0]
+ // <cmd> [options-for-level-1]
+ // etc.
+
+ uint32_t num_options;
+ uint32_t num_option_sets = 0;
+
+ for (num_options = 0; option_table[num_options].long_option != NULL; ++num_options)
+ {
+ uint32_t this_usage_mask = option_table[num_options].usage_mask;
+ if (this_usage_mask == LLDB_OPT_SET_ALL)
+ {
+ if (num_option_sets == 0)
+ num_option_sets = 1;
+ }
+ else
+ {
+ for (uint32_t j = 0; j < LLDB_MAX_NUM_OPTION_SETS; j++)
+ {
+ if (this_usage_mask & 1 << j)
+ {
+ if (num_option_sets <= j)
+ num_option_sets = j + 1;
+ }
+ }
+ }
+ }
+
+ for (uint32_t opt_set = 0; opt_set < num_option_sets; opt_set++)
+ {
+ uint32_t opt_set_mask;
+
+ opt_set_mask = 1 << opt_set;
+
+ if (opt_set > 0)
+ fprintf (out, "\n");
+ fprintf (out, "%*s%s", indent_level, "", name);
+ bool is_help_line = false;
+
+ for (uint32_t i = 0; i < num_options; ++i)
+ {
+ if (option_table[i].usage_mask & opt_set_mask)
+ {
+ CommandArgumentType arg_type = option_table[i].argument_type;
+ const char *arg_name = SBCommandInterpreter::GetArgumentTypeAsCString (arg_type);
+ // This is a bit of a hack, but there's no way to say certain options don't have arguments yet...
+ // so we do it by hand here.
+ if (option_table[i].short_option == 'h')
+ is_help_line = true;
+
+ if (option_table[i].required)
+ {
+ if (option_table[i].option_has_arg == required_argument)
+ fprintf (out, " -%c <%s>", option_table[i].short_option, arg_name);
+ else if (option_table[i].option_has_arg == optional_argument)
+ fprintf (out, " -%c [<%s>]", option_table[i].short_option, arg_name);
+ else
+ fprintf (out, " -%c", option_table[i].short_option);
+ }
+ else
+ {
+ if (option_table[i].option_has_arg == required_argument)
+ fprintf (out, " [-%c <%s>]", option_table[i].short_option, arg_name);
+ else if (option_table[i].option_has_arg == optional_argument)
+ fprintf (out, " [-%c [<%s>]]", option_table[i].short_option, arg_name);
+ else
+ fprintf (out, " [-%c]", option_table[i].short_option);
+ }
+ }
+ }
+ if (!is_help_line && (opt_set <= last_option_set_with_args))
+ fprintf (out, " [[--] <PROGRAM-ARG-1> [<PROGRAM_ARG-2> ...]]");
+ }
+
+ fprintf (out, "\n\n");
+
+ // Now print out all the detailed information about the various options: long form, short form and help text:
+ // -- long_name <argument>
+ // - short <argument>
+ // help text
+
+ // This variable is used to keep track of which options' info we've printed out, because some options can be in
+ // more than one usage level, but we only want to print the long form of its information once.
+
+ Driver::OptionData::OptionSet options_seen;
+ Driver::OptionData::OptionSet::iterator pos;
+
+ indent_level += 5;
+
+ for (uint32_t i = 0; i < num_options; ++i)
+ {
+ // Only print this option if we haven't already seen it.
+ pos = options_seen.find (option_table[i].short_option);
+ if (pos == options_seen.end())
+ {
+ CommandArgumentType arg_type = option_table[i].argument_type;
+ const char *arg_name = SBCommandInterpreter::GetArgumentTypeAsCString (arg_type);
+
+ options_seen.insert (option_table[i].short_option);
+ fprintf (out, "%*s-%c ", indent_level, "", option_table[i].short_option);
+ if (arg_type != eArgTypeNone)
+ fprintf (out, "<%s>", arg_name);
+ fprintf (out, "\n");
+ fprintf (out, "%*s--%s ", indent_level, "", option_table[i].long_option);
+ if (arg_type != eArgTypeNone)
+ fprintf (out, "<%s>", arg_name);
+ fprintf (out, "\n");
+ indent_level += 5;
+ OutputFormattedUsageText (out, indent_level, option_table[i].usage_text, screen_width);
+ indent_level -= 5;
+ fprintf (out, "\n");
+ }
+ }
+
+ indent_level -= 5;
+
+ fprintf (out, "\n%*s(If you don't provide -f then the first argument will be the file to be debugged"
+ "\n%*s so '%s -- <filename> [<ARG1> [<ARG2>]]' also works."
+ "\n%*s Remember to end the options with \"--\" if any of your arguments have a \"-\" in them.)\n\n",
+ indent_level, "",
+ indent_level, "",
+ name,
+ indent_level, "");
+}
+
+void
+BuildGetOptTable (OptionDefinition *expanded_option_table, std::vector<struct option> &getopt_table,
+ uint32_t num_options)
+{
+ if (num_options == 0)
+ return;
+
+ uint32_t i;
+ uint32_t j;
+ std::bitset<256> option_seen;
+
+ getopt_table.resize (num_options + 1);
+
+ for (i = 0, j = 0; i < num_options; ++i)
+ {
+ char short_opt = expanded_option_table[i].short_option;
+
+ if (option_seen.test(short_opt) == false)
+ {
+ getopt_table[j].name = expanded_option_table[i].long_option;
+ getopt_table[j].has_arg = expanded_option_table[i].option_has_arg;
+ getopt_table[j].flag = NULL;
+ getopt_table[j].val = expanded_option_table[i].short_option;
+ option_seen.set(short_opt);
+ ++j;
+ }
+ }
+
+ getopt_table[j].name = NULL;
+ getopt_table[j].has_arg = 0;
+ getopt_table[j].flag = NULL;
+ getopt_table[j].val = 0;
+
+}
+
+Driver::OptionData::OptionData () :
+ m_args(),
+ m_script_lang (lldb::eScriptLanguageDefault),
+ m_core_file (),
+ m_crash_log (),
+ m_source_command_files (),
+ m_debug_mode (false),
+ m_print_version (false),
+ m_print_python_path (false),
+ m_print_help (false),
+ m_wait_for(false),
+ m_process_name(),
+ m_process_pid(LLDB_INVALID_PROCESS_ID),
+ m_use_external_editor(false),
+ m_seen_options()
+{
+}
+
+Driver::OptionData::~OptionData ()
+{
+}
+
+void
+Driver::OptionData::Clear ()
+{
+ m_args.clear ();
+ m_script_lang = lldb::eScriptLanguageDefault;
+ m_source_command_files.clear ();
+ m_debug_mode = false;
+ m_print_help = false;
+ m_print_version = false;
+ m_print_python_path = false;
+ m_use_external_editor = false;
+ m_wait_for = false;
+ m_process_name.erase();
+ m_process_pid = LLDB_INVALID_PROCESS_ID;
+}
+
+void
+Driver::ResetOptionValues ()
+{
+ m_option_data.Clear ();
+}
+
+const char *
+Driver::GetFilename() const
+{
+ if (m_option_data.m_args.empty())
+ return NULL;
+ return m_option_data.m_args.front().c_str();
+}
+
+const char *
+Driver::GetCrashLogFilename() const
+{
+ if (m_option_data.m_crash_log.empty())
+ return NULL;
+ return m_option_data.m_crash_log.c_str();
+}
+
+lldb::ScriptLanguage
+Driver::GetScriptLanguage() const
+{
+ return m_option_data.m_script_lang;
+}
+
+size_t
+Driver::GetNumSourceCommandFiles () const
+{
+ return m_option_data.m_source_command_files.size();
+}
+
+const char *
+Driver::GetSourceCommandFileAtIndex (uint32_t idx) const
+{
+ if (idx < m_option_data.m_source_command_files.size())
+ return m_option_data.m_source_command_files[idx].c_str();
+ return NULL;
+}
+
+bool
+Driver::GetDebugMode() const
+{
+ return m_option_data.m_debug_mode;
+}
+
+
+// Check the arguments that were passed to this program to make sure they are valid and to get their
+// argument values (if any). Return a boolean value indicating whether or not to start up the full
+// debugger (i.e. the Command Interpreter) or not. Return FALSE if the arguments were invalid OR
+// if the user only wanted help or version information.
+
+SBError
+Driver::ParseArgs (int argc, const char *argv[], FILE *out_fh, bool &exit)
+{
+ ResetOptionValues ();
+
+ SBCommandReturnObject result;
+
+ SBError error;
+ std::string option_string;
+ struct option *long_options = NULL;
+ std::vector<struct option> long_options_vector;
+ uint32_t num_options;
+
+ for (num_options = 0; g_options[num_options].long_option != NULL; ++num_options)
+ /* Do Nothing. */;
+
+ if (num_options == 0)
+ {
+ if (argc > 1)
+ error.SetErrorStringWithFormat ("invalid number of options");
+ return error;
+ }
+
+ BuildGetOptTable (g_options, long_options_vector, num_options);
+
+ if (long_options_vector.empty())
+ long_options = NULL;
+ else
+ long_options = &long_options_vector.front();
+
+ if (long_options == NULL)
+ {
+ error.SetErrorStringWithFormat ("invalid long options");
+ return error;
+ }
+
+ // Build the option_string argument for call to getopt_long_only.
+
+ for (int i = 0; long_options[i].name != NULL; ++i)
+ {
+ if (long_options[i].flag == NULL)
+ {
+ option_string.push_back ((char) long_options[i].val);
+ switch (long_options[i].has_arg)
+ {
+ default:
+ case no_argument:
+ break;
+ case required_argument:
+ option_string.push_back (':');
+ break;
+ case optional_argument:
+ option_string.append ("::");
+ break;
+ }
+ }
+ }
+
+ // This is kind of a pain, but since we make the debugger in the Driver's constructor, we can't
+ // know at that point whether we should read in init files yet. So we don't read them in in the
+ // Driver constructor, then set the flags back to "read them in" here, and then if we see the
+ // "-n" flag, we'll turn it off again. Finally we have to read them in by hand later in the
+ // main loop.
+
+ m_debugger.SkipLLDBInitFiles (false);
+ m_debugger.SkipAppInitFiles (false);
+
+ // Prepare for & make calls to getopt_long_only.
+#if __GLIBC__
+ optind = 0;
+#else
+ optreset = 1;
+ optind = 1;
+#endif
+ int val;
+ while (1)
+ {
+ int long_options_index = -1;
+ val = ::getopt_long_only (argc, const_cast<char **>(argv), option_string.c_str(), long_options, &long_options_index);
+
+ if (val == -1)
+ break;
+ else if (val == '?')
+ {
+ m_option_data.m_print_help = true;
+ error.SetErrorStringWithFormat ("unknown or ambiguous option");
+ break;
+ }
+ else if (val == 0)
+ continue;
+ else
+ {
+ m_option_data.m_seen_options.insert ((char) val);
+ if (long_options_index == -1)
+ {
+ for (int i = 0;
+ long_options[i].name || long_options[i].has_arg || long_options[i].flag || long_options[i].val;
+ ++i)
+ {
+ if (long_options[i].val == val)
+ {
+ long_options_index = i;
+ break;
+ }
+ }
+ }
+
+ if (long_options_index >= 0)
+ {
+ const int short_option = g_options[long_options_index].short_option;
+
+ switch (short_option)
+ {
+ case 'h':
+ m_option_data.m_print_help = true;
+ break;
+
+ case 'v':
+ m_option_data.m_print_version = true;
+ break;
+
+ case 'P':
+ m_option_data.m_print_python_path = true;
+ break;
+
+ case 'c':
+ {
+ SBFileSpec file(optarg);
+ if (file.Exists())
+ {
+ m_option_data.m_core_file = optarg;
+ }
+ else
+ error.SetErrorStringWithFormat("file specified in --core (-c) option doesn't exist: '%s'", optarg);
+ }
+ break;
+
+ case 'e':
+ m_option_data.m_use_external_editor = true;
+ break;
+
+ case 'x':
+ m_debugger.SkipLLDBInitFiles (true);
+ m_debugger.SkipAppInitFiles (true);
+ break;
+
+ case 'o':
+ m_debugger.SetUseColor (false);
+ break;
+
+ case 'f':
+ {
+ SBFileSpec file(optarg);
+ if (file.Exists())
+ {
+ m_option_data.m_args.push_back (optarg);
+ }
+ else if (file.ResolveExecutableLocation())
+ {
+ char path[PATH_MAX];
+ file.GetPath (path, sizeof(path));
+ m_option_data.m_args.push_back (path);
+ }
+ else
+ error.SetErrorStringWithFormat("file specified in --file (-f) option doesn't exist: '%s'", optarg);
+ }
+ break;
+
+ case 'a':
+ if (!m_debugger.SetDefaultArchitecture (optarg))
+ error.SetErrorStringWithFormat("invalid architecture in the -a or --arch option: '%s'", optarg);
+ break;
+
+ case 'l':
+ m_option_data.m_script_lang = m_debugger.GetScriptingLanguage (optarg);
+ break;
+
+ case 'd':
+ m_option_data.m_debug_mode = true;
+ break;
+
+ case 'n':
+ m_option_data.m_process_name = optarg;
+ break;
+
+ case 'w':
+ m_option_data.m_wait_for = true;
+ break;
+
+ case 'p':
+ {
+ char *remainder;
+ m_option_data.m_process_pid = strtol (optarg, &remainder, 0);
+ if (remainder == optarg || *remainder != '\0')
+ error.SetErrorStringWithFormat ("Could not convert process PID: \"%s\" into a pid.",
+ optarg);
+ }
+ break;
+ case 's':
+ {
+ SBFileSpec file(optarg);
+ if (file.Exists())
+ m_option_data.m_source_command_files.push_back (optarg);
+ else if (file.ResolveExecutableLocation())
+ {
+ char final_path[PATH_MAX];
+ file.GetPath (final_path, sizeof(final_path));
+ std::string path_str (final_path);
+ m_option_data.m_source_command_files.push_back (path_str);
+ }
+ else
+ error.SetErrorStringWithFormat("file specified in --source (-s) option doesn't exist: '%s'", optarg);
+ }
+ break;
+
+ default:
+ m_option_data.m_print_help = true;
+ error.SetErrorStringWithFormat ("unrecognized option %c", short_option);
+ break;
+ }
+ }
+ else
+ {
+ error.SetErrorStringWithFormat ("invalid option with value %i", val);
+ }
+ if (error.Fail())
+ {
+ return error;
+ }
+ }
+ }
+
+ if (error.Fail() || m_option_data.m_print_help)
+ {
+ ShowUsage (out_fh, g_options, m_option_data);
+ exit = true;
+ }
+ else if (m_option_data.m_print_version)
+ {
+ ::fprintf (out_fh, "%s\n", m_debugger.GetVersionString());
+ exit = true;
+ }
+ else if (m_option_data.m_print_python_path)
+ {
+ SBFileSpec python_file_spec = SBHostOS::GetLLDBPythonPath();
+ if (python_file_spec.IsValid())
+ {
+ char python_path[PATH_MAX];
+ size_t num_chars = python_file_spec.GetPath(python_path, PATH_MAX);
+ if (num_chars < PATH_MAX)
+ {
+ ::fprintf (out_fh, "%s\n", python_path);
+ }
+ else
+ ::fprintf (out_fh, "<PATH TOO LONG>\n");
+ }
+ else
+ ::fprintf (out_fh, "<COULD NOT FIND PATH>\n");
+ exit = true;
+ }
+ else if (m_option_data.m_process_name.empty() && m_option_data.m_process_pid == LLDB_INVALID_PROCESS_ID)
+ {
+ // Any arguments that are left over after option parsing are for
+ // the program. If a file was specified with -f then the filename
+ // is already in the m_option_data.m_args array, and any remaining args
+ // are arguments for the inferior program. If no file was specified with
+ // -f, then what is left is the program name followed by any arguments.
+
+ // Skip any options we consumed with getopt_long_only
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ {
+ for (int arg_idx=0; arg_idx<argc; ++arg_idx)
+ {
+ const char *arg = argv[arg_idx];
+ if (arg)
+ m_option_data.m_args.push_back (arg);
+ }
+ }
+
+ }
+ else
+ {
+ // Skip any options we consumed with getopt_long_only
+ argc -= optind;
+ //argv += optind; // Commented out to keep static analyzer happy
+
+ if (argc > 0)
+ ::fprintf (out_fh, "Warning: program arguments are ignored when attaching.\n");
+ }
+
+ return error;
+}
+
+size_t
+Driver::GetProcessSTDOUT ()
+{
+ // The process has stuff waiting for stdout; get it and write it out to the appropriate place.
+ char stdio_buffer[1024];
+ size_t len;
+ size_t total_bytes = 0;
+ while ((len = m_debugger.GetSelectedTarget().GetProcess().GetSTDOUT (stdio_buffer, sizeof (stdio_buffer))) > 0)
+ {
+ m_io_channel_ap->OutWrite (stdio_buffer, len, NO_ASYNC);
+ total_bytes += len;
+ }
+ return total_bytes;
+}
+
+size_t
+Driver::GetProcessSTDERR ()
+{
+ // The process has stuff waiting for stderr; get it and write it out to the appropriate place.
+ char stdio_buffer[1024];
+ size_t len;
+ size_t total_bytes = 0;
+ while ((len = m_debugger.GetSelectedTarget().GetProcess().GetSTDERR (stdio_buffer, sizeof (stdio_buffer))) > 0)
+ {
+ m_io_channel_ap->ErrWrite (stdio_buffer, len, NO_ASYNC);
+ total_bytes += len;
+ }
+ return total_bytes;
+}
+
+void
+Driver::UpdateSelectedThread ()
+{
+ using namespace lldb;
+ SBProcess process(m_debugger.GetSelectedTarget().GetProcess());
+ if (process.IsValid())
+ {
+ SBThread curr_thread (process.GetSelectedThread());
+ SBThread thread;
+ StopReason curr_thread_stop_reason = eStopReasonInvalid;
+ 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.
+ SBThread plan_thread;
+ SBThread other_thread;
+ const size_t num_threads = process.GetNumThreads();
+ size_t i;
+ for (i = 0; i < num_threads; ++i)
+ {
+ thread = process.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.IsValid())
+ other_thread = thread;
+ break;
+ case eStopReasonPlanComplete:
+ if (!plan_thread.IsValid())
+ plan_thread = thread;
+ break;
+ }
+ }
+ if (plan_thread.IsValid())
+ process.SetSelectedThread (plan_thread);
+ else if (other_thread.IsValid())
+ process.SetSelectedThread (other_thread);
+ else
+ {
+ if (curr_thread.IsValid())
+ thread = curr_thread;
+ else
+ thread = process.GetThreadAtIndex(0);
+
+ if (thread.IsValid())
+ process.SetSelectedThread (thread);
+ }
+ }
+ }
+}
+
+// This function handles events that were broadcast by the process.
+void
+Driver::HandleBreakpointEvent (const SBEvent &event)
+{
+ using namespace lldb;
+ const uint32_t event_type = SBBreakpoint::GetBreakpointEventTypeFromEvent (event);
+
+ 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.
+ }
+ else if (event_type & eBreakpointEventTypeLocationsAdded)
+ {
+ char message[256];
+ uint32_t num_new_locations = SBBreakpoint::GetNumBreakpointLocationsFromEvent(event);
+ if (num_new_locations > 0)
+ {
+ SBBreakpoint breakpoint = SBBreakpoint::GetBreakpointFromEvent(event);
+ int message_len = ::snprintf (message, sizeof(message), "%d location%s added to breakpoint %d\n",
+ num_new_locations,
+ num_new_locations == 1 ? "" : "s",
+ breakpoint.GetID());
+ m_io_channel_ap->OutWrite(message, message_len, ASYNC);
+ }
+ }
+ 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.
+ }
+}
+
+// This function handles events that were broadcast by the process.
+void
+Driver::HandleProcessEvent (const SBEvent &event)
+{
+ using namespace lldb;
+ const uint32_t event_type = event.GetType();
+
+ if (event_type & SBProcess::eBroadcastBitSTDOUT)
+ {
+ // The process has stdout available, get it and write it out to the
+ // appropriate place.
+ GetProcessSTDOUT ();
+ }
+ else if (event_type & SBProcess::eBroadcastBitSTDERR)
+ {
+ // The process has stderr available, get it and write it out to the
+ // appropriate place.
+ GetProcessSTDERR ();
+ }
+ else if (event_type & SBProcess::eBroadcastBitStateChanged)
+ {
+ // Drain all stout and stderr so we don't see any output come after
+ // we print our prompts
+ GetProcessSTDOUT ();
+ GetProcessSTDERR ();
+ // Something changed in the process; get the event and report the process's current status and location to
+ // the user.
+ StateType event_state = SBProcess::GetStateFromEvent (event);
+ if (event_state == eStateInvalid)
+ return;
+
+ SBProcess process (SBProcess::GetProcessFromEvent (event));
+ assert (process.IsValid());
+
+ switch (event_state)
+ {
+ case eStateInvalid:
+ case eStateUnloaded:
+ case eStateConnected:
+ case eStateAttaching:
+ case eStateLaunching:
+ case eStateStepping:
+ case eStateDetached:
+ {
+ char message[1024];
+ int message_len = ::snprintf (message, sizeof(message), "Process %" PRIu64 " %s\n", process.GetProcessID(),
+ m_debugger.StateAsCString (event_state));
+ m_io_channel_ap->OutWrite(message, message_len, ASYNC);
+ }
+ break;
+
+ case eStateRunning:
+ // Don't be chatty when we run...
+ break;
+
+ case eStateExited:
+ {
+ SBCommandReturnObject result;
+ m_debugger.GetCommandInterpreter().HandleCommand("process status", result, false);
+ m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize(), ASYNC);
+ m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize(), ASYNC);
+ }
+ break;
+
+ case eStateStopped:
+ case eStateCrashed:
+ case eStateSuspended:
+ // Make sure the program hasn't been auto-restarted:
+ if (SBProcess::GetRestartedFromEvent (event))
+ {
+ size_t num_reasons = SBProcess::GetNumRestartedReasonsFromEvent(event);
+ if (num_reasons > 0)
+ {
+ // FIXME: Do we want to report this, or would that just be annoyingly chatty?
+ if (num_reasons == 1)
+ {
+ char message[1024];
+ const char *reason = SBProcess::GetRestartedReasonAtIndexFromEvent (event, 0);
+ int message_len = ::snprintf (message, sizeof(message), "Process %" PRIu64 " stopped and restarted: %s\n",
+ process.GetProcessID(), reason ? reason : "<UNKNOWN REASON>");
+ m_io_channel_ap->OutWrite(message, message_len, ASYNC);
+ }
+ else
+ {
+ char message[1024];
+ int message_len = ::snprintf (message, sizeof(message), "Process %" PRIu64 " stopped and restarted, reasons:\n",
+ process.GetProcessID());
+ m_io_channel_ap->OutWrite(message, message_len, ASYNC);
+ for (size_t i = 0; i < num_reasons; i++)
+ {
+ const char *reason = SBProcess::GetRestartedReasonAtIndexFromEvent (event, i);
+ int message_len = ::snprintf(message, sizeof(message), "\t%s\n", reason ? reason : "<UNKNOWN REASON>");
+ m_io_channel_ap->OutWrite(message, message_len, ASYNC);
+ }
+ }
+ }
+ }
+ else
+ {
+ if (GetDebugger().GetSelectedTarget() == process.GetTarget())
+ {
+ SBCommandReturnObject result;
+ UpdateSelectedThread ();
+ m_debugger.GetCommandInterpreter().HandleCommand("process status", result, false);
+ m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize(), ASYNC);
+ m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize(), ASYNC);
+ }
+ else
+ {
+ SBStream out_stream;
+ uint32_t target_idx = GetDebugger().GetIndexOfTarget(process.GetTarget());
+ if (target_idx != UINT32_MAX)
+ out_stream.Printf ("Target %d: (", target_idx);
+ else
+ out_stream.Printf ("Target <unknown index>: (");
+ process.GetTarget().GetDescription (out_stream, eDescriptionLevelBrief);
+ out_stream.Printf (") stopped.\n");
+ m_io_channel_ap->OutWrite (out_stream.GetData(), out_stream.GetSize(), ASYNC);
+ }
+ }
+ break;
+ }
+ }
+}
+
+void
+Driver::HandleThreadEvent (const SBEvent &event)
+{
+ // 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.GetType();
+ if (event_type == SBThread::eBroadcastBitStackChanged
+ || event_type == SBThread::eBroadcastBitThreadSelected)
+ {
+ SBThread thread = SBThread::GetThreadFromEvent (event);
+ if (thread.IsValid())
+ {
+ SBStream out_stream;
+ thread.GetStatus(out_stream);
+ m_io_channel_ap->OutWrite (out_stream.GetData (), out_stream.GetSize (), ASYNC);
+ }
+ }
+}
+
+// This function handles events broadcast by the IOChannel (HasInput, UserInterrupt, or ThreadShouldExit).
+
+bool
+Driver::HandleIOEvent (const SBEvent &event)
+{
+ bool quit = false;
+
+ const uint32_t event_type = event.GetType();
+
+ if (event_type & IOChannel::eBroadcastBitHasUserInput)
+ {
+ // We got some input (i.e. a command string) from the user; pass it off to the command interpreter for
+ // handling.
+
+ const char *command_string = SBEvent::GetCStringFromEvent(event);
+ if (command_string == NULL)
+ command_string = "";
+ SBCommandReturnObject result;
+
+ // We don't want the result to bypass the OutWrite function in IOChannel, as this can result in odd
+ // output orderings and problems with the prompt.
+
+ // Note that we are in the process of executing a command
+ m_executing_user_command = true;
+
+ m_debugger.GetCommandInterpreter().HandleCommand (command_string, result, true);
+
+ // Note that we are back from executing a user command
+ m_executing_user_command = false;
+
+ // Display any STDOUT/STDERR _prior_ to emitting the command result text
+ GetProcessSTDOUT ();
+ GetProcessSTDERR ();
+
+ const bool only_if_no_immediate = true;
+
+ // Now emit the command output text from the command we just executed
+ const size_t output_size = result.GetOutputSize();
+ if (output_size > 0)
+ m_io_channel_ap->OutWrite (result.GetOutput(only_if_no_immediate), output_size, NO_ASYNC);
+
+ // Now emit the command error text from the command we just executed
+ const size_t error_size = result.GetErrorSize();
+ if (error_size > 0)
+ m_io_channel_ap->OutWrite (result.GetError(only_if_no_immediate), error_size, NO_ASYNC);
+
+ // We are done getting and running our command, we can now clear the
+ // m_waiting_for_command so we can get another one.
+ m_waiting_for_command = false;
+
+ // If our editline input reader is active, it means another input reader
+ // got pushed onto the input reader and caused us to become deactivated.
+ // When the input reader above us gets popped, we will get re-activated
+ // and our prompt will refresh in our callback
+ if (m_editline_reader.IsActive())
+ {
+ ReadyForCommand ();
+ }
+ }
+ else if (event_type & IOChannel::eBroadcastBitUserInterrupt)
+ {
+ // This is here to handle control-c interrupts from the user. It has not yet really been implemented.
+ // TO BE DONE: PROPERLY HANDLE CONTROL-C FROM USER
+ //m_io_channel_ap->CancelInput();
+ // Anything else? Send Interrupt to process?
+ }
+ else if ((event_type & IOChannel::eBroadcastBitThreadShouldExit) ||
+ (event_type & IOChannel::eBroadcastBitThreadDidExit))
+ {
+ // If the IOChannel thread is trying to go away, then it is definitely
+ // time to end the debugging session.
+ quit = true;
+ }
+
+ return quit;
+}
+
+void
+Driver::MasterThreadBytesReceived (void *baton, const void *src, size_t src_len)
+{
+ Driver *driver = (Driver*)baton;
+ driver->GetFromMaster ((const char *)src, src_len);
+}
+
+void
+Driver::GetFromMaster (const char *src, size_t src_len)
+{
+ // Echo the characters back to the Debugger's stdout, that way if you
+ // type characters while a command is running, you'll see what you've typed.
+ FILE *out_fh = m_debugger.GetOutputFileHandle();
+ if (out_fh)
+ ::fwrite (src, 1, src_len, out_fh);
+}
+
+size_t
+Driver::EditLineInputReaderCallback
+(
+ void *baton,
+ SBInputReader *reader,
+ InputReaderAction notification,
+ const char *bytes,
+ size_t bytes_len
+)
+{
+ Driver *driver = (Driver *)baton;
+
+ switch (notification)
+ {
+ case eInputReaderActivate:
+ break;
+
+ case eInputReaderReactivate:
+ if (driver->m_executing_user_command == false)
+ driver->ReadyForCommand();
+ break;
+
+ case eInputReaderDeactivate:
+ break;
+
+ case eInputReaderAsynchronousOutputWritten:
+ if (driver->m_io_channel_ap.get() != NULL)
+ driver->m_io_channel_ap->RefreshPrompt();
+ break;
+
+ case eInputReaderInterrupt:
+ if (driver->m_io_channel_ap.get() != NULL)
+ {
+ SBProcess process(driver->GetDebugger().GetSelectedTarget().GetProcess());
+ if (!driver->m_io_channel_ap->EditLineHasCharacters()
+ && process.IsValid()
+ && (process.GetState() == lldb::eStateRunning || process.GetState() == lldb::eStateAttaching))
+ {
+ process.SendAsyncInterrupt ();
+ }
+ else
+ {
+ driver->m_io_channel_ap->OutWrite ("^C\n", 3, NO_ASYNC);
+ // I wish I could erase the entire input line, but there's no public API for that.
+ driver->m_io_channel_ap->EraseCharsBeforeCursor();
+ driver->m_io_channel_ap->RefreshPrompt();
+ }
+ }
+ break;
+
+ case eInputReaderEndOfFile:
+ if (driver->m_io_channel_ap.get() != NULL)
+ {
+ driver->m_io_channel_ap->OutWrite ("^D\n", 3, NO_ASYNC);
+ driver->m_io_channel_ap->RefreshPrompt ();
+ }
+ write (driver->m_editline_pty.GetMasterFileDescriptor(), "quit\n", 5);
+ break;
+
+ case eInputReaderGotToken:
+ write (driver->m_editline_pty.GetMasterFileDescriptor(), bytes, bytes_len);
+ break;
+
+ case eInputReaderDone:
+ break;
+ }
+ return bytes_len;
+}
+
+void
+Driver::MainLoop ()
+{
+ char error_str[1024];
+ if (m_editline_pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY, error_str, sizeof(error_str)) == false)
+ {
+ ::fprintf (stderr, "error: failed to open driver pseudo terminal : %s", error_str);
+ exit(1);
+ }
+ else
+ {
+ const char *driver_slave_name = m_editline_pty.GetSlaveName (error_str, sizeof(error_str));
+ if (driver_slave_name == NULL)
+ {
+ ::fprintf (stderr, "error: failed to get slave name for driver pseudo terminal : %s", error_str);
+ exit(2);
+ }
+ else
+ {
+ m_editline_slave_fh = ::fopen (driver_slave_name, "r+");
+ if (m_editline_slave_fh == NULL)
+ {
+ SBError error;
+ error.SetErrorToErrno();
+ ::fprintf (stderr, "error: failed to get open slave for driver pseudo terminal : %s",
+ error.GetCString());
+ exit(3);
+ }
+
+ ::setbuf (m_editline_slave_fh, NULL);
+ }
+ }
+
+ lldb_utility::PseudoTerminal editline_output_pty;
+ FILE *editline_output_slave_fh = NULL;
+
+ if (editline_output_pty.OpenFirstAvailableMaster (O_RDWR|O_NOCTTY, error_str, sizeof (error_str)) == false)
+ {
+ ::fprintf (stderr, "error: failed to open output pseudo terminal : %s", error_str);
+ exit(1);
+ }
+ else
+ {
+ const char *output_slave_name = editline_output_pty.GetSlaveName (error_str, sizeof(error_str));
+ if (output_slave_name == NULL)
+ {
+ ::fprintf (stderr, "error: failed to get slave name for output pseudo terminal : %s", error_str);
+ exit(2);
+ }
+ else
+ {
+ editline_output_slave_fh = ::fopen (output_slave_name, "r+");
+ if (editline_output_slave_fh == NULL)
+ {
+ SBError error;
+ error.SetErrorToErrno();
+ ::fprintf (stderr, "error: failed to get open slave for output pseudo terminal : %s",
+ error.GetCString());
+ exit(3);
+ }
+ ::setbuf (editline_output_slave_fh, NULL);
+ }
+ }
+
+ // struct termios stdin_termios;
+
+ if (::tcgetattr(STDIN_FILENO, &g_old_stdin_termios) == 0)
+ {
+ g_old_stdin_termios_is_valid = true;
+ atexit (reset_stdin_termios);
+ }
+
+ ::setbuf (stdin, NULL);
+ ::setbuf (stdout, NULL);
+
+ m_debugger.SetErrorFileHandle (stderr, false);
+ m_debugger.SetOutputFileHandle (stdout, false);
+ m_debugger.SetInputFileHandle (stdin, true);
+
+ m_debugger.SetUseExternalEditor(m_option_data.m_use_external_editor);
+
+ // You have to drain anything that comes to the master side of the PTY. master_out_comm is
+ // for that purpose. The reason you need to do this is a curious reason... editline will echo
+ // characters to the PTY when it gets characters while el_gets is not running, and then when
+ // you call el_gets (or el_getc) it will try to reset the terminal back to raw mode which blocks
+ // if there are unconsumed characters in the out buffer.
+ // However, you don't need to do anything with the characters, since editline will dump these
+ // unconsumed characters after printing the prompt again in el_gets.
+
+ SBCommunication master_out_comm("driver.editline");
+ master_out_comm.SetCloseOnEOF (false);
+ master_out_comm.AdoptFileDesriptor(m_editline_pty.GetMasterFileDescriptor(), false);
+ master_out_comm.SetReadThreadBytesReceivedCallback(Driver::MasterThreadBytesReceived, this);
+
+ if (master_out_comm.ReadThreadStart () == false)
+ {
+ ::fprintf (stderr, "error: failed to start master out read thread");
+ exit(5);
+ }
+
+ SBCommandInterpreter sb_interpreter = m_debugger.GetCommandInterpreter();
+
+ m_io_channel_ap.reset (new IOChannel(m_editline_slave_fh, editline_output_slave_fh, stdout, stderr, this));
+
+ SBCommunication out_comm_2("driver.editline_output");
+ out_comm_2.SetCloseOnEOF (false);
+ out_comm_2.AdoptFileDesriptor (editline_output_pty.GetMasterFileDescriptor(), false);
+ out_comm_2.SetReadThreadBytesReceivedCallback (IOChannel::LibeditOutputBytesReceived, m_io_channel_ap.get());
+
+ if (out_comm_2.ReadThreadStart () == false)
+ {
+ ::fprintf (stderr, "error: failed to start libedit output read thread");
+ exit (5);
+ }
+
+
+ struct winsize window_size;
+ if (isatty (STDIN_FILENO)
+ && ::ioctl (STDIN_FILENO, TIOCGWINSZ, &window_size) == 0)
+ {
+ if (window_size.ws_col > 0)
+ m_debugger.SetTerminalWidth (window_size.ws_col);
+ }
+
+ // Since input can be redirected by the debugger, we must insert our editline
+ // input reader in the queue so we know when our reader should be active
+ // and so we can receive bytes only when we are supposed to.
+ SBError err (m_editline_reader.Initialize (m_debugger,
+ Driver::EditLineInputReaderCallback, // callback
+ this, // baton
+ eInputReaderGranularityByte, // token_size
+ NULL, // end token - NULL means never done
+ NULL, // prompt - taken care of elsewhere
+ false)); // echo input - don't need Debugger
+ // to do this, we handle it elsewhere
+
+ if (err.Fail())
+ {
+ ::fprintf (stderr, "error: %s", err.GetCString());
+ exit (6);
+ }
+
+ m_debugger.PushInputReader (m_editline_reader);
+
+ SBListener listener(m_debugger.GetListener());
+ if (listener.IsValid())
+ {
+
+ listener.StartListeningForEventClass(m_debugger,
+ SBTarget::GetBroadcasterClassName(),
+ SBTarget::eBroadcastBitBreakpointChanged);
+ listener.StartListeningForEventClass(m_debugger,
+ SBThread::GetBroadcasterClassName(),
+ SBThread::eBroadcastBitStackChanged |
+ SBThread::eBroadcastBitThreadSelected);
+ listener.StartListeningForEvents (*m_io_channel_ap,
+ IOChannel::eBroadcastBitHasUserInput |
+ IOChannel::eBroadcastBitUserInterrupt |
+ IOChannel::eBroadcastBitThreadShouldExit |
+ IOChannel::eBroadcastBitThreadDidStart |
+ IOChannel::eBroadcastBitThreadDidExit);
+
+ if (m_io_channel_ap->Start ())
+ {
+ bool iochannel_thread_exited = false;
+
+ listener.StartListeningForEvents (sb_interpreter.GetBroadcaster(),
+ SBCommandInterpreter::eBroadcastBitQuitCommandReceived |
+ SBCommandInterpreter::eBroadcastBitAsynchronousOutputData |
+ SBCommandInterpreter::eBroadcastBitAsynchronousErrorData);
+
+ // Before we handle any options from the command line, we parse the
+ // .lldbinit file in the user's home directory.
+ SBCommandReturnObject result;
+ sb_interpreter.SourceInitFileInHomeDirectory(result);
+ if (GetDebugMode())
+ {
+ result.PutError (m_debugger.GetErrorFileHandle());
+ result.PutOutput (m_debugger.GetOutputFileHandle());
+ }
+
+ // Now we handle options we got from the command line
+ char command_string[PATH_MAX * 2];
+ const size_t num_source_command_files = GetNumSourceCommandFiles();
+ const bool dump_stream_only_if_no_immediate = true;
+ if (num_source_command_files > 0)
+ {
+ for (size_t i=0; i < num_source_command_files; ++i)
+ {
+ const char *command_file = GetSourceCommandFileAtIndex(i);
+ ::snprintf (command_string, sizeof(command_string), "command source '%s'", command_file);
+ m_debugger.GetCommandInterpreter().HandleCommand (command_string, result, false);
+ if (GetDebugMode())
+ {
+ result.PutError (m_debugger.GetErrorFileHandle());
+ result.PutOutput (m_debugger.GetOutputFileHandle());
+ }
+
+ // if the command sourcing generated an error - dump the result object
+ if (result.Succeeded() == false)
+ {
+ const size_t output_size = result.GetOutputSize();
+ if (output_size > 0)
+ m_io_channel_ap->OutWrite (result.GetOutput(dump_stream_only_if_no_immediate), output_size, NO_ASYNC);
+ const size_t error_size = result.GetErrorSize();
+ if (error_size > 0)
+ m_io_channel_ap->OutWrite (result.GetError(dump_stream_only_if_no_immediate), error_size, NO_ASYNC);
+ }
+
+ result.Clear();
+ }
+ }
+
+ // Was there a core file specified?
+ std::string core_file_spec("");
+ if (!m_option_data.m_core_file.empty())
+ core_file_spec.append("--core ").append(m_option_data.m_core_file);
+
+ const size_t num_args = m_option_data.m_args.size();
+ if (num_args > 0)
+ {
+ char arch_name[64];
+ if (m_debugger.GetDefaultArchitecture (arch_name, sizeof (arch_name)))
+ ::snprintf (command_string,
+ sizeof (command_string),
+ "target create --arch=%s %s \"%s\"",
+ arch_name,
+ core_file_spec.c_str(),
+ m_option_data.m_args[0].c_str());
+ else
+ ::snprintf (command_string,
+ sizeof(command_string),
+ "target create %s \"%s\"",
+ core_file_spec.c_str(),
+ m_option_data.m_args[0].c_str());
+
+ m_debugger.HandleCommand (command_string);
+
+ if (num_args > 1)
+ {
+ m_debugger.HandleCommand ("settings clear target.run-args");
+ char arg_cstr[1024];
+ for (size_t arg_idx = 1; arg_idx < num_args; ++arg_idx)
+ {
+ ::snprintf (arg_cstr,
+ sizeof(arg_cstr),
+ "settings append target.run-args \"%s\"",
+ m_option_data.m_args[arg_idx].c_str());
+ m_debugger.HandleCommand (arg_cstr);
+ }
+ }
+ }
+ else if (!core_file_spec.empty())
+ {
+ ::snprintf (command_string,
+ sizeof(command_string),
+ "target create %s",
+ core_file_spec.c_str());
+ m_debugger.HandleCommand (command_string);;
+ }
+
+ // Now that all option parsing is done, we try and parse the .lldbinit
+ // file in the current working directory
+ sb_interpreter.SourceInitFileInCurrentWorkingDirectory (result);
+ if (GetDebugMode())
+ {
+ result.PutError(m_debugger.GetErrorFileHandle());
+ result.PutOutput(m_debugger.GetOutputFileHandle());
+ }
+
+ SBEvent event;
+
+ // Make sure the IO channel is started up before we try to tell it we
+ // are ready for input
+ listener.WaitForEventForBroadcasterWithType (UINT32_MAX,
+ *m_io_channel_ap,
+ IOChannel::eBroadcastBitThreadDidStart,
+ event);
+ // If we were asked to attach, then do that here:
+ // I'm going to use the command string rather than directly
+ // calling the API's because then I don't have to recode the
+ // event handling here.
+ if (!m_option_data.m_process_name.empty()
+ || m_option_data.m_process_pid != LLDB_INVALID_PROCESS_ID)
+ {
+ std::string command_str("process attach ");
+ if (m_option_data.m_process_pid != LLDB_INVALID_PROCESS_ID)
+ {
+ command_str.append("-p ");
+ char pid_buffer[32];
+ ::snprintf (pid_buffer, sizeof(pid_buffer), "%" PRIu64, m_option_data.m_process_pid);
+ command_str.append(pid_buffer);
+ }
+ else
+ {
+ command_str.append("-n \"");
+ command_str.append(m_option_data.m_process_name);
+ command_str.push_back('\"');
+ if (m_option_data.m_wait_for)
+ command_str.append(" -w");
+ }
+
+ if (m_debugger.GetOutputFileHandle())
+ ::fprintf (m_debugger.GetOutputFileHandle(),
+ "Attaching to process with:\n %s\n",
+ command_str.c_str());
+
+ // Force the attach to be synchronous:
+ bool orig_async = m_debugger.GetAsync();
+ m_debugger.SetAsync(true);
+ m_debugger.HandleCommand(command_str.c_str());
+ m_debugger.SetAsync(orig_async);
+ }
+
+ ReadyForCommand ();
+
+ while (!GetIsDone())
+ {
+ listener.WaitForEvent (UINT32_MAX, event);
+ if (event.IsValid())
+ {
+ if (event.GetBroadcaster().IsValid())
+ {
+ uint32_t event_type = event.GetType();
+ if (event.BroadcasterMatchesRef (*m_io_channel_ap))
+ {
+ if ((event_type & IOChannel::eBroadcastBitThreadShouldExit) ||
+ (event_type & IOChannel::eBroadcastBitThreadDidExit))
+ {
+ SetIsDone();
+ if (event_type & IOChannel::eBroadcastBitThreadDidExit)
+ iochannel_thread_exited = true;
+ }
+ else
+ {
+ if (HandleIOEvent (event))
+ SetIsDone();
+ }
+ }
+ else if (SBProcess::EventIsProcessEvent (event))
+ {
+ HandleProcessEvent (event);
+ }
+ else if (SBBreakpoint::EventIsBreakpointEvent (event))
+ {
+ HandleBreakpointEvent (event);
+ }
+ else if (SBThread::EventIsThreadEvent (event))
+ {
+ HandleThreadEvent (event);
+ }
+ else if (event.BroadcasterMatchesRef (sb_interpreter.GetBroadcaster()))
+ {
+ // TODO: deprecate the eBroadcastBitQuitCommandReceived event
+ // now that we have SBCommandInterpreter::SetCommandOverrideCallback()
+ // that can take over a command
+ if (event_type & SBCommandInterpreter::eBroadcastBitQuitCommandReceived)
+ {
+ SetIsDone();
+ }
+ else if (event_type & SBCommandInterpreter::eBroadcastBitAsynchronousErrorData)
+ {
+ const char *data = SBEvent::GetCStringFromEvent (event);
+ m_io_channel_ap->ErrWrite (data, strlen(data), ASYNC);
+ }
+ else if (event_type & SBCommandInterpreter::eBroadcastBitAsynchronousOutputData)
+ {
+ const char *data = SBEvent::GetCStringFromEvent (event);
+ m_io_channel_ap->OutWrite (data, strlen(data), ASYNC);
+ }
+ }
+ }
+ }
+ }
+
+ master_out_comm.SetReadThreadBytesReceivedCallback(NULL, NULL);
+ master_out_comm.Disconnect();
+ master_out_comm.ReadThreadStop();
+
+ out_comm_2.SetReadThreadBytesReceivedCallback(NULL, NULL);
+ out_comm_2.Disconnect();
+ out_comm_2.ReadThreadStop();
+
+ editline_output_pty.CloseMasterFileDescriptor();
+ reset_stdin_termios();
+ fclose (stdin);
+
+ CloseIOChannelFile ();
+
+ if (!iochannel_thread_exited)
+ {
+ event.Clear();
+ listener.GetNextEventForBroadcasterWithType (*m_io_channel_ap,
+ IOChannel::eBroadcastBitThreadDidExit,
+ event);
+ if (!event.IsValid())
+ {
+ // Send end EOF to the driver file descriptor
+ m_io_channel_ap->Stop();
+ }
+ }
+
+ SBDebugger::Destroy (m_debugger);
+ }
+ }
+}
+
+
+void
+Driver::ReadyForCommand ()
+{
+ if (m_waiting_for_command == false)
+ {
+ m_waiting_for_command = true;
+ BroadcastEventByType (Driver::eBroadcastBitReadyForInput, true);
+ }
+}
+
+void
+Driver::ResizeWindow (unsigned short col)
+{
+ GetDebugger().SetTerminalWidth (col);
+ if (m_io_channel_ap.get() != NULL)
+ {
+ m_io_channel_ap->ElResize();
+ }
+}
+
+void
+sigwinch_handler (int signo)
+{
+ struct winsize window_size;
+ if (isatty (STDIN_FILENO)
+ && ::ioctl (STDIN_FILENO, TIOCGWINSZ, &window_size) == 0)
+ {
+ if ((window_size.ws_col > 0) && g_driver != NULL)
+ {
+ g_driver->ResizeWindow (window_size.ws_col);
+ }
+ }
+}
+
+void
+sigint_handler (int signo)
+{
+ static bool g_interrupt_sent = false;
+ if (g_driver)
+ {
+ if (!g_interrupt_sent)
+ {
+ g_interrupt_sent = true;
+ g_driver->GetDebugger().DispatchInputInterrupt();
+ g_interrupt_sent = false;
+ return;
+ }
+ }
+
+ exit (signo);
+}
+
+void
+sigtstp_handler (int signo)
+{
+ g_driver->GetDebugger().SaveInputTerminalState();
+ signal (signo, SIG_DFL);
+ kill (getpid(), signo);
+ signal (signo, sigtstp_handler);
+}
+
+void
+sigcont_handler (int signo)
+{
+ g_driver->GetDebugger().RestoreInputTerminalState();
+ signal (signo, SIG_DFL);
+ kill (getpid(), signo);
+ signal (signo, sigcont_handler);
+}
+
+int
+main (int argc, char const *argv[], const char *envp[])
+{
+ SBDebugger::Initialize();
+
+ SBHostOS::ThreadCreated ("<lldb.driver.main-thread>");
+
+ signal (SIGPIPE, SIG_IGN);
+ signal (SIGWINCH, sigwinch_handler);
+ signal (SIGINT, sigint_handler);
+ signal (SIGTSTP, sigtstp_handler);
+ signal (SIGCONT, sigcont_handler);
+
+ // Create a scope for driver so that the driver object will destroy itself
+ // before SBDebugger::Terminate() is called.
+ {
+ Driver driver;
+
+ bool exit = false;
+ SBError error (driver.ParseArgs (argc, argv, stdout, exit));
+ if (error.Fail())
+ {
+ const char *error_cstr = error.GetCString ();
+ if (error_cstr)
+ ::fprintf (stderr, "error: %s\n", error_cstr);
+ }
+ else if (!exit)
+ {
+ driver.MainLoop ();
+ }
+ }
+
+ SBDebugger::Terminate();
+ return 0;
+}
diff --git a/tools/driver/Driver.h b/tools/driver/Driver.h
new file mode 100644
index 000000000000..2a4a27df4cdc
--- /dev/null
+++ b/tools/driver/Driver.h
@@ -0,0 +1,201 @@
+//===-- Driver.h ------------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef lldb_Driver_h_
+#define lldb_Driver_h_
+
+#include "lldb/Utility/PseudoTerminal.h"
+
+#include <set>
+#include <bitset>
+#include <string>
+#include <vector>
+
+#include "lldb/API/SBDefines.h"
+#include "lldb/API/SBBroadcaster.h"
+#include "lldb/API/SBDebugger.h"
+#include "lldb/API/SBError.h"
+#include "lldb/API/SBInputReader.h"
+
+#define ASYNC true
+#define NO_ASYNC false
+
+class IOChannel;
+
+namespace lldb
+{
+ class SBInputReader;
+}
+
+
+class Driver : public lldb::SBBroadcaster
+{
+public:
+ enum {
+ eBroadcastBitReadyForInput = (1 << 0),
+ eBroadcastBitThreadShouldExit = (1 << 1)
+ };
+
+ Driver ();
+
+ virtual
+ ~Driver ();
+
+ void
+ MainLoop ();
+
+ void
+ PutSTDIN (const char *src, size_t src_len);
+
+ void
+ GetFromMaster (const char *src, size_t src_len);
+
+ bool
+ HandleIOEvent (const lldb::SBEvent &event);
+
+ void
+ HandleProcessEvent (const lldb::SBEvent &event);
+
+ void
+ HandleBreakpointEvent (const lldb::SBEvent &event);
+
+ void
+ HandleThreadEvent (const lldb::SBEvent &event);
+
+ lldb::SBError
+ ParseArgs (int argc, const char *argv[], FILE *out_fh, bool &do_exit);
+
+ const char *
+ GetFilename() const;
+
+ const char *
+ GetCrashLogFilename() const;
+
+ const char *
+ GetArchName() const;
+
+ lldb::ScriptLanguage
+ GetScriptLanguage() const;
+
+ size_t
+ GetNumSourceCommandFiles () const;
+
+ const char *
+ GetSourceCommandFileAtIndex (uint32_t idx) const;
+
+ bool
+ GetDebugMode() const;
+
+
+ class OptionData
+ {
+ public:
+ OptionData ();
+ ~OptionData ();
+
+ void
+ Clear();
+
+ //static OptionDefinition m_cmd_option_table[];
+
+ std::vector<std::string> m_args;
+ lldb::ScriptLanguage m_script_lang;
+ std::string m_core_file;
+ std::string m_crash_log;
+ std::vector<std::string> m_source_command_files;
+ bool m_debug_mode;
+ bool m_print_version;
+ bool m_print_python_path;
+ bool m_print_help;
+ bool m_wait_for;
+ std::string m_process_name;
+ lldb::pid_t m_process_pid;
+ bool m_use_external_editor; // FIXME: When we have set/show variables we can remove this from here.
+ typedef std::set<char> OptionSet;
+ OptionSet m_seen_options;
+ };
+
+
+ static lldb::SBError
+ SetOptionValue (int option_idx,
+ const char *option_arg,
+ Driver::OptionData &data);
+
+
+ lldb::SBDebugger &
+ GetDebugger()
+ {
+ return m_debugger;
+ }
+
+ bool
+ EditlineReaderIsTop ()
+ {
+ return m_debugger.InputReaderIsTopReader (m_editline_reader);
+ }
+
+ bool
+ GetIsDone () const
+ {
+ return m_done;
+ }
+
+ void
+ SetIsDone ()
+ {
+ m_done = true;
+ }
+
+ void
+ ResizeWindow (unsigned short col);
+
+private:
+ lldb::SBDebugger m_debugger;
+ lldb_utility::PseudoTerminal m_editline_pty;
+ FILE *m_editline_slave_fh;
+ lldb::SBInputReader m_editline_reader;
+ std::unique_ptr<IOChannel> m_io_channel_ap;
+ OptionData m_option_data;
+ bool m_executing_user_command;
+ bool m_waiting_for_command;
+ bool m_done;
+
+ void
+ ResetOptionValues ();
+
+ size_t
+ GetProcessSTDOUT ();
+
+ size_t
+ GetProcessSTDERR ();
+
+ void
+ UpdateSelectedThread ();
+
+ void
+ CloseIOChannelFile ();
+
+ static size_t
+ EditLineInputReaderCallback (void *baton,
+ lldb::SBInputReader *reader,
+ lldb::InputReaderAction notification,
+ const char *bytes,
+ size_t bytes_len);
+
+ static void
+ ReadThreadBytesReceived (void *baton, const void *src, size_t src_len);
+
+ static void
+ MasterThreadBytesReceived (void *baton, const void *src, size_t src_len);
+
+ void
+ ReadyForCommand ();
+};
+
+#endif // lldb_Driver_h_
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);
+}
diff --git a/tools/driver/IOChannel.h b/tools/driver/IOChannel.h
new file mode 100644
index 000000000000..36653a0c289f
--- /dev/null
+++ b/tools/driver/IOChannel.h
@@ -0,0 +1,174 @@
+//===-- IOChannel.h ---------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef lldb_IOChannel_h_
+#define lldb_IOChannel_h_
+
+#include <string>
+#include <queue>
+
+#if defined(__FreeBSD__)
+#include <readline/readline.h>
+#else
+#include <editline/readline.h>
+#endif
+#include <histedit.h>
+#include <pthread.h>
+#include <sys/time.h>
+
+#include "Driver.h"
+
+class IOChannel : public lldb::SBBroadcaster
+{
+public:
+ enum {
+ eBroadcastBitHasUserInput = (1 << 0),
+ eBroadcastBitUserInterrupt = (1 << 1),
+ eBroadcastBitThreadShouldExit = (1 << 2),
+ eBroadcastBitThreadDidExit = (1 << 3),
+ eBroadcastBitThreadDidStart = (1 << 4),
+ eBroadcastBitsSTDOUT = (1 << 5),
+ eBroadcastBitsSTDERR = (1 << 6),
+ eBroadcastBitsSTDIN = (1 << 7),
+ eAllEventBits = 0xffffffff
+ };
+
+ enum LibeditGetInputResult
+ {
+ eLibeditGetInputEOF = 0,
+ eLibeditGetInputValid = 1,
+ eLibeditGetInputEmpty = 2,
+ eLibeditGetInputResultError = 4,
+ eLibeditGetInputResultUnknown = 0xffffffff
+ };
+
+ IOChannel (FILE *editline_in,
+ FILE *editline_out,
+ FILE *out,
+ FILE *err,
+ Driver *driver = NULL);
+
+ virtual
+ ~IOChannel ();
+
+ bool
+ Start ();
+
+ bool
+ Stop ();
+
+ static void *
+ IOReadThread (void *);
+
+ void
+ Run ();
+
+ void
+ OutWrite (const char *buffer, size_t len, bool asynchronous);
+
+ void
+ ErrWrite (const char *buffer, size_t len, bool asynchronous);
+
+ LibeditGetInputResult
+ LibeditGetInput (std::string &);
+
+ static void
+ LibeditOutputBytesReceived (void *baton, const void *src,size_t src_len);
+
+ void
+ SetPrompt ();
+
+ void
+ RefreshPrompt ();
+
+ void
+ AddCommandToQueue (const char *command);
+
+ bool
+ GetCommandFromQueue (std::string &cmd);
+
+ int
+ CommandQueueSize () const;
+
+ void
+ ClearCommandQueue ();
+
+ bool
+ CommandQueueIsEmpty () const;
+
+ const char *
+ GetPrompt ();
+
+ bool
+ EditLineHasCharacters ();
+
+ void
+ EraseCharsBeforeCursor ();
+
+ static unsigned char
+ ElCompletionFn (EditLine *e, int ch);
+
+ void
+ ElResize();
+
+protected:
+
+ bool
+ IsGettingCommand () const;
+
+ void
+ SetGettingCommand (bool new_value);
+
+private:
+
+ pthread_mutex_t m_output_mutex;
+ struct timeval m_enter_elgets_time;
+
+ Driver *m_driver;
+ lldb::thread_t m_read_thread;
+ bool m_read_thread_should_exit;
+ FILE *m_out_file;
+ FILE *m_err_file;
+ std::queue<std::string> m_command_queue;
+ const char *m_completion_key;
+
+ EditLine *m_edit_line;
+ History *m_history;
+ HistEvent m_history_event;
+ bool m_getting_command;
+ bool m_expecting_prompt;
+ std::string m_prompt_str; // for accumlating the prompt as it gets written out by editline
+ bool m_refresh_request_pending;
+
+ void
+ HistorySaveLoad (bool save);
+
+ unsigned char
+ HandleCompletion (EditLine *e, int ch);
+};
+
+class IOLocker
+{
+public:
+
+ IOLocker (pthread_mutex_t &mutex);
+
+ ~IOLocker ();
+
+protected:
+
+ pthread_mutex_t *m_mutex_ptr;
+
+private:
+
+ IOLocker (const IOLocker&);
+ const IOLocker& operator= (const IOLocker&);
+};
+
+#endif // lldb_IOChannel_h_