diff options
Diffstat (limited to 'lldb/tools/driver/Driver.cpp')
-rw-r--r-- | lldb/tools/driver/Driver.cpp | 1265 |
1 files changed, 1265 insertions, 0 deletions
diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp new file mode 100644 index 00000000000..8cca697dd27 --- /dev/null +++ b/lldb/tools/driver/Driver.cpp @@ -0,0 +1,1265 @@ +//===-- Driver.cpp ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Driver.h" + +#include <getopt.h> +#include <libgen.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <unistd.h> + +#include <string> + +#include "IOChannel.h" +#include <LLDB/SBCommandInterpreter.h> +#include <LLDB/SBCommandReturnObject.h> +#include <LLDB/SBCommunication.h> +#include <LLDB/SBDebugger.h> +#include <LLDB/SBEvent.h> +#include <LLDB/SBHostOS.h> +#include <LLDB/SBListener.h> +#include <LLDB/SBSourceManager.h> +#include <LLDB/SBTarget.h> +#include <LLDB/SBThread.h> +#include <LLDB/SBProcess.h> + +using namespace lldb; + +static void reset_stdin_termios (); +static struct termios g_old_stdin_termios; + +// In the Driver::MainLoop, we change the terminal settings. This function is +// added as an atexit handler to make sure we clean them up. +static void +reset_stdin_termios () +{ + ::tcsetattr (STDIN_FILENO, TCSANOW, &g_old_stdin_termios); +} + +static lldb::OptionDefinition g_options[] = +{ + { 0, true, "help", 'h', no_argument, NULL, NULL, NULL, + "Prints out the usage information for the LLDB debugger." }, + + { 1, true, "version", 'v', no_argument, NULL, NULL, NULL, + "Prints out the current version number of the LLDB debugger." }, + + { 2, false, "file", 'f', required_argument, NULL, NULL, "<filename>", + "Tells the debugger to use the file <filename> as the program to be debugged." }, + + { 2, false, "arch", 'a', required_argument, NULL, NULL, "<architecture>", + "Tells the debugger to use the specified architecture when starting and running the program. <architecture> must be one of the architectures for which the program was compiled." }, + + { 2, false, "script-language",'l', required_argument, NULL, NULL, "<scripting-language>", + "Tells the debugger to use the specified scripting language for user-defined scripts, rather than the default. Valid scripting languages that can be specified include Python, Perl, Ruby and Tcl. Currently only the Python extensions have been implemented." }, + + { 2, false, "debug", 'd', no_argument, NULL, NULL, NULL, + "Tells the debugger to print out extra information for debugging itself." }, + + { 2, false, "source", 's', required_argument, NULL, NULL, "<file>", + "Tells the debugger to read in and execute the file <file>, which should contain lldb commands." }, + + { 3, false, "crash-log", 'c', required_argument, NULL, NULL, "<file>", + "Load executable images from a crash log for symbolication." }, + + { 0, false, NULL, 0, 0, NULL, NULL, NULL, NULL } +}; + + +Driver::Driver () : + SBBroadcaster ("Driver"), + m_editline_pty (), + m_editline_slave_fh (NULL), + m_editline_reader (), + m_io_channel_ap (), + m_option_data (), + m_waiting_for_command (false) +{ +} + +Driver::~Driver () +{ +} + +void +Driver::CloseIOChannelFile () +{ + // Write and End of File sequence to the file descriptor to ensure any + // read functions can exit. + char eof_str[] = "\x04"; + ::write (m_editline_pty.GetMasterFileDescriptor(), eof_str, strlen(eof_str)); + + m_editline_pty.CloseMasterFileDescriptor(); + + if (m_editline_slave_fh) + { + ::fclose (m_editline_slave_fh); + m_editline_slave_fh = NULL; + } +} + +// This function takes INDENT, which tells how many spaces to output at the front of each line; SPACES, which is +// a string that is output_max_columns long, containing spaces; and TEXT, which is the text that is to be output. +// It outputs the text, on multiple lines if necessary, to RESULT, with INDENT spaces at the front of each line. It +// breaks lines on spaces, tabs or newlines, shortening the line if necessary to not break in the middle of a word. +// It assumes that each output line should contain a maximum of OUTPUT_MAX_COLUMNS characters. + +void +OutputFormattedUsageText (FILE *out, int indent, char *spaces, const char *text, int output_max_columns) +{ + int len = strlen (text); + std::string text_string (text); + std::string spaces_string (spaces); + + // Force indentation to be reasonable. + if (indent >= output_max_columns) + indent = 0; + + // Will it all fit on one line? + + if (len + indent < output_max_columns) + // Output as a single line + fprintf (out, "%s%s\n", spaces_string.substr (0, indent).c_str(), text); + else + { + // We need to break it up into multiple lines. + int text_width = output_max_columns - indent - 1; + int start = 0; + int end = start; + int final_end = len; + int sub_len; + + while (end < final_end) + { + // Dont start the 'text' on a space, since we're already outputting the indentation. + while ((start < final_end) && (text[start] == ' ')) + start++; + + end = start + text_width; + if (end > final_end) + end = final_end; + else + { + // If we're not at the end of the text, make sure we break the line on white space. + while (end > start + && text[end] != ' ' && text[end] != '\t' && text[end] != '\n') + end--; + } + sub_len = end - start; + std::string substring = text_string.substr (start, sub_len); + fprintf (out, "%s%s\n", spaces_string.substr(0, indent).c_str(), substring.c_str()); + start = end + 1; + } + } +} + +void +ShowUsage (FILE *out, lldb::OptionDefinition *option_table, Driver::OptionData data) +{ + uint32_t screen_width = 80; + uint32_t indent_level = 0; + const char *name = "lldb"; + char spaces[screen_width+1]; + uint32_t i; + + for (i = 0; i < screen_width; ++i) + spaces[i] = ' '; + spaces[i] = '\n'; + + std::string spaces_string (spaces); + + fprintf (out, "\nUsage:\n\n"); + + indent_level += 2; + + + // First, show each usage level set of options, e.g. <cmd> [options-for-level-0] + // <cmd> [options-for-level-1] + // etc. + + uint32_t usage_level = 0; + uint32_t num_options; + + for (num_options = 0; option_table[num_options].long_option != NULL; ++num_options); + + for (i = 0; i < num_options; ++i) + { + if (i == 0 || option_table[i].usage_level > usage_level) + { + // Start a new level. + usage_level = option_table[i].usage_level; + if (usage_level > 0) + fprintf (out, "\n\n"); + fprintf (out, "%s%s", spaces_string.substr(0, indent_level).c_str(), name); + } + + if (option_table[i].required) + { + if (option_table[i].option_has_arg == required_argument) + fprintf (out, " -%c %s", option_table[i].short_option, option_table[i].argument_name); + else if (option_table[i].option_has_arg == optional_argument) + fprintf (out, " -%c [%s]", option_table[i].short_option, option_table[i].argument_name); + else + fprintf (out, " -%c", option_table[i].short_option); + } + else + { + if (option_table[i].option_has_arg == required_argument) + fprintf (out, " [-%c %s]", option_table[i].short_option, option_table[i].argument_name); + else if (option_table[i].option_has_arg == optional_argument) + fprintf (out, " [-%c [%s]]", option_table[i].short_option, option_table[i].argument_name); + else + fprintf (out, " [-%c]", option_table[i].short_option); + } + } + + fprintf (out, "\n\n"); + + // Now print out all the detailed information about the various options: long form, short form and help text: + // -- long_name <argument> + // - short <argument> + // help text + + // This variable is used to keep track of which options' info we've printed out, because some options can be in + // more than one usage level, but we only want to print the long form of its information once. + + Driver::OptionData::OptionSet options_seen; + Driver::OptionData::OptionSet::iterator pos; + + indent_level += 5; + + for (i = 0; i < num_options; ++i) + { + // Only print this option if we haven't already seen it. + pos = options_seen.find (option_table[i].short_option); + if (pos == options_seen.end()) + { + options_seen.insert (option_table[i].short_option); + fprintf (out, "%s-%c ", spaces_string.substr(0, indent_level).c_str(), option_table[i].short_option); + if (option_table[i].argument_name != NULL) + fprintf (out, "%s", option_table[i].argument_name); + fprintf (out, "\n"); + fprintf (out, "%s--%s ", spaces_string.substr(0, indent_level).c_str(), option_table[i].long_option); + if (option_table[i].argument_name != NULL) + fprintf (out, "%s", option_table[i].argument_name); + fprintf (out, "\n"); + indent_level += 5; + OutputFormattedUsageText (out, indent_level, spaces, option_table[i].usage_text, screen_width); + indent_level -= 5; + fprintf (out, "\n"); + } + } + + indent_level -= 5; + + fprintf (out, "\n%s('%s <filename>' also works, to specify the file to be debugged.)\n\n", + spaces_string.substr(0, indent_level).c_str(), name); +} + +void +BuildGetOptTable (lldb::OptionDefinition *expanded_option_table, struct option **getopt_table, int num_options) +{ + if (num_options == 0) + return; + + uint32_t i; + uint32_t j; + std::bitset<256> option_seen; + + for (i = 0, j = 0; i < num_options; ++i) + { + char short_opt = expanded_option_table[i].short_option; + + if (option_seen.test(short_opt) == false) + { + (*getopt_table)[j].name = expanded_option_table[i].long_option; + (*getopt_table)[j].has_arg = expanded_option_table[i].option_has_arg; + (*getopt_table)[j].flag = NULL; + (*getopt_table)[j].val = expanded_option_table[i].short_option; + option_seen.set(short_opt); + ++j; + } + } + + (*getopt_table)[j].name = NULL; + (*getopt_table)[j].has_arg = 0; + (*getopt_table)[j].flag = NULL; + (*getopt_table)[j].val = 0; + +} + +SBError +ParseOptions (Driver::OptionData &data, int argc, const char **argv) +{ + SBError error; + std::string option_string; + struct option *long_options = NULL; + int num_options; + + for (num_options = 0; g_options[num_options].long_option != NULL; ++num_options); + + if (num_options == 0) + { + if (argc > 1) + error.SetErrorStringWithFormat ("invalid number of options"); + return error; + } + + long_options = (struct option *) malloc ((num_options + 1) * sizeof (struct option)); + + BuildGetOptTable (g_options, &long_options, num_options); + + if (long_options == NULL) + { + error.SetErrorStringWithFormat ("invalid long options"); + return error; + } + + // Build the option_string argument for call to getopt_long. + + for (int i = 0; long_options[i].name != NULL; ++i) + { + if (long_options[i].flag == NULL) + { + option_string.push_back ((char) long_options[i].val); + switch (long_options[i].has_arg) + { + default: + case no_argument: + break; + case required_argument: + option_string.push_back (':'); + break; + case optional_argument: + option_string.append ("::"); + break; + } + } + } + + // Prepare for & make calls to getopt_long. + + optreset = 1; + optind = 1; + int val; + while (1) + { + int long_options_index = -1; + val = ::getopt_long (argc, (char * const *) argv, option_string.c_str(), long_options, &long_options_index); + + if (val == -1) + break; + else if (val == '?') + { + data.m_print_help = true; + error.SetErrorStringWithFormat ("unknown or ambiguous option"); + break; + } + else if (val == 0) + continue; + else + { + data.m_seen_options.insert ((char) val); + if (long_options_index == -1) + { + for (int i = 0; + long_options[i].name || long_options[i].has_arg || long_options[i].flag || long_options[i].val; + ++i) + { + if (long_options[i].val == val) + { + long_options_index = i; + break; + } + } + } + + if (long_options_index >= 0) + { + error = Driver::SetOptionValue (long_options_index, + long_options[long_options_index].has_arg == no_argument ? NULL : optarg, + data); + } + else + { + error.SetErrorStringWithFormat ("invalid option with value %i", val); + } + if (error.Fail()) + break; + } + } + + return error; +} + +Driver::OptionData::OptionData () : + m_filename(), + m_script_lang (lldb::eScriptLanguageDefault), + m_source_command_files (), + m_debug_mode (false), + m_print_help (false), + m_print_version (false) + +{ +} + +Driver::OptionData::~OptionData () +{ +} + +void +Driver::OptionData::Clear () +{ + m_filename.clear (); + m_script_lang = lldb::eScriptLanguageDefault; + m_source_command_files.clear (); + m_debug_mode = false; + m_print_help = false; + m_print_version = false; +} + +SBError +Driver::SetOptionValue (int option_idx, const char *option_arg, Driver::OptionData &option_data) +{ + SBError error; + const char short_option = (char) g_options[option_idx].short_option; + + switch (short_option) + { + case 'h': + option_data.m_print_help = true; + break; + + case 'v': + option_data.m_print_version = true; + break; + + case 'c': + option_data.m_crash_log = option_arg; + break; + + case 'f': + { + SBFileSpec file(option_arg); + if (file.Exists()) + option_data.m_filename = option_arg; + else + error.SetErrorStringWithFormat("file specified in --file (-f) option doesn't exist: '%s'", option_arg); + } + break; + + case 'a': + if (!SBDebugger::SetDefaultArchitecture (option_arg)) + error.SetErrorStringWithFormat("invalid architecture in the -a or --arch option: '%s'", option_arg); + break; + + case 'l': + option_data.m_script_lang = SBDebugger::GetScriptingLanguage (option_arg); + break; + + case 'd': + option_data.m_debug_mode = true; + break; + + case 's': + { + SBFileSpec file(option_arg); + if (file.Exists()) + option_data.m_source_command_files.push_back (option_arg); + else + error.SetErrorStringWithFormat("file specified in --source (-s) option doesn't exist: '%s'", option_arg); + } + break; + + default: + option_data.m_print_help = true; + error.SetErrorStringWithFormat ("unrecognized option %c", short_option); + break; + } + + return error; +} + +void +Driver::ResetOptionValues () +{ + m_option_data.Clear (); +} + +const char * +Driver::GetFilename() const +{ + if (m_option_data.m_filename.empty()) + return NULL; + return m_option_data.m_filename.c_str(); +} + +const char * +Driver::GetCrashLogFilename() const +{ + if (m_option_data.m_crash_log.empty()) + return NULL; + return m_option_data.m_crash_log.c_str(); +} + +lldb::ScriptLanguage +Driver::GetScriptLanguage() const +{ + return m_option_data.m_script_lang; +} + +size_t +Driver::GetNumSourceCommandFiles () const +{ + return m_option_data.m_source_command_files.size(); +} + +const char * +Driver::GetSourceCommandFileAtIndex (uint32_t idx) const +{ + if (idx < m_option_data.m_source_command_files.size()) + return m_option_data.m_source_command_files[idx].c_str(); + return NULL; +} + +bool +Driver::GetDebugMode() const +{ + return m_option_data.m_debug_mode; +} + + +// Check the arguments that were passed to this program to make sure they are valid and to get their +// argument values (if any). Return a boolean value indicating whether or not to start up the full +// debugger (i.e. the Command Interpreter) or not. Return FALSE if the arguments were invalid OR +// if the user only wanted help or version information. + +bool +Driver::ParseArgs (int argc, const char *argv[], FILE *out_fh, FILE *err_fh) +{ + bool valid = true; + + ResetOptionValues (); + + if (argc == 2 && *(argv[1]) != '-') + { + m_option_data.m_filename = argv[1]; + } + else + { + SBCommandReturnObject result; + + SBError error = ParseOptions (m_option_data, argc, argv); + if (error.Fail()) + { + const char *error_cstr = error.GetCString (); + if (error_cstr) + ::fprintf (err_fh, "error: %s\n", error_cstr); + } + } + + // Check to see if they just invoked the debugger with a filename. + + + if (m_option_data.m_print_help) + { + ShowUsage (out_fh, g_options, m_option_data); + valid = false; + } + else if (m_option_data.m_print_version) + { + ::fprintf (out_fh, "%s\n", SBDebugger::GetVersionString()); + valid = false; + } + else if (! m_option_data.m_crash_log.empty()) + { + // Handle crash log stuff here. + } + else + { + // All other combinations are valid; do nothing more here. + } + + return valid; +} + +void +Driver::GetProcessSTDOUT () +{ + // The process has stuff waiting for stdout; get it and write it out to the appropriate place. + char stdio_buffer[1024]; + size_t len; + while ((len = SBDebugger::GetCurrentTarget().GetProcess().GetSTDOUT (stdio_buffer, sizeof (stdio_buffer))) > 0) + m_io_channel_ap->OutWrite (stdio_buffer, len); +} + +void +Driver::GetProcessSTDERR () +{ + // The process has stuff waiting for stderr; get it and write it out to the appropriate place. + char stdio_buffer[1024]; + size_t len; + while ((len = SBDebugger::GetCurrentTarget().GetProcess().GetSTDERR (stdio_buffer, sizeof (stdio_buffer))) > 0) + m_io_channel_ap->ErrWrite (stdio_buffer, len); +} + +void +Driver::UpdateCurrentThread () +{ + using namespace lldb; + SBProcess process(SBDebugger::GetCurrentTarget().GetProcess()); + if (process.IsValid()) + { + SBThread curr_thread (process.GetCurrentThread()); + SBThread thread; + StopReason curr_thread_stop_reason = eStopReasonInvalid; + curr_thread_stop_reason = curr_thread.GetStopReason(); + + if (!curr_thread.IsValid() || + curr_thread_stop_reason == eStopReasonInvalid || + curr_thread_stop_reason == eStopReasonNone) + { + // Prefer a thread that has just completed its plan over another thread as current thread. + SBThread plan_thread; + SBThread other_thread; + const size_t num_threads = process.GetNumThreads(); + size_t i; + for (i = 0; i < num_threads; ++i) + { + thread = process.GetThreadAtIndex(i); + StopReason thread_stop_reason = thread.GetStopReason(); + switch (thread_stop_reason) + { + default: + case eStopReasonInvalid: + case eStopReasonNone: + break; + + case eStopReasonTrace: + case eStopReasonBreakpoint: + case eStopReasonWatchpoint: + case eStopReasonSignal: + case eStopReasonException: + if (!other_thread.IsValid()) + other_thread = thread; + break; + case eStopReasonPlanComplete: + if (!plan_thread.IsValid()) + plan_thread = thread; + break; + } + } + if (plan_thread.IsValid()) + process.SetCurrentThread (plan_thread); + else if (other_thread.IsValid()) + process.SetCurrentThread (other_thread); + else + { + if (curr_thread.IsValid()) + thread = curr_thread; + else + thread = process.GetThreadAtIndex(0); + + if (thread.IsValid()) + process.SetCurrentThread (thread); + } + } + } +} + + +// This function handles events that were broadcast by the process. +void +Driver::HandleProcessEvent (const SBEvent &event) +{ + using namespace lldb; + const uint32_t event_type = event.GetType(); + + if (event_type & SBProcess::eBroadcastBitSTDOUT) + { + // The process has stdout available, get it and write it out to the + // appropriate place. + GetProcessSTDOUT (); + } + else if (event_type & SBProcess::eBroadcastBitSTDERR) + { + // The process has stderr available, get it and write it out to the + // appropriate place. + GetProcessSTDERR (); + } + else if (event_type & SBProcess::eBroadcastBitStateChanged) + { + // Drain all stout and stderr so we don't see any output come after + // we print our prompts + GetProcessSTDOUT (); + GetProcessSTDERR (); + + // Something changed in the process; get the event and report the process's current status and location to + // the user. + StateType event_state = SBProcess::GetStateFromEvent (event); + if (event_state == eStateInvalid) + return; + + SBProcess process (SBProcess::GetProcessFromEvent (event)); + assert (process.IsValid()); + + switch (event_state) + { + case eStateInvalid: + case eStateUnloaded: + case eStateAttaching: + case eStateLaunching: + case eStateStepping: + case eStateDetached: + { + char message[1024]; + int message_len = ::snprintf (message, sizeof(message), "Process %d %s\n", process.GetProcessID(), + SBDebugger::StateAsCString (event_state)); + m_io_channel_ap->OutWrite(message, message_len); + } + break; + + case eStateRunning: + // Don't be chatty when we run... + break; + + case eStateExited: + SBDebugger::HandleCommand("status"); + m_io_channel_ap->RefreshPrompt(); + break; + + case eStateStopped: + case eStateCrashed: + case eStateSuspended: + // Make sure the program hasn't been auto-restarted: + if (SBProcess::GetRestartedFromEvent (event)) + { + // FIXME: Do we want to report this, or would that just be annoyingly chatty? + char message[1024]; + int message_len = ::snprintf (message, sizeof(message), "Process %d stopped and was programmatically restarted.\n", + process.GetProcessID()); + m_io_channel_ap->OutWrite(message, message_len); + } + else + { + UpdateCurrentThread (); + SBDebugger::HandleCommand("status"); + m_io_channel_ap->RefreshPrompt(); + } + break; + } + } +} + +// This function handles events broadcast by the IOChannel (HasInput, UserInterrupt, or ThreadShouldExit). + +bool +Driver::HandleIOEvent (const SBEvent &event) +{ + bool quit = false; + + const uint32_t event_type = event.GetType(); + + if (event_type & IOChannel::eBroadcastBitHasUserInput) + { + // We got some input (i.e. a command string) from the user; pass it off to the command interpreter for + // handling. + + const char *command_string = SBEvent::GetCStringFromEvent(event); + if (command_string == NULL) + command_string == ""; + SBCommandReturnObject result; + if (SBDebugger::GetCommandInterpreter().HandleCommand (command_string, result, true) != lldb::eReturnStatusQuit) + { + m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize()); + m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize()); + } + // We are done getting and running our command, we can now clear the + // m_waiting_for_command so we can get another one. + m_waiting_for_command = false; + + // If our editline input reader is active, it means another input reader + // got pushed onto the input reader and caused us to become deactivated. + // When the input reader above us gets popped, we will get re-activated + // and our prompt will refresh in our callback + if (m_editline_reader.IsActive()) + { + ReadyForCommand (); + } + } + else if (event_type & IOChannel::eBroadcastBitUserInterrupt) + { + // This is here to handle control-c interrupts from the user. It has not yet really been implemented. + // TO BE DONE: PROPERLY HANDLE CONTROL-C FROM USER + //m_io_channel_ap->CancelInput(); + // Anything else? Send Interrupt to process? + } + else if ((event_type & IOChannel::eBroadcastBitThreadShouldExit) || + (event_type & IOChannel::eBroadcastBitThreadDidExit)) + { + // If the IOChannel thread is trying to go away, then it is definitely + // time to end the debugging session. + quit = true; + } + + return quit; +} + + +//struct CrashImageInfo +//{ +// std::string path; +// VMRange text_range; +// UUID uuid; +//}; +// +//void +//Driver::ParseCrashLog (const char *crash_log) +//{ +// printf("Parsing crash log: %s\n", crash_log); +// +// char image_path[PATH_MAX]; +// std::vector<CrashImageInfo> crash_infos; +// if (crash_log && crash_log[0]) +// { +// FileSpec crash_log_file (crash_log); +// STLStringArray crash_log_lines; +// if (crash_log_file.ReadFileLines (crash_log_lines)) +// { +// const size_t num_crash_log_lines = crash_log_lines.size(); +// size_t i; +// for (i=0; i<num_crash_log_lines; ++i) +// { +// const char *line = crash_log_lines[i].c_str(); +// if (strstr (line, "Code Type:")) +// { +// char arch_string[256]; +// if (sscanf(line, "%s", arch_string)) +// { +// if (strcmp(arch_string, "X86-64")) +// lldb::GetDefaultArchitecture ().SetArch ("x86_64"); +// else if (strcmp(arch_string, "X86")) +// lldb::GetDefaultArchitecture ().SetArch ("i386"); +// else +// { +// ArchSpec arch(arch_string); +// if (arch.IsValid ()) +// lldb::GetDefaultArchitecture () = arch; +// else +// fprintf(stderr, "Unrecognized architecture: %s\n", arch_string); +// } +// } +// } +// else +// if (strstr(line, "Path:")) +// { +// const char *p = line + strlen("Path:"); +// while (isspace(*p)) +// ++p; +// +// m_option_data.m_filename.assign (p); +// } +// else +// if (strstr(line, "Binary Images:")) +// { +// while (++i < num_crash_log_lines) +// { +// if (crash_log_lines[i].empty()) +// break; +// +// line = crash_log_lines[i].c_str(); +// uint64_t text_start_addr; +// uint64_t text_end_addr; +// char uuid_cstr[64]; +// int bytes_consumed_before_uuid = 0; +// int bytes_consumed_after_uuid = 0; +// +// int items_parsed = ::sscanf (line, +// "%llx - %llx %*s %*s %*s %n%s %n", +// &text_start_addr, +// &text_end_addr, +// &bytes_consumed_before_uuid, +// uuid_cstr, +// &bytes_consumed_after_uuid); +// +// if (items_parsed == 3) +// { +// +// CrashImageInfo info; +// info.text_range.SetBaseAddress(text_start_addr); +// info.text_range.SetEndAddress(text_end_addr); +// +// if (uuid_cstr[0] == '<') +// { +// if (info.uuid.SetfromCString (&uuid_cstr[1]) == 0) +// info.uuid.Clear(); +// +// ::strncpy (image_path, line + bytes_consumed_after_uuid, sizeof(image_path)); +// } +// else +// { +// ::strncpy (image_path, line + bytes_consumed_before_uuid, sizeof(image_path)); +// } +// +// info.path = image_path; +// +// crash_infos.push_back (info); +// +// info.uuid.GetAsCString(uuid_cstr, sizeof(uuid_cstr)); +// +// printf("0x%16.16llx - 0x%16.16llx <%s> %s\n", +// text_start_addr, +// text_end_addr, +// uuid_cstr, +// image_path); +// } +// } +// } +// } +// } +// +// if (crash_infos.size()) +// { +// SBTarget target (SBDebugger::CreateTarget (crash_infos.front().path.c_str(), +// lldb::GetDefaultArchitecture().AsCString (), +// false)); +// if (target.IsValid()) +// { +// +// } +// } +// } +//} +// + +void +Driver::MasterThreadBytesReceived (void *baton, const void *src, size_t src_len) +{ + Driver *driver = (Driver*)baton; + driver->GetFromMaster ((const char *)src, src_len); +} + +void +Driver::GetFromMaster (const char *src, size_t src_len) +{ + // Echo the characters back to the Debugger's stdout, that way if you + // type characters while a command is running, you'll see what you've typed. + FILE *out_fh = SBDebugger::GetOutputFileHandle(); + if (out_fh) + ::fwrite (src, 1, src_len, out_fh); +} + +size_t +Driver::EditLineInputReaderCallback +( + void *baton, + SBInputReader *reader, + InputReaderAction notification, + const char *bytes, + size_t bytes_len +) +{ + Driver *driver = (Driver *)baton; + + switch (notification) + { + case eInputReaderActivate: + break; + + case eInputReaderReactivate: + driver->ReadyForCommand(); + break; + + case eInputReaderDeactivate: + break; + + case eInputReaderGotToken: + write (driver->m_editline_pty.GetMasterFileDescriptor(), bytes, bytes_len); + break; + + case eInputReaderDone: + break; + } + return bytes_len; +} + +void +Driver::MainLoop () +{ + char error_str[1024]; + if (m_editline_pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY, error_str, sizeof(error_str)) == false) + { + ::fprintf (stderr, "error: failed to open driver pseudo terminal : %s", error_str); + exit(1); + } + else + { + const char *driver_slave_name = m_editline_pty.GetSlaveName (error_str, sizeof(error_str)); + if (driver_slave_name == NULL) + { + ::fprintf (stderr, "error: failed to get slave name for driver pseudo terminal : %s", error_str); + exit(2); + } + else + { + m_editline_slave_fh = ::fopen (driver_slave_name, "r+"); + if (m_editline_slave_fh == NULL) + { + SBError error; + error.SetErrorToErrno(); + ::fprintf (stderr, "error: failed to get open slave for driver pseudo terminal : %s", + error.GetCString()); + exit(3); + } + + ::setbuf (m_editline_slave_fh, NULL); + } + } + + + // struct termios stdin_termios; + + if (::tcgetattr(STDIN_FILENO, &g_old_stdin_termios) == 0) + atexit (reset_stdin_termios); + + ::setbuf (stdin, NULL); + ::setbuf (stdout, NULL); + + SBDebugger::SetErrorFileHandle (stderr, false); + SBDebugger::SetOutputFileHandle (stdout, false); + SBDebugger::SetInputFileHandle (stdin, true); + + // You have to drain anything that comes to the master side of the PTY. master_out_comm is + // for that purpose. The reason you need to do this is a curious reason... editline will echo + // characters to the PTY when it gets characters while el_gets is not running, and then when + // you call el_gets (or el_getc) it will try to reset the terminal back to raw mode which blocks + // if there are unconsumed characters in the out buffer. + // However, you don't need to do anything with the characters, since editline will dump these + // unconsumed characters after printing the prompt again in el_gets. + + SBCommunication master_out_comm("driver.editline"); + master_out_comm.AdoptFileDesriptor(m_editline_pty.GetMasterFileDescriptor(), false); + master_out_comm.SetReadThreadBytesReceivedCallback(Driver::MasterThreadBytesReceived, this); + + if (master_out_comm.ReadThreadStart () == false) + { + ::fprintf (stderr, "error: failed to start master out read thread"); + exit(5); + } + +// const char *crash_log = GetCrashLogFilename(); +// if (crash_log) +// { +// ParseCrashLog (crash_log); +// } +// + SBCommandInterpreter sb_interpreter = SBDebugger::GetCommandInterpreter(); + + m_io_channel_ap.reset (new IOChannel(m_editline_slave_fh, stdout, stderr, this)); + + struct winsize window_size; + if (isatty (STDIN_FILENO) + && ::ioctl (STDIN_FILENO, TIOCGWINSZ, &window_size) == 0) + { + char buffer[25]; + + sprintf (buffer, "set term-width %d", window_size.ws_col); + SBDebugger::HandleCommand ((const char *) buffer); + } + + // Since input can be redirected by the debugger, we must insert our editline + // input reader in the queue so we know when our reader should be active + // and so we can receive bytes only when we are supposed to. + SBError err (m_editline_reader.Initialize (Driver::EditLineInputReaderCallback, // callback + this, // baton + eInputReaderGranularityByte, // token_size + NULL, // end token - NULL means never done + NULL, // prompt - taken care of elsewhere + false)); // echo input - don't need Debugger + // to do this, we handle it elsewhere + + if (err.Fail()) + { + ::fprintf (stderr, "error: %s", err.GetCString()); + exit (6); + } + + SBDebugger::PushInputReader (m_editline_reader); + + SBListener listener(SBDebugger::GetListener()); + if (listener.IsValid()) + { + + listener.StartListeningForEvents (*m_io_channel_ap, + IOChannel::eBroadcastBitHasUserInput | + IOChannel::eBroadcastBitUserInterrupt | + IOChannel::eBroadcastBitThreadShouldExit | + IOChannel::eBroadcastBitThreadDidStart | + IOChannel::eBroadcastBitThreadDidExit); + + if (m_io_channel_ap->Start ()) + { + bool iochannel_thread_exited = false; + + listener.StartListeningForEvents (sb_interpreter.GetBroadcaster(), + SBCommandInterpreter::eBroadcastBitQuitCommandReceived); + + // Before we handle any options from the command line, we parse the + // .lldbinit file in the user's home directory. + SBCommandReturnObject result; + sb_interpreter.SourceInitFileInHomeDirectory(result); + if (GetDebugMode()) + { + result.PutError (SBDebugger::GetErrorFileHandle()); + result.PutOutput (SBDebugger::GetOutputFileHandle()); + } + + // Now we handle options we got from the command line + char command_string[PATH_MAX * 2]; + const size_t num_source_command_files = GetNumSourceCommandFiles(); + if (num_source_command_files > 0) + { + for (size_t i=0; i < num_source_command_files; ++i) + { + const char *command_file = GetSourceCommandFileAtIndex(i); + ::snprintf (command_string, sizeof(command_string), "source '%s'", command_file); + SBDebugger::GetCommandInterpreter().HandleCommand (command_string, result, false); + if (GetDebugMode()) + { + result.PutError (SBDebugger::GetErrorFileHandle()); + result.PutOutput (SBDebugger::GetOutputFileHandle()); + } + } + } + + if (!m_option_data.m_filename.empty()) + { + char arch_name[64]; + if (SBDebugger::GetDefaultArchitecture (arch_name, sizeof (arch_name))) + ::snprintf (command_string, sizeof (command_string), "file --arch=%s '%s'", arch_name, + m_option_data.m_filename.c_str()); + else + ::snprintf (command_string, sizeof(command_string), "file '%s'", m_option_data.m_filename.c_str()); + + SBDebugger::HandleCommand (command_string); + } + + // Now that all option parsing is done, we try and parse the .lldbinit + // file in the current working directory + sb_interpreter.SourceInitFileInCurrentWorkingDirectory (result); + if (GetDebugMode()) + { + result.PutError(SBDebugger::GetErrorFileHandle()); + result.PutOutput(SBDebugger::GetOutputFileHandle()); + } + + SBEvent event; + + // Make sure the IO channel is started up before we try to tell it we + // are ready for input + listener.WaitForEventForBroadcasterWithType (UINT32_MAX, + *m_io_channel_ap, + IOChannel::eBroadcastBitThreadDidStart, + event); + + ReadyForCommand (); + + bool done = false; + while (!done) + { + listener.WaitForEvent (UINT32_MAX, event); + if (event.IsValid()) + { + if (event.GetBroadcaster().IsValid()) + { + uint32_t event_type = event.GetType(); + if (event.BroadcasterMatchesRef (*m_io_channel_ap)) + { + if ((event_type & IOChannel::eBroadcastBitThreadShouldExit) || + (event_type & IOChannel::eBroadcastBitThreadDidExit)) + { + done = true; + if (event_type & IOChannel::eBroadcastBitThreadDidExit) + iochannel_thread_exited = true; + break; + } + else + done = HandleIOEvent (event); + } + else if (event.BroadcasterMatchesRef (SBDebugger::GetCurrentTarget().GetProcess().GetBroadcaster())) + { + HandleProcessEvent (event); + } + else if (event.BroadcasterMatchesRef (sb_interpreter.GetBroadcaster())) + { + if (event_type & SBCommandInterpreter::eBroadcastBitQuitCommandReceived) + done = true; + } + } + } + } + + reset_stdin_termios (); + + CloseIOChannelFile (); + + if (!iochannel_thread_exited) + { + SBEvent event; + listener.GetNextEventForBroadcasterWithType (*m_io_channel_ap, + IOChannel::eBroadcastBitThreadDidExit, + event); + if (!event.IsValid()) + { + // Send end EOF to the driver file descriptor + m_io_channel_ap->Stop(); + } + } + + SBProcess process = SBDebugger::GetCurrentTarget().GetProcess(); + if (process.IsValid()) + process.Destroy(); + } + } +} + + +void +Driver::ReadyForCommand () +{ + if (m_waiting_for_command == false) + { + m_waiting_for_command = true; + BroadcastEventByType (Driver::eBroadcastBitReadyForInput, true); + } +} + + +int +main (int argc, char const *argv[]) +{ + + SBDebugger::Initialize(); + + SBHostOS::ThreadCreated ("[main]"); + + // Do a little setup on the debugger before we get going + SBDebugger::SetAsync(true); + Driver driver; + + bool valid_args = driver.ParseArgs (argc, argv, stdout, stderr); + if (valid_args) + { + driver.MainLoop (); + } + + SBDebugger::Terminate(); + return 0; +} |