diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index 8e608717a801..8e857e009a1b 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -1,418 +1,419 @@ //===-- Debugger.h ----------------------------------------------*- 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 // //===----------------------------------------------------------------------===// #ifndef liblldb_Debugger_h_ #define liblldb_Debugger_h_ #include #include #include #include "lldb/Core/FormatEntity.h" #include "lldb/Core/IOHandler.h" #include "lldb/Core/SourceManager.h" #include "lldb/Core/UserSettingsController.h" #include "lldb/Host/HostThread.h" #include "lldb/Host/Terminal.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Platform.h" #include "lldb/Target/TargetList.h" #include "lldb/Utility/Broadcaster.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/UserID.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private-enumerations.h" #include "lldb/lldb-private-types.h" #include "lldb/lldb-types.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/Threading.h" #include #include #include namespace llvm { class raw_ostream; } namespace lldb_private { class Address; class CommandInterpreter; class Process; class Stream; class SymbolContext; class Target; namespace repro { class DataRecorder; } /// \class Debugger Debugger.h "lldb/Core/Debugger.h" /// A class to manage flag bits. /// /// Provides a global root objects for the debugger core. class Debugger : public std::enable_shared_from_this, public UserID, public Properties { friend class SourceManager; // For GetSourceFileCache. public: ~Debugger() override; static lldb::DebuggerSP CreateInstance(lldb::LogOutputCallback log_callback = nullptr, void *baton = nullptr); static lldb::TargetSP FindTargetWithProcessID(lldb::pid_t pid); static lldb::TargetSP FindTargetWithProcess(Process *process); static void Initialize(LoadPluginCallbackType load_plugin_callback); static void Terminate(); static void SettingsInitialize(); static void SettingsTerminate(); static void Destroy(lldb::DebuggerSP &debugger_sp); static lldb::DebuggerSP FindDebuggerWithID(lldb::user_id_t id); static lldb::DebuggerSP FindDebuggerWithInstanceName(ConstString instance_name); static size_t GetNumDebuggers(); static lldb::DebuggerSP GetDebuggerAtIndex(size_t index); static bool FormatDisassemblerAddress(const FormatEntity::Entry *format, const SymbolContext *sc, const SymbolContext *prev_sc, const ExecutionContext *exe_ctx, const Address *addr, Stream &s); void Clear(); bool GetAsyncExecution(); void SetAsyncExecution(bool async); lldb::StreamFileSP GetInputFile() { return m_input_file_sp; } lldb::StreamFileSP GetOutputFile() { return m_output_file_sp; } lldb::StreamFileSP GetErrorFile() { return m_error_file_sp; } repro::DataRecorder *GetInputRecorder(); void SetInputFileHandle(FILE *fh, bool tranfer_ownership, repro::DataRecorder *recorder = nullptr); void SetOutputFileHandle(FILE *fh, bool tranfer_ownership); void SetErrorFileHandle(FILE *fh, bool tranfer_ownership); void SaveInputTerminalState(); void RestoreInputTerminalState(); lldb::StreamSP GetAsyncOutputStream(); lldb::StreamSP GetAsyncErrorStream(); CommandInterpreter &GetCommandInterpreter() { assert(m_command_interpreter_up.get()); return *m_command_interpreter_up; } ScriptInterpreter *GetScriptInterpreter(bool can_create = true); lldb::ListenerSP GetListener() { return m_listener_sp; } // This returns the Debugger's scratch source manager. It won't be able to // look up files in debug information, but it can look up files by absolute // path and display them to you. To get the target's source manager, call // GetSourceManager on the target instead. SourceManager &GetSourceManager(); lldb::TargetSP GetSelectedTarget() { return m_target_list.GetSelectedTarget(); } ExecutionContext GetSelectedExecutionContext(); /// Get accessor for the target list. /// /// The target list is part of the global debugger object. This the single /// debugger shared instance to control where targets get created and to /// allow for tracking and searching for targets based on certain criteria. /// /// \return /// A global shared target list. TargetList &GetTargetList() { return m_target_list; } PlatformList &GetPlatformList() { return m_platform_list; } void DispatchInputInterrupt(); void DispatchInputEndOfFile(); // If any of the streams are not set, set them to the in/out/err stream of // the top most input reader to ensure they at least have something void AdoptTopIOHandlerFilesIfInvalid(lldb::StreamFileSP &in, lldb::StreamFileSP &out, lldb::StreamFileSP &err); void PushIOHandler(const lldb::IOHandlerSP &reader_sp, bool cancel_top_handler = true); bool PopIOHandler(const lldb::IOHandlerSP &reader_sp); // Synchronously run an input reader until it is done void RunIOHandler(const lldb::IOHandlerSP &reader_sp); bool IsTopIOHandler(const lldb::IOHandlerSP &reader_sp); bool CheckTopIOHandlerTypes(IOHandler::Type top_type, IOHandler::Type second_top_type); void PrintAsync(const char *s, size_t len, bool is_stdout); ConstString GetTopIOHandlerControlSequence(char ch); const char *GetIOHandlerCommandPrefix(); const char *GetIOHandlerHelpPrologue(); void ClearIOHandlers(); bool GetCloseInputOnEOF() const; void SetCloseInputOnEOF(bool b); bool EnableLog(llvm::StringRef channel, llvm::ArrayRef categories, llvm::StringRef log_file, uint32_t log_options, llvm::raw_ostream &error_stream); void SetLoggingCallback(lldb::LogOutputCallback log_callback, void *baton); // Properties Functions enum StopDisassemblyType { eStopDisassemblyTypeNever = 0, eStopDisassemblyTypeNoDebugInfo, eStopDisassemblyTypeNoSource, eStopDisassemblyTypeAlways }; Status SetPropertyValue(const ExecutionContext *exe_ctx, VarSetOperationType op, llvm::StringRef property_path, llvm::StringRef value) override; bool GetAutoConfirm() const; const FormatEntity::Entry *GetDisassemblyFormat() const; const FormatEntity::Entry *GetFrameFormat() const; const FormatEntity::Entry *GetFrameFormatUnique() const; const FormatEntity::Entry *GetThreadFormat() const; const FormatEntity::Entry *GetThreadStopFormat() const; lldb::ScriptLanguage GetScriptLanguage() const; bool SetScriptLanguage(lldb::ScriptLanguage script_lang); uint32_t GetTerminalWidth() const; bool SetTerminalWidth(uint32_t term_width); llvm::StringRef GetPrompt() const; void SetPrompt(llvm::StringRef p); void SetPrompt(const char *) = delete; llvm::StringRef GetReproducerPath() const; bool GetUseExternalEditor() const; bool SetUseExternalEditor(bool use_external_editor_p); bool GetUseColor() const; bool SetUseColor(bool use_color); bool GetHighlightSource() const; lldb::StopShowColumn GetStopShowColumn() const; llvm::StringRef GetStopShowColumnAnsiPrefix() const; llvm::StringRef GetStopShowColumnAnsiSuffix() const; uint32_t GetStopSourceLineCount(bool before) const; StopDisassemblyType GetStopDisassemblyDisplay() const; uint32_t GetDisassemblyLineCount() const; bool GetAutoOneLineSummaries() const; bool GetAutoIndent() const; bool SetAutoIndent(bool b); bool GetPrintDecls() const; bool SetPrintDecls(bool b); uint32_t GetTabSize() const; bool SetTabSize(uint32_t tab_size); bool GetEscapeNonPrintables() const; bool GetNotifyVoid() const; ConstString GetInstanceName() { return m_instance_name; } bool LoadPlugin(const FileSpec &spec, Status &error); void ExecuteIOHandlers(); bool IsForwardingEvents(); void EnableForwardEvents(const lldb::ListenerSP &listener_sp); void CancelForwardEvents(const lldb::ListenerSP &listener_sp); bool IsHandlingEvents() const { return m_event_handler_thread.IsJoinable(); } Status RunREPL(lldb::LanguageType language, const char *repl_options); // This is for use in the command interpreter, when you either want the // selected target, or if no target is present you want to prime the dummy // target with entities that will be copied over to new targets. Target *GetSelectedOrDummyTarget(bool prefer_dummy = false); Target *GetDummyTarget(); lldb::BroadcasterManagerSP GetBroadcasterManager() { return m_broadcaster_manager_sp; } protected: friend class CommandInterpreter; friend class REPL; bool StartEventHandlerThread(); void StopEventHandlerThread(); static lldb::thread_result_t EventHandlerThread(lldb::thread_arg_t arg); bool HasIOHandlerThread(); bool StartIOHandlerThread(); void StopIOHandlerThread(); void JoinIOHandlerThread(); static lldb::thread_result_t IOHandlerThread(lldb::thread_arg_t arg); void DefaultEventHandler(); void HandleBreakpointEvent(const lldb::EventSP &event_sp); void HandleProcessEvent(const lldb::EventSP &event_sp); void HandleThreadEvent(const lldb::EventSP &event_sp); - size_t GetProcessSTDOUT(Process *process, Stream *stream); - - size_t GetProcessSTDERR(Process *process, Stream *stream); + // Ensures two threads don't attempt to flush process output in parallel. + std::mutex m_output_flush_mutex; + void FlushProcessOutput(Process &process, bool flush_stdout, + bool flush_stderr); SourceManager::SourceFileCache &GetSourceFileCache() { return m_source_file_cache; } void InstanceInitialize(); lldb::StreamFileSP m_input_file_sp; lldb::StreamFileSP m_output_file_sp; lldb::StreamFileSP m_error_file_sp; /// Used for shadowing the input file when capturing a reproducer. repro::DataRecorder *m_input_recorder; lldb::BroadcasterManagerSP m_broadcaster_manager_sp; // The debugger acts as a // broadcaster manager of // last resort. // It needs to get constructed before the target_list or any other member // that might want to broadcast through the debugger. TerminalState m_terminal_state; TargetList m_target_list; PlatformList m_platform_list; lldb::ListenerSP m_listener_sp; std::unique_ptr m_source_manager_up; // This is a scratch // source manager that we // return if we have no // targets. SourceManager::SourceFileCache m_source_file_cache; // All the source managers // for targets created in // this debugger used this // shared // source file cache. std::unique_ptr m_command_interpreter_up; lldb::ScriptInterpreterSP m_script_interpreter_sp; std::recursive_mutex m_script_interpreter_mutex; IOHandlerStack m_input_reader_stack; llvm::StringMap> m_log_streams; std::shared_ptr m_log_callback_stream_sp; ConstString m_instance_name; static LoadPluginCallbackType g_load_plugin_callback; typedef std::vector LoadedPluginsList; LoadedPluginsList m_loaded_plugins; HostThread m_event_handler_thread; HostThread m_io_handler_thread; Broadcaster m_sync_broadcaster; lldb::ListenerSP m_forward_listener_sp; llvm::once_flag m_clear_once; // Events for m_sync_broadcaster enum { eBroadcastBitEventThreadIsListening = (1 << 0), }; private: // Use Debugger::CreateInstance() to get a shared pointer to a new debugger // object Debugger(lldb::LogOutputCallback m_log_callback, void *baton); DISALLOW_COPY_AND_ASSIGN(Debugger); }; } // namespace lldb_private #endif // liblldb_Debugger_h_ diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index d738bbc88799..3d704d1fc1a9 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -1,605 +1,605 @@ //===-- CommandInterpreter.h ------------------------------------*- 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 // //===----------------------------------------------------------------------===// #ifndef liblldb_CommandInterpreter_h_ #define liblldb_CommandInterpreter_h_ #include "lldb/Core/Debugger.h" #include "lldb/Core/IOHandler.h" #include "lldb/Interpreter/CommandAlias.h" #include "lldb/Interpreter/CommandHistory.h" #include "lldb/Interpreter/CommandObject.h" #include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Utility/Args.h" #include "lldb/Utility/Broadcaster.h" #include "lldb/Utility/CompletionRequest.h" #include "lldb/Utility/Event.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/StringList.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private.h" #include namespace lldb_private { class CommandInterpreterRunOptions { public: /// Construct a CommandInterpreterRunOptions object. This class is used to /// control all the instances where we run multiple commands, e.g. /// HandleCommands, HandleCommandsFromFile, RunCommandInterpreter. /// /// The meanings of the options in this object are: /// /// \param[in] stop_on_continue /// If \b true, execution will end on the first command that causes the /// process in the execution context to continue. If \b false, we won't /// check the execution status. /// \param[in] stop_on_error /// If \b true, execution will end on the first command that causes an /// error. /// \param[in] stop_on_crash /// If \b true, when a command causes the target to run, and the end of the /// run is a signal or exception, stop executing the commands. /// \param[in] echo_commands /// If \b true, echo the command before executing it. If \b false, execute /// silently. /// \param[in] echo_comments /// If \b true, echo command even if it is a pure comment line. If /// \b false, print no ouput in this case. This setting has an effect only /// if \param echo_commands is \b true. /// \param[in] print_results /// If \b true and the command succeeds, print the results of the command /// after executing it. If \b false, execute silently. /// \param[in] print_errors /// If \b true and the command fails, print the results of the command /// after executing it. If \b false, execute silently. /// \param[in] add_to_history /// If \b true add the commands to the command history. If \b false, don't /// add them. CommandInterpreterRunOptions(LazyBool stop_on_continue, LazyBool stop_on_error, LazyBool stop_on_crash, LazyBool echo_commands, LazyBool echo_comments, LazyBool print_results, LazyBool print_errors, LazyBool add_to_history) : m_stop_on_continue(stop_on_continue), m_stop_on_error(stop_on_error), m_stop_on_crash(stop_on_crash), m_echo_commands(echo_commands), m_echo_comment_commands(echo_comments), m_print_results(print_results), m_print_errors(print_errors), m_add_to_history(add_to_history) {} CommandInterpreterRunOptions() : m_stop_on_continue(eLazyBoolCalculate), m_stop_on_error(eLazyBoolCalculate), m_stop_on_crash(eLazyBoolCalculate), m_echo_commands(eLazyBoolCalculate), m_echo_comment_commands(eLazyBoolCalculate), m_print_results(eLazyBoolCalculate), m_print_errors(eLazyBoolCalculate), m_add_to_history(eLazyBoolCalculate) {} void SetSilent(bool silent) { LazyBool value = silent ? eLazyBoolNo : eLazyBoolYes; m_print_results = value; m_print_errors = value; m_echo_commands = value; m_echo_comment_commands = value; m_add_to_history = value; } // These return the default behaviors if the behavior is not // eLazyBoolCalculate. But I've also left the ivars public since for // different ways of running the interpreter you might want to force // different defaults... In that case, just grab the LazyBool ivars directly // and do what you want with eLazyBoolCalculate. bool GetStopOnContinue() const { return DefaultToNo(m_stop_on_continue); } void SetStopOnContinue(bool stop_on_continue) { m_stop_on_continue = stop_on_continue ? eLazyBoolYes : eLazyBoolNo; } bool GetStopOnError() const { return DefaultToNo(m_stop_on_error); } void SetStopOnError(bool stop_on_error) { m_stop_on_error = stop_on_error ? eLazyBoolYes : eLazyBoolNo; } bool GetStopOnCrash() const { return DefaultToNo(m_stop_on_crash); } void SetStopOnCrash(bool stop_on_crash) { m_stop_on_crash = stop_on_crash ? eLazyBoolYes : eLazyBoolNo; } bool GetEchoCommands() const { return DefaultToYes(m_echo_commands); } void SetEchoCommands(bool echo_commands) { m_echo_commands = echo_commands ? eLazyBoolYes : eLazyBoolNo; } bool GetEchoCommentCommands() const { return DefaultToYes(m_echo_comment_commands); } void SetEchoCommentCommands(bool echo_comments) { m_echo_comment_commands = echo_comments ? eLazyBoolYes : eLazyBoolNo; } bool GetPrintResults() const { return DefaultToYes(m_print_results); } void SetPrintResults(bool print_results) { m_print_results = print_results ? eLazyBoolYes : eLazyBoolNo; } bool GetPrintErrors() const { return DefaultToYes(m_print_errors); } void SetPrintErrors(bool print_errors) { m_print_errors = print_errors ? eLazyBoolYes : eLazyBoolNo; } bool GetAddToHistory() const { return DefaultToYes(m_add_to_history); } void SetAddToHistory(bool add_to_history) { m_add_to_history = add_to_history ? eLazyBoolYes : eLazyBoolNo; } LazyBool m_stop_on_continue; LazyBool m_stop_on_error; LazyBool m_stop_on_crash; LazyBool m_echo_commands; LazyBool m_echo_comment_commands; LazyBool m_print_results; LazyBool m_print_errors; LazyBool m_add_to_history; private: static bool DefaultToYes(LazyBool flag) { switch (flag) { case eLazyBoolNo: return false; default: return true; } } static bool DefaultToNo(LazyBool flag) { switch (flag) { case eLazyBoolYes: return true; default: return false; } } }; class CommandInterpreter : public Broadcaster, public Properties, public IOHandlerDelegate { public: enum { eBroadcastBitThreadShouldExit = (1 << 0), eBroadcastBitResetPrompt = (1 << 1), eBroadcastBitQuitCommandReceived = (1 << 2), // User entered quit eBroadcastBitAsynchronousOutputData = (1 << 3), eBroadcastBitAsynchronousErrorData = (1 << 4) }; enum ChildrenTruncatedWarningStatus // tristate boolean to manage children // truncation warning { eNoTruncation = 0, // never truncated eUnwarnedTruncation = 1, // truncated but did not notify eWarnedTruncation = 2 // truncated and notified }; enum CommandTypes { eCommandTypesBuiltin = 0x0001, // native commands such as "frame" eCommandTypesUserDef = 0x0002, // scripted commands eCommandTypesAliases = 0x0004, // aliases such as "po" eCommandTypesHidden = 0x0008, // commands prefixed with an underscore eCommandTypesAllThem = 0xFFFF // all commands }; CommandInterpreter(Debugger &debugger, bool synchronous_execution); ~CommandInterpreter() override; // These two functions fill out the Broadcaster interface: static ConstString &GetStaticBroadcasterClass(); ConstString &GetBroadcasterClass() const override { return GetStaticBroadcasterClass(); } void SourceInitFileCwd(CommandReturnObject &result); void SourceInitFileHome(CommandReturnObject &result); bool AddCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp, bool can_replace); bool AddUserCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp, bool can_replace); lldb::CommandObjectSP GetCommandSPExact(llvm::StringRef cmd, bool include_aliases) const; CommandObject *GetCommandObject(llvm::StringRef cmd, StringList *matches = nullptr, StringList *descriptions = nullptr) const; bool CommandExists(llvm::StringRef cmd) const; bool AliasExists(llvm::StringRef cmd) const; bool UserCommandExists(llvm::StringRef cmd) const; CommandAlias *AddAlias(llvm::StringRef alias_name, lldb::CommandObjectSP &command_obj_sp, llvm::StringRef args_string = llvm::StringRef()); // Remove a command if it is removable (python or regex command) bool RemoveCommand(llvm::StringRef cmd); bool RemoveAlias(llvm::StringRef alias_name); bool GetAliasFullName(llvm::StringRef cmd, std::string &full_name) const; bool RemoveUser(llvm::StringRef alias_name); void RemoveAllUser() { m_user_dict.clear(); } const CommandAlias *GetAlias(llvm::StringRef alias_name) const; CommandObject *BuildAliasResult(llvm::StringRef alias_name, std::string &raw_input_string, std::string &alias_result, CommandReturnObject &result); bool HandleCommand(const char *command_line, LazyBool add_to_history, CommandReturnObject &result, ExecutionContext *override_context = nullptr, bool repeat_on_empty_command = true, bool no_context_switching = false); bool WasInterrupted() const; /// Execute a list of commands in sequence. /// /// \param[in] commands /// The list of commands to execute. /// \param[in,out] context /// The execution context in which to run the commands. Can be nullptr in /// which case the default /// context will be used. /// \param[in] options /// This object holds the options used to control when to stop, whether to /// execute commands, /// etc. /// \param[out] result /// This is marked as succeeding with no output if all commands execute /// safely, /// and failed with some explanation if we aborted executing the commands /// at some point. void HandleCommands(const StringList &commands, ExecutionContext *context, CommandInterpreterRunOptions &options, CommandReturnObject &result); /// Execute a list of commands from a file. /// /// \param[in] file /// The file from which to read in commands. /// \param[in,out] context /// The execution context in which to run the commands. Can be nullptr in /// which case the default /// context will be used. /// \param[in] options /// This object holds the options used to control when to stop, whether to /// execute commands, /// etc. /// \param[out] result /// This is marked as succeeding with no output if all commands execute /// safely, /// and failed with some explanation if we aborted executing the commands /// at some point. void HandleCommandsFromFile(FileSpec &file, ExecutionContext *context, CommandInterpreterRunOptions &options, CommandReturnObject &result); CommandObject *GetCommandObjectForCommand(llvm::StringRef &command_line); // This handles command line completion. You are given a pointer to the // command string buffer, to the current cursor, and to the end of the string // (in case it is not NULL terminated). You also passed in an StringList // object to fill with the returns. The first element of the array will be // filled with the string that you would need to insert at the cursor point // to complete the cursor point to the longest common matching prefix. If you // want to limit the number of elements returned, set max_return_elements to // the number of elements you want returned. Otherwise set // max_return_elements to -1. If you want to start some way into the match // list, then set match_start_point to the desired start point. Returns: -1 // if the completion character should be inserted -2 if the entire command // line should be deleted and replaced with matches.GetStringAtIndex(0) // INT_MAX if the number of matches is > max_return_elements, but it is // expensive to compute. Otherwise, returns the number of matches. // // FIXME: Only max_return_elements == -1 is supported at present. int HandleCompletion(const char *current_line, const char *cursor, const char *last_char, StringList &matches, StringList &descriptions); // This version just returns matches, and doesn't compute the substring. It // is here so the Help command can call it for the first argument. It uses // a CompletionRequest for simplicity reasons. int HandleCompletionMatches(CompletionRequest &request); int GetCommandNamesMatchingPartialString(const char *cmd_cstr, bool include_aliases, StringList &matches, StringList &descriptions); void GetHelp(CommandReturnObject &result, uint32_t types = eCommandTypesAllThem); void GetAliasHelp(const char *alias_name, StreamString &help_string); void OutputFormattedHelpText(Stream &strm, llvm::StringRef prefix, llvm::StringRef help_text); void OutputFormattedHelpText(Stream &stream, llvm::StringRef command_word, llvm::StringRef separator, llvm::StringRef help_text, size_t max_word_len); // this mimics OutputFormattedHelpText but it does perform a much simpler // formatting, basically ensuring line alignment. This is only good if you // have some complicated layout for your help text and want as little help as // reasonable in properly displaying it. Most of the times, you simply want // to type some text and have it printed in a reasonable way on screen. If // so, use OutputFormattedHelpText void OutputHelpText(Stream &stream, llvm::StringRef command_word, llvm::StringRef separator, llvm::StringRef help_text, uint32_t max_word_len); Debugger &GetDebugger() { return m_debugger; } ExecutionContext GetExecutionContext() { const bool thread_and_frame_only_if_stopped = true; return m_exe_ctx_ref.Lock(thread_and_frame_only_if_stopped); } void UpdateExecutionContext(ExecutionContext *override_context); lldb::PlatformSP GetPlatform(bool prefer_target_platform); const char *ProcessEmbeddedScriptCommands(const char *arg); void UpdatePrompt(llvm::StringRef prompt); bool Confirm(llvm::StringRef message, bool default_answer); void LoadCommandDictionary(); void Initialize(); void Clear(); bool HasCommands() const; bool HasAliases() const; bool HasUserCommands() const; bool HasAliasOptions() const; void BuildAliasCommandArgs(CommandObject *alias_cmd_obj, const char *alias_name, Args &cmd_args, std::string &raw_input_string, CommandReturnObject &result); int GetOptionArgumentPosition(const char *in_string); void SkipLLDBInitFiles(bool skip_lldbinit_files) { m_skip_lldbinit_files = skip_lldbinit_files; } void SkipAppInitFiles(bool skip_app_init_files) { m_skip_app_init_files = skip_app_init_files; } bool GetSynchronous(); void FindCommandsForApropos(llvm::StringRef word, StringList &commands_found, StringList &commands_help, bool search_builtin_commands, bool search_user_commands, bool search_alias_commands); bool GetBatchCommandMode() { return m_batch_command_mode; } bool SetBatchCommandMode(bool value) { const bool old_value = m_batch_command_mode; m_batch_command_mode = value; return old_value; } void ChildrenTruncated() { if (m_truncation_warning == eNoTruncation) m_truncation_warning = eUnwarnedTruncation; } bool TruncationWarningNecessary() { return (m_truncation_warning == eUnwarnedTruncation); } void TruncationWarningGiven() { m_truncation_warning = eWarnedTruncation; } const char *TruncationWarningText() { return "*** Some of your variables have more members than the debugger " "will show by default. To show all of them, you can either use the " "--show-all-children option to %s or raise the limit by changing " "the target.max-children-count setting.\n"; } CommandHistory &GetCommandHistory() { return m_command_history; } bool IsActive(); void RunCommandInterpreter(bool auto_handle_events, bool spawn_thread, CommandInterpreterRunOptions &options); void GetLLDBCommandsFromIOHandler(const char *prompt, IOHandlerDelegate &delegate, bool asynchronously, void *baton); void GetPythonCommandsFromIOHandler(const char *prompt, IOHandlerDelegate &delegate, bool asynchronously, void *baton); const char *GetCommandPrefix(); // Properties bool GetExpandRegexAliases() const; bool GetPromptOnQuit() const; void SetPromptOnQuit(bool b); bool GetEchoCommands() const; void SetEchoCommands(bool b); bool GetEchoCommentCommands() const; void SetEchoCommentCommands(bool b); /// Specify if the command interpreter should allow that the user can /// specify a custom exit code when calling 'quit'. void AllowExitCodeOnQuit(bool allow); /// Sets the exit code for the quit command. /// \param[in] exit_code /// The exit code that the driver should return on exit. /// \return True if the exit code was successfully set; false if the /// interpreter doesn't allow custom exit codes. /// \see AllowExitCodeOnQuit LLVM_NODISCARD bool SetQuitExitCode(int exit_code); /// Returns the exit code that the user has specified when running the /// 'quit' command. /// \param[out] exited /// Set to true if the user has called quit with a custom exit code. int GetQuitExitCode(bool &exited) const; void ResolveCommand(const char *command_line, CommandReturnObject &result); bool GetStopCmdSourceOnError() const; uint32_t GetNumErrors() const { return m_num_errors; } bool GetQuitRequested() const { return m_quit_requested; } lldb::IOHandlerSP GetIOHandler(bool force_create = false, CommandInterpreterRunOptions *options = nullptr); bool GetStoppedForCrash() const { return m_stopped_for_crash; } bool GetSpaceReplPrompts() const; protected: friend class Debugger; // IOHandlerDelegate functions void IOHandlerInputComplete(IOHandler &io_handler, std::string &line) override; ConstString IOHandlerGetControlSequence(char ch) override { if (ch == 'd') return ConstString("quit\n"); return ConstString(); } bool IOHandlerInterrupt(IOHandler &io_handler) override; - size_t GetProcessOutput(); + void GetProcessOutput(); void SetSynchronous(bool value); lldb::CommandObjectSP GetCommandSP(llvm::StringRef cmd, bool include_aliases = true, bool exact = true, StringList *matches = nullptr, StringList *descriptions = nullptr) const; private: Status PreprocessCommand(std::string &command); void SourceInitFile(FileSpec file, CommandReturnObject &result); // Completely resolves aliases and abbreviations, returning a pointer to the // final command object and updating command_line to the fully substituted // and translated command. CommandObject *ResolveCommandImpl(std::string &command_line, CommandReturnObject &result); void FindCommandsForApropos(llvm::StringRef word, StringList &commands_found, StringList &commands_help, CommandObject::CommandMap &command_map); // An interruptible wrapper around the stream output void PrintCommandOutput(Stream &stream, llvm::StringRef str); bool EchoCommandNonInteractive(llvm::StringRef line, const Flags &io_handler_flags) const; // A very simple state machine which models the command handling transitions enum class CommandHandlingState { eIdle, eInProgress, eInterrupted, }; std::atomic m_command_state{ CommandHandlingState::eIdle}; int m_iohandler_nesting_level = 0; void StartHandlingCommand(); void FinishHandlingCommand(); bool InterruptCommand(); Debugger &m_debugger; // The debugger session that this interpreter is // associated with ExecutionContextRef m_exe_ctx_ref; // The current execution context to use // when handling commands bool m_synchronous_execution; bool m_skip_lldbinit_files; bool m_skip_app_init_files; CommandObject::CommandMap m_command_dict; // Stores basic built-in commands // (they cannot be deleted, removed // or overwritten). CommandObject::CommandMap m_alias_dict; // Stores user aliases/abbreviations for commands CommandObject::CommandMap m_user_dict; // Stores user-defined commands CommandHistory m_command_history; std::string m_repeat_command; // Stores the command that will be executed for // an empty command string. lldb::IOHandlerSP m_command_io_handler_sp; char m_comment_char; bool m_batch_command_mode; ChildrenTruncatedWarningStatus m_truncation_warning; // Whether we truncated // children and whether // the user has been told uint32_t m_command_source_depth; std::vector m_command_source_flags; uint32_t m_num_errors; bool m_quit_requested; bool m_stopped_for_crash; // The exit code the user has requested when calling the 'quit' command. // No value means the user hasn't set a custom exit code so far. llvm::Optional m_quit_exit_code; // If the driver is accepts custom exit codes for the 'quit' command. bool m_allow_exit_code = false; }; } // namespace lldb_private #endif // liblldb_CommandInterpreter_h_ diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index ca19ad66bb74..5efe78635d23 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -1,1665 +1,1622 @@ //===-- Debugger.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 "lldb/Core/Debugger.h" #include "lldb/Breakpoint/Breakpoint.h" #include "lldb/Core/FormatEntity.h" #include "lldb/Core/Mangled.h" #include "lldb/Core/ModuleList.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/StreamAsynchronousIO.h" #include "lldb/Core/StreamFile.h" #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/Expression/REPL.h" #include "lldb/Host/File.h" #include "lldb/Host/FileSystem.h" #include "lldb/Host/HostInfo.h" #include "lldb/Host/Terminal.h" #include "lldb/Host/ThreadLauncher.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/OptionValue.h" #include "lldb/Interpreter/OptionValueProperties.h" #include "lldb/Interpreter/OptionValueSInt64.h" #include "lldb/Interpreter/OptionValueString.h" #include "lldb/Interpreter/Property.h" #include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Target/Language.h" #include "lldb/Target/Process.h" #include "lldb/Target/StructuredDataPlugin.h" #include "lldb/Target/Target.h" #include "lldb/Target/TargetList.h" #include "lldb/Target/Thread.h" #include "lldb/Target/ThreadList.h" #include "lldb/Utility/AnsiTerminal.h" #include "lldb/Utility/Event.h" #include "lldb/Utility/Listener.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/Reproducer.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamCallback.h" #include "lldb/Utility/StreamString.h" #if defined(_WIN32) #include "lldb/Host/windows/PosixApi.h" #include "lldb/Host/windows/windows.h" #endif #include "llvm/ADT/None.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Process.h" #include "llvm/Support/Threading.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include #include #include #include namespace lldb_private { class Address; } using namespace lldb; using namespace lldb_private; static lldb::user_id_t g_unique_id = 1; static size_t g_debugger_event_thread_stack_bytes = 8 * 1024 * 1024; #pragma mark Static Functions typedef std::vector DebuggerList; static std::recursive_mutex *g_debugger_list_mutex_ptr = nullptr; // NOTE: intentional leak to avoid issues with C++ destructor chain static DebuggerList *g_debugger_list_ptr = nullptr; // NOTE: intentional leak to avoid issues with C++ destructor chain static constexpr OptionEnumValueElement g_show_disassembly_enum_values[] = { {Debugger::eStopDisassemblyTypeNever, "never", "Never show disassembly when displaying a stop context."}, {Debugger::eStopDisassemblyTypeNoDebugInfo, "no-debuginfo", "Show disassembly when there is no debug information."}, {Debugger::eStopDisassemblyTypeNoSource, "no-source", "Show disassembly when there is no source information, or the source file " "is missing when displaying a stop context."}, {Debugger::eStopDisassemblyTypeAlways, "always", "Always show disassembly when displaying a stop context."} }; static constexpr OptionEnumValueElement g_language_enumerators[] = { {eScriptLanguageNone, "none", "Disable scripting languages."}, {eScriptLanguagePython, "python", "Select python as the default scripting language."}, {eScriptLanguageDefault, "default", "Select the lldb default as the default scripting language."} }; #define MODULE_WITH_FUNC \ "{ " \ "${module.file.basename}{`${function.name-with-args}" \ "{${frame.no-debug}${function.pc-offset}}}}" #define MODULE_WITH_FUNC_NO_ARGS \ "{ " \ "${module.file.basename}{`${function.name-without-args}" \ "{${frame.no-debug}${function.pc-offset}}}}" #define FILE_AND_LINE \ "{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}" \ ":${ansi.fg.yellow}${line.number}${ansi.normal}" \ "{:${ansi.fg.yellow}${line.column}${ansi.normal}}}" #define IS_OPTIMIZED "{${function.is-optimized} [opt]}" #define IS_ARTIFICIAL "{${frame.is-artificial} [artificial]}" #define DEFAULT_THREAD_FORMAT \ "thread #${thread.index}: tid = ${thread.id%tid}" \ "{, ${frame.pc}}" MODULE_WITH_FUNC FILE_AND_LINE \ "{, name = ${ansi.fg.green}'${thread.name}'${ansi.normal}}" \ "{, queue = ${ansi.fg.green}'${thread.queue}'${ansi.normal}}" \ "{, activity = " \ "${ansi.fg.green}'${thread.info.activity.name}'${ansi.normal}}" \ "{, ${thread.info.trace_messages} messages}" \ "{, stop reason = ${ansi.fg.red}${thread.stop-reason}${ansi.normal}}" \ "{\\nReturn value: ${thread.return-value}}" \ "{\\nCompleted expression: ${thread.completed-expression}}" \ "\\n" #define DEFAULT_THREAD_STOP_FORMAT \ "thread #${thread.index}{, name = '${thread.name}'}" \ "{, queue = ${ansi.fg.green}'${thread.queue}'${ansi.normal}}" \ "{, activity = " \ "${ansi.fg.green}'${thread.info.activity.name}'${ansi.normal}}" \ "{, ${thread.info.trace_messages} messages}" \ "{, stop reason = ${ansi.fg.red}${thread.stop-reason}${ansi.normal}}" \ "{\\nReturn value: ${thread.return-value}}" \ "{\\nCompleted expression: ${thread.completed-expression}}" \ "\\n" #define DEFAULT_FRAME_FORMAT \ "frame #${frame.index}: " \ "${ansi.fg.yellow}${frame.pc}${ansi.normal}" MODULE_WITH_FUNC FILE_AND_LINE \ IS_OPTIMIZED IS_ARTIFICIAL "\\n" #define DEFAULT_FRAME_FORMAT_NO_ARGS \ "frame #${frame.index}: " \ "${ansi.fg.yellow}${frame.pc}${ansi.normal}" MODULE_WITH_FUNC_NO_ARGS \ FILE_AND_LINE IS_OPTIMIZED IS_ARTIFICIAL "\\n" // Three parts to this disassembly format specification: // 1. If this is a new function/symbol (no previous symbol/function), print // dylib`funcname:\n // 2. If this is a symbol context change (different from previous // symbol/function), print // dylib`funcname:\n // 3. print // address <+offset>: #define DEFAULT_DISASSEMBLY_FORMAT \ "{${function.initial-function}{${module.file.basename}`}{${function.name-" \ "without-args}}:\\n}{${function.changed}\\n{${module.file.basename}`}{${" \ "function.name-without-args}}:\\n}{${current-pc-arrow} " \ "}${addr-file-or-load}{ " \ "<${function.concrete-only-addr-offset-no-padding}>}: " // gdb's disassembly format can be emulated with ${current-pc-arrow}${addr- // file-or-load}{ <${function.name-without-args}${function.concrete-only-addr- // offset-no-padding}>}: // lldb's original format for disassembly would look like this format string - // {${function.initial-function}{${module.file.basename}`}{${function.name- // without- // args}}:\n}{${function.changed}\n{${module.file.basename}`}{${function.name- // without-args}}:\n}{${current-pc-arrow} }{${addr-file-or-load}}: static constexpr OptionEnumValueElement s_stop_show_column_values[] = { {eStopShowColumnAnsiOrCaret, "ansi-or-caret", "Highlight the stop column with ANSI terminal codes when color/ANSI mode " "is enabled; otherwise, fall back to using a text-only caret (^) as if " "\"caret-only\" mode was selected."}, {eStopShowColumnAnsi, "ansi", "Highlight the stop column with ANSI " "terminal codes when running LLDB with " "color/ANSI enabled."}, {eStopShowColumnCaret, "caret", "Highlight the stop column with a caret character (^) underneath the stop " "column. This method introduces a new line in source listings that " "display thread stop locations."}, {eStopShowColumnNone, "none", "Do not highlight the stop column."}}; #define LLDB_PROPERTIES_debugger #include "CoreProperties.inc" enum { #define LLDB_PROPERTIES_debugger #include "CorePropertiesEnum.inc" }; LoadPluginCallbackType Debugger::g_load_plugin_callback = nullptr; Status Debugger::SetPropertyValue(const ExecutionContext *exe_ctx, VarSetOperationType op, llvm::StringRef property_path, llvm::StringRef value) { bool is_load_script = (property_path == "target.load-script-from-symbol-file"); bool is_escape_non_printables = (property_path == "escape-non-printables"); TargetSP target_sp; LoadScriptFromSymFile load_script_old_value; if (is_load_script && exe_ctx->GetTargetSP()) { target_sp = exe_ctx->GetTargetSP(); load_script_old_value = target_sp->TargetProperties::GetLoadScriptFromSymbolFile(); } Status error(Properties::SetPropertyValue(exe_ctx, op, property_path, value)); if (error.Success()) { // FIXME it would be nice to have "on-change" callbacks for properties if (property_path == g_debugger_properties[ePropertyPrompt].name) { llvm::StringRef new_prompt = GetPrompt(); std::string str = lldb_utility::ansi::FormatAnsiTerminalCodes( new_prompt, GetUseColor()); if (str.length()) new_prompt = str; GetCommandInterpreter().UpdatePrompt(new_prompt); auto bytes = llvm::make_unique(new_prompt); auto prompt_change_event_sp = std::make_shared( CommandInterpreter::eBroadcastBitResetPrompt, bytes.release()); GetCommandInterpreter().BroadcastEvent(prompt_change_event_sp); } else if (property_path == g_debugger_properties[ePropertyUseColor].name) { // use-color changed. Ping the prompt so it can reset the ansi terminal // codes. SetPrompt(GetPrompt()); } else if (is_load_script && target_sp && load_script_old_value == eLoadScriptFromSymFileWarn) { if (target_sp->TargetProperties::GetLoadScriptFromSymbolFile() == eLoadScriptFromSymFileTrue) { std::list errors; StreamString feedback_stream; if (!target_sp->LoadScriptingResources(errors, &feedback_stream)) { StreamFileSP stream_sp(GetErrorFile()); if (stream_sp) { for (auto error : errors) { stream_sp->Printf("%s\n", error.AsCString()); } if (feedback_stream.GetSize()) stream_sp->PutCString(feedback_stream.GetString()); } } } } else if (is_escape_non_printables) { DataVisualization::ForceUpdate(); } } return error; } bool Debugger::GetAutoConfirm() const { const uint32_t idx = ePropertyAutoConfirm; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_debugger_properties[idx].default_uint_value != 0); } const FormatEntity::Entry *Debugger::GetDisassemblyFormat() const { const uint32_t idx = ePropertyDisassemblyFormat; return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); } const FormatEntity::Entry *Debugger::GetFrameFormat() const { const uint32_t idx = ePropertyFrameFormat; return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); } const FormatEntity::Entry *Debugger::GetFrameFormatUnique() const { const uint32_t idx = ePropertyFrameFormatUnique; return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); } bool Debugger::GetNotifyVoid() const { const uint32_t idx = ePropertyNotiftVoid; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_debugger_properties[idx].default_uint_value != 0); } llvm::StringRef Debugger::GetPrompt() const { const uint32_t idx = ePropertyPrompt; return m_collection_sp->GetPropertyAtIndexAsString( nullptr, idx, g_debugger_properties[idx].default_cstr_value); } void Debugger::SetPrompt(llvm::StringRef p) { const uint32_t idx = ePropertyPrompt; m_collection_sp->SetPropertyAtIndexAsString(nullptr, idx, p); llvm::StringRef new_prompt = GetPrompt(); std::string str = lldb_utility::ansi::FormatAnsiTerminalCodes(new_prompt, GetUseColor()); if (str.length()) new_prompt = str; GetCommandInterpreter().UpdatePrompt(new_prompt); } llvm::StringRef Debugger::GetReproducerPath() const { auto &r = repro::Reproducer::Instance(); return r.GetReproducerPath().GetCString(); } const FormatEntity::Entry *Debugger::GetThreadFormat() const { const uint32_t idx = ePropertyThreadFormat; return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); } const FormatEntity::Entry *Debugger::GetThreadStopFormat() const { const uint32_t idx = ePropertyThreadStopFormat; return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); } lldb::ScriptLanguage Debugger::GetScriptLanguage() const { const uint32_t idx = ePropertyScriptLanguage; return (lldb::ScriptLanguage)m_collection_sp->GetPropertyAtIndexAsEnumeration( nullptr, idx, g_debugger_properties[idx].default_uint_value); } bool Debugger::SetScriptLanguage(lldb::ScriptLanguage script_lang) { const uint32_t idx = ePropertyScriptLanguage; return m_collection_sp->SetPropertyAtIndexAsEnumeration(nullptr, idx, script_lang); } uint32_t Debugger::GetTerminalWidth() const { const uint32_t idx = ePropertyTerminalWidth; return m_collection_sp->GetPropertyAtIndexAsSInt64( nullptr, idx, g_debugger_properties[idx].default_uint_value); } bool Debugger::SetTerminalWidth(uint32_t term_width) { const uint32_t idx = ePropertyTerminalWidth; return m_collection_sp->SetPropertyAtIndexAsSInt64(nullptr, idx, term_width); } bool Debugger::GetUseExternalEditor() const { const uint32_t idx = ePropertyUseExternalEditor; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_debugger_properties[idx].default_uint_value != 0); } bool Debugger::SetUseExternalEditor(bool b) { const uint32_t idx = ePropertyUseExternalEditor; return m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); } bool Debugger::GetUseColor() const { const uint32_t idx = ePropertyUseColor; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_debugger_properties[idx].default_uint_value != 0); } bool Debugger::SetUseColor(bool b) { const uint32_t idx = ePropertyUseColor; bool ret = m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); SetPrompt(GetPrompt()); return ret; } bool Debugger::GetHighlightSource() const { const uint32_t idx = ePropertyHighlightSource; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_debugger_properties[idx].default_uint_value); } StopShowColumn Debugger::GetStopShowColumn() const { const uint32_t idx = ePropertyStopShowColumn; return (lldb::StopShowColumn)m_collection_sp->GetPropertyAtIndexAsEnumeration( nullptr, idx, g_debugger_properties[idx].default_uint_value); } llvm::StringRef Debugger::GetStopShowColumnAnsiPrefix() const { const uint32_t idx = ePropertyStopShowColumnAnsiPrefix; return m_collection_sp->GetPropertyAtIndexAsString(nullptr, idx, ""); } llvm::StringRef Debugger::GetStopShowColumnAnsiSuffix() const { const uint32_t idx = ePropertyStopShowColumnAnsiSuffix; return m_collection_sp->GetPropertyAtIndexAsString(nullptr, idx, ""); } uint32_t Debugger::GetStopSourceLineCount(bool before) const { const uint32_t idx = before ? ePropertyStopLineCountBefore : ePropertyStopLineCountAfter; return m_collection_sp->GetPropertyAtIndexAsSInt64( nullptr, idx, g_debugger_properties[idx].default_uint_value); } Debugger::StopDisassemblyType Debugger::GetStopDisassemblyDisplay() const { const uint32_t idx = ePropertyStopDisassemblyDisplay; return (Debugger::StopDisassemblyType) m_collection_sp->GetPropertyAtIndexAsEnumeration( nullptr, idx, g_debugger_properties[idx].default_uint_value); } uint32_t Debugger::GetDisassemblyLineCount() const { const uint32_t idx = ePropertyStopDisassemblyCount; return m_collection_sp->GetPropertyAtIndexAsSInt64( nullptr, idx, g_debugger_properties[idx].default_uint_value); } bool Debugger::GetAutoOneLineSummaries() const { const uint32_t idx = ePropertyAutoOneLineSummaries; return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, idx, true); } bool Debugger::GetEscapeNonPrintables() const { const uint32_t idx = ePropertyEscapeNonPrintables; return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, idx, true); } bool Debugger::GetAutoIndent() const { const uint32_t idx = ePropertyAutoIndent; return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, idx, true); } bool Debugger::SetAutoIndent(bool b) { const uint32_t idx = ePropertyAutoIndent; return m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); } bool Debugger::GetPrintDecls() const { const uint32_t idx = ePropertyPrintDecls; return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, idx, true); } bool Debugger::SetPrintDecls(bool b) { const uint32_t idx = ePropertyPrintDecls; return m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); } uint32_t Debugger::GetTabSize() const { const uint32_t idx = ePropertyTabSize; return m_collection_sp->GetPropertyAtIndexAsUInt64( nullptr, idx, g_debugger_properties[idx].default_uint_value); } bool Debugger::SetTabSize(uint32_t tab_size) { const uint32_t idx = ePropertyTabSize; return m_collection_sp->SetPropertyAtIndexAsUInt64(nullptr, idx, tab_size); } #pragma mark Debugger // const DebuggerPropertiesSP & // Debugger::GetSettings() const //{ // return m_properties_sp; //} // void Debugger::Initialize(LoadPluginCallbackType load_plugin_callback) { assert(g_debugger_list_ptr == nullptr && "Debugger::Initialize called more than once!"); g_debugger_list_mutex_ptr = new std::recursive_mutex(); g_debugger_list_ptr = new DebuggerList(); g_load_plugin_callback = load_plugin_callback; } void Debugger::Terminate() { assert(g_debugger_list_ptr && "Debugger::Terminate called without a matching Debugger::Initialize!"); if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { // Clear our master list of debugger objects { std::lock_guard guard(*g_debugger_list_mutex_ptr); for (const auto &debugger : *g_debugger_list_ptr) debugger->Clear(); g_debugger_list_ptr->clear(); } } } void Debugger::SettingsInitialize() { Target::SettingsInitialize(); } void Debugger::SettingsTerminate() { Target::SettingsTerminate(); } bool Debugger::LoadPlugin(const FileSpec &spec, Status &error) { if (g_load_plugin_callback) { llvm::sys::DynamicLibrary dynlib = g_load_plugin_callback(shared_from_this(), spec, error); if (dynlib.isValid()) { m_loaded_plugins.push_back(dynlib); return true; } } else { // The g_load_plugin_callback is registered in SBDebugger::Initialize() and // if the public API layer isn't available (code is linking against all of // the internal LLDB static libraries), then we can't load plugins error.SetErrorString("Public API layer is not available"); } return false; } static FileSystem::EnumerateDirectoryResult LoadPluginCallback(void *baton, llvm::sys::fs::file_type ft, llvm::StringRef path) { Status error; static ConstString g_dylibext(".dylib"); static ConstString g_solibext(".so"); if (!baton) return FileSystem::eEnumerateDirectoryResultQuit; Debugger *debugger = (Debugger *)baton; namespace fs = llvm::sys::fs; // If we have a regular file, a symbolic link or unknown file type, try and // process the file. We must handle unknown as sometimes the directory // enumeration might be enumerating a file system that doesn't have correct // file type information. if (ft == fs::file_type::regular_file || ft == fs::file_type::symlink_file || ft == fs::file_type::type_unknown) { FileSpec plugin_file_spec(path); FileSystem::Instance().Resolve(plugin_file_spec); if (plugin_file_spec.GetFileNameExtension() != g_dylibext && plugin_file_spec.GetFileNameExtension() != g_solibext) { return FileSystem::eEnumerateDirectoryResultNext; } Status plugin_load_error; debugger->LoadPlugin(plugin_file_spec, plugin_load_error); return FileSystem::eEnumerateDirectoryResultNext; } else if (ft == fs::file_type::directory_file || ft == fs::file_type::symlink_file || ft == fs::file_type::type_unknown) { // Try and recurse into anything that a directory or symbolic link. We must // also do this for unknown as sometimes the directory enumeration might be // enumerating a file system that doesn't have correct file type // information. return FileSystem::eEnumerateDirectoryResultEnter; } return FileSystem::eEnumerateDirectoryResultNext; } void Debugger::InstanceInitialize() { const bool find_directories = true; const bool find_files = true; const bool find_other = true; char dir_path[PATH_MAX]; if (FileSpec dir_spec = HostInfo::GetSystemPluginDir()) { if (FileSystem::Instance().Exists(dir_spec) && dir_spec.GetPath(dir_path, sizeof(dir_path))) { FileSystem::Instance().EnumerateDirectory(dir_path, find_directories, find_files, find_other, LoadPluginCallback, this); } } if (FileSpec dir_spec = HostInfo::GetUserPluginDir()) { if (FileSystem::Instance().Exists(dir_spec) && dir_spec.GetPath(dir_path, sizeof(dir_path))) { FileSystem::Instance().EnumerateDirectory(dir_path, find_directories, find_files, find_other, LoadPluginCallback, this); } } PluginManager::DebuggerInitialize(*this); } DebuggerSP Debugger::CreateInstance(lldb::LogOutputCallback log_callback, void *baton) { DebuggerSP debugger_sp(new Debugger(log_callback, baton)); if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); g_debugger_list_ptr->push_back(debugger_sp); } debugger_sp->InstanceInitialize(); return debugger_sp; } void Debugger::Destroy(DebuggerSP &debugger_sp) { if (!debugger_sp) return; debugger_sp->Clear(); if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { if ((*pos).get() == debugger_sp.get()) { g_debugger_list_ptr->erase(pos); return; } } } } DebuggerSP Debugger::FindDebuggerWithInstanceName(ConstString instance_name) { DebuggerSP debugger_sp; if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { if ((*pos)->m_instance_name == instance_name) { debugger_sp = *pos; break; } } } return debugger_sp; } TargetSP Debugger::FindTargetWithProcessID(lldb::pid_t pid) { TargetSP target_sp; if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { target_sp = (*pos)->GetTargetList().FindTargetWithProcessID(pid); if (target_sp) break; } } return target_sp; } TargetSP Debugger::FindTargetWithProcess(Process *process) { TargetSP target_sp; if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { target_sp = (*pos)->GetTargetList().FindTargetWithProcess(process); if (target_sp) break; } } return target_sp; } Debugger::Debugger(lldb::LogOutputCallback log_callback, void *baton) : UserID(g_unique_id++), Properties(std::make_shared()), m_input_file_sp(std::make_shared(stdin, false)), m_output_file_sp(std::make_shared(stdout, false)), m_error_file_sp(std::make_shared(stderr, false)), m_input_recorder(nullptr), m_broadcaster_manager_sp(BroadcasterManager::MakeBroadcasterManager()), m_terminal_state(), m_target_list(*this), m_platform_list(), m_listener_sp(Listener::MakeListener("lldb.Debugger")), m_source_manager_up(), m_source_file_cache(), m_command_interpreter_up( llvm::make_unique(*this, false)), m_script_interpreter_sp(), m_input_reader_stack(), m_instance_name(), m_loaded_plugins(), m_event_handler_thread(), m_io_handler_thread(), m_sync_broadcaster(nullptr, "lldb.debugger.sync"), m_forward_listener_sp(), m_clear_once() { char instance_cstr[256]; snprintf(instance_cstr, sizeof(instance_cstr), "debugger_%d", (int)GetID()); m_instance_name.SetCString(instance_cstr); if (log_callback) m_log_callback_stream_sp = std::make_shared(log_callback, baton); m_command_interpreter_up->Initialize(); // Always add our default platform to the platform list PlatformSP default_platform_sp(Platform::GetHostPlatform()); assert(default_platform_sp); m_platform_list.Append(default_platform_sp, true); m_collection_sp->Initialize(g_debugger_properties); m_collection_sp->AppendProperty( ConstString("target"), ConstString("Settings specify to debugging targets."), true, Target::GetGlobalProperties()->GetValueProperties()); m_collection_sp->AppendProperty( ConstString("platform"), ConstString("Platform settings."), true, Platform::GetGlobalPlatformProperties()->GetValueProperties()); m_collection_sp->AppendProperty( ConstString("symbols"), ConstString("Symbol lookup and cache settings."), true, ModuleList::GetGlobalModuleListProperties().GetValueProperties()); if (m_command_interpreter_up) { m_collection_sp->AppendProperty( ConstString("interpreter"), ConstString("Settings specify to the debugger's command interpreter."), true, m_command_interpreter_up->GetValueProperties()); } OptionValueSInt64 *term_width = m_collection_sp->GetPropertyAtIndexAsOptionValueSInt64( nullptr, ePropertyTerminalWidth); term_width->SetMinimumValue(10); term_width->SetMaximumValue(1024); // Turn off use-color if this is a dumb terminal. const char *term = getenv("TERM"); if (term && !strcmp(term, "dumb")) SetUseColor(false); // Turn off use-color if we don't write to a terminal with color support. if (!m_output_file_sp->GetFile().GetIsTerminalWithColors()) SetUseColor(false); #if defined(_WIN32) && defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) // Enabling use of ANSI color codes because LLDB is using them to highlight // text. llvm::sys::Process::UseANSIEscapeCodes(true); #endif } Debugger::~Debugger() { Clear(); } void Debugger::Clear() { // Make sure we call this function only once. With the C++ global destructor // chain having a list of debuggers and with code that can be running on // other threads, we need to ensure this doesn't happen multiple times. // // The following functions call Debugger::Clear(): // Debugger::~Debugger(); // static void Debugger::Destroy(lldb::DebuggerSP &debugger_sp); // static void Debugger::Terminate(); llvm::call_once(m_clear_once, [this]() { ClearIOHandlers(); StopIOHandlerThread(); StopEventHandlerThread(); m_listener_sp->Clear(); int num_targets = m_target_list.GetNumTargets(); for (int i = 0; i < num_targets; i++) { TargetSP target_sp(m_target_list.GetTargetAtIndex(i)); if (target_sp) { ProcessSP process_sp(target_sp->GetProcessSP()); if (process_sp) process_sp->Finalize(); target_sp->Destroy(); } } m_broadcaster_manager_sp->Clear(); // Close the input file _before_ we close the input read communications // class as it does NOT own the input file, our m_input_file does. m_terminal_state.Clear(); if (m_input_file_sp) m_input_file_sp->GetFile().Close(); m_command_interpreter_up->Clear(); }); } bool Debugger::GetCloseInputOnEOF() const { // return m_input_comm.GetCloseOnEOF(); return false; } void Debugger::SetCloseInputOnEOF(bool b) { // m_input_comm.SetCloseOnEOF(b); } bool Debugger::GetAsyncExecution() { return !m_command_interpreter_up->GetSynchronous(); } void Debugger::SetAsyncExecution(bool async_execution) { m_command_interpreter_up->SetSynchronous(!async_execution); } repro::DataRecorder *Debugger::GetInputRecorder() { return m_input_recorder; } void Debugger::SetInputFileHandle(FILE *fh, bool tranfer_ownership, repro::DataRecorder *recorder) { m_input_recorder = recorder; if (m_input_file_sp) m_input_file_sp->GetFile().SetStream(fh, tranfer_ownership); else m_input_file_sp = std::make_shared(fh, tranfer_ownership); File &in_file = m_input_file_sp->GetFile(); if (!in_file.IsValid()) in_file.SetStream(stdin, true); // Save away the terminal state if that is relevant, so that we can restore // it in RestoreInputState. SaveInputTerminalState(); } void Debugger::SetOutputFileHandle(FILE *fh, bool tranfer_ownership) { if (m_output_file_sp) m_output_file_sp->GetFile().SetStream(fh, tranfer_ownership); else m_output_file_sp = std::make_shared(fh, tranfer_ownership); File &out_file = m_output_file_sp->GetFile(); if (!out_file.IsValid()) out_file.SetStream(stdout, false); // Do not create the ScriptInterpreter just for setting the output file // handle as the constructor will know how to do the right thing on its own. if (ScriptInterpreter *script_interpreter = GetScriptInterpreter(/*can_create=*/false)) script_interpreter->ResetOutputFileHandle(fh); } void Debugger::SetErrorFileHandle(FILE *fh, bool tranfer_ownership) { if (m_error_file_sp) m_error_file_sp->GetFile().SetStream(fh, tranfer_ownership); else m_error_file_sp = std::make_shared(fh, tranfer_ownership); File &err_file = m_error_file_sp->GetFile(); if (!err_file.IsValid()) err_file.SetStream(stderr, false); } void Debugger::SaveInputTerminalState() { if (m_input_file_sp) { File &in_file = m_input_file_sp->GetFile(); if (in_file.GetDescriptor() != File::kInvalidDescriptor) m_terminal_state.Save(in_file.GetDescriptor(), true); } } void Debugger::RestoreInputTerminalState() { m_terminal_state.Restore(); } ExecutionContext Debugger::GetSelectedExecutionContext() { ExecutionContext exe_ctx; TargetSP target_sp(GetSelectedTarget()); exe_ctx.SetTargetSP(target_sp); if (target_sp) { ProcessSP process_sp(target_sp->GetProcessSP()); exe_ctx.SetProcessSP(process_sp); if (process_sp && !process_sp->IsRunning()) { ThreadSP thread_sp(process_sp->GetThreadList().GetSelectedThread()); if (thread_sp) { exe_ctx.SetThreadSP(thread_sp); exe_ctx.SetFrameSP(thread_sp->GetSelectedFrame()); if (exe_ctx.GetFramePtr() == nullptr) exe_ctx.SetFrameSP(thread_sp->GetStackFrameAtIndex(0)); } } } return exe_ctx; } void Debugger::DispatchInputInterrupt() { std::lock_guard guard(m_input_reader_stack.GetMutex()); IOHandlerSP reader_sp(m_input_reader_stack.Top()); if (reader_sp) reader_sp->Interrupt(); } void Debugger::DispatchInputEndOfFile() { std::lock_guard guard(m_input_reader_stack.GetMutex()); IOHandlerSP reader_sp(m_input_reader_stack.Top()); if (reader_sp) reader_sp->GotEOF(); } void Debugger::ClearIOHandlers() { // The bottom input reader should be the main debugger input reader. We do // not want to close that one here. std::lock_guard guard(m_input_reader_stack.GetMutex()); while (m_input_reader_stack.GetSize() > 1) { IOHandlerSP reader_sp(m_input_reader_stack.Top()); if (reader_sp) PopIOHandler(reader_sp); } } void Debugger::ExecuteIOHandlers() { while (true) { IOHandlerSP reader_sp(m_input_reader_stack.Top()); if (!reader_sp) break; reader_sp->Run(); // Remove all input readers that are done from the top of the stack while (true) { IOHandlerSP top_reader_sp = m_input_reader_stack.Top(); if (top_reader_sp && top_reader_sp->GetIsDone()) PopIOHandler(top_reader_sp); else break; } } ClearIOHandlers(); } bool Debugger::IsTopIOHandler(const lldb::IOHandlerSP &reader_sp) { return m_input_reader_stack.IsTop(reader_sp); } bool Debugger::CheckTopIOHandlerTypes(IOHandler::Type top_type, IOHandler::Type second_top_type) { return m_input_reader_stack.CheckTopIOHandlerTypes(top_type, second_top_type); } void Debugger::PrintAsync(const char *s, size_t len, bool is_stdout) { lldb::StreamFileSP stream = is_stdout ? GetOutputFile() : GetErrorFile(); m_input_reader_stack.PrintAsync(stream.get(), s, len); } ConstString Debugger::GetTopIOHandlerControlSequence(char ch) { return m_input_reader_stack.GetTopIOHandlerControlSequence(ch); } const char *Debugger::GetIOHandlerCommandPrefix() { return m_input_reader_stack.GetTopIOHandlerCommandPrefix(); } const char *Debugger::GetIOHandlerHelpPrologue() { return m_input_reader_stack.GetTopIOHandlerHelpPrologue(); } void Debugger::RunIOHandler(const IOHandlerSP &reader_sp) { PushIOHandler(reader_sp); IOHandlerSP top_reader_sp = reader_sp; while (top_reader_sp) { top_reader_sp->Run(); if (top_reader_sp.get() == reader_sp.get()) { if (PopIOHandler(reader_sp)) break; } while (true) { top_reader_sp = m_input_reader_stack.Top(); if (top_reader_sp && top_reader_sp->GetIsDone()) PopIOHandler(top_reader_sp); else break; } } } void Debugger::AdoptTopIOHandlerFilesIfInvalid(StreamFileSP &in, StreamFileSP &out, StreamFileSP &err) { // Before an IOHandler runs, it must have in/out/err streams. This function // is called when one ore more of the streams are nullptr. We use the top // input reader's in/out/err streams, or fall back to the debugger file // handles, or we fall back onto stdin/stdout/stderr as a last resort. std::lock_guard guard(m_input_reader_stack.GetMutex()); IOHandlerSP top_reader_sp(m_input_reader_stack.Top()); // If no STDIN has been set, then set it appropriately if (!in) { if (top_reader_sp) in = top_reader_sp->GetInputStreamFile(); else in = GetInputFile(); // If there is nothing, use stdin if (!in) in = std::make_shared(stdin, false); } // If no STDOUT has been set, then set it appropriately if (!out) { if (top_reader_sp) out = top_reader_sp->GetOutputStreamFile(); else out = GetOutputFile(); // If there is nothing, use stdout if (!out) out = std::make_shared(stdout, false); } // If no STDERR has been set, then set it appropriately if (!err) { if (top_reader_sp) err = top_reader_sp->GetErrorStreamFile(); else err = GetErrorFile(); // If there is nothing, use stderr if (!err) err = std::make_shared(stdout, false); } } void Debugger::PushIOHandler(const IOHandlerSP &reader_sp, bool cancel_top_handler) { if (!reader_sp) return; std::lock_guard guard(m_input_reader_stack.GetMutex()); // Get the current top input reader... IOHandlerSP top_reader_sp(m_input_reader_stack.Top()); // Don't push the same IO handler twice... if (reader_sp == top_reader_sp) return; // Push our new input reader m_input_reader_stack.Push(reader_sp); reader_sp->Activate(); // Interrupt the top input reader to it will exit its Run() function and let // this new input reader take over if (top_reader_sp) { top_reader_sp->Deactivate(); if (cancel_top_handler) top_reader_sp->Cancel(); } } bool Debugger::PopIOHandler(const IOHandlerSP &pop_reader_sp) { if (!pop_reader_sp) return false; std::lock_guard guard(m_input_reader_stack.GetMutex()); // The reader on the stop of the stack is done, so let the next read on the // stack refresh its prompt and if there is one... if (m_input_reader_stack.IsEmpty()) return false; IOHandlerSP reader_sp(m_input_reader_stack.Top()); if (pop_reader_sp != reader_sp) return false; reader_sp->Deactivate(); reader_sp->Cancel(); m_input_reader_stack.Pop(); reader_sp = m_input_reader_stack.Top(); if (reader_sp) reader_sp->Activate(); return true; } StreamSP Debugger::GetAsyncOutputStream() { return std::make_shared(*this, true); } StreamSP Debugger::GetAsyncErrorStream() { return std::make_shared(*this, false); } size_t Debugger::GetNumDebuggers() { if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); return g_debugger_list_ptr->size(); } return 0; } lldb::DebuggerSP Debugger::GetDebuggerAtIndex(size_t index) { DebuggerSP debugger_sp; if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); if (index < g_debugger_list_ptr->size()) debugger_sp = g_debugger_list_ptr->at(index); } return debugger_sp; } DebuggerSP Debugger::FindDebuggerWithID(lldb::user_id_t id) { DebuggerSP debugger_sp; if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { if ((*pos)->GetID() == id) { debugger_sp = *pos; break; } } } return debugger_sp; } bool Debugger::FormatDisassemblerAddress(const FormatEntity::Entry *format, const SymbolContext *sc, const SymbolContext *prev_sc, const ExecutionContext *exe_ctx, const Address *addr, Stream &s) { FormatEntity::Entry format_entry; if (format == nullptr) { if (exe_ctx != nullptr && exe_ctx->HasTargetScope()) format = exe_ctx->GetTargetRef().GetDebugger().GetDisassemblyFormat(); if (format == nullptr) { FormatEntity::Parse("${addr}: ", format_entry); format = &format_entry; } } bool function_changed = false; bool initial_function = false; if (prev_sc && (prev_sc->function || prev_sc->symbol)) { if (sc && (sc->function || sc->symbol)) { if (prev_sc->symbol && sc->symbol) { if (!sc->symbol->Compare(prev_sc->symbol->GetName(), prev_sc->symbol->GetType())) { function_changed = true; } } else if (prev_sc->function && sc->function) { if (prev_sc->function->GetMangled() != sc->function->GetMangled()) { function_changed = true; } } } } // The first context on a list of instructions will have a prev_sc that has // no Function or Symbol -- if SymbolContext had an IsValid() method, it // would return false. But we do get a prev_sc pointer. if ((sc && (sc->function || sc->symbol)) && prev_sc && (prev_sc->function == nullptr && prev_sc->symbol == nullptr)) { initial_function = true; } return FormatEntity::Format(*format, s, sc, exe_ctx, addr, nullptr, function_changed, initial_function); } void Debugger::SetLoggingCallback(lldb::LogOutputCallback log_callback, void *baton) { // For simplicity's sake, I am not going to deal with how to close down any // open logging streams, I just redirect everything from here on out to the // callback. m_log_callback_stream_sp = std::make_shared(log_callback, baton); } bool Debugger::EnableLog(llvm::StringRef channel, llvm::ArrayRef categories, llvm::StringRef log_file, uint32_t log_options, llvm::raw_ostream &error_stream) { const bool should_close = true; const bool unbuffered = true; std::shared_ptr log_stream_sp; if (m_log_callback_stream_sp) { log_stream_sp = m_log_callback_stream_sp; // For now when using the callback mode you always get thread & timestamp. log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_THREAD_NAME; } else if (log_file.empty()) { log_stream_sp = std::make_shared( GetOutputFile()->GetFile().GetDescriptor(), !should_close, unbuffered); } else { auto pos = m_log_streams.find(log_file); if (pos != m_log_streams.end()) log_stream_sp = pos->second.lock(); if (!log_stream_sp) { llvm::sys::fs::OpenFlags flags = llvm::sys::fs::F_Text; if (log_options & LLDB_LOG_OPTION_APPEND) flags |= llvm::sys::fs::F_Append; int FD; if (std::error_code ec = llvm::sys::fs::openFileForWrite( log_file, FD, llvm::sys::fs::CD_CreateAlways, flags)) { error_stream << "Unable to open log file: " << ec.message(); return false; } log_stream_sp = std::make_shared(FD, should_close, unbuffered); m_log_streams[log_file] = log_stream_sp; } } assert(log_stream_sp); if (log_options == 0) log_options = LLDB_LOG_OPTION_PREPEND_THREAD_NAME | LLDB_LOG_OPTION_THREADSAFE; return Log::EnableLogChannel(log_stream_sp, log_options, channel, categories, error_stream); } ScriptInterpreter *Debugger::GetScriptInterpreter(bool can_create) { std::lock_guard locker(m_script_interpreter_mutex); if (!m_script_interpreter_sp) { if (!can_create) return nullptr; m_script_interpreter_sp = PluginManager::GetScriptInterpreterForLanguage( GetScriptLanguage(), *this); } return m_script_interpreter_sp.get(); } SourceManager &Debugger::GetSourceManager() { if (!m_source_manager_up) m_source_manager_up = llvm::make_unique(shared_from_this()); return *m_source_manager_up; } // This function handles events that were broadcast by the process. void Debugger::HandleBreakpointEvent(const EventSP &event_sp) { using namespace lldb; const uint32_t event_type = Breakpoint::BreakpointEventData::GetBreakpointEventTypeFromEvent( event_sp); // if (event_type & eBreakpointEventTypeAdded // || event_type & eBreakpointEventTypeRemoved // || event_type & eBreakpointEventTypeEnabled // || event_type & eBreakpointEventTypeDisabled // || event_type & eBreakpointEventTypeCommandChanged // || event_type & eBreakpointEventTypeConditionChanged // || event_type & eBreakpointEventTypeIgnoreChanged // || event_type & eBreakpointEventTypeLocationsResolved) // { // // Don't do anything about these events, since the breakpoint // commands already echo these actions. // } // if (event_type & eBreakpointEventTypeLocationsAdded) { uint32_t num_new_locations = Breakpoint::BreakpointEventData::GetNumBreakpointLocationsFromEvent( event_sp); if (num_new_locations > 0) { BreakpointSP breakpoint = Breakpoint::BreakpointEventData::GetBreakpointFromEvent(event_sp); StreamSP output_sp(GetAsyncOutputStream()); if (output_sp) { output_sp->Printf("%d location%s added to breakpoint %d\n", num_new_locations, num_new_locations == 1 ? "" : "s", breakpoint->GetID()); output_sp->Flush(); } } } // else if (event_type & eBreakpointEventTypeLocationsRemoved) // { // // These locations just get disabled, not sure it is worth spamming // folks about this on the command line. // } // else if (event_type & eBreakpointEventTypeLocationsResolved) // { // // This might be an interesting thing to note, but I'm going to // leave it quiet for now, it just looked noisy. // } } -size_t Debugger::GetProcessSTDOUT(Process *process, Stream *stream) { - size_t total_bytes = 0; - if (stream == nullptr) - stream = GetOutputFile().get(); - - if (stream) { - // The process has stuff waiting for stdout; get it and write it out to the - // appropriate place. - if (process == nullptr) { - TargetSP target_sp = GetTargetList().GetSelectedTarget(); - if (target_sp) - process = target_sp->GetProcessSP().get(); - } - if (process) { - Status error; - size_t len; - char stdio_buffer[1024]; - while ((len = process->GetSTDOUT(stdio_buffer, sizeof(stdio_buffer), - error)) > 0) { - stream->Write(stdio_buffer, len); - total_bytes += len; - } - } - stream->Flush(); - } - return total_bytes; -} - -size_t Debugger::GetProcessSTDERR(Process *process, Stream *stream) { - size_t total_bytes = 0; - if (stream == nullptr) - stream = GetOutputFile().get(); - - if (stream) { - // The process has stuff waiting for stderr; get it and write it out to the - // appropriate place. - if (process == nullptr) { - TargetSP target_sp = GetTargetList().GetSelectedTarget(); - if (target_sp) - process = target_sp->GetProcessSP().get(); - } - if (process) { - Status error; - size_t len; - char stdio_buffer[1024]; - while ((len = process->GetSTDERR(stdio_buffer, sizeof(stdio_buffer), - error)) > 0) { - stream->Write(stdio_buffer, len); - total_bytes += len; - } - } - stream->Flush(); - } - return total_bytes; +void Debugger::FlushProcessOutput(Process &process, bool flush_stdout, + bool flush_stderr) { + const auto &flush = [&](Stream &stream, + size_t (Process::*get)(char *, size_t, Status &)) { + Status error; + size_t len; + char buffer[1024]; + while ((len = (process.*get)(buffer, sizeof(buffer), error)) > 0) + stream.Write(buffer, len); + stream.Flush(); + }; + + std::lock_guard guard(m_output_flush_mutex); + if (flush_stdout) + flush(*GetAsyncOutputStream(), &Process::GetSTDOUT); + if (flush_stderr) + flush(*GetAsyncErrorStream(), &Process::GetSTDERR); } // This function handles events that were broadcast by the process. void Debugger::HandleProcessEvent(const EventSP &event_sp) { using namespace lldb; const uint32_t event_type = event_sp->GetType(); ProcessSP process_sp = (event_type == Process::eBroadcastBitStructuredData) ? EventDataStructuredData::GetProcessFromEvent(event_sp.get()) : Process::ProcessEventData::GetProcessFromEvent(event_sp.get()); StreamSP output_stream_sp = GetAsyncOutputStream(); StreamSP error_stream_sp = GetAsyncErrorStream(); const bool gui_enabled = IsForwardingEvents(); if (!gui_enabled) { bool pop_process_io_handler = false; assert(process_sp); bool state_is_stopped = false; const bool got_state_changed = (event_type & Process::eBroadcastBitStateChanged) != 0; const bool got_stdout = (event_type & Process::eBroadcastBitSTDOUT) != 0; const bool got_stderr = (event_type & Process::eBroadcastBitSTDERR) != 0; const bool got_structured_data = (event_type & Process::eBroadcastBitStructuredData) != 0; if (got_state_changed) { StateType event_state = Process::ProcessEventData::GetStateFromEvent(event_sp.get()); state_is_stopped = StateIsStoppedState(event_state, false); } // Display running state changes first before any STDIO if (got_state_changed && !state_is_stopped) { Process::HandleProcessStateChangedEvent(event_sp, output_stream_sp.get(), pop_process_io_handler); } - // Now display and STDOUT - if (got_stdout || got_state_changed) { - GetProcessSTDOUT(process_sp.get(), output_stream_sp.get()); - } - - // Now display and STDERR - if (got_stderr || got_state_changed) { - GetProcessSTDERR(process_sp.get(), error_stream_sp.get()); - } + // Now display STDOUT and STDERR + FlushProcessOutput(*process_sp, got_stdout || got_state_changed, + got_stderr || got_state_changed); // Give structured data events an opportunity to display. if (got_structured_data) { StructuredDataPluginSP plugin_sp = EventDataStructuredData::GetPluginFromEvent(event_sp.get()); if (plugin_sp) { auto structured_data_sp = EventDataStructuredData::GetObjectFromEvent(event_sp.get()); if (output_stream_sp) { StreamString content_stream; Status error = plugin_sp->GetDescription(structured_data_sp, content_stream); if (error.Success()) { if (!content_stream.GetString().empty()) { // Add newline. content_stream.PutChar('\n'); content_stream.Flush(); // Print it. output_stream_sp->PutCString(content_stream.GetString()); } } else { error_stream_sp->Printf("Failed to print structured " "data with plugin %s: %s", plugin_sp->GetPluginName().AsCString(), error.AsCString()); } } } } // Now display any stopped state changes after any STDIO if (got_state_changed && state_is_stopped) { Process::HandleProcessStateChangedEvent(event_sp, output_stream_sp.get(), pop_process_io_handler); } output_stream_sp->Flush(); error_stream_sp->Flush(); if (pop_process_io_handler) process_sp->PopProcessIOHandler(); } } void Debugger::HandleThreadEvent(const EventSP &event_sp) { // At present the only thread event we handle is the Frame Changed event, and // all we do for that is just reprint the thread status for that thread. using namespace lldb; const uint32_t event_type = event_sp->GetType(); const bool stop_format = true; if (event_type == Thread::eBroadcastBitStackChanged || event_type == Thread::eBroadcastBitThreadSelected) { ThreadSP thread_sp( Thread::ThreadEventData::GetThreadFromEvent(event_sp.get())); if (thread_sp) { thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format); } } } bool Debugger::IsForwardingEvents() { return (bool)m_forward_listener_sp; } void Debugger::EnableForwardEvents(const ListenerSP &listener_sp) { m_forward_listener_sp = listener_sp; } void Debugger::CancelForwardEvents(const ListenerSP &listener_sp) { m_forward_listener_sp.reset(); } void Debugger::DefaultEventHandler() { ListenerSP listener_sp(GetListener()); ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass()); ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass()); BroadcastEventSpec target_event_spec(broadcaster_class_target, Target::eBroadcastBitBreakpointChanged); BroadcastEventSpec process_event_spec( broadcaster_class_process, Process::eBroadcastBitStateChanged | Process::eBroadcastBitSTDOUT | Process::eBroadcastBitSTDERR | Process::eBroadcastBitStructuredData); BroadcastEventSpec thread_event_spec(broadcaster_class_thread, Thread::eBroadcastBitStackChanged | Thread::eBroadcastBitThreadSelected); listener_sp->StartListeningForEventSpec(m_broadcaster_manager_sp, target_event_spec); listener_sp->StartListeningForEventSpec(m_broadcaster_manager_sp, process_event_spec); listener_sp->StartListeningForEventSpec(m_broadcaster_manager_sp, thread_event_spec); listener_sp->StartListeningForEvents( m_command_interpreter_up.get(), CommandInterpreter::eBroadcastBitQuitCommandReceived | CommandInterpreter::eBroadcastBitAsynchronousOutputData | CommandInterpreter::eBroadcastBitAsynchronousErrorData); // Let the thread that spawned us know that we have started up and that we // are now listening to all required events so no events get missed m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening); bool done = false; while (!done) { EventSP event_sp; if (listener_sp->GetEvent(event_sp, llvm::None)) { if (event_sp) { Broadcaster *broadcaster = event_sp->GetBroadcaster(); if (broadcaster) { uint32_t event_type = event_sp->GetType(); ConstString broadcaster_class(broadcaster->GetBroadcasterClass()); if (broadcaster_class == broadcaster_class_process) { HandleProcessEvent(event_sp); } else if (broadcaster_class == broadcaster_class_target) { if (Breakpoint::BreakpointEventData::GetEventDataFromEvent( event_sp.get())) { HandleBreakpointEvent(event_sp); } } else if (broadcaster_class == broadcaster_class_thread) { HandleThreadEvent(event_sp); } else if (broadcaster == m_command_interpreter_up.get()) { if (event_type & CommandInterpreter::eBroadcastBitQuitCommandReceived) { done = true; } else if (event_type & CommandInterpreter::eBroadcastBitAsynchronousErrorData) { const char *data = reinterpret_cast( EventDataBytes::GetBytesFromEvent(event_sp.get())); if (data && data[0]) { StreamSP error_sp(GetAsyncErrorStream()); if (error_sp) { error_sp->PutCString(data); error_sp->Flush(); } } } else if (event_type & CommandInterpreter:: eBroadcastBitAsynchronousOutputData) { const char *data = reinterpret_cast( EventDataBytes::GetBytesFromEvent(event_sp.get())); if (data && data[0]) { StreamSP output_sp(GetAsyncOutputStream()); if (output_sp) { output_sp->PutCString(data); output_sp->Flush(); } } } } } if (m_forward_listener_sp) m_forward_listener_sp->AddEvent(event_sp); } } } } lldb::thread_result_t Debugger::EventHandlerThread(lldb::thread_arg_t arg) { ((Debugger *)arg)->DefaultEventHandler(); return {}; } bool Debugger::StartEventHandlerThread() { if (!m_event_handler_thread.IsJoinable()) { // We must synchronize with the DefaultEventHandler() thread to ensure it // is up and running and listening to events before we return from this // function. We do this by listening to events for the // eBroadcastBitEventThreadIsListening from the m_sync_broadcaster ConstString full_name("lldb.debugger.event-handler"); ListenerSP listener_sp(Listener::MakeListener(full_name.AsCString())); listener_sp->StartListeningForEvents(&m_sync_broadcaster, eBroadcastBitEventThreadIsListening); auto thread_name = full_name.GetLength() < llvm::get_max_thread_name_length() ? full_name.AsCString() : "dbg.evt-handler"; // Use larger 8MB stack for this thread llvm::Expected event_handler_thread = ThreadLauncher::LaunchThread(thread_name, EventHandlerThread, this, g_debugger_event_thread_stack_bytes); if (event_handler_thread) { m_event_handler_thread = *event_handler_thread; } else { LLDB_LOG(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST), "failed to launch host thread: {}", llvm::toString(event_handler_thread.takeError())); } // Make sure DefaultEventHandler() is running and listening to events // before we return from this function. We are only listening for events of // type eBroadcastBitEventThreadIsListening so we don't need to check the // event, we just need to wait an infinite amount of time for it (nullptr // timeout as the first parameter) lldb::EventSP event_sp; listener_sp->GetEvent(event_sp, llvm::None); } return m_event_handler_thread.IsJoinable(); } void Debugger::StopEventHandlerThread() { if (m_event_handler_thread.IsJoinable()) { GetCommandInterpreter().BroadcastEvent( CommandInterpreter::eBroadcastBitQuitCommandReceived); m_event_handler_thread.Join(nullptr); } } lldb::thread_result_t Debugger::IOHandlerThread(lldb::thread_arg_t arg) { Debugger *debugger = (Debugger *)arg; debugger->ExecuteIOHandlers(); debugger->StopEventHandlerThread(); return {}; } bool Debugger::HasIOHandlerThread() { return m_io_handler_thread.IsJoinable(); } bool Debugger::StartIOHandlerThread() { if (!m_io_handler_thread.IsJoinable()) { llvm::Expected io_handler_thread = ThreadLauncher::LaunchThread( "lldb.debugger.io-handler", IOHandlerThread, this, 8 * 1024 * 1024); // Use larger 8MB stack for this thread if (io_handler_thread) { m_io_handler_thread = *io_handler_thread; } else { LLDB_LOG(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST), "failed to launch host thread: {}", llvm::toString(io_handler_thread.takeError())); } } return m_io_handler_thread.IsJoinable(); } void Debugger::StopIOHandlerThread() { if (m_io_handler_thread.IsJoinable()) { if (m_input_file_sp) m_input_file_sp->GetFile().Close(); m_io_handler_thread.Join(nullptr); } } void Debugger::JoinIOHandlerThread() { if (HasIOHandlerThread()) { thread_result_t result; m_io_handler_thread.Join(&result); m_io_handler_thread = LLDB_INVALID_HOST_THREAD; } } Target *Debugger::GetDummyTarget() { return m_target_list.GetDummyTarget(*this).get(); } Target *Debugger::GetSelectedOrDummyTarget(bool prefer_dummy) { Target *target = nullptr; if (!prefer_dummy) { target = m_target_list.GetSelectedTarget().get(); if (target) return target; } return GetDummyTarget(); } Status Debugger::RunREPL(LanguageType language, const char *repl_options) { Status err; FileSpec repl_executable; if (language == eLanguageTypeUnknown) { std::set repl_languages; Language::GetLanguagesSupportingREPLs(repl_languages); if (repl_languages.size() == 1) { language = *repl_languages.begin(); } else if (repl_languages.empty()) { err.SetErrorStringWithFormat( "LLDB isn't configured with REPL support for any languages."); return err; } else { err.SetErrorStringWithFormat( "Multiple possible REPL languages. Please specify a language."); return err; } } Target *const target = nullptr; // passing in an empty target means the REPL must create one REPLSP repl_sp(REPL::Create(err, language, this, target, repl_options)); if (!err.Success()) { return err; } if (!repl_sp) { err.SetErrorStringWithFormat("couldn't find a REPL for %s", Language::GetNameForLanguageType(language)); return err; } repl_sp->SetCompilerOptions(repl_options); repl_sp->RunLoop(); return err; } diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index bd09f89c02a0..8af8cc42e28f 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -1,3176 +1,3158 @@ //===-- CommandInterpreter.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 #include #include #include #include "CommandObjectScript.h" #include "lldb/Interpreter/CommandObjectRegexCommand.h" #include "Commands/CommandObjectApropos.h" #include "Commands/CommandObjectBreakpoint.h" #include "Commands/CommandObjectBugreport.h" #include "Commands/CommandObjectCommands.h" #include "Commands/CommandObjectDisassemble.h" #include "Commands/CommandObjectExpression.h" #include "Commands/CommandObjectFrame.h" #include "Commands/CommandObjectGUI.h" #include "Commands/CommandObjectHelp.h" #include "Commands/CommandObjectLanguage.h" #include "Commands/CommandObjectLog.h" #include "Commands/CommandObjectMemory.h" #include "Commands/CommandObjectPlatform.h" #include "Commands/CommandObjectPlugin.h" #include "Commands/CommandObjectProcess.h" #include "Commands/CommandObjectQuit.h" #include "Commands/CommandObjectRegister.h" #include "Commands/CommandObjectReproducer.h" #include "Commands/CommandObjectSettings.h" #include "Commands/CommandObjectSource.h" #include "Commands/CommandObjectStats.h" #include "Commands/CommandObjectTarget.h" #include "Commands/CommandObjectThread.h" #include "Commands/CommandObjectType.h" #include "Commands/CommandObjectVersion.h" #include "Commands/CommandObjectWatchpoint.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/StreamFile.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/Timer.h" #ifndef LLDB_DISABLE_LIBEDIT #include "lldb/Host/Editline.h" #endif #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" #include "lldb/Interpreter/CommandCompletions.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionValueProperties.h" #include "lldb/Interpreter/Options.h" #include "lldb/Interpreter/Property.h" #include "lldb/Utility/Args.h" #include "lldb/Target/Process.h" #include "lldb/Target/TargetList.h" #include "lldb/Target/Thread.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" using namespace lldb; using namespace lldb_private; static const char *k_white_space = " \t\v"; static constexpr const char *InitFileWarning = "There is a .lldbinit file in the current directory which is not being " "read.\n" "To silence this warning without sourcing in the local .lldbinit,\n" "add the following to the lldbinit file in your home directory:\n" " settings set target.load-cwd-lldbinit false\n" "To allow lldb to source .lldbinit files in the current working " "directory,\n" "set the value of this variable to true. Only do so if you understand " "and\n" "accept the security risk."; #define LLDB_PROPERTIES_interpreter #include "InterpreterProperties.inc" enum { #define LLDB_PROPERTIES_interpreter #include "InterpreterPropertiesEnum.inc" }; ConstString &CommandInterpreter::GetStaticBroadcasterClass() { static ConstString class_name("lldb.commandInterpreter"); return class_name; } CommandInterpreter::CommandInterpreter(Debugger &debugger, bool synchronous_execution) : Broadcaster(debugger.GetBroadcasterManager(), CommandInterpreter::GetStaticBroadcasterClass().AsCString()), Properties(OptionValuePropertiesSP( new OptionValueProperties(ConstString("interpreter")))), IOHandlerDelegate(IOHandlerDelegate::Completion::LLDBCommand), m_debugger(debugger), m_synchronous_execution(synchronous_execution), m_skip_lldbinit_files(false), m_skip_app_init_files(false), m_command_io_handler_sp(), m_comment_char('#'), m_batch_command_mode(false), m_truncation_warning(eNoTruncation), m_command_source_depth(0), m_num_errors(0), m_quit_requested(false), m_stopped_for_crash(false) { SetEventName(eBroadcastBitThreadShouldExit, "thread-should-exit"); SetEventName(eBroadcastBitResetPrompt, "reset-prompt"); SetEventName(eBroadcastBitQuitCommandReceived, "quit"); CheckInWithManager(); m_collection_sp->Initialize(g_interpreter_properties); } bool CommandInterpreter::GetExpandRegexAliases() const { const uint32_t idx = ePropertyExpandRegexAliases; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_interpreter_properties[idx].default_uint_value != 0); } bool CommandInterpreter::GetPromptOnQuit() const { const uint32_t idx = ePropertyPromptOnQuit; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_interpreter_properties[idx].default_uint_value != 0); } void CommandInterpreter::SetPromptOnQuit(bool b) { const uint32_t idx = ePropertyPromptOnQuit; m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); } bool CommandInterpreter::GetEchoCommands() const { const uint32_t idx = ePropertyEchoCommands; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_interpreter_properties[idx].default_uint_value != 0); } void CommandInterpreter::SetEchoCommands(bool b) { const uint32_t idx = ePropertyEchoCommands; m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); } bool CommandInterpreter::GetEchoCommentCommands() const { const uint32_t idx = ePropertyEchoCommentCommands; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_interpreter_properties[idx].default_uint_value != 0); } void CommandInterpreter::SetEchoCommentCommands(bool b) { const uint32_t idx = ePropertyEchoCommentCommands; m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); } void CommandInterpreter::AllowExitCodeOnQuit(bool allow) { m_allow_exit_code = allow; if (!allow) m_quit_exit_code.reset(); } bool CommandInterpreter::SetQuitExitCode(int exit_code) { if (!m_allow_exit_code) return false; m_quit_exit_code = exit_code; return true; } int CommandInterpreter::GetQuitExitCode(bool &exited) const { exited = m_quit_exit_code.hasValue(); if (exited) return *m_quit_exit_code; return 0; } void CommandInterpreter::ResolveCommand(const char *command_line, CommandReturnObject &result) { std::string command = command_line; if (ResolveCommandImpl(command, result) != nullptr) { result.AppendMessageWithFormat("%s", command.c_str()); result.SetStatus(eReturnStatusSuccessFinishResult); } } bool CommandInterpreter::GetStopCmdSourceOnError() const { const uint32_t idx = ePropertyStopCmdSourceOnError; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_interpreter_properties[idx].default_uint_value != 0); } bool CommandInterpreter::GetSpaceReplPrompts() const { const uint32_t idx = ePropertySpaceReplPrompts; return m_collection_sp->GetPropertyAtIndexAsBoolean( nullptr, idx, g_interpreter_properties[idx].default_uint_value != 0); } void CommandInterpreter::Initialize() { static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); Timer scoped_timer(func_cat, LLVM_PRETTY_FUNCTION); CommandReturnObject result; LoadCommandDictionary(); // An alias arguments vector to reuse - reset it before use... OptionArgVectorSP alias_arguments_vector_sp(new OptionArgVector); // Set up some initial aliases. CommandObjectSP cmd_obj_sp = GetCommandSPExact("quit", false); if (cmd_obj_sp) { AddAlias("q", cmd_obj_sp); AddAlias("exit", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("_regexp-attach", false); if (cmd_obj_sp) AddAlias("attach", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("process detach", false); if (cmd_obj_sp) { AddAlias("detach", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("process continue", false); if (cmd_obj_sp) { AddAlias("c", cmd_obj_sp); AddAlias("continue", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("_regexp-break", false); if (cmd_obj_sp) AddAlias("b", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("_regexp-tbreak", false); if (cmd_obj_sp) AddAlias("tbreak", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("thread step-inst", false); if (cmd_obj_sp) { AddAlias("stepi", cmd_obj_sp); AddAlias("si", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("thread step-inst-over", false); if (cmd_obj_sp) { AddAlias("nexti", cmd_obj_sp); AddAlias("ni", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("thread step-in", false); if (cmd_obj_sp) { AddAlias("s", cmd_obj_sp); AddAlias("step", cmd_obj_sp); CommandAlias *sif_alias = AddAlias( "sif", cmd_obj_sp, "--end-linenumber block --step-in-target %1"); if (sif_alias) { sif_alias->SetHelp("Step through the current block, stopping if you step " "directly into a function whose name matches the " "TargetFunctionName."); sif_alias->SetSyntax("sif "); } } cmd_obj_sp = GetCommandSPExact("thread step-over", false); if (cmd_obj_sp) { AddAlias("n", cmd_obj_sp); AddAlias("next", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("thread step-out", false); if (cmd_obj_sp) { AddAlias("finish", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("frame select", false); if (cmd_obj_sp) { AddAlias("f", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("thread select", false); if (cmd_obj_sp) { AddAlias("t", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("_regexp-jump", false); if (cmd_obj_sp) { AddAlias("j", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); AddAlias("jump", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); } cmd_obj_sp = GetCommandSPExact("_regexp-list", false); if (cmd_obj_sp) { AddAlias("l", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); AddAlias("list", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); } cmd_obj_sp = GetCommandSPExact("_regexp-env", false); if (cmd_obj_sp) AddAlias("env", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("memory read", false); if (cmd_obj_sp) AddAlias("x", cmd_obj_sp); cmd_obj_sp = GetCommandSPExact("_regexp-up", false); if (cmd_obj_sp) AddAlias("up", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("_regexp-down", false); if (cmd_obj_sp) AddAlias("down", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("_regexp-display", false); if (cmd_obj_sp) AddAlias("display", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("disassemble", false); if (cmd_obj_sp) AddAlias("dis", cmd_obj_sp); cmd_obj_sp = GetCommandSPExact("disassemble", false); if (cmd_obj_sp) AddAlias("di", cmd_obj_sp); cmd_obj_sp = GetCommandSPExact("_regexp-undisplay", false); if (cmd_obj_sp) AddAlias("undisplay", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("_regexp-bt", false); if (cmd_obj_sp) AddAlias("bt", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); cmd_obj_sp = GetCommandSPExact("target create", false); if (cmd_obj_sp) AddAlias("file", cmd_obj_sp); cmd_obj_sp = GetCommandSPExact("target modules", false); if (cmd_obj_sp) AddAlias("image", cmd_obj_sp); alias_arguments_vector_sp = std::make_shared(); cmd_obj_sp = GetCommandSPExact("expression", false); if (cmd_obj_sp) { AddAlias("p", cmd_obj_sp, "--")->SetHelpLong(""); AddAlias("print", cmd_obj_sp, "--")->SetHelpLong(""); AddAlias("call", cmd_obj_sp, "--")->SetHelpLong(""); if (auto po = AddAlias("po", cmd_obj_sp, "-O --")) { po->SetHelp("Evaluate an expression on the current thread. Displays any " "returned value with formatting " "controlled by the type's author."); po->SetHelpLong(""); } AddAlias("parray", cmd_obj_sp, "--element-count %1 --")->SetHelpLong(""); AddAlias("poarray", cmd_obj_sp, "--object-description --element-count %1 --") ->SetHelpLong(""); } cmd_obj_sp = GetCommandSPExact("process kill", false); if (cmd_obj_sp) { AddAlias("kill", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("process launch", false); if (cmd_obj_sp) { alias_arguments_vector_sp = std::make_shared(); #if defined(__arm__) || defined(__arm64__) || defined(__aarch64__) AddAlias("r", cmd_obj_sp, "--"); AddAlias("run", cmd_obj_sp, "--"); #else #if defined(__APPLE__) std::string shell_option; shell_option.append("--shell-expand-args"); shell_option.append(" true"); shell_option.append(" --"); AddAlias("r", cmd_obj_sp, "--shell-expand-args true --"); AddAlias("run", cmd_obj_sp, "--shell-expand-args true --"); #else StreamString defaultshell; defaultshell.Printf("--shell=%s --", HostInfo::GetDefaultShell().GetPath().c_str()); AddAlias("r", cmd_obj_sp, defaultshell.GetString()); AddAlias("run", cmd_obj_sp, defaultshell.GetString()); #endif #endif } cmd_obj_sp = GetCommandSPExact("target symbols add", false); if (cmd_obj_sp) { AddAlias("add-dsym", cmd_obj_sp); } cmd_obj_sp = GetCommandSPExact("breakpoint set", false); if (cmd_obj_sp) { AddAlias("rbreak", cmd_obj_sp, "--func-regex %1"); } cmd_obj_sp = GetCommandSPExact("frame variable", false); if (cmd_obj_sp) { AddAlias("v", cmd_obj_sp); AddAlias("var", cmd_obj_sp); AddAlias("vo", cmd_obj_sp, "--object-description"); } cmd_obj_sp = GetCommandSPExact("register", false); if (cmd_obj_sp) { AddAlias("re", cmd_obj_sp); } } void CommandInterpreter::Clear() { m_command_io_handler_sp.reset(); } const char *CommandInterpreter::ProcessEmbeddedScriptCommands(const char *arg) { // This function has not yet been implemented. // Look for any embedded script command // If found, // get interpreter object from the command dictionary, // call execute_one_command on it, // get the results as a string, // substitute that string for current stuff. return arg; } void CommandInterpreter::LoadCommandDictionary() { static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); Timer scoped_timer(func_cat, LLVM_PRETTY_FUNCTION); lldb::ScriptLanguage script_language = m_debugger.GetScriptLanguage(); m_command_dict["apropos"] = CommandObjectSP(new CommandObjectApropos(*this)); m_command_dict["breakpoint"] = CommandObjectSP(new CommandObjectMultiwordBreakpoint(*this)); m_command_dict["bugreport"] = CommandObjectSP(new CommandObjectMultiwordBugreport(*this)); m_command_dict["command"] = CommandObjectSP(new CommandObjectMultiwordCommands(*this)); m_command_dict["disassemble"] = CommandObjectSP(new CommandObjectDisassemble(*this)); m_command_dict["expression"] = CommandObjectSP(new CommandObjectExpression(*this)); m_command_dict["frame"] = CommandObjectSP(new CommandObjectMultiwordFrame(*this)); m_command_dict["gui"] = CommandObjectSP(new CommandObjectGUI(*this)); m_command_dict["help"] = CommandObjectSP(new CommandObjectHelp(*this)); m_command_dict["log"] = CommandObjectSP(new CommandObjectLog(*this)); m_command_dict["memory"] = CommandObjectSP(new CommandObjectMemory(*this)); m_command_dict["platform"] = CommandObjectSP(new CommandObjectPlatform(*this)); m_command_dict["plugin"] = CommandObjectSP(new CommandObjectPlugin(*this)); m_command_dict["process"] = CommandObjectSP(new CommandObjectMultiwordProcess(*this)); m_command_dict["quit"] = CommandObjectSP(new CommandObjectQuit(*this)); m_command_dict["register"] = CommandObjectSP(new CommandObjectRegister(*this)); m_command_dict["reproducer"] = CommandObjectSP(new CommandObjectReproducer(*this)); m_command_dict["script"] = CommandObjectSP(new CommandObjectScript(*this, script_language)); m_command_dict["settings"] = CommandObjectSP(new CommandObjectMultiwordSettings(*this)); m_command_dict["source"] = CommandObjectSP(new CommandObjectMultiwordSource(*this)); m_command_dict["statistics"] = CommandObjectSP(new CommandObjectStats(*this)); m_command_dict["target"] = CommandObjectSP(new CommandObjectMultiwordTarget(*this)); m_command_dict["thread"] = CommandObjectSP(new CommandObjectMultiwordThread(*this)); m_command_dict["type"] = CommandObjectSP(new CommandObjectType(*this)); m_command_dict["version"] = CommandObjectSP(new CommandObjectVersion(*this)); m_command_dict["watchpoint"] = CommandObjectSP(new CommandObjectMultiwordWatchpoint(*this)); m_command_dict["language"] = CommandObjectSP(new CommandObjectLanguage(*this)); const char *break_regexes[][2] = { {"^(.*[^[:space:]])[[:space:]]*:[[:space:]]*([[:digit:]]+)[[:space:]]*$", "breakpoint set --file '%1' --line %2"}, {"^/([^/]+)/$", "breakpoint set --source-pattern-regexp '%1'"}, {"^([[:digit:]]+)[[:space:]]*$", "breakpoint set --line %1"}, {"^\\*?(0x[[:xdigit:]]+)[[:space:]]*$", "breakpoint set --address %1"}, {"^[\"']?([-+]?\\[.*\\])[\"']?[[:space:]]*$", "breakpoint set --name '%1'"}, {"^(-.*)$", "breakpoint set %1"}, {"^(.*[^[:space:]])`(.*[^[:space:]])[[:space:]]*$", "breakpoint set --name '%2' --shlib '%1'"}, {"^\\&(.*[^[:space:]])[[:space:]]*$", "breakpoint set --name '%1' --skip-prologue=0"}, {"^[\"']?(.*[^[:space:]\"'])[\"']?[[:space:]]*$", "breakpoint set --name '%1'"}}; size_t num_regexes = llvm::array_lengthof(break_regexes); std::unique_ptr break_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-break", "Set a breakpoint using one of several shorthand formats.", "\n" "_regexp-break :\n" " main.c:12 // Break at line 12 of " "main.c\n\n" "_regexp-break \n" " 12 // Break at line 12 of current " "file\n\n" "_regexp-break 0x
\n" " 0x1234000 // Break at address " "0x1234000\n\n" "_regexp-break \n" " main // Break in 'main' after the " "prologue\n\n" "_regexp-break &\n" " &main // Break at first instruction " "in 'main'\n\n" "_regexp-break `\n" " libc.so`malloc // Break in 'malloc' from " "'libc.so'\n\n" "_regexp-break //\n" " /break here/ // Break on source lines in " "current file\n" " // containing text 'break " "here'.\n", 2, CommandCompletions::eSymbolCompletion | CommandCompletions::eSourceFileCompletion, false)); if (break_regex_cmd_up) { bool success = true; for (size_t i = 0; i < num_regexes; i++) { success = break_regex_cmd_up->AddRegexCommand(break_regexes[i][0], break_regexes[i][1]); if (!success) break; } success = break_regex_cmd_up->AddRegexCommand("^$", "breakpoint list --full"); if (success) { CommandObjectSP break_regex_cmd_sp(break_regex_cmd_up.release()); m_command_dict[break_regex_cmd_sp->GetCommandName()] = break_regex_cmd_sp; } } std::unique_ptr tbreak_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-tbreak", "Set a one-shot breakpoint using one of several shorthand formats.", "\n" "_regexp-break :\n" " main.c:12 // Break at line 12 of " "main.c\n\n" "_regexp-break \n" " 12 // Break at line 12 of current " "file\n\n" "_regexp-break 0x
\n" " 0x1234000 // Break at address " "0x1234000\n\n" "_regexp-break \n" " main // Break in 'main' after the " "prologue\n\n" "_regexp-break &\n" " &main // Break at first instruction " "in 'main'\n\n" "_regexp-break `\n" " libc.so`malloc // Break in 'malloc' from " "'libc.so'\n\n" "_regexp-break //\n" " /break here/ // Break on source lines in " "current file\n" " // containing text 'break " "here'.\n", 2, CommandCompletions::eSymbolCompletion | CommandCompletions::eSourceFileCompletion, false)); if (tbreak_regex_cmd_up) { bool success = true; for (size_t i = 0; i < num_regexes; i++) { // If you add a resultant command string longer than 1024 characters be // sure to increase the size of this buffer. char buffer[1024]; int num_printed = snprintf(buffer, 1024, "%s %s", break_regexes[i][1], "-o 1"); lldbassert(num_printed < 1024); UNUSED_IF_ASSERT_DISABLED(num_printed); success = tbreak_regex_cmd_up->AddRegexCommand(break_regexes[i][0], buffer); if (!success) break; } success = tbreak_regex_cmd_up->AddRegexCommand("^$", "breakpoint list --full"); if (success) { CommandObjectSP tbreak_regex_cmd_sp(tbreak_regex_cmd_up.release()); m_command_dict[tbreak_regex_cmd_sp->GetCommandName()] = tbreak_regex_cmd_sp; } } std::unique_ptr attach_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-attach", "Attach to process by ID or name.", "_regexp-attach | ", 2, 0, false)); if (attach_regex_cmd_up) { if (attach_regex_cmd_up->AddRegexCommand("^([0-9]+)[[:space:]]*$", "process attach --pid %1") && attach_regex_cmd_up->AddRegexCommand( "^(-.*|.* -.*)$", "process attach %1") && // Any options that are // specified get passed to // 'process attach' attach_regex_cmd_up->AddRegexCommand("^(.+)$", "process attach --name '%1'") && attach_regex_cmd_up->AddRegexCommand("^$", "process attach")) { CommandObjectSP attach_regex_cmd_sp(attach_regex_cmd_up.release()); m_command_dict[attach_regex_cmd_sp->GetCommandName()] = attach_regex_cmd_sp; } } std::unique_ptr down_regex_cmd_up( new CommandObjectRegexCommand(*this, "_regexp-down", "Select a newer stack frame. Defaults to " "moving one frame, a numeric argument can " "specify an arbitrary number.", "_regexp-down []", 2, 0, false)); if (down_regex_cmd_up) { if (down_regex_cmd_up->AddRegexCommand("^$", "frame select -r -1") && down_regex_cmd_up->AddRegexCommand("^([0-9]+)$", "frame select -r -%1")) { CommandObjectSP down_regex_cmd_sp(down_regex_cmd_up.release()); m_command_dict[down_regex_cmd_sp->GetCommandName()] = down_regex_cmd_sp; } } std::unique_ptr up_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-up", "Select an older stack frame. Defaults to moving one " "frame, a numeric argument can specify an arbitrary number.", "_regexp-up []", 2, 0, false)); if (up_regex_cmd_up) { if (up_regex_cmd_up->AddRegexCommand("^$", "frame select -r 1") && up_regex_cmd_up->AddRegexCommand("^([0-9]+)$", "frame select -r %1")) { CommandObjectSP up_regex_cmd_sp(up_regex_cmd_up.release()); m_command_dict[up_regex_cmd_sp->GetCommandName()] = up_regex_cmd_sp; } } std::unique_ptr display_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-display", "Evaluate an expression at every stop (see 'help target stop-hook'.)", "_regexp-display expression", 2, 0, false)); if (display_regex_cmd_up) { if (display_regex_cmd_up->AddRegexCommand( "^(.+)$", "target stop-hook add -o \"expr -- %1\"")) { CommandObjectSP display_regex_cmd_sp(display_regex_cmd_up.release()); m_command_dict[display_regex_cmd_sp->GetCommandName()] = display_regex_cmd_sp; } } std::unique_ptr undisplay_regex_cmd_up( new CommandObjectRegexCommand(*this, "_regexp-undisplay", "Stop displaying expression at every " "stop (specified by stop-hook index.)", "_regexp-undisplay stop-hook-number", 2, 0, false)); if (undisplay_regex_cmd_up) { if (undisplay_regex_cmd_up->AddRegexCommand("^([0-9]+)$", "target stop-hook delete %1")) { CommandObjectSP undisplay_regex_cmd_sp(undisplay_regex_cmd_up.release()); m_command_dict[undisplay_regex_cmd_sp->GetCommandName()] = undisplay_regex_cmd_sp; } } std::unique_ptr connect_gdb_remote_cmd_up( new CommandObjectRegexCommand( *this, "gdb-remote", "Connect to a process via remote GDB server. " "If no host is specifed, localhost is assumed.", "gdb-remote [:]", 2, 0, false)); if (connect_gdb_remote_cmd_up) { if (connect_gdb_remote_cmd_up->AddRegexCommand( "^([^:]+|\\[[0-9a-fA-F:]+.*\\]):([0-9]+)$", "process connect --plugin gdb-remote connect://%1:%2") && connect_gdb_remote_cmd_up->AddRegexCommand( "^([[:digit:]]+)$", "process connect --plugin gdb-remote connect://localhost:%1")) { CommandObjectSP command_sp(connect_gdb_remote_cmd_up.release()); m_command_dict[command_sp->GetCommandName()] = command_sp; } } std::unique_ptr connect_kdp_remote_cmd_up( new CommandObjectRegexCommand( *this, "kdp-remote", "Connect to a process via remote KDP server. " "If no UDP port is specified, port 41139 is " "assumed.", "kdp-remote [:]", 2, 0, false)); if (connect_kdp_remote_cmd_up) { if (connect_kdp_remote_cmd_up->AddRegexCommand( "^([^:]+:[[:digit:]]+)$", "process connect --plugin kdp-remote udp://%1") && connect_kdp_remote_cmd_up->AddRegexCommand( "^(.+)$", "process connect --plugin kdp-remote udp://%1:41139")) { CommandObjectSP command_sp(connect_kdp_remote_cmd_up.release()); m_command_dict[command_sp->GetCommandName()] = command_sp; } } std::unique_ptr bt_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-bt", "Show the current thread's call stack. Any numeric argument " "displays at most that many " "frames. The argument 'all' displays all threads. Use 'settings" " set frame-format' to customize the printing of individual frames " "and 'settings set thread-format' to customize the thread header.", "bt [ | all]", 2, 0, false)); if (bt_regex_cmd_up) { // accept but don't document "bt -c " -- before bt was a regex // command if you wanted to backtrace three frames you would do "bt -c 3" // but the intention is to have this emulate the gdb "bt" command and so // now "bt 3" is the preferred form, in line with gdb. if (bt_regex_cmd_up->AddRegexCommand("^([[:digit:]]+)[[:space:]]*$", "thread backtrace -c %1") && bt_regex_cmd_up->AddRegexCommand("^-c ([[:digit:]]+)[[:space:]]*$", "thread backtrace -c %1") && bt_regex_cmd_up->AddRegexCommand("^all[[:space:]]*$", "thread backtrace all") && bt_regex_cmd_up->AddRegexCommand("^[[:space:]]*$", "thread backtrace")) { CommandObjectSP command_sp(bt_regex_cmd_up.release()); m_command_dict[command_sp->GetCommandName()] = command_sp; } } std::unique_ptr list_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-list", "List relevant source code using one of several shorthand formats.", "\n" "_regexp-list : // List around specific file/line\n" "_regexp-list // List current file around specified " "line\n" "_regexp-list // List specified function\n" "_regexp-list 0x
// List around specified address\n" "_regexp-list -[] // List previous lines\n" "_regexp-list // List subsequent lines", 2, CommandCompletions::eSourceFileCompletion, false)); if (list_regex_cmd_up) { if (list_regex_cmd_up->AddRegexCommand("^([0-9]+)[[:space:]]*$", "source list --line %1") && list_regex_cmd_up->AddRegexCommand( "^(.*[^[:space:]])[[:space:]]*:[[:space:]]*([[:digit:]]+)[[:space:]" "]*$", "source list --file '%1' --line %2") && list_regex_cmd_up->AddRegexCommand( "^\\*?(0x[[:xdigit:]]+)[[:space:]]*$", "source list --address %1") && list_regex_cmd_up->AddRegexCommand("^-[[:space:]]*$", "source list --reverse") && list_regex_cmd_up->AddRegexCommand( "^-([[:digit:]]+)[[:space:]]*$", "source list --reverse --count %1") && list_regex_cmd_up->AddRegexCommand("^(.+)$", "source list --name \"%1\"") && list_regex_cmd_up->AddRegexCommand("^$", "source list")) { CommandObjectSP list_regex_cmd_sp(list_regex_cmd_up.release()); m_command_dict[list_regex_cmd_sp->GetCommandName()] = list_regex_cmd_sp; } } std::unique_ptr env_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-env", "Shorthand for viewing and setting environment variables.", "\n" "_regexp-env // Show environment\n" "_regexp-env = // Set an environment variable", 2, 0, false)); if (env_regex_cmd_up) { if (env_regex_cmd_up->AddRegexCommand("^$", "settings show target.env-vars") && env_regex_cmd_up->AddRegexCommand("^([A-Za-z_][A-Za-z_0-9]*=.*)$", "settings set target.env-vars %1")) { CommandObjectSP env_regex_cmd_sp(env_regex_cmd_up.release()); m_command_dict[env_regex_cmd_sp->GetCommandName()] = env_regex_cmd_sp; } } std::unique_ptr jump_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-jump", "Set the program counter to a new address.", "\n" "_regexp-jump \n" "_regexp-jump + | -\n" "_regexp-jump :\n" "_regexp-jump *\n", 2, 0, false)); if (jump_regex_cmd_up) { if (jump_regex_cmd_up->AddRegexCommand("^\\*(.*)$", "thread jump --addr %1") && jump_regex_cmd_up->AddRegexCommand("^([0-9]+)$", "thread jump --line %1") && jump_regex_cmd_up->AddRegexCommand("^([^:]+):([0-9]+)$", "thread jump --file %1 --line %2") && jump_regex_cmd_up->AddRegexCommand("^([+\\-][0-9]+)$", "thread jump --by %1")) { CommandObjectSP jump_regex_cmd_sp(jump_regex_cmd_up.release()); m_command_dict[jump_regex_cmd_sp->GetCommandName()] = jump_regex_cmd_sp; } } } int CommandInterpreter::GetCommandNamesMatchingPartialString( const char *cmd_str, bool include_aliases, StringList &matches, StringList &descriptions) { AddNamesMatchingPartialString(m_command_dict, cmd_str, matches, &descriptions); if (include_aliases) { AddNamesMatchingPartialString(m_alias_dict, cmd_str, matches, &descriptions); } return matches.GetSize(); } CommandObjectSP CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, bool exact, StringList *matches, StringList *descriptions) const { CommandObjectSP command_sp; std::string cmd = cmd_str; if (HasCommands()) { auto pos = m_command_dict.find(cmd); if (pos != m_command_dict.end()) command_sp = pos->second; } if (include_aliases && HasAliases()) { auto alias_pos = m_alias_dict.find(cmd); if (alias_pos != m_alias_dict.end()) command_sp = alias_pos->second; } if (HasUserCommands()) { auto pos = m_user_dict.find(cmd); if (pos != m_user_dict.end()) command_sp = pos->second; } if (!exact && !command_sp) { // We will only get into here if we didn't find any exact matches. CommandObjectSP user_match_sp, alias_match_sp, real_match_sp; StringList local_matches; if (matches == nullptr) matches = &local_matches; unsigned int num_cmd_matches = 0; unsigned int num_alias_matches = 0; unsigned int num_user_matches = 0; // Look through the command dictionaries one by one, and if we get only one // match from any of them in toto, then return that, otherwise return an // empty CommandObjectSP and the list of matches. if (HasCommands()) { num_cmd_matches = AddNamesMatchingPartialString(m_command_dict, cmd_str, *matches, descriptions); } if (num_cmd_matches == 1) { cmd.assign(matches->GetStringAtIndex(0)); auto pos = m_command_dict.find(cmd); if (pos != m_command_dict.end()) real_match_sp = pos->second; } if (include_aliases && HasAliases()) { num_alias_matches = AddNamesMatchingPartialString(m_alias_dict, cmd_str, *matches, descriptions); } if (num_alias_matches == 1) { cmd.assign(matches->GetStringAtIndex(num_cmd_matches)); auto alias_pos = m_alias_dict.find(cmd); if (alias_pos != m_alias_dict.end()) alias_match_sp = alias_pos->second; } if (HasUserCommands()) { num_user_matches = AddNamesMatchingPartialString(m_user_dict, cmd_str, *matches, descriptions); } if (num_user_matches == 1) { cmd.assign( matches->GetStringAtIndex(num_cmd_matches + num_alias_matches)); auto pos = m_user_dict.find(cmd); if (pos != m_user_dict.end()) user_match_sp = pos->second; } // If we got exactly one match, return that, otherwise return the match // list. if (num_user_matches + num_cmd_matches + num_alias_matches == 1) { if (num_cmd_matches) return real_match_sp; else if (num_alias_matches) return alias_match_sp; else return user_match_sp; } } else if (matches && command_sp) { matches->AppendString(cmd_str); if (descriptions) descriptions->AppendString(command_sp->GetHelp()); } return command_sp; } bool CommandInterpreter::AddCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp, bool can_replace) { if (cmd_sp.get()) lldbassert((this == &cmd_sp->GetCommandInterpreter()) && "tried to add a CommandObject from a different interpreter"); if (name.empty()) return false; std::string name_sstr(name); auto name_iter = m_command_dict.find(name_sstr); if (name_iter != m_command_dict.end()) { if (!can_replace || !name_iter->second->IsRemovable()) return false; name_iter->second = cmd_sp; } else { m_command_dict[name_sstr] = cmd_sp; } return true; } bool CommandInterpreter::AddUserCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp, bool can_replace) { if (cmd_sp.get()) lldbassert((this == &cmd_sp->GetCommandInterpreter()) && "tried to add a CommandObject from a different interpreter"); if (!name.empty()) { // do not allow replacement of internal commands if (CommandExists(name)) { if (!can_replace) return false; if (!m_command_dict[name]->IsRemovable()) return false; } if (UserCommandExists(name)) { if (!can_replace) return false; if (!m_user_dict[name]->IsRemovable()) return false; } m_user_dict[name] = cmd_sp; return true; } return false; } CommandObjectSP CommandInterpreter::GetCommandSPExact(llvm::StringRef cmd_str, bool include_aliases) const { Args cmd_words(cmd_str); // Break up the command string into words, in case // it's a multi-word command. CommandObjectSP ret_val; // Possibly empty return value. if (cmd_str.empty()) return ret_val; if (cmd_words.GetArgumentCount() == 1) return GetCommandSP(cmd_str, include_aliases, true, nullptr); else { // We have a multi-word command (seemingly), so we need to do more work. // First, get the cmd_obj_sp for the first word in the command. CommandObjectSP cmd_obj_sp = GetCommandSP(llvm::StringRef(cmd_words.GetArgumentAtIndex(0)), include_aliases, true, nullptr); if (cmd_obj_sp.get() != nullptr) { // Loop through the rest of the words in the command (everything passed // in was supposed to be part of a command name), and find the // appropriate sub-command SP for each command word.... size_t end = cmd_words.GetArgumentCount(); for (size_t j = 1; j < end; ++j) { if (cmd_obj_sp->IsMultiwordObject()) { cmd_obj_sp = cmd_obj_sp->GetSubcommandSP(cmd_words.GetArgumentAtIndex(j)); if (cmd_obj_sp.get() == nullptr) // The sub-command name was invalid. Fail and return the empty // 'ret_val'. return ret_val; } else // We have more words in the command name, but we don't have a // multiword object. Fail and return empty 'ret_val'. return ret_val; } // We successfully looped through all the command words and got valid // command objects for them. Assign the last object retrieved to // 'ret_val'. ret_val = cmd_obj_sp; } } return ret_val; } CommandObject * CommandInterpreter::GetCommandObject(llvm::StringRef cmd_str, StringList *matches, StringList *descriptions) const { CommandObject *command_obj = GetCommandSP(cmd_str, false, true, matches, descriptions).get(); // If we didn't find an exact match to the command string in the commands, // look in the aliases. if (command_obj) return command_obj; command_obj = GetCommandSP(cmd_str, true, true, matches, descriptions).get(); if (command_obj) return command_obj; // If there wasn't an exact match then look for an inexact one in just the // commands command_obj = GetCommandSP(cmd_str, false, false, nullptr).get(); // Finally, if there wasn't an inexact match among the commands, look for an // inexact match in both the commands and aliases. if (command_obj) { if (matches) matches->AppendString(command_obj->GetCommandName()); if (descriptions) descriptions->AppendString(command_obj->GetHelp()); return command_obj; } return GetCommandSP(cmd_str, true, false, matches, descriptions).get(); } bool CommandInterpreter::CommandExists(llvm::StringRef cmd) const { return m_command_dict.find(cmd) != m_command_dict.end(); } bool CommandInterpreter::GetAliasFullName(llvm::StringRef cmd, std::string &full_name) const { bool exact_match = (m_alias_dict.find(cmd) != m_alias_dict.end()); if (exact_match) { full_name.assign(cmd); return exact_match; } else { StringList matches; size_t num_alias_matches; num_alias_matches = AddNamesMatchingPartialString(m_alias_dict, cmd, matches); if (num_alias_matches == 1) { // Make sure this isn't shadowing a command in the regular command space: StringList regular_matches; const bool include_aliases = false; const bool exact = false; CommandObjectSP cmd_obj_sp( GetCommandSP(cmd, include_aliases, exact, ®ular_matches)); if (cmd_obj_sp || regular_matches.GetSize() > 0) return false; else { full_name.assign(matches.GetStringAtIndex(0)); return true; } } else return false; } } bool CommandInterpreter::AliasExists(llvm::StringRef cmd) const { return m_alias_dict.find(cmd) != m_alias_dict.end(); } bool CommandInterpreter::UserCommandExists(llvm::StringRef cmd) const { return m_user_dict.find(cmd) != m_user_dict.end(); } CommandAlias * CommandInterpreter::AddAlias(llvm::StringRef alias_name, lldb::CommandObjectSP &command_obj_sp, llvm::StringRef args_string) { if (command_obj_sp.get()) lldbassert((this == &command_obj_sp->GetCommandInterpreter()) && "tried to add a CommandObject from a different interpreter"); std::unique_ptr command_alias_up( new CommandAlias(*this, command_obj_sp, args_string, alias_name)); if (command_alias_up && command_alias_up->IsValid()) { m_alias_dict[alias_name] = CommandObjectSP(command_alias_up.get()); return command_alias_up.release(); } return nullptr; } bool CommandInterpreter::RemoveAlias(llvm::StringRef alias_name) { auto pos = m_alias_dict.find(alias_name); if (pos != m_alias_dict.end()) { m_alias_dict.erase(pos); return true; } return false; } bool CommandInterpreter::RemoveCommand(llvm::StringRef cmd) { auto pos = m_command_dict.find(cmd); if (pos != m_command_dict.end()) { if (pos->second->IsRemovable()) { // Only regular expression objects or python commands are removable m_command_dict.erase(pos); return true; } } return false; } bool CommandInterpreter::RemoveUser(llvm::StringRef alias_name) { CommandObject::CommandMap::iterator pos = m_user_dict.find(alias_name); if (pos != m_user_dict.end()) { m_user_dict.erase(pos); return true; } return false; } void CommandInterpreter::GetHelp(CommandReturnObject &result, uint32_t cmd_types) { llvm::StringRef help_prologue(GetDebugger().GetIOHandlerHelpPrologue()); if (!help_prologue.empty()) { OutputFormattedHelpText(result.GetOutputStream(), llvm::StringRef(), help_prologue); } CommandObject::CommandMap::const_iterator pos; size_t max_len = FindLongestCommandWord(m_command_dict); if ((cmd_types & eCommandTypesBuiltin) == eCommandTypesBuiltin) { result.AppendMessage("Debugger commands:"); result.AppendMessage(""); for (pos = m_command_dict.begin(); pos != m_command_dict.end(); ++pos) { if (!(cmd_types & eCommandTypesHidden) && (pos->first.compare(0, 1, "_") == 0)) continue; OutputFormattedHelpText(result.GetOutputStream(), pos->first, "--", pos->second->GetHelp(), max_len); } result.AppendMessage(""); } if (!m_alias_dict.empty() && ((cmd_types & eCommandTypesAliases) == eCommandTypesAliases)) { result.AppendMessageWithFormat( "Current command abbreviations " "(type '%shelp command alias' for more info):\n", GetCommandPrefix()); result.AppendMessage(""); max_len = FindLongestCommandWord(m_alias_dict); for (auto alias_pos = m_alias_dict.begin(); alias_pos != m_alias_dict.end(); ++alias_pos) { OutputFormattedHelpText(result.GetOutputStream(), alias_pos->first, "--", alias_pos->second->GetHelp(), max_len); } result.AppendMessage(""); } if (!m_user_dict.empty() && ((cmd_types & eCommandTypesUserDef) == eCommandTypesUserDef)) { result.AppendMessage("Current user-defined commands:"); result.AppendMessage(""); max_len = FindLongestCommandWord(m_user_dict); for (pos = m_user_dict.begin(); pos != m_user_dict.end(); ++pos) { OutputFormattedHelpText(result.GetOutputStream(), pos->first, "--", pos->second->GetHelp(), max_len); } result.AppendMessage(""); } result.AppendMessageWithFormat( "For more information on any command, type '%shelp '.\n", GetCommandPrefix()); } CommandObject *CommandInterpreter::GetCommandObjectForCommand( llvm::StringRef &command_string) { // This function finds the final, lowest-level, alias-resolved command object // whose 'Execute' function will eventually be invoked by the given command // line. CommandObject *cmd_obj = nullptr; size_t start = command_string.find_first_not_of(k_white_space); size_t end = 0; bool done = false; while (!done) { if (start != std::string::npos) { // Get the next word from command_string. end = command_string.find_first_of(k_white_space, start); if (end == std::string::npos) end = command_string.size(); std::string cmd_word = command_string.substr(start, end - start); if (cmd_obj == nullptr) // Since cmd_obj is NULL we are on our first time through this loop. // Check to see if cmd_word is a valid command or alias. cmd_obj = GetCommandObject(cmd_word); else if (cmd_obj->IsMultiwordObject()) { // Our current object is a multi-word object; see if the cmd_word is a // valid sub-command for our object. CommandObject *sub_cmd_obj = cmd_obj->GetSubcommandObject(cmd_word.c_str()); if (sub_cmd_obj) cmd_obj = sub_cmd_obj; else // cmd_word was not a valid sub-command word, so we are done done = true; } else // We have a cmd_obj and it is not a multi-word object, so we are done. done = true; // If we didn't find a valid command object, or our command object is not // a multi-word object, or we are at the end of the command_string, then // we are done. Otherwise, find the start of the next word. if (!cmd_obj || !cmd_obj->IsMultiwordObject() || end >= command_string.size()) done = true; else start = command_string.find_first_not_of(k_white_space, end); } else // Unable to find any more words. done = true; } command_string = command_string.substr(end); return cmd_obj; } static const char *k_valid_command_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; static void StripLeadingSpaces(std::string &s) { if (!s.empty()) { size_t pos = s.find_first_not_of(k_white_space); if (pos == std::string::npos) s.clear(); else if (pos == 0) return; s.erase(0, pos); } } static size_t FindArgumentTerminator(const std::string &s) { const size_t s_len = s.size(); size_t offset = 0; while (offset < s_len) { size_t pos = s.find("--", offset); if (pos == std::string::npos) break; if (pos > 0) { if (isspace(s[pos - 1])) { // Check if the string ends "\s--" (where \s is a space character) or // if we have "\s--\s". if ((pos + 2 >= s_len) || isspace(s[pos + 2])) { return pos; } } } offset = pos + 2; } return std::string::npos; } static bool ExtractCommand(std::string &command_string, std::string &command, std::string &suffix, char "e_char) { command.clear(); suffix.clear(); StripLeadingSpaces(command_string); bool result = false; quote_char = '\0'; if (!command_string.empty()) { const char first_char = command_string[0]; if (first_char == '\'' || first_char == '"') { quote_char = first_char; const size_t end_quote_pos = command_string.find(quote_char, 1); if (end_quote_pos == std::string::npos) { command.swap(command_string); command_string.erase(); } else { command.assign(command_string, 1, end_quote_pos - 1); if (end_quote_pos + 1 < command_string.size()) command_string.erase(0, command_string.find_first_not_of( k_white_space, end_quote_pos + 1)); else command_string.erase(); } } else { const size_t first_space_pos = command_string.find_first_of(k_white_space); if (first_space_pos == std::string::npos) { command.swap(command_string); command_string.erase(); } else { command.assign(command_string, 0, first_space_pos); command_string.erase(0, command_string.find_first_not_of( k_white_space, first_space_pos)); } } result = true; } if (!command.empty()) { // actual commands can't start with '-' or '_' if (command[0] != '-' && command[0] != '_') { size_t pos = command.find_first_not_of(k_valid_command_chars); if (pos > 0 && pos != std::string::npos) { suffix.assign(command.begin() + pos, command.end()); command.erase(pos); } } } return result; } CommandObject *CommandInterpreter::BuildAliasResult( llvm::StringRef alias_name, std::string &raw_input_string, std::string &alias_result, CommandReturnObject &result) { CommandObject *alias_cmd_obj = nullptr; Args cmd_args(raw_input_string); alias_cmd_obj = GetCommandObject(alias_name); StreamString result_str; if (!alias_cmd_obj || !alias_cmd_obj->IsAlias()) { alias_result.clear(); return alias_cmd_obj; } std::pair desugared = ((CommandAlias *)alias_cmd_obj)->Desugar(); OptionArgVectorSP option_arg_vector_sp = desugared.second; alias_cmd_obj = desugared.first.get(); std::string alias_name_str = alias_name; if ((cmd_args.GetArgumentCount() == 0) || (alias_name_str != cmd_args.GetArgumentAtIndex(0))) cmd_args.Unshift(alias_name_str); result_str.Printf("%s", alias_cmd_obj->GetCommandName().str().c_str()); if (!option_arg_vector_sp.get()) { alias_result = result_str.GetString(); return alias_cmd_obj; } OptionArgVector *option_arg_vector = option_arg_vector_sp.get(); int value_type; std::string option; std::string value; for (const auto &entry : *option_arg_vector) { std::tie(option, value_type, value) = entry; if (option == "") { result_str.Printf(" %s", value.c_str()); continue; } result_str.Printf(" %s", option.c_str()); if (value_type == OptionParser::eNoArgument) continue; if (value_type != OptionParser::eOptionalArgument) result_str.Printf(" "); int index = GetOptionArgumentPosition(value.c_str()); if (index == 0) result_str.Printf("%s", value.c_str()); else if (static_cast(index) >= cmd_args.GetArgumentCount()) { result.AppendErrorWithFormat("Not enough arguments provided; you " "need at least %d arguments to use " "this alias.\n", index); result.SetStatus(eReturnStatusFailed); return nullptr; } else { size_t strpos = raw_input_string.find(cmd_args.GetArgumentAtIndex(index)); if (strpos != std::string::npos) raw_input_string = raw_input_string.erase( strpos, strlen(cmd_args.GetArgumentAtIndex(index))); result_str.Printf("%s", cmd_args.GetArgumentAtIndex(index)); } } alias_result = result_str.GetString(); return alias_cmd_obj; } Status CommandInterpreter::PreprocessCommand(std::string &command) { // The command preprocessor needs to do things to the command line before any // parsing of arguments or anything else is done. The only current stuff that // gets preprocessed is anything enclosed in backtick ('`') characters is // evaluated as an expression and the result of the expression must be a // scalar that can be substituted into the command. An example would be: // (lldb) memory read `$rsp + 20` Status error; // Status for any expressions that might not evaluate size_t start_backtick; size_t pos = 0; while ((start_backtick = command.find('`', pos)) != std::string::npos) { // Stop if an error was encountered during the previous iteration. if (error.Fail()) break; if (start_backtick > 0 && command[start_backtick - 1] == '\\') { // The backtick was preceded by a '\' character, remove the slash and // don't treat the backtick as the start of an expression. command.erase(start_backtick - 1, 1); // No need to add one to start_backtick since we just deleted a char. pos = start_backtick; continue; } const size_t expr_content_start = start_backtick + 1; const size_t end_backtick = command.find('`', expr_content_start); if (end_backtick == std::string::npos) { // Stop if there's no end backtick. break; } if (end_backtick == expr_content_start) { // Skip over empty expression. (two backticks in a row) command.erase(start_backtick, 2); continue; } std::string expr_str(command, expr_content_start, end_backtick - expr_content_start); ExecutionContext exe_ctx(GetExecutionContext()); Target *target = exe_ctx.GetTargetPtr(); // Get a dummy target to allow for calculator mode while processing // backticks. This also helps break the infinite loop caused when target is // null. if (!target) target = m_debugger.GetDummyTarget(); if (!target) continue; ValueObjectSP expr_result_valobj_sp; EvaluateExpressionOptions options; options.SetCoerceToId(false); options.SetUnwindOnError(true); options.SetIgnoreBreakpoints(true); options.SetKeepInMemory(false); options.SetTryAllThreads(true); options.SetTimeout(llvm::None); ExpressionResults expr_result = target->EvaluateExpression(expr_str.c_str(), exe_ctx.GetFramePtr(), expr_result_valobj_sp, options); if (expr_result == eExpressionCompleted) { Scalar scalar; if (expr_result_valobj_sp) expr_result_valobj_sp = expr_result_valobj_sp->GetQualifiedRepresentationIfAvailable( expr_result_valobj_sp->GetDynamicValueType(), true); if (expr_result_valobj_sp->ResolveValue(scalar)) { command.erase(start_backtick, end_backtick - start_backtick + 1); StreamString value_strm; const bool show_type = false; scalar.GetValue(&value_strm, show_type); size_t value_string_size = value_strm.GetSize(); if (value_string_size) { command.insert(start_backtick, value_strm.GetString()); pos = start_backtick + value_string_size; continue; } else { error.SetErrorStringWithFormat("expression value didn't result " "in a scalar value for the " "expression '%s'", expr_str.c_str()); break; } } else { error.SetErrorStringWithFormat("expression value didn't result " "in a scalar value for the " "expression '%s'", expr_str.c_str()); break; } continue; } if (expr_result_valobj_sp) error = expr_result_valobj_sp->GetError(); if (error.Success()) { switch (expr_result) { case eExpressionSetupError: error.SetErrorStringWithFormat( "expression setup error for the expression '%s'", expr_str.c_str()); break; case eExpressionParseError: error.SetErrorStringWithFormat( "expression parse error for the expression '%s'", expr_str.c_str()); break; case eExpressionResultUnavailable: error.SetErrorStringWithFormat( "expression error fetching result for the expression '%s'", expr_str.c_str()); break; case eExpressionCompleted: break; case eExpressionDiscarded: error.SetErrorStringWithFormat( "expression discarded for the expression '%s'", expr_str.c_str()); break; case eExpressionInterrupted: error.SetErrorStringWithFormat( "expression interrupted for the expression '%s'", expr_str.c_str()); break; case eExpressionHitBreakpoint: error.SetErrorStringWithFormat( "expression hit breakpoint for the expression '%s'", expr_str.c_str()); break; case eExpressionTimedOut: error.SetErrorStringWithFormat( "expression timed out for the expression '%s'", expr_str.c_str()); break; case eExpressionStoppedForDebug: error.SetErrorStringWithFormat("expression stop at entry point " "for debugging for the " "expression '%s'", expr_str.c_str()); break; } } } return error; } bool CommandInterpreter::HandleCommand(const char *command_line, LazyBool lazy_add_to_history, CommandReturnObject &result, ExecutionContext *override_context, bool repeat_on_empty_command, bool no_context_switching) { std::string command_string(command_line); std::string original_command_string(command_line); Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_COMMANDS)); llvm::PrettyStackTraceFormat stack_trace("HandleCommand(command = \"%s\")", command_line); LLDB_LOGF(log, "Processing command: %s", command_line); static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); Timer scoped_timer(func_cat, "Handling command: %s.", command_line); if (!no_context_switching) UpdateExecutionContext(override_context); if (WasInterrupted()) { result.AppendError("interrupted"); result.SetStatus(eReturnStatusFailed); return false; } bool add_to_history; if (lazy_add_to_history == eLazyBoolCalculate) add_to_history = (m_command_source_depth == 0); else add_to_history = (lazy_add_to_history == eLazyBoolYes); bool empty_command = false; bool comment_command = false; if (command_string.empty()) empty_command = true; else { const char *k_space_characters = "\t\n\v\f\r "; size_t non_space = command_string.find_first_not_of(k_space_characters); // Check for empty line or comment line (lines whose first non-space // character is the comment character for this interpreter) if (non_space == std::string::npos) empty_command = true; else if (command_string[non_space] == m_comment_char) comment_command = true; else if (command_string[non_space] == CommandHistory::g_repeat_char) { llvm::StringRef search_str(command_string); search_str = search_str.drop_front(non_space); if (auto hist_str = m_command_history.FindString(search_str)) { add_to_history = false; command_string = *hist_str; original_command_string = *hist_str; } else { result.AppendErrorWithFormat("Could not find entry: %s in history", command_string.c_str()); result.SetStatus(eReturnStatusFailed); return false; } } } if (empty_command) { if (repeat_on_empty_command) { if (m_command_history.IsEmpty()) { result.AppendError("empty command"); result.SetStatus(eReturnStatusFailed); return false; } else { command_line = m_repeat_command.c_str(); command_string = command_line; original_command_string = command_line; if (m_repeat_command.empty()) { result.AppendErrorWithFormat("No auto repeat.\n"); result.SetStatus(eReturnStatusFailed); return false; } } add_to_history = false; } else { result.SetStatus(eReturnStatusSuccessFinishNoResult); return true; } } else if (comment_command) { result.SetStatus(eReturnStatusSuccessFinishNoResult); return true; } Status error(PreprocessCommand(command_string)); if (error.Fail()) { result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } // Phase 1. // Before we do ANY kind of argument processing, we need to figure out what // the real/final command object is for the specified command. This gets // complicated by the fact that the user could have specified an alias, and, // in translating the alias, there may also be command options and/or even // data (including raw text strings) that need to be found and inserted into // the command line as part of the translation. So this first step is plain // look-up and replacement, resulting in: // 1. the command object whose Execute method will actually be called // 2. a revised command string, with all substitutions and replacements // taken care of // From 1 above, we can determine whether the Execute function wants raw // input or not. CommandObject *cmd_obj = ResolveCommandImpl(command_string, result); // Although the user may have abbreviated the command, the command_string now // has the command expanded to the full name. For example, if the input was // "br s -n main", command_string is now "breakpoint set -n main". if (log) { llvm::StringRef command_name = cmd_obj ? cmd_obj->GetCommandName() : ""; LLDB_LOGF(log, "HandleCommand, cmd_obj : '%s'", command_name.str().c_str()); LLDB_LOGF(log, "HandleCommand, (revised) command_string: '%s'", command_string.c_str()); const bool wants_raw_input = (cmd_obj != nullptr) ? cmd_obj->WantsRawCommandString() : false; LLDB_LOGF(log, "HandleCommand, wants_raw_input:'%s'", wants_raw_input ? "True" : "False"); } // Phase 2. // Take care of things like setting up the history command & calling the // appropriate Execute method on the CommandObject, with the appropriate // arguments. if (cmd_obj != nullptr) { if (add_to_history) { Args command_args(command_string); const char *repeat_command = cmd_obj->GetRepeatCommand(command_args, 0); if (repeat_command != nullptr) m_repeat_command.assign(repeat_command); else m_repeat_command.assign(original_command_string); m_command_history.AppendString(original_command_string); } std::string remainder; const std::size_t actual_cmd_name_len = cmd_obj->GetCommandName().size(); if (actual_cmd_name_len < command_string.length()) remainder = command_string.substr(actual_cmd_name_len); // Remove any initial spaces size_t pos = remainder.find_first_not_of(k_white_space); if (pos != 0 && pos != std::string::npos) remainder.erase(0, pos); LLDB_LOGF( log, "HandleCommand, command line after removing command name(s): '%s'", remainder.c_str()); cmd_obj->Execute(remainder.c_str(), result); } LLDB_LOGF(log, "HandleCommand, command %s", (result.Succeeded() ? "succeeded" : "did not succeed")); return result.Succeeded(); } int CommandInterpreter::HandleCompletionMatches(CompletionRequest &request) { int num_command_matches = 0; bool look_for_subcommand = false; // For any of the command completions a unique match will be a complete word. request.SetWordComplete(true); if (request.GetCursorIndex() == -1) { // We got nothing on the command line, so return the list of commands bool include_aliases = true; StringList new_matches, descriptions; num_command_matches = GetCommandNamesMatchingPartialString( "", include_aliases, new_matches, descriptions); request.AddCompletions(new_matches, descriptions); } else if (request.GetCursorIndex() == 0) { // The cursor is in the first argument, so just do a lookup in the // dictionary. StringList new_matches, new_descriptions; CommandObject *cmd_obj = GetCommandObject(request.GetParsedLine().GetArgumentAtIndex(0), &new_matches, &new_descriptions); if (num_command_matches == 1 && cmd_obj && cmd_obj->IsMultiwordObject() && new_matches.GetStringAtIndex(0) != nullptr && strcmp(request.GetParsedLine().GetArgumentAtIndex(0), new_matches.GetStringAtIndex(0)) == 0) { if (request.GetParsedLine().GetArgumentCount() == 1) { request.SetWordComplete(true); } else { look_for_subcommand = true; num_command_matches = 0; new_matches.DeleteStringAtIndex(0); new_descriptions.DeleteStringAtIndex(0); request.GetParsedLine().AppendArgument(llvm::StringRef()); request.SetCursorIndex(request.GetCursorIndex() + 1); request.SetCursorCharPosition(0); } } request.AddCompletions(new_matches, new_descriptions); num_command_matches = request.GetNumberOfMatches(); } if (request.GetCursorIndex() > 0 || look_for_subcommand) { // We are completing further on into a commands arguments, so find the // command and tell it to complete the command. First see if there is a // matching initial command: CommandObject *command_object = GetCommandObject(request.GetParsedLine().GetArgumentAtIndex(0)); if (command_object == nullptr) { return 0; } else { request.GetParsedLine().Shift(); request.SetCursorIndex(request.GetCursorIndex() - 1); num_command_matches = command_object->HandleCompletion(request); } } return num_command_matches; } int CommandInterpreter::HandleCompletion(const char *current_line, const char *cursor, const char *last_char, StringList &matches, StringList &descriptions) { llvm::StringRef command_line(current_line, last_char - current_line); CompletionResult result; CompletionRequest request(command_line, cursor - current_line, result); // Don't complete comments, and if the line we are completing is just the // history repeat character, substitute the appropriate history line. const char *first_arg = request.GetParsedLine().GetArgumentAtIndex(0); if (first_arg) { if (first_arg[0] == m_comment_char) return 0; else if (first_arg[0] == CommandHistory::g_repeat_char) { if (auto hist_str = m_command_history.FindString(first_arg)) { matches.InsertStringAtIndex(0, *hist_str); descriptions.InsertStringAtIndex(0, "Previous command history event"); return -2; } else return 0; } } int num_command_matches = HandleCompletionMatches(request); result.GetMatches(matches); result.GetDescriptions(descriptions); if (num_command_matches <= 0) return num_command_matches; if (request.GetParsedLine().GetArgumentCount() == 0) { // If we got an empty string, insert nothing. matches.InsertStringAtIndex(0, ""); descriptions.InsertStringAtIndex(0, ""); } else { // Now figure out if there is a common substring, and if so put that in // element 0, otherwise put an empty string in element 0. std::string command_partial_str = request.GetCursorArgumentPrefix().str(); std::string common_prefix = matches.LongestCommonPrefix(); const size_t partial_name_len = command_partial_str.size(); common_prefix.erase(0, partial_name_len); // If we matched a unique single command, add a space... Only do this if // the completer told us this was a complete word, however... if (num_command_matches == 1 && request.GetWordComplete()) { char quote_char = request.GetParsedLine()[request.GetCursorIndex()].quote; common_prefix = Args::EscapeLLDBCommandArgument(common_prefix, quote_char); if (quote_char != '\0') common_prefix.push_back(quote_char); common_prefix.push_back(' '); } matches.InsertStringAtIndex(0, common_prefix.c_str()); descriptions.InsertStringAtIndex(0, ""); } return num_command_matches; } CommandInterpreter::~CommandInterpreter() {} void CommandInterpreter::UpdatePrompt(llvm::StringRef new_prompt) { EventSP prompt_change_event_sp( new Event(eBroadcastBitResetPrompt, new EventDataBytes(new_prompt))); ; BroadcastEvent(prompt_change_event_sp); if (m_command_io_handler_sp) m_command_io_handler_sp->SetPrompt(new_prompt); } bool CommandInterpreter::Confirm(llvm::StringRef message, bool default_answer) { // Check AutoConfirm first: if (m_debugger.GetAutoConfirm()) return default_answer; IOHandlerConfirm *confirm = new IOHandlerConfirm(m_debugger, message, default_answer); IOHandlerSP io_handler_sp(confirm); m_debugger.RunIOHandler(io_handler_sp); return confirm->GetResponse(); } const CommandAlias * CommandInterpreter::GetAlias(llvm::StringRef alias_name) const { OptionArgVectorSP ret_val; auto pos = m_alias_dict.find(alias_name); if (pos != m_alias_dict.end()) return (CommandAlias *)pos->second.get(); return nullptr; } bool CommandInterpreter::HasCommands() const { return (!m_command_dict.empty()); } bool CommandInterpreter::HasAliases() const { return (!m_alias_dict.empty()); } bool CommandInterpreter::HasUserCommands() const { return (!m_user_dict.empty()); } bool CommandInterpreter::HasAliasOptions() const { return HasAliases(); } void CommandInterpreter::BuildAliasCommandArgs(CommandObject *alias_cmd_obj, const char *alias_name, Args &cmd_args, std::string &raw_input_string, CommandReturnObject &result) { OptionArgVectorSP option_arg_vector_sp = GetAlias(alias_name)->GetOptionArguments(); bool wants_raw_input = alias_cmd_obj->WantsRawCommandString(); // Make sure that the alias name is the 0th element in cmd_args std::string alias_name_str = alias_name; if (alias_name_str != cmd_args.GetArgumentAtIndex(0)) cmd_args.Unshift(alias_name_str); Args new_args(alias_cmd_obj->GetCommandName()); if (new_args.GetArgumentCount() == 2) new_args.Shift(); if (option_arg_vector_sp.get()) { if (wants_raw_input) { // We have a command that both has command options and takes raw input. // Make *sure* it has a " -- " in the right place in the // raw_input_string. size_t pos = raw_input_string.find(" -- "); if (pos == std::string::npos) { // None found; assume it goes at the beginning of the raw input string raw_input_string.insert(0, " -- "); } } OptionArgVector *option_arg_vector = option_arg_vector_sp.get(); const size_t old_size = cmd_args.GetArgumentCount(); std::vector used(old_size + 1, false); used[0] = true; int value_type; std::string option; std::string value; for (const auto &option_entry : *option_arg_vector) { std::tie(option, value_type, value) = option_entry; if (option == "") { if (!wants_raw_input || (value != "--")) { // Since we inserted this above, make sure we don't insert it twice new_args.AppendArgument(value); } continue; } if (value_type != OptionParser::eOptionalArgument) new_args.AppendArgument(option); if (value == "") continue; int index = GetOptionArgumentPosition(value.c_str()); if (index == 0) { // value was NOT a positional argument; must be a real value if (value_type != OptionParser::eOptionalArgument) new_args.AppendArgument(value); else { char buffer[255]; ::snprintf(buffer, sizeof(buffer), "%s%s", option.c_str(), value.c_str()); new_args.AppendArgument(llvm::StringRef(buffer)); } } else if (static_cast(index) >= cmd_args.GetArgumentCount()) { result.AppendErrorWithFormat("Not enough arguments provided; you " "need at least %d arguments to use " "this alias.\n", index); result.SetStatus(eReturnStatusFailed); return; } else { // Find and remove cmd_args.GetArgumentAtIndex(i) from raw_input_string size_t strpos = raw_input_string.find(cmd_args.GetArgumentAtIndex(index)); if (strpos != std::string::npos) { raw_input_string = raw_input_string.erase( strpos, strlen(cmd_args.GetArgumentAtIndex(index))); } if (value_type != OptionParser::eOptionalArgument) new_args.AppendArgument(cmd_args.GetArgumentAtIndex(index)); else { char buffer[255]; ::snprintf(buffer, sizeof(buffer), "%s%s", option.c_str(), cmd_args.GetArgumentAtIndex(index)); new_args.AppendArgument(buffer); } used[index] = true; } } for (auto entry : llvm::enumerate(cmd_args.entries())) { if (!used[entry.index()] && !wants_raw_input) new_args.AppendArgument(entry.value().ref); } cmd_args.Clear(); cmd_args.SetArguments(new_args.GetArgumentCount(), new_args.GetConstArgumentVector()); } else { result.SetStatus(eReturnStatusSuccessFinishNoResult); // This alias was not created with any options; nothing further needs to be // done, unless it is a command that wants raw input, in which case we need // to clear the rest of the data from cmd_args, since its in the raw input // string. if (wants_raw_input) { cmd_args.Clear(); cmd_args.SetArguments(new_args.GetArgumentCount(), new_args.GetConstArgumentVector()); } return; } result.SetStatus(eReturnStatusSuccessFinishNoResult); return; } int CommandInterpreter::GetOptionArgumentPosition(const char *in_string) { int position = 0; // Any string that isn't an argument position, i.e. '%' // followed by an integer, gets a position // of zero. const char *cptr = in_string; // Does it start with '%' if (cptr[0] == '%') { ++cptr; // Is the rest of it entirely digits? if (isdigit(cptr[0])) { const char *start = cptr; while (isdigit(cptr[0])) ++cptr; // We've gotten to the end of the digits; are we at the end of the // string? if (cptr[0] == '\0') position = atoi(start); } } return position; } static void GetHomeInitFile(llvm::SmallVectorImpl &init_file, llvm::StringRef suffix = {}) { std::string init_file_name = ".lldbinit"; if (!suffix.empty()) { init_file_name.append("-"); init_file_name.append(suffix.str()); } llvm::sys::path::home_directory(init_file); llvm::sys::path::append(init_file, init_file_name); FileSystem::Instance().Resolve(init_file); } static void GetCwdInitFile(llvm::SmallVectorImpl &init_file) { llvm::StringRef s = ".lldbinit"; init_file.assign(s.begin(), s.end()); FileSystem::Instance().Resolve(init_file); } static LoadCWDlldbinitFile ShouldLoadCwdInitFile() { lldb::TargetPropertiesSP properties = Target::GetGlobalProperties(); if (!properties) return eLoadCWDlldbinitFalse; return properties->GetLoadCWDlldbinitFile(); } void CommandInterpreter::SourceInitFile(FileSpec file, CommandReturnObject &result) { assert(!m_skip_lldbinit_files); if (!FileSystem::Instance().Exists(file)) { result.SetStatus(eReturnStatusSuccessFinishNoResult); return; } // Use HandleCommand to 'source' the given file; this will do the actual // broadcasting of the commands back to any appropriate listener (see // CommandObjectSource::Execute for more details). const bool saved_batch = SetBatchCommandMode(true); ExecutionContext *ctx = nullptr; CommandInterpreterRunOptions options; options.SetSilent(true); options.SetPrintErrors(true); options.SetStopOnError(false); options.SetStopOnContinue(true); HandleCommandsFromFile(file, ctx, options, result); SetBatchCommandMode(saved_batch); } void CommandInterpreter::SourceInitFileCwd(CommandReturnObject &result) { if (m_skip_lldbinit_files) { result.SetStatus(eReturnStatusSuccessFinishNoResult); return; } llvm::SmallString<128> init_file; GetCwdInitFile(init_file); if (!FileSystem::Instance().Exists(init_file)) { result.SetStatus(eReturnStatusSuccessFinishNoResult); return; } LoadCWDlldbinitFile should_load = ShouldLoadCwdInitFile(); switch (should_load) { case eLoadCWDlldbinitFalse: result.SetStatus(eReturnStatusSuccessFinishNoResult); break; case eLoadCWDlldbinitTrue: SourceInitFile(FileSpec(init_file.str()), result); break; case eLoadCWDlldbinitWarn: { llvm::SmallString<128> home_init_file; GetHomeInitFile(home_init_file); if (llvm::sys::path::parent_path(init_file) == llvm::sys::path::parent_path(home_init_file)) { result.SetStatus(eReturnStatusSuccessFinishNoResult); } else { result.AppendErrorWithFormat(InitFileWarning); result.SetStatus(eReturnStatusFailed); } } } } /// We will first see if there is an application specific ".lldbinit" file /// whose name is "~/.lldbinit" followed by a "-" and the name of the program. /// If this file doesn't exist, we fall back to just the "~/.lldbinit" file. void CommandInterpreter::SourceInitFileHome(CommandReturnObject &result) { if (m_skip_lldbinit_files) { result.SetStatus(eReturnStatusSuccessFinishNoResult); return; } llvm::SmallString<128> init_file; GetHomeInitFile(init_file); if (!m_skip_app_init_files) { llvm::StringRef program_name = HostInfo::GetProgramFileSpec().GetFilename().GetStringRef(); llvm::SmallString<128> program_init_file; GetHomeInitFile(program_init_file, program_name); if (FileSystem::Instance().Exists(program_init_file)) init_file = program_init_file; } SourceInitFile(FileSpec(init_file.str()), result); } const char *CommandInterpreter::GetCommandPrefix() { const char *prefix = GetDebugger().GetIOHandlerCommandPrefix(); return prefix == nullptr ? "" : prefix; } PlatformSP CommandInterpreter::GetPlatform(bool prefer_target_platform) { PlatformSP platform_sp; if (prefer_target_platform) { ExecutionContext exe_ctx(GetExecutionContext()); Target *target = exe_ctx.GetTargetPtr(); if (target) platform_sp = target->GetPlatform(); } if (!platform_sp) platform_sp = m_debugger.GetPlatformList().GetSelectedPlatform(); return platform_sp; } void CommandInterpreter::HandleCommands(const StringList &commands, ExecutionContext *override_context, CommandInterpreterRunOptions &options, CommandReturnObject &result) { size_t num_lines = commands.GetSize(); // If we are going to continue past a "continue" then we need to run the // commands synchronously. Make sure you reset this value anywhere you return // from the function. bool old_async_execution = m_debugger.GetAsyncExecution(); // If we've been given an execution context, set it at the start, but don't // keep resetting it or we will cause series of commands that change the // context, then do an operation that relies on that context to fail. if (override_context != nullptr) UpdateExecutionContext(override_context); if (!options.GetStopOnContinue()) { m_debugger.SetAsyncExecution(false); } for (size_t idx = 0; idx < num_lines && !WasInterrupted(); idx++) { const char *cmd = commands.GetStringAtIndex(idx); if (cmd[0] == '\0') continue; if (options.GetEchoCommands()) { // TODO: Add Stream support. result.AppendMessageWithFormat("%s %s\n", m_debugger.GetPrompt().str().c_str(), cmd); } CommandReturnObject tmp_result; // If override_context is not NULL, pass no_context_switching = true for // HandleCommand() since we updated our context already. // We might call into a regex or alias command, in which case the // add_to_history will get lost. This m_command_source_depth dingus is the // way we turn off adding to the history in that case, so set it up here. if (!options.GetAddToHistory()) m_command_source_depth++; bool success = HandleCommand(cmd, options.m_add_to_history, tmp_result, nullptr, /* override_context */ true, /* repeat_on_empty_command */ override_context != nullptr /* no_context_switching */); if (!options.GetAddToHistory()) m_command_source_depth--; if (options.GetPrintResults()) { if (tmp_result.Succeeded()) result.AppendMessage(tmp_result.GetOutputData()); } if (!success || !tmp_result.Succeeded()) { llvm::StringRef error_msg = tmp_result.GetErrorData(); if (error_msg.empty()) error_msg = ".\n"; if (options.GetStopOnError()) { result.AppendErrorWithFormat( "Aborting reading of commands after command #%" PRIu64 ": '%s' failed with %s", (uint64_t)idx, cmd, error_msg.str().c_str()); result.SetStatus(eReturnStatusFailed); m_debugger.SetAsyncExecution(old_async_execution); return; } else if (options.GetPrintResults()) { result.AppendMessageWithFormat( "Command #%" PRIu64 " '%s' failed with %s", (uint64_t)idx + 1, cmd, error_msg.str().c_str()); } } if (result.GetImmediateOutputStream()) result.GetImmediateOutputStream()->Flush(); if (result.GetImmediateErrorStream()) result.GetImmediateErrorStream()->Flush(); // N.B. Can't depend on DidChangeProcessState, because the state coming // into the command execution could be running (for instance in Breakpoint // Commands. So we check the return value to see if it is has running in // it. if ((tmp_result.GetStatus() == eReturnStatusSuccessContinuingNoResult) || (tmp_result.GetStatus() == eReturnStatusSuccessContinuingResult)) { if (options.GetStopOnContinue()) { // If we caused the target to proceed, and we're going to stop in that // case, set the status in our real result before returning. This is // an error if the continue was not the last command in the set of // commands to be run. if (idx != num_lines - 1) result.AppendErrorWithFormat( "Aborting reading of commands after command #%" PRIu64 ": '%s' continued the target.\n", (uint64_t)idx + 1, cmd); else result.AppendMessageWithFormat("Command #%" PRIu64 " '%s' continued the target.\n", (uint64_t)idx + 1, cmd); result.SetStatus(tmp_result.GetStatus()); m_debugger.SetAsyncExecution(old_async_execution); return; } } // Also check for "stop on crash here: bool should_stop = false; if (tmp_result.GetDidChangeProcessState() && options.GetStopOnCrash()) { TargetSP target_sp(m_debugger.GetTargetList().GetSelectedTarget()); if (target_sp) { ProcessSP process_sp(target_sp->GetProcessSP()); if (process_sp) { for (ThreadSP thread_sp : process_sp->GetThreadList().Threads()) { StopReason reason = thread_sp->GetStopReason(); if (reason == eStopReasonSignal || reason == eStopReasonException || reason == eStopReasonInstrumentation) { should_stop = true; break; } } } } if (should_stop) { if (idx != num_lines - 1) result.AppendErrorWithFormat( "Aborting reading of commands after command #%" PRIu64 ": '%s' stopped with a signal or exception.\n", (uint64_t)idx + 1, cmd); else result.AppendMessageWithFormat( "Command #%" PRIu64 " '%s' stopped with a signal or exception.\n", (uint64_t)idx + 1, cmd); result.SetStatus(tmp_result.GetStatus()); m_debugger.SetAsyncExecution(old_async_execution); return; } } } result.SetStatus(eReturnStatusSuccessFinishResult); m_debugger.SetAsyncExecution(old_async_execution); return; } // Make flags that we can pass into the IOHandler so our delegates can do the // right thing enum { eHandleCommandFlagStopOnContinue = (1u << 0), eHandleCommandFlagStopOnError = (1u << 1), eHandleCommandFlagEchoCommand = (1u << 2), eHandleCommandFlagEchoCommentCommand = (1u << 3), eHandleCommandFlagPrintResult = (1u << 4), eHandleCommandFlagPrintErrors = (1u << 5), eHandleCommandFlagStopOnCrash = (1u << 6) }; void CommandInterpreter::HandleCommandsFromFile( FileSpec &cmd_file, ExecutionContext *context, CommandInterpreterRunOptions &options, CommandReturnObject &result) { if (!FileSystem::Instance().Exists(cmd_file)) { result.AppendErrorWithFormat( "Error reading commands from file %s - file not found.\n", cmd_file.GetFilename().AsCString("")); result.SetStatus(eReturnStatusFailed); return; } StreamFileSP input_file_sp(new StreamFile()); std::string cmd_file_path = cmd_file.GetPath(); Status error = FileSystem::Instance().Open(input_file_sp->GetFile(), cmd_file, File::eOpenOptionRead); if (error.Fail()) { result.AppendErrorWithFormat( "error: an error occurred read file '%s': %s\n", cmd_file_path.c_str(), error.AsCString()); result.SetStatus(eReturnStatusFailed); return; } Debugger &debugger = GetDebugger(); uint32_t flags = 0; if (options.m_stop_on_continue == eLazyBoolCalculate) { if (m_command_source_flags.empty()) { // Stop on continue by default flags |= eHandleCommandFlagStopOnContinue; } else if (m_command_source_flags.back() & eHandleCommandFlagStopOnContinue) { flags |= eHandleCommandFlagStopOnContinue; } } else if (options.m_stop_on_continue == eLazyBoolYes) { flags |= eHandleCommandFlagStopOnContinue; } if (options.m_stop_on_error == eLazyBoolCalculate) { if (m_command_source_flags.empty()) { if (GetStopCmdSourceOnError()) flags |= eHandleCommandFlagStopOnError; } else if (m_command_source_flags.back() & eHandleCommandFlagStopOnError) { flags |= eHandleCommandFlagStopOnError; } } else if (options.m_stop_on_error == eLazyBoolYes) { flags |= eHandleCommandFlagStopOnError; } // stop-on-crash can only be set, if it is present in all levels of // pushed flag sets. if (options.GetStopOnCrash()) { if (m_command_source_flags.empty()) { flags |= eHandleCommandFlagStopOnCrash; } else if (m_command_source_flags.back() & eHandleCommandFlagStopOnCrash) { flags |= eHandleCommandFlagStopOnCrash; } } if (options.m_echo_commands == eLazyBoolCalculate) { if (m_command_source_flags.empty()) { // Echo command by default flags |= eHandleCommandFlagEchoCommand; } else if (m_command_source_flags.back() & eHandleCommandFlagEchoCommand) { flags |= eHandleCommandFlagEchoCommand; } } else if (options.m_echo_commands == eLazyBoolYes) { flags |= eHandleCommandFlagEchoCommand; } // We will only ever ask for this flag, if we echo commands in general. if (options.m_echo_comment_commands == eLazyBoolCalculate) { if (m_command_source_flags.empty()) { // Echo comments by default flags |= eHandleCommandFlagEchoCommentCommand; } else if (m_command_source_flags.back() & eHandleCommandFlagEchoCommentCommand) { flags |= eHandleCommandFlagEchoCommentCommand; } } else if (options.m_echo_comment_commands == eLazyBoolYes) { flags |= eHandleCommandFlagEchoCommentCommand; } if (options.m_print_results == eLazyBoolCalculate) { if (m_command_source_flags.empty()) { // Print output by default flags |= eHandleCommandFlagPrintResult; } else if (m_command_source_flags.back() & eHandleCommandFlagPrintResult) { flags |= eHandleCommandFlagPrintResult; } } else if (options.m_print_results == eLazyBoolYes) { flags |= eHandleCommandFlagPrintResult; } if (options.m_print_errors == eLazyBoolCalculate) { if (m_command_source_flags.empty()) { // Print output by default flags |= eHandleCommandFlagPrintErrors; } else if (m_command_source_flags.back() & eHandleCommandFlagPrintErrors) { flags |= eHandleCommandFlagPrintErrors; } } else if (options.m_print_errors == eLazyBoolYes) { flags |= eHandleCommandFlagPrintErrors; } if (flags & eHandleCommandFlagPrintResult) { debugger.GetOutputFile()->Printf("Executing commands in '%s'.\n", cmd_file_path.c_str()); } // Used for inheriting the right settings when "command source" might // have nested "command source" commands lldb::StreamFileSP empty_stream_sp; m_command_source_flags.push_back(flags); IOHandlerSP io_handler_sp(new IOHandlerEditline( debugger, IOHandler::Type::CommandInterpreter, input_file_sp, empty_stream_sp, // Pass in an empty stream so we inherit the top // input reader output stream empty_stream_sp, // Pass in an empty stream so we inherit the top // input reader error stream flags, nullptr, // Pass in NULL for "editline_name" so no history is saved, // or written debugger.GetPrompt(), llvm::StringRef(), false, // Not multi-line debugger.GetUseColor(), 0, *this, nullptr)); const bool old_async_execution = debugger.GetAsyncExecution(); // Set synchronous execution if we are not stopping on continue if ((flags & eHandleCommandFlagStopOnContinue) == 0) debugger.SetAsyncExecution(false); m_command_source_depth++; debugger.RunIOHandler(io_handler_sp); if (!m_command_source_flags.empty()) m_command_source_flags.pop_back(); m_command_source_depth--; result.SetStatus(eReturnStatusSuccessFinishNoResult); debugger.SetAsyncExecution(old_async_execution); } bool CommandInterpreter::GetSynchronous() { return m_synchronous_execution; } void CommandInterpreter::SetSynchronous(bool value) { m_synchronous_execution = value; } void CommandInterpreter::OutputFormattedHelpText(Stream &strm, llvm::StringRef prefix, llvm::StringRef help_text) { const uint32_t max_columns = m_debugger.GetTerminalWidth(); size_t line_width_max = max_columns - prefix.size(); if (line_width_max < 16) line_width_max = help_text.size() + prefix.size(); strm.IndentMore(prefix.size()); bool prefixed_yet = false; while (!help_text.empty()) { // Prefix the first line, indent subsequent lines to line up if (!prefixed_yet) { strm << prefix; prefixed_yet = true; } else strm.Indent(); // Never print more than the maximum on one line. llvm::StringRef this_line = help_text.substr(0, line_width_max); // Always break on an explicit newline. std::size_t first_newline = this_line.find_first_of("\n"); // Don't break on space/tab unless the text is too long to fit on one line. std::size_t last_space = llvm::StringRef::npos; if (this_line.size() != help_text.size()) last_space = this_line.find_last_of(" \t"); // Break at whichever condition triggered first. this_line = this_line.substr(0, std::min(first_newline, last_space)); strm.PutCString(this_line); strm.EOL(); // Remove whitespace / newlines after breaking. help_text = help_text.drop_front(this_line.size()).ltrim(); } strm.IndentLess(prefix.size()); } void CommandInterpreter::OutputFormattedHelpText(Stream &strm, llvm::StringRef word_text, llvm::StringRef separator, llvm::StringRef help_text, size_t max_word_len) { StreamString prefix_stream; prefix_stream.Printf(" %-*s %*s ", (int)max_word_len, word_text.data(), (int)separator.size(), separator.data()); OutputFormattedHelpText(strm, prefix_stream.GetString(), help_text); } void CommandInterpreter::OutputHelpText(Stream &strm, llvm::StringRef word_text, llvm::StringRef separator, llvm::StringRef help_text, uint32_t max_word_len) { int indent_size = max_word_len + separator.size() + 2; strm.IndentMore(indent_size); StreamString text_strm; text_strm.Printf("%-*s ", (int)max_word_len, word_text.data()); text_strm << separator << " " << help_text; const uint32_t max_columns = m_debugger.GetTerminalWidth(); llvm::StringRef text = text_strm.GetString(); uint32_t chars_left = max_columns; auto nextWordLength = [](llvm::StringRef S) { size_t pos = S.find(' '); return pos == llvm::StringRef::npos ? S.size() : pos; }; while (!text.empty()) { if (text.front() == '\n' || (text.front() == ' ' && nextWordLength(text.ltrim(' ')) > chars_left)) { strm.EOL(); strm.Indent(); chars_left = max_columns - indent_size; if (text.front() == '\n') text = text.drop_front(); else text = text.ltrim(' '); } else { strm.PutChar(text.front()); --chars_left; text = text.drop_front(); } } strm.EOL(); strm.IndentLess(indent_size); } void CommandInterpreter::FindCommandsForApropos( llvm::StringRef search_word, StringList &commands_found, StringList &commands_help, CommandObject::CommandMap &command_map) { CommandObject::CommandMap::const_iterator pos; for (pos = command_map.begin(); pos != command_map.end(); ++pos) { llvm::StringRef command_name = pos->first; CommandObject *cmd_obj = pos->second.get(); const bool search_short_help = true; const bool search_long_help = false; const bool search_syntax = false; const bool search_options = false; if (command_name.contains_lower(search_word) || cmd_obj->HelpTextContainsWord(search_word, search_short_help, search_long_help, search_syntax, search_options)) { commands_found.AppendString(cmd_obj->GetCommandName()); commands_help.AppendString(cmd_obj->GetHelp()); } if (cmd_obj->IsMultiwordObject()) { CommandObjectMultiword *cmd_multiword = cmd_obj->GetAsMultiwordCommand(); FindCommandsForApropos(search_word, commands_found, commands_help, cmd_multiword->GetSubcommandDictionary()); } } } void CommandInterpreter::FindCommandsForApropos(llvm::StringRef search_word, StringList &commands_found, StringList &commands_help, bool search_builtin_commands, bool search_user_commands, bool search_alias_commands) { CommandObject::CommandMap::const_iterator pos; if (search_builtin_commands) FindCommandsForApropos(search_word, commands_found, commands_help, m_command_dict); if (search_user_commands) FindCommandsForApropos(search_word, commands_found, commands_help, m_user_dict); if (search_alias_commands) FindCommandsForApropos(search_word, commands_found, commands_help, m_alias_dict); } void CommandInterpreter::UpdateExecutionContext( ExecutionContext *override_context) { if (override_context != nullptr) { m_exe_ctx_ref = *override_context; } else { const bool adopt_selected = true; m_exe_ctx_ref.SetTargetPtr(m_debugger.GetSelectedTarget().get(), adopt_selected); } } -size_t CommandInterpreter::GetProcessOutput() { - // 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; - Status error; +void CommandInterpreter::GetProcessOutput() { TargetSP target_sp(m_debugger.GetTargetList().GetSelectedTarget()); - if (target_sp) { - ProcessSP process_sp(target_sp->GetProcessSP()); - if (process_sp) { - while ((len = process_sp->GetSTDOUT(stdio_buffer, sizeof(stdio_buffer), - error)) > 0) { - size_t bytes_written = len; - m_debugger.GetOutputFile()->Write(stdio_buffer, bytes_written); - total_bytes += len; - } - while ((len = process_sp->GetSTDERR(stdio_buffer, sizeof(stdio_buffer), - error)) > 0) { - size_t bytes_written = len; - m_debugger.GetErrorFile()->Write(stdio_buffer, bytes_written); - total_bytes += len; - } - } - } - return total_bytes; + if (!target_sp) + return; + + if (ProcessSP process_sp = target_sp->GetProcessSP()) + m_debugger.FlushProcessOutput(*process_sp, /*flush_stdout*/ true, + /*flush_stderr*/ true); } void CommandInterpreter::StartHandlingCommand() { auto idle_state = CommandHandlingState::eIdle; if (m_command_state.compare_exchange_strong( idle_state, CommandHandlingState::eInProgress)) lldbassert(m_iohandler_nesting_level == 0); else lldbassert(m_iohandler_nesting_level > 0); ++m_iohandler_nesting_level; } void CommandInterpreter::FinishHandlingCommand() { lldbassert(m_iohandler_nesting_level > 0); if (--m_iohandler_nesting_level == 0) { auto prev_state = m_command_state.exchange(CommandHandlingState::eIdle); lldbassert(prev_state != CommandHandlingState::eIdle); } } bool CommandInterpreter::InterruptCommand() { auto in_progress = CommandHandlingState::eInProgress; return m_command_state.compare_exchange_strong( in_progress, CommandHandlingState::eInterrupted); } bool CommandInterpreter::WasInterrupted() const { bool was_interrupted = (m_command_state == CommandHandlingState::eInterrupted); lldbassert(!was_interrupted || m_iohandler_nesting_level > 0); return was_interrupted; } void CommandInterpreter::PrintCommandOutput(Stream &stream, llvm::StringRef str) { // Split the output into lines and poll for interrupt requests const char *data = str.data(); size_t size = str.size(); while (size > 0 && !WasInterrupted()) { size_t chunk_size = 0; for (; chunk_size < size; ++chunk_size) { lldbassert(data[chunk_size] != '\0'); if (data[chunk_size] == '\n') { ++chunk_size; break; } } chunk_size = stream.Write(data, chunk_size); lldbassert(size >= chunk_size); data += chunk_size; size -= chunk_size; } if (size > 0) { stream.Printf("\n... Interrupted.\n"); } } bool CommandInterpreter::EchoCommandNonInteractive( llvm::StringRef line, const Flags &io_handler_flags) const { if (!io_handler_flags.Test(eHandleCommandFlagEchoCommand)) return false; llvm::StringRef command = line.trim(); if (command.empty()) return true; if (command.front() == m_comment_char) return io_handler_flags.Test(eHandleCommandFlagEchoCommentCommand); return true; } void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler, std::string &line) { // If we were interrupted, bail out... if (WasInterrupted()) return; const bool is_interactive = io_handler.GetIsInteractive(); if (!is_interactive) { // When we are not interactive, don't execute blank lines. This will happen // sourcing a commands file. We don't want blank lines to repeat the // previous command and cause any errors to occur (like redefining an // alias, get an error and stop parsing the commands file). if (line.empty()) return; // When using a non-interactive file handle (like when sourcing commands // from a file) we need to echo the command out so we don't just see the // command output and no command... if (EchoCommandNonInteractive(line, io_handler.GetFlags())) io_handler.GetOutputStreamFile()->Printf("%s%s\n", io_handler.GetPrompt(), line.c_str()); } StartHandlingCommand(); lldb_private::CommandReturnObject result; HandleCommand(line.c_str(), eLazyBoolCalculate, result); // Now emit the command output text from the command we just executed if ((result.Succeeded() && io_handler.GetFlags().Test(eHandleCommandFlagPrintResult)) || io_handler.GetFlags().Test(eHandleCommandFlagPrintErrors)) { // Display any STDOUT/STDERR _prior_ to emitting the command result text GetProcessOutput(); if (!result.GetImmediateOutputStream()) { llvm::StringRef output = result.GetOutputData(); PrintCommandOutput(*io_handler.GetOutputStreamFile(), output); } // Now emit the command error text from the command we just executed if (!result.GetImmediateErrorStream()) { llvm::StringRef error = result.GetErrorData(); PrintCommandOutput(*io_handler.GetErrorStreamFile(), error); } } FinishHandlingCommand(); switch (result.GetStatus()) { case eReturnStatusInvalid: case eReturnStatusSuccessFinishNoResult: case eReturnStatusSuccessFinishResult: case eReturnStatusStarted: break; case eReturnStatusSuccessContinuingNoResult: case eReturnStatusSuccessContinuingResult: if (io_handler.GetFlags().Test(eHandleCommandFlagStopOnContinue)) io_handler.SetIsDone(true); break; case eReturnStatusFailed: m_num_errors++; if (io_handler.GetFlags().Test(eHandleCommandFlagStopOnError)) io_handler.SetIsDone(true); break; case eReturnStatusQuit: m_quit_requested = true; io_handler.SetIsDone(true); break; } // Finally, if we're going to stop on crash, check that here: if (!m_quit_requested && result.GetDidChangeProcessState() && io_handler.GetFlags().Test(eHandleCommandFlagStopOnCrash)) { bool should_stop = false; TargetSP target_sp(m_debugger.GetTargetList().GetSelectedTarget()); if (target_sp) { ProcessSP process_sp(target_sp->GetProcessSP()); if (process_sp) { for (ThreadSP thread_sp : process_sp->GetThreadList().Threads()) { StopReason reason = thread_sp->GetStopReason(); if ((reason == eStopReasonSignal || reason == eStopReasonException || reason == eStopReasonInstrumentation) && !result.GetAbnormalStopWasExpected()) { should_stop = true; break; } } } } if (should_stop) { io_handler.SetIsDone(true); m_stopped_for_crash = true; } } } bool CommandInterpreter::IOHandlerInterrupt(IOHandler &io_handler) { ExecutionContext exe_ctx(GetExecutionContext()); Process *process = exe_ctx.GetProcessPtr(); if (InterruptCommand()) return true; if (process) { StateType state = process->GetState(); if (StateIsRunningState(state)) { process->Halt(); return true; // Don't do any updating when we are running } } ScriptInterpreter *script_interpreter = m_debugger.GetScriptInterpreter(false); if (script_interpreter) { if (script_interpreter->Interrupt()) return true; } return false; } void CommandInterpreter::GetLLDBCommandsFromIOHandler( const char *prompt, IOHandlerDelegate &delegate, bool asynchronously, void *baton) { Debugger &debugger = GetDebugger(); IOHandlerSP io_handler_sp( new IOHandlerEditline(debugger, IOHandler::Type::CommandList, "lldb", // Name of input reader for history llvm::StringRef::withNullAsEmpty(prompt), // Prompt llvm::StringRef(), // Continuation prompt true, // Get multiple lines debugger.GetUseColor(), 0, // Don't show line numbers delegate, // IOHandlerDelegate nullptr)); // FileShadowCollector if (io_handler_sp) { io_handler_sp->SetUserData(baton); if (asynchronously) debugger.PushIOHandler(io_handler_sp); else debugger.RunIOHandler(io_handler_sp); } } void CommandInterpreter::GetPythonCommandsFromIOHandler( const char *prompt, IOHandlerDelegate &delegate, bool asynchronously, void *baton) { Debugger &debugger = GetDebugger(); IOHandlerSP io_handler_sp( new IOHandlerEditline(debugger, IOHandler::Type::PythonCode, "lldb-python", // Name of input reader for history llvm::StringRef::withNullAsEmpty(prompt), // Prompt llvm::StringRef(), // Continuation prompt true, // Get multiple lines debugger.GetUseColor(), 0, // Don't show line numbers delegate, // IOHandlerDelegate nullptr)); // FileShadowCollector if (io_handler_sp) { io_handler_sp->SetUserData(baton); if (asynchronously) debugger.PushIOHandler(io_handler_sp); else debugger.RunIOHandler(io_handler_sp); } } bool CommandInterpreter::IsActive() { return m_debugger.IsTopIOHandler(m_command_io_handler_sp); } lldb::IOHandlerSP CommandInterpreter::GetIOHandler(bool force_create, CommandInterpreterRunOptions *options) { // Always re-create the IOHandlerEditline in case the input changed. The old // instance might have had a non-interactive input and now it does or vice // versa. if (force_create || !m_command_io_handler_sp) { // Always re-create the IOHandlerEditline in case the input changed. The // old instance might have had a non-interactive input and now it does or // vice versa. uint32_t flags = 0; if (options) { if (options->m_stop_on_continue == eLazyBoolYes) flags |= eHandleCommandFlagStopOnContinue; if (options->m_stop_on_error == eLazyBoolYes) flags |= eHandleCommandFlagStopOnError; if (options->m_stop_on_crash == eLazyBoolYes) flags |= eHandleCommandFlagStopOnCrash; if (options->m_echo_commands != eLazyBoolNo) flags |= eHandleCommandFlagEchoCommand; if (options->m_echo_comment_commands != eLazyBoolNo) flags |= eHandleCommandFlagEchoCommentCommand; if (options->m_print_results != eLazyBoolNo) flags |= eHandleCommandFlagPrintResult; if (options->m_print_errors != eLazyBoolNo) flags |= eHandleCommandFlagPrintErrors; } else { flags = eHandleCommandFlagEchoCommand | eHandleCommandFlagPrintResult | eHandleCommandFlagPrintErrors; } m_command_io_handler_sp = std::make_shared( m_debugger, IOHandler::Type::CommandInterpreter, m_debugger.GetInputFile(), m_debugger.GetOutputFile(), m_debugger.GetErrorFile(), flags, "lldb", m_debugger.GetPrompt(), llvm::StringRef(), // Continuation prompt false, // Don't enable multiple line input, just single line commands m_debugger.GetUseColor(), 0, // Don't show line numbers *this, // IOHandlerDelegate GetDebugger().GetInputRecorder()); } return m_command_io_handler_sp; } void CommandInterpreter::RunCommandInterpreter( bool auto_handle_events, bool spawn_thread, CommandInterpreterRunOptions &options) { // Always re-create the command interpreter when we run it in case any file // handles have changed. bool force_create = true; m_debugger.PushIOHandler(GetIOHandler(force_create, &options)); m_stopped_for_crash = false; if (auto_handle_events) m_debugger.StartEventHandlerThread(); if (spawn_thread) { m_debugger.StartIOHandlerThread(); } else { m_debugger.ExecuteIOHandlers(); if (auto_handle_events) m_debugger.StopEventHandlerThread(); } } CommandObject * CommandInterpreter::ResolveCommandImpl(std::string &command_line, CommandReturnObject &result) { std::string scratch_command(command_line); // working copy so we don't modify // command_line unless we succeed CommandObject *cmd_obj = nullptr; StreamString revised_command_line; bool wants_raw_input = false; size_t actual_cmd_name_len = 0; std::string next_word; StringList matches; bool done = false; while (!done) { char quote_char = '\0'; std::string suffix; ExtractCommand(scratch_command, next_word, suffix, quote_char); if (cmd_obj == nullptr) { std::string full_name; bool is_alias = GetAliasFullName(next_word, full_name); cmd_obj = GetCommandObject(next_word, &matches); bool is_real_command = (!is_alias) || (cmd_obj != nullptr && !cmd_obj->IsAlias()); if (!is_real_command) { matches.Clear(); std::string alias_result; cmd_obj = BuildAliasResult(full_name, scratch_command, alias_result, result); revised_command_line.Printf("%s", alias_result.c_str()); if (cmd_obj) { wants_raw_input = cmd_obj->WantsRawCommandString(); actual_cmd_name_len = cmd_obj->GetCommandName().size(); } } else { if (cmd_obj) { llvm::StringRef cmd_name = cmd_obj->GetCommandName(); actual_cmd_name_len += cmd_name.size(); revised_command_line.Printf("%s", cmd_name.str().c_str()); wants_raw_input = cmd_obj->WantsRawCommandString(); } else { revised_command_line.Printf("%s", next_word.c_str()); } } } else { if (cmd_obj->IsMultiwordObject()) { CommandObject *sub_cmd_obj = cmd_obj->GetSubcommandObject(next_word.c_str()); if (sub_cmd_obj) { // The subcommand's name includes the parent command's name, so // restart rather than append to the revised_command_line. llvm::StringRef sub_cmd_name = sub_cmd_obj->GetCommandName(); actual_cmd_name_len = sub_cmd_name.size() + 1; revised_command_line.Clear(); revised_command_line.Printf("%s", sub_cmd_name.str().c_str()); cmd_obj = sub_cmd_obj; wants_raw_input = cmd_obj->WantsRawCommandString(); } else { if (quote_char) revised_command_line.Printf(" %c%s%s%c", quote_char, next_word.c_str(), suffix.c_str(), quote_char); else revised_command_line.Printf(" %s%s", next_word.c_str(), suffix.c_str()); done = true; } } else { if (quote_char) revised_command_line.Printf(" %c%s%s%c", quote_char, next_word.c_str(), suffix.c_str(), quote_char); else revised_command_line.Printf(" %s%s", next_word.c_str(), suffix.c_str()); done = true; } } if (cmd_obj == nullptr) { const size_t num_matches = matches.GetSize(); if (matches.GetSize() > 1) { StreamString error_msg; error_msg.Printf("Ambiguous command '%s'. Possible matches:\n", next_word.c_str()); for (uint32_t i = 0; i < num_matches; ++i) { error_msg.Printf("\t%s\n", matches.GetStringAtIndex(i)); } result.AppendRawError(error_msg.GetString()); } else { // We didn't have only one match, otherwise we wouldn't get here. lldbassert(num_matches == 0); result.AppendErrorWithFormat("'%s' is not a valid command.\n", next_word.c_str()); } result.SetStatus(eReturnStatusFailed); return nullptr; } if (cmd_obj->IsMultiwordObject()) { if (!suffix.empty()) { result.AppendErrorWithFormat( "command '%s' did not recognize '%s%s%s' as valid (subcommand " "might be invalid).\n", cmd_obj->GetCommandName().str().c_str(), next_word.empty() ? "" : next_word.c_str(), next_word.empty() ? " -- " : " ", suffix.c_str()); result.SetStatus(eReturnStatusFailed); return nullptr; } } else { // If we found a normal command, we are done done = true; if (!suffix.empty()) { switch (suffix[0]) { case '/': // GDB format suffixes { Options *command_options = cmd_obj->GetOptions(); if (command_options && command_options->SupportsLongOption("gdb-format")) { std::string gdb_format_option("--gdb-format="); gdb_format_option += (suffix.c_str() + 1); std::string cmd = revised_command_line.GetString(); size_t arg_terminator_idx = FindArgumentTerminator(cmd); if (arg_terminator_idx != std::string::npos) { // Insert the gdb format option before the "--" that terminates // options gdb_format_option.append(1, ' '); cmd.insert(arg_terminator_idx, gdb_format_option); revised_command_line.Clear(); revised_command_line.PutCString(cmd); } else revised_command_line.Printf(" %s", gdb_format_option.c_str()); if (wants_raw_input && FindArgumentTerminator(cmd) == std::string::npos) revised_command_line.PutCString(" --"); } else { result.AppendErrorWithFormat( "the '%s' command doesn't support the --gdb-format option\n", cmd_obj->GetCommandName().str().c_str()); result.SetStatus(eReturnStatusFailed); return nullptr; } } break; default: result.AppendErrorWithFormat( "unknown command shorthand suffix: '%s'\n", suffix.c_str()); result.SetStatus(eReturnStatusFailed); return nullptr; } } } if (scratch_command.empty()) done = true; } if (!scratch_command.empty()) revised_command_line.Printf(" %s", scratch_command.c_str()); if (cmd_obj != nullptr) command_line = revised_command_line.GetString(); return cmd_obj; }