hanchenye-llvm-project/lldb/tools/driver/Driver.cpp

1395 lines
48 KiB
C++
Raw Normal View History

//===-- 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_ALL, false, "no-lldbinit", 'n', no_argument, NULL, NULL, eArgTypeNone,
"Do not automatically parse any '.lldbinit' files." },
// { LLDB_OPT_SET_4, true, "crash-log", 'c', required_argument, NULL, NULL, eArgTypeFilename,
Added function name types to allow us to set breakpoints by name more intelligently. The four name types we currently have are: eFunctionNameTypeFull = (1 << 1), // The function name. // For C this is the same as just the name of the function // For C++ this is the demangled version of the mangled name. // For ObjC this is the full function signature with the + or // - and the square brackets and the class and selector eFunctionNameTypeBase = (1 << 2), // The function name only, no namespaces or arguments and no class // methods or selectors will be searched. eFunctionNameTypeMethod = (1 << 3), // Find function by method name (C++) with no namespace or arguments eFunctionNameTypeSelector = (1 << 4) // Find function by selector name (ObjC) names this allows much more flexibility when setting breakoints: (lldb) breakpoint set --name main --basename (lldb) breakpoint set --name main --fullname (lldb) breakpoint set --name main --method (lldb) breakpoint set --name main --selector The default: (lldb) breakpoint set --name main will inspect the name "main" and look for any parens, or if the name starts with "-[" or "+[" and if any are found then a full name search will happen. Else a basename search will be the default. Fixed some command option structures so not all options are required when they shouldn't be. Cleaned up the breakpoint output summary. Made the "image lookup --address <addr>" output much more verbose so it shows all the important symbol context results. Added a GetDescription method to many of the SymbolContext objects for the more verbose output. llvm-svn: 107075
2010-06-29 05:30:43 +08:00
// "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 'n':
m_debugger.SkipLLDBInitFiles (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);
exit = true;
}
else if (m_option_data.m_print_version)
{
::fprintf (out_fh, "%s\n", m_debugger.GetVersionString());
exit = true;
}
else if (! m_option_data.m_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;
}