| //===-- Driver.cpp ----------------------------------------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #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/SBStream.h" | |
| #include "lldb/API/SBStringList.h" | |
| #include "lldb/API/SBStructuredData.h" | |
| #include "lldb/Host/Config.h" | |
| #include "lldb/Host/MainLoop.h" | |
| #include "lldb/Host/MainLoopBase.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "llvm/ADT/SmallString.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Support/ConvertUTF.h" | |
| #include "llvm/Support/FileSystem.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" | |
| #ifdef _WIN32 | |
| #include "lldb/Host/Config.h" | |
| #if LLDB_ENABLE_PYTHON | |
| #include "lldb/Host/ScriptInterpreterRuntimeLoader.h" | |
| #endif | |
| #endif | |
| #include <algorithm> | |
| #include <atomic> | |
| #include <bitset> | |
| #include <clocale> | |
| #include <csignal> | |
| #include <future> | |
| #ifndef _WIN32 | |
| #include <pthread.h> | |
| #endif | |
| #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; | |
| using lldb_private::MainLoop; | |
| using lldb_private::MainLoopBase; | |
| using lldb_private::Status; | |
| namespace { | |
| using namespace llvm::opt; | |
| enum ID { | |
| OPT_INVALID = 0, // This is not an option ID. | |
| #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), | |
| #include "Options.inc" | |
| #undef OPTION | |
| }; | |
| #define OPTTABLE_CODE | |
| #include "Options.inc" | |
| class LLDBOptTable : public opt::OptTable { | |
| public: | |
| LLDBOptTable() : opt::OptTable(optionTables()) {} | |
| }; | |
| } // namespace | |
| 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; | |
| // In the Driver::MainLoop, we change the terminal settings. This function is | |
| // added as an atexit handler to make sure we clean them up. | |
| static void reset_stdin_termios() { | |
| if (g_old_stdin_termios_is_valid) { | |
| g_old_stdin_termios_is_valid = false; | |
| ::tcsetattr(STDIN_FILENO, TCSANOW, &g_old_stdin_termios); | |
| } | |
| } | |
| Driver::Driver() | |
| : SBBroadcaster("Driver"), m_debugger(SBDebugger::Create(false)) { | |
| // We want to be able to handle CTRL+D in the terminal to have it terminate | |
| // certain input | |
| m_debugger.SetCloseInputOnEOF(false); | |
| g_driver = this; | |
| } | |
| Driver::~Driver() { | |
| 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); | |
| } | |
| } | |
| // 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::ProcessArgs(const opt::InputArgList &args, bool &exiting) { | |
| SBError error; | |
| // This is kind of a pain, but since we make the debugger in the Driver's | |
| // constructor, we can't know at that point whether we should read in init | |
| // files yet. So we don't read them in in the Driver constructor, then set | |
| // the flags back to "read them in" here, and then if we see the "-n" flag, | |
| // we'll turn it off again. Finally we have to read them in by hand later in | |
| // the main loop. | |
| m_debugger.SkipLLDBInitFiles(false); | |
| m_debugger.SkipAppInitFiles(false); | |
| if (args.hasArg(OPT_no_use_colors)) { | |
| m_debugger.SetUseColor(false); | |
| WithColor::setAutoDetectFunction(disable_color); | |
| } | |
| 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)) { | |
| if (!args.hasArg(OPT_attach_name)) { | |
| error.SetErrorStringWithFormat( | |
| "--wait-for requires a name (--attach-name)"); | |
| return error; | |
| } | |
| 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; | |
| } | |
| // We need to process the options below together as their relative order | |
| // matters. | |
| 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))); | |
| // Any argument following -- is an argument for the inferior. | |
| 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::GetScriptPath(lldb::eScriptLanguagePython); | |
| 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); | |
| } | |
| ::setbuf(stdin, nullptr); | |
| ::setbuf(stdout, nullptr); | |
| m_debugger.SetErrorFileHandle(stderr, false); | |
| m_debugger.SetOutputFileHandle(stdout, false); | |
| // Don't take ownership of STDIN yet... | |
| m_debugger.SetInputFileHandle(stdin, false); | |
| m_debugger.SetUseExternalEditor(m_option_data.m_use_external_editor); | |
| m_debugger.SetShowInlineDiagnostics(true); | |
| // Set the terminal dimensions. | |
| UpdateWindowSize(); | |
| SBCommandInterpreter sb_interpreter = m_debugger.GetCommandInterpreter(); | |
| // Process lldbinit files before handling any options from the command line. | |
| SBCommandReturnObject result; | |
| sb_interpreter.SourceInitFileInGlobalDirectory(result); | |
| sb_interpreter.SourceInitFileInHomeDirectory(result, m_option_data.m_repl); | |
| // Source the local .lldbinit file if it exists and we're allowed to source. | |
| // Here we want to always print the return object because it contains the | |
| // warning and instructions to load local lldbinit files. | |
| sb_interpreter.SourceInitFileInCurrentWorkingDirectory(result); | |
| result.PutError(m_debugger.GetErrorFile()); | |
| result.PutOutput(m_debugger.GetOutputFile()); | |
| // We allow the user to specify an exit code when calling quit which we will | |
| // return when exiting. | |
| m_debugger.GetCommandInterpreter().AllowExitCodeOnQuit(true); | |
| // Now we handle options we got from the command line | |
| SBStream commands_stream; | |
| // First source in the commands specified to be run before the file arguments | |
| // are processed. | |
| WriteCommandsForSourcing(eCommandPlacementBeforeFile, commands_stream); | |
| // If we're not in --repl mode, add the commands to process the file | |
| // arguments, and the commands specified to run afterwards. | |
| 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()) { | |
| // We're in repl mode and after-file-load commands were specified. | |
| WithColor::warning() << "commands specified to run after file load (via -o " | |
| "or -s) are ignored in REPL mode.\n"; | |
| } | |
| const bool handle_events = true; | |
| const bool spawn_thread = false; | |
| // Check if we have any data in the commands stream, and if so, save it to a | |
| // temp file | |
| // so we can then run the command interpreter using the file contents. | |
| bool go_interactive = !m_option_data.m_batch; | |
| 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; | |
| } | |
| // Set the debugger into Sync mode when running the command file. Otherwise | |
| // command files that run the target won't run in a sensible way. | |
| bool old_async = m_debugger.GetAsync(); | |
| m_debugger.SetAsync(false); | |
| 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; | |
| // When running in batch mode and stopped because of an error, exit with a | |
| // non-zero exit status. | |
| 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()) { | |
| go_interactive = true; | |
| 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; | |
| // When running in batch mode and an error occurred while sourcing | |
| // the crash commands, exit with a non-zero exit status. | |
| if (m_option_data.m_batch && | |
| local_results.GetResult() == | |
| lldb::eCommandInterpreterResultCommandError) | |
| return 1; | |
| } | |
| } | |
| m_debugger.SetAsync(old_async); | |
| } | |
| // Now set the input file handle to STDIN and run the command interpreter | |
| // again in interactive mode or repl mode and let the debugger take ownership | |
| // of stdin. | |
| 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::UpdateWindowSize() { | |
| struct winsize window_size; | |
| if ((isatty(STDIN_FILENO) != 0) && | |
| ::ioctl(STDIN_FILENO, TIOCGWINSZ, &window_size) == 0) { | |
| if (window_size.ws_col > 0) { | |
| // Set both dimensions together to avoid recomputing from a stale value. | |
| #ifndef _WIN32 | |
| m_debugger.SetTerminalDimensions(window_size.ws_col, window_size.ws_row); | |
| #else | |
| m_debugger.SetTerminalDimensions(window_size.ws_col, | |
| m_debugger.GetTerminalHeight()); | |
| #endif | |
| } | |
| } | |
| } | |
| #ifdef _WIN32 | |
| void sigint_handler(int signo) { | |
| // Restore handler as it is not persistent on Windows. | |
| signal(SIGINT, sigint_handler); | |
| 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); | |
| } | |
| #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'; | |
| } | |
| int main(int argc, char const *argv[]) { | |
| // Editline uses for example iswprint which is dependent on LC_CTYPE. | |
| std::setlocale(LC_ALL, ""); | |
| std::setlocale(LC_CTYPE, ""); | |
| // Setup LLVM signal handlers and make sure we call llvm_shutdown() on | |
| // destruction. | |
| llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false); | |
| #if !defined(__APPLE__) | |
| llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL | |
| " and include the crash backtrace.\n"); | |
| #else | |
| llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL | |
| " and include the crash report from " | |
| "~/Library/Logs/DiagnosticReports/.\n"); | |
| #endif | |
| // Parse arguments. | |
| LLDBOptTable T; | |
| unsigned MissingArgIndex; | |
| unsigned MissingArgCount; | |
| ArrayRef<const char *> arg_arr = ArrayRef(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; | |
| } | |
| // Check for missing argument error. | |
| if (MissingArgCount) { | |
| WithColor::error() << "argument to '" | |
| << input_args.getArgString(MissingArgIndex) | |
| << "' is missing\n"; | |
| } | |
| // Error out on unknown options. | |
| 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 defined(_WIN32) && LLDB_ENABLE_PYTHON | |
| // liblldb.dll has direct imports from python3xx.dll (the script interpreter | |
| // plugin is statically linked into liblldb), so the loader needs to find | |
| // python3xx.dll before lldb.exe's delay-load thunk for liblldb fires on the | |
| // first SB call below. The runtime loader is statically linked into lldb.exe | |
| // via lldbHost (it has no LLDB_API export), so this call does not itself | |
| // trigger the liblldb.dll load. | |
| llvm::Expected<lldb_private::ScriptInterpreterRuntimeLoader &> python_loader = | |
| lldb_private::ScriptInterpreterRuntimeLoader::Get(eScriptLanguagePython); | |
| if (!python_loader) | |
| WithColor::warning() << llvm::toString(python_loader.takeError()) << '\n'; | |
| else if (llvm::Error err = python_loader->Load()) | |
| WithColor::warning() << llvm::toString(std::move(err)) << '\n'; | |
| #endif | |
| SBError error = SBDebugger::InitializeWithErrorHandling(); | |
| if (error.Fail()) { | |
| WithColor::error() << "initialization failed: " << error.GetCString() | |
| << '\n'; | |
| return 1; | |
| } | |
| // Setup LLDB signal handlers once the debugger has been initialized. | |
| SBDebugger::PrintDiagnosticsOnError(); | |
| #ifdef _WIN32 | |
| signal(SIGINT, sigint_handler); | |
| #else | |
| signal(SIGPIPE, SIG_IGN); | |
| // Capture the main thread's id so the signal thread can target it. | |
| pthread_t main_thread = pthread_self(); | |
| // Set when the signal thread sends itself a SIGINT to wake the main thread. | |
| // The next callback invocation observes this flag and skips the work. A | |
| // plain bool is sufficient because the callback only ever runs on the | |
| // signal thread; it lives outside the lambda because MainLoopPosix copies | |
| // the callback on every dispatch, which would discard in-lambda state. | |
| bool skip_next_sigint = false; | |
| // Handle signals in a MainLoop running on a separate thread. | |
| MainLoop signal_loop; | |
| Status signal_status; | |
| auto sigint_handler = signal_loop.RegisterSignal( | |
| SIGINT, | |
| [&, main_thread](MainLoopBase &) { | |
| // Skip the self-sent wakeup SIGINT queued at the end of the previous | |
| // invocation. | |
| if (std::exchange(skip_next_sigint, false)) | |
| return; | |
| // Temporarily restore the default disposition so that a second SIGINT | |
| // delivered while DispatchInputInterrupt is running hard-terminates | |
| // the process. This preserves the "double Ctrl-C to force exit" | |
| // escape hatch users rely on when the debugger is unresponsive. | |
| struct sigaction old_action; | |
| struct sigaction new_action = {}; | |
| new_action.sa_handler = SIG_DFL; | |
| sigemptyset(&new_action.sa_mask); | |
| int ret = sigaction(SIGINT, &new_action, &old_action); | |
| UNUSED_IF_ASSERT_DISABLED(ret); | |
| assert(ret == 0 && "sigaction failed"); | |
| if (g_driver) | |
| g_driver->GetDebugger().DispatchInputInterrupt(); | |
| ret = sigaction(SIGINT, &old_action, nullptr); | |
| UNUSED_IF_ASSERT_DISABLED(ret); | |
| assert(ret == 0 && "sigaction failed"); | |
| // Wake the main thread so any blocking syscall (e.g. the Python REPL | |
| // waiting on input or sleeping) returns with EINTR. This lets Python | |
| // observe the pending interrupt queued by DispatchInputInterrupt and | |
| // raise KeyboardInterrupt. Flag the resulting callback invocation so | |
| // it's skipped rather than re-running DispatchInputInterrupt. | |
| skip_next_sigint = true; | |
| pthread_kill(main_thread, SIGINT); | |
| }, | |
| signal_status); | |
| assert(sigint_handler && signal_status.Success()); | |
| auto sigwinch_handler = signal_loop.RegisterSignal( | |
| SIGWINCH, | |
| [&](MainLoopBase &) { | |
| if (g_driver) | |
| g_driver->UpdateWindowSize(); | |
| }, | |
| signal_status); | |
| assert(sigwinch_handler && signal_status.Success()); | |
| auto sigtstp_handler = signal_loop.RegisterSignal( | |
| SIGTSTP, | |
| [&](MainLoopBase &) { | |
| if (g_driver) | |
| g_driver->GetDebugger().SaveInputTerminalState(); | |
| struct sigaction old_action; | |
| struct sigaction new_action = {}; | |
| new_action.sa_handler = SIG_DFL; | |
| sigemptyset(&new_action.sa_mask); | |
| sigaddset(&new_action.sa_mask, SIGTSTP); | |
| int ret = sigaction(SIGTSTP, &new_action, &old_action); | |
| UNUSED_IF_ASSERT_DISABLED(ret); | |
| assert(ret == 0 && "sigaction failed"); | |
| raise(SIGTSTP); | |
| ret = sigaction(SIGTSTP, &old_action, nullptr); | |
| UNUSED_IF_ASSERT_DISABLED(ret); | |
| assert(ret == 0 && "sigaction failed"); | |
| if (g_driver) | |
| g_driver->GetDebugger().RestoreInputTerminalState(); | |
| }, | |
| signal_status); | |
| assert(sigtstp_handler && signal_status.Success()); | |
| std::thread signal_thread([&] { signal_loop.Run(); }); | |
| #endif | |
| int exit_code = 0; | |
| // Create a scope for driver so that the driver object will destroy itself | |
| // before SBDebugger::Terminate() is called. | |
| { | |
| Driver driver; | |
| bool exiting = false; | |
| SBError error(driver.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(); | |
| } | |
| } | |
| // When terminating the debugger we have to wait on all the background tasks | |
| // to complete, which can take a while. Print a message when this takes longer | |
| // than 1 second. | |
| { | |
| std::future<void> future = | |
| std::async(std::launch::async, []() { SBDebugger::Terminate(); }); | |
| if (future.wait_for(std::chrono::seconds(1)) == std::future_status::timeout) | |
| fprintf(stderr, "Waiting for background tasks to complete...\n"); | |
| future.wait(); | |
| } | |
| #if !defined(_WIN32) | |
| // Try to interrupt the signal thread. If that succeeds, wait for it to exit. | |
| if (signal_loop.AddPendingCallback( | |
| [](MainLoopBase &loop) { loop.RequestTermination(); })) | |
| signal_thread.join(); | |
| #endif | |
| return exit_code; | |
| } |