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