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