#include "Driver.h"
#include "lldb/API/SBCommandInterpreter.h"
#include "lldb/API/SBCommandInterpreterRunOptions.h"
#include "lldb/API/SBCommandReturnObject.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBFile.h"
#include "lldb/API/SBHostOS.h"
#include "lldb/API/SBLanguageRuntime.h"
#include "lldb/API/SBReproducer.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStringList.h"
#include "lldb/API/SBStructuredData.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <atomic>
#include <bitset>
#include <clocale>
#include <csignal>
#include <string>
#include <thread>
#include <utility>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#if !defined(__APPLE__)
#include "llvm/Support/DataTypes.h"
#endif
using namespace lldb;
using namespace llvm;
namespace {
enum ID {
OPT_INVALID = 0,
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \
HELPTEXT, METAVAR, VALUES) \
OPT_##ID,
#include "Options.inc"
#undef OPTION
};
#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
#include "Options.inc"
#undef PREFIX
const opt::OptTable::Info InfoTable[] = {
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \
HELPTEXT, METAVAR, VALUES) \
{ \
PREFIX, NAME, HELPTEXT, \
METAVAR, OPT_##ID, opt::Option::KIND##Class, \
PARAM, FLAGS, OPT_##GROUP, \
OPT_##ALIAS, ALIASARGS, VALUES},
#include "Options.inc"
#undef OPTION
};
class LLDBOptTable : public opt::OptTable {
public:
LLDBOptTable() : OptTable(InfoTable) {}
};
}
static void reset_stdin_termios();
static bool g_old_stdin_termios_is_valid = false;
static struct termios g_old_stdin_termios;
static bool disable_color(const raw_ostream &OS) { return false; }
static Driver *g_driver = nullptr;
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);
}
}
Driver::Driver()
: SBBroadcaster("Driver"), m_debugger(SBDebugger::Create(false)) {
m_debugger.SetCloseInputOnEOF(false);
g_driver = this;
}
Driver::~Driver() {
SBDebugger::Destroy(m_debugger);
g_driver = nullptr;
}
void Driver::OptionData::AddInitialCommand(std::string command,
CommandPlacement placement,
bool is_file, SBError &error) {
std::vector<InitialCmdEntry> *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.c_str());
if (file.Exists())
command_set->push_back(InitialCmdEntry(command, is_file));
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));
} else
error.SetErrorStringWithFormat(
"file specified in --source (-s) option doesn't exist: '%s'",
command.c_str());
} else
command_set->push_back(InitialCmdEntry(command, is_file));
}
void Driver::WriteCommandsForSourcing(CommandPlacement placement,
SBStream &strm) {
std::vector<OptionData::InitialCmdEntry> *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) {
bool source_quietly =
m_option_data.m_source_quietly || command_entry.source_quietly;
strm.Printf("command source -s %i '%s'\n",
static_cast<int>(source_quietly), command);
} else
strm.Printf("%s\n", command);
}
}
SBError Driver::ProcessArgs(const opt::InputArgList &args, bool &exiting) {
SBError error;
m_debugger.SkipLLDBInitFiles(false);
m_debugger.SkipAppInitFiles(false);
if (args.hasArg(OPT_no_use_colors)) {
m_debugger.SetUseColor(false);
WithColor::setAutoDetectFunction(disable_color);
m_option_data.m_debug_mode = true;
}
if (args.hasArg(OPT_version)) {
m_option_data.m_print_version = true;
}
if (args.hasArg(OPT_python_path)) {
m_option_data.m_print_python_path = true;
}
if (args.hasArg(OPT_print_script_interpreter_info)) {
m_option_data.m_print_script_interpreter_info = true;
}
if (args.hasArg(OPT_batch)) {
m_option_data.m_batch = true;
}
if (auto *arg = args.getLastArg(OPT_core)) {
auto arg_value = arg->getValue();
SBFileSpec file(arg_value);
if (!file.Exists()) {
error.SetErrorStringWithFormat(
"file specified in --core (-c) option doesn't exist: '%s'",
arg_value);
return error;
}
m_option_data.m_core_file = arg_value;
}
if (args.hasArg(OPT_editor)) {
m_option_data.m_use_external_editor = true;
}
if (args.hasArg(OPT_no_lldbinit)) {
m_debugger.SkipLLDBInitFiles(true);
m_debugger.SkipAppInitFiles(true);
}
if (args.hasArg(OPT_local_lldbinit)) {
lldb::SBDebugger::SetInternalVariable("target.load-cwd-lldbinit", "true",
m_debugger.GetInstanceName());
}
if (auto *arg = args.getLastArg(OPT_file)) {
auto arg_value = arg->getValue();
SBFileSpec file(arg_value);
if (file.Exists()) {
m_option_data.m_args.emplace_back(arg_value);
} else if (file.ResolveExecutableLocation()) {
char path[PATH_MAX];
file.GetPath(path, sizeof(path));
m_option_data.m_args.emplace_back(path);
} else {
error.SetErrorStringWithFormat(
"file specified in --file (-f) option doesn't exist: '%s'",
arg_value);
return error;
}
}
if (auto *arg = args.getLastArg(OPT_arch)) {
auto arg_value = arg->getValue();
if (!lldb::SBDebugger::SetDefaultArchitecture(arg_value)) {
error.SetErrorStringWithFormat(
"invalid architecture in the -a or --arch option: '%s'", arg_value);
return error;
}
}
if (auto *arg = args.getLastArg(OPT_script_language)) {
auto arg_value = arg->getValue();
m_debugger.SetScriptLanguage(m_debugger.GetScriptingLanguage(arg_value));
}
if (args.hasArg(OPT_source_quietly)) {
m_option_data.m_source_quietly = true;
}
if (auto *arg = args.getLastArg(OPT_attach_name)) {
auto arg_value = arg->getValue();
m_option_data.m_process_name = arg_value;
}
if (args.hasArg(OPT_wait_for)) {
m_option_data.m_wait_for = true;
}
if (auto *arg = args.getLastArg(OPT_attach_pid)) {
auto arg_value = arg->getValue();
char *remainder;
m_option_data.m_process_pid = strtol(arg_value, &remainder, 0);
if (remainder == arg_value || *remainder != '\0') {
error.SetErrorStringWithFormat(
"Could not convert process PID: \"%s\" into a pid.", arg_value);
return error;
}
}
if (auto *arg = args.getLastArg(OPT_repl_language)) {
auto arg_value = arg->getValue();
m_option_data.m_repl_lang =
SBLanguageRuntime::GetLanguageTypeFromString(arg_value);
if (m_option_data.m_repl_lang == eLanguageTypeUnknown) {
error.SetErrorStringWithFormat("Unrecognized language name: \"%s\"",
arg_value);
return error;
}
m_debugger.SetREPLLanguage(m_option_data.m_repl_lang);
}
if (args.hasArg(OPT_repl)) {
m_option_data.m_repl = true;
}
if (auto *arg = args.getLastArg(OPT_repl_)) {
m_option_data.m_repl = true;
if (auto arg_value = arg->getValue())
m_option_data.m_repl_options = arg_value;
}
for (auto *arg : args.filtered(OPT_source_on_crash, OPT_one_line_on_crash,
OPT_source, OPT_source_before_file,
OPT_one_line, OPT_one_line_before_file)) {
auto arg_value = arg->getValue();
if (arg->getOption().matches(OPT_source_on_crash)) {
m_option_data.AddInitialCommand(arg_value, eCommandPlacementAfterCrash,
true, error);
if (error.Fail())
return error;
}
if (arg->getOption().matches(OPT_one_line_on_crash)) {
m_option_data.AddInitialCommand(arg_value, eCommandPlacementAfterCrash,
false, error);
if (error.Fail())
return error;
}
if (arg->getOption().matches(OPT_source)) {
m_option_data.AddInitialCommand(arg_value, eCommandPlacementAfterFile,
true, error);
if (error.Fail())
return error;
}
if (arg->getOption().matches(OPT_source_before_file)) {
m_option_data.AddInitialCommand(arg_value, eCommandPlacementBeforeFile,
true, error);
if (error.Fail())
return error;
}
if (arg->getOption().matches(OPT_one_line)) {
m_option_data.AddInitialCommand(arg_value, eCommandPlacementAfterFile,
false, error);
if (error.Fail())
return error;
}
if (arg->getOption().matches(OPT_one_line_before_file)) {
m_option_data.AddInitialCommand(arg_value, eCommandPlacementBeforeFile,
false, error);
if (error.Fail())
return error;
}
}
if (m_option_data.m_process_name.empty() &&
m_option_data.m_process_pid == LLDB_INVALID_PROCESS_ID) {
for (auto *arg : args.filtered(OPT_INPUT))
m_option_data.m_args.push_back(arg->getAsString((args)));
if (auto *arg = args.getLastArgNoClaim(OPT_REM)) {
for (auto value : arg->getValues())
m_option_data.m_args.emplace_back(value);
}
} else if (args.getLastArgNoClaim() != nullptr) {
WithColor::warning() << "program arguments are ignored when attaching.\n";
}
if (m_option_data.m_print_version) {
llvm::outs() << lldb::SBDebugger::GetVersionString() << '\n';
exiting = true;
return error;
}
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) {
llvm::outs() << python_path << '\n';
} else
llvm::outs() << "<PATH TOO LONG>\n";
} else
llvm::outs() << "<COULD NOT FIND PATH>\n";
exiting = true;
return error;
}
if (m_option_data.m_print_script_interpreter_info) {
SBStructuredData info =
m_debugger.GetScriptInterpreterInfo(m_debugger.GetScriptLanguage());
if (!info) {
error.SetErrorString("no script interpreter.");
} else {
SBStream stream;
error = info.GetAsJSON(stream);
if (error.Success()) {
llvm::outs() << stream.GetData() << '\n';
}
}
exiting = true;
return error;
}
return error;
}
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 + '"';
}
int 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
::setbuf(stdin, nullptr);
#endif
::setbuf(stdout, nullptr);
m_debugger.SetErrorFileHandle(stderr, false);
m_debugger.SetOutputFileHandle(stdout, false);
m_debugger.SetInputFileHandle(stdin, false);
m_debugger.HandleCommand("setting set prompt \"(cjdb) \"");
m_debugger.SetUseExternalEditor(m_option_data.m_use_external_editor);
struct winsize window_size;
if ((isatty(STDIN_FILENO) != 0) &&
::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();
SBCommandReturnObject result;
sb_interpreter.SourceInitFileInGlobalDirectory(result);
if (m_option_data.m_debug_mode) {
result.PutError(m_debugger.GetErrorFile());
result.PutOutput(m_debugger.GetOutputFile());
}
sb_interpreter.SourceInitFileInHomeDirectory(result, m_option_data.m_repl);
if (m_option_data.m_debug_mode) {
result.PutError(m_debugger.GetErrorFile());
result.PutOutput(m_debugger.GetOutputFile());
}
sb_interpreter.SourceInitFileInCurrentWorkingDirectory(result);
result.PutError(m_debugger.GetErrorFile());
result.PutOutput(m_debugger.GetOutputFile());
m_debugger.GetCommandInterpreter().AllowExitCodeOnQuit(true);
SBStream commands_stream;
WriteCommandsForSourcing(eCommandPlacementBeforeFile, commands_stream);
if (!m_option_data.m_repl) {
const size_t num_args = m_option_data.m_args.size();
if (num_args > 0) {
char arch_name[64];
if (lldb::SBDebugger::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);
} else if (!m_option_data.m_after_file_commands.empty()) {
WithColor::warning() << "commands specified to run after file load (via -o "
"or -s) are ignored in REPL mode.\n";
}
if (m_option_data.m_debug_mode) {
result.PutError(m_debugger.GetErrorFile());
result.PutOutput(m_debugger.GetOutputFile());
}
const bool handle_events = true;
const bool spawn_thread = false;
bool go_interactive = true;
if ((commands_stream.GetData() != nullptr) &&
(commands_stream.GetSize() != 0u)) {
SBError error = m_debugger.SetInputString(commands_stream.GetData());
if (error.Fail()) {
WithColor::error() << error.GetCString() << '\n';
return 1;
}
bool old_async = m_debugger.GetAsync();
m_debugger.SetAsync(false);
SBCommandInterpreterRunOptions options;
options.SetAutoHandleEvents(true);
options.SetSpawnThread(false);
options.SetStopOnError(true);
options.SetStopOnCrash(m_option_data.m_batch);
options.SetEchoCommands(!m_option_data.m_source_quietly);
SBCommandInterpreterRunResult results =
m_debugger.RunCommandInterpreter(options);
if (results.GetResult() == lldb::eCommandInterpreterResultQuitRequested)
go_interactive = false;
if (m_option_data.m_batch &&
results.GetResult() != lldb::eCommandInterpreterResultInferiorCrash)
go_interactive = false;
if (m_option_data.m_batch &&
results.GetResult() == lldb::eCommandInterpreterResultCommandError)
return 1;
if (m_option_data.m_batch &&
results.GetResult() == lldb::eCommandInterpreterResultInferiorCrash &&
!m_option_data.m_after_crash_commands.empty()) {
SBStream crash_commands_stream;
WriteCommandsForSourcing(eCommandPlacementAfterCrash,
crash_commands_stream);
SBError error =
m_debugger.SetInputString(crash_commands_stream.GetData());
if (error.Success()) {
SBCommandInterpreterRunResult local_results =
m_debugger.RunCommandInterpreter(options);
if (local_results.GetResult() ==
lldb::eCommandInterpreterResultQuitRequested)
go_interactive = false;
if (m_option_data.m_batch &&
local_results.GetResult() ==
lldb::eCommandInterpreterResultCommandError)
return 1;
}
}
m_debugger.SetAsync(old_async);
}
if (go_interactive) {
m_debugger.SetInputFileHandle(stdin, true);
if (m_option_data.m_repl) {
const char *repl_options = nullptr;
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 != nullptr) && (error_cstr[0] != 0))
WithColor::error() << error_cstr << '\n';
else
WithColor::error() << error.GetError() << '\n';
}
} else {
m_debugger.RunCommandInterpreter(handle_events, spawn_thread);
}
}
reset_stdin_termios();
fclose(stdin);
return sb_interpreter.GetQuitStatus();
}
void Driver::ResizeWindow(unsigned short col) {
GetDebugger().SetTerminalWidth(col);
}
void sigwinch_handler(int signo) {
struct winsize window_size;
if ((isatty(STDIN_FILENO) != 0) &&
::ioctl(STDIN_FILENO, TIOCGWINSZ, &window_size) == 0) {
if ((window_size.ws_col > 0) && g_driver != nullptr) {
g_driver->ResizeWindow(window_size.ws_col);
}
}
}
void sigint_handler(int signo) {
#ifdef _WIN32
signal(SIGINT, sigint_handler);
#endif
static std::atomic_flag g_interrupt_sent = ATOMIC_FLAG_INIT;
if (g_driver != nullptr) {
if (!g_interrupt_sent.test_and_set()) {
g_driver->GetDebugger().DispatchInputInterrupt();
g_interrupt_sent.clear();
return;
}
}
_exit(signo);
}
#ifndef _WIN32
static void sigtstp_handler(int signo) {
if (g_driver != nullptr)
g_driver->GetDebugger().SaveInputTerminalState();
sigset_t set;
sigemptyset(&set);
sigaddset(&set, signo);
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
signal(signo, SIG_DFL);
raise(signo);
pthread_sigmask(SIG_BLOCK, &set, nullptr);
signal(signo, sigtstp_handler);
if (g_driver != nullptr)
g_driver->GetDebugger().RestoreInputTerminalState();
}
#endif
static void printHelp(LLDBOptTable &table, llvm::StringRef tool_name) {
std::string usage_str = tool_name.str() + " [options]";
table.printHelp(llvm::outs(), usage_str.c_str(), "LLDB", false);
std::string examples = R"___(
EXAMPLES:
The debugger can be started in several modes.
Passing an executable as a positional argument prepares lldb to debug the
given executable. To disambiguate between arguments passed to lldb and
arguments passed to the debugged executable, arguments starting with a - must
be passed after --.
lldb --arch x86_64 /path/to/program program argument -- --arch armv7
For convenience, passing the executable after -- is also supported.
lldb --arch x86_64 -- /path/to/program program argument --arch armv7
Passing one of the attach options causes lldb to immediately attach to the
given process.
lldb -p <pid>
lldb -n <process-name>
Passing --repl starts lldb in REPL mode.
lldb -r
Passing --core causes lldb to debug the core file.
lldb -c /path/to/core
Command options can be combined with these modes and cause lldb to run the
specified commands before or after events, like loading the file or crashing,
in the order provided on the command line.
lldb -O 'settings set stop-disassembly-count 20' -o 'run' -o 'bt'
lldb -S /source/before/file -s /source/after/file
lldb -K /source/before/crash -k /source/after/crash
Note: In REPL mode no file is loaded, so commands specified to run after
loading the file (via -o or -s) will be ignored.)___";
llvm::outs() << examples << '\n';
}
static llvm::Optional<int> InitializeReproducer(llvm::StringRef argv0,
opt::InputArgList &input_args) {
bool capture = input_args.hasArg(OPT_capture);
bool generate_on_exit = input_args.hasArg(OPT_generate_on_exit);
auto *capture_path = input_args.getLastArg(OPT_capture_path);
if (generate_on_exit && !capture) {
WithColor::warning()
<< "-reproducer-generate-on-exit specified without -capture\n";
}
if (capture || capture_path) {
if (capture_path) {
if (!capture)
WithColor::warning() << "-capture-path specified without -capture\n";
if (const char *error = SBReproducer::Capture(capture_path->getValue())) {
WithColor::error() << "reproducer capture failed: " << error << '\n';
return 1;
}
} else {
const char *error = SBReproducer::Capture();
if (error) {
WithColor::error() << "reproducer capture failed: " << error << '\n';
return 1;
}
}
if (generate_on_exit)
SBReproducer::SetAutoGenerate(true);
}
return llvm::None;
}
int main(int argc, char const *argv[]) {
std::setlocale(LC_ALL, "");
std::setlocale(LC_CTYPE, "");
llvm::InitLLVM IL(argc, argv, false);
LLDBOptTable T;
unsigned MissingArgIndex;
unsigned MissingArgCount;
ArrayRef<const char *> arg_arr = makeArrayRef(argv + 1, argc - 1);
opt::InputArgList input_args =
T.ParseArgs(arg_arr, MissingArgIndex, MissingArgCount);
llvm::StringRef argv0 = llvm::sys::path::filename(argv[0]);
if (input_args.hasArg(OPT_help)) {
printHelp(T, argv0);
return 0;
}
if (MissingArgCount) {
WithColor::error() << "argument to '"
<< input_args.getArgString(MissingArgIndex)
<< "' is missing\n";
}
if (input_args.hasArg(OPT_UNKNOWN)) {
for (auto *arg : input_args.filtered(OPT_UNKNOWN)) {
WithColor::error() << "unknown option: " << arg->getSpelling() << '\n';
}
}
if (MissingArgCount || input_args.hasArg(OPT_UNKNOWN)) {
llvm::errs() << "Use '" << argv0
<< " --help' for a complete list of options.\n";
return 1;
}
if (auto exit_code = InitializeReproducer(argv[0], input_args)) {
return *exit_code;
}
SBError error = SBDebugger::InitializeWithErrorHandling();
if (error.Fail()) {
WithColor::error() << "initialization failed: " << error.GetCString()
<< '\n';
return 1;
}
SBHostOS::ThreadCreated("<lldb.driver.main-thread>");
signal(SIGINT, sigint_handler);
#if !defined(_WIN32)
signal(SIGPIPE, SIG_IGN);
signal(SIGWINCH, sigwinch_handler);
signal(SIGTSTP, sigtstp_handler);
#endif
int exit_code = 0;
{
Driver driver;
bool exiting = false;
SBError error(driver.ProcessArgs(input_args, exiting));
if (error.Fail()) {
exit_code = 1;
if (const char *error_cstr = error.GetCString())
WithColor::error() << error_cstr << '\n';
} else if (!exiting) {
exit_code = driver.MainLoop();
}
}
SBDebugger::Terminate();
return exit_code;
}