diff options
Diffstat (limited to 'lldb/tools/driver')
-rw-r--r-- | lldb/tools/driver/Driver.cpp | 1265 | ||||
-rw-r--r-- | lldb/tools/driver/Driver.h | 156 | ||||
-rw-r--r-- | lldb/tools/driver/IOChannel.cpp | 449 | ||||
-rw-r--r-- | lldb/tools/driver/IOChannel.h | 113 | ||||
-rw-r--r-- | lldb/tools/driver/lldb-Info.plist | 21 |
5 files changed, 2004 insertions, 0 deletions
diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp new file mode 100644 index 00000000000..8cca697dd27 --- /dev/null +++ b/lldb/tools/driver/Driver.cpp @@ -0,0 +1,1265 @@ +//===-- 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> + +#include "IOChannel.h" +#include <LLDB/SBCommandInterpreter.h> +#include <LLDB/SBCommandReturnObject.h> +#include <LLDB/SBCommunication.h> +#include <LLDB/SBDebugger.h> +#include <LLDB/SBEvent.h> +#include <LLDB/SBHostOS.h> +#include <LLDB/SBListener.h> +#include <LLDB/SBSourceManager.h> +#include <LLDB/SBTarget.h> +#include <LLDB/SBThread.h> +#include <LLDB/SBProcess.h> + +using namespace lldb; + +static void reset_stdin_termios (); +static struct termios g_old_stdin_termios; + +// 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 () +{ + ::tcsetattr (STDIN_FILENO, TCSANOW, &g_old_stdin_termios); +} + +static lldb::OptionDefinition g_options[] = +{ + { 0, true, "help", 'h', no_argument, NULL, NULL, NULL, + "Prints out the usage information for the LLDB debugger." }, + + { 1, true, "version", 'v', no_argument, NULL, NULL, NULL, + "Prints out the current version number of the LLDB debugger." }, + + { 2, false, "file", 'f', required_argument, NULL, NULL, "<filename>", + "Tells the debugger to use the file <filename> as the program to be debugged." }, + + { 2, false, "arch", 'a', required_argument, NULL, NULL, "<architecture>", + "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." }, + + { 2, false, "script-language",'l', required_argument, NULL, NULL, "<scripting-language>", + "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." }, + + { 2, false, "debug", 'd', no_argument, NULL, NULL, NULL, + "Tells the debugger to print out extra information for debugging itself." }, + + { 2, false, "source", 's', required_argument, NULL, NULL, "<file>", + "Tells the debugger to read in and execute the file <file>, which should contain lldb commands." }, + + { 3, false, "crash-log", 'c', required_argument, NULL, NULL, "<file>", + "Load executable images from a crash log for symbolication." }, + + { 0, false, NULL, 0, 0, NULL, NULL, NULL, NULL } +}; + + +Driver::Driver () : + SBBroadcaster ("Driver"), + m_editline_pty (), + m_editline_slave_fh (NULL), + m_editline_reader (), + m_io_channel_ap (), + m_option_data (), + m_waiting_for_command (false) +{ +} + +Driver::~Driver () +{ +} + +void +Driver::CloseIOChannelFile () +{ + // Write and 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; SPACES, which is +// a string that is output_max_columns long, containing spaces; and 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, char *spaces, const char *text, int output_max_columns) +{ + int len = strlen (text); + std::string text_string (text); + std::string spaces_string (spaces); + + // 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", spaces_string.substr (0, indent).c_str(), 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", spaces_string.substr(0, indent).c_str(), substring.c_str()); + start = end + 1; + } + } +} + +void +ShowUsage (FILE *out, lldb::OptionDefinition *option_table, Driver::OptionData data) +{ + uint32_t screen_width = 80; + uint32_t indent_level = 0; + const char *name = "lldb"; + char spaces[screen_width+1]; + uint32_t i; + + for (i = 0; i < screen_width; ++i) + spaces[i] = ' '; + spaces[i] = '\n'; + + std::string spaces_string (spaces); + + 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 usage_level = 0; + uint32_t num_options; + + for (num_options = 0; option_table[num_options].long_option != NULL; ++num_options); + + for (i = 0; i < num_options; ++i) + { + if (i == 0 || option_table[i].usage_level > usage_level) + { + // Start a new level. + usage_level = option_table[i].usage_level; + if (usage_level > 0) + fprintf (out, "\n\n"); + fprintf (out, "%s%s", spaces_string.substr(0, indent_level).c_str(), name); + } + + if (option_table[i].required) + { + if (option_table[i].option_has_arg == required_argument) + fprintf (out, " -%c %s", option_table[i].short_option, option_table[i].argument_name); + else if (option_table[i].option_has_arg == optional_argument) + fprintf (out, " -%c [%s]", option_table[i].short_option, option_table[i].argument_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, option_table[i].argument_name); + else if (option_table[i].option_has_arg == optional_argument) + fprintf (out, " [-%c [%s]]", option_table[i].short_option, option_table[i].argument_name); + else + fprintf (out, " [-%c]", option_table[i].short_option); + } + } + + 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 (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()) + { + options_seen.insert (option_table[i].short_option); + fprintf (out, "%s-%c ", spaces_string.substr(0, indent_level).c_str(), option_table[i].short_option); + if (option_table[i].argument_name != NULL) + fprintf (out, "%s", option_table[i].argument_name); + fprintf (out, "\n"); + fprintf (out, "%s--%s ", spaces_string.substr(0, indent_level).c_str(), option_table[i].long_option); + if (option_table[i].argument_name != NULL) + fprintf (out, "%s", option_table[i].argument_name); + fprintf (out, "\n"); + indent_level += 5; + OutputFormattedUsageText (out, indent_level, spaces, option_table[i].usage_text, screen_width); + indent_level -= 5; + fprintf (out, "\n"); + } + } + + indent_level -= 5; + + fprintf (out, "\n%s('%s <filename>' also works, to specify the file to be debugged.)\n\n", + spaces_string.substr(0, indent_level).c_str(), name); +} + +void +BuildGetOptTable (lldb::OptionDefinition *expanded_option_table, struct option **getopt_table, int num_options) +{ + if (num_options == 0) + return; + + uint32_t i; + uint32_t j; + std::bitset<256> option_seen; + + 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; + +} + +SBError +ParseOptions (Driver::OptionData &data, int argc, const char **argv) +{ + SBError error; + std::string option_string; + struct option *long_options = NULL; + int num_options; + + for (num_options = 0; g_options[num_options].long_option != NULL; ++num_options); + + if (num_options == 0) + { + if (argc > 1) + error.SetErrorStringWithFormat ("invalid number of options"); + return error; + } + + long_options = (struct option *) malloc ((num_options + 1) * sizeof (struct option)); + + BuildGetOptTable (g_options, &long_options, num_options); + + if (long_options == NULL) + { + error.SetErrorStringWithFormat ("invalid long options"); + return error; + } + + // Build the option_string argument for call to getopt_long. + + 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; + } + } + } + + // Prepare for & make calls to getopt_long. + + optreset = 1; + optind = 1; + int val; + while (1) + { + int long_options_index = -1; + val = ::getopt_long (argc, (char * const *) argv, option_string.c_str(), long_options, &long_options_index); + + if (val == -1) + break; + else if (val == '?') + { + data.m_print_help = true; + error.SetErrorStringWithFormat ("unknown or ambiguous option"); + break; + } + else if (val == 0) + continue; + else + { + 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) + { + error = Driver::SetOptionValue (long_options_index, + long_options[long_options_index].has_arg == no_argument ? NULL : optarg, + data); + } + else + { + error.SetErrorStringWithFormat ("invalid option with value %i", val); + } + if (error.Fail()) + break; + } + } + + return error; +} + +Driver::OptionData::OptionData () : + m_filename(), + m_script_lang (lldb::eScriptLanguageDefault), + m_source_command_files (), + m_debug_mode (false), + m_print_help (false), + m_print_version (false) + +{ +} + +Driver::OptionData::~OptionData () +{ +} + +void +Driver::OptionData::Clear () +{ + m_filename.clear (); + m_script_lang = lldb::eScriptLanguageDefault; + m_source_command_files.clear (); + m_debug_mode = false; + m_print_help = false; + m_print_version = false; +} + +SBError +Driver::SetOptionValue (int option_idx, const char *option_arg, Driver::OptionData &option_data) +{ + SBError error; + const char short_option = (char) g_options[option_idx].short_option; + + switch (short_option) + { + case 'h': + option_data.m_print_help = true; + break; + + case 'v': + option_data.m_print_version = true; + break; + + case 'c': + option_data.m_crash_log = option_arg; + break; + + case 'f': + { + SBFileSpec file(option_arg); + if (file.Exists()) + option_data.m_filename = option_arg; + else + error.SetErrorStringWithFormat("file specified in --file (-f) option doesn't exist: '%s'", option_arg); + } + break; + + case 'a': + if (!SBDebugger::SetDefaultArchitecture (option_arg)) + error.SetErrorStringWithFormat("invalid architecture in the -a or --arch option: '%s'", option_arg); + break; + + case 'l': + option_data.m_script_lang = SBDebugger::GetScriptingLanguage (option_arg); + break; + + case 'd': + option_data.m_debug_mode = true; + break; + + case 's': + { + SBFileSpec file(option_arg); + if (file.Exists()) + option_data.m_source_command_files.push_back (option_arg); + else + error.SetErrorStringWithFormat("file specified in --source (-s) option doesn't exist: '%s'", option_arg); + } + break; + + default: + option_data.m_print_help = true; + error.SetErrorStringWithFormat ("unrecognized option %c", short_option); + break; + } + + return error; +} + +void +Driver::ResetOptionValues () +{ + m_option_data.Clear (); +} + +const char * +Driver::GetFilename() const +{ + if (m_option_data.m_filename.empty()) + return NULL; + return m_option_data.m_filename.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. + +bool +Driver::ParseArgs (int argc, const char *argv[], FILE *out_fh, FILE *err_fh) +{ + bool valid = true; + + ResetOptionValues (); + + if (argc == 2 && *(argv[1]) != '-') + { + m_option_data.m_filename = argv[1]; + } + else + { + SBCommandReturnObject result; + + SBError error = ParseOptions (m_option_data, argc, argv); + if (error.Fail()) + { + const char *error_cstr = error.GetCString (); + if (error_cstr) + ::fprintf (err_fh, "error: %s\n", error_cstr); + } + } + + // Check to see if they just invoked the debugger with a filename. + + + if (m_option_data.m_print_help) + { + ShowUsage (out_fh, g_options, m_option_data); + valid = false; + } + else if (m_option_data.m_print_version) + { + ::fprintf (out_fh, "%s\n", SBDebugger::GetVersionString()); + valid = false; + } + else if (! m_option_data.m_crash_log.empty()) + { + // Handle crash log stuff here. + } + else + { + // All other combinations are valid; do nothing more here. + } + + return valid; +} + +void +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; + while ((len = SBDebugger::GetCurrentTarget().GetProcess().GetSTDOUT (stdio_buffer, sizeof (stdio_buffer))) > 0) + m_io_channel_ap->OutWrite (stdio_buffer, len); +} + +void +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; + while ((len = SBDebugger::GetCurrentTarget().GetProcess().GetSTDERR (stdio_buffer, sizeof (stdio_buffer))) > 0) + m_io_channel_ap->ErrWrite (stdio_buffer, len); +} + +void +Driver::UpdateCurrentThread () +{ + using namespace lldb; + SBProcess process(SBDebugger::GetCurrentTarget().GetProcess()); + if (process.IsValid()) + { + SBThread curr_thread (process.GetCurrentThread()); + 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) + { + default: + case eStopReasonInvalid: + case eStopReasonNone: + break; + + case eStopReasonTrace: + case eStopReasonBreakpoint: + case eStopReasonWatchpoint: + case eStopReasonSignal: + case eStopReasonException: + if (!other_thread.IsValid()) + other_thread = thread; + break; + case eStopReasonPlanComplete: + if (!plan_thread.IsValid()) + plan_thread = thread; + break; + } + } + if (plan_thread.IsValid()) + process.SetCurrentThread (plan_thread); + else if (other_thread.IsValid()) + process.SetCurrentThread (other_thread); + else + { + if (curr_thread.IsValid()) + thread = curr_thread; + else + thread = process.GetThreadAtIndex(0); + + if (thread.IsValid()) + process.SetCurrentThread (thread); + } + } + } +} + + +// 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 eStateAttaching: + case eStateLaunching: + case eStateStepping: + case eStateDetached: + { + char message[1024]; + int message_len = ::snprintf (message, sizeof(message), "Process %d %s\n", process.GetProcessID(), + SBDebugger::StateAsCString (event_state)); + m_io_channel_ap->OutWrite(message, message_len); + } + break; + + case eStateRunning: + // Don't be chatty when we run... + break; + + case eStateExited: + SBDebugger::HandleCommand("status"); + m_io_channel_ap->RefreshPrompt(); + break; + + case eStateStopped: + case eStateCrashed: + case eStateSuspended: + // Make sure the program hasn't been auto-restarted: + if (SBProcess::GetRestartedFromEvent (event)) + { + // FIXME: Do we want to report this, or would that just be annoyingly chatty? + char message[1024]; + int message_len = ::snprintf (message, sizeof(message), "Process %d stopped and was programmatically restarted.\n", + process.GetProcessID()); + m_io_channel_ap->OutWrite(message, message_len); + } + else + { + UpdateCurrentThread (); + SBDebugger::HandleCommand("status"); + m_io_channel_ap->RefreshPrompt(); + } + break; + } + } +} + +// 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; + if (SBDebugger::GetCommandInterpreter().HandleCommand (command_string, result, true) != lldb::eReturnStatusQuit) + { + m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize()); + m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize()); + } + // 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; +} + + +//struct CrashImageInfo +//{ +// std::string path; +// VMRange text_range; +// UUID uuid; +//}; +// +//void +//Driver::ParseCrashLog (const char *crash_log) +//{ +// printf("Parsing crash log: %s\n", crash_log); +// +// char image_path[PATH_MAX]; +// std::vector<CrashImageInfo> crash_infos; +// if (crash_log && crash_log[0]) +// { +// FileSpec crash_log_file (crash_log); +// STLStringArray crash_log_lines; +// if (crash_log_file.ReadFileLines (crash_log_lines)) +// { +// const size_t num_crash_log_lines = crash_log_lines.size(); +// size_t i; +// for (i=0; i<num_crash_log_lines; ++i) +// { +// const char *line = crash_log_lines[i].c_str(); +// if (strstr (line, "Code Type:")) +// { +// char arch_string[256]; +// if (sscanf(line, "%s", arch_string)) +// { +// if (strcmp(arch_string, "X86-64")) +// lldb::GetDefaultArchitecture ().SetArch ("x86_64"); +// else if (strcmp(arch_string, "X86")) +// lldb::GetDefaultArchitecture ().SetArch ("i386"); +// else +// { +// ArchSpec arch(arch_string); +// if (arch.IsValid ()) +// lldb::GetDefaultArchitecture () = arch; +// else +// fprintf(stderr, "Unrecognized architecture: %s\n", arch_string); +// } +// } +// } +// else +// if (strstr(line, "Path:")) +// { +// const char *p = line + strlen("Path:"); +// while (isspace(*p)) +// ++p; +// +// m_option_data.m_filename.assign (p); +// } +// else +// if (strstr(line, "Binary Images:")) +// { +// while (++i < num_crash_log_lines) +// { +// if (crash_log_lines[i].empty()) +// break; +// +// line = crash_log_lines[i].c_str(); +// uint64_t text_start_addr; +// uint64_t text_end_addr; +// char uuid_cstr[64]; +// int bytes_consumed_before_uuid = 0; +// int bytes_consumed_after_uuid = 0; +// +// int items_parsed = ::sscanf (line, +// "%llx - %llx %*s %*s %*s %n%s %n", +// &text_start_addr, +// &text_end_addr, +// &bytes_consumed_before_uuid, +// uuid_cstr, +// &bytes_consumed_after_uuid); +// +// if (items_parsed == 3) +// { +// +// CrashImageInfo info; +// info.text_range.SetBaseAddress(text_start_addr); +// info.text_range.SetEndAddress(text_end_addr); +// +// if (uuid_cstr[0] == '<') +// { +// if (info.uuid.SetfromCString (&uuid_cstr[1]) == 0) +// info.uuid.Clear(); +// +// ::strncpy (image_path, line + bytes_consumed_after_uuid, sizeof(image_path)); +// } +// else +// { +// ::strncpy (image_path, line + bytes_consumed_before_uuid, sizeof(image_path)); +// } +// +// info.path = image_path; +// +// crash_infos.push_back (info); +// +// info.uuid.GetAsCString(uuid_cstr, sizeof(uuid_cstr)); +// +// printf("0x%16.16llx - 0x%16.16llx <%s> %s\n", +// text_start_addr, +// text_end_addr, +// uuid_cstr, +// image_path); +// } +// } +// } +// } +// } +// +// if (crash_infos.size()) +// { +// SBTarget target (SBDebugger::CreateTarget (crash_infos.front().path.c_str(), +// lldb::GetDefaultArchitecture().AsCString (), +// false)); +// if (target.IsValid()) +// { +// +// } +// } +// } +//} +// + +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 = SBDebugger::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: + driver->ReadyForCommand(); + break; + + case eInputReaderDeactivate: + 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); + } + } + + + // struct termios stdin_termios; + + if (::tcgetattr(STDIN_FILENO, &g_old_stdin_termios) == 0) + atexit (reset_stdin_termios); + + ::setbuf (stdin, NULL); + ::setbuf (stdout, NULL); + + SBDebugger::SetErrorFileHandle (stderr, false); + SBDebugger::SetOutputFileHandle (stdout, false); + SBDebugger::SetInputFileHandle (stdin, true); + + // 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.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); + } + +// const char *crash_log = GetCrashLogFilename(); +// if (crash_log) +// { +// ParseCrashLog (crash_log); +// } +// + SBCommandInterpreter sb_interpreter = SBDebugger::GetCommandInterpreter(); + + m_io_channel_ap.reset (new IOChannel(m_editline_slave_fh, stdout, stderr, this)); + + struct winsize window_size; + if (isatty (STDIN_FILENO) + && ::ioctl (STDIN_FILENO, TIOCGWINSZ, &window_size) == 0) + { + char buffer[25]; + + sprintf (buffer, "set term-width %d", window_size.ws_col); + SBDebugger::HandleCommand ((const char *) buffer); + } + + // 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 (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); + } + + SBDebugger::PushInputReader (m_editline_reader); + + SBListener listener(SBDebugger::GetListener()); + if (listener.IsValid()) + { + + 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); + + // 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 (SBDebugger::GetErrorFileHandle()); + result.PutOutput (SBDebugger::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(); + 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), "source '%s'", command_file); + SBDebugger::GetCommandInterpreter().HandleCommand (command_string, result, false); + if (GetDebugMode()) + { + result.PutError (SBDebugger::GetErrorFileHandle()); + result.PutOutput (SBDebugger::GetOutputFileHandle()); + } + } + } + + if (!m_option_data.m_filename.empty()) + { + char arch_name[64]; + if (SBDebugger::GetDefaultArchitecture (arch_name, sizeof (arch_name))) + ::snprintf (command_string, sizeof (command_string), "file --arch=%s '%s'", arch_name, + m_option_data.m_filename.c_str()); + else + ::snprintf (command_string, sizeof(command_string), "file '%s'", m_option_data.m_filename.c_str()); + + SBDebugger::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(SBDebugger::GetErrorFileHandle()); + result.PutOutput(SBDebugger::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); + + ReadyForCommand (); + + bool done = false; + while (!done) + { + 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)) + { + done = true; + if (event_type & IOChannel::eBroadcastBitThreadDidExit) + iochannel_thread_exited = true; + break; + } + else + done = HandleIOEvent (event); + } + else if (event.BroadcasterMatchesRef (SBDebugger::GetCurrentTarget().GetProcess().GetBroadcaster())) + { + HandleProcessEvent (event); + } + else if (event.BroadcasterMatchesRef (sb_interpreter.GetBroadcaster())) + { + if (event_type & SBCommandInterpreter::eBroadcastBitQuitCommandReceived) + done = true; + } + } + } + } + + reset_stdin_termios (); + + CloseIOChannelFile (); + + if (!iochannel_thread_exited) + { + SBEvent event; + 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(); + } + } + + SBProcess process = SBDebugger::GetCurrentTarget().GetProcess(); + if (process.IsValid()) + process.Destroy(); + } + } +} + + +void +Driver::ReadyForCommand () +{ + if (m_waiting_for_command == false) + { + m_waiting_for_command = true; + BroadcastEventByType (Driver::eBroadcastBitReadyForInput, true); + } +} + + +int +main (int argc, char const *argv[]) +{ + + SBDebugger::Initialize(); + + SBHostOS::ThreadCreated ("[main]"); + + // Do a little setup on the debugger before we get going + SBDebugger::SetAsync(true); + Driver driver; + + bool valid_args = driver.ParseArgs (argc, argv, stdout, stderr); + if (valid_args) + { + driver.MainLoop (); + } + + SBDebugger::Terminate(); + return 0; +} diff --git a/lldb/tools/driver/Driver.h b/lldb/tools/driver/Driver.h new file mode 100644 index 00000000000..ad82c989f1a --- /dev/null +++ b/lldb/tools/driver/Driver.h @@ -0,0 +1,156 @@ +//===-- 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 "PseudoTerminal.h" + +#include <set> +#include <bitset> +#include <string> +#include <vector> + +#include <LLDB/SBDefines.h> +#include <LLDB/SBBroadcaster.h> +#include <LLDB/SBError.h> +#include <LLDB/SBInputReader.h> + + +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); + + bool + ParseArgs (int argc, const char *argv[], FILE *out_fh, FILE *err_fh); + + 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 lldb::OptionDefinition m_cmd_option_table[]; + + std::string m_filename; + lldb::ScriptLanguage m_script_lang; + std::string m_crash_log; + std::vector<std::string> m_source_command_files; + bool m_debug_mode; + bool m_print_version; + bool m_print_help; + typedef std::set<char> OptionSet; + OptionSet m_seen_options; + }; + + + static lldb::SBError + SetOptionValue (int option_idx, + const char *option_arg, + Driver::OptionData &data); + + +private: + lldb_utility::PseudoTerminal m_editline_pty; + FILE *m_editline_slave_fh; + lldb::SBInputReader m_editline_reader; + std::auto_ptr<IOChannel> m_io_channel_ap; + OptionData m_option_data; + bool m_waiting_for_command; + + void + ResetOptionValues (); + + void + GetProcessSTDOUT (); + + void + GetProcessSTDERR (); + + void + UpdateCurrentThread (); + + 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/lldb/tools/driver/IOChannel.cpp b/lldb/tools/driver/IOChannel.cpp new file mode 100644 index 00000000000..a83e1b686e6 --- /dev/null +++ b/lldb/tools/driver/IOChannel.cpp @@ -0,0 +1,449 @@ +//===-- 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/SBCommandInterpreter.h> +#include <LLDB/SBDebugger.h> +#include <LLDB/SBError.h> +#include <LLDB/SBEvent.h> +#include <LLDB/SBFileSpec.h> +#include <LLDB/SBHostOS.h> +#include <LLDB/SBListener.h> +#include <LLDB/SBStringList.h> + +using namespace lldb; + +typedef std::map<EditLine *, std::string> PromptMap; +const char *g_default_prompt = "(lldb) "; +PromptMap g_prompt_map; + +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(); +} + +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; + } +} + +unsigned char +IOChannel::HandleCompletion (EditLine *e, int ch) +{ + assert (e == m_edit_line); + + const LineInfo *line_info = el_line(m_edit_line); + SBStringList completions; + size_t page_size = 40; + + int num_completions + = SBDebugger::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; + } + + // 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)); + if (num_completions < page_size) + { + for (int i = 1; i < num_elements; i++) + { + const char *completion_str = completions.GetStringAtIndex(i); + OutWrite("\n\t", 2); + OutWrite(completion_str, strlen (completion_str)); + } + OutWrite ("\n", 1); + } + 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++) + { + const char *completion_str = completions.GetStringAtIndex(cur_pos); + OutWrite("\n\t", 2); + OutWrite(completion_str, strlen (completion_str)); + } + + if (cur_pos >= num_elements) + { + OutWrite("\n", 1); + break; + } + + OutWrite("\nMore (Y/n/a): ", strlen ("\nMore (Y/n/a): ")); + 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 *in, + FILE *out, + FILE *err, + Driver *driver +) : + SBBroadcaster ("IOChannel"), + 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_edit_line (::el_init (SBHostOS::GetProgramFileSpec().GetFileName(), in, out, err)), + m_history (history_init()), + m_completion_key ("\t") +{ + 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); + + // Source $PWD/.editrc then $HOME/.editrc + ::el_source (m_edit_line, NULL); + + 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_CLIENTDATA, this); + + 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); +} + +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; + } +} + +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 (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); + } + } +} + +bool +IOChannel::LibeditGetInput (std::string &new_line) +{ + if (m_edit_line != NULL) + { + int line_len = 0; + const char *line = ::el_gets (m_edit_line, &line_len); + if (line) + { + // 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 + { + // Someone just hit ENTER, return the empty string + new_line.clear(); + } + // Return true to indicate success even if a string is empty + return true; + } + } + // Return false to indicate failure. This can happen when the file handle + // is closed (EOF). + new_line.clear(); + return false; +} + +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 (SBDebugger::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()) + { + if (LibeditGetInput(line) == false) + { + // EOF or some other file error occurred + 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; + break; + } + } + 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; + break; + } + } + } + } + BroadcastEventByType (IOChannel::eBroadcastBitThreadDidExit); + m_driver = NULL; + m_read_thread = NULL; +} + +bool +IOChannel::Start () +{ + if (m_read_thread != LLDB_INVALID_HOST_THREAD) + return true; + + m_read_thread = SBHostOS::ThreadCreate ("<lldb.driver.commandline_io>", IOChannel::IOReadThread, this, + NULL); + + return (m_read_thread != LLDB_INVALID_HOST_THREAD); +} + +bool +IOChannel::Stop () +{ + if (m_read_thread == NULL) + 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 () +{ + ::el_set (m_edit_line, EL_REFRESH); +} + +void +IOChannel::OutWrite (const char *buffer, size_t len) +{ + if (len == 0) + return; + ::fwrite (buffer, 1, len, m_out_file); +} + +void +IOChannel::ErrWrite (const char *buffer, size_t len) +{ + if (len == 0) + return; + ::fwrite (buffer, 1, len, m_err_file); +} + +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(); +} diff --git a/lldb/tools/driver/IOChannel.h b/lldb/tools/driver/IOChannel.h new file mode 100644 index 00000000000..dec7c82da29 --- /dev/null +++ b/lldb/tools/driver/IOChannel.h @@ -0,0 +1,113 @@ +//===-- 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> + +#include <editline/readline.h> +#include <histedit.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 + }; + + IOChannel (FILE *in, + 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); + + void + ErrWrite (const char *buffer, size_t len); + + bool + LibeditGetInput (std::string &); + + 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 (); + + static unsigned char ElCompletionFn (EditLine *e, int ch); + + bool + IsGettingCommand () const; + +private: + + 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; + void + HistorySaveLoad (bool save); + unsigned char HandleCompletion (EditLine *e, int ch); +}; + +#endif // lldb_IOChannel_h_ diff --git a/lldb/tools/driver/lldb-Info.plist b/lldb/tools/driver/lldb-Info.plist new file mode 100644 index 00000000000..7c1bfc734a7 --- /dev/null +++ b/lldb/tools/driver/lldb-Info.plist @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleIdentifier</key> + <string>com.apple.lldb</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>lldb</string> + <key>CFBundleVersion</key> + <string>2</string> + <key>SecTaskAccess</key> + <array> + <string>allowed</string> + <string>debug</string> + </array> +</dict> +</plist> |