From f034231a6a1fd5d6395206c1651de8cd9402cca3 Mon Sep 17 00:00:00 2001 From: Ed Maste Date: Fri, 23 Aug 2013 17:46:38 +0000 Subject: Import lldb as of SVN r188801 (A number of files not required for the FreeBSD build have been removed.) Sponsored by: DARPA, AFRL --- tools/driver/Driver.cpp | 1733 ++++++++++++++++++++++++++++++++++++++++++++ tools/driver/Driver.h | 201 +++++ tools/driver/IOChannel.cpp | 647 +++++++++++++++++ tools/driver/IOChannel.h | 174 +++++ 4 files changed, 2755 insertions(+) create mode 100644 tools/driver/Driver.cpp create mode 100644 tools/driver/Driver.h create mode 100644 tools/driver/IOChannel.cpp create mode 100644 tools/driver/IOChannel.h (limited to 'tools/driver') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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. 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 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 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 , 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. [options-for-level-0] + // [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, " [[--] [ ...]]"); + } + + fprintf (out, "\n\n"); + + // Now print out all the detailed information about the various options: long form, short form and help text: + // -- long_name + // - short + // 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 -- [ []]' 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 &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 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(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, "\n"); + } + else + ::fprintf (out_fh, "\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 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 : ""); + 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 : ""); + 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 : ("); + 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 (""); + + 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 +#include +#include +#include + +#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 m_args; + lldb::ScriptLanguage m_script_lang; + std::string m_core_file; + std::string m_crash_log; + std::vector 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 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 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 + +#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 +#include + +using namespace lldb; + +typedef std::map 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 (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 ("", 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 +#include + +#if defined(__FreeBSD__) +#include +#else +#include +#endif +#include +#include +#include + +#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 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_ -- cgit v1.2.3