| //===-- Driver.cpp ----------------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Driver.h" |
| |
| #include <getopt.h> |
| #include <libgen.h> |
| #include <sys/ioctl.h> |
| #include <termios.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <fcntl.h> |
| |
| #include <string> |
| |
| #include "IOChannel.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/SBSourceManager.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 struct termios g_old_stdin_termios; |
| |
| static char *g_debugger_name = (char *) ""; |
| |
| // 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[] = |
| { |
| { LLDB_OPT_SET_1, true, "help", 'h', no_argument, NULL, NULL, eArgTypeNone, |
| "Prints out the usage information for the LLDB debugger." }, |
| |
| { LLDB_OPT_SET_2, true, "version", 'v', no_argument, NULL, NULL, eArgTypeNone, |
| "Prints out the current version number of the LLDB debugger." }, |
| |
| { LLDB_OPT_SET_3, true, "arch", 'a', required_argument, NULL, NULL, eArgTypeArchitecture, |
| "Tells the debugger to use the specified architecture when starting and running the program. <architecture> must be one of the architectures for which the program was compiled." }, |
| |
| { LLDB_OPT_SET_3 | LLDB_OPT_SET_4, false, "script-language",'l', required_argument, NULL, NULL, 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_OPT_SET_3 | LLDB_OPT_SET_4, false, "debug", 'd', no_argument, NULL, NULL, eArgTypeNone, |
| "Tells the debugger to print out extra information for debugging itself." }, |
| |
| { LLDB_OPT_SET_3 | LLDB_OPT_SET_4, false, "source", 's', required_argument, NULL, NULL, eArgTypeFilename, |
| "Tells the debugger to read in and execute the file <file>, which should contain lldb commands." }, |
| |
| { LLDB_OPT_SET_3, true, "file", 'f', required_argument, NULL, NULL, eArgTypeFilename, |
| "Tells the debugger to use the file <filename> as the program to be debugged." }, |
| |
| { LLDB_OPT_SET_ALL, false, "editor", 'e', no_argument, NULL, NULL, eArgTypeNone, |
| "Tells the debugger to open source files using the host's \"external editor\" mechanism." }, |
| |
| { LLDB_OPT_SET_ALL, false, "no-lldbinit", 'n', no_argument, NULL, NULL, eArgTypeNone, |
| "Do not automatically parse any '.lldbinit' files." }, |
| |
| // { LLDB_OPT_SET_4, true, "crash-log", 'c', required_argument, NULL, NULL, eArgTypeFilename, |
| // "Load executable images from a crash log for symbolication." }, |
| |
| { 0, false, NULL, 0, 0, NULL, NULL, eArgTypeNone, NULL } |
| }; |
| |
| |
| Driver::Driver () : |
| SBBroadcaster ("Driver"), |
| m_debugger (SBDebugger::Create()), |
| m_editline_pty (), |
| m_editline_slave_fh (NULL), |
| m_editline_reader (), |
| m_io_channel_ap (), |
| m_option_data (), |
| m_waiting_for_command (false) |
| { |
| g_debugger_name = (char *) m_debugger.GetInstanceName(); |
| if (g_debugger_name == NULL) |
| g_debugger_name = (char *) ""; |
| } |
| |
| 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; 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 |
| GetArgumentName (const CommandArgumentType arg_type, std::string &arg_name) |
| { |
| //Fudge this function here, since we can't call the "real" version in lldb_private::CommandObject... |
| |
| switch (arg_type) |
| { |
| // Make cases for all the arg_types used in Driver.cpp |
| |
| case eArgTypeNone: |
| arg_name = ""; |
| break; |
| |
| case eArgTypeArchitecture: |
| arg_name = "architecture"; |
| break; |
| |
| case eArgTypeScriptLang: |
| arg_name = "script-language"; |
| break; |
| |
| case eArgTypeFilename: |
| arg_name = "filename"; |
| break; |
| } |
| return; |
| } |
| |
| |
| 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"; |
| |
| fprintf (out, "\nUsage:\n\n"); |
| |
| indent_level += 2; |
| |
| |
| // First, show each usage level set of options, e.g. <cmd> [options-for-level-0] |
| // <cmd> [options-for-level-1] |
| // etc. |
| |
| uint32_t num_options; |
| uint32_t num_option_sets = 0; |
| |
| for (num_options = 0; option_table[num_options].long_option != NULL; ++num_options) |
| { |
| uint32_t this_usage_mask = option_table[num_options].usage_mask; |
| if (this_usage_mask == LLDB_OPT_SET_ALL) |
| { |
| if (num_option_sets == 0) |
| num_option_sets = 1; |
| } |
| else |
| { |
| for (uint32_t j = 0; j < LLDB_MAX_NUM_OPTION_SETS; j++) |
| { |
| if (this_usage_mask & 1 << j) |
| { |
| if (num_option_sets <= j) |
| num_option_sets = j + 1; |
| } |
| } |
| } |
| } |
| |
| for (uint32_t opt_set = 0; opt_set < num_option_sets; opt_set++) |
| { |
| uint32_t opt_set_mask; |
| |
| opt_set_mask = 1 << opt_set; |
| |
| if (opt_set > 0) |
| fprintf (out, "\n"); |
| fprintf (out, "%*s%s", indent_level, "", name); |
| |
| 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; |
| std::string arg_name; |
| GetArgumentName (arg_type, arg_name); |
| 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.c_str()); |
| else if (option_table[i].option_has_arg == optional_argument) |
| fprintf (out, " -%c [<%s>]", option_table[i].short_option, arg_name.c_str()); |
| 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.c_str()); |
| else if (option_table[i].option_has_arg == optional_argument) |
| fprintf (out, " [-%c [<%s>]]", option_table[i].short_option, arg_name.c_str()); |
| 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 (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; |
| std::string arg_name; |
| GetArgumentName (arg_type, arg_name); |
| |
| 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.c_str()); |
| fprintf (out, "\n"); |
| fprintf (out, "%*s--%s ", indent_level, "", option_table[i].long_option); |
| if (arg_type != eArgTypeNone) |
| fprintf (out, "<%s>", arg_name.c_str()); |
| 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('%s <filename>' also works, to specify the file to be debugged.)\n\n", |
| indent_level, "", name); |
| } |
| |
| void |
| BuildGetOptTable (lldb::OptionDefinition *expanded_option_table, std::vector<struct option> &getopt_table, |
| uint32_t num_options) |
| { |
| if (num_options == 0) |
| return; |
| |
| uint32_t i; |
| uint32_t j; |
| std::bitset<256> option_seen; |
| |
| getopt_table.resize (num_options + 1); |
| |
| for (i = 0, j = 0; i < num_options; ++i) |
| { |
| char short_opt = expanded_option_table[i].short_option; |
| |
| if (option_seen.test(short_opt) == false) |
| { |
| getopt_table[j].name = expanded_option_table[i].long_option; |
| getopt_table[j].has_arg = expanded_option_table[i].option_has_arg; |
| getopt_table[j].flag = NULL; |
| getopt_table[j].val = expanded_option_table[i].short_option; |
| option_seen.set(short_opt); |
| ++j; |
| } |
| } |
| |
| getopt_table[j].name = NULL; |
| getopt_table[j].has_arg = 0; |
| getopt_table[j].flag = NULL; |
| getopt_table[j].val = 0; |
| |
| } |
| |
| Driver::OptionData::OptionData () : |
| m_filename(), |
| m_script_lang (lldb::eScriptLanguageDefault), |
| m_crash_log (), |
| m_source_command_files (), |
| m_debug_mode (false), |
| m_print_version (false), |
| m_print_help (false), |
| m_seen_options(), |
| m_use_external_editor(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; |
| m_use_external_editor = false; |
| } |
| |
| 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. |
| |
| SBError |
| Driver::ParseArgs (int argc, const char *argv[], FILE *out_fh, bool &exit) |
| { |
| ResetOptionValues (); |
| |
| SBCommandReturnObject result; |
| |
| SBError error; |
| std::string option_string; |
| struct option *long_options = NULL; |
| std::vector<struct option> long_options_vector; |
| uint32_t num_options; |
| |
| for (num_options = 0; g_options[num_options].long_option != NULL; ++num_options) |
| /* Do Nothing. */; |
| |
| if (num_options == 0) |
| { |
| if (argc > 1) |
| error.SetErrorStringWithFormat ("invalid number of options"); |
| return error; |
| } |
| |
| BuildGetOptTable (g_options, long_options_vector, num_options); |
| |
| if (long_options_vector.empty()) |
| long_options = NULL; |
| else |
| long_options = &long_options_vector.front(); |
| |
| if (long_options == NULL) |
| { |
| error.SetErrorStringWithFormat ("invalid long options"); |
| return error; |
| } |
| |
| // Build the option_string argument for call to getopt_long. |
| |
| 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. |
| #if __GLIBC__ |
| optind = 0; |
| #else |
| optreset = 1; |
| optind = 1; |
| #endif |
| int val; |
| while (1) |
| { |
| int long_options_index = -1; |
| val = ::getopt_long (argc, const_cast<char **>(argv), option_string.c_str(), long_options, &long_options_index); |
| |
| if (val == -1) |
| break; |
| else if (val == '?') |
| { |
| m_option_data.m_print_help = true; |
| error.SetErrorStringWithFormat ("unknown or ambiguous option"); |
| break; |
| } |
| else if (val == 0) |
| continue; |
| else |
| { |
| m_option_data.m_seen_options.insert ((char) val); |
| if (long_options_index == -1) |
| { |
| for (int i = 0; |
| long_options[i].name || long_options[i].has_arg || long_options[i].flag || long_options[i].val; |
| ++i) |
| { |
| if (long_options[i].val == val) |
| { |
| long_options_index = i; |
| break; |
| } |
| } |
| } |
| |
| if (long_options_index >= 0) |
| { |
| const char short_option = (char) 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 'c': |
| m_option_data.m_crash_log = optarg; |
| break; |
| |
| case 'e': |
| m_option_data.m_use_external_editor = true; |
| break; |
| |
| case 'n': |
| m_debugger.SkipLLDBInitFiles (true); |
| break; |
| |
| case 'f': |
| { |
| SBFileSpec file(optarg); |
| if (file.Exists()) |
| m_option_data.m_filename = optarg; |
| else if (file.ResolveExecutableLocation()) |
| { |
| char path[PATH_MAX]; |
| int path_len; |
| file.GetPath (path, path_len); |
| m_option_data.m_filename = 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 '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]; |
| size_t path_len; |
| file.GetPath (final_path, path_len); |
| 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 there is a trailing argument, it is the filename. |
| if (optind == argc - 1) |
| { |
| if (m_option_data.m_filename.empty()) |
| { |
| m_option_data.m_filename = argv[optind]; |
| } |
| else |
| { |
| error.SetErrorStringWithFormat ("error: don't provide a file both on in the -f option and as an argument."); |
| } |
| |
| } |
| else if (optind < argc - 1) |
| { |
| // Trailing extra arguments... |
| error.SetErrorStringWithFormat ("error: trailing extra arguments - only one the filename is allowed."); |
| } |
| |
| 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_crash_log.empty()) |
| { |
| // Handle crash log stuff here. |
| } |
| else |
| { |
| // All other combinations are valid; do nothing more here. |
| } |
| |
| 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); |
| 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); |
| 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) |
| { |
| 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.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::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. |
| if (GetProcessSTDOUT ()) |
| m_io_channel_ap->RefreshPrompt(); |
| } |
| else if (event_type & SBProcess::eBroadcastBitSTDERR) |
| { |
| // The process has stderr available, get it and write it out to the |
| // appropriate place. |
| if (GetProcessSTDERR ()) |
| m_io_channel_ap->RefreshPrompt(); |
| } |
| else if (event_type & SBProcess::eBroadcastBitStateChanged) |
| { |
| // Drain all stout and stderr so we don't see any output come after |
| // we print our prompts |
| if (GetProcessSTDOUT () |
| || GetProcessSTDERR ()) |
| m_io_channel_ap->RefreshPrompt(); |
| |
| // 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(), |
| m_debugger.StateAsCString (event_state)); |
| m_io_channel_ap->OutWrite(message, message_len); |
| } |
| 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()); |
| m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize()); |
| 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); |
| m_io_channel_ap->RefreshPrompt (); |
| } |
| else |
| { |
| SBCommandReturnObject result; |
| UpdateSelectedThread (); |
| m_debugger.GetCommandInterpreter().HandleCommand("process status", result, false); |
| m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize()); |
| m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize()); |
| 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 (m_debugger.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 (m_debugger.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 = 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: |
| 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); |
| |
| 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.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 = m_debugger.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) |
| { |
| 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.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 (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(); |
| 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 (!m_option_data.m_filename.empty()) |
| { |
| char arch_name[64]; |
| if (m_debugger.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()); |
| |
| 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); |
| |
| 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 (m_debugger.GetSelectedTarget().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) |
| { |
| 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(); |
| } |
| } |
| |
| SBProcess process = m_debugger.GetSelectedTarget().GetProcess(); |
| if (process.IsValid()) |
| process.Destroy(); |
| } |
| } |
| } |
| |
| |
| void |
| Driver::ReadyForCommand () |
| { |
| if (m_waiting_for_command == false) |
| { |
| m_waiting_for_command = true; |
| BroadcastEventByType (Driver::eBroadcastBitReadyForInput, true); |
| } |
| } |
| |
| |
| 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) && (strlen (g_debugger_name) > 0)) |
| { |
| char width_str_buffer[25]; |
| ::sprintf (width_str_buffer, "%d", window_size.ws_col); |
| SBDebugger::SetInternalVariable ("term-width", width_str_buffer, g_debugger_name); |
| } |
| } |
| } |
| |
| int |
| main (int argc, char const *argv[]) |
| { |
| SBDebugger::Initialize(); |
| |
| SBHostOS::ThreadCreated ("[main]"); |
| |
| signal (SIGPIPE, SIG_IGN); |
| signal (SIGWINCH, sigwinch_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; |
| } |