//===-- 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 // Includes for pipe() #if defined(_WIN32) #include #include #elif defined(__ANDROID_NDK__) #include #else #include #endif #include #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/SBLanguageRuntime.h" #include "lldb/API/SBListener.h" #include "lldb/API/SBProcess.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStringList.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" #include "llvm/Support/ConvertUTF.h" #include #if !defined(__APPLE__) #include "llvm/Support/DataTypes.h" #endif 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 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_5, true , "attach-pid" , 'p', required_argument, 0, eArgTypePid, "Tells the debugger to attach to a process with the given pid." }, { 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_3_TO_5, false, "source" , 's', required_argument, 0, eArgTypeFilename, "Tells the debugger to read in and execute the lldb commands in the given file, after any file provided on the command line has been loaded." }, { LLDB_3_TO_5, false, "one-line" , 'o', required_argument, 0, eArgTypeNone, "Tells the debugger to execute this one-line lldb command after any file provided on the command line has been loaded." }, { LLDB_3_TO_5, false, "source-before-file" , 'S', required_argument, 0, eArgTypeFilename, "Tells the debugger to read in and execute the lldb commands in the given file, before any file provided on the command line has been loaded." }, { LLDB_3_TO_5, false, "one-line-before-file" , 'O', required_argument, 0, eArgTypeNone, "Tells the debugger to execute this one-line lldb command before any file provided on the command line has been loaded." }, { LLDB_3_TO_5, false, "one-line-on-crash" , 'k', required_argument, 0, eArgTypeNone, "When in batch mode, tells the debugger to execute this one-line lldb command if the target crashes." }, { LLDB_3_TO_5, false, "source-on-crash" , 'K', required_argument, 0, eArgTypeFilename, "When in batch mode, tells the debugger to source this file of lldb commands if the target crashes." }, { LLDB_3_TO_5, false, "source-quietly" , 'Q', no_argument , 0, eArgTypeNone, "Tells the debugger to execute this one-line lldb command before any file provided on the command line has been loaded." }, { LLDB_3_TO_5, false, "batch" , 'b', no_argument , 0, eArgTypeNone, "Tells the debugger to running the commands from -s, -S, -o & -O, and then quit. However if any run command stopped due to a signal or crash, " "the debugger will return to the interactive prompt at the place of the crash." }, { 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" , 'X', 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." }, { 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_OPT_SET_7, true , "repl" , 'r', optional_argument, 0, eArgTypeNone, "Runs lldb in REPL mode with a stub process." }, { LLDB_OPT_SET_7, true , "repl-language" , 'R', required_argument, 0, eArgTypeNone, "Chooses the language for the REPL." }, { 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_option_data () { // We want to be able to handle CTRL+D in the terminal to have it terminate // certain input m_debugger.SetCloseInputOnEOF (false); g_driver = this; } Driver::~Driver () { g_driver = 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%*sNotes:\n", indent_level, ""); indent_level += 5; fprintf (out, "\n%*sMultiple \"-s\" and \"-o\" options can be provided. They will be processed" "\n%*sfrom left to right in order, with the source files and commands" "\n%*sinterleaved. The same is true of the \"-S\" and \"-O\" options. The before" "\n%*sfile and after file sets can intermixed freely, the command parser will" "\n%*ssort them out. The order of the file specifiers (\"-c\", \"-f\", etc.) is" "\n%*snot significant in this regard.\n\n", indent_level, "", indent_level, "", indent_level, "", indent_level, "", indent_level, "", indent_level, ""); fprintf (out, "\n%*sIf you don't provide -f then the first argument will be the file to be" "\n%*sdebugged which means that '%s -- [ []]' also" "\n%*sworks. But remember to end the options with \"--\" if any of your" "\n%*sarguments have a \"-\" in them.\n\n", indent_level, "", indent_level, "", name, indent_level, "", 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_initial_commands (), m_after_file_commands (), m_after_crash_commands(), m_debug_mode (false), m_source_quietly(false), m_print_version (false), m_print_python_path (false), m_print_help (false), m_wait_for(false), m_repl (false), m_repl_lang (eLanguageTypeUnknown), m_repl_options (), m_process_name(), m_process_pid(LLDB_INVALID_PROCESS_ID), m_use_external_editor(false), m_batch(false), m_seen_options() { } Driver::OptionData::~OptionData () { } void Driver::OptionData::Clear () { m_args.clear (); m_script_lang = lldb::eScriptLanguageDefault; m_initial_commands.clear (); m_after_file_commands.clear (); // If there is a local .lldbinit, add that to the // list of things to be sourced, if the settings // permit it. SBFileSpec local_lldbinit (".lldbinit", true); SBFileSpec homedir_dot_lldb = SBHostOS::GetUserHomeDirectory(); homedir_dot_lldb.AppendPathComponent (".lldbinit"); // Only read .lldbinit in the current working directory // if it's not the same as the .lldbinit in the home // directory (which is already being read in). if (local_lldbinit.Exists() && strcmp (local_lldbinit.GetDirectory(), homedir_dot_lldb.GetDirectory()) != 0) { char path[2048]; local_lldbinit.GetPath(path, 2047); InitialCmdEntry entry(path, true, true, true); m_after_file_commands.push_back (entry); } m_debug_mode = false; m_source_quietly = 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_batch = false; m_after_crash_commands.clear(); m_process_pid = LLDB_INVALID_PROCESS_ID; } void Driver::OptionData::AddInitialCommand (const char *command, CommandPlacement placement, bool is_file, SBError &error) { std::vector *command_set; switch (placement) { case eCommandPlacementBeforeFile: command_set = &(m_initial_commands); break; case eCommandPlacementAfterFile: command_set = &(m_after_file_commands); break; case eCommandPlacementAfterCrash: command_set = &(m_after_crash_commands); break; } if (is_file) { SBFileSpec file(command); if (file.Exists()) command_set->push_back (InitialCmdEntry(command, is_file, false)); else if (file.ResolveExecutableLocation()) { char final_path[PATH_MAX]; file.GetPath (final_path, sizeof(final_path)); command_set->push_back (InitialCmdEntry(final_path, is_file, false)); } else error.SetErrorStringWithFormat("file specified in --source (-s) option doesn't exist: '%s'", optarg); } else command_set->push_back (InitialCmdEntry(command, is_file, false)); } 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; } void Driver::WriteCommandsForSourcing (CommandPlacement placement, SBStream &strm) { std::vector *command_set; switch (placement) { case eCommandPlacementBeforeFile: command_set = &m_option_data.m_initial_commands; break; case eCommandPlacementAfterFile: command_set = &m_option_data.m_after_file_commands; break; case eCommandPlacementAfterCrash: command_set = &m_option_data.m_after_crash_commands; break; } for (const auto &command_entry : *command_set) { const char *command = command_entry.contents.c_str(); if (command_entry.is_file) { // If this command_entry is a file to be sourced, and it's the ./.lldbinit file (the .lldbinit // file in the current working directory), only read it if target.load-cwd-lldbinit is 'true'. if (command_entry.is_cwd_lldbinit_file_read) { SBStringList strlist = m_debugger.GetInternalVariableValue ("target.load-cwd-lldbinit", m_debugger.GetInstanceName()); if (strlist.GetSize() == 1 && strcmp (strlist.GetStringAtIndex(0), "warn") == 0) { FILE *output = m_debugger.GetOutputFileHandle (); ::fprintf (output, "There is a .lldbinit file in the current directory which is not being read.\n" "To silence this warning without sourcing in the local .lldbinit,\n" "add the following to the lldbinit file in your home directory:\n" " settings set target.load-cwd-lldbinit false\n" "To allow lldb to source .lldbinit files in the current working directory,\n" "set the value of this variable to true. Only do so if you understand and\n" "accept the security risk.\n"); return; } if (strlist.GetSize() == 1 && strcmp (strlist.GetStringAtIndex(0), "false") == 0) { return; } } bool source_quietly = m_option_data.m_source_quietly || command_entry.source_quietly; strm.Printf("command source -s %i '%s'\n", source_quietly, command); } else strm.Printf("%s\n", command); } } 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 &exiting) { 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 'b': m_option_data.m_batch = 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 'X': 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 'Q': m_option_data.m_source_quietly = true; break; case 'K': m_option_data.AddInitialCommand(optarg, eCommandPlacementAfterCrash, true, error); break; case 'k': m_option_data.AddInitialCommand(optarg, eCommandPlacementAfterCrash, false, error); 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 'r': m_option_data.m_repl = true; if (optarg && optarg[0]) m_option_data.m_repl_options = optarg; else m_option_data.m_repl_options.clear(); break; case 'R': m_option_data.m_repl_lang = SBLanguageRuntime::GetLanguageTypeFromString (optarg); if (m_option_data.m_repl_lang == eLanguageTypeUnknown) { error.SetErrorStringWithFormat ("Unrecognized language name: \"%s\"", optarg); } break; case 's': m_option_data.AddInitialCommand(optarg, eCommandPlacementAfterFile, true, error); break; case 'o': m_option_data.AddInitialCommand(optarg, eCommandPlacementAfterFile, false, error); break; case 'S': m_option_data.AddInitialCommand(optarg, eCommandPlacementBeforeFile, true, error); break; case 'O': m_option_data.AddInitialCommand(optarg, eCommandPlacementBeforeFile, false, error); 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); exiting = true; } else if (m_option_data.m_print_version) { ::fprintf (out_fh, "%s\n", m_debugger.GetVersionString()); exiting = 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"); exiting = 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; } static ::FILE * PrepareCommandsForSourcing (const char *commands_data, size_t commands_size, int fds[2]) { enum PIPES { READ, WRITE }; // Constants 0 and 1 for READ and WRITE ::FILE *commands_file = NULL; fds[0] = -1; fds[1] = -1; int err = 0; #ifdef _WIN32 err = _pipe(fds, commands_size, O_BINARY); #else err = pipe(fds); #endif if (err == 0) { ssize_t nrwr = write(fds[WRITE], commands_data, commands_size); if (nrwr < 0) { fprintf(stderr, "error: write(%i, %p, %" PRIu64 ") failed (errno = %i) " "when trying to open LLDB commands pipe\n", fds[WRITE], static_cast(commands_data), static_cast(commands_size), errno); } else if (static_cast(nrwr) == commands_size) { // Close the write end of the pipe so when we give the read end to // the debugger/command interpreter it will exit when it consumes all // of the data #ifdef _WIN32 _close(fds[WRITE]); fds[WRITE] = -1; #else close(fds[WRITE]); fds[WRITE] = -1; #endif // Now open the read file descriptor in a FILE * that we can give to // the debugger as an input handle commands_file = fdopen(fds[READ], "r"); if (commands_file) { fds[READ] = -1; // The FILE * 'commands_file' now owns the read descriptor // Hand ownership if the FILE * over to the debugger for "commands_file". } else { fprintf(stderr, "error: fdopen(%i, \"r\") failed (errno = %i) when " "trying to open LLDB commands pipe\n", fds[READ], errno); } } } else { fprintf(stderr, "error: can't create pipe file descriptors for LLDB commands\n"); } return commands_file; } void CleanupAfterCommandSourcing (int fds[2]) { enum PIPES { READ, WRITE }; // Constants 0 and 1 for READ and WRITE // Close any pipes that we still have ownership of if ( fds[WRITE] != -1) { #ifdef _WIN32 _close(fds[WRITE]); fds[WRITE] = -1; #else close(fds[WRITE]); fds[WRITE] = -1; #endif } if ( fds[READ] != -1) { #ifdef _WIN32 _close(fds[READ]); fds[READ] = -1; #else close(fds[READ]); fds[READ] = -1; #endif } } std::string EscapeString (std::string arg) { std::string::size_type pos = 0; while ((pos = arg.find_first_of("\"\\", pos)) != std::string::npos) { arg.insert (pos, 1, '\\'); pos += 2; } return '"' + arg + '"'; } void Driver::MainLoop () { if (::tcgetattr(STDIN_FILENO, &g_old_stdin_termios) == 0) { g_old_stdin_termios_is_valid = true; atexit (reset_stdin_termios); } #ifndef _MSC_VER // Disabling stdin buffering with MSVC's 2015 CRT exposes a bug in fgets // which causes it to miss newlines depending on whether there have been an // odd or even number of characters. Bug has been reported to MS via Connect. ::setbuf (stdin, NULL); #endif ::setbuf (stdout, NULL); m_debugger.SetErrorFileHandle (stderr, false); m_debugger.SetOutputFileHandle (stdout, false); m_debugger.SetInputFileHandle (stdin, false); // Don't take ownership of STDIN yet... m_debugger.SetUseExternalEditor(m_option_data.m_use_external_editor); 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); } SBCommandInterpreter sb_interpreter = m_debugger.GetCommandInterpreter(); // 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 SBStream commands_stream; // First source in the commands specified to be run before the file arguments are processed. WriteCommandsForSourcing (eCommandPlacementBeforeFile, commands_stream); 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))) commands_stream.Printf("target create --arch=%s %s", arch_name, EscapeString(m_option_data.m_args[0]).c_str()); else commands_stream.Printf("target create %s", EscapeString(m_option_data.m_args[0]).c_str()); if (!m_option_data.m_core_file.empty()) { commands_stream.Printf(" --core %s", EscapeString(m_option_data.m_core_file).c_str()); } commands_stream.Printf("\n"); if (num_args > 1) { commands_stream.Printf ("settings set -- target.run-args "); for (size_t arg_idx = 1; arg_idx < num_args; ++arg_idx) commands_stream.Printf(" %s", EscapeString(m_option_data.m_args[arg_idx]).c_str()); commands_stream.Printf("\n"); } } else if (!m_option_data.m_core_file.empty()) { commands_stream.Printf("target create --core %s\n", EscapeString(m_option_data.m_core_file).c_str()); } else if (!m_option_data.m_process_name.empty()) { commands_stream.Printf ("process attach --name %s", EscapeString(m_option_data.m_process_name).c_str()); if (m_option_data.m_wait_for) commands_stream.Printf(" --waitfor"); commands_stream.Printf("\n"); } else if (LLDB_INVALID_PROCESS_ID != m_option_data.m_process_pid) { commands_stream.Printf ("process attach --pid %" PRIu64 "\n", m_option_data.m_process_pid); } WriteCommandsForSourcing(eCommandPlacementAfterFile, commands_stream); if (GetDebugMode()) { result.PutError(m_debugger.GetErrorFileHandle()); result.PutOutput(m_debugger.GetOutputFileHandle()); } bool handle_events = true; bool spawn_thread = false; if (m_option_data.m_repl) { const char *repl_options = NULL; if (!m_option_data.m_repl_options.empty()) repl_options = m_option_data.m_repl_options.c_str(); SBError error (m_debugger.RunREPL(m_option_data.m_repl_lang, repl_options)); if (error.Fail()) { const char *error_cstr = error.GetCString(); if (error_cstr && error_cstr[0]) fprintf (stderr, "error: %s\n", error_cstr); else fprintf (stderr, "error: %u\n", error.GetError()); } } else { // Check if we have any data in the commands stream, and if so, save it to a temp file // so we can then run the command interpreter using the file contents. const char *commands_data = commands_stream.GetData(); const size_t commands_size = commands_stream.GetSize(); // The command file might have requested that we quit, this variable will track that. bool quit_requested = false; bool stopped_for_crash = false; if (commands_data && commands_size) { int initial_commands_fds[2]; bool success = true; FILE *commands_file = PrepareCommandsForSourcing (commands_data, commands_size, initial_commands_fds); if (commands_file) { m_debugger.SetInputFileHandle (commands_file, true); // Set the debugger into Sync mode when running the command file. Otherwise command files // that run the target won't run in a sensible way. bool old_async = m_debugger.GetAsync(); m_debugger.SetAsync(false); int num_errors; SBCommandInterpreterRunOptions options; options.SetStopOnError (true); if (m_option_data.m_batch) options.SetStopOnCrash (true); m_debugger.RunCommandInterpreter(handle_events, spawn_thread, options, num_errors, quit_requested, stopped_for_crash); if (m_option_data.m_batch && stopped_for_crash && !m_option_data.m_after_crash_commands.empty()) { int crash_command_fds[2]; SBStream crash_commands_stream; WriteCommandsForSourcing (eCommandPlacementAfterCrash, crash_commands_stream); const char *crash_commands_data = crash_commands_stream.GetData(); const size_t crash_commands_size = crash_commands_stream.GetSize(); commands_file = PrepareCommandsForSourcing (crash_commands_data, crash_commands_size, crash_command_fds); if (commands_file) { bool local_quit_requested; bool local_stopped_for_crash; m_debugger.SetInputFileHandle (commands_file, true); m_debugger.RunCommandInterpreter(handle_events, spawn_thread, options, num_errors, local_quit_requested, local_stopped_for_crash); if (local_quit_requested) quit_requested = true; } } m_debugger.SetAsync(old_async); } else success = false; // Close any pipes that we still have ownership of CleanupAfterCommandSourcing(initial_commands_fds); // Something went wrong with command pipe if (!success) { exit(1); } } // Now set the input file handle to STDIN and run the command // interpreter again in interactive mode and let the debugger // take ownership of stdin bool go_interactive = true; if (quit_requested) go_interactive = false; else if (m_option_data.m_batch && !stopped_for_crash) go_interactive = false; if (go_interactive) { m_debugger.SetInputFileHandle (stdin, true); m_debugger.RunCommandInterpreter(handle_events, spawn_thread); } } reset_stdin_termios(); fclose (stdin); SBDebugger::Destroy (m_debugger); } void Driver::ResizeWindow (unsigned short col) { GetDebugger().SetTerminalWidth (col); } 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) { if (g_driver) g_driver->GetDebugger().SaveInputTerminalState(); signal (signo, SIG_DFL); kill (getpid(), signo); signal (signo, sigtstp_handler); } void sigcont_handler (int signo) { if (g_driver) g_driver->GetDebugger().RestoreInputTerminalState(); signal (signo, SIG_DFL); kill (getpid(), signo); signal (signo, sigcont_handler); } int #ifdef WIN32 wmain(int argc, wchar_t const *wargv[]) #else main(int argc, char const *argv[]) #endif { #ifdef _WIN32 // Convert wide arguments to UTF-8 std::vector argvStrings(argc); std::vector argvPointers(argc); for (int i = 0; i != argc; ++i) { llvm::convertWideToUTF8(wargv[i], argvStrings[i]); argvPointers[i] = argvStrings[i].c_str(); } const char **argv = argvPointers.data(); #endif SBDebugger::Initialize(); SBHostOS::ThreadCreated(""); signal(SIGINT, sigint_handler); #if !defined(_MSC_VER) signal(SIGPIPE, SIG_IGN); signal(SIGWINCH, sigwinch_handler); signal(SIGTSTP, sigtstp_handler); signal(SIGCONT, sigcont_handler); #endif // Create a scope for driver so that the driver object will destroy itself // before SBDebugger::Terminate() is called. { Driver driver; bool exiting = false; SBError error(driver.ParseArgs(argc, argv, stdout, exiting)); if (error.Fail()) { const char *error_cstr = error.GetCString(); if (error_cstr) ::fprintf(stderr, "error: %s\n", error_cstr); } else if (!exiting) { driver.MainLoop(); } } SBDebugger::Terminate(); return 0; }