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