| //===-- 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 "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 <future> |
| #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 { |
| 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 PREFIX(NAME, VALUE) \ |
| static constexpr StringLiteral NAME##_init[] = VALUE; \ |
| static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \ |
| std::size(NAME##_init) - 1); |
| #include "Options.inc" |
| #undef PREFIX |
| |
| static constexpr opt::OptTable::Info InfoTable[] = { |
| #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
| #include "Options.inc" |
| #undef OPTION |
| }; |
| |
| class LLDBOptTable : public opt::GenericOptTable { |
| public: |
| LLDBOptTable() : opt::GenericOptTable(InfoTable) {} |
| }; |
| } // 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)) { |
| 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::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 |
| // Disabling stdin buffering with MSVC's 2015 CRT exposes a bug in fgets |
| // which causes it to miss newlines depending on whether there have been an |
| // odd or even number of characters. Bug has been reported to MS via Connect. |
| ::setbuf(stdin, nullptr); |
| #endif |
| ::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); |
| |
| 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(); |
| |
| // 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 = 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; |
| } |
| |
| // 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()) { |
| 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::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 // Restore handler as it is not persistent on Windows |
| 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(); |
| |
| // Unblock the signal and remove our handler. |
| sigset_t set; |
| sigemptyset(&set); |
| sigaddset(&set, signo); |
| pthread_sigmask(SIG_UNBLOCK, &set, nullptr); |
| signal(signo, SIG_DFL); |
| |
| // Now re-raise the signal. We will immediately suspend... |
| raise(signo); |
| // ... and resume after a SIGCONT. |
| |
| // Now undo the modifications. |
| 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'; |
| } |
| |
| 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; |
| } |
| |
| 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(); |
| |
| signal(SIGINT, sigint_handler); |
| #if !defined(_WIN32) |
| signal(SIGPIPE, SIG_IGN); |
| signal(SIGWINCH, sigwinch_handler); |
| signal(SIGTSTP, sigtstp_handler); |
| #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(); |
| } |
| |
| return exit_code; |
| } |