diff options
Diffstat (limited to 'lldb/source/Commands')
58 files changed, 31697 insertions, 0 deletions
diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp new file mode 100644 index 0000000000000..469a6bbbadf65 --- /dev/null +++ b/lldb/source/Commands/CommandCompletions.cpp @@ -0,0 +1,516 @@ +//===-- CommandCompletions.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 <sys/stat.h> +#if defined(__APPLE__) || defined(__linux__) +#include <pwd.h> +#endif + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringSet.h" + +#include "lldb/Core/FileSpecList.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/TildeExpressionResolver.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +using namespace lldb_private; + +CommandCompletions::CommonCompletionElement + CommandCompletions::g_common_completions[] = { + {eCustomCompletion, nullptr}, + {eSourceFileCompletion, CommandCompletions::SourceFiles}, + {eDiskFileCompletion, CommandCompletions::DiskFiles}, + {eDiskDirectoryCompletion, CommandCompletions::DiskDirectories}, + {eSymbolCompletion, CommandCompletions::Symbols}, + {eModuleCompletion, CommandCompletions::Modules}, + {eSettingsNameCompletion, CommandCompletions::SettingsNames}, + {ePlatformPluginCompletion, CommandCompletions::PlatformPluginNames}, + {eArchitectureCompletion, CommandCompletions::ArchitectureNames}, + {eVariablePathCompletion, CommandCompletions::VariablePath}, + {eNoCompletion, nullptr} // This one has to be last in the list. +}; + +bool CommandCompletions::InvokeCommonCompletionCallbacks( + CommandInterpreter &interpreter, uint32_t completion_mask, + CompletionRequest &request, SearchFilter *searcher) { + bool handled = false; + + if (completion_mask & eCustomCompletion) + return false; + + for (int i = 0;; i++) { + if (g_common_completions[i].type == eNoCompletion) + break; + else if ((g_common_completions[i].type & completion_mask) == + g_common_completions[i].type && + g_common_completions[i].callback != nullptr) { + handled = true; + g_common_completions[i].callback(interpreter, request, searcher); + } + } + return handled; +} + +void CommandCompletions::SourceFiles(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + // Find some way to switch "include support files..." + SourceFileCompleter completer(interpreter, false, request); + + if (searcher == nullptr) { + lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); + SearchFilterForUnconstrainedSearches null_searcher(target_sp); + completer.DoCompletion(&null_searcher); + } else { + completer.DoCompletion(searcher); + } +} + +static void DiskFilesOrDirectories(const llvm::Twine &partial_name, + bool only_directories, + CompletionRequest &request, + TildeExpressionResolver &Resolver) { + llvm::SmallString<256> CompletionBuffer; + llvm::SmallString<256> Storage; + partial_name.toVector(CompletionBuffer); + + if (CompletionBuffer.size() >= PATH_MAX) + return; + + namespace path = llvm::sys::path; + + llvm::StringRef SearchDir; + llvm::StringRef PartialItem; + + if (CompletionBuffer.startswith("~")) { + llvm::StringRef Buffer(CompletionBuffer); + size_t FirstSep = + Buffer.find_if([](char c) { return path::is_separator(c); }); + + llvm::StringRef Username = Buffer.take_front(FirstSep); + llvm::StringRef Remainder; + if (FirstSep != llvm::StringRef::npos) + Remainder = Buffer.drop_front(FirstSep + 1); + + llvm::SmallString<256> Resolved; + if (!Resolver.ResolveExact(Username, Resolved)) { + // We couldn't resolve it as a full username. If there were no slashes + // then this might be a partial username. We try to resolve it as such + // but after that, we're done regardless of any matches. + if (FirstSep == llvm::StringRef::npos) { + llvm::StringSet<> MatchSet; + Resolver.ResolvePartial(Username, MatchSet); + for (const auto &S : MatchSet) { + Resolved = S.getKey(); + path::append(Resolved, path::get_separator()); + request.AddCompletion(Resolved, "", CompletionMode::Partial); + } + } + return; + } + + // If there was no trailing slash, then we're done as soon as we resolve + // the expression to the correct directory. Otherwise we need to continue + // looking for matches within that directory. + if (FirstSep == llvm::StringRef::npos) { + // Make sure it ends with a separator. + path::append(CompletionBuffer, path::get_separator()); + request.AddCompletion(CompletionBuffer, "", CompletionMode::Partial); + return; + } + + // We want to keep the form the user typed, so we special case this to + // search in the fully resolved directory, but CompletionBuffer keeps the + // unmodified form that the user typed. + Storage = Resolved; + llvm::StringRef RemainderDir = path::parent_path(Remainder); + if (!RemainderDir.empty()) { + // Append the remaining path to the resolved directory. + Storage.append(path::get_separator()); + Storage.append(RemainderDir); + } + SearchDir = Storage; + } else { + SearchDir = path::parent_path(CompletionBuffer); + } + + size_t FullPrefixLen = CompletionBuffer.size(); + + PartialItem = path::filename(CompletionBuffer); + + // path::filename() will return "." when the passed path ends with a + // directory separator. We have to filter those out, but only when the + // "." doesn't come from the completion request itself. + if (PartialItem == "." && path::is_separator(CompletionBuffer.back())) + PartialItem = llvm::StringRef(); + + if (SearchDir.empty()) { + llvm::sys::fs::current_path(Storage); + SearchDir = Storage; + } + assert(!PartialItem.contains(path::get_separator())); + + // SearchDir now contains the directory to search in, and Prefix contains the + // text we want to match against items in that directory. + + FileSystem &fs = FileSystem::Instance(); + std::error_code EC; + llvm::vfs::directory_iterator Iter = fs.DirBegin(SearchDir, EC); + llvm::vfs::directory_iterator End; + for (; Iter != End && !EC; Iter.increment(EC)) { + auto &Entry = *Iter; + llvm::ErrorOr<llvm::vfs::Status> Status = fs.GetStatus(Entry.path()); + + if (!Status) + continue; + + auto Name = path::filename(Entry.path()); + + // Omit ".", ".." + if (Name == "." || Name == ".." || !Name.startswith(PartialItem)) + continue; + + bool is_dir = Status->isDirectory(); + + // If it's a symlink, then we treat it as a directory as long as the target + // is a directory. + if (Status->isSymlink()) { + FileSpec symlink_filespec(Entry.path()); + FileSpec resolved_filespec; + auto error = fs.ResolveSymbolicLink(symlink_filespec, resolved_filespec); + if (error.Success()) + is_dir = fs.IsDirectory(symlink_filespec); + } + + if (only_directories && !is_dir) + continue; + + // Shrink it back down so that it just has the original prefix the user + // typed and remove the part of the name which is common to the located + // item and what the user typed. + CompletionBuffer.resize(FullPrefixLen); + Name = Name.drop_front(PartialItem.size()); + CompletionBuffer.append(Name); + + if (is_dir) { + path::append(CompletionBuffer, path::get_separator()); + } + + CompletionMode mode = + is_dir ? CompletionMode::Partial : CompletionMode::Normal; + request.AddCompletion(CompletionBuffer, "", mode); + } +} + +static void DiskFilesOrDirectories(const llvm::Twine &partial_name, + bool only_directories, StringList &matches, + TildeExpressionResolver &Resolver) { + CompletionResult result; + std::string partial_name_str = partial_name.str(); + CompletionRequest request(partial_name_str, partial_name_str.size(), result); + DiskFilesOrDirectories(partial_name, only_directories, request, Resolver); + result.GetMatches(matches); +} + +static void DiskFilesOrDirectories(CompletionRequest &request, + bool only_directories) { + StandardTildeExpressionResolver resolver; + DiskFilesOrDirectories(request.GetCursorArgumentPrefix(), only_directories, + request, resolver); +} + +void CommandCompletions::DiskFiles(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + DiskFilesOrDirectories(request, /*only_dirs*/ false); +} + +void CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name, + StringList &matches, + TildeExpressionResolver &Resolver) { + DiskFilesOrDirectories(partial_file_name, false, matches, Resolver); +} + +void CommandCompletions::DiskDirectories(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + DiskFilesOrDirectories(request, /*only_dirs*/ true); +} + +void CommandCompletions::DiskDirectories(const llvm::Twine &partial_file_name, + StringList &matches, + TildeExpressionResolver &Resolver) { + DiskFilesOrDirectories(partial_file_name, true, matches, Resolver); +} + +void CommandCompletions::Modules(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + ModuleCompleter completer(interpreter, request); + + if (searcher == nullptr) { + lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); + SearchFilterForUnconstrainedSearches null_searcher(target_sp); + completer.DoCompletion(&null_searcher); + } else { + completer.DoCompletion(searcher); + } +} + +void CommandCompletions::Symbols(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + SymbolCompleter completer(interpreter, request); + + if (searcher == nullptr) { + lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); + SearchFilterForUnconstrainedSearches null_searcher(target_sp); + completer.DoCompletion(&null_searcher); + } else { + completer.DoCompletion(searcher); + } +} + +void CommandCompletions::SettingsNames(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + // Cache the full setting name list + static StringList g_property_names; + if (g_property_names.GetSize() == 0) { + // Generate the full setting name list on demand + lldb::OptionValuePropertiesSP properties_sp( + interpreter.GetDebugger().GetValueProperties()); + if (properties_sp) { + StreamString strm; + properties_sp->DumpValue(nullptr, strm, OptionValue::eDumpOptionName); + const std::string &str = strm.GetString(); + g_property_names.SplitIntoLines(str.c_str(), str.size()); + } + } + + for (const std::string &s : g_property_names) + request.TryCompleteCurrentArg(s); +} + +void CommandCompletions::PlatformPluginNames(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + PluginManager::AutoCompletePlatformName(request.GetCursorArgumentPrefix(), + request); +} + +void CommandCompletions::ArchitectureNames(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + ArchSpec::AutoComplete(request); +} + +void CommandCompletions::VariablePath(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + Variable::AutoComplete(interpreter.GetExecutionContext(), request); +} + +CommandCompletions::Completer::Completer(CommandInterpreter &interpreter, + CompletionRequest &request) + : m_interpreter(interpreter), m_request(request) {} + +CommandCompletions::Completer::~Completer() = default; + +// SourceFileCompleter + +CommandCompletions::SourceFileCompleter::SourceFileCompleter( + CommandInterpreter &interpreter, bool include_support_files, + CompletionRequest &request) + : CommandCompletions::Completer(interpreter, request), + m_include_support_files(include_support_files), m_matching_files() { + FileSpec partial_spec(m_request.GetCursorArgumentPrefix()); + m_file_name = partial_spec.GetFilename().GetCString(); + m_dir_name = partial_spec.GetDirectory().GetCString(); +} + +lldb::SearchDepth CommandCompletions::SourceFileCompleter::GetDepth() { + return lldb::eSearchDepthCompUnit; +} + +Searcher::CallbackReturn +CommandCompletions::SourceFileCompleter::SearchCallback(SearchFilter &filter, + SymbolContext &context, + Address *addr) { + if (context.comp_unit != nullptr) { + if (m_include_support_files) { + FileSpecList supporting_files = context.comp_unit->GetSupportFiles(); + for (size_t sfiles = 0; sfiles < supporting_files.GetSize(); sfiles++) { + const FileSpec &sfile_spec = + supporting_files.GetFileSpecAtIndex(sfiles); + const char *sfile_file_name = sfile_spec.GetFilename().GetCString(); + const char *sfile_dir_name = sfile_spec.GetFilename().GetCString(); + bool match = false; + if (m_file_name && sfile_file_name && + strstr(sfile_file_name, m_file_name) == sfile_file_name) + match = true; + if (match && m_dir_name && sfile_dir_name && + strstr(sfile_dir_name, m_dir_name) != sfile_dir_name) + match = false; + + if (match) { + m_matching_files.AppendIfUnique(sfile_spec); + } + } + } else { + const char *cur_file_name = context.comp_unit->GetFilename().GetCString(); + const char *cur_dir_name = context.comp_unit->GetDirectory().GetCString(); + + bool match = false; + if (m_file_name && cur_file_name && + strstr(cur_file_name, m_file_name) == cur_file_name) + match = true; + + if (match && m_dir_name && cur_dir_name && + strstr(cur_dir_name, m_dir_name) != cur_dir_name) + match = false; + + if (match) { + m_matching_files.AppendIfUnique(context.comp_unit); + } + } + } + return Searcher::eCallbackReturnContinue; +} + +void CommandCompletions::SourceFileCompleter::DoCompletion( + SearchFilter *filter) { + filter->Search(*this); + // Now convert the filelist to completions: + for (size_t i = 0; i < m_matching_files.GetSize(); i++) { + m_request.AddCompletion( + m_matching_files.GetFileSpecAtIndex(i).GetFilename().GetCString()); + } +} + +// SymbolCompleter + +static bool regex_chars(const char comp) { + return (comp == '[' || comp == ']' || comp == '(' || comp == ')' || + comp == '{' || comp == '}' || comp == '+' || comp == '.' || + comp == '*' || comp == '|' || comp == '^' || comp == '$' || + comp == '\\' || comp == '?'); +} + +CommandCompletions::SymbolCompleter::SymbolCompleter( + CommandInterpreter &interpreter, CompletionRequest &request) + : CommandCompletions::Completer(interpreter, request) { + std::string regex_str; + if (!m_request.GetCursorArgumentPrefix().empty()) { + regex_str.append("^"); + regex_str.append(m_request.GetCursorArgumentPrefix()); + } else { + // Match anything since the completion string is empty + regex_str.append("."); + } + std::string::iterator pos = + find_if(regex_str.begin() + 1, regex_str.end(), regex_chars); + while (pos < regex_str.end()) { + pos = regex_str.insert(pos, '\\'); + pos = find_if(pos + 2, regex_str.end(), regex_chars); + } + m_regex = RegularExpression(regex_str); +} + +lldb::SearchDepth CommandCompletions::SymbolCompleter::GetDepth() { + return lldb::eSearchDepthModule; +} + +Searcher::CallbackReturn CommandCompletions::SymbolCompleter::SearchCallback( + SearchFilter &filter, SymbolContext &context, Address *addr) { + if (context.module_sp) { + SymbolContextList sc_list; + const bool include_symbols = true; + const bool include_inlines = true; + context.module_sp->FindFunctions(m_regex, include_symbols, include_inlines, + sc_list); + + SymbolContext sc; + // Now add the functions & symbols to the list - only add if unique: + for (uint32_t i = 0; i < sc_list.GetSize(); i++) { + if (sc_list.GetContextAtIndex(i, sc)) { + ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled); + // Ensure that the function name matches the regex. This is more than a + // sanity check. It is possible that the demangled function name does + // not start with the prefix, for example when it's in an anonymous + // namespace. + if (!func_name.IsEmpty() && m_regex.Execute(func_name.GetStringRef())) + m_match_set.insert(func_name); + } + } + } + return Searcher::eCallbackReturnContinue; +} + +void CommandCompletions::SymbolCompleter::DoCompletion(SearchFilter *filter) { + filter->Search(*this); + collection::iterator pos = m_match_set.begin(), end = m_match_set.end(); + for (pos = m_match_set.begin(); pos != end; pos++) + m_request.AddCompletion((*pos).GetCString()); +} + +// ModuleCompleter +CommandCompletions::ModuleCompleter::ModuleCompleter( + CommandInterpreter &interpreter, CompletionRequest &request) + : CommandCompletions::Completer(interpreter, request) { + FileSpec partial_spec(m_request.GetCursorArgumentPrefix()); + m_file_name = partial_spec.GetFilename().GetCString(); + m_dir_name = partial_spec.GetDirectory().GetCString(); +} + +lldb::SearchDepth CommandCompletions::ModuleCompleter::GetDepth() { + return lldb::eSearchDepthModule; +} + +Searcher::CallbackReturn CommandCompletions::ModuleCompleter::SearchCallback( + SearchFilter &filter, SymbolContext &context, Address *addr) { + if (context.module_sp) { + const char *cur_file_name = + context.module_sp->GetFileSpec().GetFilename().GetCString(); + const char *cur_dir_name = + context.module_sp->GetFileSpec().GetDirectory().GetCString(); + + bool match = false; + if (m_file_name && cur_file_name && + strstr(cur_file_name, m_file_name) == cur_file_name) + match = true; + + if (match && m_dir_name && cur_dir_name && + strstr(cur_dir_name, m_dir_name) != cur_dir_name) + match = false; + + if (match) { + m_request.AddCompletion(cur_file_name); + } + } + return Searcher::eCallbackReturnContinue; +} + +void CommandCompletions::ModuleCompleter::DoCompletion(SearchFilter *filter) { + filter->Search(*this); +} diff --git a/lldb/source/Commands/CommandObjectApropos.cpp b/lldb/source/Commands/CommandObjectApropos.cpp new file mode 100644 index 0000000000000..7ba0b250fbd58 --- /dev/null +++ b/lldb/source/Commands/CommandObjectApropos.cpp @@ -0,0 +1,99 @@ +//===-- CommandObjectApropos.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 "CommandObjectApropos.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Interpreter/Property.h" +#include "lldb/Utility/Args.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectApropos + +CommandObjectApropos::CommandObjectApropos(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "apropos", + "List debugger commands related to a word or subject.", nullptr) { + CommandArgumentEntry arg; + CommandArgumentData search_word_arg; + + // Define the first (and only) variant of this arg. + search_word_arg.arg_type = eArgTypeSearchWord; + search_word_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the argument + // entry. + arg.push_back(search_word_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); +} + +CommandObjectApropos::~CommandObjectApropos() = default; + +bool CommandObjectApropos::DoExecute(Args &args, CommandReturnObject &result) { + const size_t argc = args.GetArgumentCount(); + + if (argc == 1) { + auto search_word = args[0].ref(); + if (!search_word.empty()) { + // The bulk of the work must be done inside the Command Interpreter, + // since the command dictionary is private. + StringList commands_found; + StringList commands_help; + + m_interpreter.FindCommandsForApropos(search_word, commands_found, + commands_help, true, true, true); + + if (commands_found.GetSize() == 0) { + result.AppendMessageWithFormat("No commands found pertaining to '%s'. " + "Try 'help' to see a complete list of " + "debugger commands.\n", + args[0].c_str()); + } else { + if (commands_found.GetSize() > 0) { + result.AppendMessageWithFormat( + "The following commands may relate to '%s':\n", args[0].c_str()); + const size_t max_len = commands_found.GetMaxStringLength(); + + for (size_t i = 0; i < commands_found.GetSize(); ++i) + m_interpreter.OutputFormattedHelpText( + result.GetOutputStream(), commands_found.GetStringAtIndex(i), + "--", commands_help.GetStringAtIndex(i), max_len); + } + } + + std::vector<const Property *> properties; + const size_t num_properties = + GetDebugger().Apropos(search_word, properties); + if (num_properties) { + const bool dump_qualified_name = true; + result.AppendMessageWithFormatv( + "\nThe following settings variables may relate to '{0}': \n\n", + args[0].ref()); + for (size_t i = 0; i < num_properties; ++i) + properties[i]->DumpDescription( + m_interpreter, result.GetOutputStream(), 0, dump_qualified_name); + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("'' is not a valid search word.\n"); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("'apropos' must be called with exactly one argument.\n"); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); +} diff --git a/lldb/source/Commands/CommandObjectApropos.h b/lldb/source/Commands/CommandObjectApropos.h new file mode 100644 index 0000000000000..37d86b17d1a26 --- /dev/null +++ b/lldb/source/Commands/CommandObjectApropos.h @@ -0,0 +1,31 @@ +//===-- CommandObjectApropos.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_CommandObjectApropos_h_ +#define liblldb_CommandObjectApropos_h_ + +#include "lldb/Interpreter/CommandObject.h" + +namespace lldb_private { + +// CommandObjectApropos + +class CommandObjectApropos : public CommandObjectParsed { +public: + CommandObjectApropos(CommandInterpreter &interpreter); + + ~CommandObjectApropos() override; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectApropos_h_ diff --git a/lldb/source/Commands/CommandObjectBreakpoint.cpp b/lldb/source/Commands/CommandObjectBreakpoint.cpp new file mode 100644 index 0000000000000..ad699975b5070 --- /dev/null +++ b/lldb/source/Commands/CommandObjectBreakpoint.cpp @@ -0,0 +1,2358 @@ +//===-- CommandObjectBreakpoint.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 "CommandObjectBreakpoint.h" +#include "CommandObjectBreakpointCommand.h" +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Breakpoint/BreakpointIDList.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupPythonClassWithDict.h" +#include "lldb/Interpreter/OptionValueBoolean.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/OptionValueUInt64.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadSpec.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/StreamString.h" + +#include <memory> +#include <vector> + +using namespace lldb; +using namespace lldb_private; + +static void AddBreakpointDescription(Stream *s, Breakpoint *bp, + lldb::DescriptionLevel level) { + s->IndentMore(); + bp->GetDescription(s, level, true); + s->IndentLess(); + s->EOL(); +} + +// Modifiable Breakpoint Options +#pragma mark Modify::CommandOptions +#define LLDB_OPTIONS_breakpoint_modify +#include "CommandOptions.inc" + +class lldb_private::BreakpointOptionGroup : public OptionGroup +{ +public: + BreakpointOptionGroup() : + OptionGroup(), + m_bp_opts(false) {} + + ~BreakpointOptionGroup() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_modify_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = g_breakpoint_modify_options[option_idx].short_option; + + switch (short_option) { + case 'c': + // Normally an empty breakpoint condition marks is as unset. But we need + // to say it was passed in. + m_bp_opts.SetCondition(option_arg.str().c_str()); + m_bp_opts.m_set_flags.Set(BreakpointOptions::eCondition); + break; + case 'C': + m_commands.push_back(option_arg); + break; + case 'd': + m_bp_opts.SetEnabled(false); + break; + case 'e': + m_bp_opts.SetEnabled(true); + break; + case 'G': { + bool value, success; + value = OptionArgParser::ToBoolean(option_arg, false, &success); + if (success) { + m_bp_opts.SetAutoContinue(value); + } else + error.SetErrorStringWithFormat( + "invalid boolean value '%s' passed for -G option", + option_arg.str().c_str()); + } + break; + case 'i': + { + uint32_t ignore_count; + if (option_arg.getAsInteger(0, ignore_count)) + error.SetErrorStringWithFormat("invalid ignore count '%s'", + option_arg.str().c_str()); + else + m_bp_opts.SetIgnoreCount(ignore_count); + } + break; + case 'o': { + bool value, success; + value = OptionArgParser::ToBoolean(option_arg, false, &success); + if (success) { + m_bp_opts.SetOneShot(value); + } else + error.SetErrorStringWithFormat( + "invalid boolean value '%s' passed for -o option", + option_arg.str().c_str()); + } break; + case 't': + { + lldb::tid_t thread_id = LLDB_INVALID_THREAD_ID; + if (option_arg[0] != '\0') { + if (option_arg.getAsInteger(0, thread_id)) + error.SetErrorStringWithFormat("invalid thread id string '%s'", + option_arg.str().c_str()); + } + m_bp_opts.SetThreadID(thread_id); + } + break; + case 'T': + m_bp_opts.GetThreadSpec()->SetName(option_arg.str().c_str()); + break; + case 'q': + m_bp_opts.GetThreadSpec()->SetQueueName(option_arg.str().c_str()); + break; + case 'x': + { + uint32_t thread_index = UINT32_MAX; + if (option_arg[0] != '\n') { + if (option_arg.getAsInteger(0, thread_index)) + error.SetErrorStringWithFormat("invalid thread index string '%s'", + option_arg.str().c_str()); + } + m_bp_opts.GetThreadSpec()->SetIndex(thread_index); + } + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_bp_opts.Clear(); + m_commands.clear(); + } + + Status OptionParsingFinished(ExecutionContext *execution_context) override { + if (!m_commands.empty()) + { + if (!m_commands.empty()) + { + auto cmd_data = std::make_unique<BreakpointOptions::CommandData>(); + + for (std::string &str : m_commands) + cmd_data->user_source.AppendString(str); + + cmd_data->stop_on_error = true; + m_bp_opts.SetCommandDataCallback(cmd_data); + } + } + return Status(); + } + + const BreakpointOptions &GetBreakpointOptions() + { + return m_bp_opts; + } + + std::vector<std::string> m_commands; + BreakpointOptions m_bp_opts; + +}; + +#define LLDB_OPTIONS_breakpoint_dummy +#include "CommandOptions.inc" + +class BreakpointDummyOptionGroup : public OptionGroup +{ +public: + BreakpointDummyOptionGroup() : + OptionGroup() {} + + ~BreakpointDummyOptionGroup() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_dummy_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = g_breakpoint_modify_options[option_idx].short_option; + + switch (short_option) { + case 'D': + m_use_dummy = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_use_dummy = false; + } + + bool m_use_dummy; + +}; + +#define LLDB_OPTIONS_breakpoint_set +#include "CommandOptions.inc" + +// CommandObjectBreakpointSet + +class CommandObjectBreakpointSet : public CommandObjectParsed { +public: + enum BreakpointSetType { + eSetTypeInvalid, + eSetTypeFileAndLine, + eSetTypeAddress, + eSetTypeFunctionName, + eSetTypeFunctionRegexp, + eSetTypeSourceRegexp, + eSetTypeException, + eSetTypeScripted, + }; + + CommandObjectBreakpointSet(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint set", + "Sets a breakpoint or set of breakpoints in the executable.", + "breakpoint set <cmd-options>"), + m_bp_opts(), m_python_class_options("scripted breakpoint", 'P'), + m_options() { + // We're picking up all the normal options, commands and disable. + m_all_options.Append(&m_python_class_options, LLDB_OPT_SET_1, + LLDB_OPT_SET_11); + m_all_options.Append(&m_bp_opts, + LLDB_OPT_SET_1 | LLDB_OPT_SET_3 | LLDB_OPT_SET_4, + LLDB_OPT_SET_ALL); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_1, LLDB_OPT_SET_ALL); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointSet() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() + : OptionGroup(), m_condition(), m_filenames(), m_line_num(0), m_column(0), + m_func_names(), m_func_name_type_mask(eFunctionNameTypeNone), + m_func_regexp(), m_source_text_regexp(), m_modules(), m_load_addr(), + m_catch_bp(false), m_throw_bp(true), m_hardware(false), + m_exception_language(eLanguageTypeUnknown), + m_language(lldb::eLanguageTypeUnknown), + m_skip_prologue(eLazyBoolCalculate), + m_all_files(false), m_move_to_nearest_code(eLazyBoolCalculate) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = g_breakpoint_set_options[option_idx].short_option; + + switch (short_option) { + case 'a': { + m_load_addr = OptionArgParser::ToAddress(execution_context, option_arg, + LLDB_INVALID_ADDRESS, &error); + } break; + + case 'A': + m_all_files = true; + break; + + case 'b': + m_func_names.push_back(option_arg); + m_func_name_type_mask |= eFunctionNameTypeBase; + break; + + case 'C': + if (option_arg.getAsInteger(0, m_column)) + error.SetErrorStringWithFormat("invalid column number: %s", + option_arg.str().c_str()); + break; + + case 'E': { + LanguageType language = Language::GetLanguageTypeFromString(option_arg); + + switch (language) { + case eLanguageTypeC89: + case eLanguageTypeC: + case eLanguageTypeC99: + case eLanguageTypeC11: + m_exception_language = eLanguageTypeC; + break; + case eLanguageTypeC_plus_plus: + case eLanguageTypeC_plus_plus_03: + case eLanguageTypeC_plus_plus_11: + case eLanguageTypeC_plus_plus_14: + m_exception_language = eLanguageTypeC_plus_plus; + break; + case eLanguageTypeObjC: + m_exception_language = eLanguageTypeObjC; + break; + case eLanguageTypeObjC_plus_plus: + error.SetErrorStringWithFormat( + "Set exception breakpoints separately for c++ and objective-c"); + break; + case eLanguageTypeUnknown: + error.SetErrorStringWithFormat( + "Unknown language type: '%s' for exception breakpoint", + option_arg.str().c_str()); + break; + default: + error.SetErrorStringWithFormat( + "Unsupported language type: '%s' for exception breakpoint", + option_arg.str().c_str()); + } + } break; + + case 'f': + m_filenames.AppendIfUnique(FileSpec(option_arg)); + break; + + case 'F': + m_func_names.push_back(option_arg); + m_func_name_type_mask |= eFunctionNameTypeFull; + break; + + case 'h': { + bool success; + m_catch_bp = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error.SetErrorStringWithFormat( + "Invalid boolean value for on-catch option: '%s'", + option_arg.str().c_str()); + } break; + + case 'H': + m_hardware = true; + break; + + case 'K': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (value) + m_skip_prologue = eLazyBoolYes; + else + m_skip_prologue = eLazyBoolNo; + + if (!success) + error.SetErrorStringWithFormat( + "Invalid boolean value for skip prologue option: '%s'", + option_arg.str().c_str()); + } break; + + case 'l': + if (option_arg.getAsInteger(0, m_line_num)) + error.SetErrorStringWithFormat("invalid line number: %s.", + option_arg.str().c_str()); + break; + + case 'L': + m_language = Language::GetLanguageTypeFromString(option_arg); + if (m_language == eLanguageTypeUnknown) + error.SetErrorStringWithFormat( + "Unknown language type: '%s' for breakpoint", + option_arg.str().c_str()); + break; + + case 'm': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (value) + m_move_to_nearest_code = eLazyBoolYes; + else + m_move_to_nearest_code = eLazyBoolNo; + + if (!success) + error.SetErrorStringWithFormat( + "Invalid boolean value for move-to-nearest-code option: '%s'", + option_arg.str().c_str()); + break; + } + + case 'M': + m_func_names.push_back(option_arg); + m_func_name_type_mask |= eFunctionNameTypeMethod; + break; + + case 'n': + m_func_names.push_back(option_arg); + m_func_name_type_mask |= eFunctionNameTypeAuto; + break; + + case 'N': { + if (BreakpointID::StringIsBreakpointName(option_arg, error)) + m_breakpoint_names.push_back(option_arg); + else + error.SetErrorStringWithFormat("Invalid breakpoint name: %s", + option_arg.str().c_str()); + break; + } + + case 'R': { + lldb::addr_t tmp_offset_addr; + tmp_offset_addr = OptionArgParser::ToAddress(execution_context, + option_arg, 0, &error); + if (error.Success()) + m_offset_addr = tmp_offset_addr; + } break; + + case 'O': + m_exception_extra_args.AppendArgument("-O"); + m_exception_extra_args.AppendArgument(option_arg); + break; + + case 'p': + m_source_text_regexp.assign(option_arg); + break; + + case 'r': + m_func_regexp.assign(option_arg); + break; + + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + + case 'S': + m_func_names.push_back(option_arg); + m_func_name_type_mask |= eFunctionNameTypeSelector; + break; + + case 'w': { + bool success; + m_throw_bp = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error.SetErrorStringWithFormat( + "Invalid boolean value for on-throw option: '%s'", + option_arg.str().c_str()); + } break; + + case 'X': + m_source_regex_func_names.insert(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_filenames.Clear(); + m_line_num = 0; + m_column = 0; + m_func_names.clear(); + m_func_name_type_mask = eFunctionNameTypeNone; + m_func_regexp.clear(); + m_source_text_regexp.clear(); + m_modules.Clear(); + m_load_addr = LLDB_INVALID_ADDRESS; + m_offset_addr = 0; + m_catch_bp = false; + m_throw_bp = true; + m_hardware = false; + m_exception_language = eLanguageTypeUnknown; + m_language = lldb::eLanguageTypeUnknown; + m_skip_prologue = eLazyBoolCalculate; + m_breakpoint_names.clear(); + m_all_files = false; + m_exception_extra_args.Clear(); + m_move_to_nearest_code = eLazyBoolCalculate; + m_source_regex_func_names.clear(); + m_current_key.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_set_options); + } + + // Instance variables to hold the values for command options. + + std::string m_condition; + FileSpecList m_filenames; + uint32_t m_line_num; + uint32_t m_column; + std::vector<std::string> m_func_names; + std::vector<std::string> m_breakpoint_names; + lldb::FunctionNameType m_func_name_type_mask; + std::string m_func_regexp; + std::string m_source_text_regexp; + FileSpecList m_modules; + lldb::addr_t m_load_addr; + lldb::addr_t m_offset_addr; + bool m_catch_bp; + bool m_throw_bp; + bool m_hardware; // Request to use hardware breakpoints + lldb::LanguageType m_exception_language; + lldb::LanguageType m_language; + LazyBool m_skip_prologue; + bool m_all_files; + Args m_exception_extra_args; + LazyBool m_move_to_nearest_code; + std::unordered_set<std::string> m_source_regex_func_names; + std::string m_current_key; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(m_dummy_options.m_use_dummy); + + // The following are the various types of breakpoints that could be set: + // 1). -f -l -p [-s -g] (setting breakpoint by source location) + // 2). -a [-s -g] (setting breakpoint by address) + // 3). -n [-s -g] (setting breakpoint by function name) + // 4). -r [-s -g] (setting breakpoint by function name regular + // expression) + // 5). -p -f (setting a breakpoint by comparing a reg-exp + // to source text) + // 6). -E [-w -h] (setting a breakpoint for exceptions for a + // given language.) + + BreakpointSetType break_type = eSetTypeInvalid; + + if (!m_python_class_options.GetClassName().empty()) + break_type = eSetTypeScripted; + else if (m_options.m_line_num != 0) + break_type = eSetTypeFileAndLine; + else if (m_options.m_load_addr != LLDB_INVALID_ADDRESS) + break_type = eSetTypeAddress; + else if (!m_options.m_func_names.empty()) + break_type = eSetTypeFunctionName; + else if (!m_options.m_func_regexp.empty()) + break_type = eSetTypeFunctionRegexp; + else if (!m_options.m_source_text_regexp.empty()) + break_type = eSetTypeSourceRegexp; + else if (m_options.m_exception_language != eLanguageTypeUnknown) + break_type = eSetTypeException; + + BreakpointSP bp_sp = nullptr; + FileSpec module_spec; + const bool internal = false; + + // If the user didn't specify skip-prologue, having an offset should turn + // that off. + if (m_options.m_offset_addr != 0 && + m_options.m_skip_prologue == eLazyBoolCalculate) + m_options.m_skip_prologue = eLazyBoolNo; + + switch (break_type) { + case eSetTypeFileAndLine: // Breakpoint by source position + { + FileSpec file; + const size_t num_files = m_options.m_filenames.GetSize(); + if (num_files == 0) { + if (!GetDefaultFile(target, file, result)) { + result.AppendError("No file supplied and no default file available."); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else if (num_files > 1) { + result.AppendError("Only one file at a time is allowed for file and " + "line breakpoints."); + result.SetStatus(eReturnStatusFailed); + return false; + } else + file = m_options.m_filenames.GetFileSpecAtIndex(0); + + // Only check for inline functions if + LazyBool check_inlines = eLazyBoolCalculate; + + bp_sp = target.CreateBreakpoint( + &(m_options.m_modules), file, m_options.m_line_num, + m_options.m_column, m_options.m_offset_addr, check_inlines, + m_options.m_skip_prologue, internal, m_options.m_hardware, + m_options.m_move_to_nearest_code); + } break; + + case eSetTypeAddress: // Breakpoint by address + { + // If a shared library has been specified, make an lldb_private::Address + // with the library, and use that. That way the address breakpoint + // will track the load location of the library. + size_t num_modules_specified = m_options.m_modules.GetSize(); + if (num_modules_specified == 1) { + const FileSpec *file_spec = + m_options.m_modules.GetFileSpecPointerAtIndex(0); + bp_sp = target.CreateAddressInModuleBreakpoint( + m_options.m_load_addr, internal, file_spec, m_options.m_hardware); + } else if (num_modules_specified == 0) { + bp_sp = target.CreateBreakpoint(m_options.m_load_addr, internal, + m_options.m_hardware); + } else { + result.AppendError("Only one shared library can be specified for " + "address breakpoints."); + result.SetStatus(eReturnStatusFailed); + return false; + } + break; + } + case eSetTypeFunctionName: // Breakpoint by function name + { + FunctionNameType name_type_mask = m_options.m_func_name_type_mask; + + if (name_type_mask == 0) + name_type_mask = eFunctionNameTypeAuto; + + bp_sp = target.CreateBreakpoint( + &(m_options.m_modules), &(m_options.m_filenames), + m_options.m_func_names, name_type_mask, m_options.m_language, + m_options.m_offset_addr, m_options.m_skip_prologue, internal, + m_options.m_hardware); + } break; + + case eSetTypeFunctionRegexp: // Breakpoint by regular expression function + // name + { + RegularExpression regexp(m_options.m_func_regexp); + if (llvm::Error err = regexp.GetError()) { + result.AppendErrorWithFormat( + "Function name regular expression could not be compiled: \"%s\"", + llvm::toString(std::move(err)).c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + bp_sp = target.CreateFuncRegexBreakpoint( + &(m_options.m_modules), &(m_options.m_filenames), std::move(regexp), + m_options.m_language, m_options.m_skip_prologue, internal, + m_options.m_hardware); + } + break; + case eSetTypeSourceRegexp: // Breakpoint by regexp on source text. + { + const size_t num_files = m_options.m_filenames.GetSize(); + + if (num_files == 0 && !m_options.m_all_files) { + FileSpec file; + if (!GetDefaultFile(target, file, result)) { + result.AppendError( + "No files provided and could not find default file."); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + m_options.m_filenames.Append(file); + } + } + + RegularExpression regexp(m_options.m_source_text_regexp); + if (llvm::Error err = regexp.GetError()) { + result.AppendErrorWithFormat( + "Source text regular expression could not be compiled: \"%s\"", + llvm::toString(std::move(err)).c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + bp_sp = target.CreateSourceRegexBreakpoint( + &(m_options.m_modules), &(m_options.m_filenames), + m_options.m_source_regex_func_names, std::move(regexp), internal, + m_options.m_hardware, m_options.m_move_to_nearest_code); + } break; + case eSetTypeException: { + Status precond_error; + bp_sp = target.CreateExceptionBreakpoint( + m_options.m_exception_language, m_options.m_catch_bp, + m_options.m_throw_bp, internal, &m_options.m_exception_extra_args, + &precond_error); + if (precond_error.Fail()) { + result.AppendErrorWithFormat( + "Error setting extra exception arguments: %s", + precond_error.AsCString()); + target.RemoveBreakpointByID(bp_sp->GetID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } break; + case eSetTypeScripted: { + + Status error; + bp_sp = target.CreateScriptedBreakpoint( + m_python_class_options.GetClassName().c_str(), &(m_options.m_modules), + &(m_options.m_filenames), false, m_options.m_hardware, + m_python_class_options.GetStructuredData(), &error); + if (error.Fail()) { + result.AppendErrorWithFormat( + "Error setting extra exception arguments: %s", + error.AsCString()); + target.RemoveBreakpointByID(bp_sp->GetID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } break; + default: + break; + } + + // Now set the various options that were passed in: + if (bp_sp) { + bp_sp->GetOptions()->CopyOverSetOptions(m_bp_opts.GetBreakpointOptions()); + + if (!m_options.m_breakpoint_names.empty()) { + Status name_error; + for (auto name : m_options.m_breakpoint_names) { + target.AddNameToBreakpoint(bp_sp, name.c_str(), name_error); + if (name_error.Fail()) { + result.AppendErrorWithFormat("Invalid breakpoint name: %s", + name.c_str()); + target.RemoveBreakpointByID(bp_sp->GetID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } + } + + if (bp_sp) { + Stream &output_stream = result.GetOutputStream(); + const bool show_locations = false; + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + show_locations); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + // Don't print out this warning for exception breakpoints. They can + // get set before the target is set, but we won't know how to actually + // set the breakpoint till we run. + if (bp_sp->GetNumLocations() == 0 && break_type != eSetTypeException) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else if (!bp_sp) { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } + +private: + bool GetDefaultFile(Target &target, FileSpec &file, + CommandReturnObject &result) { + uint32_t default_line; + // First use the Source Manager's default file. Then use the current stack + // frame's file. + if (!target.GetSourceManager().GetDefaultFileAndLine(file, default_line)) { + StackFrame *cur_frame = m_exe_ctx.GetFramePtr(); + if (cur_frame == nullptr) { + result.AppendError( + "No selected frame to use to find the default file."); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!cur_frame->HasDebugInformation()) { + result.AppendError("Cannot use the selected frame to find the default " + "file, it has no debug info."); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + const SymbolContext &sc = + cur_frame->GetSymbolContext(eSymbolContextLineEntry); + if (sc.line_entry.file) { + file = sc.line_entry.file; + } else { + result.AppendError("Can't find the file for the selected frame to " + "use as the default file."); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } + return true; + } + + BreakpointOptionGroup m_bp_opts; + BreakpointDummyOptionGroup m_dummy_options; + OptionGroupPythonClassWithDict m_python_class_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +// CommandObjectBreakpointModify +#pragma mark Modify + +class CommandObjectBreakpointModify : public CommandObjectParsed { +public: + CommandObjectBreakpointModify(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "breakpoint modify", + "Modify the options on a breakpoint or set of " + "breakpoints in the executable. " + "If no breakpoint is specified, acts on the last " + "created breakpoint. " + "With the exception of -e, -d and -i, passing an " + "empty argument clears the modification.", + nullptr), + m_options() { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeBreakpointID, + eArgTypeBreakpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + + m_options.Append(&m_bp_opts, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2 | LLDB_OPT_SET_3, + LLDB_OPT_SET_ALL); + m_options.Append(&m_dummy_opts, LLDB_OPT_SET_1, LLDB_OPT_SET_ALL); + m_options.Finalize(); + } + + ~CommandObjectBreakpointModify() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(m_dummy_opts.m_use_dummy); + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + BreakpointIDList valid_bp_ids; + + CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::disablePerm); + + if (result.Succeeded()) { + const size_t count = valid_bp_ids.GetSize(); + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex(i); + + if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) { + Breakpoint *bp = + target.GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) { + BreakpointLocation *location = + bp->FindLocationByID(cur_bp_id.GetLocationID()).get(); + if (location) + location->GetLocationOptions() + ->CopyOverSetOptions(m_bp_opts.GetBreakpointOptions()); + } else { + bp->GetOptions() + ->CopyOverSetOptions(m_bp_opts.GetBreakpointOptions()); + } + } + } + } + + return result.Succeeded(); + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointDummyOptionGroup m_dummy_opts; + OptionGroupOptions m_options; +}; + +// CommandObjectBreakpointEnable +#pragma mark Enable + +class CommandObjectBreakpointEnable : public CommandObjectParsed { +public: + CommandObjectBreakpointEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "enable", + "Enable the specified disabled breakpoint(s). If " + "no breakpoints are specified, enable all of them.", + nullptr) { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeBreakpointID, + eArgTypeBreakpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointEnable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + const BreakpointList &breakpoints = target.GetBreakpointList(); + + size_t num_breakpoints = breakpoints.GetSize(); + + if (num_breakpoints == 0) { + result.AppendError("No breakpoints exist to be enabled."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.empty()) { + // No breakpoint selected; enable all currently set breakpoints. + target.EnableAllowedBreakpoints(); + result.AppendMessageWithFormat("All breakpoints enabled. (%" PRIu64 + " breakpoints)\n", + (uint64_t)num_breakpoints); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular breakpoint selected; enable that breakpoint. + BreakpointIDList valid_bp_ids; + CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::disablePerm); + + if (result.Succeeded()) { + int enable_count = 0; + int loc_count = 0; + const size_t count = valid_bp_ids.GetSize(); + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex(i); + + if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) { + Breakpoint *breakpoint = + target.GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) { + BreakpointLocation *location = + breakpoint->FindLocationByID(cur_bp_id.GetLocationID()).get(); + if (location) { + location->SetEnabled(true); + ++loc_count; + } + } else { + breakpoint->SetEnabled(true); + ++enable_count; + } + } + } + result.AppendMessageWithFormat("%d breakpoints enabled.\n", + enable_count + loc_count); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + } + + return result.Succeeded(); + } +}; + +// CommandObjectBreakpointDisable +#pragma mark Disable + +class CommandObjectBreakpointDisable : public CommandObjectParsed { +public: + CommandObjectBreakpointDisable(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint disable", + "Disable the specified breakpoint(s) without deleting " + "them. If none are specified, disable all " + "breakpoints.", + nullptr) { + SetHelpLong( + "Disable the specified breakpoint(s) without deleting them. \ +If none are specified, disable all breakpoints." + R"( + +)" + "Note: disabling a breakpoint will cause none of its locations to be hit \ +regardless of whether individual locations are enabled or disabled. After the sequence:" + R"( + + (lldb) break disable 1 + (lldb) break enable 1.1 + +execution will NOT stop at location 1.1. To achieve that, type: + + (lldb) break disable 1.* + (lldb) break enable 1.1 + +)" + "The first command disables all locations for breakpoint 1, \ +the second re-enables the first location."); + + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeBreakpointID, + eArgTypeBreakpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointDisable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + const BreakpointList &breakpoints = target.GetBreakpointList(); + size_t num_breakpoints = breakpoints.GetSize(); + + if (num_breakpoints == 0) { + result.AppendError("No breakpoints exist to be disabled."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.empty()) { + // No breakpoint selected; disable all currently set breakpoints. + target.DisableAllowedBreakpoints(); + result.AppendMessageWithFormat("All breakpoints disabled. (%" PRIu64 + " breakpoints)\n", + (uint64_t)num_breakpoints); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular breakpoint selected; disable that breakpoint. + BreakpointIDList valid_bp_ids; + + CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::disablePerm); + + if (result.Succeeded()) { + int disable_count = 0; + int loc_count = 0; + const size_t count = valid_bp_ids.GetSize(); + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex(i); + + if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) { + Breakpoint *breakpoint = + target.GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) { + BreakpointLocation *location = + breakpoint->FindLocationByID(cur_bp_id.GetLocationID()).get(); + if (location) { + location->SetEnabled(false); + ++loc_count; + } + } else { + breakpoint->SetEnabled(false); + ++disable_count; + } + } + } + result.AppendMessageWithFormat("%d breakpoints disabled.\n", + disable_count + loc_count); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + } + + return result.Succeeded(); + } +}; + +// CommandObjectBreakpointList + +#pragma mark List::CommandOptions +#define LLDB_OPTIONS_breakpoint_list +#include "CommandOptions.inc" + +#pragma mark List + +class CommandObjectBreakpointList : public CommandObjectParsed { +public: + CommandObjectBreakpointList(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint list", + "List some or all breakpoints at configurable levels of detail.", + nullptr), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData bp_id_arg; + + // Define the first (and only) variant of this arg. + bp_id_arg.arg_type = eArgTypeBreakpointID; + bp_id_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(bp_id_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointList() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_level(lldb::eDescriptionLevelBrief), m_use_dummy(false) { + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'b': + m_level = lldb::eDescriptionLevelBrief; + break; + case 'D': + m_use_dummy = true; + break; + case 'f': + m_level = lldb::eDescriptionLevelFull; + break; + case 'v': + m_level = lldb::eDescriptionLevelVerbose; + break; + case 'i': + m_internal = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_level = lldb::eDescriptionLevelFull; + m_internal = false; + m_use_dummy = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_list_options); + } + + // Instance variables to hold the values for command options. + + lldb::DescriptionLevel m_level; + + bool m_internal; + bool m_use_dummy; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(m_options.m_use_dummy); + + const BreakpointList &breakpoints = + target.GetBreakpointList(m_options.m_internal); + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList(m_options.m_internal).GetListMutex(lock); + + size_t num_breakpoints = breakpoints.GetSize(); + + if (num_breakpoints == 0) { + result.AppendMessage("No breakpoints currently set."); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } + + Stream &output_stream = result.GetOutputStream(); + + if (command.empty()) { + // No breakpoint selected; show info about all currently set breakpoints. + result.AppendMessage("Current breakpoints:"); + for (size_t i = 0; i < num_breakpoints; ++i) { + Breakpoint *breakpoint = breakpoints.GetBreakpointAtIndex(i).get(); + if (breakpoint->AllowList()) + AddBreakpointDescription(&output_stream, breakpoint, + m_options.m_level); + } + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular breakpoints selected; show info about that breakpoint. + BreakpointIDList valid_bp_ids; + CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::listPerm); + + if (result.Succeeded()) { + for (size_t i = 0; i < valid_bp_ids.GetSize(); ++i) { + BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex(i); + Breakpoint *breakpoint = + target.GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + AddBreakpointDescription(&output_stream, breakpoint, + m_options.m_level); + } + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("Invalid breakpoint ID."); + result.SetStatus(eReturnStatusFailed); + } + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectBreakpointClear +#pragma mark Clear::CommandOptions + +#define LLDB_OPTIONS_breakpoint_clear +#include "CommandOptions.inc" + +#pragma mark Clear + +class CommandObjectBreakpointClear : public CommandObjectParsed { +public: + enum BreakpointClearType { eClearTypeInvalid, eClearTypeFileAndLine }; + + CommandObjectBreakpointClear(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "breakpoint clear", + "Delete or disable breakpoints matching the " + "specified source file and line.", + "breakpoint clear <cmd-options>"), + m_options() {} + + ~CommandObjectBreakpointClear() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_filename(), m_line_num(0) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + m_filename.assign(option_arg); + break; + + case 'l': + option_arg.getAsInteger(0, m_line_num); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_filename.clear(); + m_line_num = 0; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_clear_options); + } + + // Instance variables to hold the values for command options. + + std::string m_filename; + uint32_t m_line_num; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + + // The following are the various types of breakpoints that could be + // cleared: + // 1). -f -l (clearing breakpoint by source location) + + BreakpointClearType break_type = eClearTypeInvalid; + + if (m_options.m_line_num != 0) + break_type = eClearTypeFileAndLine; + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + BreakpointList &breakpoints = target.GetBreakpointList(); + size_t num_breakpoints = breakpoints.GetSize(); + + // Early return if there's no breakpoint at all. + if (num_breakpoints == 0) { + result.AppendError("Breakpoint clear: No breakpoint cleared."); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + // Find matching breakpoints and delete them. + + // First create a copy of all the IDs. + std::vector<break_id_t> BreakIDs; + for (size_t i = 0; i < num_breakpoints; ++i) + BreakIDs.push_back(breakpoints.GetBreakpointAtIndex(i)->GetID()); + + int num_cleared = 0; + StreamString ss; + switch (break_type) { + case eClearTypeFileAndLine: // Breakpoint by source position + { + const ConstString filename(m_options.m_filename.c_str()); + BreakpointLocationCollection loc_coll; + + for (size_t i = 0; i < num_breakpoints; ++i) { + Breakpoint *bp = breakpoints.FindBreakpointByID(BreakIDs[i]).get(); + + if (bp->GetMatchingFileLine(filename, m_options.m_line_num, loc_coll)) { + // If the collection size is 0, it's a full match and we can just + // remove the breakpoint. + if (loc_coll.GetSize() == 0) { + bp->GetDescription(&ss, lldb::eDescriptionLevelBrief); + ss.EOL(); + target.RemoveBreakpointByID(bp->GetID()); + ++num_cleared; + } + } + } + } break; + + default: + break; + } + + if (num_cleared > 0) { + Stream &output_stream = result.GetOutputStream(); + output_stream.Printf("%d breakpoints cleared:\n", num_cleared); + output_stream << ss.GetString(); + output_stream.EOL(); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("Breakpoint clear: No breakpoint cleared."); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectBreakpointDelete +#define LLDB_OPTIONS_breakpoint_delete +#include "CommandOptions.inc" + +#pragma mark Delete + +class CommandObjectBreakpointDelete : public CommandObjectParsed { +public: + CommandObjectBreakpointDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "breakpoint delete", + "Delete the specified breakpoint(s). If no " + "breakpoints are specified, delete them all.", + nullptr), + m_options() { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeBreakpointID, + eArgTypeBreakpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointDelete() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_use_dummy(false), m_force(false) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + m_force = true; + break; + + case 'D': + m_use_dummy = true; + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_use_dummy = false; + m_force = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_delete_options); + } + + // Instance variables to hold the values for command options. + bool m_use_dummy; + bool m_force; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(m_options.m_use_dummy); + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + const BreakpointList &breakpoints = target.GetBreakpointList(); + + size_t num_breakpoints = breakpoints.GetSize(); + + if (num_breakpoints == 0) { + result.AppendError("No breakpoints exist to be deleted."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.empty()) { + if (!m_options.m_force && + !m_interpreter.Confirm( + "About to delete all breakpoints, do you want to do that?", + true)) { + result.AppendMessage("Operation cancelled..."); + } else { + target.RemoveAllowedBreakpoints(); + result.AppendMessageWithFormat( + "All breakpoints removed. (%" PRIu64 " breakpoint%s)\n", + (uint64_t)num_breakpoints, num_breakpoints > 1 ? "s" : ""); + } + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular breakpoint selected; disable that breakpoint. + BreakpointIDList valid_bp_ids; + CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::deletePerm); + + if (result.Succeeded()) { + int delete_count = 0; + int disable_count = 0; + const size_t count = valid_bp_ids.GetSize(); + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex(i); + + if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) { + if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) { + Breakpoint *breakpoint = + target.GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + BreakpointLocation *location = + breakpoint->FindLocationByID(cur_bp_id.GetLocationID()).get(); + // It makes no sense to try to delete individual locations, so we + // disable them instead. + if (location) { + location->SetEnabled(false); + ++disable_count; + } + } else { + target.RemoveBreakpointByID(cur_bp_id.GetBreakpointID()); + ++delete_count; + } + } + } + result.AppendMessageWithFormat( + "%d breakpoints deleted; %d breakpoint locations disabled.\n", + delete_count, disable_count); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + } + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectBreakpointName +#define LLDB_OPTIONS_breakpoint_name +#include "CommandOptions.inc" + +class BreakpointNameOptionGroup : public OptionGroup { +public: + BreakpointNameOptionGroup() + : OptionGroup(), m_breakpoint(LLDB_INVALID_BREAK_ID), m_use_dummy(false) { + } + + ~BreakpointNameOptionGroup() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_name_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = g_breakpoint_name_options[option_idx].short_option; + + switch (short_option) { + case 'N': + if (BreakpointID::StringIsBreakpointName(option_arg, error) && + error.Success()) + m_name.SetValueFromString(option_arg); + break; + case 'B': + if (m_breakpoint.SetValueFromString(option_arg).Fail()) + error.SetErrorStringWithFormat( + "unrecognized value \"%s\" for breakpoint", + option_arg.str().c_str()); + break; + case 'D': + if (m_use_dummy.SetValueFromString(option_arg).Fail()) + error.SetErrorStringWithFormat( + "unrecognized value \"%s\" for use-dummy", + option_arg.str().c_str()); + break; + case 'H': + m_help_string.SetValueFromString(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_name.Clear(); + m_breakpoint.Clear(); + m_use_dummy.Clear(); + m_use_dummy.SetDefaultValue(false); + m_help_string.Clear(); + } + + OptionValueString m_name; + OptionValueUInt64 m_breakpoint; + OptionValueBoolean m_use_dummy; + OptionValueString m_help_string; +}; + +#define LLDB_OPTIONS_breakpoint_access +#include "CommandOptions.inc" + +class BreakpointAccessOptionGroup : public OptionGroup { +public: + BreakpointAccessOptionGroup() : OptionGroup() {} + + ~BreakpointAccessOptionGroup() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_access_options); + } + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option + = g_breakpoint_access_options[option_idx].short_option; + + switch (short_option) { + case 'L': { + bool value, success; + value = OptionArgParser::ToBoolean(option_arg, false, &success); + if (success) { + m_permissions.SetAllowList(value); + } else + error.SetErrorStringWithFormat( + "invalid boolean value '%s' passed for -L option", + option_arg.str().c_str()); + } break; + case 'A': { + bool value, success; + value = OptionArgParser::ToBoolean(option_arg, false, &success); + if (success) { + m_permissions.SetAllowDisable(value); + } else + error.SetErrorStringWithFormat( + "invalid boolean value '%s' passed for -L option", + option_arg.str().c_str()); + } break; + case 'D': { + bool value, success; + value = OptionArgParser::ToBoolean(option_arg, false, &success); + if (success) { + m_permissions.SetAllowDelete(value); + } else + error.SetErrorStringWithFormat( + "invalid boolean value '%s' passed for -L option", + option_arg.str().c_str()); + } break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + } + + const BreakpointName::Permissions &GetPermissions() const + { + return m_permissions; + } + BreakpointName::Permissions m_permissions; +}; + +class CommandObjectBreakpointNameConfigure : public CommandObjectParsed { +public: + CommandObjectBreakpointNameConfigure(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "configure", "Configure the options for the breakpoint" + " name provided. " + "If you provide a breakpoint id, the options will be copied from " + "the breakpoint, otherwise only the options specified will be set " + "on the name.", + "breakpoint name configure <command-options> " + "<breakpoint-name-list>"), + m_bp_opts(), m_option_group() { + // Create the first variant for the first (and only) argument for this + // command. + CommandArgumentEntry arg1; + CommandArgumentData id_arg; + id_arg.arg_type = eArgTypeBreakpointName; + id_arg.arg_repetition = eArgRepeatOptional; + arg1.push_back(id_arg); + m_arguments.push_back(arg1); + + m_option_group.Append(&m_bp_opts, + LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1); + m_option_group.Append(&m_access_options, + LLDB_OPT_SET_ALL, + LLDB_OPT_SET_ALL); + m_option_group.Append(&m_bp_id, + LLDB_OPT_SET_2|LLDB_OPT_SET_4, + LLDB_OPT_SET_ALL); + m_option_group.Finalize(); + } + + ~CommandObjectBreakpointNameConfigure() override = default; + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + + const size_t argc = command.GetArgumentCount(); + if (argc == 0) { + result.AppendError("No names provided."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Target &target = GetSelectedOrDummyTarget(false); + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + // Make a pass through first to see that all the names are legal. + for (auto &entry : command.entries()) { + Status error; + if (!BreakpointID::StringIsBreakpointName(entry.ref(), error)) + { + result.AppendErrorWithFormat("Invalid breakpoint name: %s - %s", + entry.c_str(), error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + // Now configure them, we already pre-checked the names so we don't need to + // check the error: + BreakpointSP bp_sp; + if (m_bp_id.m_breakpoint.OptionWasSet()) + { + lldb::break_id_t bp_id = m_bp_id.m_breakpoint.GetUInt64Value(); + bp_sp = target.GetBreakpointByID(bp_id); + if (!bp_sp) + { + result.AppendErrorWithFormatv("Could not find specified breakpoint {0}", + bp_id); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + Status error; + for (auto &entry : command.entries()) { + ConstString name(entry.c_str()); + BreakpointName *bp_name = target.FindBreakpointName(name, true, error); + if (!bp_name) + continue; + if (m_bp_id.m_help_string.OptionWasSet()) + bp_name->SetHelp(m_bp_id.m_help_string.GetStringValue().str().c_str()); + + if (bp_sp) + target.ConfigureBreakpointName(*bp_name, *bp_sp->GetOptions(), + m_access_options.GetPermissions()); + else + target.ConfigureBreakpointName(*bp_name, + m_bp_opts.GetBreakpointOptions(), + m_access_options.GetPermissions()); + } + return true; + } + +private: + BreakpointNameOptionGroup m_bp_id; // Only using the id part of this. + BreakpointOptionGroup m_bp_opts; + BreakpointAccessOptionGroup m_access_options; + OptionGroupOptions m_option_group; +}; + +class CommandObjectBreakpointNameAdd : public CommandObjectParsed { +public: + CommandObjectBreakpointNameAdd(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "add", "Add a name to the breakpoints provided.", + "breakpoint name add <command-options> <breakpoint-id-list>"), + m_name_options(), m_option_group() { + // Create the first variant for the first (and only) argument for this + // command. + CommandArgumentEntry arg1; + CommandArgumentData id_arg; + id_arg.arg_type = eArgTypeBreakpointID; + id_arg.arg_repetition = eArgRepeatOptional; + arg1.push_back(id_arg); + m_arguments.push_back(arg1); + + m_option_group.Append(&m_name_options, LLDB_OPT_SET_1, LLDB_OPT_SET_ALL); + m_option_group.Finalize(); + } + + ~CommandObjectBreakpointNameAdd() override = default; + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (!m_name_options.m_name.OptionWasSet()) { + result.SetError("No name option provided."); + return false; + } + + Target &target = + GetSelectedOrDummyTarget(m_name_options.m_use_dummy.GetCurrentValue()); + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + const BreakpointList &breakpoints = target.GetBreakpointList(); + + size_t num_breakpoints = breakpoints.GetSize(); + if (num_breakpoints == 0) { + result.SetError("No breakpoints, cannot add names."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Particular breakpoint selected; disable that breakpoint. + BreakpointIDList valid_bp_ids; + CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::listPerm); + + if (result.Succeeded()) { + if (valid_bp_ids.GetSize() == 0) { + result.SetError("No breakpoints specified, cannot add names."); + result.SetStatus(eReturnStatusFailed); + return false; + } + size_t num_valid_ids = valid_bp_ids.GetSize(); + const char *bp_name = m_name_options.m_name.GetCurrentValue(); + Status error; // This error reports illegal names, but we've already + // checked that, so we don't need to check it again here. + for (size_t index = 0; index < num_valid_ids; index++) { + lldb::break_id_t bp_id = + valid_bp_ids.GetBreakpointIDAtIndex(index).GetBreakpointID(); + BreakpointSP bp_sp = breakpoints.FindBreakpointByID(bp_id); + target.AddNameToBreakpoint(bp_sp, bp_name, error); + } + } + + return true; + } + +private: + BreakpointNameOptionGroup m_name_options; + OptionGroupOptions m_option_group; +}; + +class CommandObjectBreakpointNameDelete : public CommandObjectParsed { +public: + CommandObjectBreakpointNameDelete(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "delete", + "Delete a name from the breakpoints provided.", + "breakpoint name delete <command-options> <breakpoint-id-list>"), + m_name_options(), m_option_group() { + // Create the first variant for the first (and only) argument for this + // command. + CommandArgumentEntry arg1; + CommandArgumentData id_arg; + id_arg.arg_type = eArgTypeBreakpointID; + id_arg.arg_repetition = eArgRepeatOptional; + arg1.push_back(id_arg); + m_arguments.push_back(arg1); + + m_option_group.Append(&m_name_options, LLDB_OPT_SET_1, LLDB_OPT_SET_ALL); + m_option_group.Finalize(); + } + + ~CommandObjectBreakpointNameDelete() override = default; + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (!m_name_options.m_name.OptionWasSet()) { + result.SetError("No name option provided."); + return false; + } + + Target &target = + GetSelectedOrDummyTarget(m_name_options.m_use_dummy.GetCurrentValue()); + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + const BreakpointList &breakpoints = target.GetBreakpointList(); + + size_t num_breakpoints = breakpoints.GetSize(); + if (num_breakpoints == 0) { + result.SetError("No breakpoints, cannot delete names."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Particular breakpoint selected; disable that breakpoint. + BreakpointIDList valid_bp_ids; + CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::deletePerm); + + if (result.Succeeded()) { + if (valid_bp_ids.GetSize() == 0) { + result.SetError("No breakpoints specified, cannot delete names."); + result.SetStatus(eReturnStatusFailed); + return false; + } + ConstString bp_name(m_name_options.m_name.GetCurrentValue()); + size_t num_valid_ids = valid_bp_ids.GetSize(); + for (size_t index = 0; index < num_valid_ids; index++) { + lldb::break_id_t bp_id = + valid_bp_ids.GetBreakpointIDAtIndex(index).GetBreakpointID(); + BreakpointSP bp_sp = breakpoints.FindBreakpointByID(bp_id); + target.RemoveNameFromBreakpoint(bp_sp, bp_name); + } + } + + return true; + } + +private: + BreakpointNameOptionGroup m_name_options; + OptionGroupOptions m_option_group; +}; + +class CommandObjectBreakpointNameList : public CommandObjectParsed { +public: + CommandObjectBreakpointNameList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "list", + "List either the names for a breakpoint or info " + "about a given name. With no arguments, lists all " + "names", + "breakpoint name list <command-options>"), + m_name_options(), m_option_group() { + m_option_group.Append(&m_name_options, LLDB_OPT_SET_3, LLDB_OPT_SET_ALL); + m_option_group.Finalize(); + } + + ~CommandObjectBreakpointNameList() override = default; + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = + GetSelectedOrDummyTarget(m_name_options.m_use_dummy.GetCurrentValue()); + + std::vector<std::string> name_list; + if (command.empty()) { + target.GetBreakpointNames(name_list); + } else { + for (const Args::ArgEntry &arg : command) + { + name_list.push_back(arg.c_str()); + } + } + + if (name_list.empty()) { + result.AppendMessage("No breakpoint names found."); + } else { + for (const std::string &name_str : name_list) { + const char *name = name_str.c_str(); + // First print out the options for the name: + Status error; + BreakpointName *bp_name = + target.FindBreakpointName(ConstString(name), false, error); + if (bp_name) + { + StreamString s; + result.AppendMessageWithFormat("Name: %s\n", name); + if (bp_name->GetDescription(&s, eDescriptionLevelFull)) + { + result.AppendMessage(s.GetString()); + } + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + BreakpointList &breakpoints = target.GetBreakpointList(); + bool any_set = false; + for (BreakpointSP bp_sp : breakpoints.Breakpoints()) { + if (bp_sp->MatchesName(name)) { + StreamString s; + any_set = true; + bp_sp->GetDescription(&s, eDescriptionLevelBrief); + s.EOL(); + result.AppendMessage(s.GetString()); + } + } + if (!any_set) + result.AppendMessage("No breakpoints using this name."); + } else { + result.AppendMessageWithFormat("Name: %s not found.\n", name); + } + } + } + return true; + } + +private: + BreakpointNameOptionGroup m_name_options; + OptionGroupOptions m_option_group; +}; + +// CommandObjectBreakpointName +class CommandObjectBreakpointName : public CommandObjectMultiword { +public: + CommandObjectBreakpointName(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "name", "Commands to manage name tags for breakpoints", + "breakpoint name <subcommand> [<command-options>]") { + CommandObjectSP add_command_object( + new CommandObjectBreakpointNameAdd(interpreter)); + CommandObjectSP delete_command_object( + new CommandObjectBreakpointNameDelete(interpreter)); + CommandObjectSP list_command_object( + new CommandObjectBreakpointNameList(interpreter)); + CommandObjectSP configure_command_object( + new CommandObjectBreakpointNameConfigure(interpreter)); + + LoadSubCommand("add", add_command_object); + LoadSubCommand("delete", delete_command_object); + LoadSubCommand("list", list_command_object); + LoadSubCommand("configure", configure_command_object); + } + + ~CommandObjectBreakpointName() override = default; +}; + +// CommandObjectBreakpointRead +#pragma mark Read::CommandOptions +#define LLDB_OPTIONS_breakpoint_read +#include "CommandOptions.inc" + +#pragma mark Read + +class CommandObjectBreakpointRead : public CommandObjectParsed { +public: + CommandObjectBreakpointRead(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "breakpoint read", + "Read and set the breakpoints previously saved to " + "a file with \"breakpoint write\". ", + nullptr), + m_options() { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeBreakpointID, + eArgTypeBreakpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointRead() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + m_filename.assign(option_arg); + break; + case 'N': { + Status name_error; + if (!BreakpointID::StringIsBreakpointName(llvm::StringRef(option_arg), + name_error)) { + error.SetErrorStringWithFormat("Invalid breakpoint name: %s", + name_error.AsCString()); + } + m_names.push_back(option_arg); + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_filename.clear(); + m_names.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_read_options); + } + + // Instance variables to hold the values for command options. + + std::string m_filename; + std::vector<std::string> m_names; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + FileSpec input_spec(m_options.m_filename); + FileSystem::Instance().Resolve(input_spec); + BreakpointIDList new_bps; + Status error = target.CreateBreakpointsFromFile(input_spec, + m_options.m_names, new_bps); + + if (!error.Success()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Stream &output_stream = result.GetOutputStream(); + + size_t num_breakpoints = new_bps.GetSize(); + if (num_breakpoints == 0) { + result.AppendMessage("No breakpoints added."); + } else { + // No breakpoint selected; show info about all currently set breakpoints. + result.AppendMessage("New breakpoints:"); + for (size_t i = 0; i < num_breakpoints; ++i) { + BreakpointID bp_id = new_bps.GetBreakpointIDAtIndex(i); + Breakpoint *bp = target.GetBreakpointList() + .FindBreakpointByID(bp_id.GetBreakpointID()) + .get(); + if (bp) + bp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + false); + } + } + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectBreakpointWrite +#pragma mark Write::CommandOptions +#define LLDB_OPTIONS_breakpoint_write +#include "CommandOptions.inc" + +#pragma mark Write +class CommandObjectBreakpointWrite : public CommandObjectParsed { +public: + CommandObjectBreakpointWrite(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "breakpoint write", + "Write the breakpoints listed to a file that can " + "be read in with \"breakpoint read\". " + "If given no arguments, writes all breakpoints.", + nullptr), + m_options() { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeBreakpointID, + eArgTypeBreakpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointWrite() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + m_filename.assign(option_arg); + break; + case 'a': + m_append = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_filename.clear(); + m_append = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_write_options); + } + + // Instance variables to hold the values for command options. + + std::string m_filename; + bool m_append = false; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + + std::unique_lock<std::recursive_mutex> lock; + target.GetBreakpointList().GetListMutex(lock); + + BreakpointIDList valid_bp_ids; + if (!command.empty()) { + CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::listPerm); + + if (!result.Succeeded()) { + result.SetStatus(eReturnStatusFailed); + return false; + } + } + FileSpec file_spec(m_options.m_filename); + FileSystem::Instance().Resolve(file_spec); + Status error = target.SerializeBreakpointsToFile(file_spec, valid_bp_ids, + m_options.m_append); + if (!error.Success()) { + result.AppendErrorWithFormat("error serializing breakpoints: %s.", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectMultiwordBreakpoint +#pragma mark MultiwordBreakpoint + +CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint( + CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "breakpoint", + "Commands for operating on breakpoints (see 'help b' for shorthand.)", + "breakpoint <subcommand> [<command-options>]") { + CommandObjectSP list_command_object( + new CommandObjectBreakpointList(interpreter)); + CommandObjectSP enable_command_object( + new CommandObjectBreakpointEnable(interpreter)); + CommandObjectSP disable_command_object( + new CommandObjectBreakpointDisable(interpreter)); + CommandObjectSP clear_command_object( + new CommandObjectBreakpointClear(interpreter)); + CommandObjectSP delete_command_object( + new CommandObjectBreakpointDelete(interpreter)); + CommandObjectSP set_command_object( + new CommandObjectBreakpointSet(interpreter)); + CommandObjectSP command_command_object( + new CommandObjectBreakpointCommand(interpreter)); + CommandObjectSP modify_command_object( + new CommandObjectBreakpointModify(interpreter)); + CommandObjectSP name_command_object( + new CommandObjectBreakpointName(interpreter)); + CommandObjectSP write_command_object( + new CommandObjectBreakpointWrite(interpreter)); + CommandObjectSP read_command_object( + new CommandObjectBreakpointRead(interpreter)); + + list_command_object->SetCommandName("breakpoint list"); + enable_command_object->SetCommandName("breakpoint enable"); + disable_command_object->SetCommandName("breakpoint disable"); + clear_command_object->SetCommandName("breakpoint clear"); + delete_command_object->SetCommandName("breakpoint delete"); + set_command_object->SetCommandName("breakpoint set"); + command_command_object->SetCommandName("breakpoint command"); + modify_command_object->SetCommandName("breakpoint modify"); + name_command_object->SetCommandName("breakpoint name"); + write_command_object->SetCommandName("breakpoint write"); + read_command_object->SetCommandName("breakpoint read"); + + LoadSubCommand("list", list_command_object); + LoadSubCommand("enable", enable_command_object); + LoadSubCommand("disable", disable_command_object); + LoadSubCommand("clear", clear_command_object); + LoadSubCommand("delete", delete_command_object); + LoadSubCommand("set", set_command_object); + LoadSubCommand("command", command_command_object); + LoadSubCommand("modify", modify_command_object); + LoadSubCommand("name", name_command_object); + LoadSubCommand("write", write_command_object); + LoadSubCommand("read", read_command_object); +} + +CommandObjectMultiwordBreakpoint::~CommandObjectMultiwordBreakpoint() = default; + +void CommandObjectMultiwordBreakpoint::VerifyIDs(Args &args, Target *target, + bool allow_locations, + CommandReturnObject &result, + BreakpointIDList *valid_ids, + BreakpointName::Permissions + ::PermissionKinds + purpose) { + // args can be strings representing 1). integers (for breakpoint ids) + // 2). the full breakpoint & location + // canonical representation + // 3). the word "to" or a hyphen, + // representing a range (in which case there + // had *better* be an entry both before & + // after of one of the first two types. + // 4). A breakpoint name + // If args is empty, we will use the last created breakpoint (if there is + // one.) + + Args temp_args; + + if (args.empty()) { + if (target->GetLastCreatedBreakpoint()) { + valid_ids->AddBreakpointID(BreakpointID( + target->GetLastCreatedBreakpoint()->GetID(), LLDB_INVALID_BREAK_ID)); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError( + "No breakpoint specified and no last created breakpoint."); + result.SetStatus(eReturnStatusFailed); + } + return; + } + + // Create a new Args variable to use; copy any non-breakpoint-id-ranges stuff + // directly from the old ARGS to the new TEMP_ARGS. Do not copy breakpoint + // id range strings over; instead generate a list of strings for all the + // breakpoint ids in the range, and shove all of those breakpoint id strings + // into TEMP_ARGS. + + BreakpointIDList::FindAndReplaceIDRanges(args, target, allow_locations, + purpose, result, temp_args); + + // NOW, convert the list of breakpoint id strings in TEMP_ARGS into an actual + // BreakpointIDList: + + valid_ids->InsertStringArray(temp_args.GetArgumentArrayRef(), result); + + // At this point, all of the breakpoint ids that the user passed in have + // been converted to breakpoint IDs and put into valid_ids. + + if (result.Succeeded()) { + // Now that we've converted everything from args into a list of breakpoint + // ids, go through our tentative list of breakpoint id's and verify that + // they correspond to valid/currently set breakpoints. + + const size_t count = valid_ids->GetSize(); + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = valid_ids->GetBreakpointIDAtIndex(i); + Breakpoint *breakpoint = + target->GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + if (breakpoint != nullptr) { + const size_t num_locations = breakpoint->GetNumLocations(); + if (static_cast<size_t>(cur_bp_id.GetLocationID()) > num_locations) { + StreamString id_str; + BreakpointID::GetCanonicalReference( + &id_str, cur_bp_id.GetBreakpointID(), cur_bp_id.GetLocationID()); + i = valid_ids->GetSize() + 1; + result.AppendErrorWithFormat( + "'%s' is not a currently valid breakpoint/location id.\n", + id_str.GetData()); + result.SetStatus(eReturnStatusFailed); + } + } else { + i = valid_ids->GetSize() + 1; + result.AppendErrorWithFormat( + "'%d' is not a currently valid breakpoint ID.\n", + cur_bp_id.GetBreakpointID()); + result.SetStatus(eReturnStatusFailed); + } + } + } +} diff --git a/lldb/source/Commands/CommandObjectBreakpoint.h b/lldb/source/Commands/CommandObjectBreakpoint.h new file mode 100644 index 0000000000000..cba1f3f774eee --- /dev/null +++ b/lldb/source/Commands/CommandObjectBreakpoint.h @@ -0,0 +1,60 @@ +//===-- CommandObjectBreakpoint.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_CommandObjectBreakpoint_h_ +#define liblldb_CommandObjectBreakpoint_h_ + + +#include <utility> +#include <vector> + +#include "lldb/lldb-private.h" +#include "lldb/Breakpoint/BreakpointName.h" +#include "lldb/Core/Address.h" +#include "lldb/Core/STLUtils.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" + + +namespace lldb_private { + +// CommandObjectMultiwordBreakpoint + +class CommandObjectMultiwordBreakpoint : public CommandObjectMultiword { +public: + CommandObjectMultiwordBreakpoint(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordBreakpoint() override; + + static void VerifyBreakpointOrLocationIDs(Args &args, Target *target, + CommandReturnObject &result, + BreakpointIDList *valid_ids, + BreakpointName::Permissions + ::PermissionKinds purpose) { + VerifyIDs(args, target, true, result, valid_ids, purpose); + } + + static void VerifyBreakpointIDs(Args &args, Target *target, + CommandReturnObject &result, + BreakpointIDList *valid_ids, + BreakpointName::Permissions::PermissionKinds + purpose) { + VerifyIDs(args, target, false, result, valid_ids, purpose); + } + +private: + static void VerifyIDs(Args &args, Target *target, bool allow_locations, + CommandReturnObject &result, + BreakpointIDList *valid_ids, + BreakpointName::Permissions::PermissionKinds + purpose); +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectBreakpoint_h_ diff --git a/lldb/source/Commands/CommandObjectBreakpointCommand.cpp b/lldb/source/Commands/CommandObjectBreakpointCommand.cpp new file mode 100644 index 0000000000000..a6bcd1d8dc32e --- /dev/null +++ b/lldb/source/Commands/CommandObjectBreakpointCommand.cpp @@ -0,0 +1,723 @@ +//===-- CommandObjectBreakpointCommand.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 "CommandObjectBreakpointCommand.h" +#include "CommandObjectBreakpoint.h" +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Breakpoint/BreakpointIDList.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Core/IOHandler.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/State.h" + +#include "llvm/ADT/STLExtras.h" + +using namespace lldb; +using namespace lldb_private; + +// FIXME: "script-type" needs to have its contents determined dynamically, so +// somebody can add a new scripting language to lldb and have it pickable here +// without having to change this enumeration by hand and rebuild lldb proper. +static constexpr OptionEnumValueElement g_script_option_enumeration[] = { + { + eScriptLanguageNone, + "command", + "Commands are in the lldb command interpreter language", + }, + { + eScriptLanguagePython, + "python", + "Commands are in the Python language.", + }, + { + eSortOrderByName, + "default-script", + "Commands are in the default scripting language.", + }, +}; + +static constexpr OptionEnumValues ScriptOptionEnum() { + return OptionEnumValues(g_script_option_enumeration); +} + +#define LLDB_OPTIONS_breakpoint_command_add +#include "CommandOptions.inc" + +class CommandObjectBreakpointCommandAdd : public CommandObjectParsed, + public IOHandlerDelegateMultiline { +public: + CommandObjectBreakpointCommandAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "add", + "Add LLDB commands to a breakpoint, to be executed " + "whenever the breakpoint is hit." + " If no breakpoint is specified, adds the " + "commands to the last created breakpoint.", + nullptr), + IOHandlerDelegateMultiline("DONE", + IOHandlerDelegate::Completion::LLDBCommand), + m_options() { + SetHelpLong( + R"( +General information about entering breakpoint commands +------------------------------------------------------ + +)" + "This command will prompt for commands to be executed when the specified \ +breakpoint is hit. Each command is typed on its own line following the '> ' \ +prompt until 'DONE' is entered." + R"( + +)" + "Syntactic errors may not be detected when initially entered, and many \ +malformed commands can silently fail when executed. If your breakpoint commands \ +do not appear to be executing, double-check the command syntax." + R"( + +)" + "Note: You may enter any debugger command exactly as you would at the debugger \ +prompt. There is no limit to the number of commands supplied, but do NOT enter \ +more than one command per line." + R"( + +Special information about PYTHON breakpoint commands +---------------------------------------------------- + +)" + "You may enter either one or more lines of Python, including function \ +definitions or calls to functions that will have been imported by the time \ +the code executes. Single line breakpoint commands will be interpreted 'as is' \ +when the breakpoint is hit. Multiple lines of Python will be wrapped in a \ +generated function, and a call to the function will be attached to the breakpoint." + R"( + +This auto-generated function is passed in three arguments: + + frame: an lldb.SBFrame object for the frame which hit breakpoint. + + bp_loc: an lldb.SBBreakpointLocation object that represents the breakpoint location that was hit. + + dict: the python session dictionary hit. + +)" + "When specifying a python function with the --python-function option, you need \ +to supply the function name prepended by the module name:" + R"( + + --python-function myutils.breakpoint_callback + +The function itself must have the following prototype: + +def breakpoint_callback(frame, bp_loc, dict): + # Your code goes here + +)" + "The arguments are the same as the arguments passed to generated functions as \ +described above. Note that the global variable 'lldb.frame' will NOT be updated when \ +this function is called, so be sure to use the 'frame' argument. The 'frame' argument \ +can get you to the thread via frame.GetThread(), the thread can get you to the \ +process via thread.GetProcess(), and the process can get you back to the target \ +via process.GetTarget()." + R"( + +)" + "Important Note: As Python code gets collected into functions, access to global \ +variables requires explicit scoping using the 'global' keyword. Be sure to use correct \ +Python syntax, including indentation, when entering Python breakpoint commands." + R"( + +Example Python one-line breakpoint command: + +(lldb) breakpoint command add -s python 1 +Enter your Python command(s). Type 'DONE' to end. +> print "Hit this breakpoint!" +> DONE + +As a convenience, this also works for a short Python one-liner: + +(lldb) breakpoint command add -s python 1 -o 'import time; print time.asctime()' +(lldb) run +Launching '.../a.out' (x86_64) +(lldb) Fri Sep 10 12:17:45 2010 +Process 21778 Stopped +* thread #1: tid = 0x2e03, 0x0000000100000de8 a.out`c + 7 at main.c:39, stop reason = breakpoint 1.1, queue = com.apple.main-thread + 36 + 37 int c(int val) + 38 { + 39 -> return val + 3; + 40 } + 41 + 42 int main (int argc, char const *argv[]) + +Example multiple line Python breakpoint command: + +(lldb) breakpoint command add -s p 1 +Enter your Python command(s). Type 'DONE' to end. +> global bp_count +> bp_count = bp_count + 1 +> print "Hit this breakpoint " + repr(bp_count) + " times!" +> DONE + +Example multiple line Python breakpoint command, using function definition: + +(lldb) breakpoint command add -s python 1 +Enter your Python command(s). Type 'DONE' to end. +> def breakpoint_output (bp_no): +> out_string = "Hit breakpoint number " + repr (bp_no) +> print out_string +> return True +> breakpoint_output (1) +> DONE + +)" + "In this case, since there is a reference to a global variable, \ +'bp_count', you will also need to make sure 'bp_count' exists and is \ +initialized:" + R"( + +(lldb) script +>>> bp_count = 0 +>>> quit() + +)" + "Your Python code, however organized, can optionally return a value. \ +If the returned value is False, that tells LLDB not to stop at the breakpoint \ +to which the code is associated. Returning anything other than False, or even \ +returning None, or even omitting a return statement entirely, will cause \ +LLDB to stop." + R"( + +)" + "Final Note: A warning that no breakpoint command was generated when there \ +are no syntax errors may indicate that a function was declared but never called."); + + CommandArgumentEntry arg; + CommandArgumentData bp_id_arg; + + // Define the first (and only) variant of this arg. + bp_id_arg.arg_type = eArgTypeBreakpointID; + bp_id_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(bp_id_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointCommandAdd() override = default; + + Options *GetOptions() override { return &m_options; } + + void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { + StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + if (output_sp && interactive) { + output_sp->PutCString(g_reader_instructions); + output_sp->Flush(); + } + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &line) override { + io_handler.SetIsDone(true); + + std::vector<BreakpointOptions *> *bp_options_vec = + (std::vector<BreakpointOptions *> *)io_handler.GetUserData(); + for (BreakpointOptions *bp_options : *bp_options_vec) { + if (!bp_options) + continue; + + auto cmd_data = std::make_unique<BreakpointOptions::CommandData>(); + cmd_data->user_source.SplitIntoLines(line.c_str(), line.size()); + bp_options->SetCommandDataCallback(cmd_data); + } + } + + void CollectDataForBreakpointCommandCallback( + std::vector<BreakpointOptions *> &bp_options_vec, + CommandReturnObject &result) { + m_interpreter.GetLLDBCommandsFromIOHandler( + "> ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + &bp_options_vec); // Baton for the "io_handler" that will be passed back + // into our IOHandlerDelegate functions + } + + /// Set a one-liner as the callback for the breakpoint. + void + SetBreakpointCommandCallback(std::vector<BreakpointOptions *> &bp_options_vec, + const char *oneliner) { + for (auto bp_options : bp_options_vec) { + auto cmd_data = std::make_unique<BreakpointOptions::CommandData>(); + + cmd_data->user_source.AppendString(oneliner); + cmd_data->stop_on_error = m_options.m_stop_on_error; + + bp_options->SetCommandDataCallback(cmd_data); + } + } + + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_use_commands(false), m_use_script_language(false), + m_script_language(eScriptLanguageNone), m_use_one_liner(false), + m_one_liner(), m_function_name() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'o': + m_use_one_liner = true; + m_one_liner = option_arg; + break; + + case 's': + m_script_language = (lldb::ScriptLanguage)OptionArgParser::ToOptionEnum( + option_arg, + g_breakpoint_command_add_options[option_idx].enum_values, + eScriptLanguageNone, error); + + if (m_script_language == eScriptLanguagePython || + m_script_language == eScriptLanguageDefault) { + m_use_script_language = true; + } else { + m_use_script_language = false; + } + break; + + case 'e': { + bool success = false; + m_stop_on_error = + OptionArgParser::ToBoolean(option_arg, false, &success); + if (!success) + error.SetErrorStringWithFormat( + "invalid value for stop-on-error: \"%s\"", + option_arg.str().c_str()); + } break; + + case 'F': + m_use_one_liner = false; + m_use_script_language = true; + m_function_name.assign(option_arg); + break; + + case 'D': + m_use_dummy = true; + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_use_commands = true; + m_use_script_language = false; + m_script_language = eScriptLanguageNone; + + m_use_one_liner = false; + m_stop_on_error = true; + m_one_liner.clear(); + m_function_name.clear(); + m_use_dummy = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_command_add_options); + } + + // Instance variables to hold the values for command options. + + bool m_use_commands; + bool m_use_script_language; + lldb::ScriptLanguage m_script_language; + + // Instance variables to hold the values for one_liner options. + bool m_use_one_liner; + std::string m_one_liner; + bool m_stop_on_error; + std::string m_function_name; + bool m_use_dummy; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(m_options.m_use_dummy); + + const BreakpointList &breakpoints = target.GetBreakpointList(); + size_t num_breakpoints = breakpoints.GetSize(); + + if (num_breakpoints == 0) { + result.AppendError("No breakpoints exist to have commands added"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!m_options.m_use_script_language && + !m_options.m_function_name.empty()) { + result.AppendError("need to enable scripting to have a function run as a " + "breakpoint command"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + BreakpointIDList valid_bp_ids; + CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::listPerm); + + m_bp_options_vec.clear(); + + if (result.Succeeded()) { + const size_t count = valid_bp_ids.GetSize(); + + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex(i); + if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) { + Breakpoint *bp = + target.GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + BreakpointOptions *bp_options = nullptr; + if (cur_bp_id.GetLocationID() == LLDB_INVALID_BREAK_ID) { + // This breakpoint does not have an associated location. + bp_options = bp->GetOptions(); + } else { + BreakpointLocationSP bp_loc_sp( + bp->FindLocationByID(cur_bp_id.GetLocationID())); + // This breakpoint does have an associated location. Get its + // breakpoint options. + if (bp_loc_sp) + bp_options = bp_loc_sp->GetLocationOptions(); + } + if (bp_options) + m_bp_options_vec.push_back(bp_options); + } + } + + // If we are using script language, get the script interpreter in order + // to set or collect command callback. Otherwise, call the methods + // associated with this object. + if (m_options.m_use_script_language) { + ScriptInterpreter *script_interp = GetDebugger().GetScriptInterpreter(); + // Special handling for one-liner specified inline. + if (m_options.m_use_one_liner) { + script_interp->SetBreakpointCommandCallback( + m_bp_options_vec, m_options.m_one_liner.c_str()); + } else if (!m_options.m_function_name.empty()) { + script_interp->SetBreakpointCommandCallbackFunction( + m_bp_options_vec, m_options.m_function_name.c_str()); + } else { + script_interp->CollectDataForBreakpointCommandCallback( + m_bp_options_vec, result); + } + } else { + // Special handling for one-liner specified inline. + if (m_options.m_use_one_liner) + SetBreakpointCommandCallback(m_bp_options_vec, + m_options.m_one_liner.c_str()); + else + CollectDataForBreakpointCommandCallback(m_bp_options_vec, result); + } + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; + std::vector<BreakpointOptions *> m_bp_options_vec; // This stores the + // breakpoint options that + // we are currently + // collecting commands for. In the CollectData... calls we need to hand this + // off to the IOHandler, which may run asynchronously. So we have to have + // some way to keep it alive, and not leak it. Making it an ivar of the + // command object, which never goes away achieves this. Note that if we were + // able to run the same command concurrently in one interpreter we'd have to + // make this "per invocation". But there are many more reasons why it is not + // in general safe to do that in lldb at present, so it isn't worthwhile to + // come up with a more complex mechanism to address this particular weakness + // right now. + static const char *g_reader_instructions; +}; + +const char *CommandObjectBreakpointCommandAdd::g_reader_instructions = + "Enter your debugger command(s). Type 'DONE' to end.\n"; + +// CommandObjectBreakpointCommandDelete + +#define LLDB_OPTIONS_breakpoint_command_delete +#include "CommandOptions.inc" + +class CommandObjectBreakpointCommandDelete : public CommandObjectParsed { +public: + CommandObjectBreakpointCommandDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "delete", + "Delete the set of commands from a breakpoint.", + nullptr), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData bp_id_arg; + + // Define the first (and only) variant of this arg. + bp_id_arg.arg_type = eArgTypeBreakpointID; + bp_id_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(bp_id_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointCommandDelete() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_use_dummy(false) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'D': + m_use_dummy = true; + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_use_dummy = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_breakpoint_command_delete_options); + } + + // Instance variables to hold the values for command options. + bool m_use_dummy; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(m_options.m_use_dummy); + + const BreakpointList &breakpoints = target.GetBreakpointList(); + size_t num_breakpoints = breakpoints.GetSize(); + + if (num_breakpoints == 0) { + result.AppendError("No breakpoints exist to have commands deleted"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.empty()) { + result.AppendError( + "No breakpoint specified from which to delete the commands"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + BreakpointIDList valid_bp_ids; + CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs( + command, &target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::listPerm); + + if (result.Succeeded()) { + const size_t count = valid_bp_ids.GetSize(); + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex(i); + if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) { + Breakpoint *bp = + target.GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) { + BreakpointLocationSP bp_loc_sp( + bp->FindLocationByID(cur_bp_id.GetLocationID())); + if (bp_loc_sp) + bp_loc_sp->ClearCallback(); + else { + result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", + cur_bp_id.GetBreakpointID(), + cur_bp_id.GetLocationID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + bp->ClearCallback(); + } + } + } + } + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectBreakpointCommandList + +class CommandObjectBreakpointCommandList : public CommandObjectParsed { +public: + CommandObjectBreakpointCommandList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "list", + "List the script or set of commands to be " + "executed when the breakpoint is hit.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg; + CommandArgumentData bp_id_arg; + + // Define the first (and only) variant of this arg. + bp_id_arg.arg_type = eArgTypeBreakpointID; + bp_id_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(bp_id_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectBreakpointCommandList() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + + const BreakpointList &breakpoints = target->GetBreakpointList(); + size_t num_breakpoints = breakpoints.GetSize(); + + if (num_breakpoints == 0) { + result.AppendError("No breakpoints exist for which to list commands"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.empty()) { + result.AppendError( + "No breakpoint specified for which to list the commands"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + BreakpointIDList valid_bp_ids; + CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs( + command, target, result, &valid_bp_ids, + BreakpointName::Permissions::PermissionKinds::listPerm); + + if (result.Succeeded()) { + const size_t count = valid_bp_ids.GetSize(); + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex(i); + if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) { + Breakpoint *bp = + target->GetBreakpointByID(cur_bp_id.GetBreakpointID()).get(); + + if (bp) { + BreakpointLocationSP bp_loc_sp; + if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) { + bp_loc_sp = bp->FindLocationByID(cur_bp_id.GetLocationID()); + if (!bp_loc_sp) + { + result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", + cur_bp_id.GetBreakpointID(), + cur_bp_id.GetLocationID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + StreamString id_str; + BreakpointID::GetCanonicalReference(&id_str, + cur_bp_id.GetBreakpointID(), + cur_bp_id.GetLocationID()); + const Baton *baton = nullptr; + if (bp_loc_sp) + baton = bp_loc_sp + ->GetOptionsSpecifyingKind(BreakpointOptions::eCallback) + ->GetBaton(); + else + baton = bp->GetOptions()->GetBaton(); + + if (baton) { + result.GetOutputStream().Printf("Breakpoint %s:\n", + id_str.GetData()); + result.GetOutputStream().IndentMore(); + baton->GetDescription(&result.GetOutputStream(), + eDescriptionLevelFull); + result.GetOutputStream().IndentLess(); + } else { + result.AppendMessageWithFormat( + "Breakpoint %s does not have an associated command.\n", + id_str.GetData()); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Invalid breakpoint ID: %u.\n", + cur_bp_id.GetBreakpointID()); + result.SetStatus(eReturnStatusFailed); + } + } + } + + return result.Succeeded(); + } +}; + +// CommandObjectBreakpointCommand + +CommandObjectBreakpointCommand::CommandObjectBreakpointCommand( + CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "command", "Commands for adding, removing and listing " + "LLDB commands executed when a breakpoint is " + "hit.", + "command <sub-command> [<sub-command-options>] <breakpoint-id>") { + CommandObjectSP add_command_object( + new CommandObjectBreakpointCommandAdd(interpreter)); + CommandObjectSP delete_command_object( + new CommandObjectBreakpointCommandDelete(interpreter)); + CommandObjectSP list_command_object( + new CommandObjectBreakpointCommandList(interpreter)); + + add_command_object->SetCommandName("breakpoint command add"); + delete_command_object->SetCommandName("breakpoint command delete"); + list_command_object->SetCommandName("breakpoint command list"); + + LoadSubCommand("add", add_command_object); + LoadSubCommand("delete", delete_command_object); + LoadSubCommand("list", list_command_object); +} + +CommandObjectBreakpointCommand::~CommandObjectBreakpointCommand() = default; diff --git a/lldb/source/Commands/CommandObjectBreakpointCommand.h b/lldb/source/Commands/CommandObjectBreakpointCommand.h new file mode 100644 index 0000000000000..b18e003368bed --- /dev/null +++ b/lldb/source/Commands/CommandObjectBreakpointCommand.h @@ -0,0 +1,33 @@ +//===-- CommandObjectBreakpointCommand.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_CommandObjectBreakpointCommand_h_ +#define liblldb_CommandObjectBreakpointCommand_h_ + + + +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/lldb-types.h" + +namespace lldb_private { + +// CommandObjectMultiwordBreakpoint + +class CommandObjectBreakpointCommand : public CommandObjectMultiword { +public: + CommandObjectBreakpointCommand(CommandInterpreter &interpreter); + + ~CommandObjectBreakpointCommand() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectBreakpointCommand_h_ diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp new file mode 100644 index 0000000000000..259affbe6e0ac --- /dev/null +++ b/lldb/source/Commands/CommandObjectCommands.cpp @@ -0,0 +1,1872 @@ +//===-- CommandObjectCommands.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 "llvm/ADT/StringRef.h" + +#include "CommandObjectCommands.h" +#include "CommandObjectHelp.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/IOHandler.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandHistory.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandObjectRegexCommand.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionValueBoolean.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/OptionValueUInt64.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/StringList.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectCommandsSource + +#define LLDB_OPTIONS_history +#include "CommandOptions.inc" + +class CommandObjectCommandsHistory : public CommandObjectParsed { +public: + CommandObjectCommandsHistory(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "command history", + "Dump the history of commands in this session.\n" + "Commands in the history list can be run again " + "using \"!<INDEX>\". \"!-<OFFSET>\" will re-run " + "the command that is <OFFSET> commands from the end" + " of the list (counting the current command).", + nullptr), + m_options() {} + + ~CommandObjectCommandsHistory() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_start_idx(0), m_stop_idx(0), m_count(0), m_clear(false) { + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'c': + error = m_count.SetValueFromString(option_arg, eVarSetOperationAssign); + break; + case 's': + if (option_arg == "end") { + m_start_idx.SetCurrentValue(UINT64_MAX); + m_start_idx.SetOptionWasSet(); + } else + error = m_start_idx.SetValueFromString(option_arg, + eVarSetOperationAssign); + break; + case 'e': + error = + m_stop_idx.SetValueFromString(option_arg, eVarSetOperationAssign); + break; + case 'C': + m_clear.SetCurrentValue(true); + m_clear.SetOptionWasSet(); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_start_idx.Clear(); + m_stop_idx.Clear(); + m_count.Clear(); + m_clear.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_history_options); + } + + // Instance variables to hold the values for command options. + + OptionValueUInt64 m_start_idx; + OptionValueUInt64 m_stop_idx; + OptionValueUInt64 m_count; + OptionValueBoolean m_clear; + }; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (m_options.m_clear.GetCurrentValue() && + m_options.m_clear.OptionWasSet()) { + m_interpreter.GetCommandHistory().Clear(); + result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult); + } else { + if (m_options.m_start_idx.OptionWasSet() && + m_options.m_stop_idx.OptionWasSet() && + m_options.m_count.OptionWasSet()) { + result.AppendError("--count, --start-index and --end-index cannot be " + "all specified in the same invocation"); + result.SetStatus(lldb::eReturnStatusFailed); + } else { + std::pair<bool, uint64_t> start_idx( + m_options.m_start_idx.OptionWasSet(), + m_options.m_start_idx.GetCurrentValue()); + std::pair<bool, uint64_t> stop_idx( + m_options.m_stop_idx.OptionWasSet(), + m_options.m_stop_idx.GetCurrentValue()); + std::pair<bool, uint64_t> count(m_options.m_count.OptionWasSet(), + m_options.m_count.GetCurrentValue()); + + const CommandHistory &history(m_interpreter.GetCommandHistory()); + + if (start_idx.first && start_idx.second == UINT64_MAX) { + if (count.first) { + start_idx.second = history.GetSize() - count.second; + stop_idx.second = history.GetSize() - 1; + } else if (stop_idx.first) { + start_idx.second = stop_idx.second; + stop_idx.second = history.GetSize() - 1; + } else { + start_idx.second = 0; + stop_idx.second = history.GetSize() - 1; + } + } else { + if (!start_idx.first && !stop_idx.first && !count.first) { + start_idx.second = 0; + stop_idx.second = history.GetSize() - 1; + } else if (start_idx.first) { + if (count.first) { + stop_idx.second = start_idx.second + count.second - 1; + } else if (!stop_idx.first) { + stop_idx.second = history.GetSize() - 1; + } + } else if (stop_idx.first) { + if (count.first) { + if (stop_idx.second >= count.second) + start_idx.second = stop_idx.second - count.second + 1; + else + start_idx.second = 0; + } + } else /* if (count.first) */ + { + start_idx.second = 0; + stop_idx.second = count.second - 1; + } + } + history.Dump(result.GetOutputStream(), start_idx.second, + stop_idx.second); + } + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectCommandsSource + +#define LLDB_OPTIONS_source +#include "CommandOptions.inc" + +class CommandObjectCommandsSource : public CommandObjectParsed { +public: + CommandObjectCommandsSource(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "command source", + "Read and execute LLDB commands from the file <filename>.", + nullptr), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData file_arg; + + // Define the first (and only) variant of this arg. + file_arg.arg_type = eArgTypeFilename; + file_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(file_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectCommandsSource() override = default; + + const char *GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + return ""; + } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, + request, nullptr); + } + + Options *GetOptions() override { return &m_options; } + +protected: + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_stop_on_error(true), m_silent_run(false), + m_stop_on_continue(true) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'e': + error = m_stop_on_error.SetValueFromString(option_arg); + break; + + case 'c': + error = m_stop_on_continue.SetValueFromString(option_arg); + break; + + case 's': + error = m_silent_run.SetValueFromString(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_stop_on_error.Clear(); + m_silent_run.Clear(); + m_stop_on_continue.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_source_options); + } + + // Instance variables to hold the values for command options. + + OptionValueBoolean m_stop_on_error; + OptionValueBoolean m_silent_run; + OptionValueBoolean m_stop_on_continue; + }; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.GetArgumentCount() != 1) { + result.AppendErrorWithFormat( + "'%s' takes exactly one executable filename argument.\n", + GetCommandName().str().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + FileSpec cmd_file(command[0].ref()); + FileSystem::Instance().Resolve(cmd_file); + ExecutionContext *exe_ctx = nullptr; // Just use the default context. + + // If any options were set, then use them + if (m_options.m_stop_on_error.OptionWasSet() || + m_options.m_silent_run.OptionWasSet() || + m_options.m_stop_on_continue.OptionWasSet()) { + // Use user set settings + CommandInterpreterRunOptions options; + + if (m_options.m_stop_on_continue.OptionWasSet()) + options.SetStopOnContinue( + m_options.m_stop_on_continue.GetCurrentValue()); + + if (m_options.m_stop_on_error.OptionWasSet()) + options.SetStopOnError(m_options.m_stop_on_error.GetCurrentValue()); + + // Individual silent setting is override for global command echo settings. + if (m_options.m_silent_run.GetCurrentValue()) { + options.SetSilent(true); + } else { + options.SetPrintResults(true); + options.SetPrintErrors(true); + options.SetEchoCommands(m_interpreter.GetEchoCommands()); + options.SetEchoCommentCommands(m_interpreter.GetEchoCommentCommands()); + } + + m_interpreter.HandleCommandsFromFile(cmd_file, exe_ctx, options, result); + } else { + // No options were set, inherit any settings from nested "command source" + // commands, or set to sane default settings... + CommandInterpreterRunOptions options; + m_interpreter.HandleCommandsFromFile(cmd_file, exe_ctx, options, result); + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +#pragma mark CommandObjectCommandsAlias +// CommandObjectCommandsAlias + +#define LLDB_OPTIONS_alias +#include "CommandOptions.inc" + +static const char *g_python_command_instructions = + "Enter your Python command(s). Type 'DONE' to end.\n" + "You must define a Python function with this signature:\n" + "def my_command_impl(debugger, args, result, internal_dict):\n"; + +class CommandObjectCommandsAlias : public CommandObjectRaw { +protected: + class CommandOptions : public OptionGroup { + public: + CommandOptions() : OptionGroup(), m_help(), m_long_help() {} + + ~CommandOptions() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_alias_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + + const int short_option = GetDefinitions()[option_idx].short_option; + std::string option_str(option_value); + + switch (short_option) { + case 'h': + m_help.SetCurrentValue(option_str); + m_help.SetOptionWasSet(); + break; + + case 'H': + m_long_help.SetCurrentValue(option_str); + m_long_help.SetOptionWasSet(); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_help.Clear(); + m_long_help.Clear(); + } + + OptionValueString m_help; + OptionValueString m_long_help; + }; + + OptionGroupOptions m_option_group; + CommandOptions m_command_options; + +public: + Options *GetOptions() override { return &m_option_group; } + + CommandObjectCommandsAlias(CommandInterpreter &interpreter) + : CommandObjectRaw( + interpreter, "command alias", + "Define a custom command in terms of an existing command."), + m_option_group(), m_command_options() { + m_option_group.Append(&m_command_options); + m_option_group.Finalize(); + + SetHelpLong( + "'alias' allows the user to create a short-cut or abbreviation for long \ +commands, multi-word commands, and commands that take particular options. \ +Below are some simple examples of how one might use the 'alias' command:" + R"( + +(lldb) command alias sc script + + Creates the abbreviation 'sc' for the 'script' command. + +(lldb) command alias bp breakpoint + +)" + " Creates the abbreviation 'bp' for the 'breakpoint' command. Since \ +breakpoint commands are two-word commands, the user would still need to \ +enter the second word after 'bp', e.g. 'bp enable' or 'bp delete'." + R"( + +(lldb) command alias bpl breakpoint list + + Creates the abbreviation 'bpl' for the two-word command 'breakpoint list'. + +)" + "An alias can include some options for the command, with the values either \ +filled in at the time the alias is created, or specified as positional \ +arguments, to be filled in when the alias is invoked. The following example \ +shows how to create aliases with options:" + R"( + +(lldb) command alias bfl breakpoint set -f %1 -l %2 + +)" + " Creates the abbreviation 'bfl' (for break-file-line), with the -f and -l \ +options already part of the alias. So if the user wants to set a breakpoint \ +by file and line without explicitly having to use the -f and -l options, the \ +user can now use 'bfl' instead. The '%1' and '%2' are positional placeholders \ +for the actual arguments that will be passed when the alias command is used. \ +The number in the placeholder refers to the position/order the actual value \ +occupies when the alias is used. All the occurrences of '%1' in the alias \ +will be replaced with the first argument, all the occurrences of '%2' in the \ +alias will be replaced with the second argument, and so on. This also allows \ +actual arguments to be used multiple times within an alias (see 'process \ +launch' example below)." + R"( + +)" + "Note: the positional arguments must substitute as whole words in the resultant \ +command, so you can't at present do something like this to append the file extension \ +\".cpp\":" + R"( + +(lldb) command alias bcppfl breakpoint set -f %1.cpp -l %2 + +)" + "For more complex aliasing, use the \"command regex\" command instead. In the \ +'bfl' case above, the actual file value will be filled in with the first argument \ +following 'bfl' and the actual line number value will be filled in with the second \ +argument. The user would use this alias as follows:" + R"( + +(lldb) command alias bfl breakpoint set -f %1 -l %2 +(lldb) bfl my-file.c 137 + +This would be the same as if the user had entered 'breakpoint set -f my-file.c -l 137'. + +Another example: + +(lldb) command alias pltty process launch -s -o %1 -e %1 +(lldb) pltty /dev/tty0 + + Interpreted as 'process launch -s -o /dev/tty0 -e /dev/tty0' + +)" + "If the user always wanted to pass the same value to a particular option, the \ +alias could be defined with that value directly in the alias as a constant, \ +rather than using a positional placeholder:" + R"( + +(lldb) command alias bl3 breakpoint set -f %1 -l 3 + + Always sets a breakpoint on line 3 of whatever file is indicated.)"); + + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentEntry arg3; + CommandArgumentData alias_arg; + CommandArgumentData cmd_arg; + CommandArgumentData options_arg; + + // Define the first (and only) variant of this arg. + alias_arg.arg_type = eArgTypeAliasName; + alias_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(alias_arg); + + // Define the first (and only) variant of this arg. + cmd_arg.arg_type = eArgTypeCommandName; + cmd_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(cmd_arg); + + // Define the first (and only) variant of this arg. + options_arg.arg_type = eArgTypeAliasOptions; + options_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg3.push_back(options_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + m_arguments.push_back(arg3); + } + + ~CommandObjectCommandsAlias() override = default; + +protected: + bool DoExecute(llvm::StringRef raw_command_line, + CommandReturnObject &result) override { + if (raw_command_line.empty()) { + result.AppendError("'command alias' requires at least two arguments"); + return false; + } + + ExecutionContext exe_ctx = GetCommandInterpreter().GetExecutionContext(); + m_option_group.NotifyOptionParsingStarting(&exe_ctx); + + OptionsWithRaw args_with_suffix(raw_command_line); + const char *remainder = args_with_suffix.GetRawPart().c_str(); + + if (args_with_suffix.HasArgs()) + if (!ParseOptionsAndNotify(args_with_suffix.GetArgs(), result, + m_option_group, exe_ctx)) + return false; + + llvm::StringRef raw_command_string(remainder); + Args args(raw_command_string); + + if (args.GetArgumentCount() < 2) { + result.AppendError("'command alias' requires at least two arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Get the alias command. + + auto alias_command = args[0].ref(); + if (alias_command.startswith("-")) { + result.AppendError("aliases starting with a dash are not supported"); + if (alias_command == "--help" || alias_command == "--long-help") { + result.AppendWarning("if trying to pass options to 'command alias' add " + "a -- at the end of the options"); + } + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Strip the new alias name off 'raw_command_string' (leave it on args, + // which gets passed to 'Execute', which does the stripping itself. + size_t pos = raw_command_string.find(alias_command); + if (pos == 0) { + raw_command_string = raw_command_string.substr(alias_command.size()); + pos = raw_command_string.find_first_not_of(' '); + if ((pos != std::string::npos) && (pos > 0)) + raw_command_string = raw_command_string.substr(pos); + } else { + result.AppendError("Error parsing command string. No alias created."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Verify that the command is alias-able. + if (m_interpreter.CommandExists(alias_command)) { + result.AppendErrorWithFormat( + "'%s' is a permanent debugger command and cannot be redefined.\n", + args[0].c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Get CommandObject that is being aliased. The command name is read from + // the front of raw_command_string. raw_command_string is returned with the + // name of the command object stripped off the front. + llvm::StringRef original_raw_command_string = raw_command_string; + CommandObject *cmd_obj = + m_interpreter.GetCommandObjectForCommand(raw_command_string); + + if (!cmd_obj) { + result.AppendErrorWithFormat("invalid command given to 'command alias'. " + "'%s' does not begin with a valid command." + " No alias created.", + original_raw_command_string.str().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!cmd_obj->WantsRawCommandString()) { + // Note that args was initialized with the original command, and has not + // been updated to this point. Therefore can we pass it to the version of + // Execute that does not need/expect raw input in the alias. + return HandleAliasingNormalCommand(args, result); + } else { + return HandleAliasingRawCommand(alias_command, raw_command_string, + *cmd_obj, result); + } + return result.Succeeded(); + } + + bool HandleAliasingRawCommand(llvm::StringRef alias_command, + llvm::StringRef raw_command_string, + CommandObject &cmd_obj, + CommandReturnObject &result) { + // Verify & handle any options/arguments passed to the alias command + + OptionArgVectorSP option_arg_vector_sp = + OptionArgVectorSP(new OptionArgVector); + + if (CommandObjectSP cmd_obj_sp = + m_interpreter.GetCommandSPExact(cmd_obj.GetCommandName(), false)) { + if (m_interpreter.AliasExists(alias_command) || + m_interpreter.UserCommandExists(alias_command)) { + result.AppendWarningWithFormat( + "Overwriting existing definition for '%s'.\n", + alias_command.str().c_str()); + } + if (CommandAlias *alias = m_interpreter.AddAlias( + alias_command, cmd_obj_sp, raw_command_string)) { + if (m_command_options.m_help.OptionWasSet()) + alias->SetHelp(m_command_options.m_help.GetCurrentValue()); + if (m_command_options.m_long_help.OptionWasSet()) + alias->SetHelpLong(m_command_options.m_long_help.GetCurrentValue()); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("Unable to create requested alias.\n"); + result.SetStatus(eReturnStatusFailed); + } + + } else { + result.AppendError("Unable to create requested alias.\n"); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } + + bool HandleAliasingNormalCommand(Args &args, CommandReturnObject &result) { + size_t argc = args.GetArgumentCount(); + + if (argc < 2) { + result.AppendError("'command alias' requires at least two arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Save these in std::strings since we're going to shift them off. + const std::string alias_command(args[0].ref()); + const std::string actual_command(args[1].ref()); + + args.Shift(); // Shift the alias command word off the argument vector. + args.Shift(); // Shift the old command word off the argument vector. + + // Verify that the command is alias'able, and get the appropriate command + // object. + + if (m_interpreter.CommandExists(alias_command)) { + result.AppendErrorWithFormat( + "'%s' is a permanent debugger command and cannot be redefined.\n", + alias_command.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + CommandObjectSP command_obj_sp( + m_interpreter.GetCommandSPExact(actual_command, true)); + CommandObjectSP subcommand_obj_sp; + bool use_subcommand = false; + if (!command_obj_sp) { + result.AppendErrorWithFormat("'%s' is not an existing command.\n", + actual_command.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + CommandObject *cmd_obj = command_obj_sp.get(); + CommandObject *sub_cmd_obj = nullptr; + OptionArgVectorSP option_arg_vector_sp = + OptionArgVectorSP(new OptionArgVector); + + while (cmd_obj->IsMultiwordObject() && !args.empty()) { + auto sub_command = args[0].ref(); + assert(!sub_command.empty()); + subcommand_obj_sp = cmd_obj->GetSubcommandSP(sub_command); + if (!subcommand_obj_sp) { + result.AppendErrorWithFormat( + "'%s' is not a valid sub-command of '%s'. " + "Unable to create alias.\n", + args[0].c_str(), actual_command.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + sub_cmd_obj = subcommand_obj_sp.get(); + use_subcommand = true; + args.Shift(); // Shift the sub_command word off the argument vector. + cmd_obj = sub_cmd_obj; + } + + // Verify & handle any options/arguments passed to the alias command + + std::string args_string; + + if (!args.empty()) { + CommandObjectSP tmp_sp = + m_interpreter.GetCommandSPExact(cmd_obj->GetCommandName(), false); + if (use_subcommand) + tmp_sp = m_interpreter.GetCommandSPExact(sub_cmd_obj->GetCommandName(), + false); + + args.GetCommandString(args_string); + } + + if (m_interpreter.AliasExists(alias_command) || + m_interpreter.UserCommandExists(alias_command)) { + result.AppendWarningWithFormat( + "Overwriting existing definition for '%s'.\n", alias_command.c_str()); + } + + if (CommandAlias *alias = m_interpreter.AddAlias( + alias_command, use_subcommand ? subcommand_obj_sp : command_obj_sp, + args_string)) { + if (m_command_options.m_help.OptionWasSet()) + alias->SetHelp(m_command_options.m_help.GetCurrentValue()); + if (m_command_options.m_long_help.OptionWasSet()) + alias->SetHelpLong(m_command_options.m_long_help.GetCurrentValue()); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("Unable to create requested alias.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectCommandsUnalias +// CommandObjectCommandsUnalias + +class CommandObjectCommandsUnalias : public CommandObjectParsed { +public: + CommandObjectCommandsUnalias(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "command unalias", + "Delete one or more custom commands defined by 'command alias'.", + nullptr) { + CommandArgumentEntry arg; + CommandArgumentData alias_arg; + + // Define the first (and only) variant of this arg. + alias_arg.arg_type = eArgTypeAliasName; + alias_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(alias_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectCommandsUnalias() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + CommandObject::CommandMap::iterator pos; + CommandObject *cmd_obj; + + if (args.empty()) { + result.AppendError("must call 'unalias' with a valid alias"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + auto command_name = args[0].ref(); + cmd_obj = m_interpreter.GetCommandObject(command_name); + if (!cmd_obj) { + result.AppendErrorWithFormat( + "'%s' is not a known command.\nTry 'help' to see a " + "current list of commands.\n", + args[0].c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_interpreter.CommandExists(command_name)) { + if (cmd_obj->IsRemovable()) { + result.AppendErrorWithFormat( + "'%s' is not an alias, it is a debugger command which can be " + "removed using the 'command delete' command.\n", + args[0].c_str()); + } else { + result.AppendErrorWithFormat( + "'%s' is a permanent debugger command and cannot be removed.\n", + args[0].c_str()); + } + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!m_interpreter.RemoveAlias(command_name)) { + if (m_interpreter.AliasExists(command_name)) + result.AppendErrorWithFormat( + "Error occurred while attempting to unalias '%s'.\n", + args[0].c_str()); + else + result.AppendErrorWithFormat("'%s' is not an existing alias.\n", + args[0].c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectCommandsDelete +// CommandObjectCommandsDelete + +class CommandObjectCommandsDelete : public CommandObjectParsed { +public: + CommandObjectCommandsDelete(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "command delete", + "Delete one or more custom commands defined by 'command regex'.", + nullptr) { + CommandArgumentEntry arg; + CommandArgumentData alias_arg; + + // Define the first (and only) variant of this arg. + alias_arg.arg_type = eArgTypeCommandName; + alias_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(alias_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectCommandsDelete() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + CommandObject::CommandMap::iterator pos; + + if (args.empty()) { + result.AppendErrorWithFormat("must call '%s' with one or more valid user " + "defined regular expression command names", + GetCommandName().str().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + auto command_name = args[0].ref(); + if (!m_interpreter.CommandExists(command_name)) { + StreamString error_msg_stream; + const bool generate_upropos = true; + const bool generate_type_lookup = false; + CommandObjectHelp::GenerateAdditionalHelpAvenuesMessage( + &error_msg_stream, command_name, llvm::StringRef(), llvm::StringRef(), + generate_upropos, generate_type_lookup); + result.AppendError(error_msg_stream.GetString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!m_interpreter.RemoveCommand(command_name)) { + result.AppendErrorWithFormat( + "'%s' is a permanent debugger command and cannot be removed.\n", + args[0].c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } +}; + +// CommandObjectCommandsAddRegex + +#define LLDB_OPTIONS_regex +#include "CommandOptions.inc" + +#pragma mark CommandObjectCommandsAddRegex + +class CommandObjectCommandsAddRegex : public CommandObjectParsed, + public IOHandlerDelegateMultiline { +public: + CommandObjectCommandsAddRegex(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "command regex", "Define a custom command in terms of " + "existing commands by matching " + "regular expressions.", + "command regex <cmd-name> [s/<regex>/<subst>/ ...]"), + IOHandlerDelegateMultiline("", + IOHandlerDelegate::Completion::LLDBCommand), + m_options() { + SetHelpLong( + R"( +)" + "This command allows the user to create powerful regular expression commands \ +with substitutions. The regular expressions and substitutions are specified \ +using the regular expression substitution format of:" + R"( + + s/<regex>/<subst>/ + +)" + "<regex> is a regular expression that can use parenthesis to capture regular \ +expression input and substitute the captured matches in the output using %1 \ +for the first match, %2 for the second, and so on." + R"( + +)" + "The regular expressions can all be specified on the command line if more than \ +one argument is provided. If just the command name is provided on the command \ +line, then the regular expressions and substitutions can be entered on separate \ +lines, followed by an empty line to terminate the command definition." + R"( + +EXAMPLES + +)" + "The following example will define a regular expression command named 'f' that \ +will call 'finish' if there are no arguments, or 'frame select <frame-idx>' if \ +a number follows 'f':" + R"( + + (lldb) command regex f s/^$/finish/ 's/([0-9]+)/frame select %1/')"); + } + + ~CommandObjectCommandsAddRegex() override = default; + +protected: + void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { + StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + if (output_sp && interactive) { + output_sp->PutCString("Enter one or more sed substitution commands in " + "the form: 's/<regex>/<subst>/'.\nTerminate the " + "substitution list with an empty line.\n"); + output_sp->Flush(); + } + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &data) override { + io_handler.SetIsDone(true); + if (m_regex_cmd_up) { + StringList lines; + if (lines.SplitIntoLines(data)) { + bool check_only = false; + for (const std::string &line : lines) { + Status error = AppendRegexSubstitution(line, check_only); + if (error.Fail()) { + if (!GetDebugger().GetCommandInterpreter().GetBatchCommandMode()) { + StreamSP out_stream = GetDebugger().GetAsyncOutputStream(); + out_stream->Printf("error: %s\n", error.AsCString()); + } + } + } + } + if (m_regex_cmd_up->HasRegexEntries()) { + CommandObjectSP cmd_sp(m_regex_cmd_up.release()); + m_interpreter.AddCommand(cmd_sp->GetCommandName(), cmd_sp, true); + } + } + } + + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + if (argc == 0) { + result.AppendError("usage: 'command regex <command-name> " + "[s/<regex1>/<subst1>/ s/<regex2>/<subst2>/ ...]'\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Status error; + auto name = command[0].ref(); + m_regex_cmd_up = std::make_unique<CommandObjectRegexCommand>( + m_interpreter, name, m_options.GetHelp(), m_options.GetSyntax(), 10, 0, + true); + + if (argc == 1) { + Debugger &debugger = GetDebugger(); + bool color_prompt = debugger.GetUseColor(); + const bool multiple_lines = true; // Get multiple lines + IOHandlerSP io_handler_sp(new IOHandlerEditline( + debugger, IOHandler::Type::Other, + "lldb-regex", // Name of input reader for history + llvm::StringRef("> "), // Prompt + llvm::StringRef(), // Continuation prompt + multiple_lines, color_prompt, + 0, // Don't show line numbers + *this, nullptr)); + + if (io_handler_sp) { + debugger.PushIOHandler(io_handler_sp); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + } else { + for (auto &entry : command.entries().drop_front()) { + bool check_only = false; + error = AppendRegexSubstitution(entry.ref(), check_only); + if (error.Fail()) + break; + } + + if (error.Success()) { + AddRegexCommandToInterpreter(); + } + } + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } + + Status AppendRegexSubstitution(const llvm::StringRef ®ex_sed, + bool check_only) { + Status error; + + if (!m_regex_cmd_up) { + error.SetErrorStringWithFormat( + "invalid regular expression command object for: '%.*s'", + (int)regex_sed.size(), regex_sed.data()); + return error; + } + + size_t regex_sed_size = regex_sed.size(); + + if (regex_sed_size <= 1) { + error.SetErrorStringWithFormat( + "regular expression substitution string is too short: '%.*s'", + (int)regex_sed.size(), regex_sed.data()); + return error; + } + + if (regex_sed[0] != 's') { + error.SetErrorStringWithFormat("regular expression substitution string " + "doesn't start with 's': '%.*s'", + (int)regex_sed.size(), regex_sed.data()); + return error; + } + const size_t first_separator_char_pos = 1; + // use the char that follows 's' as the regex separator character so we can + // have "s/<regex>/<subst>/" or "s|<regex>|<subst>|" + const char separator_char = regex_sed[first_separator_char_pos]; + const size_t second_separator_char_pos = + regex_sed.find(separator_char, first_separator_char_pos + 1); + + if (second_separator_char_pos == std::string::npos) { + error.SetErrorStringWithFormat( + "missing second '%c' separator char after '%.*s' in '%.*s'", + separator_char, + (int)(regex_sed.size() - first_separator_char_pos - 1), + regex_sed.data() + (first_separator_char_pos + 1), + (int)regex_sed.size(), regex_sed.data()); + return error; + } + + const size_t third_separator_char_pos = + regex_sed.find(separator_char, second_separator_char_pos + 1); + + if (third_separator_char_pos == std::string::npos) { + error.SetErrorStringWithFormat( + "missing third '%c' separator char after '%.*s' in '%.*s'", + separator_char, + (int)(regex_sed.size() - second_separator_char_pos - 1), + regex_sed.data() + (second_separator_char_pos + 1), + (int)regex_sed.size(), regex_sed.data()); + return error; + } + + if (third_separator_char_pos != regex_sed_size - 1) { + // Make sure that everything that follows the last regex separator char + if (regex_sed.find_first_not_of("\t\n\v\f\r ", + third_separator_char_pos + 1) != + std::string::npos) { + error.SetErrorStringWithFormat( + "extra data found after the '%.*s' regular expression substitution " + "string: '%.*s'", + (int)third_separator_char_pos + 1, regex_sed.data(), + (int)(regex_sed.size() - third_separator_char_pos - 1), + regex_sed.data() + (third_separator_char_pos + 1)); + return error; + } + } else if (first_separator_char_pos + 1 == second_separator_char_pos) { + error.SetErrorStringWithFormat( + "<regex> can't be empty in 's%c<regex>%c<subst>%c' string: '%.*s'", + separator_char, separator_char, separator_char, (int)regex_sed.size(), + regex_sed.data()); + return error; + } else if (second_separator_char_pos + 1 == third_separator_char_pos) { + error.SetErrorStringWithFormat( + "<subst> can't be empty in 's%c<regex>%c<subst>%c' string: '%.*s'", + separator_char, separator_char, separator_char, (int)regex_sed.size(), + regex_sed.data()); + return error; + } + + if (!check_only) { + std::string regex(regex_sed.substr(first_separator_char_pos + 1, + second_separator_char_pos - + first_separator_char_pos - 1)); + std::string subst(regex_sed.substr(second_separator_char_pos + 1, + third_separator_char_pos - + second_separator_char_pos - 1)); + m_regex_cmd_up->AddRegexCommand(regex.c_str(), subst.c_str()); + } + return error; + } + + void AddRegexCommandToInterpreter() { + if (m_regex_cmd_up) { + if (m_regex_cmd_up->HasRegexEntries()) { + CommandObjectSP cmd_sp(m_regex_cmd_up.release()); + m_interpreter.AddCommand(cmd_sp->GetCommandName(), cmd_sp, true); + } + } + } + +private: + std::unique_ptr<CommandObjectRegexCommand> m_regex_cmd_up; + + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'h': + m_help.assign(option_arg); + break; + case 's': + m_syntax.assign(option_arg); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_help.clear(); + m_syntax.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_regex_options); + } + + // TODO: Convert these functions to return StringRefs. + const char *GetHelp() { + return (m_help.empty() ? nullptr : m_help.c_str()); + } + + const char *GetSyntax() { + return (m_syntax.empty() ? nullptr : m_syntax.c_str()); + } + + protected: + // Instance variables to hold the values for command options. + + std::string m_help; + std::string m_syntax; + }; + + Options *GetOptions() override { return &m_options; } + + CommandOptions m_options; +}; + +class CommandObjectPythonFunction : public CommandObjectRaw { +public: + CommandObjectPythonFunction(CommandInterpreter &interpreter, std::string name, + std::string funct, std::string help, + ScriptedCommandSynchronicity synch) + : CommandObjectRaw(interpreter, name), + m_function_name(funct), m_synchro(synch), m_fetched_help_long(false) { + if (!help.empty()) + SetHelp(help); + else { + StreamString stream; + stream.Printf("For more information run 'help %s'", name.c_str()); + SetHelp(stream.GetString()); + } + } + + ~CommandObjectPythonFunction() override = default; + + bool IsRemovable() const override { return true; } + + const std::string &GetFunctionName() { return m_function_name; } + + ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; } + + llvm::StringRef GetHelpLong() override { + if (m_fetched_help_long) + return CommandObjectRaw::GetHelpLong(); + + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + if (!scripter) + return CommandObjectRaw::GetHelpLong(); + + std::string docstring; + m_fetched_help_long = + scripter->GetDocumentationForItem(m_function_name.c_str(), docstring); + if (!docstring.empty()) + SetHelpLong(docstring); + return CommandObjectRaw::GetHelpLong(); + } + +protected: + bool DoExecute(llvm::StringRef raw_command_line, + CommandReturnObject &result) override { + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + + Status error; + + result.SetStatus(eReturnStatusInvalid); + + if (!scripter || + !scripter->RunScriptBasedCommand(m_function_name.c_str(), + raw_command_line, m_synchro, result, + error, m_exe_ctx)) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } else { + // Don't change the status if the command already set it... + if (result.GetStatus() == eReturnStatusInvalid) { + if (result.GetOutputData().empty()) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + } + } + + return result.Succeeded(); + } + +private: + std::string m_function_name; + ScriptedCommandSynchronicity m_synchro; + bool m_fetched_help_long; +}; + +class CommandObjectScriptingObject : public CommandObjectRaw { +public: + CommandObjectScriptingObject(CommandInterpreter &interpreter, + std::string name, + StructuredData::GenericSP cmd_obj_sp, + ScriptedCommandSynchronicity synch) + : CommandObjectRaw(interpreter, name), + m_cmd_obj_sp(cmd_obj_sp), m_synchro(synch), m_fetched_help_short(false), + m_fetched_help_long(false) { + StreamString stream; + stream.Printf("For more information run 'help %s'", name.c_str()); + SetHelp(stream.GetString()); + if (ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter()) + GetFlags().Set(scripter->GetFlagsForCommandObject(cmd_obj_sp)); + } + + ~CommandObjectScriptingObject() override = default; + + bool IsRemovable() const override { return true; } + + ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; } + + llvm::StringRef GetHelp() override { + if (m_fetched_help_short) + return CommandObjectRaw::GetHelp(); + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + if (!scripter) + return CommandObjectRaw::GetHelp(); + std::string docstring; + m_fetched_help_short = + scripter->GetShortHelpForCommandObject(m_cmd_obj_sp, docstring); + if (!docstring.empty()) + SetHelp(docstring); + + return CommandObjectRaw::GetHelp(); + } + + llvm::StringRef GetHelpLong() override { + if (m_fetched_help_long) + return CommandObjectRaw::GetHelpLong(); + + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + if (!scripter) + return CommandObjectRaw::GetHelpLong(); + + std::string docstring; + m_fetched_help_long = + scripter->GetLongHelpForCommandObject(m_cmd_obj_sp, docstring); + if (!docstring.empty()) + SetHelpLong(docstring); + return CommandObjectRaw::GetHelpLong(); + } + +protected: + bool DoExecute(llvm::StringRef raw_command_line, + CommandReturnObject &result) override { + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + + Status error; + + result.SetStatus(eReturnStatusInvalid); + + if (!scripter || + !scripter->RunScriptBasedCommand(m_cmd_obj_sp, raw_command_line, + m_synchro, result, error, m_exe_ctx)) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } else { + // Don't change the status if the command already set it... + if (result.GetStatus() == eReturnStatusInvalid) { + if (result.GetOutputData().empty()) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + } + } + + return result.Succeeded(); + } + +private: + StructuredData::GenericSP m_cmd_obj_sp; + ScriptedCommandSynchronicity m_synchro; + bool m_fetched_help_short : 1; + bool m_fetched_help_long : 1; +}; + +// CommandObjectCommandsScriptImport +#define LLDB_OPTIONS_script_import +#include "CommandOptions.inc" + +class CommandObjectCommandsScriptImport : public CommandObjectParsed { +public: + CommandObjectCommandsScriptImport(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "command script import", + "Import a scripting module in LLDB.", nullptr), + m_options() { + CommandArgumentEntry arg1; + CommandArgumentData cmd_arg; + + // Define the first (and only) variant of this arg. + cmd_arg.arg_type = eArgTypeFilename; + cmd_arg.arg_repetition = eArgRepeatPlus; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(cmd_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectCommandsScriptImport() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, + request, nullptr); + } + + Options *GetOptions() override { return &m_options; } + +protected: + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'r': + m_allow_reload = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_allow_reload = true; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_script_import_options); + } + + // Instance variables to hold the values for command options. + + bool m_allow_reload; + }; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (GetDebugger().GetScriptLanguage() != lldb::eScriptLanguagePython) { + result.AppendError("only scripting language supported for module " + "importing is currently Python"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.empty()) { + result.AppendError("command script import needs one or more arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + for (auto &entry : command.entries()) { + Status error; + + const bool init_session = true; + // FIXME: this is necessary because CommandObject::CheckRequirements() + // assumes that commands won't ever be recursively invoked, but it's + // actually possible to craft a Python script that does other "command + // script imports" in __lldb_init_module the real fix is to have + // recursive commands possible with a CommandInvocation object separate + // from the CommandObject itself, so that recursive command invocations + // won't stomp on each other (wrt to execution contents, options, and + // more) + m_exe_ctx.Clear(); + if (GetDebugger().GetScriptInterpreter()->LoadScriptingModule( + entry.c_str(), m_options.m_allow_reload, init_session, error)) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendErrorWithFormat("module importing failed: %s", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectCommandsScriptAdd +static constexpr OptionEnumValueElement g_script_synchro_type[] = { + { + eScriptedCommandSynchronicitySynchronous, + "synchronous", + "Run synchronous", + }, + { + eScriptedCommandSynchronicityAsynchronous, + "asynchronous", + "Run asynchronous", + }, + { + eScriptedCommandSynchronicityCurrentValue, + "current", + "Do not alter current setting", + }, +}; + +static constexpr OptionEnumValues ScriptSynchroType() { + return OptionEnumValues(g_script_synchro_type); +} + +#define LLDB_OPTIONS_script_add +#include "CommandOptions.inc" + +class CommandObjectCommandsScriptAdd : public CommandObjectParsed, + public IOHandlerDelegateMultiline { +public: + CommandObjectCommandsScriptAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "command script add", + "Add a scripted function as an LLDB command.", + nullptr), + IOHandlerDelegateMultiline("DONE"), m_options() { + CommandArgumentEntry arg1; + CommandArgumentData cmd_arg; + + // Define the first (and only) variant of this arg. + cmd_arg.arg_type = eArgTypeCommandName; + cmd_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(cmd_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectCommandsScriptAdd() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_class_name(), m_funct_name(), m_short_help(), + m_synchronicity(eScriptedCommandSynchronicitySynchronous) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + if (!option_arg.empty()) + m_funct_name = option_arg; + break; + case 'c': + if (!option_arg.empty()) + m_class_name = option_arg; + break; + case 'h': + if (!option_arg.empty()) + m_short_help = option_arg; + break; + case 's': + m_synchronicity = + (ScriptedCommandSynchronicity)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, 0, error); + if (!error.Success()) + error.SetErrorStringWithFormat( + "unrecognized value for synchronicity '%s'", + option_arg.str().c_str()); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_class_name.clear(); + m_funct_name.clear(); + m_short_help.clear(); + m_synchronicity = eScriptedCommandSynchronicitySynchronous; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_script_add_options); + } + + // Instance variables to hold the values for command options. + + std::string m_class_name; + std::string m_funct_name; + std::string m_short_help; + ScriptedCommandSynchronicity m_synchronicity; + }; + + void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { + StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + if (output_sp && interactive) { + output_sp->PutCString(g_python_command_instructions); + output_sp->Flush(); + } + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &data) override { + StreamFileSP error_sp = io_handler.GetErrorStreamFileSP(); + + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + if (interpreter) { + + StringList lines; + lines.SplitIntoLines(data); + if (lines.GetSize() > 0) { + std::string funct_name_str; + if (interpreter->GenerateScriptAliasFunction(lines, funct_name_str)) { + if (funct_name_str.empty()) { + error_sp->Printf("error: unable to obtain a function name, didn't " + "add python command.\n"); + error_sp->Flush(); + } else { + // everything should be fine now, let's add this alias + + CommandObjectSP command_obj_sp(new CommandObjectPythonFunction( + m_interpreter, m_cmd_name, funct_name_str, m_short_help, + m_synchronicity)); + + if (!m_interpreter.AddUserCommand(m_cmd_name, command_obj_sp, + true)) { + error_sp->Printf("error: unable to add selected command, didn't " + "add python command.\n"); + error_sp->Flush(); + } + } + } else { + error_sp->Printf( + "error: unable to create function, didn't add python command.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf("error: empty function, didn't add python command.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf( + "error: script interpreter missing, didn't add python command.\n"); + error_sp->Flush(); + } + + io_handler.SetIsDone(true); + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (GetDebugger().GetScriptLanguage() != lldb::eScriptLanguagePython) { + result.AppendError("only scripting language supported for scripted " + "commands is currently Python"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() != 1) { + result.AppendError("'command script add' requires one argument"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Store the options in case we get multi-line input + m_cmd_name = command[0].ref(); + m_short_help.assign(m_options.m_short_help); + m_synchronicity = m_options.m_synchronicity; + + if (m_options.m_class_name.empty()) { + if (m_options.m_funct_name.empty()) { + m_interpreter.GetPythonCommandsFromIOHandler( + " ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + nullptr); // Baton for the "io_handler" that will be passed back + // into our IOHandlerDelegate functions + } else { + CommandObjectSP new_cmd(new CommandObjectPythonFunction( + m_interpreter, m_cmd_name, m_options.m_funct_name, + m_options.m_short_help, m_synchronicity)); + if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("cannot add command"); + result.SetStatus(eReturnStatusFailed); + } + } + } else { + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + if (!interpreter) { + result.AppendError("cannot find ScriptInterpreter"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + auto cmd_obj_sp = interpreter->CreateScriptCommandObject( + m_options.m_class_name.c_str()); + if (!cmd_obj_sp) { + result.AppendError("cannot create helper object"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + CommandObjectSP new_cmd(new CommandObjectScriptingObject( + m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity)); + if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("cannot add command"); + result.SetStatus(eReturnStatusFailed); + } + } + + return result.Succeeded(); + } + + CommandOptions m_options; + std::string m_cmd_name; + std::string m_short_help; + ScriptedCommandSynchronicity m_synchronicity; +}; + +// CommandObjectCommandsScriptList + +class CommandObjectCommandsScriptList : public CommandObjectParsed { +public: + CommandObjectCommandsScriptList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "command script list", + "List defined scripted commands.", nullptr) {} + + ~CommandObjectCommandsScriptList() override = default; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.GetArgumentCount() != 0) { + result.AppendError("'command script list' doesn't take any arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + m_interpreter.GetHelp(result, CommandInterpreter::eCommandTypesUserDef); + + result.SetStatus(eReturnStatusSuccessFinishResult); + + return true; + } +}; + +// CommandObjectCommandsScriptClear + +class CommandObjectCommandsScriptClear : public CommandObjectParsed { +public: + CommandObjectCommandsScriptClear(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "command script clear", + "Delete all scripted commands.", nullptr) {} + + ~CommandObjectCommandsScriptClear() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.GetArgumentCount() != 0) { + result.AppendError("'command script clear' doesn't take any arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + m_interpreter.RemoveAllUser(); + + result.SetStatus(eReturnStatusSuccessFinishResult); + + return true; + } +}; + +// CommandObjectCommandsScriptDelete + +class CommandObjectCommandsScriptDelete : public CommandObjectParsed { +public: + CommandObjectCommandsScriptDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "command script delete", + "Delete a scripted command.", nullptr) { + CommandArgumentEntry arg1; + CommandArgumentData cmd_arg; + + // Define the first (and only) variant of this arg. + cmd_arg.arg_type = eArgTypeCommandName; + cmd_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(cmd_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectCommandsScriptDelete() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + + if (command.GetArgumentCount() != 1) { + result.AppendError("'command script delete' requires one argument"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + auto cmd_name = command[0].ref(); + + if (cmd_name.empty() || !m_interpreter.HasUserCommands() || + !m_interpreter.UserCommandExists(cmd_name)) { + result.AppendErrorWithFormat("command %s not found", command[0].c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + m_interpreter.RemoveUser(cmd_name); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } +}; + +#pragma mark CommandObjectMultiwordCommandsScript + +// CommandObjectMultiwordCommandsScript + +class CommandObjectMultiwordCommandsScript : public CommandObjectMultiword { +public: + CommandObjectMultiwordCommandsScript(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "command script", "Commands for managing custom " + "commands implemented by " + "interpreter scripts.", + "command script <subcommand> [<subcommand-options>]") { + LoadSubCommand("add", CommandObjectSP( + new CommandObjectCommandsScriptAdd(interpreter))); + LoadSubCommand( + "delete", + CommandObjectSP(new CommandObjectCommandsScriptDelete(interpreter))); + LoadSubCommand( + "clear", + CommandObjectSP(new CommandObjectCommandsScriptClear(interpreter))); + LoadSubCommand("list", CommandObjectSP(new CommandObjectCommandsScriptList( + interpreter))); + LoadSubCommand( + "import", + CommandObjectSP(new CommandObjectCommandsScriptImport(interpreter))); + } + + ~CommandObjectMultiwordCommandsScript() override = default; +}; + +#pragma mark CommandObjectMultiwordCommands + +// CommandObjectMultiwordCommands + +CommandObjectMultiwordCommands::CommandObjectMultiwordCommands( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "command", + "Commands for managing custom LLDB commands.", + "command <subcommand> [<subcommand-options>]") { + LoadSubCommand("source", + CommandObjectSP(new CommandObjectCommandsSource(interpreter))); + LoadSubCommand("alias", + CommandObjectSP(new CommandObjectCommandsAlias(interpreter))); + LoadSubCommand("unalias", CommandObjectSP( + new CommandObjectCommandsUnalias(interpreter))); + LoadSubCommand("delete", + CommandObjectSP(new CommandObjectCommandsDelete(interpreter))); + LoadSubCommand( + "regex", CommandObjectSP(new CommandObjectCommandsAddRegex(interpreter))); + LoadSubCommand("history", CommandObjectSP( + new CommandObjectCommandsHistory(interpreter))); + LoadSubCommand( + "script", + CommandObjectSP(new CommandObjectMultiwordCommandsScript(interpreter))); +} + +CommandObjectMultiwordCommands::~CommandObjectMultiwordCommands() = default; diff --git a/lldb/source/Commands/CommandObjectCommands.h b/lldb/source/Commands/CommandObjectCommands.h new file mode 100644 index 0000000000000..468ee53344f17 --- /dev/null +++ b/lldb/source/Commands/CommandObjectCommands.h @@ -0,0 +1,30 @@ +//===-- CommandObjectCommands.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_CommandObjectCommands_h_ +#define liblldb_CommandObjectCommands_h_ + +#include "lldb/Core/STLUtils.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +// CommandObjectMultiwordCommands + +class CommandObjectMultiwordCommands : public CommandObjectMultiword { +public: + CommandObjectMultiwordCommands(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordCommands() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectCommands_h_ diff --git a/lldb/source/Commands/CommandObjectDisassemble.cpp b/lldb/source/Commands/CommandObjectDisassemble.cpp new file mode 100644 index 0000000000000..69e2d757b5fe2 --- /dev/null +++ b/lldb/source/Commands/CommandObjectDisassemble.cpp @@ -0,0 +1,523 @@ +//===-- CommandObjectDisassemble.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 "CommandObjectDisassemble.h" +#include "lldb/Core/AddressRange.h" +#include "lldb/Core/Disassembler.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/SourceManager.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" + +#define DEFAULT_DISASM_BYTE_SIZE 32 +#define DEFAULT_DISASM_NUM_INS 4 + +using namespace lldb; +using namespace lldb_private; + +#define LLDB_OPTIONS_disassemble +#include "CommandOptions.inc" + +CommandObjectDisassemble::CommandOptions::CommandOptions() + : Options(), num_lines_context(0), num_instructions(0), func_name(), + current_function(false), start_addr(), end_addr(), at_pc(false), + frame_line(false), plugin_name(), flavor_string(), arch(), + some_location_specified(false), symbol_containing_addr() { + OptionParsingStarting(nullptr); +} + +CommandObjectDisassemble::CommandOptions::~CommandOptions() = default; + +Status CommandObjectDisassemble::CommandOptions::SetOptionValue( + uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'm': + show_mixed = true; + break; + + case 'C': + if (option_arg.getAsInteger(0, num_lines_context)) + error.SetErrorStringWithFormat("invalid num context lines string: \"%s\"", + option_arg.str().c_str()); + break; + + case 'c': + if (option_arg.getAsInteger(0, num_instructions)) + error.SetErrorStringWithFormat( + "invalid num of instructions string: \"%s\"", + option_arg.str().c_str()); + break; + + case 'b': + show_bytes = true; + break; + + case 's': { + start_addr = OptionArgParser::ToAddress(execution_context, option_arg, + LLDB_INVALID_ADDRESS, &error); + if (start_addr != LLDB_INVALID_ADDRESS) + some_location_specified = true; + } break; + case 'e': { + end_addr = OptionArgParser::ToAddress(execution_context, option_arg, + LLDB_INVALID_ADDRESS, &error); + if (end_addr != LLDB_INVALID_ADDRESS) + some_location_specified = true; + } break; + + case 'n': + func_name.assign(option_arg); + some_location_specified = true; + break; + + case 'p': + at_pc = true; + some_location_specified = true; + break; + + case 'l': + frame_line = true; + // Disassemble the current source line kind of implies showing mixed source + // code context. + show_mixed = true; + some_location_specified = true; + break; + + case 'P': + plugin_name.assign(option_arg); + break; + + case 'F': { + TargetSP target_sp = + execution_context ? execution_context->GetTargetSP() : TargetSP(); + if (target_sp && (target_sp->GetArchitecture().GetTriple().getArch() == + llvm::Triple::x86 || + target_sp->GetArchitecture().GetTriple().getArch() == + llvm::Triple::x86_64)) { + flavor_string.assign(option_arg); + } else + error.SetErrorStringWithFormat("Disassembler flavors are currently only " + "supported for x86 and x86_64 targets."); + break; + } + + case 'r': + raw = true; + break; + + case 'f': + current_function = true; + some_location_specified = true; + break; + + case 'A': + if (execution_context) { + const auto &target_sp = execution_context->GetTargetSP(); + auto platform_ptr = target_sp ? target_sp->GetPlatform().get() : nullptr; + arch = Platform::GetAugmentedArchSpec(platform_ptr, option_arg); + } + break; + + case 'a': { + symbol_containing_addr = OptionArgParser::ToAddress( + execution_context, option_arg, LLDB_INVALID_ADDRESS, &error); + if (symbol_containing_addr != LLDB_INVALID_ADDRESS) { + some_location_specified = true; + } + } break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; +} + +void CommandObjectDisassemble::CommandOptions::OptionParsingStarting( + ExecutionContext *execution_context) { + show_mixed = false; + show_bytes = false; + num_lines_context = 0; + num_instructions = 0; + func_name.clear(); + current_function = false; + at_pc = false; + frame_line = false; + start_addr = LLDB_INVALID_ADDRESS; + end_addr = LLDB_INVALID_ADDRESS; + symbol_containing_addr = LLDB_INVALID_ADDRESS; + raw = false; + plugin_name.clear(); + + Target *target = + execution_context ? execution_context->GetTargetPtr() : nullptr; + + // This is a hack till we get the ability to specify features based on + // architecture. For now GetDisassemblyFlavor is really only valid for x86 + // (and for the llvm assembler plugin, but I'm papering over that since that + // is the only disassembler plugin we have... + if (target) { + if (target->GetArchitecture().GetTriple().getArch() == llvm::Triple::x86 || + target->GetArchitecture().GetTriple().getArch() == + llvm::Triple::x86_64) { + flavor_string.assign(target->GetDisassemblyFlavor()); + } else + flavor_string.assign("default"); + + } else + flavor_string.assign("default"); + + arch.Clear(); + some_location_specified = false; +} + +Status CommandObjectDisassemble::CommandOptions::OptionParsingFinished( + ExecutionContext *execution_context) { + if (!some_location_specified) + current_function = true; + return Status(); +} + +llvm::ArrayRef<OptionDefinition> +CommandObjectDisassemble::CommandOptions::GetDefinitions() { + return llvm::makeArrayRef(g_disassemble_options); +} + +// CommandObjectDisassemble + +CommandObjectDisassemble::CommandObjectDisassemble( + CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "disassemble", + "Disassemble specified instructions in the current target. " + "Defaults to the current function for the current thread and " + "stack frame.", + "disassemble [<cmd-options>]", eCommandRequiresTarget), + m_options() {} + +CommandObjectDisassemble::~CommandObjectDisassemble() = default; + +bool CommandObjectDisassemble::DoExecute(Args &command, + CommandReturnObject &result) { + Target *target = &GetSelectedTarget(); + + if (!m_options.arch.IsValid()) + m_options.arch = target->GetArchitecture(); + + if (!m_options.arch.IsValid()) { + result.AppendError( + "use the --arch option or set the target architecture to disassemble"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *plugin_name = m_options.GetPluginName(); + const char *flavor_string = m_options.GetFlavorString(); + + DisassemblerSP disassembler = + Disassembler::FindPlugin(m_options.arch, flavor_string, plugin_name); + + if (!disassembler) { + if (plugin_name) { + result.AppendErrorWithFormat( + "Unable to find Disassembler plug-in named '%s' that supports the " + "'%s' architecture.\n", + plugin_name, m_options.arch.GetArchitectureName()); + } else + result.AppendErrorWithFormat( + "Unable to find Disassembler plug-in for the '%s' architecture.\n", + m_options.arch.GetArchitectureName()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (flavor_string != nullptr && + !disassembler->FlavorValidForArchSpec(m_options.arch, + flavor_string)) + result.AppendWarningWithFormat( + "invalid disassembler flavor \"%s\", using default.\n", flavor_string); + + result.SetStatus(eReturnStatusSuccessFinishResult); + + if (!command.empty()) { + result.AppendErrorWithFormat( + "\"disassemble\" arguments are specified as options.\n"); + const int terminal_width = + GetCommandInterpreter().GetDebugger().GetTerminalWidth(); + GetOptions()->GenerateOptionUsage(result.GetErrorStream(), this, + terminal_width); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.show_mixed && m_options.num_lines_context == 0) + m_options.num_lines_context = 2; + + // Always show the PC in the disassembly + uint32_t options = Disassembler::eOptionMarkPCAddress; + + // Mark the source line for the current PC only if we are doing mixed source + // and assembly + if (m_options.show_mixed) + options |= Disassembler::eOptionMarkPCSourceLine; + + if (m_options.show_bytes) + options |= Disassembler::eOptionShowBytes; + + if (m_options.raw) + options |= Disassembler::eOptionRawOuput; + + if (!m_options.func_name.empty()) { + ConstString name(m_options.func_name.c_str()); + + if (Disassembler::Disassemble( + GetDebugger(), m_options.arch, plugin_name, flavor_string, + m_exe_ctx, name, + nullptr, // Module * + m_options.num_instructions, m_options.show_mixed, + m_options.show_mixed ? m_options.num_lines_context : 0, options, + result.GetOutputStream())) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Unable to find symbol with name '%s'.\n", + name.GetCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + std::vector<AddressRange> ranges; + AddressRange range; + StackFrame *frame = m_exe_ctx.GetFramePtr(); + if (m_options.frame_line) { + if (frame == nullptr) { + result.AppendError("Cannot disassemble around the current line without " + "a selected frame.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + LineEntry pc_line_entry( + frame->GetSymbolContext(eSymbolContextLineEntry).line_entry); + if (pc_line_entry.IsValid()) { + range = pc_line_entry.range; + } else { + m_options.at_pc = + true; // No line entry, so just disassemble around the current pc + m_options.show_mixed = false; + } + } else if (m_options.current_function) { + if (frame == nullptr) { + result.AppendError("Cannot disassemble around the current function " + "without a selected frame.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + Symbol *symbol = frame->GetSymbolContext(eSymbolContextSymbol).symbol; + if (symbol) { + range.GetBaseAddress() = symbol->GetAddress(); + range.SetByteSize(symbol->GetByteSize()); + } + } + + // Did the "m_options.frame_line" find a valid range already? If so skip + // the rest... + if (range.GetByteSize() == 0) { + if (m_options.at_pc) { + if (frame == nullptr) { + result.AppendError("Cannot disassemble around the current PC without " + "a selected frame.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + range.GetBaseAddress() = frame->GetFrameCodeAddress(); + if (m_options.num_instructions == 0) { + // Disassembling at the PC always disassembles some number of + // instructions (not the whole function). + m_options.num_instructions = DEFAULT_DISASM_NUM_INS; + } + ranges.push_back(range); + } else { + range.GetBaseAddress().SetOffset(m_options.start_addr); + if (range.GetBaseAddress().IsValid()) { + if (m_options.end_addr != LLDB_INVALID_ADDRESS) { + if (m_options.end_addr <= m_options.start_addr) { + result.AppendErrorWithFormat( + "End address before start address.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + range.SetByteSize(m_options.end_addr - m_options.start_addr); + } + ranges.push_back(range); + } else { + if (m_options.symbol_containing_addr != LLDB_INVALID_ADDRESS && + target) { + if (!target->GetSectionLoadList().IsEmpty()) { + bool failed = false; + Address symbol_containing_address; + if (target->GetSectionLoadList().ResolveLoadAddress( + m_options.symbol_containing_addr, + symbol_containing_address)) { + ModuleSP module_sp(symbol_containing_address.GetModule()); + SymbolContext sc; + bool resolve_tail_call_address = true; // PC can be one past the + // address range of the + // function. + module_sp->ResolveSymbolContextForAddress( + symbol_containing_address, eSymbolContextEverything, sc, + resolve_tail_call_address); + if (sc.function || sc.symbol) { + sc.GetAddressRange(eSymbolContextFunction | + eSymbolContextSymbol, + 0, false, range); + } else { + failed = true; + } + } else { + failed = true; + } + if (failed) { + result.AppendErrorWithFormat( + "Could not find function bounds for address 0x%" PRIx64 + "\n", + m_options.symbol_containing_addr); + result.SetStatus(eReturnStatusFailed); + return false; + } + ranges.push_back(range); + } else { + for (lldb::ModuleSP module_sp : target->GetImages().Modules()) { + lldb::addr_t file_addr = m_options.symbol_containing_addr; + Address file_address; + if (module_sp->ResolveFileAddress(file_addr, file_address)) { + SymbolContext sc; + bool resolve_tail_call_address = true; // PC can be one past + // the address range of + // the function. + module_sp->ResolveSymbolContextForAddress( + file_address, eSymbolContextEverything, sc, + resolve_tail_call_address); + if (sc.function || sc.symbol) { + sc.GetAddressRange(eSymbolContextFunction | + eSymbolContextSymbol, + 0, false, range); + ranges.push_back(range); + } + } + } + } + } + } + } + } else + ranges.push_back(range); + + if (m_options.num_instructions != 0) { + if (ranges.empty()) { + // The default action is to disassemble the current frame function. + if (frame) { + SymbolContext sc(frame->GetSymbolContext(eSymbolContextFunction | + eSymbolContextSymbol)); + if (sc.function) + range.GetBaseAddress() = + sc.function->GetAddressRange().GetBaseAddress(); + else if (sc.symbol && sc.symbol->ValueIsAddress()) + range.GetBaseAddress() = sc.symbol->GetAddress(); + else + range.GetBaseAddress() = frame->GetFrameCodeAddress(); + } + + if (!range.GetBaseAddress().IsValid()) { + result.AppendError("invalid frame"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + bool print_sc_header = ranges.size() > 1; + for (AddressRange cur_range : ranges) { + if (Disassembler::Disassemble( + GetDebugger(), m_options.arch, plugin_name, flavor_string, + m_exe_ctx, cur_range.GetBaseAddress(), + m_options.num_instructions, m_options.show_mixed, + m_options.show_mixed ? m_options.num_lines_context : 0, options, + result.GetOutputStream())) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + if (m_options.start_addr != LLDB_INVALID_ADDRESS) + result.AppendErrorWithFormat( + "Failed to disassemble memory at 0x%8.8" PRIx64 ".\n", + m_options.start_addr); + else if (m_options.symbol_containing_addr != LLDB_INVALID_ADDRESS) + result.AppendErrorWithFormat( + "Failed to disassemble memory in function at 0x%8.8" PRIx64 + ".\n", + m_options.symbol_containing_addr); + result.SetStatus(eReturnStatusFailed); + } + } + if (print_sc_header) + result.AppendMessage("\n"); + } else { + if (ranges.empty()) { + // The default action is to disassemble the current frame function. + if (frame) { + SymbolContext sc(frame->GetSymbolContext(eSymbolContextFunction | + eSymbolContextSymbol)); + if (sc.function) + range = sc.function->GetAddressRange(); + else if (sc.symbol && sc.symbol->ValueIsAddress()) { + range.GetBaseAddress() = sc.symbol->GetAddress(); + range.SetByteSize(sc.symbol->GetByteSize()); + } else + range.GetBaseAddress() = frame->GetFrameCodeAddress(); + } else { + result.AppendError("invalid frame"); + result.SetStatus(eReturnStatusFailed); + return false; + } + ranges.push_back(range); + } + + bool print_sc_header = ranges.size() > 1; + for (AddressRange cur_range : ranges) { + if (cur_range.GetByteSize() == 0) + cur_range.SetByteSize(DEFAULT_DISASM_BYTE_SIZE); + + if (Disassembler::Disassemble( + GetDebugger(), m_options.arch, plugin_name, flavor_string, + m_exe_ctx, cur_range, m_options.num_instructions, + m_options.show_mixed, + m_options.show_mixed ? m_options.num_lines_context : 0, options, + result.GetOutputStream())) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat( + "Failed to disassemble memory at 0x%8.8" PRIx64 ".\n", + cur_range.GetBaseAddress().GetLoadAddress(target)); + result.SetStatus(eReturnStatusFailed); + } + if (print_sc_header) + result.AppendMessage("\n"); + } + } + } + + return result.Succeeded(); +} diff --git a/lldb/source/Commands/CommandObjectDisassemble.h b/lldb/source/Commands/CommandObjectDisassemble.h new file mode 100644 index 0000000000000..70193e914c7f6 --- /dev/null +++ b/lldb/source/Commands/CommandObjectDisassemble.h @@ -0,0 +1,81 @@ +//===-- CommandObjectDisassemble.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_CommandObjectDisassemble_h_ +#define liblldb_CommandObjectDisassemble_h_ + +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Utility/ArchSpec.h" + +namespace lldb_private { + +// CommandObjectDisassemble + +class CommandObjectDisassemble : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions(); + + ~CommandOptions() override; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override; + + void OptionParsingStarting(ExecutionContext *execution_context) override; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override; + + const char *GetPluginName() { + return (plugin_name.empty() ? nullptr : plugin_name.c_str()); + } + + const char *GetFlavorString() { + if (flavor_string.empty() || flavor_string == "default") + return nullptr; + return flavor_string.c_str(); + } + + Status OptionParsingFinished(ExecutionContext *execution_context) override; + + bool show_mixed; // Show mixed source/assembly + bool show_bytes; + uint32_t num_lines_context; + uint32_t num_instructions; + bool raw; + std::string func_name; + bool current_function; + lldb::addr_t start_addr; + lldb::addr_t end_addr; + bool at_pc; + bool frame_line; + std::string plugin_name; + std::string flavor_string; + ArchSpec arch; + bool some_location_specified; // If no location was specified, we'll select + // "at_pc". This should be set + // in SetOptionValue if anything the selects a location is set. + lldb::addr_t symbol_containing_addr; + }; + + CommandObjectDisassemble(CommandInterpreter &interpreter); + + ~CommandObjectDisassemble() override; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override; + + CommandOptions m_options; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectDisassemble_h_ diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp new file mode 100644 index 0000000000000..9bafdc1498041 --- /dev/null +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -0,0 +1,682 @@ +//===-- CommandObjectExpression.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 "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" + +#include "CommandObjectExpression.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObjectVariable.h" +#include "lldb/DataFormatters/ValueObjectPrinter.h" +#include "lldb/Expression/DWARFExpression.h" +#include "lldb/Expression/REPL.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" + +using namespace lldb; +using namespace lldb_private; + +CommandObjectExpression::CommandOptions::CommandOptions() : OptionGroup() {} + +CommandObjectExpression::CommandOptions::~CommandOptions() = default; + +static constexpr OptionEnumValueElement g_description_verbosity_type[] = { + { + eLanguageRuntimeDescriptionDisplayVerbosityCompact, + "compact", + "Only show the description string", + }, + { + eLanguageRuntimeDescriptionDisplayVerbosityFull, + "full", + "Show the full output, including persistent variable's name and type", + }, +}; + +static constexpr OptionEnumValues DescriptionVerbosityTypes() { + return OptionEnumValues(g_description_verbosity_type); +} + +#define LLDB_OPTIONS_expression +#include "CommandOptions.inc" + +Status CommandObjectExpression::CommandOptions::SetOptionValue( + uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + + const int short_option = GetDefinitions()[option_idx].short_option; + + switch (short_option) { + case 'l': + language = Language::GetLanguageTypeFromString(option_arg); + if (language == eLanguageTypeUnknown) + error.SetErrorStringWithFormat( + "unknown language type: '%s' for expression", + option_arg.str().c_str()); + break; + + case 'a': { + bool success; + bool result; + result = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error.SetErrorStringWithFormat( + "invalid all-threads value setting: \"%s\"", + option_arg.str().c_str()); + else + try_all_threads = result; + } break; + + case 'i': { + bool success; + bool tmp_value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (success) + ignore_breakpoints = tmp_value; + else + error.SetErrorStringWithFormat( + "could not convert \"%s\" to a boolean value.", + option_arg.str().c_str()); + break; + } + + case 'j': { + bool success; + bool tmp_value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (success) + allow_jit = tmp_value; + else + error.SetErrorStringWithFormat( + "could not convert \"%s\" to a boolean value.", + option_arg.str().c_str()); + break; + } + + case 't': + if (option_arg.getAsInteger(0, timeout)) { + timeout = 0; + error.SetErrorStringWithFormat("invalid timeout setting \"%s\"", + option_arg.str().c_str()); + } + break; + + case 'u': { + bool success; + bool tmp_value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (success) + unwind_on_error = tmp_value; + else + error.SetErrorStringWithFormat( + "could not convert \"%s\" to a boolean value.", + option_arg.str().c_str()); + break; + } + + case 'v': + if (option_arg.empty()) { + m_verbosity = eLanguageRuntimeDescriptionDisplayVerbosityFull; + break; + } + m_verbosity = (LanguageRuntimeDescriptionDisplayVerbosity) + OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, 0, error); + if (!error.Success()) + error.SetErrorStringWithFormat( + "unrecognized value for description-verbosity '%s'", + option_arg.str().c_str()); + break; + + case 'g': + debug = true; + unwind_on_error = false; + ignore_breakpoints = false; + break; + + case 'p': + top_level = true; + break; + + case 'X': { + bool success; + bool tmp_value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (success) + auto_apply_fixits = tmp_value ? eLazyBoolYes : eLazyBoolNo; + else + error.SetErrorStringWithFormat( + "could not convert \"%s\" to a boolean value.", + option_arg.str().c_str()); + break; + } + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; +} + +void CommandObjectExpression::CommandOptions::OptionParsingStarting( + ExecutionContext *execution_context) { + auto process_sp = + execution_context ? execution_context->GetProcessSP() : ProcessSP(); + if (process_sp) { + ignore_breakpoints = process_sp->GetIgnoreBreakpointsInExpressions(); + unwind_on_error = process_sp->GetUnwindOnErrorInExpressions(); + } else { + ignore_breakpoints = true; + unwind_on_error = true; + } + + show_summary = true; + try_all_threads = true; + timeout = 0; + debug = false; + language = eLanguageTypeUnknown; + m_verbosity = eLanguageRuntimeDescriptionDisplayVerbosityCompact; + auto_apply_fixits = eLazyBoolCalculate; + top_level = false; + allow_jit = true; +} + +llvm::ArrayRef<OptionDefinition> +CommandObjectExpression::CommandOptions::GetDefinitions() { + return llvm::makeArrayRef(g_expression_options); +} + +CommandObjectExpression::CommandObjectExpression( + CommandInterpreter &interpreter) + : CommandObjectRaw( + interpreter, "expression", "Evaluate an expression on the current " + "thread. Displays any returned value " + "with LLDB's default formatting.", + "", eCommandProcessMustBePaused | eCommandTryTargetAPILock), + IOHandlerDelegate(IOHandlerDelegate::Completion::Expression), + m_option_group(), m_format_options(eFormatDefault), + m_repl_option(LLDB_OPT_SET_1, false, "repl", 'r', "Drop into REPL", false, + true), + m_command_options(), m_expr_line_count(0), m_expr_lines() { + SetHelpLong( + R"( +Single and multi-line expressions: + +)" + " The expression provided on the command line must be a complete expression \ +with no newlines. To evaluate a multi-line expression, \ +hit a return after an empty expression, and lldb will enter the multi-line expression editor. \ +Hit return on an empty line to end the multi-line expression." + + R"( + +Timeouts: + +)" + " If the expression can be evaluated statically (without running code) then it will be. \ +Otherwise, by default the expression will run on the current thread with a short timeout: \ +currently .25 seconds. If it doesn't return in that time, the evaluation will be interrupted \ +and resumed with all threads running. You can use the -a option to disable retrying on all \ +threads. You can use the -t option to set a shorter timeout." + R"( + +User defined variables: + +)" + " You can define your own variables for convenience or to be used in subsequent expressions. \ +You define them the same way you would define variables in C. If the first character of \ +your user defined variable is a $, then the variable's value will be available in future \ +expressions, otherwise it will just be available in the current expression." + R"( + +Continuing evaluation after a breakpoint: + +)" + " If the \"-i false\" option is used, and execution is interrupted by a breakpoint hit, once \ +you are done with your investigation, you can either remove the expression execution frames \ +from the stack with \"thread return -x\" or if you are still interested in the expression result \ +you can issue the \"continue\" command and the expression evaluation will complete and the \ +expression result will be available using the \"thread.completed-expression\" key in the thread \ +format." + + R"( + +Examples: + + expr my_struct->a = my_array[3] + expr -f bin -- (index * 8) + 5 + expr unsigned int $foo = 5 + expr char c[] = \"foo\"; c[0])"); + + CommandArgumentEntry arg; + CommandArgumentData expression_arg; + + // Define the first (and only) variant of this arg. + expression_arg.arg_type = eArgTypeExpression; + expression_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the argument + // entry. + arg.push_back(expression_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + + // Add the "--format" and "--gdb-format" + m_option_group.Append(&m_format_options, + OptionGroupFormat::OPTION_GROUP_FORMAT | + OptionGroupFormat::OPTION_GROUP_GDB_FMT, + LLDB_OPT_SET_1); + m_option_group.Append(&m_command_options); + m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2); + m_option_group.Append(&m_repl_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_3); + m_option_group.Finalize(); +} + +CommandObjectExpression::~CommandObjectExpression() = default; + +Options *CommandObjectExpression::GetOptions() { return &m_option_group; } + +void CommandObjectExpression::HandleCompletion(CompletionRequest &request) { + EvaluateExpressionOptions options; + options.SetCoerceToId(m_varobj_options.use_objc); + options.SetLanguage(m_command_options.language); + options.SetExecutionPolicy(lldb_private::eExecutionPolicyNever); + options.SetAutoApplyFixIts(false); + options.SetGenerateDebugInfo(false); + + // We need a valid execution context with a frame pointer for this + // completion, so if we don't have one we should try to make a valid + // execution context. + if (m_interpreter.GetExecutionContext().GetFramePtr() == nullptr) + m_interpreter.UpdateExecutionContext(nullptr); + + // This didn't work, so let's get out before we start doing things that + // expect a valid frame pointer. + if (m_interpreter.GetExecutionContext().GetFramePtr() == nullptr) + return; + + ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); + + Target *target = exe_ctx.GetTargetPtr(); + + if (!target) + target = &GetDummyTarget(); + + unsigned cursor_pos = request.GetRawCursorPos(); + llvm::StringRef code = request.GetRawLine(); + + const std::size_t original_code_size = code.size(); + + // Remove the first token which is 'expr' or some alias/abbreviation of that. + code = llvm::getToken(code).second.ltrim(); + OptionsWithRaw args(code); + code = args.GetRawPart(); + + // The position where the expression starts in the command line. + assert(original_code_size >= code.size()); + std::size_t raw_start = original_code_size - code.size(); + + // Check if the cursor is actually in the expression string, and if not, we + // exit. + // FIXME: We should complete the options here. + if (cursor_pos < raw_start) + return; + + // Make the cursor_pos again relative to the start of the code string. + assert(cursor_pos >= raw_start); + cursor_pos -= raw_start; + + auto language = exe_ctx.GetFrameRef().GetLanguage(); + + Status error; + lldb::UserExpressionSP expr(target->GetUserExpressionForLanguage( + code, llvm::StringRef(), language, UserExpression::eResultTypeAny, + options, nullptr, error)); + if (error.Fail()) + return; + + expr->Complete(exe_ctx, request, cursor_pos); +} + +static lldb_private::Status +CanBeUsedForElementCountPrinting(ValueObject &valobj) { + CompilerType type(valobj.GetCompilerType()); + CompilerType pointee; + if (!type.IsPointerType(&pointee)) + return Status("as it does not refer to a pointer"); + if (pointee.IsVoidType()) + return Status("as it refers to a pointer to void"); + return Status(); +} + +bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr, + Stream *output_stream, + Stream *error_stream, + CommandReturnObject *result) { + // Don't use m_exe_ctx as this might be called asynchronously after the + // command object DoExecute has finished when doing multi-line expression + // that use an input reader... + ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); + + Target *target = exe_ctx.GetTargetPtr(); + + if (!target) + target = &GetDummyTarget(); + + lldb::ValueObjectSP result_valobj_sp; + bool keep_in_memory = true; + StackFrame *frame = exe_ctx.GetFramePtr(); + + EvaluateExpressionOptions options; + options.SetCoerceToId(m_varobj_options.use_objc); + options.SetUnwindOnError(m_command_options.unwind_on_error); + options.SetIgnoreBreakpoints(m_command_options.ignore_breakpoints); + options.SetKeepInMemory(keep_in_memory); + options.SetUseDynamic(m_varobj_options.use_dynamic); + options.SetTryAllThreads(m_command_options.try_all_threads); + options.SetDebug(m_command_options.debug); + options.SetLanguage(m_command_options.language); + options.SetExecutionPolicy( + m_command_options.allow_jit + ? EvaluateExpressionOptions::default_execution_policy + : lldb_private::eExecutionPolicyNever); + + bool auto_apply_fixits; + if (m_command_options.auto_apply_fixits == eLazyBoolCalculate) + auto_apply_fixits = target->GetEnableAutoApplyFixIts(); + else + auto_apply_fixits = m_command_options.auto_apply_fixits == eLazyBoolYes; + + options.SetAutoApplyFixIts(auto_apply_fixits); + + if (m_command_options.top_level) + options.SetExecutionPolicy(eExecutionPolicyTopLevel); + + // If there is any chance we are going to stop and want to see what went + // wrong with our expression, we should generate debug info + if (!m_command_options.ignore_breakpoints || + !m_command_options.unwind_on_error) + options.SetGenerateDebugInfo(true); + + if (m_command_options.timeout > 0) + options.SetTimeout(std::chrono::microseconds(m_command_options.timeout)); + else + options.SetTimeout(llvm::None); + + ExpressionResults success = target->EvaluateExpression( + expr, frame, result_valobj_sp, options, &m_fixed_expression); + + // We only tell you about the FixIt if we applied it. The compiler errors + // will suggest the FixIt if it parsed. + if (error_stream && !m_fixed_expression.empty() && + target->GetEnableNotifyAboutFixIts()) { + if (success == eExpressionCompleted) + error_stream->Printf(" Fix-it applied, fixed expression was: \n %s\n", + m_fixed_expression.c_str()); + } + + if (result_valobj_sp) { + Format format = m_format_options.GetFormat(); + + if (result_valobj_sp->GetError().Success()) { + if (format != eFormatVoid) { + if (format != eFormatDefault) + result_valobj_sp->SetFormat(format); + + if (m_varobj_options.elem_count > 0) { + Status error(CanBeUsedForElementCountPrinting(*result_valobj_sp)); + if (error.Fail()) { + result->AppendErrorWithFormat( + "expression cannot be used with --element-count %s\n", + error.AsCString("")); + result->SetStatus(eReturnStatusFailed); + return false; + } + } + + DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions( + m_command_options.m_verbosity, format)); + options.SetVariableFormatDisplayLanguage( + result_valobj_sp->GetPreferredDisplayLanguage()); + + result_valobj_sp->Dump(*output_stream, options); + + if (result) + result->SetStatus(eReturnStatusSuccessFinishResult); + } + } else { + if (result_valobj_sp->GetError().GetError() == + UserExpression::kNoResult) { + if (format != eFormatVoid && GetDebugger().GetNotifyVoid()) { + error_stream->PutCString("(void)\n"); + } + + if (result) + result->SetStatus(eReturnStatusSuccessFinishResult); + } else { + const char *error_cstr = result_valobj_sp->GetError().AsCString(); + if (error_cstr && error_cstr[0]) { + const size_t error_cstr_len = strlen(error_cstr); + const bool ends_with_newline = error_cstr[error_cstr_len - 1] == '\n'; + if (strstr(error_cstr, "error:") != error_cstr) + error_stream->PutCString("error: "); + error_stream->Write(error_cstr, error_cstr_len); + if (!ends_with_newline) + error_stream->EOL(); + } else { + error_stream->PutCString("error: unknown error\n"); + } + + if (result) + result->SetStatus(eReturnStatusFailed); + } + } + } + + return true; +} + +void CommandObjectExpression::IOHandlerInputComplete(IOHandler &io_handler, + std::string &line) { + io_handler.SetIsDone(true); + // StreamSP output_stream = + // io_handler.GetDebugger().GetAsyncOutputStream(); + // StreamSP error_stream = io_handler.GetDebugger().GetAsyncErrorStream(); + StreamFileSP output_sp = io_handler.GetOutputStreamFileSP(); + StreamFileSP error_sp = io_handler.GetErrorStreamFileSP(); + + EvaluateExpression(line.c_str(), output_sp.get(), error_sp.get()); + if (output_sp) + output_sp->Flush(); + if (error_sp) + error_sp->Flush(); +} + +bool CommandObjectExpression::IOHandlerIsInputComplete(IOHandler &io_handler, + StringList &lines) { + // An empty lines is used to indicate the end of input + const size_t num_lines = lines.GetSize(); + if (num_lines > 0 && lines[num_lines - 1].empty()) { + // Remove the last empty line from "lines" so it doesn't appear in our + // resulting input and return true to indicate we are done getting lines + lines.PopBack(); + return true; + } + return false; +} + +void CommandObjectExpression::GetMultilineExpression() { + m_expr_lines.clear(); + m_expr_line_count = 0; + + Debugger &debugger = GetCommandInterpreter().GetDebugger(); + bool color_prompt = debugger.GetUseColor(); + const bool multiple_lines = true; // Get multiple lines + IOHandlerSP io_handler_sp( + new IOHandlerEditline(debugger, IOHandler::Type::Expression, + "lldb-expr", // Name of input reader for history + llvm::StringRef(), // No prompt + llvm::StringRef(), // Continuation prompt + multiple_lines, color_prompt, + 1, // Show line numbers starting at 1 + *this, nullptr)); + + StreamFileSP output_sp = io_handler_sp->GetOutputStreamFileSP(); + if (output_sp) { + output_sp->PutCString( + "Enter expressions, then terminate with an empty line to evaluate:\n"); + output_sp->Flush(); + } + debugger.PushIOHandler(io_handler_sp); +} + +static EvaluateExpressionOptions +GetExprOptions(ExecutionContext &ctx, + CommandObjectExpression::CommandOptions command_options) { + command_options.OptionParsingStarting(&ctx); + + // Default certain settings for REPL regardless of the global settings. + command_options.unwind_on_error = false; + command_options.ignore_breakpoints = false; + command_options.debug = false; + + EvaluateExpressionOptions expr_options; + expr_options.SetUnwindOnError(command_options.unwind_on_error); + expr_options.SetIgnoreBreakpoints(command_options.ignore_breakpoints); + expr_options.SetTryAllThreads(command_options.try_all_threads); + + if (command_options.timeout > 0) + expr_options.SetTimeout(std::chrono::microseconds(command_options.timeout)); + else + expr_options.SetTimeout(llvm::None); + + return expr_options; +} + +bool CommandObjectExpression::DoExecute(llvm::StringRef command, + CommandReturnObject &result) { + m_fixed_expression.clear(); + auto exe_ctx = GetCommandInterpreter().GetExecutionContext(); + m_option_group.NotifyOptionParsingStarting(&exe_ctx); + + if (command.empty()) { + GetMultilineExpression(); + return result.Succeeded(); + } + + OptionsWithRaw args(command); + llvm::StringRef expr = args.GetRawPart(); + + if (args.HasArgs()) { + if (!ParseOptionsAndNotify(args.GetArgs(), result, m_option_group, exe_ctx)) + return false; + + if (m_repl_option.GetOptionValue().GetCurrentValue()) { + Target *target = m_interpreter.GetExecutionContext().GetTargetPtr(); + if (target) { + // Drop into REPL + m_expr_lines.clear(); + m_expr_line_count = 0; + + Debugger &debugger = target->GetDebugger(); + + // Check if the LLDB command interpreter is sitting on top of a REPL + // that launched it... + if (debugger.CheckTopIOHandlerTypes(IOHandler::Type::CommandInterpreter, + IOHandler::Type::REPL)) { + // the LLDB command interpreter is sitting on top of a REPL that + // launched it, so just say the command interpreter is done and + // fall back to the existing REPL + m_interpreter.GetIOHandler(false)->SetIsDone(true); + } else { + // We are launching the REPL on top of the current LLDB command + // interpreter, so just push one + bool initialize = false; + Status repl_error; + REPLSP repl_sp(target->GetREPL(repl_error, m_command_options.language, + nullptr, false)); + + if (!repl_sp) { + initialize = true; + repl_sp = target->GetREPL(repl_error, m_command_options.language, + nullptr, true); + if (!repl_error.Success()) { + result.SetError(repl_error); + return result.Succeeded(); + } + } + + if (repl_sp) { + if (initialize) { + repl_sp->SetEvaluateOptions( + GetExprOptions(exe_ctx, m_command_options)); + repl_sp->SetFormatOptions(m_format_options); + repl_sp->SetValueObjectDisplayOptions(m_varobj_options); + } + + IOHandlerSP io_handler_sp(repl_sp->GetIOHandler()); + + io_handler_sp->SetIsDone(false); + + debugger.PushIOHandler(io_handler_sp); + } else { + repl_error.SetErrorStringWithFormat( + "Couldn't create a REPL for %s", + Language::GetNameForLanguageType(m_command_options.language)); + result.SetError(repl_error); + return result.Succeeded(); + } + } + } + } + // No expression following options + else if (expr.empty()) { + GetMultilineExpression(); + return result.Succeeded(); + } + } + + Target &target = GetSelectedOrDummyTarget(); + if (EvaluateExpression(expr, &(result.GetOutputStream()), + &(result.GetErrorStream()), &result)) { + + if (!m_fixed_expression.empty() && target.GetEnableNotifyAboutFixIts()) { + CommandHistory &history = m_interpreter.GetCommandHistory(); + // FIXME: Can we figure out what the user actually typed (e.g. some alias + // for expr???) + // If we can it would be nice to show that. + std::string fixed_command("expression "); + if (args.HasArgs()) { + // Add in any options that might have been in the original command: + fixed_command.append(args.GetArgStringWithDelimiter()); + fixed_command.append(m_fixed_expression); + } else + fixed_command.append(m_fixed_expression); + history.AppendString(fixed_command); + } + // Increment statistics to record this expression evaluation success. + target.IncrementStats(StatisticKind::ExpressionSuccessful); + return true; + } + + // Increment statistics to record this expression evaluation failure. + target.IncrementStats(StatisticKind::ExpressionFailure); + result.SetStatus(eReturnStatusFailed); + return false; +} diff --git a/lldb/source/Commands/CommandObjectExpression.h b/lldb/source/Commands/CommandObjectExpression.h new file mode 100644 index 0000000000000..8ef764239069f --- /dev/null +++ b/lldb/source/Commands/CommandObjectExpression.h @@ -0,0 +1,87 @@ +//===-- CommandObjectExpression.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_CommandObjectExpression_h_ +#define liblldb_CommandObjectExpression_h_ + +#include "lldb/Core/IOHandler.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/OptionGroupBoolean.h" +#include "lldb/Interpreter/OptionGroupFormat.h" +#include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/lldb-private-enumerations.h" +namespace lldb_private { + +class CommandObjectExpression : public CommandObjectRaw, + public IOHandlerDelegate { +public: + class CommandOptions : public OptionGroup { + public: + CommandOptions(); + + ~CommandOptions() override; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override; + + void OptionParsingStarting(ExecutionContext *execution_context) override; + + bool top_level; + bool unwind_on_error; + bool ignore_breakpoints; + bool allow_jit; + bool show_types; + bool show_summary; + bool debug; + uint32_t timeout; + bool try_all_threads; + lldb::LanguageType language; + LanguageRuntimeDescriptionDisplayVerbosity m_verbosity; + LazyBool auto_apply_fixits; + }; + + CommandObjectExpression(CommandInterpreter &interpreter); + + ~CommandObjectExpression() override; + + Options *GetOptions() override; + + void HandleCompletion(CompletionRequest &request) override; + +protected: + // IOHandler::Delegate functions + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &line) override; + + bool IOHandlerIsInputComplete(IOHandler &io_handler, + StringList &lines) override; + + bool DoExecute(llvm::StringRef command, CommandReturnObject &result) override; + + bool EvaluateExpression(llvm::StringRef expr, Stream *output_stream, + Stream *error_stream, + CommandReturnObject *result = nullptr); + + void GetMultilineExpression(); + + OptionGroupOptions m_option_group; + OptionGroupFormat m_format_options; + OptionGroupValueObjectDisplay m_varobj_options; + OptionGroupBoolean m_repl_option; + CommandOptions m_command_options; + uint32_t m_expr_line_count; + std::string m_expr_lines; // Multi-line expression support + std::string m_fixed_expression; // Holds the current expression's fixed text. +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectExpression_h_ diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp new file mode 100644 index 0000000000000..6a7facdaff35f --- /dev/null +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -0,0 +1,1115 @@ +//===-- CommandObjectFrame.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 "CommandObjectFrame.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectVariable.h" +#include "lldb/DataFormatters/DataVisualization.h" +#include "lldb/DataFormatters/ValueObjectPrinter.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Host/StringConvert.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionGroupFormat.h" +#include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" +#include "lldb/Interpreter/OptionGroupVariable.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/Timer.h" + +#include <memory> +#include <string> + +using namespace lldb; +using namespace lldb_private; + +#pragma mark CommandObjectFrameDiagnose + +// CommandObjectFrameInfo + +// CommandObjectFrameDiagnose + +#define LLDB_OPTIONS_frame_diag +#include "CommandOptions.inc" + +class CommandObjectFrameDiagnose : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + switch (short_option) { + case 'r': + reg = ConstString(option_arg); + break; + + case 'a': { + address.emplace(); + if (option_arg.getAsInteger(0, *address)) { + address.reset(); + error.SetErrorStringWithFormat("invalid address argument '%s'", + option_arg.str().c_str()); + } + } break; + + case 'o': { + offset.emplace(); + if (option_arg.getAsInteger(0, *offset)) { + offset.reset(); + error.SetErrorStringWithFormat("invalid offset argument '%s'", + option_arg.str().c_str()); + } + } break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + address.reset(); + reg.reset(); + offset.reset(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_frame_diag_options); + } + + // Options. + llvm::Optional<lldb::addr_t> address; + llvm::Optional<ConstString> reg; + llvm::Optional<int64_t> offset; + }; + + CommandObjectFrameDiagnose(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "frame diagnose", + "Try to determine what path path the current stop " + "location used to get to a register or address", + nullptr, + eCommandRequiresThread | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData index_arg; + + // Define the first (and only) variant of this arg. + index_arg.arg_type = eArgTypeFrameIndex; + index_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(index_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectFrameDiagnose() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Thread *thread = m_exe_ctx.GetThreadPtr(); + StackFrameSP frame_sp = thread->GetSelectedFrame(); + + ValueObjectSP valobj_sp; + + if (m_options.address.hasValue()) { + if (m_options.reg.hasValue() || m_options.offset.hasValue()) { + result.AppendError( + "`frame diagnose --address` is incompatible with other arguments."); + result.SetStatus(eReturnStatusFailed); + return false; + } + valobj_sp = frame_sp->GuessValueForAddress(m_options.address.getValue()); + } else if (m_options.reg.hasValue()) { + valobj_sp = frame_sp->GuessValueForRegisterAndOffset( + m_options.reg.getValue(), m_options.offset.getValueOr(0)); + } else { + StopInfoSP stop_info_sp = thread->GetStopInfo(); + if (!stop_info_sp) { + result.AppendError("No arguments provided, and no stop info."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + valobj_sp = StopInfo::GetCrashingDereference(stop_info_sp); + } + + if (!valobj_sp) { + result.AppendError("No diagnosis available."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + + DumpValueObjectOptions::DeclPrintingHelper helper = [&valobj_sp]( + ConstString type, ConstString var, const DumpValueObjectOptions &opts, + Stream &stream) -> bool { + const ValueObject::GetExpressionPathFormat format = ValueObject:: + GetExpressionPathFormat::eGetExpressionPathFormatHonorPointers; + const bool qualify_cxx_base_classes = false; + valobj_sp->GetExpressionPath(stream, qualify_cxx_base_classes, format); + stream.PutCString(" ="); + return true; + }; + + DumpValueObjectOptions options; + options.SetDeclPrintingHelper(helper); + ValueObjectPrinter printer(valobj_sp.get(), &result.GetOutputStream(), + options); + printer.PrintValueObject(); + + return true; + } + +protected: + CommandOptions m_options; +}; + +#pragma mark CommandObjectFrameInfo + +// CommandObjectFrameInfo + +class CommandObjectFrameInfo : public CommandObjectParsed { +public: + CommandObjectFrameInfo(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "frame info", "List information about the current " + "stack frame in the current thread.", + "frame info", + eCommandRequiresFrame | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) {} + + ~CommandObjectFrameInfo() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + m_exe_ctx.GetFrameRef().DumpUsingSettingsFormat(&result.GetOutputStream()); + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectFrameSelect + +// CommandObjectFrameSelect + +#define LLDB_OPTIONS_frame_select +#include "CommandOptions.inc" + +class CommandObjectFrameSelect : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + switch (short_option) { + case 'r': { + int32_t offset = 0; + if (option_arg.getAsInteger(0, offset) || offset == INT32_MIN) { + error.SetErrorStringWithFormat("invalid frame offset argument '%s'", + option_arg.str().c_str()); + } else + relative_frame_offset = offset; + break; + } + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + relative_frame_offset.reset(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_frame_select_options); + } + + llvm::Optional<int32_t> relative_frame_offset; + }; + + CommandObjectFrameSelect(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "frame select", "Select the current stack frame by " + "index from within the current thread " + "(see 'thread backtrace'.)", + nullptr, + eCommandRequiresThread | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData index_arg; + + // Define the first (and only) variant of this arg. + index_arg.arg_type = eArgTypeFrameIndex; + index_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(index_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectFrameSelect() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + // No need to check "thread" for validity as eCommandRequiresThread ensures + // it is valid + Thread *thread = m_exe_ctx.GetThreadPtr(); + + uint32_t frame_idx = UINT32_MAX; + if (m_options.relative_frame_offset.hasValue()) { + // The one and only argument is a signed relative frame index + frame_idx = thread->GetSelectedFrameIndex(); + if (frame_idx == UINT32_MAX) + frame_idx = 0; + + if (*m_options.relative_frame_offset < 0) { + if (static_cast<int32_t>(frame_idx) >= + -*m_options.relative_frame_offset) + frame_idx += *m_options.relative_frame_offset; + else { + if (frame_idx == 0) { + // If you are already at the bottom of the stack, then just warn + // and don't reset the frame. + result.AppendError("Already at the bottom of the stack."); + result.SetStatus(eReturnStatusFailed); + return false; + } else + frame_idx = 0; + } + } else if (*m_options.relative_frame_offset > 0) { + // I don't want "up 20" where "20" takes you past the top of the stack + // to produce + // an error, but rather to just go to the top. So I have to count the + // stack here... + const uint32_t num_frames = thread->GetStackFrameCount(); + if (static_cast<int32_t>(num_frames - frame_idx) > + *m_options.relative_frame_offset) + frame_idx += *m_options.relative_frame_offset; + else { + if (frame_idx == num_frames - 1) { + // If we are already at the top of the stack, just warn and don't + // reset the frame. + result.AppendError("Already at the top of the stack."); + result.SetStatus(eReturnStatusFailed); + return false; + } else + frame_idx = num_frames - 1; + } + } + } else { + if (command.GetArgumentCount() > 1) { + result.AppendErrorWithFormat( + "too many arguments; expected frame-index, saw '%s'.\n", + command[0].c_str()); + m_options.GenerateOptionUsage( + result.GetErrorStream(), this, + GetCommandInterpreter().GetDebugger().GetTerminalWidth()); + return false; + } + + if (command.GetArgumentCount() == 1) { + if (command[0].ref().getAsInteger(0, frame_idx)) { + result.AppendErrorWithFormat("invalid frame index argument '%s'.", + command[0].c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else if (command.GetArgumentCount() == 0) { + frame_idx = thread->GetSelectedFrameIndex(); + if (frame_idx == UINT32_MAX) { + frame_idx = 0; + } + } + } + + bool success = thread->SetSelectedFrameByIndexNoisily( + frame_idx, result.GetOutputStream()); + if (success) { + m_exe_ctx.SetFrameSP(thread->GetSelectedFrame()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Frame index (%u) out of range.\n", + frame_idx); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } + +protected: + CommandOptions m_options; +}; + +#pragma mark CommandObjectFrameVariable +// List images with associated information +class CommandObjectFrameVariable : public CommandObjectParsed { +public: + CommandObjectFrameVariable(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "frame variable", + "Show variables for the current stack frame. Defaults to all " + "arguments and local variables in scope. Names of argument, " + "local, file static and file global variables can be specified. " + "Children of aggregate variables can be specified such as " + "'var->child.x'. The -> and [] operators in 'frame variable' do " + "not invoke operator overloads if they exist, but directly access " + "the specified element. If you want to trigger operator overloads " + "use the expression command to print the variable instead." + "\nIt is worth noting that except for overloaded " + "operators, when printing local variables 'expr local_var' and " + "'frame var local_var' produce the same " + "results. However, 'frame variable' is more efficient, since it " + "uses debug information and memory reads directly, rather than " + "parsing and evaluating an expression, which may even involve " + "JITing and running code in the target program.", + nullptr, eCommandRequiresFrame | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused | eCommandRequiresProcess), + m_option_group(), + m_option_variable( + true), // Include the frame specific options by passing "true" + m_option_format(eFormatDefault), + m_varobj_options() { + CommandArgumentEntry arg; + CommandArgumentData var_name_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeVarName; + var_name_arg.arg_repetition = eArgRepeatStar; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(var_name_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + + m_option_group.Append(&m_option_variable, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_option_format, + OptionGroupFormat::OPTION_GROUP_FORMAT | + OptionGroupFormat::OPTION_GROUP_GDB_FMT, + LLDB_OPT_SET_1); + m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectFrameVariable() override = default; + + Options *GetOptions() override { return &m_option_group; } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + // Arguments are the standard source file completer. + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eVariablePathCompletion, + request, nullptr); + } + +protected: + llvm::StringRef GetScopeString(VariableSP var_sp) { + if (!var_sp) + return llvm::StringRef::withNullAsEmpty(nullptr); + + switch (var_sp->GetScope()) { + case eValueTypeVariableGlobal: + return "GLOBAL: "; + case eValueTypeVariableStatic: + return "STATIC: "; + case eValueTypeVariableArgument: + return "ARG: "; + case eValueTypeVariableLocal: + return "LOCAL: "; + case eValueTypeVariableThreadLocal: + return "THREAD: "; + default: + break; + } + + return llvm::StringRef::withNullAsEmpty(nullptr); + } + + bool DoExecute(Args &command, CommandReturnObject &result) override { + // No need to check "frame" for validity as eCommandRequiresFrame ensures + // it is valid + StackFrame *frame = m_exe_ctx.GetFramePtr(); + + Stream &s = result.GetOutputStream(); + + // Be careful about the stack frame, if any summary formatter runs code, it + // might clear the StackFrameList for the thread. So hold onto a shared + // pointer to the frame so it stays alive. + + VariableList *variable_list = + frame->GetVariableList(m_option_variable.show_globals); + + VariableSP var_sp; + ValueObjectSP valobj_sp; + + TypeSummaryImplSP summary_format_sp; + if (!m_option_variable.summary.IsCurrentValueEmpty()) + DataVisualization::NamedSummaryFormats::GetSummaryFormat( + ConstString(m_option_variable.summary.GetCurrentValue()), + summary_format_sp); + else if (!m_option_variable.summary_string.IsCurrentValueEmpty()) + summary_format_sp = std::make_shared<StringSummaryFormat>( + TypeSummaryImpl::Flags(), + m_option_variable.summary_string.GetCurrentValue()); + + DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions( + eLanguageRuntimeDescriptionDisplayVerbosityFull, eFormatDefault, + summary_format_sp)); + + const SymbolContext &sym_ctx = + frame->GetSymbolContext(eSymbolContextFunction); + if (sym_ctx.function && sym_ctx.function->IsTopLevelFunction()) + m_option_variable.show_globals = true; + + if (variable_list) { + const Format format = m_option_format.GetFormat(); + options.SetFormat(format); + + if (!command.empty()) { + VariableList regex_var_list; + + // If we have any args to the variable command, we will make variable + // objects from them... + for (auto &entry : command) { + if (m_option_variable.use_regex) { + const size_t regex_start_index = regex_var_list.GetSize(); + llvm::StringRef name_str = entry.ref(); + RegularExpression regex(name_str); + if (regex.IsValid()) { + size_t num_matches = 0; + const size_t num_new_regex_vars = + variable_list->AppendVariablesIfUnique(regex, regex_var_list, + num_matches); + if (num_new_regex_vars > 0) { + for (size_t regex_idx = regex_start_index, + end_index = regex_var_list.GetSize(); + regex_idx < end_index; ++regex_idx) { + var_sp = regex_var_list.GetVariableAtIndex(regex_idx); + if (var_sp) { + valobj_sp = frame->GetValueObjectForFrameVariable( + var_sp, m_varobj_options.use_dynamic); + if (valobj_sp) { + std::string scope_string; + if (m_option_variable.show_scope) + scope_string = GetScopeString(var_sp).str(); + + if (!scope_string.empty()) + s.PutCString(scope_string); + + if (m_option_variable.show_decl && + var_sp->GetDeclaration().GetFile()) { + bool show_fullpaths = false; + bool show_module = true; + if (var_sp->DumpDeclaration(&s, show_fullpaths, + show_module)) + s.PutCString(": "); + } + valobj_sp->Dump(result.GetOutputStream(), options); + } + } + } + } else if (num_matches == 0) { + result.GetErrorStream().Printf("error: no variables matched " + "the regular expression '%s'.\n", + entry.c_str()); + } + } else { + if (llvm::Error err = regex.GetError()) + result.GetErrorStream().Printf( + "error: %s\n", llvm::toString(std::move(err)).c_str()); + else + result.GetErrorStream().Printf( + "error: unknown regex error when compiling '%s'\n", + entry.c_str()); + } + } else // No regex, either exact variable names or variable + // expressions. + { + Status error; + uint32_t expr_path_options = + StackFrame::eExpressionPathOptionCheckPtrVsMember | + StackFrame::eExpressionPathOptionsAllowDirectIVarAccess | + StackFrame::eExpressionPathOptionsInspectAnonymousUnions; + lldb::VariableSP var_sp; + valobj_sp = frame->GetValueForVariableExpressionPath( + entry.ref(), m_varobj_options.use_dynamic, expr_path_options, + var_sp, error); + if (valobj_sp) { + std::string scope_string; + if (m_option_variable.show_scope) + scope_string = GetScopeString(var_sp).str(); + + if (!scope_string.empty()) + s.PutCString(scope_string); + if (m_option_variable.show_decl && var_sp && + var_sp->GetDeclaration().GetFile()) { + var_sp->GetDeclaration().DumpStopContext(&s, false); + s.PutCString(": "); + } + + options.SetFormat(format); + options.SetVariableFormatDisplayLanguage( + valobj_sp->GetPreferredDisplayLanguage()); + + Stream &output_stream = result.GetOutputStream(); + options.SetRootValueObjectName( + valobj_sp->GetParent() ? entry.c_str() : nullptr); + valobj_sp->Dump(output_stream, options); + } else { + const char *error_cstr = error.AsCString(nullptr); + if (error_cstr) + result.GetErrorStream().Printf("error: %s\n", error_cstr); + else + result.GetErrorStream().Printf("error: unable to find any " + "variable expression path that " + "matches '%s'.\n", + entry.c_str()); + } + } + } + } else // No command arg specified. Use variable_list, instead. + { + const size_t num_variables = variable_list->GetSize(); + if (num_variables > 0) { + for (size_t i = 0; i < num_variables; i++) { + var_sp = variable_list->GetVariableAtIndex(i); + switch (var_sp->GetScope()) { + case eValueTypeVariableGlobal: + if (!m_option_variable.show_globals) + continue; + break; + case eValueTypeVariableStatic: + if (!m_option_variable.show_globals) + continue; + break; + case eValueTypeVariableArgument: + if (!m_option_variable.show_args) + continue; + break; + case eValueTypeVariableLocal: + if (!m_option_variable.show_locals) + continue; + break; + default: + continue; + break; + } + std::string scope_string; + if (m_option_variable.show_scope) + scope_string = GetScopeString(var_sp).str(); + + // Use the variable object code to make sure we are using the same + // APIs as the public API will be using... + valobj_sp = frame->GetValueObjectForFrameVariable( + var_sp, m_varobj_options.use_dynamic); + if (valobj_sp) { + // When dumping all variables, don't print any variables that are + // not in scope to avoid extra unneeded output + if (valobj_sp->IsInScope()) { + if (!valobj_sp->GetTargetSP() + ->GetDisplayRuntimeSupportValues() && + valobj_sp->IsRuntimeSupportValue()) + continue; + + if (!scope_string.empty()) + s.PutCString(scope_string); + + if (m_option_variable.show_decl && + var_sp->GetDeclaration().GetFile()) { + var_sp->GetDeclaration().DumpStopContext(&s, false); + s.PutCString(": "); + } + + options.SetFormat(format); + options.SetVariableFormatDisplayLanguage( + valobj_sp->GetPreferredDisplayLanguage()); + options.SetRootValueObjectName( + var_sp ? var_sp->GetName().AsCString() : nullptr); + valobj_sp->Dump(result.GetOutputStream(), options); + } + } + } + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } + + if (m_option_variable.show_recognized_args) { + auto recognized_frame = frame->GetRecognizedFrame(); + if (recognized_frame) { + ValueObjectListSP recognized_arg_list = + recognized_frame->GetRecognizedArguments(); + if (recognized_arg_list) { + for (auto &rec_value_sp : recognized_arg_list->GetObjects()) { + options.SetFormat(m_option_format.GetFormat()); + options.SetVariableFormatDisplayLanguage( + rec_value_sp->GetPreferredDisplayLanguage()); + options.SetRootValueObjectName(rec_value_sp->GetName().AsCString()); + rec_value_sp->Dump(result.GetOutputStream(), options); + } + } + } + } + + if (m_interpreter.TruncationWarningNecessary()) { + result.GetOutputStream().Printf(m_interpreter.TruncationWarningText(), + m_cmd_name.c_str()); + m_interpreter.TruncationWarningGiven(); + } + + // Increment statistics. + bool res = result.Succeeded(); + Target &target = GetSelectedOrDummyTarget(); + if (res) + target.IncrementStats(StatisticKind::FrameVarSuccess); + else + target.IncrementStats(StatisticKind::FrameVarFailure); + return res; + } + +protected: + OptionGroupOptions m_option_group; + OptionGroupVariable m_option_variable; + OptionGroupFormat m_option_format; + OptionGroupValueObjectDisplay m_varobj_options; +}; + +#pragma mark CommandObjectFrameRecognizer + +#define LLDB_OPTIONS_frame_recognizer_add +#include "CommandOptions.inc" + +class CommandObjectFrameRecognizerAdd : public CommandObjectParsed { +private: + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'l': + m_class_name = std::string(option_arg); + break; + case 's': + m_module = std::string(option_arg); + break; + case 'n': + m_function = std::string(option_arg); + break; + case 'x': + m_regex = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_module = ""; + m_function = ""; + m_class_name = ""; + m_regex = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_frame_recognizer_add_options); + } + + // Instance variables to hold the values for command options. + std::string m_class_name; + std::string m_module; + std::string m_function; + bool m_regex; + }; + + CommandOptions m_options; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override; + +public: + CommandObjectFrameRecognizerAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "frame recognizer add", + "Add a new frame recognizer.", nullptr), + m_options() { + SetHelpLong(R"( +Frame recognizers allow for retrieving information about special frames based on +ABI, arguments or other special properties of that frame, even without source +code or debug info. Currently, one use case is to extract function arguments +that would otherwise be unaccesible, or augment existing arguments. + +Adding a custom frame recognizer is possible by implementing a Python class +and using the 'frame recognizer add' command. The Python class should have a +'get_recognized_arguments' method and it will receive an argument of type +lldb.SBFrame representing the current frame that we are trying to recognize. +The method should return a (possibly empty) list of lldb.SBValue objects that +represent the recognized arguments. + +An example of a recognizer that retrieves the file descriptor values from libc +functions 'read', 'write' and 'close' follows: + + class LibcFdRecognizer(object): + def get_recognized_arguments(self, frame): + if frame.name in ["read", "write", "close"]: + fd = frame.EvaluateExpression("$arg1").unsigned + value = lldb.target.CreateValueFromExpression("fd", "(int)%d" % fd) + return [value] + return [] + +The file containing this implementation can be imported via 'command script +import' and then we can register this recognizer with 'frame recognizer add'. +It's important to restrict the recognizer to the libc library (which is +libsystem_kernel.dylib on macOS) to avoid matching functions with the same name +in other modules: + +(lldb) command script import .../fd_recognizer.py +(lldb) frame recognizer add -l fd_recognizer.LibcFdRecognizer -n read -s libsystem_kernel.dylib + +When the program is stopped at the beginning of the 'read' function in libc, we +can view the recognizer arguments in 'frame variable': + +(lldb) b read +(lldb) r +Process 1234 stopped +* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3 + frame #0: 0x00007fff06013ca0 libsystem_kernel.dylib`read +(lldb) frame variable +(int) fd = 3 + + )"); + } + ~CommandObjectFrameRecognizerAdd() override = default; +}; + +bool CommandObjectFrameRecognizerAdd::DoExecute(Args &command, + CommandReturnObject &result) { +#ifndef LLDB_DISABLE_PYTHON + if (m_options.m_class_name.empty()) { + result.AppendErrorWithFormat( + "%s needs a Python class name (-l argument).\n", m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_module.empty()) { + result.AppendErrorWithFormat("%s needs a module name (-s argument).\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_function.empty()) { + result.AppendErrorWithFormat("%s needs a function name (-n argument).\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + + if (interpreter && + !interpreter->CheckObjectExists(m_options.m_class_name.c_str())) { + result.AppendWarning( + "The provided class does not exist - please define it " + "before attempting to use this frame recognizer"); + } + + StackFrameRecognizerSP recognizer_sp = + StackFrameRecognizerSP(new ScriptedStackFrameRecognizer( + interpreter, m_options.m_class_name.c_str())); + if (m_options.m_regex) { + auto module = + RegularExpressionSP(new RegularExpression(m_options.m_module)); + auto func = + RegularExpressionSP(new RegularExpression(m_options.m_function)); + StackFrameRecognizerManager::AddRecognizer(recognizer_sp, module, func); + } else { + auto module = ConstString(m_options.m_module); + auto func = ConstString(m_options.m_function); + StackFrameRecognizerManager::AddRecognizer(recognizer_sp, module, func); + } +#endif + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); +} + +class CommandObjectFrameRecognizerClear : public CommandObjectParsed { +public: + CommandObjectFrameRecognizerClear(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "frame recognizer clear", + "Delete all frame recognizers.", nullptr) {} + + ~CommandObjectFrameRecognizerClear() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + StackFrameRecognizerManager::RemoveAllRecognizers(); + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +class CommandObjectFrameRecognizerDelete : public CommandObjectParsed { + public: + CommandObjectFrameRecognizerDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "frame recognizer delete", + "Delete an existing frame recognizer.", nullptr) {} + + ~CommandObjectFrameRecognizerDelete() override = default; + + protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.GetArgumentCount() == 0) { + if (!m_interpreter.Confirm( + "About to delete all frame recognizers, do you want to do that?", + true)) { + result.AppendMessage("Operation cancelled..."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + StackFrameRecognizerManager::RemoveAllRecognizers(); + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } + + if (command.GetArgumentCount() != 1) { + result.AppendErrorWithFormat("'%s' takes zero or one arguments.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + uint32_t recognizer_id = + StringConvert::ToUInt32(command.GetArgumentAtIndex(0), 0, 0); + + StackFrameRecognizerManager::RemoveRecognizerWithID(recognizer_id); + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +class CommandObjectFrameRecognizerList : public CommandObjectParsed { + public: + CommandObjectFrameRecognizerList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "frame recognizer list", + "Show a list of active frame recognizers.", + nullptr) {} + + ~CommandObjectFrameRecognizerList() override = default; + + protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + bool any_printed = false; + StackFrameRecognizerManager::ForEach( + [&result, &any_printed](uint32_t recognizer_id, std::string name, + std::string function, std::string symbol, + bool regexp) { + if (name == "") name = "(internal)"; + result.GetOutputStream().Printf( + "%d: %s, module %s, function %s%s\n", recognizer_id, name.c_str(), + function.c_str(), symbol.c_str(), regexp ? " (regexp)" : ""); + any_printed = true; + }); + + if (any_printed) + result.SetStatus(eReturnStatusSuccessFinishResult); + else { + result.GetOutputStream().PutCString("no matching results found.\n"); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + return result.Succeeded(); + } +}; + +class CommandObjectFrameRecognizerInfo : public CommandObjectParsed { + public: + CommandObjectFrameRecognizerInfo(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "frame recognizer info", + "Show which frame recognizer is applied a stack frame (if any).", + nullptr) { + CommandArgumentEntry arg; + CommandArgumentData index_arg; + + // Define the first (and only) variant of this arg. + index_arg.arg_type = eArgTypeFrameIndex; + index_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(index_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectFrameRecognizerInfo() override = default; + + protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + if (process == nullptr) { + result.AppendError("no process"); + result.SetStatus(eReturnStatusFailed); + return false; + } + Thread *thread = m_exe_ctx.GetThreadPtr(); + if (thread == nullptr) { + result.AppendError("no thread"); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (command.GetArgumentCount() != 1) { + result.AppendErrorWithFormat( + "'%s' takes exactly one frame index argument.\n", m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + uint32_t frame_index = + StringConvert::ToUInt32(command.GetArgumentAtIndex(0), 0, 0); + StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_index); + if (!frame_sp) { + result.AppendErrorWithFormat("no frame with index %u", frame_index); + result.SetStatus(eReturnStatusFailed); + return false; + } + + auto recognizer = + StackFrameRecognizerManager::GetRecognizerForFrame(frame_sp); + + Stream &output_stream = result.GetOutputStream(); + output_stream.Printf("frame %d ", frame_index); + if (recognizer) { + output_stream << "is recognized by "; + output_stream << recognizer->GetName(); + } else { + output_stream << "not recognized by any recognizer"; + } + output_stream.EOL(); + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +class CommandObjectFrameRecognizer : public CommandObjectMultiword { + public: + CommandObjectFrameRecognizer(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "frame recognizer", + "Commands for editing and viewing frame recognizers.", + "frame recognizer [<sub-command-options>] ") { + LoadSubCommand( + "add", + CommandObjectSP(new CommandObjectFrameRecognizerAdd(interpreter))); + LoadSubCommand( + "clear", + CommandObjectSP(new CommandObjectFrameRecognizerClear(interpreter))); + LoadSubCommand( + "delete", + CommandObjectSP(new CommandObjectFrameRecognizerDelete(interpreter))); + LoadSubCommand( + "list", + CommandObjectSP(new CommandObjectFrameRecognizerList(interpreter))); + LoadSubCommand( + "info", + CommandObjectSP(new CommandObjectFrameRecognizerInfo(interpreter))); + } + + ~CommandObjectFrameRecognizer() override = default; +}; + +#pragma mark CommandObjectMultiwordFrame + +// CommandObjectMultiwordFrame + +CommandObjectMultiwordFrame::CommandObjectMultiwordFrame( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "frame", "Commands for selecting and " + "examing the current " + "thread's stack frames.", + "frame <subcommand> [<subcommand-options>]") { + LoadSubCommand("diagnose", + CommandObjectSP(new CommandObjectFrameDiagnose(interpreter))); + LoadSubCommand("info", + CommandObjectSP(new CommandObjectFrameInfo(interpreter))); + LoadSubCommand("select", + CommandObjectSP(new CommandObjectFrameSelect(interpreter))); + LoadSubCommand("variable", + CommandObjectSP(new CommandObjectFrameVariable(interpreter))); +#ifndef LLDB_DISABLE_PYTHON + LoadSubCommand( + "recognizer", + CommandObjectSP(new CommandObjectFrameRecognizer(interpreter))); +#endif +} + +CommandObjectMultiwordFrame::~CommandObjectMultiwordFrame() = default; diff --git a/lldb/source/Commands/CommandObjectFrame.h b/lldb/source/Commands/CommandObjectFrame.h new file mode 100644 index 0000000000000..46a59f71733be --- /dev/null +++ b/lldb/source/Commands/CommandObjectFrame.h @@ -0,0 +1,28 @@ +//===-- CommandObjectFrame.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_CommandObjectFrame_h_ +#define liblldb_CommandObjectFrame_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" + +namespace lldb_private { + +// CommandObjectMultiwordFrame + +class CommandObjectMultiwordFrame : public CommandObjectMultiword { +public: + CommandObjectMultiwordFrame(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordFrame() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectFrame_h_ diff --git a/lldb/source/Commands/CommandObjectGUI.cpp b/lldb/source/Commands/CommandObjectGUI.cpp new file mode 100644 index 0000000000000..fac2e96277839 --- /dev/null +++ b/lldb/source/Commands/CommandObjectGUI.cpp @@ -0,0 +1,52 @@ +//===-- CommandObjectGUI.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 "CommandObjectGUI.h" + +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectGUI + +CommandObjectGUI::CommandObjectGUI(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "gui", + "Switch into the curses based GUI mode.", "gui") {} + +CommandObjectGUI::~CommandObjectGUI() {} + +bool CommandObjectGUI::DoExecute(Args &args, CommandReturnObject &result) { +#ifndef LLDB_DISABLE_CURSES + if (args.GetArgumentCount() == 0) { + Debugger &debugger = GetDebugger(); + + File &input = debugger.GetInputFile(); + File &output = debugger.GetOutputFile(); + if (input.GetStream() && output.GetStream() && input.GetIsRealTerminal() && + input.GetIsInteractive()) { + IOHandlerSP io_handler_sp(new IOHandlerCursesGUI(debugger)); + if (io_handler_sp) + debugger.PushIOHandler(io_handler_sp); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("the gui command requires an interactive terminal."); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("the gui command takes no arguments."); + result.SetStatus(eReturnStatusFailed); + } + return true; +#else + result.AppendError("lldb was not build with gui support"); + return false; +#endif +} diff --git a/lldb/source/Commands/CommandObjectGUI.h b/lldb/source/Commands/CommandObjectGUI.h new file mode 100644 index 0000000000000..a19aad18ec357 --- /dev/null +++ b/lldb/source/Commands/CommandObjectGUI.h @@ -0,0 +1,30 @@ +//===-- CommandObjectGUI.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_CommandObjectGUI_h_ +#define liblldb_CommandObjectGUI_h_ + +#include "lldb/Interpreter/CommandObject.h" + +namespace lldb_private { + +// CommandObjectGUI + +class CommandObjectGUI : public CommandObjectParsed { +public: + CommandObjectGUI(CommandInterpreter &interpreter); + + ~CommandObjectGUI() override; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectGUI_h_ diff --git a/lldb/source/Commands/CommandObjectHelp.cpp b/lldb/source/Commands/CommandObjectHelp.cpp new file mode 100644 index 0000000000000..c02a583bf9dfd --- /dev/null +++ b/lldb/source/Commands/CommandObjectHelp.cpp @@ -0,0 +1,223 @@ +//===-- CommandObjectHelp.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 "CommandObjectHelp.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/Options.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectHelp + +void CommandObjectHelp::GenerateAdditionalHelpAvenuesMessage( + Stream *s, llvm::StringRef command, llvm::StringRef prefix, + llvm::StringRef subcommand, bool include_upropos, + bool include_type_lookup) { + if (!s || command.empty()) + return; + + std::string command_str = command.str(); + std::string prefix_str = prefix.str(); + std::string subcommand_str = subcommand.str(); + const std::string &lookup_str = !subcommand_str.empty() ? subcommand_str : command_str; + s->Printf("'%s' is not a known command.\n", command_str.c_str()); + s->Printf("Try '%shelp' to see a current list of commands.\n", + prefix.str().c_str()); + if (include_upropos) { + s->Printf("Try '%sapropos %s' for a list of related commands.\n", + prefix_str.c_str(), lookup_str.c_str()); + } + if (include_type_lookup) { + s->Printf("Try '%stype lookup %s' for information on types, methods, " + "functions, modules, etc.", + prefix_str.c_str(), lookup_str.c_str()); + } +} + +CommandObjectHelp::CommandObjectHelp(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "help", "Show a list of all debugger " + "commands, or give details " + "about a specific command.", + "help [<cmd-name>]"), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData command_arg; + + // Define the first (and only) variant of this arg. + command_arg.arg_type = eArgTypeCommandName; + command_arg.arg_repetition = eArgRepeatStar; + + // There is only one variant this argument could be; put it into the argument + // entry. + arg.push_back(command_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); +} + +CommandObjectHelp::~CommandObjectHelp() = default; + +#define LLDB_OPTIONS_help +#include "CommandOptions.inc" + +llvm::ArrayRef<OptionDefinition> +CommandObjectHelp::CommandOptions::GetDefinitions() { + return llvm::makeArrayRef(g_help_options); +} + +bool CommandObjectHelp::DoExecute(Args &command, CommandReturnObject &result) { + CommandObject::CommandMap::iterator pos; + CommandObject *cmd_obj; + const size_t argc = command.GetArgumentCount(); + + // 'help' doesn't take any arguments, other than command names. If argc is + // 0, we show the user all commands (aliases and user commands if asked for). + // Otherwise every argument must be the name of a command or a sub-command. + if (argc == 0) { + uint32_t cmd_types = CommandInterpreter::eCommandTypesBuiltin; + if (m_options.m_show_aliases) + cmd_types |= CommandInterpreter::eCommandTypesAliases; + if (m_options.m_show_user_defined) + cmd_types |= CommandInterpreter::eCommandTypesUserDef; + if (m_options.m_show_hidden) + cmd_types |= CommandInterpreter::eCommandTypesHidden; + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + m_interpreter.GetHelp(result, cmd_types); // General help + } else { + // Get command object for the first command argument. Only search built-in + // command dictionary. + StringList matches; + auto command_name = command[0].ref(); + cmd_obj = m_interpreter.GetCommandObject(command_name, &matches); + + if (cmd_obj != nullptr) { + StringList matches; + bool all_okay = true; + CommandObject *sub_cmd_obj = cmd_obj; + // Loop down through sub_command dictionaries until we find the command + // object that corresponds to the help command entered. + std::string sub_command; + for (auto &entry : command.entries().drop_front()) { + sub_command = entry.ref(); + matches.Clear(); + if (sub_cmd_obj->IsAlias()) + sub_cmd_obj = + ((CommandAlias *)sub_cmd_obj)->GetUnderlyingCommand().get(); + if (!sub_cmd_obj->IsMultiwordObject()) { + all_okay = false; + break; + } else { + CommandObject *found_cmd; + found_cmd = + sub_cmd_obj->GetSubcommandObject(sub_command.c_str(), &matches); + if (found_cmd == nullptr || matches.GetSize() > 1) { + all_okay = false; + break; + } else + sub_cmd_obj = found_cmd; + } + } + + if (!all_okay || (sub_cmd_obj == nullptr)) { + std::string cmd_string; + command.GetCommandString(cmd_string); + if (matches.GetSize() >= 2) { + StreamString s; + s.Printf("ambiguous command %s", cmd_string.c_str()); + size_t num_matches = matches.GetSize(); + for (size_t match_idx = 0; match_idx < num_matches; match_idx++) { + s.Printf("\n\t%s", matches.GetStringAtIndex(match_idx)); + } + s.Printf("\n"); + result.AppendError(s.GetString()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!sub_cmd_obj) { + StreamString error_msg_stream; + GenerateAdditionalHelpAvenuesMessage( + &error_msg_stream, cmd_string.c_str(), + m_interpreter.GetCommandPrefix(), sub_command.c_str()); + result.AppendError(error_msg_stream.GetString()); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + GenerateAdditionalHelpAvenuesMessage( + &result.GetOutputStream(), cmd_string.c_str(), + m_interpreter.GetCommandPrefix(), sub_command.c_str()); + result.GetOutputStream().Printf( + "\nThe closest match is '%s'. Help on it follows.\n\n", + sub_cmd_obj->GetCommandName().str().c_str()); + } + } + + sub_cmd_obj->GenerateHelpText(result); + std::string alias_full_name; + // Don't use AliasExists here, that only checks exact name matches. If + // the user typed a shorter unique alias name, we should still tell them + // it was an alias. + if (m_interpreter.GetAliasFullName(command_name, alias_full_name)) { + StreamString sstr; + m_interpreter.GetAlias(alias_full_name)->GetAliasExpansion(sstr); + result.GetOutputStream().Printf("\n'%s' is an abbreviation for %s\n", + command[0].c_str(), sstr.GetData()); + } + } else if (matches.GetSize() > 0) { + Stream &output_strm = result.GetOutputStream(); + output_strm.Printf("Help requested with ambiguous command name, possible " + "completions:\n"); + const size_t match_count = matches.GetSize(); + for (size_t i = 0; i < match_count; i++) { + output_strm.Printf("\t%s\n", matches.GetStringAtIndex(i)); + } + } else { + // Maybe the user is asking for help about a command argument rather than + // a command. + const CommandArgumentType arg_type = + CommandObject::LookupArgumentName(command_name); + if (arg_type != eArgTypeLastArg) { + Stream &output_strm = result.GetOutputStream(); + CommandObject::GetArgumentHelp(output_strm, arg_type, m_interpreter); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + StreamString error_msg_stream; + GenerateAdditionalHelpAvenuesMessage(&error_msg_stream, command_name, + m_interpreter.GetCommandPrefix(), + ""); + result.AppendError(error_msg_stream.GetString()); + result.SetStatus(eReturnStatusFailed); + } + } + } + + return result.Succeeded(); +} + +void CommandObjectHelp::HandleCompletion(CompletionRequest &request) { + // Return the completions of the commands in the help system: + if (request.GetCursorIndex() == 0) { + m_interpreter.HandleCompletionMatches(request); + return; + } + CommandObject *cmd_obj = + m_interpreter.GetCommandObject(request.GetParsedLine()[0].ref()); + + // The command that they are getting help on might be ambiguous, in which + // case we should complete that, otherwise complete with the command the + // user is getting help on... + + if (cmd_obj) { + request.ShiftArguments(); + cmd_obj->HandleCompletion(request); + return; + } + m_interpreter.HandleCompletionMatches(request); +} diff --git a/lldb/source/Commands/CommandObjectHelp.h b/lldb/source/Commands/CommandObjectHelp.h new file mode 100644 index 0000000000000..52a00ac79ff9a --- /dev/null +++ b/lldb/source/Commands/CommandObjectHelp.h @@ -0,0 +1,87 @@ +//===-- CommandObjectHelp.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_CommandObjectHelp_h_ +#define liblldb_CommandObjectHelp_h_ + +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/Options.h" + +namespace lldb_private { + +// CommandObjectHelp + +class CommandObjectHelp : public CommandObjectParsed { +public: + CommandObjectHelp(CommandInterpreter &interpreter); + + ~CommandObjectHelp() override; + + void HandleCompletion(CompletionRequest &request) override; + + static void GenerateAdditionalHelpAvenuesMessage( + Stream *s, llvm::StringRef command, llvm::StringRef prefix, + llvm::StringRef subcommand, bool include_upropos = true, + bool include_type_lookup = true); + + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override {} + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'a': + m_show_aliases = false; + break; + case 'u': + m_show_user_defined = false; + break; + case 'h': + m_show_hidden = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_show_aliases = true; + m_show_user_defined = true; + m_show_hidden = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override; + + // Instance variables to hold the values for command options. + + bool m_show_aliases; + bool m_show_user_defined; + bool m_show_hidden; + }; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override; + +private: + CommandOptions m_options; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectHelp_h_ diff --git a/lldb/source/Commands/CommandObjectLanguage.cpp b/lldb/source/Commands/CommandObjectLanguage.cpp new file mode 100644 index 0000000000000..47c9e2a52023e --- /dev/null +++ b/lldb/source/Commands/CommandObjectLanguage.cpp @@ -0,0 +1,30 @@ +//===-- CommandObjectLanguage.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 "CommandObjectLanguage.h" + +#include "lldb/Host/Host.h" + +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" + +#include "lldb/Target/Language.h" +#include "lldb/Target/LanguageRuntime.h" + +using namespace lldb; +using namespace lldb_private; + +CommandObjectLanguage::CommandObjectLanguage(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "language", "Commands specific to a source language.", + "language <language-name> <subcommand> [<subcommand-options>]") { + // Let the LanguageRuntime populates this command with subcommands + LanguageRuntime::InitializeCommands(this); +} + +CommandObjectLanguage::~CommandObjectLanguage() {} diff --git a/lldb/source/Commands/CommandObjectLanguage.h b/lldb/source/Commands/CommandObjectLanguage.h new file mode 100644 index 0000000000000..b86457c99af37 --- /dev/null +++ b/lldb/source/Commands/CommandObjectLanguage.h @@ -0,0 +1,29 @@ +//===-- CommandObjectLanguage.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_CommandObjectLanguage_h_ +#define liblldb_CommandObjectLanguage_h_ + + + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/lldb-types.h" + +namespace lldb_private { +class CommandObjectLanguage : public CommandObjectMultiword { +public: + CommandObjectLanguage(CommandInterpreter &interpreter); + + ~CommandObjectLanguage() override; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result); +}; +} // namespace lldb_private + +#endif // liblldb_CommandObjectLanguage_h_ diff --git a/lldb/source/Commands/CommandObjectLog.cpp b/lldb/source/Commands/CommandObjectLog.cpp new file mode 100644 index 0000000000000..31a876c3430ec --- /dev/null +++ b/lldb/source/Commands/CommandObjectLog.cpp @@ -0,0 +1,390 @@ +//===-- CommandObjectLog.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 "CommandObjectLog.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Symbol/LineTable.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Symbol/SymbolVendor.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/Timer.h" + +using namespace lldb; +using namespace lldb_private; + +#define LLDB_OPTIONS_log +#include "CommandOptions.inc" + +/// Common completion logic for log enable/disable. +static void CompleteEnableDisable(CompletionRequest &request) { + size_t arg_index = request.GetCursorIndex(); + if (arg_index == 0) { // We got: log enable/disable x[tab] + for (llvm::StringRef channel : Log::ListChannels()) + request.TryCompleteCurrentArg(channel); + } else if (arg_index >= 1) { // We got: log enable/disable channel x[tab] + llvm::StringRef channel = request.GetParsedLine().GetArgumentAtIndex(0); + Log::ForEachChannelCategory( + channel, [&request](llvm::StringRef name, llvm::StringRef desc) { + request.TryCompleteCurrentArg(name, desc); + }); + } +} + +class CommandObjectLogEnable : public CommandObjectParsed { +public: + // Constructors and Destructors + CommandObjectLogEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "log enable", + "Enable logging for a single log channel.", + nullptr), + m_options() { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData channel_arg; + CommandArgumentData category_arg; + + // Define the first (and only) variant of this arg. + channel_arg.arg_type = eArgTypeLogChannel; + channel_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(channel_arg); + + category_arg.arg_type = eArgTypeLogCategory; + category_arg.arg_repetition = eArgRepeatPlus; + + arg2.push_back(category_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + } + + ~CommandObjectLogEnable() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), log_file(), log_options(0) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + log_file.SetFile(option_arg, FileSpec::Style::native); + FileSystem::Instance().Resolve(log_file); + break; + case 't': + log_options |= LLDB_LOG_OPTION_THREADSAFE; + break; + case 'v': + log_options |= LLDB_LOG_OPTION_VERBOSE; + break; + case 's': + log_options |= LLDB_LOG_OPTION_PREPEND_SEQUENCE; + break; + case 'T': + log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP; + break; + case 'p': + log_options |= LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD; + break; + case 'n': + log_options |= LLDB_LOG_OPTION_PREPEND_THREAD_NAME; + break; + case 'S': + log_options |= LLDB_LOG_OPTION_BACKTRACE; + break; + case 'a': + log_options |= LLDB_LOG_OPTION_APPEND; + break; + case 'F': + log_options |= LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + log_file.Clear(); + log_options = 0; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_log_options); + } + + // Instance variables to hold the values for command options. + + FileSpec log_file; + uint32_t log_options; + }; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CompleteEnableDisable(request); + } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + if (args.GetArgumentCount() < 2) { + result.AppendErrorWithFormat( + "%s takes a log channel and one or more log types.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Store into a std::string since we're about to shift the channel off. + const std::string channel = args[0].ref(); + args.Shift(); // Shift off the channel + char log_file[PATH_MAX]; + if (m_options.log_file) + m_options.log_file.GetPath(log_file, sizeof(log_file)); + else + log_file[0] = '\0'; + + std::string error; + llvm::raw_string_ostream error_stream(error); + bool success = + GetDebugger().EnableLog(channel, args.GetArgumentArrayRef(), log_file, + m_options.log_options, error_stream); + result.GetErrorStream() << error_stream.str(); + + if (success) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + else + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +class CommandObjectLogDisable : public CommandObjectParsed { +public: + // Constructors and Destructors + CommandObjectLogDisable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "log disable", + "Disable one or more log channel categories.", + nullptr) { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData channel_arg; + CommandArgumentData category_arg; + + // Define the first (and only) variant of this arg. + channel_arg.arg_type = eArgTypeLogChannel; + channel_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(channel_arg); + + category_arg.arg_type = eArgTypeLogCategory; + category_arg.arg_repetition = eArgRepeatPlus; + + arg2.push_back(category_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + } + + ~CommandObjectLogDisable() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CompleteEnableDisable(request); + } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + if (args.empty()) { + result.AppendErrorWithFormat( + "%s takes a log channel and one or more log types.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const std::string channel = args[0].ref(); + args.Shift(); // Shift off the channel + if (channel == "all") { + Log::DisableAllLogChannels(); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + std::string error; + llvm::raw_string_ostream error_stream(error); + if (Log::DisableLogChannel(channel, args.GetArgumentArrayRef(), + error_stream)) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + result.GetErrorStream() << error_stream.str(); + } + return result.Succeeded(); + } +}; + +class CommandObjectLogList : public CommandObjectParsed { +public: + // Constructors and Destructors + CommandObjectLogList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "log list", + "List the log categories for one or more log " + "channels. If none specified, lists them all.", + nullptr) { + CommandArgumentEntry arg; + CommandArgumentData channel_arg; + + // Define the first (and only) variant of this arg. + channel_arg.arg_type = eArgTypeLogChannel; + channel_arg.arg_repetition = eArgRepeatStar; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(channel_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectLogList() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + for (llvm::StringRef channel : Log::ListChannels()) + request.TryCompleteCurrentArg(channel); + } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + std::string output; + llvm::raw_string_ostream output_stream(output); + if (args.empty()) { + Log::ListAllLogChannels(output_stream); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + bool success = true; + for (const auto &entry : args.entries()) + success = + success && Log::ListChannelCategories(entry.ref(), output_stream); + if (success) + result.SetStatus(eReturnStatusSuccessFinishResult); + } + result.GetOutputStream() << output_stream.str(); + return result.Succeeded(); + } +}; + +class CommandObjectLogTimer : public CommandObjectParsed { +public: + // Constructors and Destructors + CommandObjectLogTimer(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "log timers", + "Enable, disable, dump, and reset LLDB internal " + "performance timers.", + "log timers < enable <depth> | disable | dump | " + "increment <bool> | reset >") {} + + ~CommandObjectLogTimer() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + result.SetStatus(eReturnStatusFailed); + + if (args.GetArgumentCount() == 1) { + auto sub_command = args[0].ref(); + + if (sub_command.equals_lower("enable")) { + Timer::SetDisplayDepth(UINT32_MAX); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else if (sub_command.equals_lower("disable")) { + Timer::DumpCategoryTimes(&result.GetOutputStream()); + Timer::SetDisplayDepth(0); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else if (sub_command.equals_lower("dump")) { + Timer::DumpCategoryTimes(&result.GetOutputStream()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else if (sub_command.equals_lower("reset")) { + Timer::ResetCategoryTimes(); + result.SetStatus(eReturnStatusSuccessFinishResult); + } + } else if (args.GetArgumentCount() == 2) { + auto sub_command = args[0].ref(); + auto param = args[1].ref(); + + if (sub_command.equals_lower("enable")) { + uint32_t depth; + if (param.consumeInteger(0, depth)) { + result.AppendError( + "Could not convert enable depth to an unsigned integer."); + } else { + Timer::SetDisplayDepth(depth); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + } else if (sub_command.equals_lower("increment")) { + bool success; + bool increment = OptionArgParser::ToBoolean(param, false, &success); + if (success) { + Timer::SetQuiet(!increment); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else + result.AppendError("Could not convert increment value to boolean."); + } + } + + if (!result.Succeeded()) { + result.AppendError("Missing subcommand"); + result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str()); + } + return result.Succeeded(); + } +}; + +CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "log", + "Commands controlling LLDB internal logging.", + "log <subcommand> [<command-options>]") { + LoadSubCommand("enable", + CommandObjectSP(new CommandObjectLogEnable(interpreter))); + LoadSubCommand("disable", + CommandObjectSP(new CommandObjectLogDisable(interpreter))); + LoadSubCommand("list", + CommandObjectSP(new CommandObjectLogList(interpreter))); + LoadSubCommand("timers", + CommandObjectSP(new CommandObjectLogTimer(interpreter))); +} + +CommandObjectLog::~CommandObjectLog() = default; diff --git a/lldb/source/Commands/CommandObjectLog.h b/lldb/source/Commands/CommandObjectLog.h new file mode 100644 index 0000000000000..b2da900e21edb --- /dev/null +++ b/lldb/source/Commands/CommandObjectLog.h @@ -0,0 +1,35 @@ +//===-- CommandObjectLog.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_CommandObjectLog_h_ +#define liblldb_CommandObjectLog_h_ + +#include <map> +#include <string> + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +// CommandObjectLog + +class CommandObjectLog : public CommandObjectMultiword { +public: + // Constructors and Destructors + CommandObjectLog(CommandInterpreter &interpreter); + + ~CommandObjectLog() override; + +private: + // For CommandObjectLog only + DISALLOW_COPY_AND_ASSIGN(CommandObjectLog); +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectLog_h_ diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp new file mode 100644 index 0000000000000..38bd3d1790969 --- /dev/null +++ b/lldb/source/Commands/CommandObjectMemory.cpp @@ -0,0 +1,1786 @@ +//===-- CommandObjectMemory.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 "CommandObjectMemory.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/DumpDataExtractor.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Section.h" +#include "lldb/Core/ValueObjectMemory.h" +#include "lldb/DataFormatters/ValueObjectPrinter.h" +#include "lldb/Expression/ExpressionVariable.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupFormat.h" +#include "lldb/Interpreter/OptionGroupOutputFile.h" +#include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" +#include "lldb/Interpreter/OptionValueLanguage.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/MemoryHistory.h" +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/DataBufferLLVM.h" +#include "lldb/Utility/StreamString.h" + +#include "lldb/lldb-private.h" + +#include <cinttypes> +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +#define LLDB_OPTIONS_memory_read +#include "CommandOptions.inc" + +class OptionGroupReadMemory : public OptionGroup { +public: + OptionGroupReadMemory() + : m_num_per_line(1, 1), m_output_as_binary(false), m_view_as_type(), + m_offset(0, 0), m_language_for_type(eLanguageTypeUnknown) {} + + ~OptionGroupReadMemory() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_memory_read_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + const int short_option = g_memory_read_options[option_idx].short_option; + + switch (short_option) { + case 'l': + error = m_num_per_line.SetValueFromString(option_value); + if (m_num_per_line.GetCurrentValue() == 0) + error.SetErrorStringWithFormat( + "invalid value for --num-per-line option '%s'", + option_value.str().c_str()); + break; + + case 'b': + m_output_as_binary = true; + break; + + case 't': + error = m_view_as_type.SetValueFromString(option_value); + break; + + case 'r': + m_force = true; + break; + + case 'x': + error = m_language_for_type.SetValueFromString(option_value); + break; + + case 'E': + error = m_offset.SetValueFromString(option_value); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_num_per_line.Clear(); + m_output_as_binary = false; + m_view_as_type.Clear(); + m_force = false; + m_offset.Clear(); + m_language_for_type.Clear(); + } + + Status FinalizeSettings(Target *target, OptionGroupFormat &format_options) { + Status error; + OptionValueUInt64 &byte_size_value = format_options.GetByteSizeValue(); + OptionValueUInt64 &count_value = format_options.GetCountValue(); + const bool byte_size_option_set = byte_size_value.OptionWasSet(); + const bool num_per_line_option_set = m_num_per_line.OptionWasSet(); + const bool count_option_set = format_options.GetCountValue().OptionWasSet(); + + switch (format_options.GetFormat()) { + default: + break; + + case eFormatBoolean: + if (!byte_size_option_set) + byte_size_value = 1; + if (!num_per_line_option_set) + m_num_per_line = 1; + if (!count_option_set) + format_options.GetCountValue() = 8; + break; + + case eFormatCString: + break; + + case eFormatInstruction: + if (count_option_set) + byte_size_value = target->GetArchitecture().GetMaximumOpcodeByteSize(); + m_num_per_line = 1; + break; + + case eFormatAddressInfo: + if (!byte_size_option_set) + byte_size_value = target->GetArchitecture().GetAddressByteSize(); + m_num_per_line = 1; + if (!count_option_set) + format_options.GetCountValue() = 8; + break; + + case eFormatPointer: + byte_size_value = target->GetArchitecture().GetAddressByteSize(); + if (!num_per_line_option_set) + m_num_per_line = 4; + if (!count_option_set) + format_options.GetCountValue() = 8; + break; + + case eFormatBinary: + case eFormatFloat: + case eFormatOctal: + case eFormatDecimal: + case eFormatEnum: + case eFormatUnicode8: + case eFormatUnicode16: + case eFormatUnicode32: + case eFormatUnsigned: + case eFormatHexFloat: + if (!byte_size_option_set) + byte_size_value = 4; + if (!num_per_line_option_set) + m_num_per_line = 1; + if (!count_option_set) + format_options.GetCountValue() = 8; + break; + + case eFormatBytes: + case eFormatBytesWithASCII: + if (byte_size_option_set) { + if (byte_size_value > 1) + error.SetErrorStringWithFormat( + "display format (bytes/bytes with ASCII) conflicts with the " + "specified byte size %" PRIu64 "\n" + "\tconsider using a different display format or don't specify " + "the byte size.", + byte_size_value.GetCurrentValue()); + } else + byte_size_value = 1; + if (!num_per_line_option_set) + m_num_per_line = 16; + if (!count_option_set) + format_options.GetCountValue() = 32; + break; + + case eFormatCharArray: + case eFormatChar: + case eFormatCharPrintable: + if (!byte_size_option_set) + byte_size_value = 1; + if (!num_per_line_option_set) + m_num_per_line = 32; + if (!count_option_set) + format_options.GetCountValue() = 64; + break; + + case eFormatComplex: + if (!byte_size_option_set) + byte_size_value = 8; + if (!num_per_line_option_set) + m_num_per_line = 1; + if (!count_option_set) + format_options.GetCountValue() = 8; + break; + + case eFormatComplexInteger: + if (!byte_size_option_set) + byte_size_value = 8; + if (!num_per_line_option_set) + m_num_per_line = 1; + if (!count_option_set) + format_options.GetCountValue() = 8; + break; + + case eFormatHex: + if (!byte_size_option_set) + byte_size_value = 4; + if (!num_per_line_option_set) { + switch (byte_size_value) { + case 1: + case 2: + m_num_per_line = 8; + break; + case 4: + m_num_per_line = 4; + break; + case 8: + m_num_per_line = 2; + break; + default: + m_num_per_line = 1; + break; + } + } + if (!count_option_set) + count_value = 8; + break; + + case eFormatVectorOfChar: + case eFormatVectorOfSInt8: + case eFormatVectorOfUInt8: + case eFormatVectorOfSInt16: + case eFormatVectorOfUInt16: + case eFormatVectorOfSInt32: + case eFormatVectorOfUInt32: + case eFormatVectorOfSInt64: + case eFormatVectorOfUInt64: + case eFormatVectorOfFloat16: + case eFormatVectorOfFloat32: + case eFormatVectorOfFloat64: + case eFormatVectorOfUInt128: + if (!byte_size_option_set) + byte_size_value = 128; + if (!num_per_line_option_set) + m_num_per_line = 1; + if (!count_option_set) + count_value = 4; + break; + } + return error; + } + + bool AnyOptionWasSet() const { + return m_num_per_line.OptionWasSet() || m_output_as_binary || + m_view_as_type.OptionWasSet() || m_offset.OptionWasSet() || + m_language_for_type.OptionWasSet(); + } + + OptionValueUInt64 m_num_per_line; + bool m_output_as_binary; + OptionValueString m_view_as_type; + bool m_force; + OptionValueUInt64 m_offset; + OptionValueLanguage m_language_for_type; +}; + +// Read memory from the inferior process +class CommandObjectMemoryRead : public CommandObjectParsed { +public: + CommandObjectMemoryRead(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "memory read", + "Read from the memory of the current target process.", nullptr, + eCommandRequiresTarget | eCommandProcessMustBePaused), + m_option_group(), m_format_options(eFormatBytesWithASCII, 1, 8), + m_memory_options(), m_outfile_options(), m_varobj_options(), + m_next_addr(LLDB_INVALID_ADDRESS), m_prev_byte_size(0), + m_prev_format_options(eFormatBytesWithASCII, 1, 8), + m_prev_memory_options(), m_prev_outfile_options(), + m_prev_varobj_options() { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData start_addr_arg; + CommandArgumentData end_addr_arg; + + // Define the first (and only) variant of this arg. + start_addr_arg.arg_type = eArgTypeAddressOrExpression; + start_addr_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(start_addr_arg); + + // Define the first (and only) variant of this arg. + end_addr_arg.arg_type = eArgTypeAddressOrExpression; + end_addr_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(end_addr_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + + // Add the "--format" and "--count" options to group 1 and 3 + m_option_group.Append(&m_format_options, + OptionGroupFormat::OPTION_GROUP_FORMAT | + OptionGroupFormat::OPTION_GROUP_COUNT, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2 | LLDB_OPT_SET_3); + m_option_group.Append(&m_format_options, + OptionGroupFormat::OPTION_GROUP_GDB_FMT, + LLDB_OPT_SET_1 | LLDB_OPT_SET_3); + // Add the "--size" option to group 1 and 2 + m_option_group.Append(&m_format_options, + OptionGroupFormat::OPTION_GROUP_SIZE, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2); + m_option_group.Append(&m_memory_options); + m_option_group.Append(&m_outfile_options, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2 | LLDB_OPT_SET_3); + m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_3); + m_option_group.Finalize(); + } + + ~CommandObjectMemoryRead() override = default; + + Options *GetOptions() override { return &m_option_group; } + + const char *GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + return m_cmd_name.c_str(); + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + // No need to check "target" for validity as eCommandRequiresTarget ensures + // it is valid + Target *target = m_exe_ctx.GetTargetPtr(); + + const size_t argc = command.GetArgumentCount(); + + if ((argc == 0 && m_next_addr == LLDB_INVALID_ADDRESS) || argc > 2) { + result.AppendErrorWithFormat("%s takes a start address expression with " + "an optional end address expression.\n", + m_cmd_name.c_str()); + result.AppendRawWarning("Expressions should be quoted if they contain " + "spaces or other special characters.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + CompilerType compiler_type; + Status error; + + const char *view_as_type_cstr = + m_memory_options.m_view_as_type.GetCurrentValue(); + if (view_as_type_cstr && view_as_type_cstr[0]) { + // We are viewing memory as a type + + const bool exact_match = false; + TypeList type_list; + uint32_t reference_count = 0; + uint32_t pointer_count = 0; + size_t idx; + +#define ALL_KEYWORDS \ + KEYWORD("const") \ + KEYWORD("volatile") \ + KEYWORD("restrict") \ + KEYWORD("struct") \ + KEYWORD("class") \ + KEYWORD("union") + +#define KEYWORD(s) s, + static const char *g_keywords[] = {ALL_KEYWORDS}; +#undef KEYWORD + +#define KEYWORD(s) (sizeof(s) - 1), + static const int g_keyword_lengths[] = {ALL_KEYWORDS}; +#undef KEYWORD + +#undef ALL_KEYWORDS + + static size_t g_num_keywords = sizeof(g_keywords) / sizeof(const char *); + std::string type_str(view_as_type_cstr); + + // Remove all instances of g_keywords that are followed by spaces + for (size_t i = 0; i < g_num_keywords; ++i) { + const char *keyword = g_keywords[i]; + int keyword_len = g_keyword_lengths[i]; + + idx = 0; + while ((idx = type_str.find(keyword, idx)) != std::string::npos) { + if (type_str[idx + keyword_len] == ' ' || + type_str[idx + keyword_len] == '\t') { + type_str.erase(idx, keyword_len + 1); + idx = 0; + } else { + idx += keyword_len; + } + } + } + bool done = type_str.empty(); + // + idx = type_str.find_first_not_of(" \t"); + if (idx > 0 && idx != std::string::npos) + type_str.erase(0, idx); + while (!done) { + // Strip trailing spaces + if (type_str.empty()) + done = true; + else { + switch (type_str[type_str.size() - 1]) { + case '*': + ++pointer_count; + LLVM_FALLTHROUGH; + case ' ': + case '\t': + type_str.erase(type_str.size() - 1); + break; + + case '&': + if (reference_count == 0) { + reference_count = 1; + type_str.erase(type_str.size() - 1); + } else { + result.AppendErrorWithFormat("invalid type string: '%s'\n", + view_as_type_cstr); + result.SetStatus(eReturnStatusFailed); + return false; + } + break; + + default: + done = true; + break; + } + } + } + + llvm::DenseSet<lldb_private::SymbolFile *> searched_symbol_files; + ConstString lookup_type_name(type_str.c_str()); + StackFrame *frame = m_exe_ctx.GetFramePtr(); + ModuleSP search_first; + if (frame) { + search_first = frame->GetSymbolContext(eSymbolContextModule).module_sp; + } + target->GetImages().FindTypes(search_first.get(), lookup_type_name, + exact_match, 1, searched_symbol_files, + type_list); + + if (type_list.GetSize() == 0 && lookup_type_name.GetCString()) { + LanguageType language_for_type = + m_memory_options.m_language_for_type.GetCurrentValue(); + std::set<LanguageType> languages_to_check; + if (language_for_type != eLanguageTypeUnknown) { + languages_to_check.insert(language_for_type); + } else { + languages_to_check = Language::GetSupportedLanguages(); + } + + std::set<CompilerType> user_defined_types; + for (auto lang : languages_to_check) { + if (auto *persistent_vars = + target->GetPersistentExpressionStateForLanguage(lang)) { + if (llvm::Optional<CompilerType> type = + persistent_vars->GetCompilerTypeFromPersistentDecl( + lookup_type_name)) { + user_defined_types.emplace(*type); + } + } + } + + if (user_defined_types.size() > 1) { + result.AppendErrorWithFormat( + "Mutiple types found matching raw type '%s', please disambiguate " + "by specifying the language with -x", + lookup_type_name.GetCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (user_defined_types.size() == 1) { + compiler_type = *user_defined_types.begin(); + } + } + + if (!compiler_type.IsValid()) { + if (type_list.GetSize() == 0) { + result.AppendErrorWithFormat("unable to find any types that match " + "the raw type '%s' for full type '%s'\n", + lookup_type_name.GetCString(), + view_as_type_cstr); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + TypeSP type_sp(type_list.GetTypeAtIndex(0)); + compiler_type = type_sp->GetFullCompilerType(); + } + } + + while (pointer_count > 0) { + CompilerType pointer_type = compiler_type.GetPointerType(); + if (pointer_type.IsValid()) + compiler_type = pointer_type; + else { + result.AppendError("unable make a pointer type\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + --pointer_count; + } + + llvm::Optional<uint64_t> size = compiler_type.GetByteSize(nullptr); + if (!size) { + result.AppendErrorWithFormat( + "unable to get the byte size of the type '%s'\n", + view_as_type_cstr); + result.SetStatus(eReturnStatusFailed); + return false; + } + m_format_options.GetByteSizeValue() = *size; + + if (!m_format_options.GetCountValue().OptionWasSet()) + m_format_options.GetCountValue() = 1; + } else { + error = m_memory_options.FinalizeSettings(target, m_format_options); + } + + // Look for invalid combinations of settings + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + lldb::addr_t addr; + size_t total_byte_size = 0; + if (argc == 0) { + // Use the last address and byte size and all options as they were if no + // options have been set + addr = m_next_addr; + total_byte_size = m_prev_byte_size; + compiler_type = m_prev_compiler_type; + if (!m_format_options.AnyOptionWasSet() && + !m_memory_options.AnyOptionWasSet() && + !m_outfile_options.AnyOptionWasSet() && + !m_varobj_options.AnyOptionWasSet()) { + m_format_options = m_prev_format_options; + m_memory_options = m_prev_memory_options; + m_outfile_options = m_prev_outfile_options; + m_varobj_options = m_prev_varobj_options; + } + } + + size_t item_count = m_format_options.GetCountValue().GetCurrentValue(); + + // TODO For non-8-bit byte addressable architectures this needs to be + // revisited to fully support all lldb's range of formatting options. + // Furthermore code memory reads (for those architectures) will not be + // correctly formatted even w/o formatting options. + size_t item_byte_size = + target->GetArchitecture().GetDataByteSize() > 1 + ? target->GetArchitecture().GetDataByteSize() + : m_format_options.GetByteSizeValue().GetCurrentValue(); + + const size_t num_per_line = + m_memory_options.m_num_per_line.GetCurrentValue(); + + if (total_byte_size == 0) { + total_byte_size = item_count * item_byte_size; + if (total_byte_size == 0) + total_byte_size = 32; + } + + if (argc > 0) + addr = OptionArgParser::ToAddress(&m_exe_ctx, command[0].ref(), + LLDB_INVALID_ADDRESS, &error); + + if (addr == LLDB_INVALID_ADDRESS) { + result.AppendError("invalid start address expression."); + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (argc == 2) { + lldb::addr_t end_addr = OptionArgParser::ToAddress( + &m_exe_ctx, command[1].ref(), LLDB_INVALID_ADDRESS, nullptr); + if (end_addr == LLDB_INVALID_ADDRESS) { + result.AppendError("invalid end address expression."); + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (end_addr <= addr) { + result.AppendErrorWithFormat( + "end address (0x%" PRIx64 + ") must be greater that the start address (0x%" PRIx64 ").\n", + end_addr, addr); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (m_format_options.GetCountValue().OptionWasSet()) { + result.AppendErrorWithFormat( + "specify either the end address (0x%" PRIx64 + ") or the count (--count %" PRIu64 "), not both.\n", + end_addr, (uint64_t)item_count); + result.SetStatus(eReturnStatusFailed); + return false; + } + + total_byte_size = end_addr - addr; + item_count = total_byte_size / item_byte_size; + } + + uint32_t max_unforced_size = target->GetMaximumMemReadSize(); + + if (total_byte_size > max_unforced_size && !m_memory_options.m_force) { + result.AppendErrorWithFormat( + "Normally, \'memory read\' will not read over %" PRIu32 + " bytes of data.\n", + max_unforced_size); + result.AppendErrorWithFormat( + "Please use --force to override this restriction just once.\n"); + result.AppendErrorWithFormat("or set target.max-memory-read-size if you " + "will often need a larger limit.\n"); + return false; + } + + DataBufferSP data_sp; + size_t bytes_read = 0; + if (compiler_type.GetOpaqueQualType()) { + // Make sure we don't display our type as ASCII bytes like the default + // memory read + if (!m_format_options.GetFormatValue().OptionWasSet()) + m_format_options.GetFormatValue().SetCurrentValue(eFormatDefault); + + llvm::Optional<uint64_t> size = compiler_type.GetByteSize(nullptr); + if (!size) { + result.AppendError("can't get size of type"); + return false; + } + bytes_read = *size * m_format_options.GetCountValue().GetCurrentValue(); + + if (argc > 0) + addr = addr + (*size * m_memory_options.m_offset.GetCurrentValue()); + } else if (m_format_options.GetFormatValue().GetCurrentValue() != + eFormatCString) { + data_sp = std::make_shared<DataBufferHeap>(total_byte_size, '\0'); + if (data_sp->GetBytes() == nullptr) { + result.AppendErrorWithFormat( + "can't allocate 0x%" PRIx32 + " bytes for the memory read buffer, specify a smaller size to read", + (uint32_t)total_byte_size); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Address address(addr, nullptr); + bytes_read = target->ReadMemory(address, false, data_sp->GetBytes(), + data_sp->GetByteSize(), error); + if (bytes_read == 0) { + const char *error_cstr = error.AsCString(); + if (error_cstr && error_cstr[0]) { + result.AppendError(error_cstr); + } else { + result.AppendErrorWithFormat( + "failed to read memory from 0x%" PRIx64 ".\n", addr); + } + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (bytes_read < total_byte_size) + result.AppendWarningWithFormat( + "Not all bytes (%" PRIu64 "/%" PRIu64 + ") were able to be read from 0x%" PRIx64 ".\n", + (uint64_t)bytes_read, (uint64_t)total_byte_size, addr); + } else { + // we treat c-strings as a special case because they do not have a fixed + // size + if (m_format_options.GetByteSizeValue().OptionWasSet() && + !m_format_options.HasGDBFormat()) + item_byte_size = m_format_options.GetByteSizeValue().GetCurrentValue(); + else + item_byte_size = target->GetMaximumSizeOfStringSummary(); + if (!m_format_options.GetCountValue().OptionWasSet()) + item_count = 1; + data_sp = std::make_shared<DataBufferHeap>( + (item_byte_size + 1) * item_count, + '\0'); // account for NULLs as necessary + if (data_sp->GetBytes() == nullptr) { + result.AppendErrorWithFormat( + "can't allocate 0x%" PRIx64 + " bytes for the memory read buffer, specify a smaller size to read", + (uint64_t)((item_byte_size + 1) * item_count)); + result.SetStatus(eReturnStatusFailed); + return false; + } + uint8_t *data_ptr = data_sp->GetBytes(); + auto data_addr = addr; + auto count = item_count; + item_count = 0; + bool break_on_no_NULL = false; + while (item_count < count) { + std::string buffer; + buffer.resize(item_byte_size + 1, 0); + Status error; + size_t read = target->ReadCStringFromMemory(data_addr, &buffer[0], + item_byte_size + 1, error); + if (error.Fail()) { + result.AppendErrorWithFormat( + "failed to read memory from 0x%" PRIx64 ".\n", addr); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (item_byte_size == read) { + result.AppendWarningWithFormat( + "unable to find a NULL terminated string at 0x%" PRIx64 + ".Consider increasing the maximum read length.\n", + data_addr); + --read; + break_on_no_NULL = true; + } else + ++read; // account for final NULL byte + + memcpy(data_ptr, &buffer[0], read); + data_ptr += read; + data_addr += read; + bytes_read += read; + item_count++; // if we break early we know we only read item_count + // strings + + if (break_on_no_NULL) + break; + } + data_sp = + std::make_shared<DataBufferHeap>(data_sp->GetBytes(), bytes_read + 1); + } + + m_next_addr = addr + bytes_read; + m_prev_byte_size = bytes_read; + m_prev_format_options = m_format_options; + m_prev_memory_options = m_memory_options; + m_prev_outfile_options = m_outfile_options; + m_prev_varobj_options = m_varobj_options; + m_prev_compiler_type = compiler_type; + + std::unique_ptr<Stream> output_stream_storage; + Stream *output_stream_p = nullptr; + const FileSpec &outfile_spec = + m_outfile_options.GetFile().GetCurrentValue(); + + std::string path = outfile_spec.GetPath(); + if (outfile_spec) { + + auto open_options = File::eOpenOptionWrite | File::eOpenOptionCanCreate; + const bool append = m_outfile_options.GetAppend().GetCurrentValue(); + if (append) + open_options |= File::eOpenOptionAppend; + + auto outfile = FileSystem::Instance().Open(outfile_spec, open_options); + + if (outfile) { + auto outfile_stream_up = + std::make_unique<StreamFile>(std::move(outfile.get())); + if (m_memory_options.m_output_as_binary) { + const size_t bytes_written = + outfile_stream_up->Write(data_sp->GetBytes(), bytes_read); + if (bytes_written > 0) { + result.GetOutputStream().Printf( + "%zi bytes %s to '%s'\n", bytes_written, + append ? "appended" : "written", path.c_str()); + return true; + } else { + result.AppendErrorWithFormat("Failed to write %" PRIu64 + " bytes to '%s'.\n", + (uint64_t)bytes_read, path.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + // We are going to write ASCII to the file just point the + // output_stream to our outfile_stream... + output_stream_storage = std::move(outfile_stream_up); + output_stream_p = output_stream_storage.get(); + } + } else { + result.AppendErrorWithFormat("Failed to open file '%s' for %s:\n", + path.c_str(), append ? "append" : "write"); + + result.AppendError(llvm::toString(outfile.takeError())); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + output_stream_p = &result.GetOutputStream(); + } + + ExecutionContextScope *exe_scope = m_exe_ctx.GetBestExecutionContextScope(); + if (compiler_type.GetOpaqueQualType()) { + for (uint32_t i = 0; i < item_count; ++i) { + addr_t item_addr = addr + (i * item_byte_size); + Address address(item_addr); + StreamString name_strm; + name_strm.Printf("0x%" PRIx64, item_addr); + ValueObjectSP valobj_sp(ValueObjectMemory::Create( + exe_scope, name_strm.GetString(), address, compiler_type)); + if (valobj_sp) { + Format format = m_format_options.GetFormat(); + if (format != eFormatDefault) + valobj_sp->SetFormat(format); + + DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions( + eLanguageRuntimeDescriptionDisplayVerbosityFull, format)); + + valobj_sp->Dump(*output_stream_p, options); + } else { + result.AppendErrorWithFormat( + "failed to create a value object for: (%s) %s\n", + view_as_type_cstr, name_strm.GetData()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + return true; + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + DataExtractor data(data_sp, target->GetArchitecture().GetByteOrder(), + target->GetArchitecture().GetAddressByteSize(), + target->GetArchitecture().GetDataByteSize()); + + Format format = m_format_options.GetFormat(); + if (((format == eFormatChar) || (format == eFormatCharPrintable)) && + (item_byte_size != 1)) { + // if a count was not passed, or it is 1 + if (!m_format_options.GetCountValue().OptionWasSet() || item_count == 1) { + // this turns requests such as + // memory read -fc -s10 -c1 *charPtrPtr + // which make no sense (what is a char of size 10?) into a request for + // fetching 10 chars of size 1 from the same memory location + format = eFormatCharArray; + item_count = item_byte_size; + item_byte_size = 1; + } else { + // here we passed a count, and it was not 1 so we have a byte_size and + // a count we could well multiply those, but instead let's just fail + result.AppendErrorWithFormat( + "reading memory as characters of size %" PRIu64 " is not supported", + (uint64_t)item_byte_size); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + assert(output_stream_p); + size_t bytes_dumped = DumpDataExtractor( + data, output_stream_p, 0, format, item_byte_size, item_count, + num_per_line / target->GetArchitecture().GetDataByteSize(), addr, 0, 0, + exe_scope); + m_next_addr = addr + bytes_dumped; + output_stream_p->EOL(); + return true; + } + + OptionGroupOptions m_option_group; + OptionGroupFormat m_format_options; + OptionGroupReadMemory m_memory_options; + OptionGroupOutputFile m_outfile_options; + OptionGroupValueObjectDisplay m_varobj_options; + lldb::addr_t m_next_addr; + lldb::addr_t m_prev_byte_size; + OptionGroupFormat m_prev_format_options; + OptionGroupReadMemory m_prev_memory_options; + OptionGroupOutputFile m_prev_outfile_options; + OptionGroupValueObjectDisplay m_prev_varobj_options; + CompilerType m_prev_compiler_type; +}; + +#define LLDB_OPTIONS_memory_find +#include "CommandOptions.inc" + +// Find the specified data in memory +class CommandObjectMemoryFind : public CommandObjectParsed { +public: + class OptionGroupFindMemory : public OptionGroup { + public: + OptionGroupFindMemory() : OptionGroup(), m_count(1), m_offset(0) {} + + ~OptionGroupFindMemory() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_memory_find_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + const int short_option = g_memory_find_options[option_idx].short_option; + + switch (short_option) { + case 'e': + m_expr.SetValueFromString(option_value); + break; + + case 's': + m_string.SetValueFromString(option_value); + break; + + case 'c': + if (m_count.SetValueFromString(option_value).Fail()) + error.SetErrorString("unrecognized value for count"); + break; + + case 'o': + if (m_offset.SetValueFromString(option_value).Fail()) + error.SetErrorString("unrecognized value for dump-offset"); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_expr.Clear(); + m_string.Clear(); + m_count.Clear(); + } + + OptionValueString m_expr; + OptionValueString m_string; + OptionValueUInt64 m_count; + OptionValueUInt64 m_offset; + }; + + CommandObjectMemoryFind(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "memory find", + "Find a value in the memory of the current target process.", + nullptr, eCommandRequiresProcess | eCommandProcessMustBeLaunched), + m_option_group(), m_memory_options() { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData addr_arg; + CommandArgumentData value_arg; + + // Define the first (and only) variant of this arg. + addr_arg.arg_type = eArgTypeAddressOrExpression; + addr_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(addr_arg); + + // Define the first (and only) variant of this arg. + value_arg.arg_type = eArgTypeAddressOrExpression; + value_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(value_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + + m_option_group.Append(&m_memory_options); + m_option_group.Finalize(); + } + + ~CommandObjectMemoryFind() override = default; + + Options *GetOptions() override { return &m_option_group; } + +protected: + class ProcessMemoryIterator { + public: + ProcessMemoryIterator(ProcessSP process_sp, lldb::addr_t base) + : m_process_sp(process_sp), m_base_addr(base), m_is_valid(true) { + lldbassert(process_sp.get() != nullptr); + } + + bool IsValid() { return m_is_valid; } + + uint8_t operator[](lldb::addr_t offset) { + if (!IsValid()) + return 0; + + uint8_t retval = 0; + Status error; + if (0 == + m_process_sp->ReadMemory(m_base_addr + offset, &retval, 1, error)) { + m_is_valid = false; + return 0; + } + + return retval; + } + + private: + ProcessSP m_process_sp; + lldb::addr_t m_base_addr; + bool m_is_valid; + }; + bool DoExecute(Args &command, CommandReturnObject &result) override { + // No need to check "process" for validity as eCommandRequiresProcess + // ensures it is valid + Process *process = m_exe_ctx.GetProcessPtr(); + + const size_t argc = command.GetArgumentCount(); + + if (argc != 2) { + result.AppendError("two addresses needed for memory find"); + return false; + } + + Status error; + lldb::addr_t low_addr = OptionArgParser::ToAddress( + &m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error); + if (low_addr == LLDB_INVALID_ADDRESS || error.Fail()) { + result.AppendError("invalid low address"); + return false; + } + lldb::addr_t high_addr = OptionArgParser::ToAddress( + &m_exe_ctx, command[1].ref(), LLDB_INVALID_ADDRESS, &error); + if (high_addr == LLDB_INVALID_ADDRESS || error.Fail()) { + result.AppendError("invalid high address"); + return false; + } + + if (high_addr <= low_addr) { + result.AppendError( + "starting address must be smaller than ending address"); + return false; + } + + lldb::addr_t found_location = LLDB_INVALID_ADDRESS; + + DataBufferHeap buffer; + + if (m_memory_options.m_string.OptionWasSet()) + buffer.CopyData(m_memory_options.m_string.GetStringValue()); + else if (m_memory_options.m_expr.OptionWasSet()) { + StackFrame *frame = m_exe_ctx.GetFramePtr(); + ValueObjectSP result_sp; + if ((eExpressionCompleted == + process->GetTarget().EvaluateExpression( + m_memory_options.m_expr.GetStringValue(), frame, result_sp)) && + result_sp) { + uint64_t value = result_sp->GetValueAsUnsigned(0); + llvm::Optional<uint64_t> size = + result_sp->GetCompilerType().GetByteSize(nullptr); + if (!size) + return false; + switch (*size) { + case 1: { + uint8_t byte = (uint8_t)value; + buffer.CopyData(&byte, 1); + } break; + case 2: { + uint16_t word = (uint16_t)value; + buffer.CopyData(&word, 2); + } break; + case 4: { + uint32_t lword = (uint32_t)value; + buffer.CopyData(&lword, 4); + } break; + case 8: { + buffer.CopyData(&value, 8); + } break; + case 3: + case 5: + case 6: + case 7: + result.AppendError("unknown type. pass a string instead"); + return false; + default: + result.AppendError( + "result size larger than 8 bytes. pass a string instead"); + return false; + } + } else { + result.AppendError( + "expression evaluation failed. pass a string instead"); + return false; + } + } else { + result.AppendError( + "please pass either a block of text, or an expression to evaluate."); + return false; + } + + size_t count = m_memory_options.m_count.GetCurrentValue(); + found_location = low_addr; + bool ever_found = false; + while (count) { + found_location = FastSearch(found_location, high_addr, buffer.GetBytes(), + buffer.GetByteSize()); + if (found_location == LLDB_INVALID_ADDRESS) { + if (!ever_found) { + result.AppendMessage("data not found within the range.\n"); + result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult); + } else + result.AppendMessage("no more matches within the range.\n"); + break; + } + result.AppendMessageWithFormat("data found at location: 0x%" PRIx64 "\n", + found_location); + + DataBufferHeap dumpbuffer(32, 0); + process->ReadMemory( + found_location + m_memory_options.m_offset.GetCurrentValue(), + dumpbuffer.GetBytes(), dumpbuffer.GetByteSize(), error); + if (!error.Fail()) { + DataExtractor data(dumpbuffer.GetBytes(), dumpbuffer.GetByteSize(), + process->GetByteOrder(), + process->GetAddressByteSize()); + DumpDataExtractor( + data, &result.GetOutputStream(), 0, lldb::eFormatBytesWithASCII, 1, + dumpbuffer.GetByteSize(), 16, + found_location + m_memory_options.m_offset.GetCurrentValue(), 0, 0); + result.GetOutputStream().EOL(); + } + + --count; + found_location++; + ever_found = true; + } + + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); + return true; + } + + lldb::addr_t FastSearch(lldb::addr_t low, lldb::addr_t high, uint8_t *buffer, + size_t buffer_size) { + const size_t region_size = high - low; + + if (region_size < buffer_size) + return LLDB_INVALID_ADDRESS; + + std::vector<size_t> bad_char_heuristic(256, buffer_size); + ProcessSP process_sp = m_exe_ctx.GetProcessSP(); + ProcessMemoryIterator iterator(process_sp, low); + + for (size_t idx = 0; idx < buffer_size - 1; idx++) { + decltype(bad_char_heuristic)::size_type bcu_idx = buffer[idx]; + bad_char_heuristic[bcu_idx] = buffer_size - idx - 1; + } + for (size_t s = 0; s <= (region_size - buffer_size);) { + int64_t j = buffer_size - 1; + while (j >= 0 && buffer[j] == iterator[s + j]) + j--; + if (j < 0) + return low + s; + else + s += bad_char_heuristic[iterator[s + buffer_size - 1]]; + } + + return LLDB_INVALID_ADDRESS; + } + + OptionGroupOptions m_option_group; + OptionGroupFindMemory m_memory_options; +}; + +#define LLDB_OPTIONS_memory_write +#include "CommandOptions.inc" + +// Write memory to the inferior process +class CommandObjectMemoryWrite : public CommandObjectParsed { +public: + class OptionGroupWriteMemory : public OptionGroup { + public: + OptionGroupWriteMemory() : OptionGroup() {} + + ~OptionGroupWriteMemory() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_memory_write_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + const int short_option = g_memory_write_options[option_idx].short_option; + + switch (short_option) { + case 'i': + m_infile.SetFile(option_value, FileSpec::Style::native); + FileSystem::Instance().Resolve(m_infile); + if (!FileSystem::Instance().Exists(m_infile)) { + m_infile.Clear(); + error.SetErrorStringWithFormat("input file does not exist: '%s'", + option_value.str().c_str()); + } + break; + + case 'o': { + if (option_value.getAsInteger(0, m_infile_offset)) { + m_infile_offset = 0; + error.SetErrorStringWithFormat("invalid offset string '%s'", + option_value.str().c_str()); + } + } break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_infile.Clear(); + m_infile_offset = 0; + } + + FileSpec m_infile; + off_t m_infile_offset; + }; + + CommandObjectMemoryWrite(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "memory write", + "Write to the memory of the current target process.", nullptr, + eCommandRequiresProcess | eCommandProcessMustBeLaunched), + m_option_group(), m_format_options(eFormatBytes, 1, UINT64_MAX), + m_memory_options() { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData addr_arg; + CommandArgumentData value_arg; + + // Define the first (and only) variant of this arg. + addr_arg.arg_type = eArgTypeAddress; + addr_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(addr_arg); + + // Define the first (and only) variant of this arg. + value_arg.arg_type = eArgTypeValue; + value_arg.arg_repetition = eArgRepeatPlus; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(value_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + + m_option_group.Append(&m_format_options, + OptionGroupFormat::OPTION_GROUP_FORMAT, + LLDB_OPT_SET_1); + m_option_group.Append(&m_format_options, + OptionGroupFormat::OPTION_GROUP_SIZE, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2); + m_option_group.Append(&m_memory_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_2); + m_option_group.Finalize(); + } + + ~CommandObjectMemoryWrite() override = default; + + Options *GetOptions() override { return &m_option_group; } + + bool UIntValueIsValidForSize(uint64_t uval64, size_t total_byte_size) { + if (total_byte_size > 8) + return false; + + if (total_byte_size == 8) + return true; + + const uint64_t max = ((uint64_t)1 << (uint64_t)(total_byte_size * 8)) - 1; + return uval64 <= max; + } + + bool SIntValueIsValidForSize(int64_t sval64, size_t total_byte_size) { + if (total_byte_size > 8) + return false; + + if (total_byte_size == 8) + return true; + + const int64_t max = ((int64_t)1 << (uint64_t)(total_byte_size * 8 - 1)) - 1; + const int64_t min = ~(max); + return min <= sval64 && sval64 <= max; + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + // No need to check "process" for validity as eCommandRequiresProcess + // ensures it is valid + Process *process = m_exe_ctx.GetProcessPtr(); + + const size_t argc = command.GetArgumentCount(); + + if (m_memory_options.m_infile) { + if (argc < 1) { + result.AppendErrorWithFormat( + "%s takes a destination address when writing file contents.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else if (argc < 2) { + result.AppendErrorWithFormat( + "%s takes a destination address and at least one value.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + StreamString buffer( + Stream::eBinary, + process->GetTarget().GetArchitecture().GetAddressByteSize(), + process->GetTarget().GetArchitecture().GetByteOrder()); + + OptionValueUInt64 &byte_size_value = m_format_options.GetByteSizeValue(); + size_t item_byte_size = byte_size_value.GetCurrentValue(); + + Status error; + lldb::addr_t addr = OptionArgParser::ToAddress( + &m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error); + + if (addr == LLDB_INVALID_ADDRESS) { + result.AppendError("invalid address expression\n"); + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_memory_options.m_infile) { + size_t length = SIZE_MAX; + if (item_byte_size > 1) + length = item_byte_size; + auto data_sp = FileSystem::Instance().CreateDataBuffer( + m_memory_options.m_infile.GetPath(), length, + m_memory_options.m_infile_offset); + if (data_sp) { + length = data_sp->GetByteSize(); + if (length > 0) { + Status error; + size_t bytes_written = + process->WriteMemory(addr, data_sp->GetBytes(), length, error); + + if (bytes_written == length) { + // All bytes written + result.GetOutputStream().Printf( + "%" PRIu64 " bytes were written to 0x%" PRIx64 "\n", + (uint64_t)bytes_written, addr); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else if (bytes_written > 0) { + // Some byte written + result.GetOutputStream().Printf( + "%" PRIu64 " bytes of %" PRIu64 + " requested were written to 0x%" PRIx64 "\n", + (uint64_t)bytes_written, (uint64_t)length, addr); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Memory write to 0x%" PRIx64 + " failed: %s.\n", + addr, error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + } else { + result.AppendErrorWithFormat("Unable to read contents of file.\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } else if (item_byte_size == 0) { + if (m_format_options.GetFormat() == eFormatPointer) + item_byte_size = buffer.GetAddressByteSize(); + else + item_byte_size = 1; + } + + command.Shift(); // shift off the address argument + uint64_t uval64; + int64_t sval64; + bool success = false; + for (auto &entry : command) { + switch (m_format_options.GetFormat()) { + case kNumFormats: + case eFormatFloat: // TODO: add support for floats soon + case eFormatCharPrintable: + case eFormatBytesWithASCII: + case eFormatComplex: + case eFormatEnum: + case eFormatUnicode8: + case eFormatUnicode16: + case eFormatUnicode32: + case eFormatVectorOfChar: + case eFormatVectorOfSInt8: + case eFormatVectorOfUInt8: + case eFormatVectorOfSInt16: + case eFormatVectorOfUInt16: + case eFormatVectorOfSInt32: + case eFormatVectorOfUInt32: + case eFormatVectorOfSInt64: + case eFormatVectorOfUInt64: + case eFormatVectorOfFloat16: + case eFormatVectorOfFloat32: + case eFormatVectorOfFloat64: + case eFormatVectorOfUInt128: + case eFormatOSType: + case eFormatComplexInteger: + case eFormatAddressInfo: + case eFormatHexFloat: + case eFormatInstruction: + case eFormatVoid: + result.AppendError("unsupported format for writing memory"); + result.SetStatus(eReturnStatusFailed); + return false; + + case eFormatDefault: + case eFormatBytes: + case eFormatHex: + case eFormatHexUppercase: + case eFormatPointer: + { + // Decode hex bytes + // Be careful, getAsInteger with a radix of 16 rejects "0xab" so we + // have to special case that: + bool success = false; + if (entry.ref().startswith("0x")) + success = !entry.ref().getAsInteger(0, uval64); + if (!success) + success = !entry.ref().getAsInteger(16, uval64); + if (!success) { + result.AppendErrorWithFormat( + "'%s' is not a valid hex string value.\n", entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!UIntValueIsValidForSize(uval64, item_byte_size)) { + result.AppendErrorWithFormat("Value 0x%" PRIx64 + " is too large to fit in a %" PRIu64 + " byte unsigned integer value.\n", + uval64, (uint64_t)item_byte_size); + result.SetStatus(eReturnStatusFailed); + return false; + } + buffer.PutMaxHex64(uval64, item_byte_size); + break; + } + case eFormatBoolean: + uval64 = OptionArgParser::ToBoolean(entry.ref(), false, &success); + if (!success) { + result.AppendErrorWithFormat( + "'%s' is not a valid boolean string value.\n", entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + buffer.PutMaxHex64(uval64, item_byte_size); + break; + + case eFormatBinary: + if (entry.ref().getAsInteger(2, uval64)) { + result.AppendErrorWithFormat( + "'%s' is not a valid binary string value.\n", entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!UIntValueIsValidForSize(uval64, item_byte_size)) { + result.AppendErrorWithFormat("Value 0x%" PRIx64 + " is too large to fit in a %" PRIu64 + " byte unsigned integer value.\n", + uval64, (uint64_t)item_byte_size); + result.SetStatus(eReturnStatusFailed); + return false; + } + buffer.PutMaxHex64(uval64, item_byte_size); + break; + + case eFormatCharArray: + case eFormatChar: + case eFormatCString: { + if (entry.ref().empty()) + break; + + size_t len = entry.ref().size(); + // Include the NULL for C strings... + if (m_format_options.GetFormat() == eFormatCString) + ++len; + Status error; + if (process->WriteMemory(addr, entry.c_str(), len, error) == len) { + addr += len; + } else { + result.AppendErrorWithFormat("Memory write to 0x%" PRIx64 + " failed: %s.\n", + addr, error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + break; + } + case eFormatDecimal: + if (entry.ref().getAsInteger(0, sval64)) { + result.AppendErrorWithFormat( + "'%s' is not a valid signed decimal value.\n", entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!SIntValueIsValidForSize(sval64, item_byte_size)) { + result.AppendErrorWithFormat( + "Value %" PRIi64 " is too large or small to fit in a %" PRIu64 + " byte signed integer value.\n", + sval64, (uint64_t)item_byte_size); + result.SetStatus(eReturnStatusFailed); + return false; + } + buffer.PutMaxHex64(sval64, item_byte_size); + break; + + case eFormatUnsigned: + + if (!entry.ref().getAsInteger(0, uval64)) { + result.AppendErrorWithFormat( + "'%s' is not a valid unsigned decimal string value.\n", + entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!UIntValueIsValidForSize(uval64, item_byte_size)) { + result.AppendErrorWithFormat("Value %" PRIu64 + " is too large to fit in a %" PRIu64 + " byte unsigned integer value.\n", + uval64, (uint64_t)item_byte_size); + result.SetStatus(eReturnStatusFailed); + return false; + } + buffer.PutMaxHex64(uval64, item_byte_size); + break; + + case eFormatOctal: + if (entry.ref().getAsInteger(8, uval64)) { + result.AppendErrorWithFormat( + "'%s' is not a valid octal string value.\n", entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!UIntValueIsValidForSize(uval64, item_byte_size)) { + result.AppendErrorWithFormat("Value %" PRIo64 + " is too large to fit in a %" PRIu64 + " byte unsigned integer value.\n", + uval64, (uint64_t)item_byte_size); + result.SetStatus(eReturnStatusFailed); + return false; + } + buffer.PutMaxHex64(uval64, item_byte_size); + break; + } + } + + if (!buffer.GetString().empty()) { + Status error; + if (process->WriteMemory(addr, buffer.GetString().data(), + buffer.GetString().size(), + error) == buffer.GetString().size()) + return true; + else { + result.AppendErrorWithFormat("Memory write to 0x%" PRIx64 + " failed: %s.\n", + addr, error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + return true; + } + + OptionGroupOptions m_option_group; + OptionGroupFormat m_format_options; + OptionGroupWriteMemory m_memory_options; +}; + +// Get malloc/free history of a memory address. +class CommandObjectMemoryHistory : public CommandObjectParsed { +public: + CommandObjectMemoryHistory(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "memory history", "Print recorded stack traces for " + "allocation/deallocation events " + "associated with an address.", + nullptr, + eCommandRequiresTarget | eCommandRequiresProcess | + eCommandProcessMustBePaused | eCommandProcessMustBeLaunched) { + CommandArgumentEntry arg1; + CommandArgumentData addr_arg; + + // Define the first (and only) variant of this arg. + addr_arg.arg_type = eArgTypeAddress; + addr_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(addr_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectMemoryHistory() override = default; + + const char *GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + return m_cmd_name.c_str(); + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc == 0 || argc > 1) { + result.AppendErrorWithFormat("%s takes an address expression", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Status error; + lldb::addr_t addr = OptionArgParser::ToAddress( + &m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error); + + if (addr == LLDB_INVALID_ADDRESS) { + result.AppendError("invalid address expression"); + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Stream *output_stream = &result.GetOutputStream(); + + const ProcessSP &process_sp = m_exe_ctx.GetProcessSP(); + const MemoryHistorySP &memory_history = + MemoryHistory::FindPlugin(process_sp); + + if (!memory_history) { + result.AppendError("no available memory history provider"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + HistoryThreads thread_list = memory_history->GetHistoryThreads(addr); + + const bool stop_format = false; + for (auto thread : thread_list) { + thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format); + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + + return true; + } +}; + +// CommandObjectMemoryRegion +#pragma mark CommandObjectMemoryRegion + +class CommandObjectMemoryRegion : public CommandObjectParsed { +public: + CommandObjectMemoryRegion(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "memory region", + "Get information on the memory region containing " + "an address in the current target process.", + "memory region ADDR", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched), + m_prev_end_addr(LLDB_INVALID_ADDRESS) {} + + ~CommandObjectMemoryRegion() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + ProcessSP process_sp = m_exe_ctx.GetProcessSP(); + if (process_sp) { + Status error; + lldb::addr_t load_addr = m_prev_end_addr; + m_prev_end_addr = LLDB_INVALID_ADDRESS; + + const size_t argc = command.GetArgumentCount(); + if (argc > 1 || (argc == 0 && load_addr == LLDB_INVALID_ADDRESS)) { + result.AppendErrorWithFormat("'%s' takes one argument:\nUsage: %s\n", + m_cmd_name.c_str(), m_cmd_syntax.c_str()); + result.SetStatus(eReturnStatusFailed); + } else { + if (command.GetArgumentCount() == 1) { + auto load_addr_str = command[0].ref(); + load_addr = OptionArgParser::ToAddress(&m_exe_ctx, load_addr_str, + LLDB_INVALID_ADDRESS, &error); + if (error.Fail() || load_addr == LLDB_INVALID_ADDRESS) { + result.AppendErrorWithFormat( + "invalid address argument \"%s\": %s\n", command[0].c_str(), + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + + lldb_private::MemoryRegionInfo range_info; + error = process_sp->GetMemoryRegionInfo(load_addr, range_info); + if (error.Success()) { + lldb_private::Address addr; + ConstString name = range_info.GetName(); + ConstString section_name; + if (process_sp->GetTarget().ResolveLoadAddress(load_addr, addr)) { + SectionSP section_sp(addr.GetSection()); + if (section_sp) { + // Got the top most section, not the deepest section + while (section_sp->GetParent()) + section_sp = section_sp->GetParent(); + section_name = section_sp->GetName(); + } + } + result.AppendMessageWithFormat( + "[0x%16.16" PRIx64 "-0x%16.16" PRIx64 ") %c%c%c%s%s%s%s\n", + range_info.GetRange().GetRangeBase(), + range_info.GetRange().GetRangeEnd(), + range_info.GetReadable() ? 'r' : '-', + range_info.GetWritable() ? 'w' : '-', + range_info.GetExecutable() ? 'x' : '-', + name ? " " : "", name.AsCString(""), + section_name ? " " : "", section_name.AsCString("")); + m_prev_end_addr = range_info.GetRange().GetRangeEnd(); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.SetStatus(eReturnStatusFailed); + result.AppendErrorWithFormat("%s\n", error.AsCString()); + } + } + } else { + m_prev_end_addr = LLDB_INVALID_ADDRESS; + result.AppendError("invalid process"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + const char *GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + // If we repeat this command, repeat it without any arguments so we can + // show the next memory range + return m_cmd_name.c_str(); + } + + lldb::addr_t m_prev_end_addr; +}; + +// CommandObjectMemory + +CommandObjectMemory::CommandObjectMemory(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "memory", + "Commands for operating on memory in the current target process.", + "memory <subcommand> [<subcommand-options>]") { + LoadSubCommand("find", + CommandObjectSP(new CommandObjectMemoryFind(interpreter))); + LoadSubCommand("read", + CommandObjectSP(new CommandObjectMemoryRead(interpreter))); + LoadSubCommand("write", + CommandObjectSP(new CommandObjectMemoryWrite(interpreter))); + LoadSubCommand("history", + CommandObjectSP(new CommandObjectMemoryHistory(interpreter))); + LoadSubCommand("region", + CommandObjectSP(new CommandObjectMemoryRegion(interpreter))); +} + +CommandObjectMemory::~CommandObjectMemory() = default; diff --git a/lldb/source/Commands/CommandObjectMemory.h b/lldb/source/Commands/CommandObjectMemory.h new file mode 100644 index 0000000000000..f94cdf3287aa2 --- /dev/null +++ b/lldb/source/Commands/CommandObjectMemory.h @@ -0,0 +1,25 @@ +//===-- CommandObjectMemory.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_CommandObjectMemory_h_ +#define liblldb_CommandObjectMemory_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +class CommandObjectMemory : public CommandObjectMultiword { +public: + CommandObjectMemory(CommandInterpreter &interpreter); + + ~CommandObjectMemory() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectMemory_h_ diff --git a/lldb/source/Commands/CommandObjectMultiword.cpp b/lldb/source/Commands/CommandObjectMultiword.cpp new file mode 100644 index 0000000000000..03a3770d8df70 --- /dev/null +++ b/lldb/source/Commands/CommandObjectMultiword.cpp @@ -0,0 +1,386 @@ +//===-- CommandObjectMultiword.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/Interpreter/CommandObjectMultiword.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/Options.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectMultiword + +CommandObjectMultiword::CommandObjectMultiword(CommandInterpreter &interpreter, + const char *name, + const char *help, + const char *syntax, + uint32_t flags) + : CommandObject(interpreter, name, help, syntax, flags), + m_can_be_removed(false) {} + +CommandObjectMultiword::~CommandObjectMultiword() = default; + +CommandObjectSP CommandObjectMultiword::GetSubcommandSP(llvm::StringRef sub_cmd, + StringList *matches) { + CommandObjectSP return_cmd_sp; + CommandObject::CommandMap::iterator pos; + + if (!m_subcommand_dict.empty()) { + pos = m_subcommand_dict.find(sub_cmd); + if (pos != m_subcommand_dict.end()) { + // An exact match; append the sub_cmd to the 'matches' string list. + if (matches) + matches->AppendString(sub_cmd); + return_cmd_sp = pos->second; + } else { + StringList local_matches; + if (matches == nullptr) + matches = &local_matches; + int num_matches = + AddNamesMatchingPartialString(m_subcommand_dict, sub_cmd, *matches); + + if (num_matches == 1) { + // Cleaner, but slightly less efficient would be to call back into this + // function, since I now know I have an exact match... + + sub_cmd = matches->GetStringAtIndex(0); + pos = m_subcommand_dict.find(sub_cmd); + if (pos != m_subcommand_dict.end()) + return_cmd_sp = pos->second; + } + } + } + return return_cmd_sp; +} + +CommandObject * +CommandObjectMultiword::GetSubcommandObject(llvm::StringRef sub_cmd, + StringList *matches) { + return GetSubcommandSP(sub_cmd, matches).get(); +} + +bool CommandObjectMultiword::LoadSubCommand(llvm::StringRef name, + const CommandObjectSP &cmd_obj) { + if (cmd_obj) + assert((&GetCommandInterpreter() == &cmd_obj->GetCommandInterpreter()) && + "tried to add a CommandObject from a different interpreter"); + + CommandMap::iterator pos; + bool success = true; + + pos = m_subcommand_dict.find(name); + if (pos == m_subcommand_dict.end()) { + m_subcommand_dict[name] = cmd_obj; + } else + success = false; + + return success; +} + +bool CommandObjectMultiword::Execute(const char *args_string, + CommandReturnObject &result) { + Args args(args_string); + const size_t argc = args.GetArgumentCount(); + if (argc == 0) { + this->CommandObject::GenerateHelpText(result); + return result.Succeeded(); + } + + auto sub_command = args[0].ref(); + if (sub_command.empty()) { + result.AppendError("Need to specify a non-empty subcommand."); + return result.Succeeded(); + } + + if (sub_command.equals_lower("help")) { + this->CommandObject::GenerateHelpText(result); + return result.Succeeded(); + } + + if (m_subcommand_dict.empty()) { + result.AppendErrorWithFormat("'%s' does not have any subcommands.\n", + GetCommandName().str().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + StringList matches; + CommandObject *sub_cmd_obj = GetSubcommandObject(sub_command, &matches); + if (sub_cmd_obj != nullptr) { + // Now call CommandObject::Execute to process options in `rest_of_line`. + // From there the command-specific version of Execute will be called, with + // the processed arguments. + + args.Shift(); + sub_cmd_obj->Execute(args_string, result); + return result.Succeeded(); + } + + std::string error_msg; + const size_t num_subcmd_matches = matches.GetSize(); + if (num_subcmd_matches > 0) + error_msg.assign("ambiguous command "); + else + error_msg.assign("invalid command "); + + error_msg.append("'"); + error_msg.append(GetCommandName()); + error_msg.append(" "); + error_msg.append(sub_command); + error_msg.append("'."); + + if (num_subcmd_matches > 0) { + error_msg.append(" Possible completions:"); + for (const std::string &match : matches) { + error_msg.append("\n\t"); + error_msg.append(match); + } + } + error_msg.append("\n"); + result.AppendRawError(error_msg.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; +} + +void CommandObjectMultiword::GenerateHelpText(Stream &output_stream) { + // First time through here, generate the help text for the object and push it + // to the return result object as well + + CommandObject::GenerateHelpText(output_stream); + output_stream.PutCString("\nThe following subcommands are supported:\n\n"); + + CommandMap::iterator pos; + uint32_t max_len = FindLongestCommandWord(m_subcommand_dict); + + if (max_len) + max_len += 4; // Indent the output by 4 spaces. + + for (pos = m_subcommand_dict.begin(); pos != m_subcommand_dict.end(); ++pos) { + std::string indented_command(" "); + indented_command.append(pos->first); + if (pos->second->WantsRawCommandString()) { + std::string help_text(pos->second->GetHelp()); + help_text.append(" Expects 'raw' input (see 'help raw-input'.)"); + m_interpreter.OutputFormattedHelpText(output_stream, + indented_command.c_str(), "--", + help_text.c_str(), max_len); + } else + m_interpreter.OutputFormattedHelpText(output_stream, + indented_command.c_str(), "--", + pos->second->GetHelp(), max_len); + } + + output_stream.PutCString("\nFor more help on any particular subcommand, type " + "'help <command> <subcommand>'.\n"); +} + +void CommandObjectMultiword::HandleCompletion(CompletionRequest &request) { + auto arg0 = request.GetParsedLine()[0].ref(); + if (request.GetCursorIndex() == 0) { + StringList new_matches, descriptions; + AddNamesMatchingPartialString(m_subcommand_dict, arg0, new_matches, + &descriptions); + request.AddCompletions(new_matches, descriptions); + + if (new_matches.GetSize() == 1 && + new_matches.GetStringAtIndex(0) != nullptr && + (arg0 == new_matches.GetStringAtIndex(0))) { + StringList temp_matches; + CommandObject *cmd_obj = GetSubcommandObject(arg0, &temp_matches); + if (cmd_obj != nullptr) { + if (request.GetParsedLine().GetArgumentCount() != 1) { + request.GetParsedLine().Shift(); + request.AppendEmptyArgument(); + cmd_obj->HandleCompletion(request); + } + } + } + return; + } + + StringList new_matches; + CommandObject *sub_command_object = GetSubcommandObject(arg0, &new_matches); + if (sub_command_object == nullptr) { + request.AddCompletions(new_matches); + return; + } + + // Remove the one match that we got from calling GetSubcommandObject. + new_matches.DeleteStringAtIndex(0); + request.AddCompletions(new_matches); + request.ShiftArguments(); + sub_command_object->HandleCompletion(request); +} + +const char *CommandObjectMultiword::GetRepeatCommand(Args ¤t_command_args, + uint32_t index) { + index++; + if (current_command_args.GetArgumentCount() <= index) + return nullptr; + CommandObject *sub_command_object = + GetSubcommandObject(current_command_args[index].ref()); + if (sub_command_object == nullptr) + return nullptr; + return sub_command_object->GetRepeatCommand(current_command_args, index); +} + +void CommandObjectMultiword::AproposAllSubCommands(llvm::StringRef prefix, + llvm::StringRef search_word, + StringList &commands_found, + StringList &commands_help) { + CommandObject::CommandMap::const_iterator pos; + + for (pos = m_subcommand_dict.begin(); pos != m_subcommand_dict.end(); ++pos) { + const char *command_name = pos->first.c_str(); + CommandObject *sub_cmd_obj = pos->second.get(); + StreamString complete_command_name; + + complete_command_name << prefix << " " << command_name; + + if (sub_cmd_obj->HelpTextContainsWord(search_word)) { + commands_found.AppendString(complete_command_name.GetString()); + commands_help.AppendString(sub_cmd_obj->GetHelp()); + } + + if (sub_cmd_obj->IsMultiwordObject()) + sub_cmd_obj->AproposAllSubCommands(complete_command_name.GetString(), + search_word, commands_found, + commands_help); + } +} + +CommandObjectProxy::CommandObjectProxy(CommandInterpreter &interpreter, + const char *name, const char *help, + const char *syntax, uint32_t flags) + : CommandObject(interpreter, name, help, syntax, flags) {} + +CommandObjectProxy::~CommandObjectProxy() = default; + +llvm::StringRef CommandObjectProxy::GetHelpLong() { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->GetHelpLong(); + return llvm::StringRef(); +} + +bool CommandObjectProxy::IsRemovable() const { + const CommandObject *proxy_command = + const_cast<CommandObjectProxy *>(this)->GetProxyCommandObject(); + if (proxy_command) + return proxy_command->IsRemovable(); + return false; +} + +bool CommandObjectProxy::IsMultiwordObject() { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->IsMultiwordObject(); + return false; +} + +CommandObjectMultiword *CommandObjectProxy::GetAsMultiwordCommand() { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->GetAsMultiwordCommand(); + return nullptr; +} + +void CommandObjectProxy::GenerateHelpText(Stream &result) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->GenerateHelpText(result); +} + +lldb::CommandObjectSP +CommandObjectProxy::GetSubcommandSP(llvm::StringRef sub_cmd, + StringList *matches) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->GetSubcommandSP(sub_cmd, matches); + return lldb::CommandObjectSP(); +} + +CommandObject *CommandObjectProxy::GetSubcommandObject(llvm::StringRef sub_cmd, + StringList *matches) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->GetSubcommandObject(sub_cmd, matches); + return nullptr; +} + +void CommandObjectProxy::AproposAllSubCommands(llvm::StringRef prefix, + llvm::StringRef search_word, + StringList &commands_found, + StringList &commands_help) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->AproposAllSubCommands(prefix, search_word, + commands_found, commands_help); +} + +bool CommandObjectProxy::LoadSubCommand( + llvm::StringRef cmd_name, const lldb::CommandObjectSP &command_sp) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->LoadSubCommand(cmd_name, command_sp); + return false; +} + +bool CommandObjectProxy::WantsRawCommandString() { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->WantsRawCommandString(); + return false; +} + +bool CommandObjectProxy::WantsCompletion() { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->WantsCompletion(); + return false; +} + +Options *CommandObjectProxy::GetOptions() { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->GetOptions(); + return nullptr; +} + +void CommandObjectProxy::HandleCompletion(CompletionRequest &request) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + proxy_command->HandleCompletion(request); +} + +void CommandObjectProxy::HandleArgumentCompletion( + CompletionRequest &request, OptionElementVector &opt_element_vector) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + proxy_command->HandleArgumentCompletion(request, opt_element_vector); +} + +const char *CommandObjectProxy::GetRepeatCommand(Args ¤t_command_args, + uint32_t index) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->GetRepeatCommand(current_command_args, index); + return nullptr; +} + +bool CommandObjectProxy::Execute(const char *args_string, + CommandReturnObject &result) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + return proxy_command->Execute(args_string, result); + result.AppendError("command is not implemented"); + result.SetStatus(eReturnStatusFailed); + return false; +} diff --git a/lldb/source/Commands/CommandObjectPlatform.cpp b/lldb/source/Commands/CommandObjectPlatform.cpp new file mode 100644 index 0000000000000..fbd13aa37bdab --- /dev/null +++ b/lldb/source/Commands/CommandObjectPlatform.cpp @@ -0,0 +1,1751 @@ +//===-- CommandObjectPlatform.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 <mutex> +#include "CommandObjectPlatform.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Host/StringConvert.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandOptionValidators.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionGroupFile.h" +#include "lldb/Interpreter/OptionGroupPlatform.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/DataExtractor.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Threading.h" + +using namespace lldb; +using namespace lldb_private; + +static mode_t ParsePermissionString(const char *) = delete; + +static mode_t ParsePermissionString(llvm::StringRef permissions) { + if (permissions.size() != 9) + return (mode_t)(-1); + bool user_r, user_w, user_x, group_r, group_w, group_x, world_r, world_w, + world_x; + + user_r = (permissions[0] == 'r'); + user_w = (permissions[1] == 'w'); + user_x = (permissions[2] == 'x'); + + group_r = (permissions[3] == 'r'); + group_w = (permissions[4] == 'w'); + group_x = (permissions[5] == 'x'); + + world_r = (permissions[6] == 'r'); + world_w = (permissions[7] == 'w'); + world_x = (permissions[8] == 'x'); + + mode_t user, group, world; + user = (user_r ? 4 : 0) | (user_w ? 2 : 0) | (user_x ? 1 : 0); + group = (group_r ? 4 : 0) | (group_w ? 2 : 0) | (group_x ? 1 : 0); + world = (world_r ? 4 : 0) | (world_w ? 2 : 0) | (world_x ? 1 : 0); + + return user | group | world; +} + +#define LLDB_OPTIONS_permissions +#include "CommandOptions.inc" + +class OptionPermissions : public OptionGroup { +public: + OptionPermissions() {} + + ~OptionPermissions() override = default; + + lldb_private::Status + SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + char short_option = (char)GetDefinitions()[option_idx].short_option; + switch (short_option) { + case 'v': { + if (option_arg.getAsInteger(8, m_permissions)) { + m_permissions = 0777; + error.SetErrorStringWithFormat("invalid value for permissions: %s", + option_arg.str().c_str()); + } + + } break; + case 's': { + mode_t perms = ParsePermissionString(option_arg); + if (perms == (mode_t)-1) + error.SetErrorStringWithFormat("invalid value for permissions: %s", + option_arg.str().c_str()); + else + m_permissions = perms; + } break; + case 'r': + m_permissions |= lldb::eFilePermissionsUserRead; + break; + case 'w': + m_permissions |= lldb::eFilePermissionsUserWrite; + break; + case 'x': + m_permissions |= lldb::eFilePermissionsUserExecute; + break; + case 'R': + m_permissions |= lldb::eFilePermissionsGroupRead; + break; + case 'W': + m_permissions |= lldb::eFilePermissionsGroupWrite; + break; + case 'X': + m_permissions |= lldb::eFilePermissionsGroupExecute; + break; + case 'd': + m_permissions |= lldb::eFilePermissionsWorldRead; + break; + case 't': + m_permissions |= lldb::eFilePermissionsWorldWrite; + break; + case 'e': + m_permissions |= lldb::eFilePermissionsWorldExecute; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_permissions = 0; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_permissions_options); + } + + // Instance variables to hold the values for command options. + + uint32_t m_permissions; + +private: + DISALLOW_COPY_AND_ASSIGN(OptionPermissions); +}; + +// "platform select <platform-name>" +class CommandObjectPlatformSelect : public CommandObjectParsed { +public: + CommandObjectPlatformSelect(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform select", + "Create a platform if needed and select it as the " + "current platform.", + "platform select <platform-name>", 0), + m_option_group(), + m_platform_options( + false) // Don't include the "--platform" option by passing false + { + m_option_group.Append(&m_platform_options, LLDB_OPT_SET_ALL, 1); + m_option_group.Finalize(); + } + + ~CommandObjectPlatformSelect() override = default; + + void HandleCompletion(CompletionRequest &request) override { + CommandCompletions::PlatformPluginNames(GetCommandInterpreter(), request, + nullptr); + } + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + if (args.GetArgumentCount() == 1) { + const char *platform_name = args.GetArgumentAtIndex(0); + if (platform_name && platform_name[0]) { + const bool select = true; + m_platform_options.SetPlatformName(platform_name); + Status error; + ArchSpec platform_arch; + PlatformSP platform_sp(m_platform_options.CreatePlatformWithOptions( + m_interpreter, ArchSpec(), select, error, platform_arch)); + if (platform_sp) { + GetDebugger().GetPlatformList().SetSelectedPlatform(platform_sp); + + platform_sp->GetStatus(result.GetOutputStream()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("invalid platform name"); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError( + "platform create takes a platform name as an argument\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + OptionGroupOptions m_option_group; + OptionGroupPlatform m_platform_options; +}; + +// "platform list" +class CommandObjectPlatformList : public CommandObjectParsed { +public: + CommandObjectPlatformList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform list", + "List all platforms that are available.", nullptr, + 0) {} + + ~CommandObjectPlatformList() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + Stream &ostrm = result.GetOutputStream(); + ostrm.Printf("Available platforms:\n"); + + PlatformSP host_platform_sp(Platform::GetHostPlatform()); + ostrm.Printf("%s: %s\n", host_platform_sp->GetPluginName().GetCString(), + host_platform_sp->GetDescription()); + + uint32_t idx; + for (idx = 0; true; ++idx) { + const char *plugin_name = + PluginManager::GetPlatformPluginNameAtIndex(idx); + if (plugin_name == nullptr) + break; + const char *plugin_desc = + PluginManager::GetPlatformPluginDescriptionAtIndex(idx); + if (plugin_desc == nullptr) + break; + ostrm.Printf("%s: %s\n", plugin_name, plugin_desc); + } + + if (idx == 0) { + result.AppendError("no platforms are available\n"); + result.SetStatus(eReturnStatusFailed); + } else + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +// "platform status" +class CommandObjectPlatformStatus : public CommandObjectParsed { +public: + CommandObjectPlatformStatus(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform status", + "Display status for the current platform.", nullptr, + 0) {} + + ~CommandObjectPlatformStatus() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + Stream &ostrm = result.GetOutputStream(); + + Target *target = GetDebugger().GetSelectedTarget().get(); + PlatformSP platform_sp; + if (target) { + platform_sp = target->GetPlatform(); + } + if (!platform_sp) { + platform_sp = GetDebugger().GetPlatformList().GetSelectedPlatform(); + } + if (platform_sp) { + platform_sp->GetStatus(ostrm); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("no platform is currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// "platform connect <connect-url>" +class CommandObjectPlatformConnect : public CommandObjectParsed { +public: + CommandObjectPlatformConnect(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "platform connect", + "Select the current platform by providing a connection URL.", + "platform connect <connect-url>", 0) {} + + ~CommandObjectPlatformConnect() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + Stream &ostrm = result.GetOutputStream(); + + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + Status error(platform_sp->ConnectRemote(args)); + if (error.Success()) { + platform_sp->GetStatus(ostrm); + result.SetStatus(eReturnStatusSuccessFinishResult); + + platform_sp->ConnectToWaitingProcesses(GetDebugger(), error); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("%s\n", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform is currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + Options *GetOptions() override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + OptionGroupOptions *m_platform_options = nullptr; + if (platform_sp) { + m_platform_options = platform_sp->GetConnectionOptions(m_interpreter); + if (m_platform_options != nullptr && !m_platform_options->m_did_finalize) + m_platform_options->Finalize(); + } + return m_platform_options; + } +}; + +// "platform disconnect" +class CommandObjectPlatformDisconnect : public CommandObjectParsed { +public: + CommandObjectPlatformDisconnect(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform disconnect", + "Disconnect from the current platform.", + "platform disconnect", 0) {} + + ~CommandObjectPlatformDisconnect() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + if (args.GetArgumentCount() == 0) { + Status error; + + if (platform_sp->IsConnected()) { + // Cache the instance name if there is one since we are about to + // disconnect and the name might go with it. + const char *hostname_cstr = platform_sp->GetHostname(); + std::string hostname; + if (hostname_cstr) + hostname.assign(hostname_cstr); + + error = platform_sp->DisconnectRemote(); + if (error.Success()) { + Stream &ostrm = result.GetOutputStream(); + if (hostname.empty()) + ostrm.Printf("Disconnected from \"%s\"\n", + platform_sp->GetPluginName().GetCString()); + else + ostrm.Printf("Disconnected from \"%s\"\n", hostname.c_str()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("%s", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + // Not connected... + result.AppendErrorWithFormat( + "not connected to '%s'", + platform_sp->GetPluginName().GetCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + // Bad args + result.AppendError( + "\"platform disconnect\" doesn't take any arguments"); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform is currently selected"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// "platform settings" +class CommandObjectPlatformSettings : public CommandObjectParsed { +public: + CommandObjectPlatformSettings(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform settings", + "Set settings for the current target's platform, " + "or for a platform by name.", + "platform settings", 0), + m_options(), + m_option_working_dir(LLDB_OPT_SET_1, false, "working-dir", 'w', 0, + eArgTypePath, + "The working directory for the platform.") { + m_options.Append(&m_option_working_dir, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + } + + ~CommandObjectPlatformSettings() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + if (m_option_working_dir.GetOptionValue().OptionWasSet()) + platform_sp->SetWorkingDirectory( + m_option_working_dir.GetOptionValue().GetCurrentValue()); + } else { + result.AppendError("no platform is currently selected"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + Options *GetOptions() override { + if (!m_options.DidFinalize()) + m_options.Finalize(); + return &m_options; + } + +protected: + OptionGroupOptions m_options; + OptionGroupFile m_option_working_dir; +}; + +// "platform mkdir" +class CommandObjectPlatformMkDir : public CommandObjectParsed { +public: + CommandObjectPlatformMkDir(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform mkdir", + "Make a new directory on the remote end.", nullptr, + 0), + m_options() {} + + ~CommandObjectPlatformMkDir() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + std::string cmd_line; + args.GetCommandString(cmd_line); + uint32_t mode; + const OptionPermissions *options_permissions = + (const OptionPermissions *)m_options.GetGroupWithOption('r'); + if (options_permissions) + mode = options_permissions->m_permissions; + else + mode = lldb::eFilePermissionsUserRWX | lldb::eFilePermissionsGroupRWX | + lldb::eFilePermissionsWorldRX; + Status error = platform_sp->MakeDirectory(FileSpec(cmd_line), mode); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + Options *GetOptions() override { + if (!m_options.DidFinalize()) { + m_options.Append(new OptionPermissions()); + m_options.Finalize(); + } + return &m_options; + } + + OptionGroupOptions m_options; +}; + +// "platform fopen" +class CommandObjectPlatformFOpen : public CommandObjectParsed { +public: + CommandObjectPlatformFOpen(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform file open", + "Open a file on the remote end.", nullptr, 0), + m_options() {} + + ~CommandObjectPlatformFOpen() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + Status error; + std::string cmd_line; + args.GetCommandString(cmd_line); + mode_t perms; + const OptionPermissions *options_permissions = + (const OptionPermissions *)m_options.GetGroupWithOption('r'); + if (options_permissions) + perms = options_permissions->m_permissions; + else + perms = lldb::eFilePermissionsUserRW | lldb::eFilePermissionsGroupRW | + lldb::eFilePermissionsWorldRead; + lldb::user_id_t fd = platform_sp->OpenFile( + FileSpec(cmd_line), + File::eOpenOptionRead | File::eOpenOptionWrite | + File::eOpenOptionAppend | File::eOpenOptionCanCreate, + perms, error); + if (error.Success()) { + result.AppendMessageWithFormat("File Descriptor = %" PRIu64 "\n", fd); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + Options *GetOptions() override { + if (!m_options.DidFinalize()) { + m_options.Append(new OptionPermissions()); + m_options.Finalize(); + } + return &m_options; + } + + OptionGroupOptions m_options; +}; + +// "platform fclose" +class CommandObjectPlatformFClose : public CommandObjectParsed { +public: + CommandObjectPlatformFClose(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform file close", + "Close a file on the remote end.", nullptr, 0) {} + + ~CommandObjectPlatformFClose() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + std::string cmd_line; + args.GetCommandString(cmd_line); + const lldb::user_id_t fd = + StringConvert::ToUInt64(cmd_line.c_str(), UINT64_MAX); + Status error; + bool success = platform_sp->CloseFile(fd, error); + if (success) { + result.AppendMessageWithFormat("file %" PRIu64 " closed.\n", fd); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// "platform fread" + +#define LLDB_OPTIONS_platform_fread +#include "CommandOptions.inc" + +class CommandObjectPlatformFRead : public CommandObjectParsed { +public: + CommandObjectPlatformFRead(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform file read", + "Read data from a file on the remote end.", nullptr, + 0), + m_options() {} + + ~CommandObjectPlatformFRead() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + std::string cmd_line; + args.GetCommandString(cmd_line); + const lldb::user_id_t fd = + StringConvert::ToUInt64(cmd_line.c_str(), UINT64_MAX); + std::string buffer(m_options.m_count, 0); + Status error; + uint32_t retcode = platform_sp->ReadFile( + fd, m_options.m_offset, &buffer[0], m_options.m_count, error); + result.AppendMessageWithFormat("Return = %d\n", retcode); + result.AppendMessageWithFormat("Data = \"%s\"\n", buffer.c_str()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("no platform currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + Options *GetOptions() override { return &m_options; } + +protected: + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + char short_option = (char)m_getopt_table[option_idx].val; + + switch (short_option) { + case 'o': + if (option_arg.getAsInteger(0, m_offset)) + error.SetErrorStringWithFormat("invalid offset: '%s'", + option_arg.str().c_str()); + break; + case 'c': + if (option_arg.getAsInteger(0, m_count)) + error.SetErrorStringWithFormat("invalid offset: '%s'", + option_arg.str().c_str()); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_offset = 0; + m_count = 1; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_platform_fread_options); + } + + // Instance variables to hold the values for command options. + + uint32_t m_offset; + uint32_t m_count; + }; + + CommandOptions m_options; +}; + +// "platform fwrite" + +#define LLDB_OPTIONS_platform_fwrite +#include "CommandOptions.inc" + +class CommandObjectPlatformFWrite : public CommandObjectParsed { +public: + CommandObjectPlatformFWrite(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform file write", + "Write data to a file on the remote end.", nullptr, + 0), + m_options() {} + + ~CommandObjectPlatformFWrite() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + std::string cmd_line; + args.GetCommandString(cmd_line); + Status error; + const lldb::user_id_t fd = + StringConvert::ToUInt64(cmd_line.c_str(), UINT64_MAX); + uint32_t retcode = + platform_sp->WriteFile(fd, m_options.m_offset, &m_options.m_data[0], + m_options.m_data.size(), error); + result.AppendMessageWithFormat("Return = %d\n", retcode); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("no platform currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + Options *GetOptions() override { return &m_options; } + +protected: + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + char short_option = (char)m_getopt_table[option_idx].val; + + switch (short_option) { + case 'o': + if (option_arg.getAsInteger(0, m_offset)) + error.SetErrorStringWithFormat("invalid offset: '%s'", + option_arg.str().c_str()); + break; + case 'd': + m_data.assign(option_arg); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_offset = 0; + m_data.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_platform_fwrite_options); + } + + // Instance variables to hold the values for command options. + + uint32_t m_offset; + std::string m_data; + }; + + CommandOptions m_options; +}; + +class CommandObjectPlatformFile : public CommandObjectMultiword { +public: + // Constructors and Destructors + CommandObjectPlatformFile(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "platform file", + "Commands to access files on the current platform.", + "platform file [open|close|read|write] ...") { + LoadSubCommand( + "open", CommandObjectSP(new CommandObjectPlatformFOpen(interpreter))); + LoadSubCommand( + "close", CommandObjectSP(new CommandObjectPlatformFClose(interpreter))); + LoadSubCommand( + "read", CommandObjectSP(new CommandObjectPlatformFRead(interpreter))); + LoadSubCommand( + "write", CommandObjectSP(new CommandObjectPlatformFWrite(interpreter))); + } + + ~CommandObjectPlatformFile() override = default; + +private: + // For CommandObjectPlatform only + DISALLOW_COPY_AND_ASSIGN(CommandObjectPlatformFile); +}; + +// "platform get-file remote-file-path host-file-path" +class CommandObjectPlatformGetFile : public CommandObjectParsed { +public: + CommandObjectPlatformGetFile(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "platform get-file", + "Transfer a file from the remote end to the local host.", + "platform get-file <remote-file-spec> <local-file-spec>", 0) { + SetHelpLong( + R"(Examples: + +(lldb) platform get-file /the/remote/file/path /the/local/file/path + + Transfer a file from the remote end with file path /the/remote/file/path to the local host.)"); + + CommandArgumentEntry arg1, arg2; + CommandArgumentData file_arg_remote, file_arg_host; + + // Define the first (and only) variant of this arg. + file_arg_remote.arg_type = eArgTypeFilename; + file_arg_remote.arg_repetition = eArgRepeatPlain; + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(file_arg_remote); + + // Define the second (and only) variant of this arg. + file_arg_host.arg_type = eArgTypeFilename; + file_arg_host.arg_repetition = eArgRepeatPlain; + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(file_arg_host); + + // Push the data for the first and the second arguments into the + // m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + } + + ~CommandObjectPlatformGetFile() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + // If the number of arguments is incorrect, issue an error message. + if (args.GetArgumentCount() != 2) { + result.GetErrorStream().Printf("error: required arguments missing; " + "specify both the source and destination " + "file paths\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + const char *remote_file_path = args.GetArgumentAtIndex(0); + const char *local_file_path = args.GetArgumentAtIndex(1); + Status error = platform_sp->GetFile(FileSpec(remote_file_path), + FileSpec(local_file_path)); + if (error.Success()) { + result.AppendMessageWithFormat( + "successfully get-file from %s (remote) to %s (host)\n", + remote_file_path, local_file_path); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendMessageWithFormat("get-file failed: %s\n", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// "platform get-size remote-file-path" +class CommandObjectPlatformGetSize : public CommandObjectParsed { +public: + CommandObjectPlatformGetSize(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform get-size", + "Get the file size from the remote end.", + "platform get-size <remote-file-spec>", 0) { + SetHelpLong( + R"(Examples: + +(lldb) platform get-size /the/remote/file/path + + Get the file size from the remote end with path /the/remote/file/path.)"); + + CommandArgumentEntry arg1; + CommandArgumentData file_arg_remote; + + // Define the first (and only) variant of this arg. + file_arg_remote.arg_type = eArgTypeFilename; + file_arg_remote.arg_repetition = eArgRepeatPlain; + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(file_arg_remote); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectPlatformGetSize() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + // If the number of arguments is incorrect, issue an error message. + if (args.GetArgumentCount() != 1) { + result.GetErrorStream().Printf("error: required argument missing; " + "specify the source file path as the only " + "argument\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + std::string remote_file_path(args.GetArgumentAtIndex(0)); + user_id_t size = platform_sp->GetFileSize(FileSpec(remote_file_path)); + if (size != UINT64_MAX) { + result.AppendMessageWithFormat("File size of %s (remote): %" PRIu64 + "\n", + remote_file_path.c_str(), size); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendMessageWithFormat( + "Error getting file size of %s (remote)\n", + remote_file_path.c_str()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// "platform put-file" +class CommandObjectPlatformPutFile : public CommandObjectParsed { +public: + CommandObjectPlatformPutFile(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "platform put-file", + "Transfer a file from this system to the remote end.", nullptr, 0) { + } + + ~CommandObjectPlatformPutFile() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + const char *src = args.GetArgumentAtIndex(0); + const char *dst = args.GetArgumentAtIndex(1); + + FileSpec src_fs(src); + FileSystem::Instance().Resolve(src_fs); + FileSpec dst_fs(dst ? dst : src_fs.GetFilename().GetCString()); + + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + Status error(platform_sp->PutFile(src_fs, dst_fs)); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform currently selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// "platform process launch" +class CommandObjectPlatformProcessLaunch : public CommandObjectParsed { +public: + CommandObjectPlatformProcessLaunch(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform process launch", + "Launch a new process on a remote platform.", + "platform process launch program", + eCommandRequiresTarget | eCommandTryTargetAPILock), + m_options() {} + + ~CommandObjectPlatformProcessLaunch() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + Target *target = GetDebugger().GetSelectedTarget().get(); + PlatformSP platform_sp; + if (target) { + platform_sp = target->GetPlatform(); + } + if (!platform_sp) { + platform_sp = GetDebugger().GetPlatformList().GetSelectedPlatform(); + } + + if (platform_sp) { + Status error; + const size_t argc = args.GetArgumentCount(); + Target *target = m_exe_ctx.GetTargetPtr(); + Module *exe_module = target->GetExecutableModulePointer(); + if (exe_module) { + m_options.launch_info.GetExecutableFile() = exe_module->GetFileSpec(); + llvm::SmallString<128> exe_path; + m_options.launch_info.GetExecutableFile().GetPath(exe_path); + if (!exe_path.empty()) + m_options.launch_info.GetArguments().AppendArgument(exe_path); + m_options.launch_info.GetArchitecture() = exe_module->GetArchitecture(); + } + + if (argc > 0) { + if (m_options.launch_info.GetExecutableFile()) { + // We already have an executable file, so we will use this and all + // arguments to this function are extra arguments + m_options.launch_info.GetArguments().AppendArguments(args); + } else { + // We don't have any file yet, so the first argument is our + // executable, and the rest are program arguments + const bool first_arg_is_executable = true; + m_options.launch_info.SetArguments(args, first_arg_is_executable); + } + } + + if (m_options.launch_info.GetExecutableFile()) { + Debugger &debugger = GetDebugger(); + + if (argc == 0) + target->GetRunArguments(m_options.launch_info.GetArguments()); + + ProcessSP process_sp(platform_sp->DebugProcess( + m_options.launch_info, debugger, target, error)); + if (process_sp && process_sp->IsAlive()) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } + + if (error.Success()) + result.AppendError("process launch failed"); + else + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } else { + result.AppendError("'platform process launch' uses the current target " + "file and arguments, or the executable and its " + "arguments can be specified in this command"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + result.AppendError("no platform is selected\n"); + } + return result.Succeeded(); + } + +protected: + ProcessLaunchCommandOptions m_options; +}; + +// "platform process list" + +static PosixPlatformCommandOptionValidator posix_validator; +#define LLDB_OPTIONS_platform_process_list +#include "CommandOptions.inc" + +class CommandObjectPlatformProcessList : public CommandObjectParsed { +public: + CommandObjectPlatformProcessList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform process list", + "List processes on a remote platform by name, pid, " + "or many other matching attributes.", + "platform process list", 0), + m_options() {} + + ~CommandObjectPlatformProcessList() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + Target *target = GetDebugger().GetSelectedTarget().get(); + PlatformSP platform_sp; + if (target) { + platform_sp = target->GetPlatform(); + } + if (!platform_sp) { + platform_sp = GetDebugger().GetPlatformList().GetSelectedPlatform(); + } + + if (platform_sp) { + Status error; + if (args.GetArgumentCount() == 0) { + if (platform_sp) { + Stream &ostrm = result.GetOutputStream(); + + lldb::pid_t pid = + m_options.match_info.GetProcessInfo().GetProcessID(); + if (pid != LLDB_INVALID_PROCESS_ID) { + ProcessInstanceInfo proc_info; + if (platform_sp->GetProcessInfo(pid, proc_info)) { + ProcessInstanceInfo::DumpTableHeader(ostrm, m_options.show_args, + m_options.verbose); + proc_info.DumpAsTableRow(ostrm, platform_sp->GetUserIDResolver(), + m_options.show_args, m_options.verbose); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat( + "no process found with pid = %" PRIu64 "\n", pid); + result.SetStatus(eReturnStatusFailed); + } + } else { + ProcessInstanceInfoList proc_infos; + const uint32_t matches = + platform_sp->FindProcesses(m_options.match_info, proc_infos); + const char *match_desc = nullptr; + const char *match_name = + m_options.match_info.GetProcessInfo().GetName(); + if (match_name && match_name[0]) { + switch (m_options.match_info.GetNameMatchType()) { + case NameMatch::Ignore: + break; + case NameMatch::Equals: + match_desc = "matched"; + break; + case NameMatch::Contains: + match_desc = "contained"; + break; + case NameMatch::StartsWith: + match_desc = "started with"; + break; + case NameMatch::EndsWith: + match_desc = "ended with"; + break; + case NameMatch::RegularExpression: + match_desc = "matched the regular expression"; + break; + } + } + + if (matches == 0) { + if (match_desc) + result.AppendErrorWithFormat( + "no processes were found that %s \"%s\" on the \"%s\" " + "platform\n", + match_desc, match_name, + platform_sp->GetPluginName().GetCString()); + else + result.AppendErrorWithFormat( + "no processes were found on the \"%s\" platform\n", + platform_sp->GetPluginName().GetCString()); + result.SetStatus(eReturnStatusFailed); + } else { + result.AppendMessageWithFormat( + "%u matching process%s found on \"%s\"", matches, + matches > 1 ? "es were" : " was", + platform_sp->GetName().GetCString()); + if (match_desc) + result.AppendMessageWithFormat(" whose name %s \"%s\"", + match_desc, match_name); + result.AppendMessageWithFormat("\n"); + ProcessInstanceInfo::DumpTableHeader(ostrm, m_options.show_args, + m_options.verbose); + for (uint32_t i = 0; i < matches; ++i) { + proc_infos.GetProcessInfoAtIndex(i).DumpAsTableRow( + ostrm, platform_sp->GetUserIDResolver(), + m_options.show_args, m_options.verbose); + } + } + } + } + } else { + result.AppendError("invalid args: process list takes only options\n"); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform is selected\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), match_info(), show_args(false), verbose(false) { + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + bool success = false; + + uint32_t id = LLDB_INVALID_PROCESS_ID; + success = !option_arg.getAsInteger(0, id); + switch (short_option) { + case 'p': { + match_info.GetProcessInfo().SetProcessID(id); + if (!success) + error.SetErrorStringWithFormat("invalid process ID string: '%s'", + option_arg.str().c_str()); + break; + } + case 'P': + match_info.GetProcessInfo().SetParentProcessID(id); + if (!success) + error.SetErrorStringWithFormat( + "invalid parent process ID string: '%s'", + option_arg.str().c_str()); + break; + + case 'u': + match_info.GetProcessInfo().SetUserID(success ? id : UINT32_MAX); + if (!success) + error.SetErrorStringWithFormat("invalid user ID string: '%s'", + option_arg.str().c_str()); + break; + + case 'U': + match_info.GetProcessInfo().SetEffectiveUserID(success ? id + : UINT32_MAX); + if (!success) + error.SetErrorStringWithFormat( + "invalid effective user ID string: '%s'", + option_arg.str().c_str()); + break; + + case 'g': + match_info.GetProcessInfo().SetGroupID(success ? id : UINT32_MAX); + if (!success) + error.SetErrorStringWithFormat("invalid group ID string: '%s'", + option_arg.str().c_str()); + break; + + case 'G': + match_info.GetProcessInfo().SetEffectiveGroupID(success ? id + : UINT32_MAX); + if (!success) + error.SetErrorStringWithFormat( + "invalid effective group ID string: '%s'", + option_arg.str().c_str()); + break; + + case 'a': { + TargetSP target_sp = + execution_context ? execution_context->GetTargetSP() : TargetSP(); + DebuggerSP debugger_sp = + target_sp ? target_sp->GetDebugger().shared_from_this() + : DebuggerSP(); + PlatformSP platform_sp = + debugger_sp ? debugger_sp->GetPlatformList().GetSelectedPlatform() + : PlatformSP(); + match_info.GetProcessInfo().GetArchitecture() = + Platform::GetAugmentedArchSpec(platform_sp.get(), option_arg); + } break; + + case 'n': + match_info.GetProcessInfo().GetExecutableFile().SetFile( + option_arg, FileSpec::Style::native); + match_info.SetNameMatchType(NameMatch::Equals); + break; + + case 'e': + match_info.GetProcessInfo().GetExecutableFile().SetFile( + option_arg, FileSpec::Style::native); + match_info.SetNameMatchType(NameMatch::EndsWith); + break; + + case 's': + match_info.GetProcessInfo().GetExecutableFile().SetFile( + option_arg, FileSpec::Style::native); + match_info.SetNameMatchType(NameMatch::StartsWith); + break; + + case 'c': + match_info.GetProcessInfo().GetExecutableFile().SetFile( + option_arg, FileSpec::Style::native); + match_info.SetNameMatchType(NameMatch::Contains); + break; + + case 'r': + match_info.GetProcessInfo().GetExecutableFile().SetFile( + option_arg, FileSpec::Style::native); + match_info.SetNameMatchType(NameMatch::RegularExpression); + break; + + case 'A': + show_args = true; + break; + + case 'v': + verbose = true; + break; + + case 'x': + match_info.SetMatchAllUsers(true); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + match_info.Clear(); + show_args = false; + verbose = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_platform_process_list_options); + } + + // Instance variables to hold the values for command options. + + ProcessInstanceInfoMatch match_info; + bool show_args; + bool verbose; + }; + + CommandOptions m_options; +}; + +// "platform process info" +class CommandObjectPlatformProcessInfo : public CommandObjectParsed { +public: + CommandObjectPlatformProcessInfo(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "platform process info", + "Get detailed information for one or more process by process ID.", + "platform process info <pid> [<pid> <pid> ...]", 0) { + CommandArgumentEntry arg; + CommandArgumentData pid_args; + + // Define the first (and only) variant of this arg. + pid_args.arg_type = eArgTypePid; + pid_args.arg_repetition = eArgRepeatStar; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(pid_args); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectPlatformProcessInfo() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + Target *target = GetDebugger().GetSelectedTarget().get(); + PlatformSP platform_sp; + if (target) { + platform_sp = target->GetPlatform(); + } + if (!platform_sp) { + platform_sp = GetDebugger().GetPlatformList().GetSelectedPlatform(); + } + + if (platform_sp) { + const size_t argc = args.GetArgumentCount(); + if (argc > 0) { + Status error; + + if (platform_sp->IsConnected()) { + Stream &ostrm = result.GetOutputStream(); + for (auto &entry : args.entries()) { + lldb::pid_t pid; + if (entry.ref().getAsInteger(0, pid)) { + result.AppendErrorWithFormat("invalid process ID argument '%s'", + entry.ref().str().c_str()); + result.SetStatus(eReturnStatusFailed); + break; + } else { + ProcessInstanceInfo proc_info; + if (platform_sp->GetProcessInfo(pid, proc_info)) { + ostrm.Printf("Process information for process %" PRIu64 ":\n", + pid); + proc_info.Dump(ostrm, platform_sp->GetUserIDResolver()); + } else { + ostrm.Printf("error: no process information is available for " + "process %" PRIu64 "\n", + pid); + } + ostrm.EOL(); + } + } + } else { + // Not connected... + result.AppendErrorWithFormat( + "not connected to '%s'", + platform_sp->GetPluginName().GetCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + // No args + result.AppendError("one or more process id(s) must be specified"); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("no platform is currently selected"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +#define LLDB_OPTIONS_platform_process_attach +#include "CommandOptions.inc" + +class CommandObjectPlatformProcessAttach : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + char short_option = (char)m_getopt_table[option_idx].val; + switch (short_option) { + case 'p': { + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + if (option_arg.getAsInteger(0, pid)) { + error.SetErrorStringWithFormat("invalid process ID '%s'", + option_arg.str().c_str()); + } else { + attach_info.SetProcessID(pid); + } + } break; + + case 'P': + attach_info.SetProcessPluginName(option_arg); + break; + + case 'n': + attach_info.GetExecutableFile().SetFile(option_arg, + FileSpec::Style::native); + break; + + case 'w': + attach_info.SetWaitForLaunch(true); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + attach_info.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_platform_process_attach_options); + } + + void HandleOptionArgumentCompletion( + CompletionRequest &request, OptionElementVector &opt_element_vector, + int opt_element_index, CommandInterpreter &interpreter) override { + int opt_arg_pos = opt_element_vector[opt_element_index].opt_arg_pos; + int opt_defs_index = opt_element_vector[opt_element_index].opt_defs_index; + + // We are only completing the name option for now... + + // Are we in the name? + if (GetDefinitions()[opt_defs_index].short_option != 'n') + return; + + // Look to see if there is a -P argument provided, and if so use that + // plugin, otherwise use the default plugin. + + const char *partial_name = nullptr; + partial_name = request.GetParsedLine().GetArgumentAtIndex(opt_arg_pos); + + PlatformSP platform_sp(interpreter.GetPlatform(true)); + if (!platform_sp) + return; + + ProcessInstanceInfoList process_infos; + ProcessInstanceInfoMatch match_info; + if (partial_name) { + match_info.GetProcessInfo().GetExecutableFile().SetFile( + partial_name, FileSpec::Style::native); + match_info.SetNameMatchType(NameMatch::StartsWith); + } + platform_sp->FindProcesses(match_info, process_infos); + const uint32_t num_matches = process_infos.GetSize(); + if (num_matches == 0) + return; + + for (uint32_t i = 0; i < num_matches; ++i) { + request.AddCompletion(process_infos.GetProcessNameAtIndex(i)); + } + return; + } + + // Options table: Required for subclasses of Options. + + static OptionDefinition g_option_table[]; + + // Instance variables to hold the values for command options. + + ProcessAttachInfo attach_info; + }; + + CommandObjectPlatformProcessAttach(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform process attach", + "Attach to a process.", + "platform process attach <cmd-options>"), + m_options() {} + + ~CommandObjectPlatformProcessAttach() override = default; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + Status err; + ProcessSP remote_process_sp = platform_sp->Attach( + m_options.attach_info, GetDebugger(), nullptr, err); + if (err.Fail()) { + result.AppendError(err.AsCString()); + result.SetStatus(eReturnStatusFailed); + } else if (!remote_process_sp) { + result.AppendError("could not attach: unknown reason"); + result.SetStatus(eReturnStatusFailed); + } else + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("no platform is currently selected"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + Options *GetOptions() override { return &m_options; } + +protected: + CommandOptions m_options; +}; + +class CommandObjectPlatformProcess : public CommandObjectMultiword { +public: + // Constructors and Destructors + CommandObjectPlatformProcess(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "platform process", + "Commands to query, launch and attach to " + "processes on the current platform.", + "platform process [attach|launch|list] ...") { + LoadSubCommand( + "attach", + CommandObjectSP(new CommandObjectPlatformProcessAttach(interpreter))); + LoadSubCommand( + "launch", + CommandObjectSP(new CommandObjectPlatformProcessLaunch(interpreter))); + LoadSubCommand("info", CommandObjectSP(new CommandObjectPlatformProcessInfo( + interpreter))); + LoadSubCommand("list", CommandObjectSP(new CommandObjectPlatformProcessList( + interpreter))); + } + + ~CommandObjectPlatformProcess() override = default; + +private: + // For CommandObjectPlatform only + DISALLOW_COPY_AND_ASSIGN(CommandObjectPlatformProcess); +}; + +// "platform shell" +#define LLDB_OPTIONS_platform_shell +#include "CommandOptions.inc" + +class CommandObjectPlatformShell : public CommandObjectRaw { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_platform_shell_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + + const char short_option = (char)GetDefinitions()[option_idx].short_option; + + switch (short_option) { + case 't': + uint32_t timeout_sec; + if (option_arg.getAsInteger(10, timeout_sec)) + error.SetErrorStringWithFormat( + "could not convert \"%s\" to a numeric value.", + option_arg.str().c_str()); + else + timeout = std::chrono::seconds(timeout_sec); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override {} + + Timeout<std::micro> timeout = std::chrono::seconds(10); + }; + + CommandObjectPlatformShell(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "platform shell", + "Run a shell command on the current platform.", + "platform shell <shell-command>", 0), + m_options() {} + + ~CommandObjectPlatformShell() override = default; + + Options *GetOptions() override { return &m_options; } + + bool DoExecute(llvm::StringRef raw_command_line, + CommandReturnObject &result) override { + ExecutionContext exe_ctx = GetCommandInterpreter().GetExecutionContext(); + m_options.NotifyOptionParsingStarting(&exe_ctx); + + + // Print out an usage syntax on an empty command line. + if (raw_command_line.empty()) { + result.GetOutputStream().Printf("%s\n", this->GetSyntax().str().c_str()); + return true; + } + + OptionsWithRaw args(raw_command_line); + const char *expr = args.GetRawPart().c_str(); + + if (args.HasArgs()) + if (!ParseOptions(args.GetArgs(), result)) + return false; + + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + Status error; + if (platform_sp) { + FileSpec working_dir{}; + std::string output; + int status = -1; + int signo = -1; + error = (platform_sp->RunShellCommand(expr, working_dir, &status, &signo, + &output, m_options.timeout)); + if (!output.empty()) + result.GetOutputStream().PutCString(output); + if (status > 0) { + if (signo > 0) { + const char *signo_cstr = Host::GetSignalAsCString(signo); + if (signo_cstr) + result.GetOutputStream().Printf( + "error: command returned with status %i and signal %s\n", + status, signo_cstr); + else + result.GetOutputStream().Printf( + "error: command returned with status %i and signal %i\n", + status, signo); + } else + result.GetOutputStream().Printf( + "error: command returned with status %i\n", status); + } + } else { + result.GetOutputStream().Printf( + "error: cannot run remote shell commands without a platform\n"); + error.SetErrorString( + "error: cannot run remote shell commands without a platform"); + } + + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } else { + result.SetStatus(eReturnStatusSuccessFinishResult); + } + return true; + } + + CommandOptions m_options; +}; + +// "platform install" - install a target to a remote end +class CommandObjectPlatformInstall : public CommandObjectParsed { +public: + CommandObjectPlatformInstall(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "platform target-install", + "Install a target (bundle or executable file) to the remote end.", + "platform target-install <local-thing> <remote-sandbox>", 0) {} + + ~CommandObjectPlatformInstall() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + if (args.GetArgumentCount() != 2) { + result.AppendError("platform target-install takes two arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + // TODO: move the bulk of this code over to the platform itself + FileSpec src(args.GetArgumentAtIndex(0)); + FileSystem::Instance().Resolve(src); + FileSpec dst(args.GetArgumentAtIndex(1)); + if (!FileSystem::Instance().Exists(src)) { + result.AppendError("source location does not exist or is not accessible"); + result.SetStatus(eReturnStatusFailed); + return false; + } + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (!platform_sp) { + result.AppendError("no platform currently selected"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Status error = platform_sp->Install(src, dst); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendErrorWithFormat("install failed: %s", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +CommandObjectPlatform::CommandObjectPlatform(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "platform", "Commands to manage and create platforms.", + "platform [connect|disconnect|info|list|status|select] ...") { + LoadSubCommand("select", + CommandObjectSP(new CommandObjectPlatformSelect(interpreter))); + LoadSubCommand("list", + CommandObjectSP(new CommandObjectPlatformList(interpreter))); + LoadSubCommand("status", + CommandObjectSP(new CommandObjectPlatformStatus(interpreter))); + LoadSubCommand("connect", CommandObjectSP( + new CommandObjectPlatformConnect(interpreter))); + LoadSubCommand( + "disconnect", + CommandObjectSP(new CommandObjectPlatformDisconnect(interpreter))); + LoadSubCommand("settings", CommandObjectSP(new CommandObjectPlatformSettings( + interpreter))); + LoadSubCommand("mkdir", + CommandObjectSP(new CommandObjectPlatformMkDir(interpreter))); + LoadSubCommand("file", + CommandObjectSP(new CommandObjectPlatformFile(interpreter))); + LoadSubCommand("get-file", CommandObjectSP(new CommandObjectPlatformGetFile( + interpreter))); + LoadSubCommand("get-size", CommandObjectSP(new CommandObjectPlatformGetSize( + interpreter))); + LoadSubCommand("put-file", CommandObjectSP(new CommandObjectPlatformPutFile( + interpreter))); + LoadSubCommand("process", CommandObjectSP( + new CommandObjectPlatformProcess(interpreter))); + LoadSubCommand("shell", + CommandObjectSP(new CommandObjectPlatformShell(interpreter))); + LoadSubCommand( + "target-install", + CommandObjectSP(new CommandObjectPlatformInstall(interpreter))); +} + +CommandObjectPlatform::~CommandObjectPlatform() = default; diff --git a/lldb/source/Commands/CommandObjectPlatform.h b/lldb/source/Commands/CommandObjectPlatform.h new file mode 100644 index 0000000000000..c94d2ea2cc4d1 --- /dev/null +++ b/lldb/source/Commands/CommandObjectPlatform.h @@ -0,0 +1,31 @@ +//===-- CommandObjectPlatform.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_CommandObjectPlatform_h_ +#define liblldb_CommandObjectPlatform_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" + +namespace lldb_private { + +// CommandObjectPlatform + +class CommandObjectPlatform : public CommandObjectMultiword { +public: + CommandObjectPlatform(CommandInterpreter &interpreter); + + ~CommandObjectPlatform() override; + +private: + DISALLOW_COPY_AND_ASSIGN(CommandObjectPlatform); +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectPlatform_h_ diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp new file mode 100644 index 0000000000000..b70885061385f --- /dev/null +++ b/lldb/source/Commands/CommandObjectPlugin.cpp @@ -0,0 +1,82 @@ +//===-- CommandObjectPlugin.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 "CommandObjectPlugin.h" +#include "lldb/Host/Host.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" + +using namespace lldb; +using namespace lldb_private; + +class CommandObjectPluginLoad : public CommandObjectParsed { +public: + CommandObjectPluginLoad(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin load", + "Import a dylib that implements an LLDB plugin.", + nullptr) { + CommandArgumentEntry arg1; + CommandArgumentData cmd_arg; + + // Define the first (and only) variant of this arg. + cmd_arg.arg_type = eArgTypeFilename; + cmd_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(cmd_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectPluginLoad() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, + request, nullptr); + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + size_t argc = command.GetArgumentCount(); + + if (argc != 1) { + result.AppendError("'plugin load' requires one argument"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Status error; + + FileSpec dylib_fspec(command[0].ref()); + FileSystem::Instance().Resolve(dylib_fspec); + + if (GetDebugger().LoadPlugin(dylib_fspec, error)) + result.SetStatus(eReturnStatusSuccessFinishResult); + else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } +}; + +CommandObjectPlugin::CommandObjectPlugin(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "plugin", + "Commands for managing LLDB plugins.", + "plugin <subcommand> [<subcommand-options>]") { + LoadSubCommand("load", + CommandObjectSP(new CommandObjectPluginLoad(interpreter))); +} + +CommandObjectPlugin::~CommandObjectPlugin() = default; diff --git a/lldb/source/Commands/CommandObjectPlugin.h b/lldb/source/Commands/CommandObjectPlugin.h new file mode 100644 index 0000000000000..0aabb13994079 --- /dev/null +++ b/lldb/source/Commands/CommandObjectPlugin.h @@ -0,0 +1,28 @@ +//===-- CommandObjectPlugin.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_CommandObjectPlugin_h_ +#define liblldb_CommandObjectPlugin_h_ + + + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/lldb-types.h" + +namespace lldb_private { + +class CommandObjectPlugin : public CommandObjectMultiword { +public: + CommandObjectPlugin(CommandInterpreter &interpreter); + + ~CommandObjectPlugin() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectPlugin_h_ diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp new file mode 100644 index 0000000000000..e5aa78afabb31 --- /dev/null +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -0,0 +1,1514 @@ +//===-- CommandObjectProcess.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 "CommandObjectProcess.h" +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Breakpoint/BreakpointSite.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Host/StringConvert.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/State.h" + +using namespace lldb; +using namespace lldb_private; + +class CommandObjectProcessLaunchOrAttach : public CommandObjectParsed { +public: + CommandObjectProcessLaunchOrAttach(CommandInterpreter &interpreter, + const char *name, const char *help, + const char *syntax, uint32_t flags, + const char *new_process_action) + : CommandObjectParsed(interpreter, name, help, syntax, flags), + m_new_process_action(new_process_action) {} + + ~CommandObjectProcessLaunchOrAttach() override = default; + +protected: + bool StopProcessIfNecessary(Process *process, StateType &state, + CommandReturnObject &result) { + state = eStateInvalid; + if (process) { + state = process->GetState(); + + if (process->IsAlive() && state != eStateConnected) { + char message[1024]; + if (process->GetState() == eStateAttaching) + ::snprintf(message, sizeof(message), + "There is a pending attach, abort it and %s?", + m_new_process_action.c_str()); + else if (process->GetShouldDetach()) + ::snprintf(message, sizeof(message), + "There is a running process, detach from it and %s?", + m_new_process_action.c_str()); + else + ::snprintf(message, sizeof(message), + "There is a running process, kill it and %s?", + m_new_process_action.c_str()); + + if (!m_interpreter.Confirm(message, true)) { + result.SetStatus(eReturnStatusFailed); + return false; + } else { + if (process->GetShouldDetach()) { + bool keep_stopped = false; + Status detach_error(process->Detach(keep_stopped)); + if (detach_error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + process = nullptr; + } else { + result.AppendErrorWithFormat( + "Failed to detach from process: %s\n", + detach_error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + Status destroy_error(process->Destroy(false)); + if (destroy_error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + process = nullptr; + } else { + result.AppendErrorWithFormat("Failed to kill process: %s\n", + destroy_error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + } + } + } + return result.Succeeded(); + } + + std::string m_new_process_action; +}; + +// CommandObjectProcessLaunch +#pragma mark CommandObjectProcessLaunch +class CommandObjectProcessLaunch : public CommandObjectProcessLaunchOrAttach { +public: + CommandObjectProcessLaunch(CommandInterpreter &interpreter) + : CommandObjectProcessLaunchOrAttach( + interpreter, "process launch", + "Launch the executable in the debugger.", nullptr, + eCommandRequiresTarget, "restart"), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData run_args_arg; + + // Define the first (and only) variant of this arg. + run_args_arg.arg_type = eArgTypeRunArgs; + run_args_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(run_args_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectProcessLaunch() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, + request, nullptr); + } + + Options *GetOptions() override { return &m_options; } + + const char *GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + // No repeat for "process launch"... + return ""; + } + +protected: + bool DoExecute(Args &launch_args, CommandReturnObject &result) override { + Debugger &debugger = GetDebugger(); + Target *target = debugger.GetSelectedTarget().get(); + // If our listener is nullptr, users aren't allows to launch + ModuleSP exe_module_sp = target->GetExecutableModule(); + + if (exe_module_sp == nullptr) { + result.AppendError("no file in target, create a debug target using the " + "'target create' command"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + StateType state = eStateInvalid; + + if (!StopProcessIfNecessary(m_exe_ctx.GetProcessPtr(), state, result)) + return false; + + llvm::StringRef target_settings_argv0 = target->GetArg0(); + + // Determine whether we will disable ASLR or leave it in the default state + // (i.e. enabled if the platform supports it). First check if the process + // launch options explicitly turn on/off + // disabling ASLR. If so, use that setting; + // otherwise, use the 'settings target.disable-aslr' setting. + bool disable_aslr = false; + if (m_options.disable_aslr != eLazyBoolCalculate) { + // The user specified an explicit setting on the process launch line. + // Use it. + disable_aslr = (m_options.disable_aslr == eLazyBoolYes); + } else { + // The user did not explicitly specify whether to disable ASLR. Fall + // back to the target.disable-aslr setting. + disable_aslr = target->GetDisableASLR(); + } + + if (disable_aslr) + m_options.launch_info.GetFlags().Set(eLaunchFlagDisableASLR); + else + m_options.launch_info.GetFlags().Clear(eLaunchFlagDisableASLR); + + if (target->GetDetachOnError()) + m_options.launch_info.GetFlags().Set(eLaunchFlagDetachOnError); + + if (target->GetDisableSTDIO()) + m_options.launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO); + + // Merge the launch info environment with the target environment. + Environment target_env = target->GetEnvironment(); + m_options.launch_info.GetEnvironment().insert(target_env.begin(), + target_env.end()); + + if (!target_settings_argv0.empty()) { + m_options.launch_info.GetArguments().AppendArgument( + target_settings_argv0); + m_options.launch_info.SetExecutableFile( + exe_module_sp->GetPlatformFileSpec(), false); + } else { + m_options.launch_info.SetExecutableFile( + exe_module_sp->GetPlatformFileSpec(), true); + } + + if (launch_args.GetArgumentCount() == 0) { + m_options.launch_info.GetArguments().AppendArguments( + target->GetProcessLaunchInfo().GetArguments()); + } else { + m_options.launch_info.GetArguments().AppendArguments(launch_args); + // Save the arguments for subsequent runs in the current target. + target->SetRunArguments(launch_args); + } + + StreamString stream; + Status error = target->Launch(m_options.launch_info, &stream); + + if (error.Success()) { + ProcessSP process_sp(target->GetProcessSP()); + if (process_sp) { + // There is a race condition where this thread will return up the call + // stack to the main command handler and show an (lldb) prompt before + // HandlePrivateEvent (from PrivateStateThread) has a chance to call + // PushProcessIOHandler(). + process_sp->SyncIOHandler(0, std::chrono::seconds(2)); + + llvm::StringRef data = stream.GetString(); + if (!data.empty()) + result.AppendMessage(data); + const char *archname = + exe_module_sp->GetArchitecture().GetArchitectureName(); + result.AppendMessageWithFormat( + "Process %" PRIu64 " launched: '%s' (%s)\n", process_sp->GetID(), + exe_module_sp->GetFileSpec().GetPath().c_str(), archname); + result.SetStatus(eReturnStatusSuccessFinishResult); + result.SetDidChangeProcessState(true); + } else { + result.AppendError( + "no error returned from Target::Launch, and target has no process"); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + +protected: + ProcessLaunchCommandOptions m_options; +}; + +#define LLDB_OPTIONS_process_attach +#include "CommandOptions.inc" + +#pragma mark CommandObjectProcessAttach +class CommandObjectProcessAttach : public CommandObjectProcessLaunchOrAttach { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + switch (short_option) { + case 'c': + attach_info.SetContinueOnceAttached(true); + break; + + case 'p': { + lldb::pid_t pid; + if (option_arg.getAsInteger(0, pid)) { + error.SetErrorStringWithFormat("invalid process ID '%s'", + option_arg.str().c_str()); + } else { + attach_info.SetProcessID(pid); + } + } break; + + case 'P': + attach_info.SetProcessPluginName(option_arg); + break; + + case 'n': + attach_info.GetExecutableFile().SetFile(option_arg, + FileSpec::Style::native); + break; + + case 'w': + attach_info.SetWaitForLaunch(true); + break; + + case 'i': + attach_info.SetIgnoreExisting(false); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + attach_info.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_process_attach_options); + } + + void HandleOptionArgumentCompletion( + CompletionRequest &request, OptionElementVector &opt_element_vector, + int opt_element_index, CommandInterpreter &interpreter) override { + int opt_arg_pos = opt_element_vector[opt_element_index].opt_arg_pos; + int opt_defs_index = opt_element_vector[opt_element_index].opt_defs_index; + + // We are only completing the name option for now... + + // Are we in the name? + if (GetDefinitions()[opt_defs_index].short_option != 'n') + return; + + // Look to see if there is a -P argument provided, and if so use that + // plugin, otherwise use the default plugin. + + const char *partial_name = nullptr; + partial_name = request.GetParsedLine().GetArgumentAtIndex(opt_arg_pos); + + PlatformSP platform_sp(interpreter.GetPlatform(true)); + if (!platform_sp) + return; + ProcessInstanceInfoList process_infos; + ProcessInstanceInfoMatch match_info; + if (partial_name) { + match_info.GetProcessInfo().GetExecutableFile().SetFile( + partial_name, FileSpec::Style::native); + match_info.SetNameMatchType(NameMatch::StartsWith); + } + platform_sp->FindProcesses(match_info, process_infos); + const size_t num_matches = process_infos.GetSize(); + if (num_matches == 0) + return; + for (size_t i = 0; i < num_matches; ++i) { + request.AddCompletion(process_infos.GetProcessNameAtIndex(i)); + } + } + + // Instance variables to hold the values for command options. + + ProcessAttachInfo attach_info; + }; + + CommandObjectProcessAttach(CommandInterpreter &interpreter) + : CommandObjectProcessLaunchOrAttach( + interpreter, "process attach", "Attach to a process.", + "process attach <cmd-options>", 0, "attach"), + m_options() {} + + ~CommandObjectProcessAttach() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + + Target *target = GetDebugger().GetSelectedTarget().get(); + // N.B. The attach should be synchronous. It doesn't help much to get the + // prompt back between initiating the attach and the target actually + // stopping. So even if the interpreter is set to be asynchronous, we wait + // for the stop ourselves here. + + StateType state = eStateInvalid; + Process *process = m_exe_ctx.GetProcessPtr(); + + if (!StopProcessIfNecessary(process, state, result)) + return false; + + if (target == nullptr) { + // If there isn't a current target create one. + TargetSP new_target_sp; + Status error; + + error = GetDebugger().GetTargetList().CreateTarget( + GetDebugger(), "", "", eLoadDependentsNo, + nullptr, // No platform options + new_target_sp); + target = new_target_sp.get(); + if (target == nullptr || error.Fail()) { + result.AppendError(error.AsCString("Error creating target")); + return false; + } + GetDebugger().GetTargetList().SetSelectedTarget(target); + } + + // Record the old executable module, we want to issue a warning if the + // process of attaching changed the current executable (like somebody said + // "file foo" then attached to a PID whose executable was bar.) + + ModuleSP old_exec_module_sp = target->GetExecutableModule(); + ArchSpec old_arch_spec = target->GetArchitecture(); + + if (command.GetArgumentCount()) { + result.AppendErrorWithFormat("Invalid arguments for '%s'.\nUsage: %s\n", + m_cmd_name.c_str(), m_cmd_syntax.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + m_interpreter.UpdateExecutionContext(nullptr); + StreamString stream; + const auto error = target->Attach(m_options.attach_info, &stream); + if (error.Success()) { + ProcessSP process_sp(target->GetProcessSP()); + if (process_sp) { + result.AppendMessage(stream.GetString()); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + result.SetDidChangeProcessState(true); + } else { + result.AppendError( + "no error returned from Target::Attach, and target has no process"); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("attach failed: %s\n", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + + if (!result.Succeeded()) + return false; + + // Okay, we're done. Last step is to warn if the executable module has + // changed: + char new_path[PATH_MAX]; + ModuleSP new_exec_module_sp(target->GetExecutableModule()); + if (!old_exec_module_sp) { + // We might not have a module if we attached to a raw pid... + if (new_exec_module_sp) { + new_exec_module_sp->GetFileSpec().GetPath(new_path, PATH_MAX); + result.AppendMessageWithFormat("Executable module set to \"%s\".\n", + new_path); + } + } else if (old_exec_module_sp->GetFileSpec() != + new_exec_module_sp->GetFileSpec()) { + char old_path[PATH_MAX]; + + old_exec_module_sp->GetFileSpec().GetPath(old_path, PATH_MAX); + new_exec_module_sp->GetFileSpec().GetPath(new_path, PATH_MAX); + + result.AppendWarningWithFormat( + "Executable module changed from \"%s\" to \"%s\".\n", old_path, + new_path); + } + + if (!old_arch_spec.IsValid()) { + result.AppendMessageWithFormat( + "Architecture set to: %s.\n", + target->GetArchitecture().GetTriple().getTriple().c_str()); + } else if (!old_arch_spec.IsExactMatch(target->GetArchitecture())) { + result.AppendWarningWithFormat( + "Architecture changed from %s to %s.\n", + old_arch_spec.GetTriple().getTriple().c_str(), + target->GetArchitecture().GetTriple().getTriple().c_str()); + } + + // This supports the use-case scenario of immediately continuing the + // process once attached. + if (m_options.attach_info.GetContinueOnceAttached()) + m_interpreter.HandleCommand("process continue", eLazyBoolNo, result); + + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectProcessContinue + +#define LLDB_OPTIONS_process_continue +#include "CommandOptions.inc" + +#pragma mark CommandObjectProcessContinue + +class CommandObjectProcessContinue : public CommandObjectParsed { +public: + CommandObjectProcessContinue(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "process continue", + "Continue execution of all threads in the current process.", + "process continue", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_options() {} + + ~CommandObjectProcessContinue() override = default; + +protected: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + switch (short_option) { + case 'i': + if (option_arg.getAsInteger(0, m_ignore)) + error.SetErrorStringWithFormat( + "invalid value for ignore option: \"%s\", should be a number.", + option_arg.str().c_str()); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_ignore = 0; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_process_continue_options); + } + + uint32_t m_ignore; + }; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + bool synchronous_execution = m_interpreter.GetSynchronous(); + StateType state = process->GetState(); + if (state == eStateStopped) { + if (command.GetArgumentCount() != 0) { + result.AppendErrorWithFormat( + "The '%s' command does not take any arguments.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_ignore > 0) { + ThreadSP sel_thread_sp(GetDefaultThread()->shared_from_this()); + if (sel_thread_sp) { + StopInfoSP stop_info_sp = sel_thread_sp->GetStopInfo(); + if (stop_info_sp && + stop_info_sp->GetStopReason() == eStopReasonBreakpoint) { + lldb::break_id_t bp_site_id = + (lldb::break_id_t)stop_info_sp->GetValue(); + BreakpointSiteSP bp_site_sp( + process->GetBreakpointSiteList().FindByID(bp_site_id)); + if (bp_site_sp) { + const size_t num_owners = bp_site_sp->GetNumberOfOwners(); + for (size_t i = 0; i < num_owners; i++) { + Breakpoint &bp_ref = + bp_site_sp->GetOwnerAtIndex(i)->GetBreakpoint(); + if (!bp_ref.IsInternal()) { + bp_ref.SetIgnoreCount(m_options.m_ignore); + } + } + } + } + } + } + + { // Scope for thread list mutex: + std::lock_guard<std::recursive_mutex> guard( + process->GetThreadList().GetMutex()); + const uint32_t num_threads = process->GetThreadList().GetSize(); + + // Set the actions that the threads should each take when resuming + for (uint32_t idx = 0; idx < num_threads; ++idx) { + const bool override_suspend = false; + process->GetThreadList().GetThreadAtIndex(idx)->SetResumeState( + eStateRunning, override_suspend); + } + } + + const uint32_t iohandler_id = process->GetIOHandlerID(); + + StreamString stream; + Status error; + if (synchronous_execution) + error = process->ResumeSynchronous(&stream); + else + error = process->Resume(); + + if (error.Success()) { + // There is a race condition where this thread will return up the call + // stack to the main command handler and show an (lldb) prompt before + // HandlePrivateEvent (from PrivateStateThread) has a chance to call + // PushProcessIOHandler(). + process->SyncIOHandler(iohandler_id, std::chrono::seconds(2)); + + result.AppendMessageWithFormat("Process %" PRIu64 " resuming\n", + process->GetID()); + if (synchronous_execution) { + // If any state changed events had anything to say, add that to the + // result + result.AppendMessage(stream.GetString()); + + result.SetDidChangeProcessState(true); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.SetStatus(eReturnStatusSuccessContinuingNoResult); + } + } else { + result.AppendErrorWithFormat("Failed to resume process: %s.\n", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat( + "Process cannot be continued from its current state (%s).\n", + StateAsCString(state)); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + Options *GetOptions() override { return &m_options; } + + CommandOptions m_options; +}; + +// CommandObjectProcessDetach +#define LLDB_OPTIONS_process_detach +#include "CommandOptions.inc" + +#pragma mark CommandObjectProcessDetach + +class CommandObjectProcessDetach : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 's': + bool tmp_result; + bool success; + tmp_result = OptionArgParser::ToBoolean(option_arg, false, &success); + if (!success) + error.SetErrorStringWithFormat("invalid boolean option: \"%s\"", + option_arg.str().c_str()); + else { + if (tmp_result) + m_keep_stopped = eLazyBoolYes; + else + m_keep_stopped = eLazyBoolNo; + } + break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_keep_stopped = eLazyBoolCalculate; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_process_detach_options); + } + + // Instance variables to hold the values for command options. + LazyBool m_keep_stopped; + }; + + CommandObjectProcessDetach(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process detach", + "Detach from the current target process.", + "process detach", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched), + m_options() {} + + ~CommandObjectProcessDetach() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + // FIXME: This will be a Command Option: + bool keep_stopped; + if (m_options.m_keep_stopped == eLazyBoolCalculate) { + // Check the process default: + keep_stopped = process->GetDetachKeepsStopped(); + } else if (m_options.m_keep_stopped == eLazyBoolYes) + keep_stopped = true; + else + keep_stopped = false; + + Status error(process->Detach(keep_stopped)); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Detach failed: %s\n", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectProcessConnect +#define LLDB_OPTIONS_process_connect +#include "CommandOptions.inc" + +#pragma mark CommandObjectProcessConnect + +class CommandObjectProcessConnect : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'p': + plugin_name.assign(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + plugin_name.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_process_connect_options); + } + + // Instance variables to hold the values for command options. + + std::string plugin_name; + }; + + CommandObjectProcessConnect(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process connect", + "Connect to a remote debug service.", + "process connect <remote-url>", 0), + m_options() {} + + ~CommandObjectProcessConnect() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.GetArgumentCount() != 1) { + result.AppendErrorWithFormat( + "'%s' takes exactly one argument:\nUsage: %s\n", m_cmd_name.c_str(), + m_cmd_syntax.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Process *process = m_exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) { + result.AppendErrorWithFormat( + "Process %" PRIu64 + " is currently being debugged, kill the process before connecting.\n", + process->GetID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *plugin_name = nullptr; + if (!m_options.plugin_name.empty()) + plugin_name = m_options.plugin_name.c_str(); + + Status error; + Debugger &debugger = GetDebugger(); + PlatformSP platform_sp = m_interpreter.GetPlatform(true); + ProcessSP process_sp = platform_sp->ConnectProcess( + command.GetArgumentAtIndex(0), plugin_name, debugger, + debugger.GetSelectedTarget().get(), error); + if (error.Fail() || process_sp == nullptr) { + result.AppendError(error.AsCString("Error connecting to the process")); + result.SetStatus(eReturnStatusFailed); + return false; + } + return true; + } + + CommandOptions m_options; +}; + +// CommandObjectProcessPlugin +#pragma mark CommandObjectProcessPlugin + +class CommandObjectProcessPlugin : public CommandObjectProxy { +public: + CommandObjectProcessPlugin(CommandInterpreter &interpreter) + : CommandObjectProxy( + interpreter, "process plugin", + "Send a custom command to the current target process plug-in.", + "process plugin <args>", 0) {} + + ~CommandObjectProcessPlugin() override = default; + + CommandObject *GetProxyCommandObject() override { + Process *process = m_interpreter.GetExecutionContext().GetProcessPtr(); + if (process) + return process->GetPluginCommandObject(); + return nullptr; + } +}; + +// CommandObjectProcessLoad +#define LLDB_OPTIONS_process_load +#include "CommandOptions.inc" + +#pragma mark CommandObjectProcessLoad + +class CommandObjectProcessLoad : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + switch (short_option) { + case 'i': + do_install = true; + if (!option_arg.empty()) + install_path.SetFile(option_arg, FileSpec::Style::native); + break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + do_install = false; + install_path.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_process_load_options); + } + + // Instance variables to hold the values for command options. + bool do_install; + FileSpec install_path; + }; + + CommandObjectProcessLoad(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process load", + "Load a shared library into the current process.", + "process load <filename> [<filename> ...]", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused), + m_options() {} + + ~CommandObjectProcessLoad() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + + for (auto &entry : command.entries()) { + Status error; + PlatformSP platform = process->GetTarget().GetPlatform(); + llvm::StringRef image_path = entry.ref(); + uint32_t image_token = LLDB_INVALID_IMAGE_TOKEN; + + if (!m_options.do_install) { + FileSpec image_spec(image_path); + platform->ResolveRemotePath(image_spec, image_spec); + image_token = + platform->LoadImage(process, FileSpec(), image_spec, error); + } else if (m_options.install_path) { + FileSpec image_spec(image_path); + FileSystem::Instance().Resolve(image_spec); + platform->ResolveRemotePath(m_options.install_path, + m_options.install_path); + image_token = platform->LoadImage(process, image_spec, + m_options.install_path, error); + } else { + FileSpec image_spec(image_path); + FileSystem::Instance().Resolve(image_spec); + image_token = + platform->LoadImage(process, image_spec, FileSpec(), error); + } + + if (image_token != LLDB_INVALID_IMAGE_TOKEN) { + result.AppendMessageWithFormat( + "Loading \"%s\"...ok\nImage %u loaded.\n", image_path.str().c_str(), + image_token); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("failed to load '%s': %s", + image_path.str().c_str(), + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectProcessUnload +#pragma mark CommandObjectProcessUnload + +class CommandObjectProcessUnload : public CommandObjectParsed { +public: + CommandObjectProcessUnload(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "process unload", + "Unload a shared library from the current process using the index " + "returned by a previous call to \"process load\".", + "process unload <index>", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) {} + + ~CommandObjectProcessUnload() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + + for (auto &entry : command.entries()) { + uint32_t image_token; + if (entry.ref().getAsInteger(0, image_token)) { + result.AppendErrorWithFormat("invalid image index argument '%s'", + entry.ref().str().c_str()); + result.SetStatus(eReturnStatusFailed); + break; + } else { + Status error(process->GetTarget().GetPlatform()->UnloadImage( + process, image_token)); + if (error.Success()) { + result.AppendMessageWithFormat( + "Unloading shared library with index %u...ok\n", image_token); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("failed to unload image: %s", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + break; + } + } + } + return result.Succeeded(); + } +}; + +// CommandObjectProcessSignal +#pragma mark CommandObjectProcessSignal + +class CommandObjectProcessSignal : public CommandObjectParsed { +public: + CommandObjectProcessSignal(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process signal", + "Send a UNIX signal to the current target process.", + nullptr, eCommandRequiresProcess | + eCommandTryTargetAPILock) { + CommandArgumentEntry arg; + CommandArgumentData signal_arg; + + // Define the first (and only) variant of this arg. + signal_arg.arg_type = eArgTypeUnixSignal; + signal_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(signal_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectProcessSignal() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + + if (command.GetArgumentCount() == 1) { + int signo = LLDB_INVALID_SIGNAL_NUMBER; + + const char *signal_name = command.GetArgumentAtIndex(0); + if (::isxdigit(signal_name[0])) + signo = + StringConvert::ToSInt32(signal_name, LLDB_INVALID_SIGNAL_NUMBER, 0); + else + signo = process->GetUnixSignals()->GetSignalNumberFromName(signal_name); + + if (signo == LLDB_INVALID_SIGNAL_NUMBER) { + result.AppendErrorWithFormat("Invalid signal argument '%s'.\n", + command.GetArgumentAtIndex(0)); + result.SetStatus(eReturnStatusFailed); + } else { + Status error(process->Signal(signo)); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Failed to send signal %i: %s\n", signo, + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + } else { + result.AppendErrorWithFormat( + "'%s' takes exactly one signal number argument:\nUsage: %s\n", + m_cmd_name.c_str(), m_cmd_syntax.c_str()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// CommandObjectProcessInterrupt +#pragma mark CommandObjectProcessInterrupt + +class CommandObjectProcessInterrupt : public CommandObjectParsed { +public: + CommandObjectProcessInterrupt(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process interrupt", + "Interrupt the current target process.", + "process interrupt", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched) {} + + ~CommandObjectProcessInterrupt() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + if (process == nullptr) { + result.AppendError("no process to halt"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + bool clear_thread_plans = true; + Status error(process->Halt(clear_thread_plans)); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Failed to halt process: %s\n", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("'%s' takes no arguments:\nUsage: %s\n", + m_cmd_name.c_str(), m_cmd_syntax.c_str()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// CommandObjectProcessKill +#pragma mark CommandObjectProcessKill + +class CommandObjectProcessKill : public CommandObjectParsed { +public: + CommandObjectProcessKill(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process kill", + "Terminate the current target process.", + "process kill", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched) {} + + ~CommandObjectProcessKill() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + if (process == nullptr) { + result.AppendError("no process to kill"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + Status error(process->Destroy(true)); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Failed to kill process: %s\n", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("'%s' takes no arguments:\nUsage: %s\n", + m_cmd_name.c_str(), m_cmd_syntax.c_str()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +// CommandObjectProcessSaveCore +#pragma mark CommandObjectProcessSaveCore + +class CommandObjectProcessSaveCore : public CommandObjectParsed { +public: + CommandObjectProcessSaveCore(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process save-core", + "Save the current process as a core file using an " + "appropriate file type.", + "process save-core FILE", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched) {} + + ~CommandObjectProcessSaveCore() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + ProcessSP process_sp = m_exe_ctx.GetProcessSP(); + if (process_sp) { + if (command.GetArgumentCount() == 1) { + FileSpec output_file(command.GetArgumentAtIndex(0)); + Status error = PluginManager::SaveCore(process_sp, output_file); + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat( + "Failed to save core file for process: %s\n", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("'%s' takes one arguments:\nUsage: %s\n", + m_cmd_name.c_str(), m_cmd_syntax.c_str()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("invalid process"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + return result.Succeeded(); + } +}; + +// CommandObjectProcessStatus +#pragma mark CommandObjectProcessStatus + +class CommandObjectProcessStatus : public CommandObjectParsed { +public: + CommandObjectProcessStatus(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "process status", + "Show status and stop location for the current target process.", + "process status", + eCommandRequiresProcess | eCommandTryTargetAPILock) {} + + ~CommandObjectProcessStatus() override = default; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + Stream &strm = result.GetOutputStream(); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + // No need to check "process" for validity as eCommandRequiresProcess + // ensures it is valid + Process *process = m_exe_ctx.GetProcessPtr(); + const bool only_threads_with_stop_reason = true; + const uint32_t start_frame = 0; + const uint32_t num_frames = 1; + const uint32_t num_frames_with_source = 1; + const bool stop_format = true; + process->GetStatus(strm); + process->GetThreadStatus(strm, only_threads_with_stop_reason, start_frame, + num_frames, num_frames_with_source, stop_format); + return result.Succeeded(); + } +}; + +// CommandObjectProcessHandle +#define LLDB_OPTIONS_process_handle +#include "CommandOptions.inc" + +#pragma mark CommandObjectProcessHandle + +class CommandObjectProcessHandle : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 's': + stop = option_arg; + break; + case 'n': + notify = option_arg; + break; + case 'p': + pass = option_arg; + break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + stop.clear(); + notify.clear(); + pass.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_process_handle_options); + } + + // Instance variables to hold the values for command options. + + std::string stop; + std::string notify; + std::string pass; + }; + + CommandObjectProcessHandle(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process handle", + "Manage LLDB handling of OS signals for the " + "current target process. Defaults to showing " + "current policy.", + nullptr, eCommandRequiresTarget), + m_options() { + SetHelpLong("\nIf no signals are specified, update them all. If no update " + "option is specified, list the current values."); + CommandArgumentEntry arg; + CommandArgumentData signal_arg; + + signal_arg.arg_type = eArgTypeUnixSignal; + signal_arg.arg_repetition = eArgRepeatStar; + + arg.push_back(signal_arg); + + m_arguments.push_back(arg); + } + + ~CommandObjectProcessHandle() override = default; + + Options *GetOptions() override { return &m_options; } + + bool VerifyCommandOptionValue(const std::string &option, int &real_value) { + bool okay = true; + bool success = false; + bool tmp_value = OptionArgParser::ToBoolean(option, false, &success); + + if (success && tmp_value) + real_value = 1; + else if (success && !tmp_value) + real_value = 0; + else { + // If the value isn't 'true' or 'false', it had better be 0 or 1. + real_value = StringConvert::ToUInt32(option.c_str(), 3); + if (real_value != 0 && real_value != 1) + okay = false; + } + + return okay; + } + + void PrintSignalHeader(Stream &str) { + str.Printf("NAME PASS STOP NOTIFY\n"); + str.Printf("=========== ===== ===== ======\n"); + } + + void PrintSignal(Stream &str, int32_t signo, const char *sig_name, + const UnixSignalsSP &signals_sp) { + bool stop; + bool suppress; + bool notify; + + str.Printf("%-11s ", sig_name); + if (signals_sp->GetSignalInfo(signo, suppress, stop, notify)) { + bool pass = !suppress; + str.Printf("%s %s %s", (pass ? "true " : "false"), + (stop ? "true " : "false"), (notify ? "true " : "false")); + } + str.Printf("\n"); + } + + void PrintSignalInformation(Stream &str, Args &signal_args, + int num_valid_signals, + const UnixSignalsSP &signals_sp) { + PrintSignalHeader(str); + + if (num_valid_signals > 0) { + size_t num_args = signal_args.GetArgumentCount(); + for (size_t i = 0; i < num_args; ++i) { + int32_t signo = signals_sp->GetSignalNumberFromName( + signal_args.GetArgumentAtIndex(i)); + if (signo != LLDB_INVALID_SIGNAL_NUMBER) + PrintSignal(str, signo, signal_args.GetArgumentAtIndex(i), + signals_sp); + } + } else // Print info for ALL signals + { + int32_t signo = signals_sp->GetFirstSignalNumber(); + while (signo != LLDB_INVALID_SIGNAL_NUMBER) { + PrintSignal(str, signo, signals_sp->GetSignalAsCString(signo), + signals_sp); + signo = signals_sp->GetNextSignalNumber(signo); + } + } + } + +protected: + bool DoExecute(Args &signal_args, CommandReturnObject &result) override { + Target *target_sp = &GetSelectedTarget(); + + ProcessSP process_sp = target_sp->GetProcessSP(); + + if (!process_sp) { + result.AppendError("No current process; cannot handle signals until you " + "have a valid process.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + int stop_action = -1; // -1 means leave the current setting alone + int pass_action = -1; // -1 means leave the current setting alone + int notify_action = -1; // -1 means leave the current setting alone + + if (!m_options.stop.empty() && + !VerifyCommandOptionValue(m_options.stop, stop_action)) { + result.AppendError("Invalid argument for command option --stop; must be " + "true or false.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!m_options.notify.empty() && + !VerifyCommandOptionValue(m_options.notify, notify_action)) { + result.AppendError("Invalid argument for command option --notify; must " + "be true or false.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!m_options.pass.empty() && + !VerifyCommandOptionValue(m_options.pass, pass_action)) { + result.AppendError("Invalid argument for command option --pass; must be " + "true or false.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + size_t num_args = signal_args.GetArgumentCount(); + UnixSignalsSP signals_sp = process_sp->GetUnixSignals(); + int num_signals_set = 0; + + if (num_args > 0) { + for (const auto &arg : signal_args) { + int32_t signo = signals_sp->GetSignalNumberFromName(arg.c_str()); + if (signo != LLDB_INVALID_SIGNAL_NUMBER) { + // Casting the actions as bools here should be okay, because + // VerifyCommandOptionValue guarantees the value is either 0 or 1. + if (stop_action != -1) + signals_sp->SetShouldStop(signo, stop_action); + if (pass_action != -1) { + bool suppress = !pass_action; + signals_sp->SetShouldSuppress(signo, suppress); + } + if (notify_action != -1) + signals_sp->SetShouldNotify(signo, notify_action); + ++num_signals_set; + } else { + result.AppendErrorWithFormat("Invalid signal name '%s'\n", + arg.c_str()); + } + } + } else { + // No signal specified, if any command options were specified, update ALL + // signals. + if ((notify_action != -1) || (stop_action != -1) || (pass_action != -1)) { + if (m_interpreter.Confirm( + "Do you really want to update all the signals?", false)) { + int32_t signo = signals_sp->GetFirstSignalNumber(); + while (signo != LLDB_INVALID_SIGNAL_NUMBER) { + if (notify_action != -1) + signals_sp->SetShouldNotify(signo, notify_action); + if (stop_action != -1) + signals_sp->SetShouldStop(signo, stop_action); + if (pass_action != -1) { + bool suppress = !pass_action; + signals_sp->SetShouldSuppress(signo, suppress); + } + signo = signals_sp->GetNextSignalNumber(signo); + } + } + } + } + + PrintSignalInformation(result.GetOutputStream(), signal_args, + num_signals_set, signals_sp); + + if (num_signals_set > 0) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + else + result.SetStatus(eReturnStatusFailed); + + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectMultiwordProcess + +CommandObjectMultiwordProcess::CommandObjectMultiwordProcess( + CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "process", + "Commands for interacting with processes on the current platform.", + "process <subcommand> [<subcommand-options>]") { + LoadSubCommand("attach", + CommandObjectSP(new CommandObjectProcessAttach(interpreter))); + LoadSubCommand("launch", + CommandObjectSP(new CommandObjectProcessLaunch(interpreter))); + LoadSubCommand("continue", CommandObjectSP(new CommandObjectProcessContinue( + interpreter))); + LoadSubCommand("connect", + CommandObjectSP(new CommandObjectProcessConnect(interpreter))); + LoadSubCommand("detach", + CommandObjectSP(new CommandObjectProcessDetach(interpreter))); + LoadSubCommand("load", + CommandObjectSP(new CommandObjectProcessLoad(interpreter))); + LoadSubCommand("unload", + CommandObjectSP(new CommandObjectProcessUnload(interpreter))); + LoadSubCommand("signal", + CommandObjectSP(new CommandObjectProcessSignal(interpreter))); + LoadSubCommand("handle", + CommandObjectSP(new CommandObjectProcessHandle(interpreter))); + LoadSubCommand("status", + CommandObjectSP(new CommandObjectProcessStatus(interpreter))); + LoadSubCommand("interrupt", CommandObjectSP(new CommandObjectProcessInterrupt( + interpreter))); + LoadSubCommand("kill", + CommandObjectSP(new CommandObjectProcessKill(interpreter))); + LoadSubCommand("plugin", + CommandObjectSP(new CommandObjectProcessPlugin(interpreter))); + LoadSubCommand("save-core", CommandObjectSP(new CommandObjectProcessSaveCore( + interpreter))); +} + +CommandObjectMultiwordProcess::~CommandObjectMultiwordProcess() = default; diff --git a/lldb/source/Commands/CommandObjectProcess.h b/lldb/source/Commands/CommandObjectProcess.h new file mode 100644 index 0000000000000..3b1ff26dbb050 --- /dev/null +++ b/lldb/source/Commands/CommandObjectProcess.h @@ -0,0 +1,27 @@ +//===-- CommandObjectProcess.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_CommandObjectProcess_h_ +#define liblldb_CommandObjectProcess_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +// CommandObjectMultiwordProcess + +class CommandObjectMultiwordProcess : public CommandObjectMultiword { +public: + CommandObjectMultiwordProcess(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordProcess() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectProcess_h_ diff --git a/lldb/source/Commands/CommandObjectQuit.cpp b/lldb/source/Commands/CommandObjectQuit.cpp new file mode 100644 index 0000000000000..70ee336f8a1bd --- /dev/null +++ b/lldb/source/Commands/CommandObjectQuit.cpp @@ -0,0 +1,107 @@ +//===-- CommandObjectQuit.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 "CommandObjectQuit.h" + +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/StreamString.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectQuit + +CommandObjectQuit::CommandObjectQuit(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "quit", "Quit the LLDB debugger.", + "quit [exit-code]") {} + +CommandObjectQuit::~CommandObjectQuit() {} + +// returns true if there is at least one alive process is_a_detach will be true +// if all alive processes will be detached when you quit and false if at least +// one process will be killed instead +bool CommandObjectQuit::ShouldAskForConfirmation(bool &is_a_detach) { + if (!m_interpreter.GetPromptOnQuit()) + return false; + bool should_prompt = false; + is_a_detach = true; + for (uint32_t debugger_idx = 0; debugger_idx < Debugger::GetNumDebuggers(); + debugger_idx++) { + DebuggerSP debugger_sp(Debugger::GetDebuggerAtIndex(debugger_idx)); + if (!debugger_sp) + continue; + const TargetList &target_list(debugger_sp->GetTargetList()); + for (uint32_t target_idx = 0; + target_idx < static_cast<uint32_t>(target_list.GetNumTargets()); + target_idx++) { + TargetSP target_sp(target_list.GetTargetAtIndex(target_idx)); + if (!target_sp) + continue; + ProcessSP process_sp(target_sp->GetProcessSP()); + if (process_sp && process_sp->IsValid() && process_sp->IsAlive() && + process_sp->WarnBeforeDetach()) { + should_prompt = true; + if (!process_sp->GetShouldDetach()) { + // if we need to kill at least one process, just say so and return + is_a_detach = false; + return should_prompt; + } + } + } + } + return should_prompt; +} + +bool CommandObjectQuit::DoExecute(Args &command, CommandReturnObject &result) { + bool is_a_detach = true; + if (ShouldAskForConfirmation(is_a_detach)) { + StreamString message; + message.Printf("Quitting LLDB will %s one or more processes. Do you really " + "want to proceed", + (is_a_detach ? "detach from" : "kill")); + if (!m_interpreter.Confirm(message.GetString(), true)) { + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (command.GetArgumentCount() > 1) { + result.AppendError("Too many arguments for 'quit'. Only an optional exit " + "code is allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // We parse the exit code argument if there is one. + if (command.GetArgumentCount() == 1) { + llvm::StringRef arg = command.GetArgumentAtIndex(0); + int exit_code; + if (arg.getAsInteger(/*autodetect radix*/ 0, exit_code)) { + lldb_private::StreamString s; + std::string arg_str = arg.str(); + s.Printf("Couldn't parse '%s' as integer for exit code.", arg_str.data()); + result.AppendError(s.GetString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (!m_interpreter.SetQuitExitCode(exit_code)) { + result.AppendError("The current driver doesn't allow custom exit codes" + " for the quit command."); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + const uint32_t event_type = + CommandInterpreter::eBroadcastBitQuitCommandReceived; + m_interpreter.BroadcastEvent(event_type); + result.SetStatus(eReturnStatusQuit); + return true; +} diff --git a/lldb/source/Commands/CommandObjectQuit.h b/lldb/source/Commands/CommandObjectQuit.h new file mode 100644 index 0000000000000..458ef2456fcaa --- /dev/null +++ b/lldb/source/Commands/CommandObjectQuit.h @@ -0,0 +1,32 @@ +//===-- CommandObjectQuit.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_CommandObjectQuit_h_ +#define liblldb_CommandObjectQuit_h_ + +#include "lldb/Interpreter/CommandObject.h" + +namespace lldb_private { + +// CommandObjectQuit + +class CommandObjectQuit : public CommandObjectParsed { +public: + CommandObjectQuit(CommandInterpreter &interpreter); + + ~CommandObjectQuit() override; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override; + + bool ShouldAskForConfirmation(bool &is_a_detach); +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectQuit_h_ diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp new file mode 100644 index 0000000000000..13266f8fce353 --- /dev/null +++ b/lldb/source/Commands/CommandObjectRegister.cpp @@ -0,0 +1,396 @@ +//===-- CommandObjectRegister.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 "CommandObjectRegister.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/DumpRegisterValue.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionGroupFormat.h" +#include "lldb/Interpreter/OptionValueArray.h" +#include "lldb/Interpreter/OptionValueBoolean.h" +#include "lldb/Interpreter/OptionValueUInt64.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/Scalar.h" +#include "llvm/Support/Errno.h" + +using namespace lldb; +using namespace lldb_private; + +// "register read" +#define LLDB_OPTIONS_register_read +#include "CommandOptions.inc" + +class CommandObjectRegisterRead : public CommandObjectParsed { +public: + CommandObjectRegisterRead(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "register read", + "Dump the contents of one or more register values from the current " + "frame. If no register is specified, dumps them all.", + nullptr, + eCommandRequiresFrame | eCommandRequiresRegContext | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_option_group(), m_format_options(eFormatDefault), + m_command_options() { + CommandArgumentEntry arg; + CommandArgumentData register_arg; + + // Define the first (and only) variant of this arg. + register_arg.arg_type = eArgTypeRegisterName; + register_arg.arg_repetition = eArgRepeatStar; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(register_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + + // Add the "--format" + m_option_group.Append(&m_format_options, + OptionGroupFormat::OPTION_GROUP_FORMAT | + OptionGroupFormat::OPTION_GROUP_GDB_FMT, + LLDB_OPT_SET_ALL); + m_option_group.Append(&m_command_options); + m_option_group.Finalize(); + } + + ~CommandObjectRegisterRead() override = default; + + Options *GetOptions() override { return &m_option_group; } + + bool DumpRegister(const ExecutionContext &exe_ctx, Stream &strm, + RegisterContext *reg_ctx, const RegisterInfo *reg_info) { + if (reg_info) { + RegisterValue reg_value; + + if (reg_ctx->ReadRegister(reg_info, reg_value)) { + strm.Indent(); + + bool prefix_with_altname = (bool)m_command_options.alternate_name; + bool prefix_with_name = !prefix_with_altname; + DumpRegisterValue(reg_value, &strm, reg_info, prefix_with_name, + prefix_with_altname, m_format_options.GetFormat(), 8); + if ((reg_info->encoding == eEncodingUint) || + (reg_info->encoding == eEncodingSint)) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && reg_info->byte_size == process->GetAddressByteSize()) { + addr_t reg_addr = reg_value.GetAsUInt64(LLDB_INVALID_ADDRESS); + if (reg_addr != LLDB_INVALID_ADDRESS) { + Address so_reg_addr; + if (exe_ctx.GetTargetRef() + .GetSectionLoadList() + .ResolveLoadAddress(reg_addr, so_reg_addr)) { + strm.PutCString(" "); + so_reg_addr.Dump(&strm, exe_ctx.GetBestExecutionContextScope(), + Address::DumpStyleResolvedDescription); + } + } + } + } + strm.EOL(); + return true; + } + } + return false; + } + + bool DumpRegisterSet(const ExecutionContext &exe_ctx, Stream &strm, + RegisterContext *reg_ctx, size_t set_idx, + bool primitive_only = false) { + uint32_t unavailable_count = 0; + uint32_t available_count = 0; + + if (!reg_ctx) + return false; // thread has no registers (i.e. core files are corrupt, + // incomplete crash logs...) + + const RegisterSet *const reg_set = reg_ctx->GetRegisterSet(set_idx); + if (reg_set) { + strm.Printf("%s:\n", (reg_set->name ? reg_set->name : "unknown")); + strm.IndentMore(); + const size_t num_registers = reg_set->num_registers; + for (size_t reg_idx = 0; reg_idx < num_registers; ++reg_idx) { + const uint32_t reg = reg_set->registers[reg_idx]; + const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg); + // Skip the dumping of derived register if primitive_only is true. + if (primitive_only && reg_info && reg_info->value_regs) + continue; + + if (DumpRegister(exe_ctx, strm, reg_ctx, reg_info)) + ++available_count; + else + ++unavailable_count; + } + strm.IndentLess(); + if (unavailable_count) { + strm.Indent(); + strm.Printf("%u registers were unavailable.\n", unavailable_count); + } + strm.EOL(); + } + return available_count > 0; + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Stream &strm = result.GetOutputStream(); + RegisterContext *reg_ctx = m_exe_ctx.GetRegisterContext(); + + const RegisterInfo *reg_info = nullptr; + if (command.GetArgumentCount() == 0) { + size_t set_idx; + + size_t num_register_sets = 1; + const size_t set_array_size = m_command_options.set_indexes.GetSize(); + if (set_array_size > 0) { + for (size_t i = 0; i < set_array_size; ++i) { + set_idx = m_command_options.set_indexes[i]->GetUInt64Value(UINT32_MAX, + nullptr); + if (set_idx < reg_ctx->GetRegisterSetCount()) { + if (!DumpRegisterSet(m_exe_ctx, strm, reg_ctx, set_idx)) { + if (errno) + result.AppendErrorWithFormatv("register read failed: {0}\n", + llvm::sys::StrError()); + else + result.AppendError("unknown error while reading registers.\n"); + result.SetStatus(eReturnStatusFailed); + break; + } + } else { + result.AppendErrorWithFormat( + "invalid register set index: %" PRIu64 "\n", (uint64_t)set_idx); + result.SetStatus(eReturnStatusFailed); + break; + } + } + } else { + if (m_command_options.dump_all_sets) + num_register_sets = reg_ctx->GetRegisterSetCount(); + + for (set_idx = 0; set_idx < num_register_sets; ++set_idx) { + // When dump_all_sets option is set, dump primitive as well as + // derived registers. + DumpRegisterSet(m_exe_ctx, strm, reg_ctx, set_idx, + !m_command_options.dump_all_sets.GetCurrentValue()); + } + } + } else { + if (m_command_options.dump_all_sets) { + result.AppendError("the --all option can't be used when registers " + "names are supplied as arguments\n"); + result.SetStatus(eReturnStatusFailed); + } else if (m_command_options.set_indexes.GetSize() > 0) { + result.AppendError("the --set <set> option can't be used when " + "registers names are supplied as arguments\n"); + result.SetStatus(eReturnStatusFailed); + } else { + for (auto &entry : command) { + // in most LLDB commands we accept $rbx as the name for register RBX + // - and here we would reject it and non-existant. we should be more + // consistent towards the user and allow them to say reg read $rbx - + // internally, however, we should be strict and not allow ourselves + // to call our registers $rbx in our own API + auto arg_str = entry.ref(); + arg_str.consume_front("$"); + + reg_info = reg_ctx->GetRegisterInfoByName(arg_str); + + if (reg_info) { + if (!DumpRegister(m_exe_ctx, strm, reg_ctx, reg_info)) + strm.Printf("%-12s = error: unavailable\n", reg_info->name); + } else { + result.AppendErrorWithFormat("Invalid register name '%s'.\n", + arg_str.str().c_str()); + } + } + } + } + return result.Succeeded(); + } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() + : OptionGroup(), + set_indexes(OptionValue::ConvertTypeToMask(OptionValue::eTypeUInt64)), + dump_all_sets(false, false), // Initial and default values are false + alternate_name(false, false) {} + + ~CommandOptions() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_register_read_options); + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + set_indexes.Clear(); + dump_all_sets.Clear(); + alternate_name.Clear(); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + switch (short_option) { + case 's': { + OptionValueSP value_sp(OptionValueUInt64::Create(option_value, error)); + if (value_sp) + set_indexes.AppendValue(value_sp); + } break; + + case 'a': + // When we don't use OptionValue::SetValueFromCString(const char *) to + // set an option value, it won't be marked as being set in the options + // so we make a call to let users know the value was set via option + dump_all_sets.SetCurrentValue(true); + dump_all_sets.SetOptionWasSet(); + break; + + case 'A': + // When we don't use OptionValue::SetValueFromCString(const char *) to + // set an option value, it won't be marked as being set in the options + // so we make a call to let users know the value was set via option + alternate_name.SetCurrentValue(true); + dump_all_sets.SetOptionWasSet(); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + // Instance variables to hold the values for command options. + OptionValueArray set_indexes; + OptionValueBoolean dump_all_sets; + OptionValueBoolean alternate_name; + }; + + OptionGroupOptions m_option_group; + OptionGroupFormat m_format_options; + CommandOptions m_command_options; +}; + +// "register write" +class CommandObjectRegisterWrite : public CommandObjectParsed { +public: + CommandObjectRegisterWrite(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "register write", + "Modify a single register value.", nullptr, + eCommandRequiresFrame | eCommandRequiresRegContext | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused) { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData register_arg; + CommandArgumentData value_arg; + + // Define the first (and only) variant of this arg. + register_arg.arg_type = eArgTypeRegisterName; + register_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(register_arg); + + // Define the first (and only) variant of this arg. + value_arg.arg_type = eArgTypeValue; + value_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(value_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + } + + ~CommandObjectRegisterWrite() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + DataExtractor reg_data; + RegisterContext *reg_ctx = m_exe_ctx.GetRegisterContext(); + + if (command.GetArgumentCount() != 2) { + result.AppendError( + "register write takes exactly 2 arguments: <reg-name> <value>"); + result.SetStatus(eReturnStatusFailed); + } else { + auto reg_name = command[0].ref(); + auto value_str = command[1].ref(); + + // in most LLDB commands we accept $rbx as the name for register RBX - + // and here we would reject it and non-existant. we should be more + // consistent towards the user and allow them to say reg write $rbx - + // internally, however, we should be strict and not allow ourselves to + // call our registers $rbx in our own API + reg_name.consume_front("$"); + + const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name); + + if (reg_info) { + RegisterValue reg_value; + + Status error(reg_value.SetValueFromString(reg_info, value_str)); + if (error.Success()) { + if (reg_ctx->WriteRegister(reg_info, reg_value)) { + // Toss all frames and anything else in the thread after a register + // has been written. + m_exe_ctx.GetThreadRef().Flush(); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } + } + if (error.AsCString()) { + result.AppendErrorWithFormat( + "Failed to write register '%s' with value '%s': %s\n", + reg_name.str().c_str(), value_str.str().c_str(), + error.AsCString()); + } else { + result.AppendErrorWithFormat( + "Failed to write register '%s' with value '%s'", + reg_name.str().c_str(), value_str.str().c_str()); + } + result.SetStatus(eReturnStatusFailed); + } else { + result.AppendErrorWithFormat("Register not found for '%s'.\n", + reg_name.str().c_str()); + result.SetStatus(eReturnStatusFailed); + } + } + return result.Succeeded(); + } +}; + +// CommandObjectRegister constructor +CommandObjectRegister::CommandObjectRegister(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "register", + "Commands to access registers for the current " + "thread and stack frame.", + "register [read|write] ...") { + LoadSubCommand("read", + CommandObjectSP(new CommandObjectRegisterRead(interpreter))); + LoadSubCommand("write", + CommandObjectSP(new CommandObjectRegisterWrite(interpreter))); +} + +CommandObjectRegister::~CommandObjectRegister() = default; diff --git a/lldb/source/Commands/CommandObjectRegister.h b/lldb/source/Commands/CommandObjectRegister.h new file mode 100644 index 0000000000000..6fc47cf386a34 --- /dev/null +++ b/lldb/source/Commands/CommandObjectRegister.h @@ -0,0 +1,32 @@ +//===-- CommandObjectRegister.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_CommandObjectRegister_h_ +#define liblldb_CommandObjectRegister_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +// CommandObjectRegister + +class CommandObjectRegister : public CommandObjectMultiword { +public: + // Constructors and Destructors + CommandObjectRegister(CommandInterpreter &interpreter); + + ~CommandObjectRegister() override; + +private: + // For CommandObjectRegister only + DISALLOW_COPY_AND_ASSIGN(CommandObjectRegister); +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectRegister_h_ diff --git a/lldb/source/Commands/CommandObjectReproducer.cpp b/lldb/source/Commands/CommandObjectReproducer.cpp new file mode 100644 index 0000000000000..dc4579c20fc2e --- /dev/null +++ b/lldb/source/Commands/CommandObjectReproducer.cpp @@ -0,0 +1,385 @@ +//===-- CommandObjectReproducer.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 "CommandObjectReproducer.h" + +#include "lldb/Host/OptionParser.h" +#include "lldb/Utility/GDBRemote.h" +#include "lldb/Utility/Reproducer.h" + +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupBoolean.h" + +using namespace lldb; +using namespace llvm; +using namespace lldb_private; +using namespace lldb_private::repro; + +enum ReproducerProvider { + eReproducerProviderCommands, + eReproducerProviderFiles, + eReproducerProviderGDB, + eReproducerProviderVersion, + eReproducerProviderWorkingDirectory, + eReproducerProviderNone +}; + +static constexpr OptionEnumValueElement g_reproducer_provider_type[] = { + { + eReproducerProviderCommands, + "commands", + "Command Interpreter Commands", + }, + { + eReproducerProviderFiles, + "files", + "Files", + }, + { + eReproducerProviderGDB, + "gdb", + "GDB Remote Packets", + }, + { + eReproducerProviderVersion, + "version", + "Version", + }, + { + eReproducerProviderWorkingDirectory, + "cwd", + "Working Directory", + }, + { + eReproducerProviderNone, + "none", + "None", + }, +}; + +static constexpr OptionEnumValues ReproducerProviderType() { + return OptionEnumValues(g_reproducer_provider_type); +} + +#define LLDB_OPTIONS_reproducer +#include "CommandOptions.inc" + +class CommandObjectReproducerGenerate : public CommandObjectParsed { +public: + CommandObjectReproducerGenerate(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "reproducer generate", + "Generate reproducer on disk. When the debugger is in capture " + "mode, this command will output the reproducer to a directory on " + "disk. In replay mode this command in a no-op.", + nullptr) {} + + ~CommandObjectReproducerGenerate() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (!command.empty()) { + result.AppendErrorWithFormat("'%s' takes no arguments", + m_cmd_name.c_str()); + return false; + } + + auto &r = Reproducer::Instance(); + if (auto generator = r.GetGenerator()) { + generator->Keep(); + } else if (r.IsReplaying()) { + // Make this operation a NOP in replay mode. + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } else { + result.AppendErrorWithFormat("Unable to get the reproducer generator"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.GetOutputStream() + << "Reproducer written to '" << r.GetReproducerPath() << "'\n"; + result.GetOutputStream() + << "Please have a look at the directory to assess if you're willing to " + "share the contained information.\n"; + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +class CommandObjectReproducerStatus : public CommandObjectParsed { +public: + CommandObjectReproducerStatus(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "reproducer status", + "Show the current reproducer status. In capture mode the debugger " + "is collecting all the information it needs to create a " + "reproducer. In replay mode the reproducer is replaying a " + "reproducer. When the reproducers are off, no data is collected " + "and no reproducer can be generated.", + nullptr) {} + + ~CommandObjectReproducerStatus() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (!command.empty()) { + result.AppendErrorWithFormat("'%s' takes no arguments", + m_cmd_name.c_str()); + return false; + } + + auto &r = Reproducer::Instance(); + if (r.IsCapturing()) { + result.GetOutputStream() << "Reproducer is in capture mode.\n"; + } else if (r.IsReplaying()) { + result.GetOutputStream() << "Reproducer is in replay mode.\n"; + } else { + result.GetOutputStream() << "Reproducer is off.\n"; + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +static void SetError(CommandReturnObject &result, Error err) { + result.GetErrorStream().Printf("error: %s\n", + toString(std::move(err)).c_str()); + result.SetStatus(eReturnStatusFailed); +} + +class CommandObjectReproducerDump : public CommandObjectParsed { +public: + CommandObjectReproducerDump(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "reproducer dump", + "Dump the information contained in a reproducer. " + "If no reproducer is specified during replay, it " + "dumps the content of the current reproducer.", + nullptr) {} + + ~CommandObjectReproducerDump() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), file() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + file.SetFile(option_arg, FileSpec::Style::native); + FileSystem::Instance().Resolve(file); + break; + case 'p': + provider = (ReproducerProvider)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, 0, error); + if (!error.Success()) + error.SetErrorStringWithFormat("unrecognized value for provider '%s'", + option_arg.str().c_str()); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + file.Clear(); + provider = eReproducerProviderNone; + } + + ArrayRef<OptionDefinition> GetDefinitions() override { + return makeArrayRef(g_reproducer_options); + } + + FileSpec file; + ReproducerProvider provider = eReproducerProviderNone; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (!command.empty()) { + result.AppendErrorWithFormat("'%s' takes no arguments", + m_cmd_name.c_str()); + return false; + } + + // If no reproducer path is specified, use the loader currently used for + // replay. Otherwise create a new loader just for dumping. + llvm::Optional<Loader> loader_storage; + Loader *loader = nullptr; + if (!m_options.file) { + loader = Reproducer::Instance().GetLoader(); + if (loader == nullptr) { + result.SetError( + "Not specifying a reproducer is only support during replay."); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return false; + } + } else { + loader_storage.emplace(m_options.file); + loader = &(*loader_storage); + if (Error err = loader->LoadIndex()) { + SetError(result, std::move(err)); + return false; + } + } + + // If we get here we should have a valid loader. + assert(loader); + + switch (m_options.provider) { + case eReproducerProviderFiles: { + FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>(); + + // Read the VFS mapping. + ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = + vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath()); + if (!buffer) { + SetError(result, errorCodeToError(buffer.getError())); + return false; + } + + // Initialize a VFS from the given mapping. + IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML( + std::move(buffer.get()), nullptr, vfs_mapping.GetPath()); + + // Dump the VFS to a buffer. + std::string str; + raw_string_ostream os(str); + static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os); + os.flush(); + + // Return the string. + result.AppendMessage(str); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + case eReproducerProviderVersion: { + Expected<std::string> version = loader->LoadBuffer<VersionProvider>(); + if (!version) { + SetError(result, version.takeError()); + return false; + } + result.AppendMessage(*version); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + case eReproducerProviderWorkingDirectory: { + Expected<std::string> cwd = + loader->LoadBuffer<WorkingDirectoryProvider>(); + if (!cwd) { + SetError(result, cwd.takeError()); + return false; + } + result.AppendMessage(*cwd); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + case eReproducerProviderCommands: { + // Create a new command loader. + std::unique_ptr<repro::CommandLoader> command_loader = + repro::CommandLoader::Create(loader); + if (!command_loader) { + SetError(result, + make_error<StringError>(llvm::inconvertibleErrorCode(), + "Unable to create command loader.")); + return false; + } + + // Iterate over the command files and dump them. + while (true) { + llvm::Optional<std::string> command_file = + command_loader->GetNextFile(); + if (!command_file) + break; + + auto command_buffer = llvm::MemoryBuffer::getFile(*command_file); + if (auto err = command_buffer.getError()) { + SetError(result, errorCodeToError(err)); + return false; + } + result.AppendMessage((*command_buffer)->getBuffer()); + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + case eReproducerProviderGDB: { + FileSpec gdb_file = loader->GetFile<ProcessGDBRemoteProvider::Info>(); + auto error_or_file = MemoryBuffer::getFile(gdb_file.GetPath()); + if (auto err = error_or_file.getError()) { + SetError(result, errorCodeToError(err)); + return false; + } + + std::vector<GDBRemotePacket> packets; + yaml::Input yin((*error_or_file)->getBuffer()); + yin >> packets; + + if (auto err = yin.error()) { + SetError(result, errorCodeToError(err)); + return false; + } + + for (GDBRemotePacket &packet : packets) { + packet.Dump(result.GetOutputStream()); + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + case eReproducerProviderNone: + result.SetError("No valid provider specified."); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +CommandObjectReproducer::CommandObjectReproducer( + CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "reproducer", + "Commands for manipulating reproducers. Reproducers make it possible " + "to capture full debug sessions with all its dependencies. The " + "resulting reproducer is used to replay the debug session while " + "debugging the debugger.\n" + "Because reproducers need the whole the debug session from " + "beginning to end, you need to launch the debugger in capture or " + "replay mode, commonly though the command line driver.\n" + "Reproducers are unrelated record-replay debugging, as you cannot " + "interact with the debugger during replay.\n", + "reproducer <subcommand> [<subcommand-options>]") { + LoadSubCommand( + "generate", + CommandObjectSP(new CommandObjectReproducerGenerate(interpreter))); + LoadSubCommand("status", CommandObjectSP( + new CommandObjectReproducerStatus(interpreter))); + LoadSubCommand("dump", + CommandObjectSP(new CommandObjectReproducerDump(interpreter))); +} + +CommandObjectReproducer::~CommandObjectReproducer() = default; diff --git a/lldb/source/Commands/CommandObjectReproducer.h b/lldb/source/Commands/CommandObjectReproducer.h new file mode 100644 index 0000000000000..ad377241f814c --- /dev/null +++ b/lldb/source/Commands/CommandObjectReproducer.h @@ -0,0 +1,28 @@ +//===-- CommandObjectReproducer.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_CommandObjectReproducer_h_ +#define liblldb_CommandObjectReproducer_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" + +namespace lldb_private { + +// CommandObjectReproducer + +class CommandObjectReproducer : public CommandObjectMultiword { +public: + CommandObjectReproducer(CommandInterpreter &interpreter); + + ~CommandObjectReproducer() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectReproducer_h_ diff --git a/lldb/source/Commands/CommandObjectSettings.cpp b/lldb/source/Commands/CommandObjectSettings.cpp new file mode 100644 index 0000000000000..248a04613d7a9 --- /dev/null +++ b/lldb/source/Commands/CommandObjectSettings.cpp @@ -0,0 +1,1145 @@ +//===-- CommandObjectSettings.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 "CommandObjectSettings.h" + +#include "llvm/ADT/StringRef.h" + +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionValueProperties.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectSettingsSet +#define LLDB_OPTIONS_settings_set +#include "CommandOptions.inc" + +class CommandObjectSettingsSet : public CommandObjectRaw { +public: + CommandObjectSettingsSet(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "settings set", + "Set the value of the specified debugger setting."), + m_options() { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData var_name_arg; + CommandArgumentData value_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(var_name_arg); + + // Define the first (and only) variant of this arg. + value_arg.arg_type = eArgTypeValue; + value_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(value_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + + SetHelpLong( + "\nWhen setting a dictionary or array variable, you can set multiple entries \ +at once by giving the values to the set command. For example:" + R"( + +(lldb) settings set target.run-args value1 value2 value3 +(lldb) settings set target.env-vars MYPATH=~/.:/usr/bin SOME_ENV_VAR=12345 + +(lldb) settings show target.run-args + [0]: 'value1' + [1]: 'value2' + [3]: 'value3' +(lldb) settings show target.env-vars + 'MYPATH=~/.:/usr/bin' + 'SOME_ENV_VAR=12345' + +)" + "Warning: The 'set' command re-sets the entire array or dictionary. If you \ +just want to add, remove or update individual values (or add something to \ +the end), use one of the other settings sub-commands: append, replace, \ +insert-before or insert-after."); + } + + ~CommandObjectSettingsSet() override = default; + + // Overrides base class's behavior where WantsCompletion = + // !WantsRawCommandString. + bool WantsCompletion() override { return true; } + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_global(false) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + m_force = true; + break; + case 'g': + m_global = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_global = false; + m_force = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_settings_set_options); + } + + // Instance variables to hold the values for command options. + bool m_global; + bool m_force; + }; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + + const size_t argc = request.GetParsedLine().GetArgumentCount(); + const char *arg = nullptr; + size_t setting_var_idx; + for (setting_var_idx = 0; setting_var_idx < argc; ++setting_var_idx) { + arg = request.GetParsedLine().GetArgumentAtIndex(setting_var_idx); + if (arg && arg[0] != '-') + break; // We found our setting variable name index + } + if (request.GetCursorIndex() == setting_var_idx) { + // Attempting to complete setting variable name + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + return; + } + arg = + request.GetParsedLine().GetArgumentAtIndex(request.GetCursorIndex()); + + if (!arg) + return; + + // Complete option name + if (arg[0] != '-') + return; + + // Complete setting value + const char *setting_var_name = + request.GetParsedLine().GetArgumentAtIndex(setting_var_idx); + Status error; + lldb::OptionValueSP value_sp(GetDebugger().GetPropertyValue( + &m_exe_ctx, setting_var_name, false, error)); + if (!value_sp) + return; + value_sp->AutoComplete(m_interpreter, request); + } + +protected: + bool DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + Args cmd_args(command); + + // Process possible options. + if (!ParseOptions(cmd_args, result)) + return false; + + const size_t min_argc = m_options.m_force ? 1 : 2; + const size_t argc = cmd_args.GetArgumentCount(); + + if ((argc < min_argc) && (!m_options.m_global)) { + result.AppendError("'settings set' takes more arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *var_name = cmd_args.GetArgumentAtIndex(0); + if ((var_name == nullptr) || (var_name[0] == '\0')) { + result.AppendError( + "'settings set' command requires a valid variable name"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // A missing value corresponds to clearing the setting when "force" is + // specified. + if (argc == 1 && m_options.m_force) { + Status error(GetDebugger().SetPropertyValue( + &m_exe_ctx, eVarSetOperationClear, var_name, llvm::StringRef())); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + return result.Succeeded(); + } + + // Split the raw command into var_name and value pair. + llvm::StringRef var_value(command); + var_value = var_value.split(var_name).second.ltrim(); + + Status error; + if (m_options.m_global) + error = GetDebugger().SetPropertyValue(nullptr, eVarSetOperationAssign, + var_name, var_value); + + if (error.Success()) { + // FIXME this is the same issue as the one in commands script import + // we could be setting target.load-script-from-symbol-file which would + // cause Python scripts to be loaded, which could run LLDB commands (e.g. + // settings set target.process.python-os-plugin-path) and cause a crash + // if we did not clear the command's exe_ctx first + ExecutionContext exe_ctx(m_exe_ctx); + m_exe_ctx.Clear(); + error = GetDebugger().SetPropertyValue(&exe_ctx, eVarSetOperationAssign, + var_name, var_value); + } + + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + result.SetStatus(eReturnStatusSuccessFinishResult); + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectSettingsShow -- Show current values + +class CommandObjectSettingsShow : public CommandObjectParsed { +public: + CommandObjectSettingsShow(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "settings show", + "Show matching debugger settings and their current " + "values. Defaults to showing all settings.", + nullptr) { + CommandArgumentEntry arg1; + CommandArgumentData var_name_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(var_name_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectSettingsShow() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + result.SetStatus(eReturnStatusSuccessFinishResult); + + if (!args.empty()) { + for (const auto &arg : args) { + Status error(GetDebugger().DumpPropertyValue( + &m_exe_ctx, result.GetOutputStream(), arg.ref(), + OptionValue::eDumpGroupValue)); + if (error.Success()) { + result.GetOutputStream().EOL(); + } else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + } else { + GetDebugger().DumpAllPropertyValues(&m_exe_ctx, result.GetOutputStream(), + OptionValue::eDumpGroupValue); + } + + return result.Succeeded(); + } +}; + +// CommandObjectSettingsWrite -- Write settings to file +#define LLDB_OPTIONS_settings_write +#include "CommandOptions.inc" + +class CommandObjectSettingsWrite : public CommandObjectParsed { +public: + CommandObjectSettingsWrite(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "settings export", + "Write matching debugger settings and their " + "current values to a file that can be read in with " + "\"settings read\". Defaults to writing all settings.", + nullptr), + m_options() { + CommandArgumentEntry arg1; + CommandArgumentData var_name_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(var_name_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectSettingsWrite() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + m_filename.assign(option_arg); + break; + case 'a': + m_append = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_filename.clear(); + m_append = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_settings_write_options); + } + + // Instance variables to hold the values for command options. + std::string m_filename; + bool m_append = false; + }; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + FileSpec file_spec(m_options.m_filename); + FileSystem::Instance().Resolve(file_spec); + std::string path(file_spec.GetPath()); + auto options = File::eOpenOptionWrite | File::eOpenOptionCanCreate; + if (m_options.m_append) + options |= File::eOpenOptionAppend; + else + options |= File::eOpenOptionTruncate; + + StreamFile out_file(path.c_str(), options, + lldb::eFilePermissionsFileDefault); + + if (!out_file.GetFile().IsValid()) { + result.AppendErrorWithFormat("%s: unable to write to file", path.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Exporting should not be context sensitive. + ExecutionContext clean_ctx; + + if (args.empty()) { + GetDebugger().DumpAllPropertyValues(&clean_ctx, out_file, + OptionValue::eDumpGroupExport); + return result.Succeeded(); + } + + for (const auto &arg : args) { + Status error(GetDebugger().DumpPropertyValue( + &clean_ctx, out_file, arg.ref(), OptionValue::eDumpGroupExport)); + if (!error.Success()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectSettingsRead -- Read settings from file +#define LLDB_OPTIONS_settings_read +#include "CommandOptions.inc" + +class CommandObjectSettingsRead : public CommandObjectParsed { +public: + CommandObjectSettingsRead(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "settings read", + "Read settings previously saved to a file with \"settings write\".", + nullptr), + m_options() {} + + ~CommandObjectSettingsRead() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + m_filename.assign(option_arg); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_filename.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_settings_read_options); + } + + // Instance variables to hold the values for command options. + std::string m_filename; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + FileSpec file(m_options.m_filename); + FileSystem::Instance().Resolve(file); + ExecutionContext clean_ctx; + CommandInterpreterRunOptions options; + options.SetAddToHistory(false); + options.SetEchoCommands(false); + options.SetPrintResults(true); + options.SetPrintErrors(true); + options.SetStopOnError(false); + m_interpreter.HandleCommandsFromFile(file, &clean_ctx, options, result); + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectSettingsList -- List settable variables + +class CommandObjectSettingsList : public CommandObjectParsed { +public: + CommandObjectSettingsList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "settings list", + "List and describe matching debugger settings. " + "Defaults to all listing all settings.", + nullptr) { + CommandArgumentEntry arg; + CommandArgumentData var_name_arg; + CommandArgumentData prefix_name_arg; + + // Define the first variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatOptional; + + // Define the second variant of this arg. + prefix_name_arg.arg_type = eArgTypeSettingPrefix; + prefix_name_arg.arg_repetition = eArgRepeatOptional; + + arg.push_back(var_name_arg); + arg.push_back(prefix_name_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectSettingsList() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + result.SetStatus(eReturnStatusSuccessFinishResult); + + const bool will_modify = false; + const size_t argc = args.GetArgumentCount(); + if (argc > 0) { + const bool dump_qualified_name = true; + + // TODO: Convert to StringRef based enumeration. Requires converting + // GetPropertyAtPath first. + for (size_t i = 0; i < argc; ++i) { + const char *property_path = args.GetArgumentAtIndex(i); + + const Property *property = + GetDebugger().GetValueProperties()->GetPropertyAtPath( + &m_exe_ctx, will_modify, property_path); + + if (property) { + property->DumpDescription(m_interpreter, result.GetOutputStream(), 0, + dump_qualified_name); + } else { + result.AppendErrorWithFormat("invalid property path '%s'", + property_path); + result.SetStatus(eReturnStatusFailed); + } + } + } else { + GetDebugger().DumpAllDescriptions(m_interpreter, + result.GetOutputStream()); + } + + return result.Succeeded(); + } +}; + +// CommandObjectSettingsRemove + +class CommandObjectSettingsRemove : public CommandObjectRaw { +public: + CommandObjectSettingsRemove(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "settings remove", + "Remove a value from a setting, specified by array " + "index or dictionary key.") { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData var_name_arg; + CommandArgumentData index_arg; + CommandArgumentData key_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(var_name_arg); + + // Define the first variant of this arg. + index_arg.arg_type = eArgTypeSettingIndex; + index_arg.arg_repetition = eArgRepeatPlain; + + // Define the second variant of this arg. + key_arg.arg_type = eArgTypeSettingKey; + key_arg.arg_repetition = eArgRepeatPlain; + + // Push both variants into this arg + arg2.push_back(index_arg); + arg2.push_back(key_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + } + + ~CommandObjectSettingsRemove() override = default; + + bool WantsCompletion() override { return true; } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + if (request.GetCursorIndex() < 2) + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + } + +protected: + bool DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + + Args cmd_args(command); + + // Process possible options. + if (!ParseOptions(cmd_args, result)) + return false; + + const size_t argc = cmd_args.GetArgumentCount(); + if (argc == 0) { + result.AppendError("'settings remove' takes an array or dictionary item, " + "or an array followed by one or more indexes, or a " + "dictionary followed by one or more key names to " + "remove"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *var_name = cmd_args.GetArgumentAtIndex(0); + if ((var_name == nullptr) || (var_name[0] == '\0')) { + result.AppendError( + "'settings remove' command requires a valid variable name"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Split the raw command into var_name and value pair. + llvm::StringRef var_value(command); + var_value = var_value.split(var_name).second.trim(); + + Status error(GetDebugger().SetPropertyValue( + &m_exe_ctx, eVarSetOperationRemove, var_name, var_value)); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + return result.Succeeded(); + } +}; + +// CommandObjectSettingsReplace + +class CommandObjectSettingsReplace : public CommandObjectRaw { +public: + CommandObjectSettingsReplace(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "settings replace", + "Replace the debugger setting value specified by " + "array index or dictionary key.") { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentEntry arg3; + CommandArgumentData var_name_arg; + CommandArgumentData index_arg; + CommandArgumentData key_arg; + CommandArgumentData value_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(var_name_arg); + + // Define the first (variant of this arg. + index_arg.arg_type = eArgTypeSettingIndex; + index_arg.arg_repetition = eArgRepeatPlain; + + // Define the second (variant of this arg. + key_arg.arg_type = eArgTypeSettingKey; + key_arg.arg_repetition = eArgRepeatPlain; + + // Put both variants into this arg + arg2.push_back(index_arg); + arg2.push_back(key_arg); + + // Define the first (and only) variant of this arg. + value_arg.arg_type = eArgTypeValue; + value_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg3.push_back(value_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + m_arguments.push_back(arg3); + } + + ~CommandObjectSettingsReplace() override = default; + + // Overrides base class's behavior where WantsCompletion = + // !WantsRawCommandString. + bool WantsCompletion() override { return true; } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + // Attempting to complete variable name + if (request.GetCursorIndex() < 2) + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + } + +protected: + bool DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + + Args cmd_args(command); + const char *var_name = cmd_args.GetArgumentAtIndex(0); + if ((var_name == nullptr) || (var_name[0] == '\0')) { + result.AppendError("'settings replace' command requires a valid variable " + "name; No value supplied"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Split the raw command into var_name, index_value, and value triple. + llvm::StringRef var_value(command); + var_value = var_value.split(var_name).second.trim(); + + Status error(GetDebugger().SetPropertyValue( + &m_exe_ctx, eVarSetOperationReplace, var_name, var_value)); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + + return result.Succeeded(); + } +}; + +// CommandObjectSettingsInsertBefore + +class CommandObjectSettingsInsertBefore : public CommandObjectRaw { +public: + CommandObjectSettingsInsertBefore(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "settings insert-before", + "Insert one or more values into an debugger array " + "setting immediately before the specified element " + "index.") { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentEntry arg3; + CommandArgumentData var_name_arg; + CommandArgumentData index_arg; + CommandArgumentData value_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(var_name_arg); + + // Define the first (variant of this arg. + index_arg.arg_type = eArgTypeSettingIndex; + index_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(index_arg); + + // Define the first (and only) variant of this arg. + value_arg.arg_type = eArgTypeValue; + value_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg3.push_back(value_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + m_arguments.push_back(arg3); + } + + ~CommandObjectSettingsInsertBefore() override = default; + + // Overrides base class's behavior where WantsCompletion = + // !WantsRawCommandString. + bool WantsCompletion() override { return true; } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + // Attempting to complete variable name + if (request.GetCursorIndex() < 2) + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + } + +protected: + bool DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + + Args cmd_args(command); + const size_t argc = cmd_args.GetArgumentCount(); + + if (argc < 3) { + result.AppendError("'settings insert-before' takes more arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *var_name = cmd_args.GetArgumentAtIndex(0); + if ((var_name == nullptr) || (var_name[0] == '\0')) { + result.AppendError("'settings insert-before' command requires a valid " + "variable name; No value supplied"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Split the raw command into var_name, index_value, and value triple. + llvm::StringRef var_value(command); + var_value = var_value.split(var_name).second.trim(); + + Status error(GetDebugger().SetPropertyValue( + &m_exe_ctx, eVarSetOperationInsertBefore, var_name, var_value)); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + return result.Succeeded(); + } +}; + +// CommandObjectSettingInsertAfter + +class CommandObjectSettingsInsertAfter : public CommandObjectRaw { +public: + CommandObjectSettingsInsertAfter(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "settings insert-after", + "Insert one or more values into a debugger array " + "settings after the specified element index.") { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentEntry arg3; + CommandArgumentData var_name_arg; + CommandArgumentData index_arg; + CommandArgumentData value_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(var_name_arg); + + // Define the first (variant of this arg. + index_arg.arg_type = eArgTypeSettingIndex; + index_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(index_arg); + + // Define the first (and only) variant of this arg. + value_arg.arg_type = eArgTypeValue; + value_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg3.push_back(value_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + m_arguments.push_back(arg3); + } + + ~CommandObjectSettingsInsertAfter() override = default; + + // Overrides base class's behavior where WantsCompletion = + // !WantsRawCommandString. + bool WantsCompletion() override { return true; } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + // Attempting to complete variable name + if (request.GetCursorIndex() < 2) + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + } + +protected: + bool DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + + Args cmd_args(command); + const size_t argc = cmd_args.GetArgumentCount(); + + if (argc < 3) { + result.AppendError("'settings insert-after' takes more arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *var_name = cmd_args.GetArgumentAtIndex(0); + if ((var_name == nullptr) || (var_name[0] == '\0')) { + result.AppendError("'settings insert-after' command requires a valid " + "variable name; No value supplied"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Split the raw command into var_name, index_value, and value triple. + llvm::StringRef var_value(command); + var_value = var_value.split(var_name).second.trim(); + + Status error(GetDebugger().SetPropertyValue( + &m_exe_ctx, eVarSetOperationInsertAfter, var_name, var_value)); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + return result.Succeeded(); + } +}; + +// CommandObjectSettingsAppend + +class CommandObjectSettingsAppend : public CommandObjectRaw { +public: + CommandObjectSettingsAppend(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "settings append", + "Append one or more values to a debugger array, " + "dictionary, or string setting.") { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData var_name_arg; + CommandArgumentData value_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(var_name_arg); + + // Define the first (and only) variant of this arg. + value_arg.arg_type = eArgTypeValue; + value_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg2.push_back(value_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + } + + ~CommandObjectSettingsAppend() override = default; + + // Overrides base class's behavior where WantsCompletion = + // !WantsRawCommandString. + bool WantsCompletion() override { return true; } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + // Attempting to complete variable name + if (request.GetCursorIndex() < 2) + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + } + +protected: + bool DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + Args cmd_args(command); + const size_t argc = cmd_args.GetArgumentCount(); + + if (argc < 2) { + result.AppendError("'settings append' takes more arguments"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *var_name = cmd_args.GetArgumentAtIndex(0); + if ((var_name == nullptr) || (var_name[0] == '\0')) { + result.AppendError("'settings append' command requires a valid variable " + "name; No value supplied"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Do not perform cmd_args.Shift() since StringRef is manipulating the raw + // character string later on. + + // Split the raw command into var_name and value pair. + llvm::StringRef var_value(command); + var_value = var_value.split(var_name).second.trim(); + + Status error(GetDebugger().SetPropertyValue( + &m_exe_ctx, eVarSetOperationAppend, var_name, var_value)); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + return result.Succeeded(); + } +}; + +// CommandObjectSettingsClear + +class CommandObjectSettingsClear : public CommandObjectParsed { +public: + CommandObjectSettingsClear(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "settings clear", + "Clear a debugger setting array, dictionary, or string.", nullptr) { + CommandArgumentEntry arg; + CommandArgumentData var_name_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeSettingVariableName; + var_name_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(var_name_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectSettingsClear() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + // Attempting to complete variable name + if (request.GetCursorIndex() < 2) + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSettingsNameCompletion, + request, nullptr); + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + const size_t argc = command.GetArgumentCount(); + + if (argc != 1) { + result.AppendError("'settings clear' takes exactly one argument"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *var_name = command.GetArgumentAtIndex(0); + if ((var_name == nullptr) || (var_name[0] == '\0')) { + result.AppendError("'settings clear' command requires a valid variable " + "name; No value supplied"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Status error(GetDebugger().SetPropertyValue( + &m_exe_ctx, eVarSetOperationClear, var_name, llvm::StringRef())); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + return result.Succeeded(); + } +}; + +// CommandObjectMultiwordSettings + +CommandObjectMultiwordSettings::CommandObjectMultiwordSettings( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "settings", + "Commands for managing LLDB settings.", + "settings <subcommand> [<command-options>]") { + LoadSubCommand("set", + CommandObjectSP(new CommandObjectSettingsSet(interpreter))); + LoadSubCommand("show", + CommandObjectSP(new CommandObjectSettingsShow(interpreter))); + LoadSubCommand("list", + CommandObjectSP(new CommandObjectSettingsList(interpreter))); + LoadSubCommand("remove", + CommandObjectSP(new CommandObjectSettingsRemove(interpreter))); + LoadSubCommand("replace", CommandObjectSP( + new CommandObjectSettingsReplace(interpreter))); + LoadSubCommand( + "insert-before", + CommandObjectSP(new CommandObjectSettingsInsertBefore(interpreter))); + LoadSubCommand( + "insert-after", + CommandObjectSP(new CommandObjectSettingsInsertAfter(interpreter))); + LoadSubCommand("append", + CommandObjectSP(new CommandObjectSettingsAppend(interpreter))); + LoadSubCommand("clear", + CommandObjectSP(new CommandObjectSettingsClear(interpreter))); + LoadSubCommand("write", + CommandObjectSP(new CommandObjectSettingsWrite(interpreter))); + LoadSubCommand("read", + CommandObjectSP(new CommandObjectSettingsRead(interpreter))); +} + +CommandObjectMultiwordSettings::~CommandObjectMultiwordSettings() = default; diff --git a/lldb/source/Commands/CommandObjectSettings.h b/lldb/source/Commands/CommandObjectSettings.h new file mode 100644 index 0000000000000..730425953ea70 --- /dev/null +++ b/lldb/source/Commands/CommandObjectSettings.h @@ -0,0 +1,29 @@ +//===-- CommandObjectSettings.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_CommandObjectSettings_h_ +#define liblldb_CommandObjectSettings_h_ + +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" + +namespace lldb_private { + +// CommandObjectMultiwordSettings + +class CommandObjectMultiwordSettings : public CommandObjectMultiword { +public: + CommandObjectMultiwordSettings(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordSettings() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectSettings_h_ diff --git a/lldb/source/Commands/CommandObjectSource.cpp b/lldb/source/Commands/CommandObjectSource.cpp new file mode 100644 index 0000000000000..78c8bc8119267 --- /dev/null +++ b/lldb/source/Commands/CommandObjectSource.cpp @@ -0,0 +1,1279 @@ +//===-- CommandObjectSource.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 "CommandObjectSource.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/FileLineResolver.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/SourceManager.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/TargetList.h" +#include "lldb/Utility/FileSpec.h" + +using namespace lldb; +using namespace lldb_private; + +#pragma mark CommandObjectSourceInfo +// CommandObjectSourceInfo - debug line entries dumping command +#define LLDB_OPTIONS_source_info +#include "CommandOptions.inc" + +class CommandObjectSourceInfo : public CommandObjectParsed { + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + switch (short_option) { + case 'l': + if (option_arg.getAsInteger(0, start_line)) + error.SetErrorStringWithFormat("invalid line number: '%s'", + option_arg.str().c_str()); + break; + + case 'e': + if (option_arg.getAsInteger(0, end_line)) + error.SetErrorStringWithFormat("invalid line number: '%s'", + option_arg.str().c_str()); + break; + + case 'c': + if (option_arg.getAsInteger(0, num_lines)) + error.SetErrorStringWithFormat("invalid line count: '%s'", + option_arg.str().c_str()); + break; + + case 'f': + file_name = option_arg; + break; + + case 'n': + symbol_name = option_arg; + break; + + case 'a': { + address = OptionArgParser::ToAddress(execution_context, option_arg, + LLDB_INVALID_ADDRESS, &error); + } break; + case 's': + modules.push_back(std::string(option_arg)); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + file_spec.Clear(); + file_name.clear(); + symbol_name.clear(); + address = LLDB_INVALID_ADDRESS; + start_line = 0; + end_line = 0; + num_lines = 0; + modules.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_source_info_options); + } + + // Instance variables to hold the values for command options. + FileSpec file_spec; + std::string file_name; + std::string symbol_name; + lldb::addr_t address; + uint32_t start_line; + uint32_t end_line; + uint32_t num_lines; + STLStringArray modules; + }; + +public: + CommandObjectSourceInfo(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "source info", + "Display source line information for the current target " + "process. Defaults to instruction pointer in current stack " + "frame.", + nullptr, eCommandRequiresTarget), + m_options() {} + + ~CommandObjectSourceInfo() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + // Dump the line entries in each symbol context. Return the number of entries + // found. If module_list is set, only dump lines contained in one of the + // modules. If file_spec is set, only dump lines in the file. If the + // start_line option was specified, don't print lines less than start_line. + // If the end_line option was specified, don't print lines greater than + // end_line. If the num_lines option was specified, dont print more than + // num_lines entries. + uint32_t DumpLinesInSymbolContexts(Stream &strm, + const SymbolContextList &sc_list, + const ModuleList &module_list, + const FileSpec &file_spec) { + uint32_t start_line = m_options.start_line; + uint32_t end_line = m_options.end_line; + uint32_t num_lines = m_options.num_lines; + Target *target = m_exe_ctx.GetTargetPtr(); + + uint32_t num_matches = 0; + bool has_path = false; + if (file_spec) { + assert(file_spec.GetFilename().AsCString()); + has_path = (file_spec.GetDirectory().AsCString() != nullptr); + } + + // Dump all the line entries for the file in the list. + ConstString last_module_file_name; + uint32_t num_scs = sc_list.GetSize(); + for (uint32_t i = 0; i < num_scs; ++i) { + SymbolContext sc; + sc_list.GetContextAtIndex(i, sc); + if (sc.comp_unit) { + Module *module = sc.module_sp.get(); + CompileUnit *cu = sc.comp_unit; + const LineEntry &line_entry = sc.line_entry; + assert(module && cu); + + // Are we looking for specific modules, files or lines? + if (module_list.GetSize() && + module_list.GetIndexForModule(module) == LLDB_INVALID_INDEX32) + continue; + if (file_spec && + !lldb_private::FileSpec::Equal(file_spec, line_entry.file, + has_path)) + continue; + if (start_line > 0 && line_entry.line < start_line) + continue; + if (end_line > 0 && line_entry.line > end_line) + continue; + if (num_lines > 0 && num_matches > num_lines) + continue; + + // Print a new header if the module changed. + ConstString module_file_name = + module->GetFileSpec().GetFilename(); + assert(module_file_name); + if (module_file_name != last_module_file_name) { + if (num_matches > 0) + strm << "\n\n"; + strm << "Lines found in module `" << module_file_name << "\n"; + } + // Dump the line entry. + line_entry.GetDescription(&strm, lldb::eDescriptionLevelBrief, cu, + target, /*show_address_only=*/false); + strm << "\n"; + last_module_file_name = module_file_name; + num_matches++; + } + } + return num_matches; + } + + // Dump the requested line entries for the file in the compilation unit. + // Return the number of entries found. If module_list is set, only dump lines + // contained in one of the modules. If the start_line option was specified, + // don't print lines less than start_line. If the end_line option was + // specified, don't print lines greater than end_line. If the num_lines + // option was specified, dont print more than num_lines entries. + uint32_t DumpFileLinesInCompUnit(Stream &strm, Module *module, + CompileUnit *cu, const FileSpec &file_spec) { + uint32_t start_line = m_options.start_line; + uint32_t end_line = m_options.end_line; + uint32_t num_lines = m_options.num_lines; + Target *target = m_exe_ctx.GetTargetPtr(); + + uint32_t num_matches = 0; + assert(module); + if (cu) { + assert(file_spec.GetFilename().AsCString()); + bool has_path = (file_spec.GetDirectory().AsCString() != nullptr); + const FileSpecList &cu_file_list = cu->GetSupportFiles(); + size_t file_idx = cu_file_list.FindFileIndex(0, file_spec, has_path); + if (file_idx != UINT32_MAX) { + // Update the file to how it appears in the CU. + const FileSpec &cu_file_spec = + cu_file_list.GetFileSpecAtIndex(file_idx); + + // Dump all matching lines at or above start_line for the file in the + // CU. + ConstString file_spec_name = file_spec.GetFilename(); + ConstString module_file_name = + module->GetFileSpec().GetFilename(); + bool cu_header_printed = false; + uint32_t line = start_line; + while (true) { + LineEntry line_entry; + + // Find the lowest index of a line entry with a line equal to or + // higher than 'line'. + uint32_t start_idx = 0; + start_idx = cu->FindLineEntry(start_idx, line, &cu_file_spec, + /*exact=*/false, &line_entry); + if (start_idx == UINT32_MAX) + // No more line entries for our file in this CU. + break; + + if (end_line > 0 && line_entry.line > end_line) + break; + + // Loop through to find any other entries for this line, dumping + // each. + line = line_entry.line; + do { + num_matches++; + if (num_lines > 0 && num_matches > num_lines) + break; + assert(lldb_private::FileSpec::Equal(cu_file_spec, line_entry.file, + has_path)); + if (!cu_header_printed) { + if (num_matches > 0) + strm << "\n\n"; + strm << "Lines found for file " << file_spec_name + << " in compilation unit " << cu->GetFilename() << " in `" + << module_file_name << "\n"; + cu_header_printed = true; + } + line_entry.GetDescription(&strm, lldb::eDescriptionLevelBrief, cu, + target, /*show_address_only=*/false); + strm << "\n"; + + // Anymore after this one? + start_idx++; + start_idx = cu->FindLineEntry(start_idx, line, &cu_file_spec, + /*exact=*/true, &line_entry); + } while (start_idx != UINT32_MAX); + + // Try the next higher line, starting over at start_idx 0. + line++; + } + } + } + return num_matches; + } + + // Dump the requested line entries for the file in the module. Return the + // number of entries found. If module_list is set, only dump lines contained + // in one of the modules. If the start_line option was specified, don't print + // lines less than start_line. If the end_line option was specified, don't + // print lines greater than end_line. If the num_lines option was specified, + // dont print more than num_lines entries. + uint32_t DumpFileLinesInModule(Stream &strm, Module *module, + const FileSpec &file_spec) { + uint32_t num_matches = 0; + if (module) { + // Look through all the compilation units (CUs) in this module for ones + // that contain lines of code from this source file. + for (size_t i = 0; i < module->GetNumCompileUnits(); i++) { + // Look for a matching source file in this CU. + CompUnitSP cu_sp(module->GetCompileUnitAtIndex(i)); + if (cu_sp) { + num_matches += + DumpFileLinesInCompUnit(strm, module, cu_sp.get(), file_spec); + } + } + } + return num_matches; + } + + // Given an address and a list of modules, append the symbol contexts of all + // line entries containing the address found in the modules and return the + // count of matches. If none is found, return an error in 'error_strm'. + size_t GetSymbolContextsForAddress(const ModuleList &module_list, + lldb::addr_t addr, + SymbolContextList &sc_list, + StreamString &error_strm) { + Address so_addr; + size_t num_matches = 0; + assert(module_list.GetSize() > 0); + Target *target = m_exe_ctx.GetTargetPtr(); + if (target->GetSectionLoadList().IsEmpty()) { + // The target isn't loaded yet, we need to lookup the file address in all + // modules. Note: the module list option does not apply to addresses. + const size_t num_modules = module_list.GetSize(); + for (size_t i = 0; i < num_modules; ++i) { + ModuleSP module_sp(module_list.GetModuleAtIndex(i)); + if (!module_sp) + continue; + if (module_sp->ResolveFileAddress(addr, so_addr)) { + SymbolContext sc; + sc.Clear(true); + if (module_sp->ResolveSymbolContextForAddress( + so_addr, eSymbolContextEverything, sc) & + eSymbolContextLineEntry) { + sc_list.AppendIfUnique(sc, /*merge_symbol_into_function=*/false); + ++num_matches; + } + } + } + if (num_matches == 0) + error_strm.Printf("Source information for file address 0x%" PRIx64 + " not found in any modules.\n", + addr); + } else { + // The target has some things loaded, resolve this address to a compile + // unit + file + line and display + if (target->GetSectionLoadList().ResolveLoadAddress(addr, so_addr)) { + ModuleSP module_sp(so_addr.GetModule()); + // Check to make sure this module is in our list. + if (module_sp && + module_list.GetIndexForModule(module_sp.get()) != + LLDB_INVALID_INDEX32) { + SymbolContext sc; + sc.Clear(true); + if (module_sp->ResolveSymbolContextForAddress( + so_addr, eSymbolContextEverything, sc) & + eSymbolContextLineEntry) { + sc_list.AppendIfUnique(sc, /*merge_symbol_into_function=*/false); + ++num_matches; + } else { + StreamString addr_strm; + so_addr.Dump(&addr_strm, nullptr, + Address::DumpStyleModuleWithFileAddress); + error_strm.Printf( + "Address 0x%" PRIx64 " resolves to %s, but there is" + " no source information available for this address.\n", + addr, addr_strm.GetData()); + } + } else { + StreamString addr_strm; + so_addr.Dump(&addr_strm, nullptr, + Address::DumpStyleModuleWithFileAddress); + error_strm.Printf("Address 0x%" PRIx64 + " resolves to %s, but it cannot" + " be found in any modules.\n", + addr, addr_strm.GetData()); + } + } else + error_strm.Printf("Unable to resolve address 0x%" PRIx64 ".\n", addr); + } + return num_matches; + } + + // Dump the line entries found in functions matching the name specified in + // the option. + bool DumpLinesInFunctions(CommandReturnObject &result) { + SymbolContextList sc_list_funcs; + ConstString name(m_options.symbol_name.c_str()); + SymbolContextList sc_list_lines; + Target *target = m_exe_ctx.GetTargetPtr(); + uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); + + // Note: module_list can't be const& because FindFunctionSymbols isn't + // const. + ModuleList module_list = + (m_module_list.GetSize() > 0) ? m_module_list : target->GetImages(); + module_list.FindFunctions(name, eFunctionNameTypeAuto, + /*include_symbols=*/false, + /*include_inlines=*/true, sc_list_funcs); + size_t num_matches = sc_list_funcs.GetSize(); + + if (!num_matches) { + // If we didn't find any functions with that name, try searching for + // symbols that line up exactly with function addresses. + SymbolContextList sc_list_symbols; + module_list.FindFunctionSymbols( + name, eFunctionNameTypeAuto, sc_list_symbols); + size_t num_symbol_matches = sc_list_symbols.GetSize(); + for (size_t i = 0; i < num_symbol_matches; i++) { + SymbolContext sc; + sc_list_symbols.GetContextAtIndex(i, sc); + if (sc.symbol && sc.symbol->ValueIsAddress()) { + const Address &base_address = sc.symbol->GetAddressRef(); + Function *function = base_address.CalculateSymbolContextFunction(); + if (function) { + sc_list_funcs.Append(SymbolContext(function)); + num_matches++; + } + } + } + } + if (num_matches == 0) { + result.AppendErrorWithFormat("Could not find function named \'%s\'.\n", + m_options.symbol_name.c_str()); + return false; + } + for (size_t i = 0; i < num_matches; i++) { + SymbolContext sc; + sc_list_funcs.GetContextAtIndex(i, sc); + bool context_found_for_symbol = false; + // Loop through all the ranges in the function. + AddressRange range; + for (uint32_t r = 0; + sc.GetAddressRange(eSymbolContextEverything, r, + /*use_inline_block_range=*/true, range); + ++r) { + // Append the symbol contexts for each address in the range to + // sc_list_lines. + const Address &base_address = range.GetBaseAddress(); + const addr_t size = range.GetByteSize(); + lldb::addr_t start_addr = base_address.GetLoadAddress(target); + if (start_addr == LLDB_INVALID_ADDRESS) + start_addr = base_address.GetFileAddress(); + lldb::addr_t end_addr = start_addr + size; + for (lldb::addr_t addr = start_addr; addr < end_addr; + addr += addr_byte_size) { + StreamString error_strm; + if (!GetSymbolContextsForAddress(module_list, addr, sc_list_lines, + error_strm)) + result.AppendWarningWithFormat("in symbol '%s': %s", + sc.GetFunctionName().AsCString(), + error_strm.GetData()); + else + context_found_for_symbol = true; + } + } + if (!context_found_for_symbol) + result.AppendWarningWithFormat("Unable to find line information" + " for matching symbol '%s'.\n", + sc.GetFunctionName().AsCString()); + } + if (sc_list_lines.GetSize() == 0) { + result.AppendErrorWithFormat("No line information could be found" + " for any symbols matching '%s'.\n", + name.AsCString()); + return false; + } + FileSpec file_spec; + if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list_lines, + module_list, file_spec)) { + result.AppendErrorWithFormat( + "Unable to dump line information for symbol '%s'.\n", + name.AsCString()); + return false; + } + return true; + } + + // Dump the line entries found for the address specified in the option. + bool DumpLinesForAddress(CommandReturnObject &result) { + Target *target = m_exe_ctx.GetTargetPtr(); + SymbolContextList sc_list; + + StreamString error_strm; + if (!GetSymbolContextsForAddress(target->GetImages(), m_options.address, + sc_list, error_strm)) { + result.AppendErrorWithFormat("%s.\n", error_strm.GetData()); + return false; + } + ModuleList module_list; + FileSpec file_spec; + if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list, + module_list, file_spec)) { + result.AppendErrorWithFormat("No modules contain load address 0x%" PRIx64 + ".\n", + m_options.address); + return false; + } + return true; + } + + // Dump the line entries found in the file specified in the option. + bool DumpLinesForFile(CommandReturnObject &result) { + FileSpec file_spec(m_options.file_name); + const char *filename = m_options.file_name.c_str(); + Target *target = m_exe_ctx.GetTargetPtr(); + const ModuleList &module_list = + (m_module_list.GetSize() > 0) ? m_module_list : target->GetImages(); + + bool displayed_something = false; + const size_t num_modules = module_list.GetSize(); + for (uint32_t i = 0; i < num_modules; ++i) { + // Dump lines for this module. + Module *module = module_list.GetModulePointerAtIndex(i); + assert(module); + if (DumpFileLinesInModule(result.GetOutputStream(), module, file_spec)) + displayed_something = true; + } + if (!displayed_something) { + result.AppendErrorWithFormat("No source filenames matched '%s'.\n", + filename); + return false; + } + return true; + } + + // Dump the line entries for the current frame. + bool DumpLinesForFrame(CommandReturnObject &result) { + StackFrame *cur_frame = m_exe_ctx.GetFramePtr(); + if (cur_frame == nullptr) { + result.AppendError( + "No selected frame to use to find the default source."); + return false; + } else if (!cur_frame->HasDebugInformation()) { + result.AppendError("No debug info for the selected frame."); + return false; + } else { + const SymbolContext &sc = + cur_frame->GetSymbolContext(eSymbolContextLineEntry); + SymbolContextList sc_list; + sc_list.Append(sc); + ModuleList module_list; + FileSpec file_spec; + if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list, + module_list, file_spec)) { + result.AppendError( + "No source line info available for the selected frame."); + return false; + } + } + return true; + } + + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc != 0) { + result.AppendErrorWithFormat("'%s' takes no arguments, only flags.\n", + GetCommandName().str().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Target *target = m_exe_ctx.GetTargetPtr(); + if (target == nullptr) { + target = GetDebugger().GetSelectedTarget().get(); + if (target == nullptr) { + result.AppendError("invalid target, create a debug target using the " + "'target create' command."); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + + // Collect the list of modules to search. + m_module_list.Clear(); + if (!m_options.modules.empty()) { + for (size_t i = 0, e = m_options.modules.size(); i < e; ++i) { + FileSpec module_file_spec(m_options.modules[i]); + if (module_file_spec) { + ModuleSpec module_spec(module_file_spec); + target->GetImages().FindModules(module_spec, m_module_list); + if (m_module_list.IsEmpty()) + result.AppendWarningWithFormat("No module found for '%s'.\n", + m_options.modules[i].c_str()); + } + } + if (!m_module_list.GetSize()) { + result.AppendError("No modules match the input."); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else if (target->GetImages().GetSize() == 0) { + result.AppendError("The target has no associated executable images."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Check the arguments to see what lines we should dump. + if (!m_options.symbol_name.empty()) { + // Print lines for symbol. + if (DumpLinesInFunctions(result)) + result.SetStatus(eReturnStatusSuccessFinishResult); + else + result.SetStatus(eReturnStatusFailed); + } else if (m_options.address != LLDB_INVALID_ADDRESS) { + // Print lines for an address. + if (DumpLinesForAddress(result)) + result.SetStatus(eReturnStatusSuccessFinishResult); + else + result.SetStatus(eReturnStatusFailed); + } else if (!m_options.file_name.empty()) { + // Dump lines for a file. + if (DumpLinesForFile(result)) + result.SetStatus(eReturnStatusSuccessFinishResult); + else + result.SetStatus(eReturnStatusFailed); + } else { + // Dump the line for the current frame. + if (DumpLinesForFrame(result)) + result.SetStatus(eReturnStatusSuccessFinishResult); + else + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + CommandOptions m_options; + ModuleList m_module_list; +}; + +#pragma mark CommandObjectSourceList +// CommandObjectSourceList +#define LLDB_OPTIONS_source_list +#include "CommandOptions.inc" + +class CommandObjectSourceList : public CommandObjectParsed { + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + switch (short_option) { + case 'l': + if (option_arg.getAsInteger(0, start_line)) + error.SetErrorStringWithFormat("invalid line number: '%s'", + option_arg.str().c_str()); + break; + + case 'c': + if (option_arg.getAsInteger(0, num_lines)) + error.SetErrorStringWithFormat("invalid line count: '%s'", + option_arg.str().c_str()); + break; + + case 'f': + file_name = option_arg; + break; + + case 'n': + symbol_name = option_arg; + break; + + case 'a': { + address = OptionArgParser::ToAddress(execution_context, option_arg, + LLDB_INVALID_ADDRESS, &error); + } break; + case 's': + modules.push_back(std::string(option_arg)); + break; + + case 'b': + show_bp_locs = true; + break; + case 'r': + reverse = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + file_spec.Clear(); + file_name.clear(); + symbol_name.clear(); + address = LLDB_INVALID_ADDRESS; + start_line = 0; + num_lines = 0; + show_bp_locs = false; + reverse = false; + modules.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_source_list_options); + } + + // Instance variables to hold the values for command options. + FileSpec file_spec; + std::string file_name; + std::string symbol_name; + lldb::addr_t address; + uint32_t start_line; + uint32_t num_lines; + STLStringArray modules; + bool show_bp_locs; + bool reverse; + }; + +public: + CommandObjectSourceList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "source list", + "Display source code for the current target " + "process as specified by options.", + nullptr, eCommandRequiresTarget), + m_options() {} + + ~CommandObjectSourceList() override = default; + + Options *GetOptions() override { return &m_options; } + + const char *GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + // This is kind of gross, but the command hasn't been parsed yet so we + // can't look at the option values for this invocation... I have to scan + // the arguments directly. + auto iter = + llvm::find_if(current_command_args, [](const Args::ArgEntry &e) { + return e.ref() == "-r" || e.ref() == "--reverse"; + }); + if (iter == current_command_args.end()) + return m_cmd_name.c_str(); + + if (m_reverse_name.empty()) { + m_reverse_name = m_cmd_name; + m_reverse_name.append(" -r"); + } + return m_reverse_name.c_str(); + } + +protected: + struct SourceInfo { + ConstString function; + LineEntry line_entry; + + SourceInfo(ConstString name, const LineEntry &line_entry) + : function(name), line_entry(line_entry) {} + + SourceInfo() : function(), line_entry() {} + + bool IsValid() const { return (bool)function && line_entry.IsValid(); } + + bool operator==(const SourceInfo &rhs) const { + return function == rhs.function && + line_entry.original_file == rhs.line_entry.original_file && + line_entry.line == rhs.line_entry.line; + } + + bool operator!=(const SourceInfo &rhs) const { + return function != rhs.function || + line_entry.original_file != rhs.line_entry.original_file || + line_entry.line != rhs.line_entry.line; + } + + bool operator<(const SourceInfo &rhs) const { + if (function.GetCString() < rhs.function.GetCString()) + return true; + if (line_entry.file.GetDirectory().GetCString() < + rhs.line_entry.file.GetDirectory().GetCString()) + return true; + if (line_entry.file.GetFilename().GetCString() < + rhs.line_entry.file.GetFilename().GetCString()) + return true; + if (line_entry.line < rhs.line_entry.line) + return true; + return false; + } + }; + + size_t DisplayFunctionSource(const SymbolContext &sc, SourceInfo &source_info, + CommandReturnObject &result) { + if (!source_info.IsValid()) { + source_info.function = sc.GetFunctionName(); + source_info.line_entry = sc.GetFunctionStartLineEntry(); + } + + if (sc.function) { + Target *target = m_exe_ctx.GetTargetPtr(); + + FileSpec start_file; + uint32_t start_line; + uint32_t end_line; + FileSpec end_file; + + if (sc.block == nullptr) { + // Not an inlined function + sc.function->GetStartLineSourceInfo(start_file, start_line); + if (start_line == 0) { + result.AppendErrorWithFormat("Could not find line information for " + "start of function: \"%s\".\n", + source_info.function.GetCString()); + result.SetStatus(eReturnStatusFailed); + return 0; + } + sc.function->GetEndLineSourceInfo(end_file, end_line); + } else { + // We have an inlined function + start_file = source_info.line_entry.file; + start_line = source_info.line_entry.line; + end_line = start_line + m_options.num_lines; + } + + // This is a little hacky, but the first line table entry for a function + // points to the "{" that starts the function block. It would be nice to + // actually get the function declaration in there too. So back up a bit, + // but not further than what you're going to display. + uint32_t extra_lines; + if (m_options.num_lines >= 10) + extra_lines = 5; + else + extra_lines = m_options.num_lines / 2; + uint32_t line_no; + if (start_line <= extra_lines) + line_no = 1; + else + line_no = start_line - extra_lines; + + // For fun, if the function is shorter than the number of lines we're + // supposed to display, only display the function... + if (end_line != 0) { + if (m_options.num_lines > end_line - line_no) + m_options.num_lines = end_line - line_no + extra_lines; + } + + m_breakpoint_locations.Clear(); + + if (m_options.show_bp_locs) { + const bool show_inlines = true; + m_breakpoint_locations.Reset(start_file, 0, show_inlines); + SearchFilterForUnconstrainedSearches target_search_filter( + m_exe_ctx.GetTargetSP()); + target_search_filter.Search(m_breakpoint_locations); + } + + result.AppendMessageWithFormat("File: %s\n", + start_file.GetPath().c_str()); + // We don't care about the column here. + const uint32_t column = 0; + return target->GetSourceManager().DisplaySourceLinesWithLineNumbers( + start_file, line_no, column, 0, m_options.num_lines, "", + &result.GetOutputStream(), GetBreakpointLocations()); + } else { + result.AppendErrorWithFormat( + "Could not find function info for: \"%s\".\n", + m_options.symbol_name.c_str()); + } + return 0; + } + + // From Jim: The FindMatchingFunctions / FindMatchingFunctionSymbols + // functions "take a possibly empty vector of strings which are names of + // modules, and run the two search functions on the subset of the full module + // list that matches the strings in the input vector". If we wanted to put + // these somewhere, there should probably be a module-filter-list that can be + // passed to the various ModuleList::Find* calls, which would either be a + // vector of string names or a ModuleSpecList. + void FindMatchingFunctions(Target *target, ConstString name, + SymbolContextList &sc_list) { + // Displaying the source for a symbol: + bool include_inlines = true; + bool include_symbols = false; + + if (m_options.num_lines == 0) + m_options.num_lines = 10; + + const size_t num_modules = m_options.modules.size(); + if (num_modules > 0) { + ModuleList matching_modules; + for (size_t i = 0; i < num_modules; ++i) { + FileSpec module_file_spec(m_options.modules[i]); + if (module_file_spec) { + ModuleSpec module_spec(module_file_spec); + matching_modules.Clear(); + target->GetImages().FindModules(module_spec, matching_modules); + matching_modules.FindFunctions( + name, eFunctionNameTypeAuto, include_symbols, include_inlines, + sc_list); + } + } + } else { + target->GetImages().FindFunctions(name, eFunctionNameTypeAuto, + include_symbols, include_inlines, + sc_list); + } + } + + void FindMatchingFunctionSymbols(Target *target, ConstString name, + SymbolContextList &sc_list) { + const size_t num_modules = m_options.modules.size(); + if (num_modules > 0) { + ModuleList matching_modules; + for (size_t i = 0; i < num_modules; ++i) { + FileSpec module_file_spec(m_options.modules[i]); + if (module_file_spec) { + ModuleSpec module_spec(module_file_spec); + matching_modules.Clear(); + target->GetImages().FindModules(module_spec, matching_modules); + matching_modules.FindFunctionSymbols(name, eFunctionNameTypeAuto, + sc_list); + } + } + } else { + target->GetImages().FindFunctionSymbols(name, eFunctionNameTypeAuto, + sc_list); + } + } + + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc != 0) { + result.AppendErrorWithFormat("'%s' takes no arguments, only flags.\n", + GetCommandName().str().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Target *target = m_exe_ctx.GetTargetPtr(); + + if (!m_options.symbol_name.empty()) { + SymbolContextList sc_list; + ConstString name(m_options.symbol_name.c_str()); + + // Displaying the source for a symbol. Search for function named name. + FindMatchingFunctions(target, name, sc_list); + size_t num_matches = sc_list.GetSize(); + if (!num_matches) { + // If we didn't find any functions with that name, try searching for + // symbols that line up exactly with function addresses. + SymbolContextList sc_list_symbols; + FindMatchingFunctionSymbols(target, name, sc_list_symbols); + size_t num_symbol_matches =sc_list_symbols.GetSize(); + + for (size_t i = 0; i < num_symbol_matches; i++) { + SymbolContext sc; + sc_list_symbols.GetContextAtIndex(i, sc); + if (sc.symbol && sc.symbol->ValueIsAddress()) { + const Address &base_address = sc.symbol->GetAddressRef(); + Function *function = base_address.CalculateSymbolContextFunction(); + if (function) { + sc_list.Append(SymbolContext(function)); + num_matches++; + break; + } + } + } + } + + if (num_matches == 0) { + result.AppendErrorWithFormat("Could not find function named: \"%s\".\n", + m_options.symbol_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (num_matches > 1) { + std::set<SourceInfo> source_match_set; + + bool displayed_something = false; + for (size_t i = 0; i < num_matches; i++) { + SymbolContext sc; + sc_list.GetContextAtIndex(i, sc); + SourceInfo source_info(sc.GetFunctionName(), + sc.GetFunctionStartLineEntry()); + + if (source_info.IsValid()) { + if (source_match_set.find(source_info) == source_match_set.end()) { + source_match_set.insert(source_info); + if (DisplayFunctionSource(sc, source_info, result)) + displayed_something = true; + } + } + } + + if (displayed_something) + result.SetStatus(eReturnStatusSuccessFinishResult); + else + result.SetStatus(eReturnStatusFailed); + } else { + SymbolContext sc; + sc_list.GetContextAtIndex(0, sc); + SourceInfo source_info; + + if (DisplayFunctionSource(sc, source_info, result)) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.SetStatus(eReturnStatusFailed); + } + } + return result.Succeeded(); + } else if (m_options.address != LLDB_INVALID_ADDRESS) { + Address so_addr; + StreamString error_strm; + SymbolContextList sc_list; + + if (target->GetSectionLoadList().IsEmpty()) { + // The target isn't loaded yet, we need to lookup the file address in + // all modules + const ModuleList &module_list = target->GetImages(); + const size_t num_modules = module_list.GetSize(); + for (size_t i = 0; i < num_modules; ++i) { + ModuleSP module_sp(module_list.GetModuleAtIndex(i)); + if (module_sp && + module_sp->ResolveFileAddress(m_options.address, so_addr)) { + SymbolContext sc; + sc.Clear(true); + if (module_sp->ResolveSymbolContextForAddress( + so_addr, eSymbolContextEverything, sc) & + eSymbolContextLineEntry) + sc_list.Append(sc); + } + } + + if (sc_list.GetSize() == 0) { + result.AppendErrorWithFormat( + "no modules have source information for file address 0x%" PRIx64 + ".\n", + m_options.address); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + // The target has some things loaded, resolve this address to a compile + // unit + file + line and display + if (target->GetSectionLoadList().ResolveLoadAddress(m_options.address, + so_addr)) { + ModuleSP module_sp(so_addr.GetModule()); + if (module_sp) { + SymbolContext sc; + sc.Clear(true); + if (module_sp->ResolveSymbolContextForAddress( + so_addr, eSymbolContextEverything, sc) & + eSymbolContextLineEntry) { + sc_list.Append(sc); + } else { + so_addr.Dump(&error_strm, nullptr, + Address::DumpStyleModuleWithFileAddress); + result.AppendErrorWithFormat("address resolves to %s, but there " + "is no line table information " + "available for this address.\n", + error_strm.GetData()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } + + if (sc_list.GetSize() == 0) { + result.AppendErrorWithFormat( + "no modules contain load address 0x%" PRIx64 ".\n", + m_options.address); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + uint32_t num_matches = sc_list.GetSize(); + for (uint32_t i = 0; i < num_matches; ++i) { + SymbolContext sc; + sc_list.GetContextAtIndex(i, sc); + if (sc.comp_unit) { + if (m_options.show_bp_locs) { + m_breakpoint_locations.Clear(); + const bool show_inlines = true; + m_breakpoint_locations.Reset(*sc.comp_unit, 0, show_inlines); + SearchFilterForUnconstrainedSearches target_search_filter( + target->shared_from_this()); + target_search_filter.Search(m_breakpoint_locations); + } + + bool show_fullpaths = true; + bool show_module = true; + bool show_inlined_frames = true; + const bool show_function_arguments = true; + const bool show_function_name = true; + sc.DumpStopContext(&result.GetOutputStream(), + m_exe_ctx.GetBestExecutionContextScope(), + sc.line_entry.range.GetBaseAddress(), + show_fullpaths, show_module, show_inlined_frames, + show_function_arguments, show_function_name); + result.GetOutputStream().EOL(); + + if (m_options.num_lines == 0) + m_options.num_lines = 10; + + size_t lines_to_back_up = + m_options.num_lines >= 10 ? 5 : m_options.num_lines / 2; + + const uint32_t column = + (GetDebugger().GetStopShowColumn() != eStopShowColumnNone) + ? sc.line_entry.column + : 0; + target->GetSourceManager().DisplaySourceLinesWithLineNumbers( + sc.comp_unit, sc.line_entry.line, column, lines_to_back_up, + m_options.num_lines - lines_to_back_up, "->", + &result.GetOutputStream(), GetBreakpointLocations()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } + } + } else if (m_options.file_name.empty()) { + // Last valid source manager context, or the current frame if no valid + // last context in source manager. One little trick here, if you type the + // exact same list command twice in a row, it is more likely because you + // typed it once, then typed it again + if (m_options.start_line == 0) { + if (target->GetSourceManager().DisplayMoreWithLineNumbers( + &result.GetOutputStream(), m_options.num_lines, + m_options.reverse, GetBreakpointLocations())) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } + } else { + if (m_options.num_lines == 0) + m_options.num_lines = 10; + + if (m_options.show_bp_locs) { + SourceManager::FileSP last_file_sp( + target->GetSourceManager().GetLastFile()); + if (last_file_sp) { + const bool show_inlines = true; + m_breakpoint_locations.Reset(last_file_sp->GetFileSpec(), 0, + show_inlines); + SearchFilterForUnconstrainedSearches target_search_filter( + target->shared_from_this()); + target_search_filter.Search(m_breakpoint_locations); + } + } else + m_breakpoint_locations.Clear(); + + const uint32_t column = 0; + if (target->GetSourceManager() + .DisplaySourceLinesWithLineNumbersUsingLastFile( + m_options.start_line, // Line to display + m_options.num_lines, // Lines after line to + UINT32_MAX, // Don't mark "line" + column, + "", // Don't mark "line" + &result.GetOutputStream(), GetBreakpointLocations())) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } + } + } else { + const char *filename = m_options.file_name.c_str(); + + bool check_inlines = false; + SymbolContextList sc_list; + size_t num_matches = 0; + + if (!m_options.modules.empty()) { + ModuleList matching_modules; + for (size_t i = 0, e = m_options.modules.size(); i < e; ++i) { + FileSpec module_file_spec(m_options.modules[i]); + if (module_file_spec) { + ModuleSpec module_spec(module_file_spec); + matching_modules.Clear(); + target->GetImages().FindModules(module_spec, matching_modules); + num_matches += matching_modules.ResolveSymbolContextForFilePath( + filename, 0, check_inlines, + SymbolContextItem(eSymbolContextModule | + eSymbolContextCompUnit), + sc_list); + } + } + } else { + num_matches = target->GetImages().ResolveSymbolContextForFilePath( + filename, 0, check_inlines, + eSymbolContextModule | eSymbolContextCompUnit, sc_list); + } + + if (num_matches == 0) { + result.AppendErrorWithFormat("Could not find source file \"%s\".\n", + m_options.file_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (num_matches > 1) { + bool got_multiple = false; + FileSpec *test_cu_spec = nullptr; + + for (unsigned i = 0; i < num_matches; i++) { + SymbolContext sc; + sc_list.GetContextAtIndex(i, sc); + if (sc.comp_unit) { + if (test_cu_spec) { + if (test_cu_spec != static_cast<FileSpec *>(sc.comp_unit)) + got_multiple = true; + break; + } else + test_cu_spec = sc.comp_unit; + } + } + if (got_multiple) { + result.AppendErrorWithFormat( + "Multiple source files found matching: \"%s.\"\n", + m_options.file_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + SymbolContext sc; + if (sc_list.GetContextAtIndex(0, sc)) { + if (sc.comp_unit) { + if (m_options.show_bp_locs) { + const bool show_inlines = true; + m_breakpoint_locations.Reset(*sc.comp_unit, 0, show_inlines); + SearchFilterForUnconstrainedSearches target_search_filter( + target->shared_from_this()); + target_search_filter.Search(m_breakpoint_locations); + } else + m_breakpoint_locations.Clear(); + + if (m_options.num_lines == 0) + m_options.num_lines = 10; + const uint32_t column = 0; + target->GetSourceManager().DisplaySourceLinesWithLineNumbers( + sc.comp_unit, m_options.start_line, column, + 0, m_options.num_lines, + "", &result.GetOutputStream(), GetBreakpointLocations()); + + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("No comp unit found for: \"%s.\"\n", + m_options.file_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } + return result.Succeeded(); + } + + const SymbolContextList *GetBreakpointLocations() { + if (m_breakpoint_locations.GetFileLineMatches().GetSize() > 0) + return &m_breakpoint_locations.GetFileLineMatches(); + return nullptr; + } + + CommandOptions m_options; + FileLineResolver m_breakpoint_locations; + std::string m_reverse_name; +}; + +#pragma mark CommandObjectMultiwordSource +// CommandObjectMultiwordSource + +CommandObjectMultiwordSource::CommandObjectMultiwordSource( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "source", "Commands for examining " + "source code described by " + "debug information for the " + "current target process.", + "source <subcommand> [<subcommand-options>]") { + LoadSubCommand("info", + CommandObjectSP(new CommandObjectSourceInfo(interpreter))); + LoadSubCommand("list", + CommandObjectSP(new CommandObjectSourceList(interpreter))); +} + +CommandObjectMultiwordSource::~CommandObjectMultiwordSource() = default; diff --git a/lldb/source/Commands/CommandObjectSource.h b/lldb/source/Commands/CommandObjectSource.h new file mode 100644 index 0000000000000..d72122d55dc74 --- /dev/null +++ b/lldb/source/Commands/CommandObjectSource.h @@ -0,0 +1,30 @@ +//===-- CommandObjectSource.h.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_CommandObjectSource_h_ +#define liblldb_CommandObjectSource_h_ + +#include "lldb/Core/STLUtils.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +// CommandObjectMultiwordSource + +class CommandObjectMultiwordSource : public CommandObjectMultiword { +public: + CommandObjectMultiwordSource(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordSource() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectSource_h_ diff --git a/lldb/source/Commands/CommandObjectStats.cpp b/lldb/source/Commands/CommandObjectStats.cpp new file mode 100644 index 0000000000000..e3a1f9433662e --- /dev/null +++ b/lldb/source/Commands/CommandObjectStats.cpp @@ -0,0 +1,106 @@ +//===-- CommandObjectStats.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 "CommandObjectStats.h" +#include "lldb/Host/Host.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Target/Target.h" + +using namespace lldb; +using namespace lldb_private; + +class CommandObjectStatsEnable : public CommandObjectParsed { +public: + CommandObjectStatsEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "enable", + "Enable statistics collection", nullptr, + eCommandProcessMustBePaused) {} + + ~CommandObjectStatsEnable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + + if (target.GetCollectingStats()) { + result.AppendError("statistics already enabled"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + target.SetCollectingStats(true); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } +}; + +class CommandObjectStatsDisable : public CommandObjectParsed { +public: + CommandObjectStatsDisable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "disable", + "Disable statistics collection", nullptr, + eCommandProcessMustBePaused) {} + + ~CommandObjectStatsDisable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + + if (!target.GetCollectingStats()) { + result.AppendError("need to enable statistics before disabling them"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + target.SetCollectingStats(false); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } +}; + +class CommandObjectStatsDump : public CommandObjectParsed { +public: + CommandObjectStatsDump(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "dump", "Dump statistics results", + nullptr, eCommandProcessMustBePaused) {} + + ~CommandObjectStatsDump() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + + uint32_t i = 0; + for (auto &stat : target.GetStatistics()) { + result.AppendMessageWithFormat( + "%s : %u\n", + lldb_private::GetStatDescription(static_cast<lldb_private::StatisticKind>(i)) + .c_str(), + stat); + i += 1; + } + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } +}; + +CommandObjectStats::CommandObjectStats(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "statistics", + "Print statistics about a debugging session", + "statistics <subcommand> [<subcommand-options>]") { + LoadSubCommand("enable", + CommandObjectSP(new CommandObjectStatsEnable(interpreter))); + LoadSubCommand("disable", + CommandObjectSP(new CommandObjectStatsDisable(interpreter))); + LoadSubCommand("dump", + CommandObjectSP(new CommandObjectStatsDump(interpreter))); +} + +CommandObjectStats::~CommandObjectStats() = default; diff --git a/lldb/source/Commands/CommandObjectStats.h b/lldb/source/Commands/CommandObjectStats.h new file mode 100644 index 0000000000000..27e9a6ff865a2 --- /dev/null +++ b/lldb/source/Commands/CommandObjectStats.h @@ -0,0 +1,24 @@ +//===-- CommandObjectStats.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_CommandObjectStats_h_ +#define liblldb_CommandObjectStats_h_ + +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { +class CommandObjectStats : public CommandObjectMultiword { +public: + CommandObjectStats(CommandInterpreter &interpreter); + + ~CommandObjectStats() override; +}; +} // namespace lldb_private + +#endif // liblldb_CommandObjectLanguage_h_ diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp new file mode 100644 index 0000000000000..abf7895a73849 --- /dev/null +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -0,0 +1,4902 @@ +//===-- CommandObjectTarget.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 "CommandObjectTarget.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/IOHandler.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/Section.h" +#include "lldb/Core/ValueObjectVariable.h" +#include "lldb/DataFormatters/ValueObjectPrinter.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Host/StringConvert.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupArchitecture.h" +#include "lldb/Interpreter/OptionGroupBoolean.h" +#include "lldb/Interpreter/OptionGroupFile.h" +#include "lldb/Interpreter/OptionGroupFormat.h" +#include "lldb/Interpreter/OptionGroupPlatform.h" +#include "lldb/Interpreter/OptionGroupString.h" +#include "lldb/Interpreter/OptionGroupUInt64.h" +#include "lldb/Interpreter/OptionGroupUUID.h" +#include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" +#include "lldb/Interpreter/OptionGroupVariable.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/FuncUnwinders.h" +#include "lldb/Symbol/LineTable.h" +#include "lldb/Symbol/LocateSymbolFile.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Symbol/UnwindPlan.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadSpec.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/Timer.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatAdapters.h" + +#include <cerrno> + +using namespace lldb; +using namespace lldb_private; + +static void DumpTargetInfo(uint32_t target_idx, Target *target, + const char *prefix_cstr, + bool show_stopped_process_status, Stream &strm) { + const ArchSpec &target_arch = target->GetArchitecture(); + + Module *exe_module = target->GetExecutableModulePointer(); + char exe_path[PATH_MAX]; + bool exe_valid = false; + if (exe_module) + exe_valid = exe_module->GetFileSpec().GetPath(exe_path, sizeof(exe_path)); + + if (!exe_valid) + ::strcpy(exe_path, "<none>"); + + strm.Printf("%starget #%u: %s", prefix_cstr ? prefix_cstr : "", target_idx, + exe_path); + + uint32_t properties = 0; + if (target_arch.IsValid()) { + strm.Printf("%sarch=", properties++ > 0 ? ", " : " ( "); + target_arch.DumpTriple(strm); + properties++; + } + PlatformSP platform_sp(target->GetPlatform()); + if (platform_sp) + strm.Printf("%splatform=%s", properties++ > 0 ? ", " : " ( ", + platform_sp->GetName().GetCString()); + + ProcessSP process_sp(target->GetProcessSP()); + bool show_process_status = false; + if (process_sp) { + lldb::pid_t pid = process_sp->GetID(); + StateType state = process_sp->GetState(); + if (show_stopped_process_status) + show_process_status = StateIsStoppedState(state, true); + const char *state_cstr = StateAsCString(state); + if (pid != LLDB_INVALID_PROCESS_ID) + strm.Printf("%spid=%" PRIu64, properties++ > 0 ? ", " : " ( ", pid); + strm.Printf("%sstate=%s", properties++ > 0 ? ", " : " ( ", state_cstr); + } + if (properties > 0) + strm.PutCString(" )\n"); + else + strm.EOL(); + if (show_process_status) { + const bool only_threads_with_stop_reason = true; + const uint32_t start_frame = 0; + const uint32_t num_frames = 1; + const uint32_t num_frames_with_source = 1; + const bool stop_format = false; + process_sp->GetStatus(strm); + process_sp->GetThreadStatus(strm, only_threads_with_stop_reason, + start_frame, num_frames, + num_frames_with_source, stop_format); + } +} + +static uint32_t DumpTargetList(TargetList &target_list, + bool show_stopped_process_status, Stream &strm) { + const uint32_t num_targets = target_list.GetNumTargets(); + if (num_targets) { + TargetSP selected_target_sp(target_list.GetSelectedTarget()); + strm.PutCString("Current targets:\n"); + for (uint32_t i = 0; i < num_targets; ++i) { + TargetSP target_sp(target_list.GetTargetAtIndex(i)); + if (target_sp) { + bool is_selected = target_sp.get() == selected_target_sp.get(); + DumpTargetInfo(i, target_sp.get(), is_selected ? "* " : " ", + show_stopped_process_status, strm); + } + } + } + return num_targets; +} + +// Note that the negation in the argument name causes a slightly confusing +// mapping of the enum values. +static constexpr OptionEnumValueElement g_dependents_enumaration[] = { + { + eLoadDependentsDefault, + "default", + "Only load dependents when the target is an executable.", + }, + { + eLoadDependentsNo, + "true", + "Don't load dependents, even if the target is an executable.", + }, + { + eLoadDependentsYes, + "false", + "Load dependents, even if the target is not an executable.", + }, +}; + +#define LLDB_OPTIONS_target_dependents +#include "CommandOptions.inc" + +class OptionGroupDependents : public OptionGroup { +public: + OptionGroupDependents() {} + + ~OptionGroupDependents() override {} + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_target_dependents_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + + // For compatibility no value means don't load dependents. + if (option_value.empty()) { + m_load_dependent_files = eLoadDependentsNo; + return error; + } + + const char short_option = + g_target_dependents_options[option_idx].short_option; + if (short_option == 'd') { + LoadDependentFiles tmp_load_dependents; + tmp_load_dependents = (LoadDependentFiles)OptionArgParser::ToOptionEnum( + option_value, g_target_dependents_options[option_idx].enum_values, 0, + error); + if (error.Success()) + m_load_dependent_files = tmp_load_dependents; + } else { + error.SetErrorStringWithFormat("unrecognized short option '%c'", + short_option); + } + + return error; + } + + Status SetOptionValue(uint32_t, const char *, ExecutionContext *) = delete; + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_load_dependent_files = eLoadDependentsDefault; + } + + LoadDependentFiles m_load_dependent_files; + +private: + DISALLOW_COPY_AND_ASSIGN(OptionGroupDependents); +}; + +#pragma mark CommandObjectTargetCreate + +// "target create" + +class CommandObjectTargetCreate : public CommandObjectParsed { +public: + CommandObjectTargetCreate(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target create", + "Create a target using the argument as the main executable.", + nullptr), + m_option_group(), m_arch_option(), + m_core_file(LLDB_OPT_SET_1, false, "core", 'c', 0, eArgTypeFilename, + "Fullpath to a core file to use for this target."), + m_platform_path(LLDB_OPT_SET_1, false, "platform-path", 'P', 0, + eArgTypePath, + "Path to the remote file to use for this target."), + m_symbol_file(LLDB_OPT_SET_1, false, "symfile", 's', 0, + eArgTypeFilename, + "Fullpath to a stand alone debug " + "symbols file for when debug symbols " + "are not in the executable."), + m_remote_file( + LLDB_OPT_SET_1, false, "remote-file", 'r', 0, eArgTypeFilename, + "Fullpath to the file on the remote host if debugging remotely."), + m_add_dependents() { + CommandArgumentEntry arg; + CommandArgumentData file_arg; + + // Define the first (and only) variant of this arg. + file_arg.arg_type = eArgTypeFilename; + file_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(file_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + + m_option_group.Append(&m_arch_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_core_file, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_platform_path, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_symbol_file, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_remote_file, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_add_dependents, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectTargetCreate() override = default; + + Options *GetOptions() override { return &m_option_group; } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, + request, nullptr); + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + FileSpec core_file(m_core_file.GetOptionValue().GetCurrentValue()); + FileSpec remote_file(m_remote_file.GetOptionValue().GetCurrentValue()); + + if (core_file) { + if (!FileSystem::Instance().Exists(core_file)) { + result.AppendErrorWithFormat("core file '%s' doesn't exist", + core_file.GetPath().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (!FileSystem::Instance().Readable(core_file)) { + result.AppendErrorWithFormat("core file '%s' is not readable", + core_file.GetPath().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (argc == 1 || core_file || remote_file) { + FileSpec symfile(m_symbol_file.GetOptionValue().GetCurrentValue()); + if (symfile) { + if (FileSystem::Instance().Exists(symfile)) { + if (!FileSystem::Instance().Readable(symfile)) { + result.AppendErrorWithFormat("symbol file '%s' is not readable", + symfile.GetPath().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + char symfile_path[PATH_MAX]; + symfile.GetPath(symfile_path, sizeof(symfile_path)); + result.AppendErrorWithFormat("invalid symbol file path '%s'", + symfile_path); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + const char *file_path = command.GetArgumentAtIndex(0); + static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); + Timer scoped_timer(func_cat, "(lldb) target create '%s'", file_path); + FileSpec file_spec; + + if (file_path) { + file_spec.SetFile(file_path, FileSpec::Style::native); + FileSystem::Instance().Resolve(file_spec); + } + + bool must_set_platform_path = false; + + Debugger &debugger = GetDebugger(); + + TargetSP target_sp; + llvm::StringRef arch_cstr = m_arch_option.GetArchitectureName(); + Status error(debugger.GetTargetList().CreateTarget( + debugger, file_path, arch_cstr, + m_add_dependents.m_load_dependent_files, nullptr, target_sp)); + + if (target_sp) { + // Only get the platform after we create the target because we might + // have switched platforms depending on what the arguments were to + // CreateTarget() we can't rely on the selected platform. + + PlatformSP platform_sp = target_sp->GetPlatform(); + + if (remote_file) { + if (platform_sp) { + // I have a remote file.. two possible cases + if (file_spec && FileSystem::Instance().Exists(file_spec)) { + // if the remote file does not exist, push it there + if (!platform_sp->GetFileExists(remote_file)) { + Status err = platform_sp->PutFile(file_spec, remote_file); + if (err.Fail()) { + result.AppendError(err.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } else { + // there is no local file and we need one + // in order to make the remote ---> local transfer we need a + // platform + // TODO: if the user has passed in a --platform argument, use it + // to fetch the right platform + if (!platform_sp) { + result.AppendError( + "unable to perform remote debugging without a platform"); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (file_path) { + // copy the remote file to the local file + Status err = platform_sp->GetFile(remote_file, file_spec); + if (err.Fail()) { + result.AppendError(err.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + // make up a local file + result.AppendError("remote --> local transfer without local " + "path is not implemented yet"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } else { + result.AppendError("no platform found for target"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (symfile || remote_file) { + ModuleSP module_sp(target_sp->GetExecutableModule()); + if (module_sp) { + if (symfile) + module_sp->SetSymbolFileFileSpec(symfile); + if (remote_file) { + std::string remote_path = remote_file.GetPath(); + target_sp->SetArg0(remote_path.c_str()); + module_sp->SetPlatformFileSpec(remote_file); + } + } + } + + debugger.GetTargetList().SetSelectedTarget(target_sp.get()); + if (must_set_platform_path) { + ModuleSpec main_module_spec(file_spec); + ModuleSP module_sp = target_sp->GetOrCreateModule(main_module_spec, + true /* notify */); + if (module_sp) + module_sp->SetPlatformFileSpec(remote_file); + } + if (core_file) { + char core_path[PATH_MAX]; + core_file.GetPath(core_path, sizeof(core_path)); + if (FileSystem::Instance().Exists(core_file)) { + if (!FileSystem::Instance().Readable(core_file)) { + result.AppendMessageWithFormat( + "Core file '%s' is not readable.\n", core_path); + result.SetStatus(eReturnStatusFailed); + return false; + } + FileSpec core_file_dir; + core_file_dir.GetDirectory() = core_file.GetDirectory(); + target_sp->AppendExecutableSearchPaths(core_file_dir); + + ProcessSP process_sp(target_sp->CreateProcess( + GetDebugger().GetListener(), llvm::StringRef(), &core_file)); + + if (process_sp) { + // Seems weird that we Launch a core file, but that is what we + // do! + error = process_sp->LoadCore(); + + if (error.Fail()) { + result.AppendError( + error.AsCString("can't find plug-in for core file")); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + result.AppendMessageWithFormat( + "Core file '%s' (%s) was loaded.\n", core_path, + target_sp->GetArchitecture().GetArchitectureName()); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + } else { + result.AppendErrorWithFormat( + "Unable to find process plug-in for core file '%s'\n", + core_path); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("Core file '%s' does not exist\n", + core_path); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendMessageWithFormat( + "Current executable set to '%s' (%s).\n", + file_spec.GetPath().c_str(), + target_sp->GetArchitecture().GetArchitectureName()); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + } else { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("'%s' takes exactly one executable path " + "argument, or use the --core option.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + +private: + OptionGroupOptions m_option_group; + OptionGroupArchitecture m_arch_option; + OptionGroupFile m_core_file; + OptionGroupFile m_platform_path; + OptionGroupFile m_symbol_file; + OptionGroupFile m_remote_file; + OptionGroupDependents m_add_dependents; +}; + +#pragma mark CommandObjectTargetList + +// "target list" + +class CommandObjectTargetList : public CommandObjectParsed { +public: + CommandObjectTargetList(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target list", + "List all current targets in the current debug session.", nullptr) { + } + + ~CommandObjectTargetList() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + if (args.GetArgumentCount() == 0) { + Stream &strm = result.GetOutputStream(); + + bool show_stopped_process_status = false; + if (DumpTargetList(GetDebugger().GetTargetList(), + show_stopped_process_status, strm) == 0) { + strm.PutCString("No targets.\n"); + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("the 'target list' command takes no arguments\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetSelect + +// "target select" + +class CommandObjectTargetSelect : public CommandObjectParsed { +public: + CommandObjectTargetSelect(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target select", + "Select a target as the current target by target index.", nullptr) { + } + + ~CommandObjectTargetSelect() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + if (args.GetArgumentCount() == 1) { + bool success = false; + const char *target_idx_arg = args.GetArgumentAtIndex(0); + uint32_t target_idx = + StringConvert::ToUInt32(target_idx_arg, UINT32_MAX, 0, &success); + if (success) { + TargetList &target_list = GetDebugger().GetTargetList(); + const uint32_t num_targets = target_list.GetNumTargets(); + if (target_idx < num_targets) { + TargetSP target_sp(target_list.GetTargetAtIndex(target_idx)); + if (target_sp) { + Stream &strm = result.GetOutputStream(); + target_list.SetSelectedTarget(target_sp.get()); + bool show_stopped_process_status = false; + DumpTargetList(target_list, show_stopped_process_status, strm); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("target #%u is NULL in target list\n", + target_idx); + result.SetStatus(eReturnStatusFailed); + } + } else { + if (num_targets > 0) { + result.AppendErrorWithFormat( + "index %u is out of range, valid target indexes are 0 - %u\n", + target_idx, num_targets - 1); + } else { + result.AppendErrorWithFormat( + "index %u is out of range since there are no active targets\n", + target_idx); + } + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("invalid index string value '%s'\n", + target_idx_arg); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError( + "'target select' takes a single argument: a target index\n"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetSelect + +// "target delete" + +class CommandObjectTargetDelete : public CommandObjectParsed { +public: + CommandObjectTargetDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target delete", + "Delete one or more targets by target index.", + nullptr), + m_option_group(), m_all_option(LLDB_OPT_SET_1, false, "all", 'a', + "Delete all targets.", false, true), + m_cleanup_option( + LLDB_OPT_SET_1, false, "clean", 'c', + "Perform extra cleanup to minimize memory consumption after " + "deleting the target. " + "By default, LLDB will keep in memory any modules previously " + "loaded by the target as well " + "as all of its debug info. Specifying --clean will unload all of " + "these shared modules and " + "cause them to be reparsed again the next time the target is run", + false, true) { + m_option_group.Append(&m_all_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_cleanup_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectTargetDelete() override = default; + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + const size_t argc = args.GetArgumentCount(); + std::vector<TargetSP> delete_target_list; + TargetList &target_list = GetDebugger().GetTargetList(); + TargetSP target_sp; + + if (m_all_option.GetOptionValue()) { + for (int i = 0; i < target_list.GetNumTargets(); ++i) + delete_target_list.push_back(target_list.GetTargetAtIndex(i)); + } else if (argc > 0) { + const uint32_t num_targets = target_list.GetNumTargets(); + // Bail out if don't have any targets. + if (num_targets == 0) { + result.AppendError("no targets to delete"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + for (auto &entry : args.entries()) { + uint32_t target_idx; + if (entry.ref().getAsInteger(0, target_idx)) { + result.AppendErrorWithFormat("invalid target index '%s'\n", + entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (target_idx < num_targets) { + target_sp = target_list.GetTargetAtIndex(target_idx); + if (target_sp) { + delete_target_list.push_back(target_sp); + continue; + } + } + if (num_targets > 1) + result.AppendErrorWithFormat("target index %u is out of range, valid " + "target indexes are 0 - %u\n", + target_idx, num_targets - 1); + else + result.AppendErrorWithFormat( + "target index %u is out of range, the only valid index is 0\n", + target_idx); + + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + target_sp = target_list.GetSelectedTarget(); + if (!target_sp) { + result.AppendErrorWithFormat("no target is currently selected\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + delete_target_list.push_back(target_sp); + } + + const size_t num_targets_to_delete = delete_target_list.size(); + for (size_t idx = 0; idx < num_targets_to_delete; ++idx) { + target_sp = delete_target_list[idx]; + target_list.DeleteTarget(target_sp); + target_sp->Destroy(); + } + // If "--clean" was specified, prune any orphaned shared modules from the + // global shared module list + if (m_cleanup_option.GetOptionValue()) { + const bool mandatory = true; + ModuleList::RemoveOrphanSharedModules(mandatory); + } + result.GetOutputStream().Printf("%u targets deleted.\n", + (uint32_t)num_targets_to_delete); + result.SetStatus(eReturnStatusSuccessFinishResult); + + return true; + } + + OptionGroupOptions m_option_group; + OptionGroupBoolean m_all_option; + OptionGroupBoolean m_cleanup_option; +}; + +#pragma mark CommandObjectTargetVariable + +// "target variable" + +class CommandObjectTargetVariable : public CommandObjectParsed { + static const uint32_t SHORT_OPTION_FILE = 0x66696c65; // 'file' + static const uint32_t SHORT_OPTION_SHLB = 0x73686c62; // 'shlb' + +public: + CommandObjectTargetVariable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target variable", + "Read global variables for the current target, " + "before or while running a process.", + nullptr, eCommandRequiresTarget), + m_option_group(), + m_option_variable(false), // Don't include frame options + m_option_format(eFormatDefault), + m_option_compile_units(LLDB_OPT_SET_1, false, "file", SHORT_OPTION_FILE, + 0, eArgTypeFilename, + "A basename or fullpath to a file that contains " + "global variables. This option can be " + "specified multiple times."), + m_option_shared_libraries( + LLDB_OPT_SET_1, false, "shlib", SHORT_OPTION_SHLB, 0, + eArgTypeFilename, + "A basename or fullpath to a shared library to use in the search " + "for global " + "variables. This option can be specified multiple times."), + m_varobj_options() { + CommandArgumentEntry arg; + CommandArgumentData var_name_arg; + + // Define the first (and only) variant of this arg. + var_name_arg.arg_type = eArgTypeVarName; + var_name_arg.arg_repetition = eArgRepeatPlus; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(var_name_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + + m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_option_variable, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_option_format, + OptionGroupFormat::OPTION_GROUP_FORMAT | + OptionGroupFormat::OPTION_GROUP_GDB_FMT, + LLDB_OPT_SET_1); + m_option_group.Append(&m_option_compile_units, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1); + m_option_group.Append(&m_option_shared_libraries, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectTargetVariable() override = default; + + void DumpValueObject(Stream &s, VariableSP &var_sp, ValueObjectSP &valobj_sp, + const char *root_name) { + DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions()); + + if (!valobj_sp->GetTargetSP()->GetDisplayRuntimeSupportValues() && + valobj_sp->IsRuntimeSupportValue()) + return; + + switch (var_sp->GetScope()) { + case eValueTypeVariableGlobal: + if (m_option_variable.show_scope) + s.PutCString("GLOBAL: "); + break; + + case eValueTypeVariableStatic: + if (m_option_variable.show_scope) + s.PutCString("STATIC: "); + break; + + case eValueTypeVariableArgument: + if (m_option_variable.show_scope) + s.PutCString(" ARG: "); + break; + + case eValueTypeVariableLocal: + if (m_option_variable.show_scope) + s.PutCString(" LOCAL: "); + break; + + case eValueTypeVariableThreadLocal: + if (m_option_variable.show_scope) + s.PutCString("THREAD: "); + break; + + default: + break; + } + + if (m_option_variable.show_decl) { + bool show_fullpaths = false; + bool show_module = true; + if (var_sp->DumpDeclaration(&s, show_fullpaths, show_module)) + s.PutCString(": "); + } + + const Format format = m_option_format.GetFormat(); + if (format != eFormatDefault) + options.SetFormat(format); + + options.SetRootValueObjectName(root_name); + + valobj_sp->Dump(s, options); + } + + static size_t GetVariableCallback(void *baton, const char *name, + VariableList &variable_list) { + size_t old_size = variable_list.GetSize(); + Target *target = static_cast<Target *>(baton); + if (target) + target->GetImages().FindGlobalVariables(ConstString(name), UINT32_MAX, + variable_list); + return variable_list.GetSize() - old_size; + } + + Options *GetOptions() override { return &m_option_group; } + +protected: + void DumpGlobalVariableList(const ExecutionContext &exe_ctx, + const SymbolContext &sc, + const VariableList &variable_list, Stream &s) { + size_t count = variable_list.GetSize(); + if (count > 0) { + if (sc.module_sp) { + if (sc.comp_unit) { + s.Printf("Global variables for %s in %s:\n", + sc.comp_unit->GetPath().c_str(), + sc.module_sp->GetFileSpec().GetPath().c_str()); + } else { + s.Printf("Global variables for %s\n", + sc.module_sp->GetFileSpec().GetPath().c_str()); + } + } else if (sc.comp_unit) { + s.Printf("Global variables for %s\n", sc.comp_unit->GetPath().c_str()); + } + + for (uint32_t i = 0; i < count; ++i) { + VariableSP var_sp(variable_list.GetVariableAtIndex(i)); + if (var_sp) { + ValueObjectSP valobj_sp(ValueObjectVariable::Create( + exe_ctx.GetBestExecutionContextScope(), var_sp)); + + if (valobj_sp) + DumpValueObject(s, var_sp, valobj_sp, + var_sp->GetName().GetCString()); + } + } + } + } + + bool DoExecute(Args &args, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + const size_t argc = args.GetArgumentCount(); + Stream &s = result.GetOutputStream(); + + if (argc > 0) { + + // TODO: Convert to entry-based iteration. Requires converting + // DumpValueObject. + for (size_t idx = 0; idx < argc; ++idx) { + VariableList variable_list; + ValueObjectList valobj_list; + + const char *arg = args.GetArgumentAtIndex(idx); + size_t matches = 0; + bool use_var_name = false; + if (m_option_variable.use_regex) { + RegularExpression regex(llvm::StringRef::withNullAsEmpty(arg)); + if (!regex.IsValid()) { + result.GetErrorStream().Printf( + "error: invalid regular expression: '%s'\n", arg); + result.SetStatus(eReturnStatusFailed); + return false; + } + use_var_name = true; + target->GetImages().FindGlobalVariables(regex, UINT32_MAX, + variable_list); + matches = variable_list.GetSize(); + } else { + Status error(Variable::GetValuesForVariableExpressionPath( + arg, m_exe_ctx.GetBestExecutionContextScope(), + GetVariableCallback, target, variable_list, valobj_list)); + matches = variable_list.GetSize(); + } + + if (matches == 0) { + result.GetErrorStream().Printf( + "error: can't find global variable '%s'\n", arg); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + for (uint32_t global_idx = 0; global_idx < matches; ++global_idx) { + VariableSP var_sp(variable_list.GetVariableAtIndex(global_idx)); + if (var_sp) { + ValueObjectSP valobj_sp( + valobj_list.GetValueObjectAtIndex(global_idx)); + if (!valobj_sp) + valobj_sp = ValueObjectVariable::Create( + m_exe_ctx.GetBestExecutionContextScope(), var_sp); + + if (valobj_sp) + DumpValueObject(s, var_sp, valobj_sp, + use_var_name ? var_sp->GetName().GetCString() + : arg); + } + } + } + } + } else { + const FileSpecList &compile_units = + m_option_compile_units.GetOptionValue().GetCurrentValue(); + const FileSpecList &shlibs = + m_option_shared_libraries.GetOptionValue().GetCurrentValue(); + SymbolContextList sc_list; + const size_t num_compile_units = compile_units.GetSize(); + const size_t num_shlibs = shlibs.GetSize(); + if (num_compile_units == 0 && num_shlibs == 0) { + bool success = false; + StackFrame *frame = m_exe_ctx.GetFramePtr(); + CompileUnit *comp_unit = nullptr; + if (frame) { + SymbolContext sc = frame->GetSymbolContext(eSymbolContextCompUnit); + if (sc.comp_unit) { + const bool can_create = true; + VariableListSP comp_unit_varlist_sp( + sc.comp_unit->GetVariableList(can_create)); + if (comp_unit_varlist_sp) { + size_t count = comp_unit_varlist_sp->GetSize(); + if (count > 0) { + DumpGlobalVariableList(m_exe_ctx, sc, *comp_unit_varlist_sp, s); + success = true; + } + } + } + } + if (!success) { + if (frame) { + if (comp_unit) + result.AppendErrorWithFormat( + "no global variables in current compile unit: %s\n", + comp_unit->GetPath().c_str()); + else + result.AppendErrorWithFormat( + "no debug information for frame %u\n", + frame->GetFrameIndex()); + } else + result.AppendError("'target variable' takes one or more global " + "variable names as arguments\n"); + result.SetStatus(eReturnStatusFailed); + } + } else { + SymbolContextList sc_list; + // We have one or more compile unit or shlib + if (num_shlibs > 0) { + for (size_t shlib_idx = 0; shlib_idx < num_shlibs; ++shlib_idx) { + const FileSpec module_file(shlibs.GetFileSpecAtIndex(shlib_idx)); + ModuleSpec module_spec(module_file); + + ModuleSP module_sp( + target->GetImages().FindFirstModule(module_spec)); + if (module_sp) { + if (num_compile_units > 0) { + for (size_t cu_idx = 0; cu_idx < num_compile_units; ++cu_idx) + module_sp->FindCompileUnits( + compile_units.GetFileSpecAtIndex(cu_idx), sc_list); + } else { + SymbolContext sc; + sc.module_sp = module_sp; + sc_list.Append(sc); + } + } else { + // Didn't find matching shlib/module in target... + result.AppendErrorWithFormat( + "target doesn't contain the specified shared library: %s\n", + module_file.GetPath().c_str()); + } + } + } else { + // No shared libraries, we just want to find globals for the compile + // units files that were specified + for (size_t cu_idx = 0; cu_idx < num_compile_units; ++cu_idx) + target->GetImages().FindCompileUnits( + compile_units.GetFileSpecAtIndex(cu_idx), sc_list); + } + + const uint32_t num_scs = sc_list.GetSize(); + if (num_scs > 0) { + SymbolContext sc; + for (uint32_t sc_idx = 0; sc_idx < num_scs; ++sc_idx) { + if (sc_list.GetContextAtIndex(sc_idx, sc)) { + if (sc.comp_unit) { + const bool can_create = true; + VariableListSP comp_unit_varlist_sp( + sc.comp_unit->GetVariableList(can_create)); + if (comp_unit_varlist_sp) + DumpGlobalVariableList(m_exe_ctx, sc, *comp_unit_varlist_sp, + s); + } else if (sc.module_sp) { + // Get all global variables for this module + lldb_private::RegularExpression all_globals_regex( + llvm::StringRef( + ".")); // Any global with at least one character + VariableList variable_list; + sc.module_sp->FindGlobalVariables(all_globals_regex, UINT32_MAX, + variable_list); + DumpGlobalVariableList(m_exe_ctx, sc, variable_list, s); + } + } + } + } + } + } + + if (m_interpreter.TruncationWarningNecessary()) { + result.GetOutputStream().Printf(m_interpreter.TruncationWarningText(), + m_cmd_name.c_str()); + m_interpreter.TruncationWarningGiven(); + } + + return result.Succeeded(); + } + + OptionGroupOptions m_option_group; + OptionGroupVariable m_option_variable; + OptionGroupFormat m_option_format; + OptionGroupFileList m_option_compile_units; + OptionGroupFileList m_option_shared_libraries; + OptionGroupValueObjectDisplay m_varobj_options; +}; + +#pragma mark CommandObjectTargetModulesSearchPathsAdd + +class CommandObjectTargetModulesSearchPathsAdd : public CommandObjectParsed { +public: + CommandObjectTargetModulesSearchPathsAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target modules search-paths add", + "Add new image search paths substitution pairs to " + "the current target.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg; + CommandArgumentData old_prefix_arg; + CommandArgumentData new_prefix_arg; + + // Define the first variant of this arg pair. + old_prefix_arg.arg_type = eArgTypeOldPathPrefix; + old_prefix_arg.arg_repetition = eArgRepeatPairPlus; + + // Define the first variant of this arg pair. + new_prefix_arg.arg_type = eArgTypeNewPathPrefix; + new_prefix_arg.arg_repetition = eArgRepeatPairPlus; + + // There are two required arguments that must always occur together, i.e. + // an argument "pair". Because they must always occur together, they are + // treated as two variants of one argument rather than two independent + // arguments. Push them both into the first argument position for + // m_arguments... + + arg.push_back(old_prefix_arg); + arg.push_back(new_prefix_arg); + + m_arguments.push_back(arg); + } + + ~CommandObjectTargetModulesSearchPathsAdd() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + const size_t argc = command.GetArgumentCount(); + if (argc & 1) { + result.AppendError("add requires an even number of arguments\n"); + result.SetStatus(eReturnStatusFailed); + } else { + for (size_t i = 0; i < argc; i += 2) { + const char *from = command.GetArgumentAtIndex(i); + const char *to = command.GetArgumentAtIndex(i + 1); + + if (from[0] && to[0]) { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + if (log) { + LLDB_LOGF(log, + "target modules search path adding ImageSearchPath " + "pair: '%s' -> '%s'", + from, to); + } + bool last_pair = ((argc - i) == 2); + target->GetImageSearchPathList().Append( + ConstString(from), ConstString(to), + last_pair); // Notify if this is the last pair + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + if (from[0]) + result.AppendError("<path-prefix> can't be empty\n"); + else + result.AppendError("<new-path-prefix> can't be empty\n"); + result.SetStatus(eReturnStatusFailed); + } + } + } + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetModulesSearchPathsClear + +class CommandObjectTargetModulesSearchPathsClear : public CommandObjectParsed { +public: + CommandObjectTargetModulesSearchPathsClear(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target modules search-paths clear", + "Clear all current image search path substitution " + "pairs from the current target.", + "target modules search-paths clear", + eCommandRequiresTarget) {} + + ~CommandObjectTargetModulesSearchPathsClear() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + bool notify = true; + target->GetImageSearchPathList().Clear(notify); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetModulesSearchPathsInsert + +class CommandObjectTargetModulesSearchPathsInsert : public CommandObjectParsed { +public: + CommandObjectTargetModulesSearchPathsInsert(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target modules search-paths insert", + "Insert a new image search path substitution pair " + "into the current target at the specified index.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData index_arg; + CommandArgumentData old_prefix_arg; + CommandArgumentData new_prefix_arg; + + // Define the first and only variant of this arg. + index_arg.arg_type = eArgTypeIndex; + index_arg.arg_repetition = eArgRepeatPlain; + + // Put the one and only variant into the first arg for m_arguments: + arg1.push_back(index_arg); + + // Define the first variant of this arg pair. + old_prefix_arg.arg_type = eArgTypeOldPathPrefix; + old_prefix_arg.arg_repetition = eArgRepeatPairPlus; + + // Define the first variant of this arg pair. + new_prefix_arg.arg_type = eArgTypeNewPathPrefix; + new_prefix_arg.arg_repetition = eArgRepeatPairPlus; + + // There are two required arguments that must always occur together, i.e. + // an argument "pair". Because they must always occur together, they are + // treated as two variants of one argument rather than two independent + // arguments. Push them both into the same argument position for + // m_arguments... + + arg2.push_back(old_prefix_arg); + arg2.push_back(new_prefix_arg); + + // Add arguments to m_arguments. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + } + + ~CommandObjectTargetModulesSearchPathsInsert() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + size_t argc = command.GetArgumentCount(); + // check for at least 3 arguments and an odd number of parameters + if (argc >= 3 && argc & 1) { + bool success = false; + + uint32_t insert_idx = StringConvert::ToUInt32( + command.GetArgumentAtIndex(0), UINT32_MAX, 0, &success); + + if (!success) { + result.AppendErrorWithFormat( + "<index> parameter is not an integer: '%s'.\n", + command.GetArgumentAtIndex(0)); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + // shift off the index + command.Shift(); + argc = command.GetArgumentCount(); + + for (uint32_t i = 0; i < argc; i += 2, ++insert_idx) { + const char *from = command.GetArgumentAtIndex(i); + const char *to = command.GetArgumentAtIndex(i + 1); + + if (from[0] && to[0]) { + bool last_pair = ((argc - i) == 2); + target->GetImageSearchPathList().Insert( + ConstString(from), ConstString(to), insert_idx, last_pair); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + if (from[0]) + result.AppendError("<path-prefix> can't be empty\n"); + else + result.AppendError("<new-path-prefix> can't be empty\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } else { + result.AppendError("insert requires at least three arguments\n"); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetModulesSearchPathsList + +class CommandObjectTargetModulesSearchPathsList : public CommandObjectParsed { +public: + CommandObjectTargetModulesSearchPathsList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target modules search-paths list", + "List all current image search path substitution " + "pairs in the current target.", + "target modules search-paths list", + eCommandRequiresTarget) {} + + ~CommandObjectTargetModulesSearchPathsList() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + if (command.GetArgumentCount() != 0) { + result.AppendError("list takes no arguments\n"); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + target->GetImageSearchPathList().Dump(&result.GetOutputStream()); + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetModulesSearchPathsQuery + +class CommandObjectTargetModulesSearchPathsQuery : public CommandObjectParsed { +public: + CommandObjectTargetModulesSearchPathsQuery(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target modules search-paths query", + "Transform a path using the first applicable image search path.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg; + CommandArgumentData path_arg; + + // Define the first (and only) variant of this arg. + path_arg.arg_type = eArgTypeDirectoryName; + path_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(path_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectTargetModulesSearchPathsQuery() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + if (command.GetArgumentCount() != 1) { + result.AppendError("query requires one argument\n"); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + ConstString orig(command.GetArgumentAtIndex(0)); + ConstString transformed; + if (target->GetImageSearchPathList().RemapPath(orig, transformed)) + result.GetOutputStream().Printf("%s\n", transformed.GetCString()); + else + result.GetOutputStream().Printf("%s\n", orig.GetCString()); + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +// Static Helper functions +static void DumpModuleArchitecture(Stream &strm, Module *module, + bool full_triple, uint32_t width) { + if (module) { + StreamString arch_strm; + + if (full_triple) + module->GetArchitecture().DumpTriple(arch_strm); + else + arch_strm.PutCString(module->GetArchitecture().GetArchitectureName()); + std::string arch_str = arch_strm.GetString(); + + if (width) + strm.Printf("%-*s", width, arch_str.c_str()); + else + strm.PutCString(arch_str); + } +} + +static void DumpModuleUUID(Stream &strm, Module *module) { + if (module && module->GetUUID().IsValid()) + module->GetUUID().Dump(&strm); + else + strm.PutCString(" "); +} + +static uint32_t DumpCompileUnitLineTable(CommandInterpreter &interpreter, + Stream &strm, Module *module, + const FileSpec &file_spec, + lldb::DescriptionLevel desc_level) { + uint32_t num_matches = 0; + if (module) { + SymbolContextList sc_list; + num_matches = module->ResolveSymbolContextsForFileSpec( + file_spec, 0, false, eSymbolContextCompUnit, sc_list); + + for (uint32_t i = 0; i < num_matches; ++i) { + SymbolContext sc; + if (sc_list.GetContextAtIndex(i, sc)) { + if (i > 0) + strm << "\n\n"; + + strm << "Line table for " << *static_cast<FileSpec *>(sc.comp_unit) + << " in `" << module->GetFileSpec().GetFilename() << "\n"; + LineTable *line_table = sc.comp_unit->GetLineTable(); + if (line_table) + line_table->GetDescription( + &strm, interpreter.GetExecutionContext().GetTargetPtr(), + desc_level); + else + strm << "No line table"; + } + } + } + return num_matches; +} + +static void DumpFullpath(Stream &strm, const FileSpec *file_spec_ptr, + uint32_t width) { + if (file_spec_ptr) { + if (width > 0) { + std::string fullpath = file_spec_ptr->GetPath(); + strm.Printf("%-*s", width, fullpath.c_str()); + return; + } else { + file_spec_ptr->Dump(&strm); + return; + } + } + // Keep the width spacing correct if things go wrong... + if (width > 0) + strm.Printf("%-*s", width, ""); +} + +static void DumpDirectory(Stream &strm, const FileSpec *file_spec_ptr, + uint32_t width) { + if (file_spec_ptr) { + if (width > 0) + strm.Printf("%-*s", width, file_spec_ptr->GetDirectory().AsCString("")); + else + file_spec_ptr->GetDirectory().Dump(&strm); + return; + } + // Keep the width spacing correct if things go wrong... + if (width > 0) + strm.Printf("%-*s", width, ""); +} + +static void DumpBasename(Stream &strm, const FileSpec *file_spec_ptr, + uint32_t width) { + if (file_spec_ptr) { + if (width > 0) + strm.Printf("%-*s", width, file_spec_ptr->GetFilename().AsCString("")); + else + file_spec_ptr->GetFilename().Dump(&strm); + return; + } + // Keep the width spacing correct if things go wrong... + if (width > 0) + strm.Printf("%-*s", width, ""); +} + +static size_t DumpModuleObjfileHeaders(Stream &strm, ModuleList &module_list) { + size_t num_dumped = 0; + std::lock_guard<std::recursive_mutex> guard(module_list.GetMutex()); + const size_t num_modules = module_list.GetSize(); + if (num_modules > 0) { + strm.Printf("Dumping headers for %" PRIu64 " module(s).\n", + static_cast<uint64_t>(num_modules)); + strm.IndentMore(); + for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { + Module *module = module_list.GetModulePointerAtIndexUnlocked(image_idx); + if (module) { + if (num_dumped++ > 0) { + strm.EOL(); + strm.EOL(); + } + ObjectFile *objfile = module->GetObjectFile(); + if (objfile) + objfile->Dump(&strm); + else { + strm.Format("No object file for module: {0:F}\n", + module->GetFileSpec()); + } + } + } + strm.IndentLess(); + } + return num_dumped; +} + +static void DumpModuleSymtab(CommandInterpreter &interpreter, Stream &strm, + Module *module, SortOrder sort_order) { + if (!module) + return; + if (Symtab *symtab = module->GetSymtab()) + symtab->Dump(&strm, interpreter.GetExecutionContext().GetTargetPtr(), + sort_order); +} + +static void DumpModuleSections(CommandInterpreter &interpreter, Stream &strm, + Module *module) { + if (module) { + SectionList *section_list = module->GetSectionList(); + if (section_list) { + strm.Printf("Sections for '%s' (%s):\n", + module->GetSpecificationDescription().c_str(), + module->GetArchitecture().GetArchitectureName()); + strm.IndentMore(); + section_list->Dump(&strm, + interpreter.GetExecutionContext().GetTargetPtr(), true, + UINT32_MAX); + strm.IndentLess(); + } + } +} + +static bool DumpModuleSymbolFile(Stream &strm, Module *module) { + if (module) { + if (SymbolFile *symbol_file = module->GetSymbolFile(true)) { + symbol_file->Dump(strm); + return true; + } + } + return false; +} + +static void DumpAddress(ExecutionContextScope *exe_scope, + const Address &so_addr, bool verbose, Stream &strm) { + strm.IndentMore(); + strm.Indent(" Address: "); + so_addr.Dump(&strm, exe_scope, Address::DumpStyleModuleWithFileAddress); + strm.PutCString(" ("); + so_addr.Dump(&strm, exe_scope, Address::DumpStyleSectionNameOffset); + strm.PutCString(")\n"); + strm.Indent(" Summary: "); + const uint32_t save_indent = strm.GetIndentLevel(); + strm.SetIndentLevel(save_indent + 13); + so_addr.Dump(&strm, exe_scope, Address::DumpStyleResolvedDescription); + strm.SetIndentLevel(save_indent); + // Print out detailed address information when verbose is enabled + if (verbose) { + strm.EOL(); + so_addr.Dump(&strm, exe_scope, Address::DumpStyleDetailedSymbolContext); + } + strm.IndentLess(); +} + +static bool LookupAddressInModule(CommandInterpreter &interpreter, Stream &strm, + Module *module, uint32_t resolve_mask, + lldb::addr_t raw_addr, lldb::addr_t offset, + bool verbose) { + if (module) { + lldb::addr_t addr = raw_addr - offset; + Address so_addr; + SymbolContext sc; + Target *target = interpreter.GetExecutionContext().GetTargetPtr(); + if (target && !target->GetSectionLoadList().IsEmpty()) { + if (!target->GetSectionLoadList().ResolveLoadAddress(addr, so_addr)) + return false; + else if (so_addr.GetModule().get() != module) + return false; + } else { + if (!module->ResolveFileAddress(addr, so_addr)) + return false; + } + + ExecutionContextScope *exe_scope = + interpreter.GetExecutionContext().GetBestExecutionContextScope(); + DumpAddress(exe_scope, so_addr, verbose, strm); + // strm.IndentMore(); + // strm.Indent (" Address: "); + // so_addr.Dump (&strm, exe_scope, + // Address::DumpStyleModuleWithFileAddress); + // strm.PutCString (" ("); + // so_addr.Dump (&strm, exe_scope, + // Address::DumpStyleSectionNameOffset); + // strm.PutCString (")\n"); + // strm.Indent (" Summary: "); + // const uint32_t save_indent = strm.GetIndentLevel (); + // strm.SetIndentLevel (save_indent + 13); + // so_addr.Dump (&strm, exe_scope, + // Address::DumpStyleResolvedDescription); + // strm.SetIndentLevel (save_indent); + // // Print out detailed address information when verbose is enabled + // if (verbose) + // { + // strm.EOL(); + // so_addr.Dump (&strm, exe_scope, + // Address::DumpStyleDetailedSymbolContext); + // } + // strm.IndentLess(); + return true; + } + + return false; +} + +static uint32_t LookupSymbolInModule(CommandInterpreter &interpreter, + Stream &strm, Module *module, + const char *name, bool name_is_regex, + bool verbose) { + if (!module) + return 0; + + Symtab *symtab = module->GetSymtab(); + if (!symtab) + return 0; + + SymbolContext sc; + std::vector<uint32_t> match_indexes; + ConstString symbol_name(name); + uint32_t num_matches = 0; + if (name_is_regex) { + RegularExpression name_regexp(symbol_name.GetStringRef()); + num_matches = symtab->AppendSymbolIndexesMatchingRegExAndType( + name_regexp, eSymbolTypeAny, match_indexes); + } else { + num_matches = + symtab->AppendSymbolIndexesWithName(symbol_name, match_indexes); + } + + if (num_matches > 0) { + strm.Indent(); + strm.Printf("%u symbols match %s'%s' in ", num_matches, + name_is_regex ? "the regular expression " : "", name); + DumpFullpath(strm, &module->GetFileSpec(), 0); + strm.PutCString(":\n"); + strm.IndentMore(); + for (uint32_t i = 0; i < num_matches; ++i) { + Symbol *symbol = symtab->SymbolAtIndex(match_indexes[i]); + if (symbol && symbol->ValueIsAddress()) { + DumpAddress( + interpreter.GetExecutionContext().GetBestExecutionContextScope(), + symbol->GetAddressRef(), verbose, strm); + } + } + strm.IndentLess(); + } + return num_matches; +} + +static void DumpSymbolContextList(ExecutionContextScope *exe_scope, + Stream &strm, SymbolContextList &sc_list, + bool verbose) { + strm.IndentMore(); + + const uint32_t num_matches = sc_list.GetSize(); + + for (uint32_t i = 0; i < num_matches; ++i) { + SymbolContext sc; + if (sc_list.GetContextAtIndex(i, sc)) { + AddressRange range; + + sc.GetAddressRange(eSymbolContextEverything, 0, true, range); + + DumpAddress(exe_scope, range.GetBaseAddress(), verbose, strm); + } + } + strm.IndentLess(); +} + +static size_t LookupFunctionInModule(CommandInterpreter &interpreter, + Stream &strm, Module *module, + const char *name, bool name_is_regex, + bool include_inlines, bool include_symbols, + bool verbose) { + if (module && name && name[0]) { + SymbolContextList sc_list; + size_t num_matches = 0; + if (name_is_regex) { + RegularExpression function_name_regex((llvm::StringRef(name))); + module->FindFunctions(function_name_regex, include_symbols, + include_inlines, sc_list); + } else { + ConstString function_name(name); + module->FindFunctions(function_name, nullptr, eFunctionNameTypeAuto, + include_symbols, include_inlines, sc_list); + } + num_matches = sc_list.GetSize(); + if (num_matches) { + strm.Indent(); + strm.Printf("%" PRIu64 " match%s found in ", (uint64_t)num_matches, + num_matches > 1 ? "es" : ""); + DumpFullpath(strm, &module->GetFileSpec(), 0); + strm.PutCString(":\n"); + DumpSymbolContextList( + interpreter.GetExecutionContext().GetBestExecutionContextScope(), + strm, sc_list, verbose); + } + return num_matches; + } + return 0; +} + +static size_t LookupTypeInModule(CommandInterpreter &interpreter, Stream &strm, + Module *module, const char *name_cstr, + bool name_is_regex) { + TypeList type_list; + if (module && name_cstr && name_cstr[0]) { + const uint32_t max_num_matches = UINT32_MAX; + size_t num_matches = 0; + bool name_is_fully_qualified = false; + + ConstString name(name_cstr); + llvm::DenseSet<lldb_private::SymbolFile *> searched_symbol_files; + module->FindTypes(name, name_is_fully_qualified, max_num_matches, + searched_symbol_files, type_list); + + if (type_list.Empty()) + return 0; + + strm.Indent(); + strm.Printf("%" PRIu64 " match%s found in ", (uint64_t)num_matches, + num_matches > 1 ? "es" : ""); + DumpFullpath(strm, &module->GetFileSpec(), 0); + strm.PutCString(":\n"); + for (TypeSP type_sp : type_list.Types()) { + if (!type_sp) + continue; + // Resolve the clang type so that any forward references to types + // that haven't yet been parsed will get parsed. + type_sp->GetFullCompilerType(); + type_sp->GetDescription(&strm, eDescriptionLevelFull, true); + // Print all typedef chains + TypeSP typedef_type_sp(type_sp); + TypeSP typedefed_type_sp(typedef_type_sp->GetTypedefType()); + while (typedefed_type_sp) { + strm.EOL(); + strm.Printf(" typedef '%s': ", + typedef_type_sp->GetName().GetCString()); + typedefed_type_sp->GetFullCompilerType(); + typedefed_type_sp->GetDescription(&strm, eDescriptionLevelFull, true); + typedef_type_sp = typedefed_type_sp; + typedefed_type_sp = typedef_type_sp->GetTypedefType(); + } + } + strm.EOL(); + } + return type_list.GetSize(); +} + +static size_t LookupTypeHere(CommandInterpreter &interpreter, Stream &strm, + Module &module, const char *name_cstr, + bool name_is_regex) { + TypeList type_list; + const uint32_t max_num_matches = UINT32_MAX; + bool name_is_fully_qualified = false; + + ConstString name(name_cstr); + llvm::DenseSet<SymbolFile *> searched_symbol_files; + module.FindTypes(name, name_is_fully_qualified, max_num_matches, + searched_symbol_files, type_list); + + if (type_list.Empty()) + return 0; + + strm.Indent(); + strm.PutCString("Best match found in "); + DumpFullpath(strm, &module.GetFileSpec(), 0); + strm.PutCString(":\n"); + + TypeSP type_sp(type_list.GetTypeAtIndex(0)); + if (type_sp) { + // Resolve the clang type so that any forward references to types that + // haven't yet been parsed will get parsed. + type_sp->GetFullCompilerType(); + type_sp->GetDescription(&strm, eDescriptionLevelFull, true); + // Print all typedef chains + TypeSP typedef_type_sp(type_sp); + TypeSP typedefed_type_sp(typedef_type_sp->GetTypedefType()); + while (typedefed_type_sp) { + strm.EOL(); + strm.Printf(" typedef '%s': ", + typedef_type_sp->GetName().GetCString()); + typedefed_type_sp->GetFullCompilerType(); + typedefed_type_sp->GetDescription(&strm, eDescriptionLevelFull, true); + typedef_type_sp = typedefed_type_sp; + typedefed_type_sp = typedef_type_sp->GetTypedefType(); + } + } + strm.EOL(); + return type_list.GetSize(); +} + +static uint32_t LookupFileAndLineInModule(CommandInterpreter &interpreter, + Stream &strm, Module *module, + const FileSpec &file_spec, + uint32_t line, bool check_inlines, + bool verbose) { + if (module && file_spec) { + SymbolContextList sc_list; + const uint32_t num_matches = module->ResolveSymbolContextsForFileSpec( + file_spec, line, check_inlines, eSymbolContextEverything, sc_list); + if (num_matches > 0) { + strm.Indent(); + strm.Printf("%u match%s found in ", num_matches, + num_matches > 1 ? "es" : ""); + strm << file_spec; + if (line > 0) + strm.Printf(":%u", line); + strm << " in "; + DumpFullpath(strm, &module->GetFileSpec(), 0); + strm.PutCString(":\n"); + DumpSymbolContextList( + interpreter.GetExecutionContext().GetBestExecutionContextScope(), + strm, sc_list, verbose); + return num_matches; + } + } + return 0; +} + +static size_t FindModulesByName(Target *target, const char *module_name, + ModuleList &module_list, + bool check_global_list) { + FileSpec module_file_spec(module_name); + ModuleSpec module_spec(module_file_spec); + + const size_t initial_size = module_list.GetSize(); + + if (check_global_list) { + // Check the global list + std::lock_guard<std::recursive_mutex> guard( + Module::GetAllocationModuleCollectionMutex()); + const size_t num_modules = Module::GetNumberAllocatedModules(); + ModuleSP module_sp; + for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { + Module *module = Module::GetAllocatedModuleAtIndex(image_idx); + + if (module) { + if (module->MatchesModuleSpec(module_spec)) { + module_sp = module->shared_from_this(); + module_list.AppendIfNeeded(module_sp); + } + } + } + } else { + if (target) { + target->GetImages().FindModules(module_spec, module_list); + const size_t num_matches = module_list.GetSize(); + + // Not found in our module list for our target, check the main shared + // module list in case it is a extra file used somewhere else + if (num_matches == 0) { + module_spec.GetArchitecture() = target->GetArchitecture(); + ModuleList::FindSharedModules(module_spec, module_list); + } + } else { + ModuleList::FindSharedModules(module_spec, module_list); + } + } + + return module_list.GetSize() - initial_size; +} + +#pragma mark CommandObjectTargetModulesModuleAutoComplete + +// A base command object class that can auto complete with module file +// paths + +class CommandObjectTargetModulesModuleAutoComplete + : public CommandObjectParsed { +public: + CommandObjectTargetModulesModuleAutoComplete(CommandInterpreter &interpreter, + const char *name, + const char *help, + const char *syntax, + uint32_t flags = 0) + : CommandObjectParsed(interpreter, name, help, syntax, flags) { + CommandArgumentEntry arg; + CommandArgumentData file_arg; + + // Define the first (and only) variant of this arg. + file_arg.arg_type = eArgTypeFilename; + file_arg.arg_repetition = eArgRepeatStar; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(file_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectTargetModulesModuleAutoComplete() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eModuleCompletion, request, + nullptr); + } +}; + +#pragma mark CommandObjectTargetModulesSourceFileAutoComplete + +// A base command object class that can auto complete with module source +// file paths + +class CommandObjectTargetModulesSourceFileAutoComplete + : public CommandObjectParsed { +public: + CommandObjectTargetModulesSourceFileAutoComplete( + CommandInterpreter &interpreter, const char *name, const char *help, + const char *syntax, uint32_t flags) + : CommandObjectParsed(interpreter, name, help, syntax, flags) { + CommandArgumentEntry arg; + CommandArgumentData source_file_arg; + + // Define the first (and only) variant of this arg. + source_file_arg.arg_type = eArgTypeSourceFile; + source_file_arg.arg_repetition = eArgRepeatPlus; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(source_file_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectTargetModulesSourceFileAutoComplete() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eSourceFileCompletion, + request, nullptr); + } +}; + +#pragma mark CommandObjectTargetModulesDumpObjfile + +class CommandObjectTargetModulesDumpObjfile + : public CommandObjectTargetModulesModuleAutoComplete { +public: + CommandObjectTargetModulesDumpObjfile(CommandInterpreter &interpreter) + : CommandObjectTargetModulesModuleAutoComplete( + interpreter, "target modules dump objfile", + "Dump the object file headers from one or more target modules.", + nullptr, eCommandRequiresTarget) {} + + ~CommandObjectTargetModulesDumpObjfile() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + + uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + + size_t num_dumped = 0; + if (command.GetArgumentCount() == 0) { + // Dump all headers for all modules images + num_dumped = DumpModuleObjfileHeaders(result.GetOutputStream(), + target->GetImages()); + if (num_dumped == 0) { + result.AppendError("the target has no associated executable images"); + result.SetStatus(eReturnStatusFailed); + } + } else { + // Find the modules that match the basename or full path. + ModuleList module_list; + const char *arg_cstr; + for (int arg_idx = 0; + (arg_cstr = command.GetArgumentAtIndex(arg_idx)) != nullptr; + ++arg_idx) { + size_t num_matched = + FindModulesByName(target, arg_cstr, module_list, true); + if (num_matched == 0) { + result.AppendWarningWithFormat( + "Unable to find an image that matches '%s'.\n", arg_cstr); + } + } + // Dump all the modules we found. + num_dumped = + DumpModuleObjfileHeaders(result.GetOutputStream(), module_list); + } + + if (num_dumped > 0) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("no matching executable images found"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetModulesDumpSymtab + +static constexpr OptionEnumValueElement g_sort_option_enumeration[] = { + { + eSortOrderNone, + "none", + "No sorting, use the original symbol table order.", + }, + { + eSortOrderByAddress, + "address", + "Sort output by symbol address.", + }, + { + eSortOrderByName, + "name", + "Sort output by symbol name.", + }, +}; + +#define LLDB_OPTIONS_target_modules_dump_symtab +#include "CommandOptions.inc" + +class CommandObjectTargetModulesDumpSymtab + : public CommandObjectTargetModulesModuleAutoComplete { +public: + CommandObjectTargetModulesDumpSymtab(CommandInterpreter &interpreter) + : CommandObjectTargetModulesModuleAutoComplete( + interpreter, "target modules dump symtab", + "Dump the symbol table from one or more target modules.", nullptr, + eCommandRequiresTarget), + m_options() {} + + ~CommandObjectTargetModulesDumpSymtab() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_sort_order(eSortOrderNone) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 's': + m_sort_order = (SortOrder)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, + eSortOrderNone, error); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_sort_order = eSortOrderNone; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_target_modules_dump_symtab_options); + } + + SortOrder m_sort_order; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + uint32_t num_dumped = 0; + + uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + + if (command.GetArgumentCount() == 0) { + // Dump all sections for all modules images + std::lock_guard<std::recursive_mutex> guard( + target->GetImages().GetMutex()); + const size_t num_modules = target->GetImages().GetSize(); + if (num_modules > 0) { + result.GetOutputStream().Printf("Dumping symbol table for %" PRIu64 + " modules.\n", + (uint64_t)num_modules); + for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { + if (num_dumped > 0) { + result.GetOutputStream().EOL(); + result.GetOutputStream().EOL(); + } + if (m_interpreter.WasInterrupted()) + break; + num_dumped++; + DumpModuleSymtab( + m_interpreter, result.GetOutputStream(), + target->GetImages().GetModulePointerAtIndexUnlocked(image_idx), + m_options.m_sort_order); + } + } else { + result.AppendError("the target has no associated executable images"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + // Dump specified images (by basename or fullpath) + const char *arg_cstr; + for (int arg_idx = 0; + (arg_cstr = command.GetArgumentAtIndex(arg_idx)) != nullptr; + ++arg_idx) { + ModuleList module_list; + const size_t num_matches = + FindModulesByName(target, arg_cstr, module_list, true); + if (num_matches > 0) { + for (size_t i = 0; i < num_matches; ++i) { + Module *module = module_list.GetModulePointerAtIndex(i); + if (module) { + if (num_dumped > 0) { + result.GetOutputStream().EOL(); + result.GetOutputStream().EOL(); + } + if (m_interpreter.WasInterrupted()) + break; + num_dumped++; + DumpModuleSymtab(m_interpreter, result.GetOutputStream(), module, + m_options.m_sort_order); + } + } + } else + result.AppendWarningWithFormat( + "Unable to find an image that matches '%s'.\n", arg_cstr); + } + } + + if (num_dumped > 0) + result.SetStatus(eReturnStatusSuccessFinishResult); + else { + result.AppendError("no matching executable images found"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +#pragma mark CommandObjectTargetModulesDumpSections + +// Image section dumping command + +class CommandObjectTargetModulesDumpSections + : public CommandObjectTargetModulesModuleAutoComplete { +public: + CommandObjectTargetModulesDumpSections(CommandInterpreter &interpreter) + : CommandObjectTargetModulesModuleAutoComplete( + interpreter, "target modules dump sections", + "Dump the sections from one or more target modules.", + //"target modules dump sections [<file1> ...]") + nullptr, eCommandRequiresTarget) {} + + ~CommandObjectTargetModulesDumpSections() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + uint32_t num_dumped = 0; + + uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + + if (command.GetArgumentCount() == 0) { + // Dump all sections for all modules images + const size_t num_modules = target->GetImages().GetSize(); + if (num_modules > 0) { + result.GetOutputStream().Printf("Dumping sections for %" PRIu64 + " modules.\n", + (uint64_t)num_modules); + for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { + if (m_interpreter.WasInterrupted()) + break; + num_dumped++; + DumpModuleSections( + m_interpreter, result.GetOutputStream(), + target->GetImages().GetModulePointerAtIndex(image_idx)); + } + } else { + result.AppendError("the target has no associated executable images"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + // Dump specified images (by basename or fullpath) + const char *arg_cstr; + for (int arg_idx = 0; + (arg_cstr = command.GetArgumentAtIndex(arg_idx)) != nullptr; + ++arg_idx) { + ModuleList module_list; + const size_t num_matches = + FindModulesByName(target, arg_cstr, module_list, true); + if (num_matches > 0) { + for (size_t i = 0; i < num_matches; ++i) { + if (m_interpreter.WasInterrupted()) + break; + Module *module = module_list.GetModulePointerAtIndex(i); + if (module) { + num_dumped++; + DumpModuleSections(m_interpreter, result.GetOutputStream(), + module); + } + } + } else { + // Check the global list + std::lock_guard<std::recursive_mutex> guard( + Module::GetAllocationModuleCollectionMutex()); + + result.AppendWarningWithFormat( + "Unable to find an image that matches '%s'.\n", arg_cstr); + } + } + } + + if (num_dumped > 0) + result.SetStatus(eReturnStatusSuccessFinishResult); + else { + result.AppendError("no matching executable images found"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetModulesDumpSections + +// Clang AST dumping command + +class CommandObjectTargetModulesDumpClangAST + : public CommandObjectTargetModulesModuleAutoComplete { +public: + CommandObjectTargetModulesDumpClangAST(CommandInterpreter &interpreter) + : CommandObjectTargetModulesModuleAutoComplete( + interpreter, "target modules dump ast", + "Dump the clang ast for a given module's symbol file.", + //"target modules dump ast [<file1> ...]") + nullptr, eCommandRequiresTarget) {} + + ~CommandObjectTargetModulesDumpClangAST() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + + const size_t num_modules = target->GetImages().GetSize(); + if (num_modules == 0) { + result.AppendError("the target has no associated executable images"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + // Dump all ASTs for all modules images + result.GetOutputStream().Printf("Dumping clang ast for %" PRIu64 + " modules.\n", + (uint64_t)num_modules); + for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { + if (m_interpreter.WasInterrupted()) + break; + Module *m = target->GetImages().GetModulePointerAtIndex(image_idx); + if (SymbolFile *sf = m->GetSymbolFile()) + sf->DumpClangAST(result.GetOutputStream()); + } + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + + // Dump specified ASTs (by basename or fullpath) + for (const Args::ArgEntry &arg : command.entries()) { + ModuleList module_list; + const size_t num_matches = + FindModulesByName(target, arg.c_str(), module_list, true); + if (num_matches == 0) { + // Check the global list + std::lock_guard<std::recursive_mutex> guard( + Module::GetAllocationModuleCollectionMutex()); + + result.AppendWarningWithFormat( + "Unable to find an image that matches '%s'.\n", arg.c_str()); + continue; + } + + for (size_t i = 0; i < num_matches; ++i) { + if (m_interpreter.WasInterrupted()) + break; + Module *m = module_list.GetModulePointerAtIndex(i); + if (SymbolFile *sf = m->GetSymbolFile()) + sf->DumpClangAST(result.GetOutputStream()); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } +}; + +#pragma mark CommandObjectTargetModulesDumpSymfile + +// Image debug symbol dumping command + +class CommandObjectTargetModulesDumpSymfile + : public CommandObjectTargetModulesModuleAutoComplete { +public: + CommandObjectTargetModulesDumpSymfile(CommandInterpreter &interpreter) + : CommandObjectTargetModulesModuleAutoComplete( + interpreter, "target modules dump symfile", + "Dump the debug symbol file for one or more target modules.", + //"target modules dump symfile [<file1> ...]") + nullptr, eCommandRequiresTarget) {} + + ~CommandObjectTargetModulesDumpSymfile() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + uint32_t num_dumped = 0; + + uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + + if (command.GetArgumentCount() == 0) { + // Dump all sections for all modules images + const ModuleList &target_modules = target->GetImages(); + std::lock_guard<std::recursive_mutex> guard(target_modules.GetMutex()); + const size_t num_modules = target_modules.GetSize(); + if (num_modules > 0) { + result.GetOutputStream().Printf("Dumping debug symbols for %" PRIu64 + " modules.\n", + (uint64_t)num_modules); + for (uint32_t image_idx = 0; image_idx < num_modules; ++image_idx) { + if (m_interpreter.WasInterrupted()) + break; + if (DumpModuleSymbolFile( + result.GetOutputStream(), + target_modules.GetModulePointerAtIndexUnlocked(image_idx))) + num_dumped++; + } + } else { + result.AppendError("the target has no associated executable images"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + // Dump specified images (by basename or fullpath) + const char *arg_cstr; + for (int arg_idx = 0; + (arg_cstr = command.GetArgumentAtIndex(arg_idx)) != nullptr; + ++arg_idx) { + ModuleList module_list; + const size_t num_matches = + FindModulesByName(target, arg_cstr, module_list, true); + if (num_matches > 0) { + for (size_t i = 0; i < num_matches; ++i) { + if (m_interpreter.WasInterrupted()) + break; + Module *module = module_list.GetModulePointerAtIndex(i); + if (module) { + if (DumpModuleSymbolFile(result.GetOutputStream(), module)) + num_dumped++; + } + } + } else + result.AppendWarningWithFormat( + "Unable to find an image that matches '%s'.\n", arg_cstr); + } + } + + if (num_dumped > 0) + result.SetStatus(eReturnStatusSuccessFinishResult); + else { + result.AppendError("no matching executable images found"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetModulesDumpLineTable +#define LLDB_OPTIONS_target_modules_dump +#include "CommandOptions.inc" + +// Image debug line table dumping command + +class CommandObjectTargetModulesDumpLineTable + : public CommandObjectTargetModulesSourceFileAutoComplete { +public: + CommandObjectTargetModulesDumpLineTable(CommandInterpreter &interpreter) + : CommandObjectTargetModulesSourceFileAutoComplete( + interpreter, "target modules dump line-table", + "Dump the line table for one or more compilation units.", nullptr, + eCommandRequiresTarget) {} + + ~CommandObjectTargetModulesDumpLineTable() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + uint32_t total_num_dumped = 0; + + uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + + if (command.GetArgumentCount() == 0) { + result.AppendError("file option must be specified."); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } else { + // Dump specified images (by basename or fullpath) + const char *arg_cstr; + for (int arg_idx = 0; + (arg_cstr = command.GetArgumentAtIndex(arg_idx)) != nullptr; + ++arg_idx) { + FileSpec file_spec(arg_cstr); + + const ModuleList &target_modules = target->GetImages(); + std::lock_guard<std::recursive_mutex> guard(target_modules.GetMutex()); + const size_t num_modules = target_modules.GetSize(); + if (num_modules > 0) { + uint32_t num_dumped = 0; + for (uint32_t i = 0; i < num_modules; ++i) { + if (m_interpreter.WasInterrupted()) + break; + if (DumpCompileUnitLineTable( + m_interpreter, result.GetOutputStream(), + target_modules.GetModulePointerAtIndexUnlocked(i), + file_spec, + m_options.m_verbose ? eDescriptionLevelFull + : eDescriptionLevelBrief)) + num_dumped++; + } + if (num_dumped == 0) + result.AppendWarningWithFormat( + "No source filenames matched '%s'.\n", arg_cstr); + else + total_num_dumped += num_dumped; + } + } + } + + if (total_num_dumped > 0) + result.SetStatus(eReturnStatusSuccessFinishResult); + else { + result.AppendError("no source filenames matched any command arguments"); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + assert(option_idx == 0 && "We only have one option."); + m_verbose = true; + + return Status(); + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_verbose = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_target_modules_dump_options); + } + + bool m_verbose; + }; + + CommandOptions m_options; +}; + +#pragma mark CommandObjectTargetModulesDump + +// Dump multi-word command for target modules + +class CommandObjectTargetModulesDump : public CommandObjectMultiword { +public: + // Constructors and Destructors + CommandObjectTargetModulesDump(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "target modules dump", + "Commands for dumping information about one or " + "more target modules.", + "target modules dump " + "[headers|symtab|sections|ast|symfile|line-table] " + "[<file1> <file2> ...]") { + LoadSubCommand("objfile", + CommandObjectSP( + new CommandObjectTargetModulesDumpObjfile(interpreter))); + LoadSubCommand( + "symtab", + CommandObjectSP(new CommandObjectTargetModulesDumpSymtab(interpreter))); + LoadSubCommand("sections", + CommandObjectSP(new CommandObjectTargetModulesDumpSections( + interpreter))); + LoadSubCommand("symfile", + CommandObjectSP( + new CommandObjectTargetModulesDumpSymfile(interpreter))); + LoadSubCommand( + "ast", CommandObjectSP( + new CommandObjectTargetModulesDumpClangAST(interpreter))); + LoadSubCommand("line-table", + CommandObjectSP(new CommandObjectTargetModulesDumpLineTable( + interpreter))); + } + + ~CommandObjectTargetModulesDump() override = default; +}; + +class CommandObjectTargetModulesAdd : public CommandObjectParsed { +public: + CommandObjectTargetModulesAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target modules add", + "Add a new module to the current target's modules.", + "target modules add [<module>]", + eCommandRequiresTarget), + m_option_group(), m_symbol_file(LLDB_OPT_SET_1, false, "symfile", 's', + 0, eArgTypeFilename, + "Fullpath to a stand alone debug " + "symbols file for when debug symbols " + "are not in the executable.") { + m_option_group.Append(&m_uuid_option_group, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1); + m_option_group.Append(&m_symbol_file, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectTargetModulesAdd() override = default; + + Options *GetOptions() override { return &m_option_group; } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, + request, nullptr); + } + +protected: + OptionGroupOptions m_option_group; + OptionGroupUUID m_uuid_option_group; + OptionGroupFile m_symbol_file; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + bool flush = false; + + const size_t argc = args.GetArgumentCount(); + if (argc == 0) { + if (m_uuid_option_group.GetOptionValue().OptionWasSet()) { + // We are given a UUID only, go locate the file + ModuleSpec module_spec; + module_spec.GetUUID() = + m_uuid_option_group.GetOptionValue().GetCurrentValue(); + if (m_symbol_file.GetOptionValue().OptionWasSet()) + module_spec.GetSymbolFileSpec() = + m_symbol_file.GetOptionValue().GetCurrentValue(); + if (Symbols::DownloadObjectAndSymbolFile(module_spec)) { + ModuleSP module_sp( + target->GetOrCreateModule(module_spec, true /* notify */)); + if (module_sp) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } else { + StreamString strm; + module_spec.GetUUID().Dump(&strm); + if (module_spec.GetFileSpec()) { + if (module_spec.GetSymbolFileSpec()) { + result.AppendErrorWithFormat( + "Unable to create the executable or symbol file with " + "UUID %s with path %s and symbol file %s", + strm.GetData(), module_spec.GetFileSpec().GetPath().c_str(), + module_spec.GetSymbolFileSpec().GetPath().c_str()); + } else { + result.AppendErrorWithFormat( + "Unable to create the executable or symbol file with " + "UUID %s with path %s", + strm.GetData(), + module_spec.GetFileSpec().GetPath().c_str()); + } + } else { + result.AppendErrorWithFormat("Unable to create the executable " + "or symbol file with UUID %s", + strm.GetData()); + } + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + StreamString strm; + module_spec.GetUUID().Dump(&strm); + result.AppendErrorWithFormat( + "Unable to locate the executable or symbol file with UUID %s", + strm.GetData()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + result.AppendError( + "one or more executable image paths must be specified"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + for (auto &entry : args.entries()) { + if (entry.ref().empty()) + continue; + + FileSpec file_spec(entry.ref()); + if (FileSystem::Instance().Exists(file_spec)) { + ModuleSpec module_spec(file_spec); + if (m_uuid_option_group.GetOptionValue().OptionWasSet()) + module_spec.GetUUID() = + m_uuid_option_group.GetOptionValue().GetCurrentValue(); + if (m_symbol_file.GetOptionValue().OptionWasSet()) + module_spec.GetSymbolFileSpec() = + m_symbol_file.GetOptionValue().GetCurrentValue(); + if (!module_spec.GetArchitecture().IsValid()) + module_spec.GetArchitecture() = target->GetArchitecture(); + Status error; + ModuleSP module_sp(target->GetOrCreateModule( + module_spec, true /* notify */, &error)); + if (!module_sp) { + const char *error_cstr = error.AsCString(); + if (error_cstr) + result.AppendError(error_cstr); + else + result.AppendErrorWithFormat("unsupported module: %s", + entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + flush = true; + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + std::string resolved_path = file_spec.GetPath(); + result.SetStatus(eReturnStatusFailed); + if (resolved_path != entry.ref()) { + result.AppendErrorWithFormat( + "invalid module path '%s' with resolved path '%s'\n", + entry.ref().str().c_str(), resolved_path.c_str()); + break; + } + result.AppendErrorWithFormat("invalid module path '%s'\n", + entry.c_str()); + break; + } + } + } + + if (flush) { + ProcessSP process = target->GetProcessSP(); + if (process) + process->Flush(); + } + + return result.Succeeded(); + } +}; + +class CommandObjectTargetModulesLoad + : public CommandObjectTargetModulesModuleAutoComplete { +public: + CommandObjectTargetModulesLoad(CommandInterpreter &interpreter) + : CommandObjectTargetModulesModuleAutoComplete( + interpreter, "target modules load", + "Set the load addresses for one or more sections in a target " + "module.", + "target modules load [--file <module> --uuid <uuid>] <sect-name> " + "<address> [<sect-name> <address> ....]", + eCommandRequiresTarget), + m_option_group(), + m_file_option(LLDB_OPT_SET_1, false, "file", 'f', 0, eArgTypeName, + "Fullpath or basename for module to load.", ""), + m_load_option(LLDB_OPT_SET_1, false, "load", 'l', + "Write file contents to the memory.", false, true), + m_pc_option(LLDB_OPT_SET_1, false, "set-pc-to-entry", 'p', + "Set PC to the entry point." + " Only applicable with '--load' option.", + false, true), + m_slide_option(LLDB_OPT_SET_1, false, "slide", 's', 0, eArgTypeOffset, + "Set the load address for all sections to be the " + "virtual address in the file plus the offset.", + 0) { + m_option_group.Append(&m_uuid_option_group, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1); + m_option_group.Append(&m_file_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_load_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_pc_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_slide_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectTargetModulesLoad() override = default; + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + const bool load = m_load_option.GetOptionValue().GetCurrentValue(); + const bool set_pc = m_pc_option.GetOptionValue().GetCurrentValue(); + + const size_t argc = args.GetArgumentCount(); + ModuleSpec module_spec; + bool search_using_module_spec = false; + + // Allow "load" option to work without --file or --uuid option. + if (load) { + if (!m_file_option.GetOptionValue().OptionWasSet() && + !m_uuid_option_group.GetOptionValue().OptionWasSet()) { + ModuleList &module_list = target->GetImages(); + if (module_list.GetSize() == 1) { + search_using_module_spec = true; + module_spec.GetFileSpec() = + module_list.GetModuleAtIndex(0)->GetFileSpec(); + } + } + } + + if (m_file_option.GetOptionValue().OptionWasSet()) { + search_using_module_spec = true; + const char *arg_cstr = m_file_option.GetOptionValue().GetCurrentValue(); + const bool use_global_module_list = true; + ModuleList module_list; + const size_t num_matches = FindModulesByName( + target, arg_cstr, module_list, use_global_module_list); + if (num_matches == 1) { + module_spec.GetFileSpec() = + module_list.GetModuleAtIndex(0)->GetFileSpec(); + } else if (num_matches > 1) { + search_using_module_spec = false; + result.AppendErrorWithFormat( + "more than 1 module matched by name '%s'\n", arg_cstr); + result.SetStatus(eReturnStatusFailed); + } else { + search_using_module_spec = false; + result.AppendErrorWithFormat("no object file for module '%s'\n", + arg_cstr); + result.SetStatus(eReturnStatusFailed); + } + } + + if (m_uuid_option_group.GetOptionValue().OptionWasSet()) { + search_using_module_spec = true; + module_spec.GetUUID() = + m_uuid_option_group.GetOptionValue().GetCurrentValue(); + } + + if (search_using_module_spec) { + ModuleList matching_modules; + target->GetImages().FindModules(module_spec, matching_modules); + const size_t num_matches = matching_modules.GetSize(); + + char path[PATH_MAX]; + if (num_matches == 1) { + Module *module = matching_modules.GetModulePointerAtIndex(0); + if (module) { + ObjectFile *objfile = module->GetObjectFile(); + if (objfile) { + SectionList *section_list = module->GetSectionList(); + if (section_list) { + bool changed = false; + if (argc == 0) { + if (m_slide_option.GetOptionValue().OptionWasSet()) { + const addr_t slide = + m_slide_option.GetOptionValue().GetCurrentValue(); + const bool slide_is_offset = true; + module->SetLoadAddress(*target, slide, slide_is_offset, + changed); + } else { + result.AppendError("one or more section name + load " + "address pair must be specified"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + if (m_slide_option.GetOptionValue().OptionWasSet()) { + result.AppendError("The \"--slide <offset>\" option can't " + "be used in conjunction with setting " + "section load addresses.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + for (size_t i = 0; i < argc; i += 2) { + const char *sect_name = args.GetArgumentAtIndex(i); + const char *load_addr_cstr = args.GetArgumentAtIndex(i + 1); + if (sect_name && load_addr_cstr) { + ConstString const_sect_name(sect_name); + bool success = false; + addr_t load_addr = StringConvert::ToUInt64( + load_addr_cstr, LLDB_INVALID_ADDRESS, 0, &success); + if (success) { + SectionSP section_sp( + section_list->FindSectionByName(const_sect_name)); + if (section_sp) { + if (section_sp->IsThreadSpecific()) { + result.AppendErrorWithFormat( + "thread specific sections are not yet " + "supported (section '%s')\n", + sect_name); + result.SetStatus(eReturnStatusFailed); + break; + } else { + if (target->GetSectionLoadList() + .SetSectionLoadAddress(section_sp, load_addr)) + changed = true; + result.AppendMessageWithFormat( + "section '%s' loaded at 0x%" PRIx64 "\n", + sect_name, load_addr); + } + } else { + result.AppendErrorWithFormat("no section found that " + "matches the section " + "name '%s'\n", + sect_name); + result.SetStatus(eReturnStatusFailed); + break; + } + } else { + result.AppendErrorWithFormat( + "invalid load address string '%s'\n", load_addr_cstr); + result.SetStatus(eReturnStatusFailed); + break; + } + } else { + if (sect_name) + result.AppendError("section names must be followed by " + "a load address.\n"); + else + result.AppendError("one or more section name + load " + "address pair must be specified.\n"); + result.SetStatus(eReturnStatusFailed); + break; + } + } + } + + if (changed) { + target->ModulesDidLoad(matching_modules); + Process *process = m_exe_ctx.GetProcessPtr(); + if (process) + process->Flush(); + } + if (load) { + ProcessSP process = target->CalculateProcess(); + Address file_entry = objfile->GetEntryPointAddress(); + if (!process) { + result.AppendError("No process"); + return false; + } + if (set_pc && !file_entry.IsValid()) { + result.AppendError("No entry address in object file"); + return false; + } + std::vector<ObjectFile::LoadableData> loadables( + objfile->GetLoadableData(*target)); + if (loadables.size() == 0) { + result.AppendError("No loadable sections"); + return false; + } + Status error = process->WriteObjectFile(std::move(loadables)); + if (error.Fail()) { + result.AppendError(error.AsCString()); + return false; + } + if (set_pc) { + ThreadList &thread_list = process->GetThreadList(); + RegisterContextSP reg_context( + thread_list.GetSelectedThread()->GetRegisterContext()); + addr_t file_entry_addr = file_entry.GetLoadAddress(target); + if (!reg_context->SetPC(file_entry_addr)) { + result.AppendErrorWithFormat("failed to set PC value to " + "0x%" PRIx64 "\n", + file_entry_addr); + result.SetStatus(eReturnStatusFailed); + } + } + } + } else { + module->GetFileSpec().GetPath(path, sizeof(path)); + result.AppendErrorWithFormat("no sections in object file '%s'\n", + path); + result.SetStatus(eReturnStatusFailed); + } + } else { + module->GetFileSpec().GetPath(path, sizeof(path)); + result.AppendErrorWithFormat("no object file for module '%s'\n", + path); + result.SetStatus(eReturnStatusFailed); + } + } else { + FileSpec *module_spec_file = module_spec.GetFileSpecPtr(); + if (module_spec_file) { + module_spec_file->GetPath(path, sizeof(path)); + result.AppendErrorWithFormat("invalid module '%s'.\n", path); + } else + result.AppendError("no module spec"); + result.SetStatus(eReturnStatusFailed); + } + } else { + std::string uuid_str; + + if (module_spec.GetFileSpec()) + module_spec.GetFileSpec().GetPath(path, sizeof(path)); + else + path[0] = '\0'; + + if (module_spec.GetUUIDPtr()) + uuid_str = module_spec.GetUUID().GetAsString(); + if (num_matches > 1) { + result.AppendErrorWithFormat( + "multiple modules match%s%s%s%s:\n", path[0] ? " file=" : "", + path, !uuid_str.empty() ? " uuid=" : "", uuid_str.c_str()); + for (size_t i = 0; i < num_matches; ++i) { + if (matching_modules.GetModulePointerAtIndex(i) + ->GetFileSpec() + .GetPath(path, sizeof(path))) + result.AppendMessageWithFormat("%s\n", path); + } + } else { + result.AppendErrorWithFormat( + "no modules were found that match%s%s%s%s.\n", + path[0] ? " file=" : "", path, !uuid_str.empty() ? " uuid=" : "", + uuid_str.c_str()); + } + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("either the \"--file <module>\" or the \"--uuid " + "<uuid>\" option must be specified.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + return result.Succeeded(); + } + + OptionGroupOptions m_option_group; + OptionGroupUUID m_uuid_option_group; + OptionGroupString m_file_option; + OptionGroupBoolean m_load_option; + OptionGroupBoolean m_pc_option; + OptionGroupUInt64 m_slide_option; +}; + +// List images with associated information +#define LLDB_OPTIONS_target_modules_list +#include "CommandOptions.inc" + +class CommandObjectTargetModulesList : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_format_array(), m_use_global_module_list(false), + m_module_addr(LLDB_INVALID_ADDRESS) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + + const int short_option = m_getopt_table[option_idx].val; + if (short_option == 'g') { + m_use_global_module_list = true; + } else if (short_option == 'a') { + m_module_addr = OptionArgParser::ToAddress( + execution_context, option_arg, LLDB_INVALID_ADDRESS, &error); + } else { + unsigned long width = 0; + option_arg.getAsInteger(0, width); + m_format_array.push_back(std::make_pair(short_option, width)); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_format_array.clear(); + m_use_global_module_list = false; + m_module_addr = LLDB_INVALID_ADDRESS; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_target_modules_list_options); + } + + // Instance variables to hold the values for command options. + typedef std::vector<std::pair<char, uint32_t>> FormatWidthCollection; + FormatWidthCollection m_format_array; + bool m_use_global_module_list; + lldb::addr_t m_module_addr; + }; + + CommandObjectTargetModulesList(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target modules list", + "List current executable and dependent shared library images.", + "target modules list [<cmd-options>]"), + m_options() {} + + ~CommandObjectTargetModulesList() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = GetDebugger().GetSelectedTarget().get(); + const bool use_global_module_list = m_options.m_use_global_module_list; + // Define a local module list here to ensure it lives longer than any + // "locker" object which might lock its contents below (through the + // "module_list_ptr" variable). + ModuleList module_list; + if (target == nullptr && !use_global_module_list) { + result.AppendError("invalid target, create a debug target using the " + "'target create' command"); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + if (target) { + uint32_t addr_byte_size = + target->GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + } + // Dump all sections for all modules images + Stream &strm = result.GetOutputStream(); + + if (m_options.m_module_addr != LLDB_INVALID_ADDRESS) { + if (target) { + Address module_address; + if (module_address.SetLoadAddress(m_options.m_module_addr, target)) { + ModuleSP module_sp(module_address.GetModule()); + if (module_sp) { + PrintModule(target, module_sp.get(), 0, strm); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat( + "Couldn't find module matching address: 0x%" PRIx64 ".", + m_options.m_module_addr); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat( + "Couldn't find module containing address: 0x%" PRIx64 ".", + m_options.m_module_addr); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError( + "Can only look up modules by address with a valid target."); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + size_t num_modules = 0; + + // This locker will be locked on the mutex in module_list_ptr if it is + // non-nullptr. Otherwise it will lock the + // AllocationModuleCollectionMutex when accessing the global module list + // directly. + std::unique_lock<std::recursive_mutex> guard( + Module::GetAllocationModuleCollectionMutex(), std::defer_lock); + + const ModuleList *module_list_ptr = nullptr; + const size_t argc = command.GetArgumentCount(); + if (argc == 0) { + if (use_global_module_list) { + guard.lock(); + num_modules = Module::GetNumberAllocatedModules(); + } else { + module_list_ptr = &target->GetImages(); + } + } else { + // TODO: Convert to entry based iteration. Requires converting + // FindModulesByName. + for (size_t i = 0; i < argc; ++i) { + // Dump specified images (by basename or fullpath) + const char *arg_cstr = command.GetArgumentAtIndex(i); + const size_t num_matches = FindModulesByName( + target, arg_cstr, module_list, use_global_module_list); + if (num_matches == 0) { + if (argc == 1) { + result.AppendErrorWithFormat("no modules found that match '%s'", + arg_cstr); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } + + module_list_ptr = &module_list; + } + + std::unique_lock<std::recursive_mutex> lock; + if (module_list_ptr != nullptr) { + lock = + std::unique_lock<std::recursive_mutex>(module_list_ptr->GetMutex()); + + num_modules = module_list_ptr->GetSize(); + } + + if (num_modules > 0) { + for (uint32_t image_idx = 0; image_idx < num_modules; ++image_idx) { + ModuleSP module_sp; + Module *module; + if (module_list_ptr) { + module_sp = module_list_ptr->GetModuleAtIndexUnlocked(image_idx); + module = module_sp.get(); + } else { + module = Module::GetAllocatedModuleAtIndex(image_idx); + module_sp = module->shared_from_this(); + } + + const size_t indent = strm.Printf("[%3u] ", image_idx); + PrintModule(target, module, indent, strm); + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + if (argc) { + if (use_global_module_list) + result.AppendError( + "the global module list has no matching modules"); + else + result.AppendError("the target has no matching modules"); + } else { + if (use_global_module_list) + result.AppendError("the global module list is empty"); + else + result.AppendError( + "the target has no associated executable images"); + } + result.SetStatus(eReturnStatusFailed); + return false; + } + } + return result.Succeeded(); + } + + void PrintModule(Target *target, Module *module, int indent, Stream &strm) { + if (module == nullptr) { + strm.PutCString("Null module"); + return; + } + + bool dump_object_name = false; + if (m_options.m_format_array.empty()) { + m_options.m_format_array.push_back(std::make_pair('u', 0)); + m_options.m_format_array.push_back(std::make_pair('h', 0)); + m_options.m_format_array.push_back(std::make_pair('f', 0)); + m_options.m_format_array.push_back(std::make_pair('S', 0)); + } + const size_t num_entries = m_options.m_format_array.size(); + bool print_space = false; + for (size_t i = 0; i < num_entries; ++i) { + if (print_space) + strm.PutChar(' '); + print_space = true; + const char format_char = m_options.m_format_array[i].first; + uint32_t width = m_options.m_format_array[i].second; + switch (format_char) { + case 'A': + DumpModuleArchitecture(strm, module, false, width); + break; + + case 't': + DumpModuleArchitecture(strm, module, true, width); + break; + + case 'f': + DumpFullpath(strm, &module->GetFileSpec(), width); + dump_object_name = true; + break; + + case 'd': + DumpDirectory(strm, &module->GetFileSpec(), width); + break; + + case 'b': + DumpBasename(strm, &module->GetFileSpec(), width); + dump_object_name = true; + break; + + case 'h': + case 'o': + // Image header address + { + uint32_t addr_nibble_width = + target ? (target->GetArchitecture().GetAddressByteSize() * 2) + : 16; + + ObjectFile *objfile = module->GetObjectFile(); + if (objfile) { + Address base_addr(objfile->GetBaseAddress()); + if (base_addr.IsValid()) { + if (target && !target->GetSectionLoadList().IsEmpty()) { + lldb::addr_t load_addr = + base_addr.GetLoadAddress(target); + if (load_addr == LLDB_INVALID_ADDRESS) { + base_addr.Dump(&strm, target, + Address::DumpStyleModuleWithFileAddress, + Address::DumpStyleFileAddress); + } else { + if (format_char == 'o') { + // Show the offset of slide for the image + strm.Printf( + "0x%*.*" PRIx64, addr_nibble_width, addr_nibble_width, + load_addr - base_addr.GetFileAddress()); + } else { + // Show the load address of the image + strm.Printf("0x%*.*" PRIx64, addr_nibble_width, + addr_nibble_width, load_addr); + } + } + break; + } + // The address was valid, but the image isn't loaded, output the + // address in an appropriate format + base_addr.Dump(&strm, target, Address::DumpStyleFileAddress); + break; + } + } + strm.Printf("%*s", addr_nibble_width + 2, ""); + } + break; + + case 'r': { + size_t ref_count = 0; + ModuleSP module_sp(module->shared_from_this()); + if (module_sp) { + // Take one away to make sure we don't count our local "module_sp" + ref_count = module_sp.use_count() - 1; + } + if (width) + strm.Printf("{%*" PRIu64 "}", width, (uint64_t)ref_count); + else + strm.Printf("{%" PRIu64 "}", (uint64_t)ref_count); + } break; + + case 's': + case 'S': { + if (const SymbolFile *symbol_file = module->GetSymbolFile()) { + const FileSpec symfile_spec = + symbol_file->GetObjectFile()->GetFileSpec(); + if (format_char == 'S') { + // Dump symbol file only if different from module file + if (!symfile_spec || symfile_spec == module->GetFileSpec()) { + print_space = false; + break; + } + // Add a newline and indent past the index + strm.Printf("\n%*s", indent, ""); + } + DumpFullpath(strm, &symfile_spec, width); + dump_object_name = true; + break; + } + strm.Printf("%.*s", width, "<NONE>"); + } break; + + case 'm': + strm.Format("{0:%c}", llvm::fmt_align(module->GetModificationTime(), + llvm::AlignStyle::Left, width)); + break; + + case 'p': + strm.Printf("%p", static_cast<void *>(module)); + break; + + case 'u': + DumpModuleUUID(strm, module); + break; + + default: + break; + } + } + if (dump_object_name) { + const char *object_name = module->GetObjectName().GetCString(); + if (object_name) + strm.Printf("(%s)", object_name); + } + strm.EOL(); + } + + CommandOptions m_options; +}; + +#pragma mark CommandObjectTargetModulesShowUnwind + +// Lookup unwind information in images +#define LLDB_OPTIONS_target_modules_show_unwind +#include "CommandOptions.inc" + +class CommandObjectTargetModulesShowUnwind : public CommandObjectParsed { +public: + enum { + eLookupTypeInvalid = -1, + eLookupTypeAddress = 0, + eLookupTypeSymbol, + eLookupTypeFunction, + eLookupTypeFunctionOrSymbol, + kNumLookupTypes + }; + + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_type(eLookupTypeInvalid), m_str(), + m_addr(LLDB_INVALID_ADDRESS) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'a': { + m_str = option_arg; + m_type = eLookupTypeAddress; + m_addr = OptionArgParser::ToAddress(execution_context, option_arg, + LLDB_INVALID_ADDRESS, &error); + if (m_addr == LLDB_INVALID_ADDRESS) + error.SetErrorStringWithFormat("invalid address string '%s'", + option_arg.str().c_str()); + break; + } + + case 'n': + m_str = option_arg; + m_type = eLookupTypeFunctionOrSymbol; + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_type = eLookupTypeInvalid; + m_str.clear(); + m_addr = LLDB_INVALID_ADDRESS; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_target_modules_show_unwind_options); + } + + // Instance variables to hold the values for command options. + + int m_type; // Should be a eLookupTypeXXX enum after parsing options + std::string m_str; // Holds name lookup + lldb::addr_t m_addr; // Holds the address to lookup + }; + + CommandObjectTargetModulesShowUnwind(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target modules show-unwind", + "Show synthesized unwind instructions for a function.", nullptr, + eCommandRequiresTarget | eCommandRequiresProcess | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_options() {} + + ~CommandObjectTargetModulesShowUnwind() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + Process *process = m_exe_ctx.GetProcessPtr(); + ABI *abi = nullptr; + if (process) + abi = process->GetABI().get(); + + if (process == nullptr) { + result.AppendError( + "You must have a process running to use this command."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ThreadList threads(process->GetThreadList()); + if (threads.GetSize() == 0) { + result.AppendError("The process must be paused to use this command."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ThreadSP thread(threads.GetThreadAtIndex(0)); + if (!thread) { + result.AppendError("The process must be paused to use this command."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + SymbolContextList sc_list; + + if (m_options.m_type == eLookupTypeFunctionOrSymbol) { + ConstString function_name(m_options.m_str.c_str()); + target->GetImages().FindFunctions(function_name, eFunctionNameTypeAuto, + true, false, sc_list); + } else if (m_options.m_type == eLookupTypeAddress && target) { + Address addr; + if (target->GetSectionLoadList().ResolveLoadAddress(m_options.m_addr, + addr)) { + SymbolContext sc; + ModuleSP module_sp(addr.GetModule()); + module_sp->ResolveSymbolContextForAddress(addr, + eSymbolContextEverything, sc); + if (sc.function || sc.symbol) { + sc_list.Append(sc); + } + } + } else { + result.AppendError( + "address-expression or function name option must be specified."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + size_t num_matches = sc_list.GetSize(); + if (num_matches == 0) { + result.AppendErrorWithFormat("no unwind data found that matches '%s'.", + m_options.m_str.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + for (uint32_t idx = 0; idx < num_matches; idx++) { + SymbolContext sc; + sc_list.GetContextAtIndex(idx, sc); + if (sc.symbol == nullptr && sc.function == nullptr) + continue; + if (!sc.module_sp || sc.module_sp->GetObjectFile() == nullptr) + continue; + AddressRange range; + if (!sc.GetAddressRange(eSymbolContextFunction | eSymbolContextSymbol, 0, + false, range)) + continue; + if (!range.GetBaseAddress().IsValid()) + continue; + ConstString funcname(sc.GetFunctionName()); + if (funcname.IsEmpty()) + continue; + addr_t start_addr = range.GetBaseAddress().GetLoadAddress(target); + if (abi) + start_addr = abi->FixCodeAddress(start_addr); + + FuncUnwindersSP func_unwinders_sp( + sc.module_sp->GetUnwindTable() + .GetUncachedFuncUnwindersContainingAddress(start_addr, sc)); + if (!func_unwinders_sp) + continue; + + result.GetOutputStream().Printf( + "UNWIND PLANS for %s`%s (start addr 0x%" PRIx64 ")\n\n", + sc.module_sp->GetPlatformFileSpec().GetFilename().AsCString(), + funcname.AsCString(), start_addr); + + UnwindPlanSP non_callsite_unwind_plan = + func_unwinders_sp->GetUnwindPlanAtNonCallSite(*target, *thread); + if (non_callsite_unwind_plan) { + result.GetOutputStream().Printf( + "Asynchronous (not restricted to call-sites) UnwindPlan is '%s'\n", + non_callsite_unwind_plan->GetSourceName().AsCString()); + } + UnwindPlanSP callsite_unwind_plan = + func_unwinders_sp->GetUnwindPlanAtCallSite(*target, *thread); + if (callsite_unwind_plan) { + result.GetOutputStream().Printf( + "Synchronous (restricted to call-sites) UnwindPlan is '%s'\n", + callsite_unwind_plan->GetSourceName().AsCString()); + } + UnwindPlanSP fast_unwind_plan = + func_unwinders_sp->GetUnwindPlanFastUnwind(*target, *thread); + if (fast_unwind_plan) { + result.GetOutputStream().Printf( + "Fast UnwindPlan is '%s'\n", + fast_unwind_plan->GetSourceName().AsCString()); + } + + result.GetOutputStream().Printf("\n"); + + UnwindPlanSP assembly_sp = + func_unwinders_sp->GetAssemblyUnwindPlan(*target, *thread); + if (assembly_sp) { + result.GetOutputStream().Printf( + "Assembly language inspection UnwindPlan:\n"); + assembly_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + UnwindPlanSP of_unwind_sp = + func_unwinders_sp->GetObjectFileUnwindPlan(*target); + if (of_unwind_sp) { + result.GetOutputStream().Printf("object file UnwindPlan:\n"); + of_unwind_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + UnwindPlanSP of_unwind_augmented_sp = + func_unwinders_sp->GetObjectFileAugmentedUnwindPlan(*target, + *thread); + if (of_unwind_augmented_sp) { + result.GetOutputStream().Printf("object file augmented UnwindPlan:\n"); + of_unwind_augmented_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + UnwindPlanSP ehframe_sp = + func_unwinders_sp->GetEHFrameUnwindPlan(*target); + if (ehframe_sp) { + result.GetOutputStream().Printf("eh_frame UnwindPlan:\n"); + ehframe_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + UnwindPlanSP ehframe_augmented_sp = + func_unwinders_sp->GetEHFrameAugmentedUnwindPlan(*target, *thread); + if (ehframe_augmented_sp) { + result.GetOutputStream().Printf("eh_frame augmented UnwindPlan:\n"); + ehframe_augmented_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + if (UnwindPlanSP plan_sp = + func_unwinders_sp->GetDebugFrameUnwindPlan(*target)) { + result.GetOutputStream().Printf("debug_frame UnwindPlan:\n"); + plan_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + if (UnwindPlanSP plan_sp = + func_unwinders_sp->GetDebugFrameAugmentedUnwindPlan(*target, + *thread)) { + result.GetOutputStream().Printf("debug_frame augmented UnwindPlan:\n"); + plan_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + UnwindPlanSP arm_unwind_sp = + func_unwinders_sp->GetArmUnwindUnwindPlan(*target); + if (arm_unwind_sp) { + result.GetOutputStream().Printf("ARM.exidx unwind UnwindPlan:\n"); + arm_unwind_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + if (UnwindPlanSP symfile_plan_sp = + func_unwinders_sp->GetSymbolFileUnwindPlan(*thread)) { + result.GetOutputStream().Printf("Symbol file UnwindPlan:\n"); + symfile_plan_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + UnwindPlanSP compact_unwind_sp = + func_unwinders_sp->GetCompactUnwindUnwindPlan(*target); + if (compact_unwind_sp) { + result.GetOutputStream().Printf("Compact unwind UnwindPlan:\n"); + compact_unwind_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + if (fast_unwind_plan) { + result.GetOutputStream().Printf("Fast UnwindPlan:\n"); + fast_unwind_plan->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + ABISP abi_sp = process->GetABI(); + if (abi_sp) { + UnwindPlan arch_default(lldb::eRegisterKindGeneric); + if (abi_sp->CreateDefaultUnwindPlan(arch_default)) { + result.GetOutputStream().Printf("Arch default UnwindPlan:\n"); + arch_default.Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + UnwindPlan arch_entry(lldb::eRegisterKindGeneric); + if (abi_sp->CreateFunctionEntryUnwindPlan(arch_entry)) { + result.GetOutputStream().Printf( + "Arch default at entry point UnwindPlan:\n"); + arch_entry.Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + } + + result.GetOutputStream().Printf("\n"); + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// Lookup information in images +#define LLDB_OPTIONS_target_modules_lookup +#include "CommandOptions.inc" + +class CommandObjectTargetModulesLookup : public CommandObjectParsed { +public: + enum { + eLookupTypeInvalid = -1, + eLookupTypeAddress = 0, + eLookupTypeSymbol, + eLookupTypeFileLine, // Line is optional + eLookupTypeFunction, + eLookupTypeFunctionOrSymbol, + eLookupTypeType, + kNumLookupTypes + }; + + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'a': { + m_type = eLookupTypeAddress; + m_addr = OptionArgParser::ToAddress(execution_context, option_arg, + LLDB_INVALID_ADDRESS, &error); + } break; + + case 'o': + if (option_arg.getAsInteger(0, m_offset)) + error.SetErrorStringWithFormat("invalid offset string '%s'", + option_arg.str().c_str()); + break; + + case 's': + m_str = option_arg; + m_type = eLookupTypeSymbol; + break; + + case 'f': + m_file.SetFile(option_arg, FileSpec::Style::native); + m_type = eLookupTypeFileLine; + break; + + case 'i': + m_include_inlines = false; + break; + + case 'l': + if (option_arg.getAsInteger(0, m_line_number)) + error.SetErrorStringWithFormat("invalid line number string '%s'", + option_arg.str().c_str()); + else if (m_line_number == 0) + error.SetErrorString("zero is an invalid line number"); + m_type = eLookupTypeFileLine; + break; + + case 'F': + m_str = option_arg; + m_type = eLookupTypeFunction; + break; + + case 'n': + m_str = option_arg; + m_type = eLookupTypeFunctionOrSymbol; + break; + + case 't': + m_str = option_arg; + m_type = eLookupTypeType; + break; + + case 'v': + m_verbose = true; + break; + + case 'A': + m_print_all = true; + break; + + case 'r': + m_use_regex = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_type = eLookupTypeInvalid; + m_str.clear(); + m_file.Clear(); + m_addr = LLDB_INVALID_ADDRESS; + m_offset = 0; + m_line_number = 0; + m_use_regex = false; + m_include_inlines = true; + m_verbose = false; + m_print_all = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_target_modules_lookup_options); + } + + int m_type; // Should be a eLookupTypeXXX enum after parsing options + std::string m_str; // Holds name lookup + FileSpec m_file; // Files for file lookups + lldb::addr_t m_addr; // Holds the address to lookup + lldb::addr_t + m_offset; // Subtract this offset from m_addr before doing lookups. + uint32_t m_line_number; // Line number for file+line lookups + bool m_use_regex; // Name lookups in m_str are regular expressions. + bool m_include_inlines; // Check for inline entries when looking up by + // file/line. + bool m_verbose; // Enable verbose lookup info + bool m_print_all; // Print all matches, even in cases where there's a best + // match. + }; + + CommandObjectTargetModulesLookup(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target modules lookup", + "Look up information within executable and " + "dependent shared library images.", + nullptr, eCommandRequiresTarget), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData file_arg; + + // Define the first (and only) variant of this arg. + file_arg.arg_type = eArgTypeFilename; + file_arg.arg_repetition = eArgRepeatStar; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(file_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectTargetModulesLookup() override = default; + + Options *GetOptions() override { return &m_options; } + + bool LookupHere(CommandInterpreter &interpreter, CommandReturnObject &result, + bool &syntax_error) { + switch (m_options.m_type) { + case eLookupTypeAddress: + case eLookupTypeFileLine: + case eLookupTypeFunction: + case eLookupTypeFunctionOrSymbol: + case eLookupTypeSymbol: + default: + return false; + case eLookupTypeType: + break; + } + + StackFrameSP frame = m_exe_ctx.GetFrameSP(); + + if (!frame) + return false; + + const SymbolContext &sym_ctx(frame->GetSymbolContext(eSymbolContextModule)); + + if (!sym_ctx.module_sp) + return false; + + switch (m_options.m_type) { + default: + return false; + case eLookupTypeType: + if (!m_options.m_str.empty()) { + if (LookupTypeHere(m_interpreter, result.GetOutputStream(), + *sym_ctx.module_sp, m_options.m_str.c_str(), + m_options.m_use_regex)) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + } + break; + } + + return true; + } + + bool LookupInModule(CommandInterpreter &interpreter, Module *module, + CommandReturnObject &result, bool &syntax_error) { + switch (m_options.m_type) { + case eLookupTypeAddress: + if (m_options.m_addr != LLDB_INVALID_ADDRESS) { + if (LookupAddressInModule( + m_interpreter, result.GetOutputStream(), module, + eSymbolContextEverything | + (m_options.m_verbose + ? static_cast<int>(eSymbolContextVariable) + : 0), + m_options.m_addr, m_options.m_offset, m_options.m_verbose)) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + } + break; + + case eLookupTypeSymbol: + if (!m_options.m_str.empty()) { + if (LookupSymbolInModule(m_interpreter, result.GetOutputStream(), + module, m_options.m_str.c_str(), + m_options.m_use_regex, m_options.m_verbose)) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + } + break; + + case eLookupTypeFileLine: + if (m_options.m_file) { + if (LookupFileAndLineInModule( + m_interpreter, result.GetOutputStream(), module, + m_options.m_file, m_options.m_line_number, + m_options.m_include_inlines, m_options.m_verbose)) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + } + break; + + case eLookupTypeFunctionOrSymbol: + case eLookupTypeFunction: + if (!m_options.m_str.empty()) { + if (LookupFunctionInModule( + m_interpreter, result.GetOutputStream(), module, + m_options.m_str.c_str(), m_options.m_use_regex, + m_options.m_include_inlines, + m_options.m_type == + eLookupTypeFunctionOrSymbol, // include symbols + m_options.m_verbose)) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + } + break; + + case eLookupTypeType: + if (!m_options.m_str.empty()) { + if (LookupTypeInModule(m_interpreter, result.GetOutputStream(), module, + m_options.m_str.c_str(), + m_options.m_use_regex)) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + } + break; + + default: + m_options.GenerateOptionUsage( + result.GetErrorStream(), this, + GetCommandInterpreter().GetDebugger().GetTerminalWidth()); + syntax_error = true; + break; + } + + result.SetStatus(eReturnStatusFailed); + return false; + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + bool syntax_error = false; + uint32_t i; + uint32_t num_successful_lookups = 0; + uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + // Dump all sections for all modules images + + if (command.GetArgumentCount() == 0) { + ModuleSP current_module; + + // Where it is possible to look in the current symbol context first, + // try that. If this search was successful and --all was not passed, + // don't print anything else. + if (LookupHere(m_interpreter, result, syntax_error)) { + result.GetOutputStream().EOL(); + num_successful_lookups++; + if (!m_options.m_print_all) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } + } + + // Dump all sections for all other modules + + const ModuleList &target_modules = target->GetImages(); + std::lock_guard<std::recursive_mutex> guard(target_modules.GetMutex()); + const size_t num_modules = target_modules.GetSize(); + if (num_modules > 0) { + for (i = 0; i < num_modules && !syntax_error; ++i) { + Module *module_pointer = + target_modules.GetModulePointerAtIndexUnlocked(i); + + if (module_pointer != current_module.get() && + LookupInModule(m_interpreter, + target_modules.GetModulePointerAtIndexUnlocked(i), + result, syntax_error)) { + result.GetOutputStream().EOL(); + num_successful_lookups++; + } + } + } else { + result.AppendError("the target has no associated executable images"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + // Dump specified images (by basename or fullpath) + const char *arg_cstr; + for (i = 0; (arg_cstr = command.GetArgumentAtIndex(i)) != nullptr && + !syntax_error; + ++i) { + ModuleList module_list; + const size_t num_matches = + FindModulesByName(target, arg_cstr, module_list, false); + if (num_matches > 0) { + for (size_t j = 0; j < num_matches; ++j) { + Module *module = module_list.GetModulePointerAtIndex(j); + if (module) { + if (LookupInModule(m_interpreter, module, result, syntax_error)) { + result.GetOutputStream().EOL(); + num_successful_lookups++; + } + } + } + } else + result.AppendWarningWithFormat( + "Unable to find an image that matches '%s'.\n", arg_cstr); + } + } + + if (num_successful_lookups > 0) + result.SetStatus(eReturnStatusSuccessFinishResult); + else + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +#pragma mark CommandObjectMultiwordImageSearchPaths + +// CommandObjectMultiwordImageSearchPaths + +class CommandObjectTargetModulesImageSearchPaths + : public CommandObjectMultiword { +public: + CommandObjectTargetModulesImageSearchPaths(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "target modules search-paths", + "Commands for managing module search paths for a target.", + "target modules search-paths <subcommand> [<subcommand-options>]") { + LoadSubCommand( + "add", CommandObjectSP( + new CommandObjectTargetModulesSearchPathsAdd(interpreter))); + LoadSubCommand( + "clear", CommandObjectSP(new CommandObjectTargetModulesSearchPathsClear( + interpreter))); + LoadSubCommand( + "insert", + CommandObjectSP( + new CommandObjectTargetModulesSearchPathsInsert(interpreter))); + LoadSubCommand( + "list", CommandObjectSP(new CommandObjectTargetModulesSearchPathsList( + interpreter))); + LoadSubCommand( + "query", CommandObjectSP(new CommandObjectTargetModulesSearchPathsQuery( + interpreter))); + } + + ~CommandObjectTargetModulesImageSearchPaths() override = default; +}; + +#pragma mark CommandObjectTargetModules + +// CommandObjectTargetModules + +class CommandObjectTargetModules : public CommandObjectMultiword { +public: + // Constructors and Destructors + CommandObjectTargetModules(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "target modules", + "Commands for accessing information for one or " + "more target modules.", + "target modules <sub-command> ...") { + LoadSubCommand( + "add", CommandObjectSP(new CommandObjectTargetModulesAdd(interpreter))); + LoadSubCommand("load", CommandObjectSP(new CommandObjectTargetModulesLoad( + interpreter))); + LoadSubCommand("dump", CommandObjectSP(new CommandObjectTargetModulesDump( + interpreter))); + LoadSubCommand("list", CommandObjectSP(new CommandObjectTargetModulesList( + interpreter))); + LoadSubCommand( + "lookup", + CommandObjectSP(new CommandObjectTargetModulesLookup(interpreter))); + LoadSubCommand( + "search-paths", + CommandObjectSP( + new CommandObjectTargetModulesImageSearchPaths(interpreter))); + LoadSubCommand( + "show-unwind", + CommandObjectSP(new CommandObjectTargetModulesShowUnwind(interpreter))); + } + + ~CommandObjectTargetModules() override = default; + +private: + // For CommandObjectTargetModules only + DISALLOW_COPY_AND_ASSIGN(CommandObjectTargetModules); +}; + +class CommandObjectTargetSymbolsAdd : public CommandObjectParsed { +public: + CommandObjectTargetSymbolsAdd(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target symbols add", + "Add a debug symbol file to one of the target's current modules by " + "specifying a path to a debug symbols file, or using the options " + "to specify a module to download symbols for.", + "target symbols add <cmd-options> [<symfile>]", + eCommandRequiresTarget), + m_option_group(), + m_file_option( + LLDB_OPT_SET_1, false, "shlib", 's', + CommandCompletions::eModuleCompletion, eArgTypeShlibName, + "Fullpath or basename for module to find debug symbols for."), + m_current_frame_option( + LLDB_OPT_SET_2, false, "frame", 'F', + "Locate the debug symbols the currently selected frame.", false, + true) + + { + m_option_group.Append(&m_uuid_option_group, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1); + m_option_group.Append(&m_file_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_current_frame_option, LLDB_OPT_SET_2, + LLDB_OPT_SET_2); + m_option_group.Finalize(); + } + + ~CommandObjectTargetSymbolsAdd() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, + request, nullptr); + } + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool AddModuleSymbols(Target *target, ModuleSpec &module_spec, bool &flush, + CommandReturnObject &result) { + const FileSpec &symbol_fspec = module_spec.GetSymbolFileSpec(); + if (symbol_fspec) { + char symfile_path[PATH_MAX]; + symbol_fspec.GetPath(symfile_path, sizeof(symfile_path)); + + if (!module_spec.GetUUID().IsValid()) { + if (!module_spec.GetFileSpec() && !module_spec.GetPlatformFileSpec()) + module_spec.GetFileSpec().GetFilename() = symbol_fspec.GetFilename(); + } + // We now have a module that represents a symbol file that can be used + // for a module that might exist in the current target, so we need to + // find that module in the target + ModuleList matching_module_list; + + size_t num_matches = 0; + // First extract all module specs from the symbol file + lldb_private::ModuleSpecList symfile_module_specs; + if (ObjectFile::GetModuleSpecifications(module_spec.GetSymbolFileSpec(), + 0, 0, symfile_module_specs)) { + // Now extract the module spec that matches the target architecture + ModuleSpec target_arch_module_spec; + ModuleSpec symfile_module_spec; + target_arch_module_spec.GetArchitecture() = target->GetArchitecture(); + if (symfile_module_specs.FindMatchingModuleSpec(target_arch_module_spec, + symfile_module_spec)) { + // See if it has a UUID? + if (symfile_module_spec.GetUUID().IsValid()) { + // It has a UUID, look for this UUID in the target modules + ModuleSpec symfile_uuid_module_spec; + symfile_uuid_module_spec.GetUUID() = symfile_module_spec.GetUUID(); + target->GetImages().FindModules(symfile_uuid_module_spec, + matching_module_list); + num_matches = matching_module_list.GetSize(); + } + } + + if (num_matches == 0) { + // No matches yet, iterate through the module specs to find a UUID + // value that we can match up to an image in our target + const size_t num_symfile_module_specs = + symfile_module_specs.GetSize(); + for (size_t i = 0; i < num_symfile_module_specs && num_matches == 0; + ++i) { + if (symfile_module_specs.GetModuleSpecAtIndex( + i, symfile_module_spec)) { + if (symfile_module_spec.GetUUID().IsValid()) { + // It has a UUID, look for this UUID in the target modules + ModuleSpec symfile_uuid_module_spec; + symfile_uuid_module_spec.GetUUID() = + symfile_module_spec.GetUUID(); + target->GetImages().FindModules(symfile_uuid_module_spec, + matching_module_list); + num_matches = matching_module_list.GetSize(); + } + } + } + } + } + + // Just try to match up the file by basename if we have no matches at + // this point + if (num_matches == 0) { + target->GetImages().FindModules(module_spec, matching_module_list); + num_matches = matching_module_list.GetSize(); + } + + while (num_matches == 0) { + ConstString filename_no_extension( + module_spec.GetFileSpec().GetFileNameStrippingExtension()); + // Empty string returned, lets bail + if (!filename_no_extension) + break; + + // Check if there was no extension to strip and the basename is the + // same + if (filename_no_extension == module_spec.GetFileSpec().GetFilename()) + break; + + // Replace basename with one less extension + module_spec.GetFileSpec().GetFilename() = filename_no_extension; + + target->GetImages().FindModules(module_spec, matching_module_list); + num_matches = matching_module_list.GetSize(); + } + + if (num_matches > 1) { + result.AppendErrorWithFormat("multiple modules match symbol file '%s', " + "use the --uuid option to resolve the " + "ambiguity.\n", + symfile_path); + } else if (num_matches == 1) { + ModuleSP module_sp(matching_module_list.GetModuleAtIndex(0)); + + // The module has not yet created its symbol vendor, we can just give + // the existing target module the symfile path to use for when it + // decides to create it! + module_sp->SetSymbolFileFileSpec(symbol_fspec); + + SymbolFile *symbol_file = + module_sp->GetSymbolFile(true, &result.GetErrorStream()); + if (symbol_file) { + ObjectFile *object_file = symbol_file->GetObjectFile(); + + if (object_file && object_file->GetFileSpec() == symbol_fspec) { + // Provide feedback that the symfile has been successfully added. + const FileSpec &module_fs = module_sp->GetFileSpec(); + result.AppendMessageWithFormat( + "symbol file '%s' has been added to '%s'\n", symfile_path, + module_fs.GetPath().c_str()); + + // Let clients know something changed in the module if it is + // currently loaded + ModuleList module_list; + module_list.Append(module_sp); + target->SymbolsDidLoad(module_list); + + // Make sure we load any scripting resources that may be embedded + // in the debug info files in case the platform supports that. + Status error; + StreamString feedback_stream; + module_sp->LoadScriptingResourceInTarget(target, error, + &feedback_stream); + if (error.Fail() && error.AsCString()) + result.AppendWarningWithFormat( + "unable to load scripting data for module %s - error " + "reported was %s", + module_sp->GetFileSpec() + .GetFileNameStrippingExtension() + .GetCString(), + error.AsCString()); + else if (feedback_stream.GetSize()) + result.AppendWarningWithFormat("%s", feedback_stream.GetData()); + + flush = true; + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + } + // Clear the symbol file spec if anything went wrong + module_sp->SetSymbolFileFileSpec(FileSpec()); + } + + namespace fs = llvm::sys::fs; + if (module_spec.GetUUID().IsValid()) { + StreamString ss_symfile_uuid; + module_spec.GetUUID().Dump(&ss_symfile_uuid); + result.AppendErrorWithFormat( + "symbol file '%s' (%s) does not match any existing module%s\n", + symfile_path, ss_symfile_uuid.GetData(), + !fs::is_regular_file(symbol_fspec.GetPath()) + ? "\n please specify the full path to the symbol file" + : ""); + } else { + result.AppendErrorWithFormat( + "symbol file '%s' does not match any existing module%s\n", + symfile_path, + !fs::is_regular_file(symbol_fspec.GetPath()) + ? "\n please specify the full path to the symbol file" + : ""); + } + } else { + result.AppendError( + "one or more executable image paths must be specified"); + } + result.SetStatus(eReturnStatusFailed); + return false; + } + + bool DoExecute(Args &args, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + result.SetStatus(eReturnStatusFailed); + bool flush = false; + ModuleSpec module_spec; + const bool uuid_option_set = + m_uuid_option_group.GetOptionValue().OptionWasSet(); + const bool file_option_set = m_file_option.GetOptionValue().OptionWasSet(); + const bool frame_option_set = + m_current_frame_option.GetOptionValue().OptionWasSet(); + const size_t argc = args.GetArgumentCount(); + + if (argc == 0) { + if (uuid_option_set || file_option_set || frame_option_set) { + bool success = false; + bool error_set = false; + if (frame_option_set) { + Process *process = m_exe_ctx.GetProcessPtr(); + if (process) { + const StateType process_state = process->GetState(); + if (StateIsStoppedState(process_state, true)) { + StackFrame *frame = m_exe_ctx.GetFramePtr(); + if (frame) { + ModuleSP frame_module_sp( + frame->GetSymbolContext(eSymbolContextModule).module_sp); + if (frame_module_sp) { + if (FileSystem::Instance().Exists( + frame_module_sp->GetPlatformFileSpec())) { + module_spec.GetArchitecture() = + frame_module_sp->GetArchitecture(); + module_spec.GetFileSpec() = + frame_module_sp->GetPlatformFileSpec(); + } + module_spec.GetUUID() = frame_module_sp->GetUUID(); + success = module_spec.GetUUID().IsValid() || + module_spec.GetFileSpec(); + } else { + result.AppendError("frame has no module"); + error_set = true; + } + } else { + result.AppendError("invalid current frame"); + error_set = true; + } + } else { + result.AppendErrorWithFormat("process is not stopped: %s", + StateAsCString(process_state)); + error_set = true; + } + } else { + result.AppendError( + "a process must exist in order to use the --frame option"); + error_set = true; + } + } else { + if (uuid_option_set) { + module_spec.GetUUID() = + m_uuid_option_group.GetOptionValue().GetCurrentValue(); + success |= module_spec.GetUUID().IsValid(); + } else if (file_option_set) { + module_spec.GetFileSpec() = + m_file_option.GetOptionValue().GetCurrentValue(); + ModuleSP module_sp( + target->GetImages().FindFirstModule(module_spec)); + if (module_sp) { + module_spec.GetFileSpec() = module_sp->GetFileSpec(); + module_spec.GetPlatformFileSpec() = + module_sp->GetPlatformFileSpec(); + module_spec.GetUUID() = module_sp->GetUUID(); + module_spec.GetArchitecture() = module_sp->GetArchitecture(); + } else { + module_spec.GetArchitecture() = target->GetArchitecture(); + } + success |= module_spec.GetUUID().IsValid() || + FileSystem::Instance().Exists(module_spec.GetFileSpec()); + } + } + + if (success) { + if (Symbols::DownloadObjectAndSymbolFile(module_spec)) { + if (module_spec.GetSymbolFileSpec()) + success = AddModuleSymbols(target, module_spec, flush, result); + } + } + + if (!success && !error_set) { + StreamString error_strm; + if (uuid_option_set) { + error_strm.PutCString("unable to find debug symbols for UUID "); + module_spec.GetUUID().Dump(&error_strm); + } else if (file_option_set) { + error_strm.PutCString( + "unable to find debug symbols for the executable file "); + error_strm << module_spec.GetFileSpec(); + } else if (frame_option_set) { + error_strm.PutCString( + "unable to find debug symbols for the current frame"); + } + result.AppendError(error_strm.GetString()); + } + } else { + result.AppendError("one or more symbol file paths must be specified, " + "or options must be specified"); + } + } else { + if (uuid_option_set) { + result.AppendError("specify either one or more paths to symbol files " + "or use the --uuid option without arguments"); + } else if (frame_option_set) { + result.AppendError("specify either one or more paths to symbol files " + "or use the --frame option without arguments"); + } else if (file_option_set && argc > 1) { + result.AppendError("specify at most one symbol file path when " + "--shlib option is set"); + } else { + PlatformSP platform_sp(target->GetPlatform()); + + for (auto &entry : args.entries()) { + if (!entry.ref().empty()) { + auto &symbol_file_spec = module_spec.GetSymbolFileSpec(); + symbol_file_spec.SetFile(entry.ref(), FileSpec::Style::native); + FileSystem::Instance().Resolve(symbol_file_spec); + if (file_option_set) { + module_spec.GetFileSpec() = + m_file_option.GetOptionValue().GetCurrentValue(); + } + if (platform_sp) { + FileSpec symfile_spec; + if (platform_sp + ->ResolveSymbolFile(*target, module_spec, symfile_spec) + .Success()) + module_spec.GetSymbolFileSpec() = symfile_spec; + } + + ArchSpec arch; + bool symfile_exists = + FileSystem::Instance().Exists(module_spec.GetSymbolFileSpec()); + + if (symfile_exists) { + if (!AddModuleSymbols(target, module_spec, flush, result)) + break; + } else { + std::string resolved_symfile_path = + module_spec.GetSymbolFileSpec().GetPath(); + if (resolved_symfile_path != entry.ref()) { + result.AppendErrorWithFormat( + "invalid module path '%s' with resolved path '%s'\n", + entry.c_str(), resolved_symfile_path.c_str()); + break; + } + result.AppendErrorWithFormat("invalid module path '%s'\n", + entry.c_str()); + break; + } + } + } + } + } + + if (flush) { + Process *process = m_exe_ctx.GetProcessPtr(); + if (process) + process->Flush(); + } + return result.Succeeded(); + } + + OptionGroupOptions m_option_group; + OptionGroupUUID m_uuid_option_group; + OptionGroupFile m_file_option; + OptionGroupBoolean m_current_frame_option; +}; + +#pragma mark CommandObjectTargetSymbols + +// CommandObjectTargetSymbols + +class CommandObjectTargetSymbols : public CommandObjectMultiword { +public: + // Constructors and Destructors + CommandObjectTargetSymbols(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "target symbols", + "Commands for adding and managing debug symbol files.", + "target symbols <sub-command> ...") { + LoadSubCommand( + "add", CommandObjectSP(new CommandObjectTargetSymbolsAdd(interpreter))); + } + + ~CommandObjectTargetSymbols() override = default; + +private: + // For CommandObjectTargetModules only + DISALLOW_COPY_AND_ASSIGN(CommandObjectTargetSymbols); +}; + +#pragma mark CommandObjectTargetStopHookAdd + +// CommandObjectTargetStopHookAdd +#define LLDB_OPTIONS_target_stop_hook_add +#include "CommandOptions.inc" + +class CommandObjectTargetStopHookAdd : public CommandObjectParsed, + public IOHandlerDelegateMultiline { +public: + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_line_start(0), m_line_end(UINT_MAX), + m_func_name_type_mask(eFunctionNameTypeAuto), + m_sym_ctx_specified(false), m_thread_specified(false), + m_use_one_liner(false), m_one_liner() {} + + ~CommandOptions() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_target_stop_hook_add_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'c': + m_class_name = option_arg; + m_sym_ctx_specified = true; + break; + + case 'e': + if (option_arg.getAsInteger(0, m_line_end)) { + error.SetErrorStringWithFormat("invalid end line number: \"%s\"", + option_arg.str().c_str()); + break; + } + m_sym_ctx_specified = true; + break; + + case 'G': { + bool value, success; + value = OptionArgParser::ToBoolean(option_arg, false, &success); + if (success) { + m_auto_continue = value; + } else + error.SetErrorStringWithFormat( + "invalid boolean value '%s' passed for -G option", + option_arg.str().c_str()); + } + break; + case 'l': + if (option_arg.getAsInteger(0, m_line_start)) { + error.SetErrorStringWithFormat("invalid start line number: \"%s\"", + option_arg.str().c_str()); + break; + } + m_sym_ctx_specified = true; + break; + + case 'i': + m_no_inlines = true; + break; + + case 'n': + m_function_name = option_arg; + m_func_name_type_mask |= eFunctionNameTypeAuto; + m_sym_ctx_specified = true; + break; + + case 'f': + m_file_name = option_arg; + m_sym_ctx_specified = true; + break; + + case 's': + m_module_name = option_arg; + m_sym_ctx_specified = true; + break; + + case 't': + if (option_arg.getAsInteger(0, m_thread_id)) + error.SetErrorStringWithFormat("invalid thread id string '%s'", + option_arg.str().c_str()); + m_thread_specified = true; + break; + + case 'T': + m_thread_name = option_arg; + m_thread_specified = true; + break; + + case 'q': + m_queue_name = option_arg; + m_thread_specified = true; + break; + + case 'x': + if (option_arg.getAsInteger(0, m_thread_index)) + error.SetErrorStringWithFormat("invalid thread index string '%s'", + option_arg.str().c_str()); + m_thread_specified = true; + break; + + case 'o': + m_use_one_liner = true; + m_one_liner.push_back(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_class_name.clear(); + m_function_name.clear(); + m_line_start = 0; + m_line_end = UINT_MAX; + m_file_name.clear(); + m_module_name.clear(); + m_func_name_type_mask = eFunctionNameTypeAuto; + m_thread_id = LLDB_INVALID_THREAD_ID; + m_thread_index = UINT32_MAX; + m_thread_name.clear(); + m_queue_name.clear(); + + m_no_inlines = false; + m_sym_ctx_specified = false; + m_thread_specified = false; + + m_use_one_liner = false; + m_one_liner.clear(); + m_auto_continue = false; + } + + std::string m_class_name; + std::string m_function_name; + uint32_t m_line_start; + uint32_t m_line_end; + std::string m_file_name; + std::string m_module_name; + uint32_t m_func_name_type_mask; // A pick from lldb::FunctionNameType. + lldb::tid_t m_thread_id; + uint32_t m_thread_index; + std::string m_thread_name; + std::string m_queue_name; + bool m_sym_ctx_specified; + bool m_no_inlines; + bool m_thread_specified; + // Instance variables to hold the values for one_liner options. + bool m_use_one_liner; + std::vector<std::string> m_one_liner; + bool m_auto_continue; + }; + + CommandObjectTargetStopHookAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target stop-hook add", + "Add a hook to be executed when the target stops.", + "target stop-hook add"), + IOHandlerDelegateMultiline("DONE", + IOHandlerDelegate::Completion::LLDBCommand), + m_options() {} + + ~CommandObjectTargetStopHookAdd() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { + StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + if (output_sp && interactive) { + output_sp->PutCString( + "Enter your stop hook command(s). Type 'DONE' to end.\n"); + output_sp->Flush(); + } + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &line) override { + if (m_stop_hook_sp) { + if (line.empty()) { + StreamFileSP error_sp(io_handler.GetErrorStreamFileSP()); + if (error_sp) { + error_sp->Printf("error: stop hook #%" PRIu64 + " aborted, no commands.\n", + m_stop_hook_sp->GetID()); + error_sp->Flush(); + } + Target *target = GetDebugger().GetSelectedTarget().get(); + if (target) + target->RemoveStopHookByID(m_stop_hook_sp->GetID()); + } else { + m_stop_hook_sp->GetCommandPointer()->SplitIntoLines(line); + StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + if (output_sp) { + output_sp->Printf("Stop hook #%" PRIu64 " added.\n", + m_stop_hook_sp->GetID()); + output_sp->Flush(); + } + } + m_stop_hook_sp.reset(); + } + io_handler.SetIsDone(true); + } + + bool DoExecute(Args &command, CommandReturnObject &result) override { + m_stop_hook_sp.reset(); + + Target &target = GetSelectedOrDummyTarget(); + Target::StopHookSP new_hook_sp = target.CreateStopHook(); + + // First step, make the specifier. + std::unique_ptr<SymbolContextSpecifier> specifier_up; + if (m_options.m_sym_ctx_specified) { + specifier_up.reset( + new SymbolContextSpecifier(GetDebugger().GetSelectedTarget())); + + if (!m_options.m_module_name.empty()) { + specifier_up->AddSpecification( + m_options.m_module_name.c_str(), + SymbolContextSpecifier::eModuleSpecified); + } + + if (!m_options.m_class_name.empty()) { + specifier_up->AddSpecification( + m_options.m_class_name.c_str(), + SymbolContextSpecifier::eClassOrNamespaceSpecified); + } + + if (!m_options.m_file_name.empty()) { + specifier_up->AddSpecification(m_options.m_file_name.c_str(), + SymbolContextSpecifier::eFileSpecified); + } + + if (m_options.m_line_start != 0) { + specifier_up->AddLineSpecification( + m_options.m_line_start, + SymbolContextSpecifier::eLineStartSpecified); + } + + if (m_options.m_line_end != UINT_MAX) { + specifier_up->AddLineSpecification( + m_options.m_line_end, SymbolContextSpecifier::eLineEndSpecified); + } + + if (!m_options.m_function_name.empty()) { + specifier_up->AddSpecification( + m_options.m_function_name.c_str(), + SymbolContextSpecifier::eFunctionSpecified); + } + } + + if (specifier_up) + new_hook_sp->SetSpecifier(specifier_up.release()); + + // Next see if any of the thread options have been entered: + + if (m_options.m_thread_specified) { + ThreadSpec *thread_spec = new ThreadSpec(); + + if (m_options.m_thread_id != LLDB_INVALID_THREAD_ID) { + thread_spec->SetTID(m_options.m_thread_id); + } + + if (m_options.m_thread_index != UINT32_MAX) + thread_spec->SetIndex(m_options.m_thread_index); + + if (!m_options.m_thread_name.empty()) + thread_spec->SetName(m_options.m_thread_name.c_str()); + + if (!m_options.m_queue_name.empty()) + thread_spec->SetQueueName(m_options.m_queue_name.c_str()); + + new_hook_sp->SetThreadSpecifier(thread_spec); + } + + new_hook_sp->SetAutoContinue(m_options.m_auto_continue); + if (m_options.m_use_one_liner) { + // Use one-liners. + for (auto cmd : m_options.m_one_liner) + new_hook_sp->GetCommandPointer()->AppendString( + cmd.c_str()); + result.AppendMessageWithFormat("Stop hook #%" PRIu64 " added.\n", + new_hook_sp->GetID()); + } else { + m_stop_hook_sp = new_hook_sp; + m_interpreter.GetLLDBCommandsFromIOHandler( + "> ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + nullptr); // Baton for the "io_handler" that will be passed back + // into our IOHandlerDelegate functions + } + result.SetStatus(eReturnStatusSuccessFinishNoResult); + + return result.Succeeded(); + } + +private: + CommandOptions m_options; + Target::StopHookSP m_stop_hook_sp; +}; + +#pragma mark CommandObjectTargetStopHookDelete + +// CommandObjectTargetStopHookDelete + +class CommandObjectTargetStopHookDelete : public CommandObjectParsed { +public: + CommandObjectTargetStopHookDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target stop-hook delete", + "Delete a stop-hook.", + "target stop-hook delete [<idx>]") {} + + ~CommandObjectTargetStopHookDelete() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + // FIXME: see if we can use the breakpoint id style parser? + size_t num_args = command.GetArgumentCount(); + if (num_args == 0) { + if (!m_interpreter.Confirm("Delete all stop hooks?", true)) { + result.SetStatus(eReturnStatusFailed); + return false; + } else { + target.RemoveAllStopHooks(); + } + } else { + bool success; + for (size_t i = 0; i < num_args; i++) { + lldb::user_id_t user_id = StringConvert::ToUInt32( + command.GetArgumentAtIndex(i), 0, 0, &success); + if (!success) { + result.AppendErrorWithFormat("invalid stop hook id: \"%s\".\n", + command.GetArgumentAtIndex(i)); + result.SetStatus(eReturnStatusFailed); + return false; + } + success = target.RemoveStopHookByID(user_id); + if (!success) { + result.AppendErrorWithFormat("unknown stop hook id: \"%s\".\n", + command.GetArgumentAtIndex(i)); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectTargetStopHookEnableDisable + +// CommandObjectTargetStopHookEnableDisable + +class CommandObjectTargetStopHookEnableDisable : public CommandObjectParsed { +public: + CommandObjectTargetStopHookEnableDisable(CommandInterpreter &interpreter, + bool enable, const char *name, + const char *help, const char *syntax) + : CommandObjectParsed(interpreter, name, help, syntax), m_enable(enable) { + } + + ~CommandObjectTargetStopHookEnableDisable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + // FIXME: see if we can use the breakpoint id style parser? + size_t num_args = command.GetArgumentCount(); + bool success; + + if (num_args == 0) { + target.SetAllStopHooksActiveState(m_enable); + } else { + for (size_t i = 0; i < num_args; i++) { + lldb::user_id_t user_id = StringConvert::ToUInt32( + command.GetArgumentAtIndex(i), 0, 0, &success); + if (!success) { + result.AppendErrorWithFormat("invalid stop hook id: \"%s\".\n", + command.GetArgumentAtIndex(i)); + result.SetStatus(eReturnStatusFailed); + return false; + } + success = target.SetStopHookActiveStateByID(user_id, m_enable); + if (!success) { + result.AppendErrorWithFormat("unknown stop hook id: \"%s\".\n", + command.GetArgumentAtIndex(i)); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + } + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } + +private: + bool m_enable; +}; + +#pragma mark CommandObjectTargetStopHookList + +// CommandObjectTargetStopHookList + +class CommandObjectTargetStopHookList : public CommandObjectParsed { +public: + CommandObjectTargetStopHookList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target stop-hook list", + "List all stop-hooks.", + "target stop-hook list [<type>]") {} + + ~CommandObjectTargetStopHookList() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedOrDummyTarget(); + + size_t num_hooks = target.GetNumStopHooks(); + if (num_hooks == 0) { + result.GetOutputStream().PutCString("No stop hooks.\n"); + } else { + for (size_t i = 0; i < num_hooks; i++) { + Target::StopHookSP this_hook = target.GetStopHookAtIndex(i); + if (i > 0) + result.GetOutputStream().PutCString("\n"); + this_hook->GetDescription(&(result.GetOutputStream()), + eDescriptionLevelFull); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +#pragma mark CommandObjectMultiwordTargetStopHooks + +// CommandObjectMultiwordTargetStopHooks + +class CommandObjectMultiwordTargetStopHooks : public CommandObjectMultiword { +public: + CommandObjectMultiwordTargetStopHooks(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "target stop-hook", + "Commands for operating on debugger target stop-hooks.", + "target stop-hook <subcommand> [<subcommand-options>]") { + LoadSubCommand("add", CommandObjectSP( + new CommandObjectTargetStopHookAdd(interpreter))); + LoadSubCommand( + "delete", + CommandObjectSP(new CommandObjectTargetStopHookDelete(interpreter))); + LoadSubCommand("disable", + CommandObjectSP(new CommandObjectTargetStopHookEnableDisable( + interpreter, false, "target stop-hook disable [<id>]", + "Disable a stop-hook.", "target stop-hook disable"))); + LoadSubCommand("enable", + CommandObjectSP(new CommandObjectTargetStopHookEnableDisable( + interpreter, true, "target stop-hook enable [<id>]", + "Enable a stop-hook.", "target stop-hook enable"))); + LoadSubCommand("list", CommandObjectSP(new CommandObjectTargetStopHookList( + interpreter))); + } + + ~CommandObjectMultiwordTargetStopHooks() override = default; +}; + +#pragma mark CommandObjectMultiwordTarget + +// CommandObjectMultiwordTarget + +CommandObjectMultiwordTarget::CommandObjectMultiwordTarget( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "target", + "Commands for operating on debugger targets.", + "target <subcommand> [<subcommand-options>]") { + LoadSubCommand("create", + CommandObjectSP(new CommandObjectTargetCreate(interpreter))); + LoadSubCommand("delete", + CommandObjectSP(new CommandObjectTargetDelete(interpreter))); + LoadSubCommand("list", + CommandObjectSP(new CommandObjectTargetList(interpreter))); + LoadSubCommand("select", + CommandObjectSP(new CommandObjectTargetSelect(interpreter))); + LoadSubCommand( + "stop-hook", + CommandObjectSP(new CommandObjectMultiwordTargetStopHooks(interpreter))); + LoadSubCommand("modules", + CommandObjectSP(new CommandObjectTargetModules(interpreter))); + LoadSubCommand("symbols", + CommandObjectSP(new CommandObjectTargetSymbols(interpreter))); + LoadSubCommand("variable", + CommandObjectSP(new CommandObjectTargetVariable(interpreter))); +} + +CommandObjectMultiwordTarget::~CommandObjectMultiwordTarget() = default; diff --git a/lldb/source/Commands/CommandObjectTarget.h b/lldb/source/Commands/CommandObjectTarget.h new file mode 100644 index 0000000000000..86d554c8a3101 --- /dev/null +++ b/lldb/source/Commands/CommandObjectTarget.h @@ -0,0 +1,28 @@ +//===-- CommandObjectTarget.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_CommandObjectTarget_h_ +#define liblldb_CommandObjectTarget_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" + +namespace lldb_private { + +// CommandObjectMultiwordTarget + +class CommandObjectMultiwordTarget : public CommandObjectMultiword { +public: + CommandObjectMultiwordTarget(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordTarget() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectTarget_h_ diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp new file mode 100644 index 0000000000000..8c52745539020 --- /dev/null +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -0,0 +1,2082 @@ +//===-- CommandObjectThread.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 "CommandObjectThread.h" + +#include "lldb/Core/SourceManager.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Host/StringConvert.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupPythonClassWithDict.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/LineEntry.h" +#include "lldb/Symbol/LineTable.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SystemRuntime.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Target/ThreadPlanStepInRange.h" +#include "lldb/Target/ThreadPlanStepInstruction.h" +#include "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Target/ThreadPlanStepRange.h" +#include "lldb/Utility/State.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectIterateOverThreads + +class CommandObjectIterateOverThreads : public CommandObjectParsed { + + class UniqueStack { + + public: + UniqueStack(std::stack<lldb::addr_t> stack_frames, uint32_t thread_index_id) + : m_stack_frames(stack_frames) { + m_thread_index_ids.push_back(thread_index_id); + } + + void AddThread(uint32_t thread_index_id) const { + m_thread_index_ids.push_back(thread_index_id); + } + + const std::vector<uint32_t> &GetUniqueThreadIndexIDs() const { + return m_thread_index_ids; + } + + lldb::tid_t GetRepresentativeThread() const { + return m_thread_index_ids.front(); + } + + friend bool inline operator<(const UniqueStack &lhs, + const UniqueStack &rhs) { + return lhs.m_stack_frames < rhs.m_stack_frames; + } + + protected: + // Mark the thread index as mutable, as we don't care about it from a const + // perspective, we only care about m_stack_frames so we keep our std::set + // sorted. + mutable std::vector<uint32_t> m_thread_index_ids; + std::stack<lldb::addr_t> m_stack_frames; + }; + +public: + CommandObjectIterateOverThreads(CommandInterpreter &interpreter, + const char *name, const char *help, + const char *syntax, uint32_t flags) + : CommandObjectParsed(interpreter, name, help, syntax, flags) {} + + ~CommandObjectIterateOverThreads() override = default; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + result.SetStatus(m_success_return); + + bool all_threads = false; + if (command.GetArgumentCount() == 0) { + Thread *thread = m_exe_ctx.GetThreadPtr(); + if (!thread || !HandleOneThread(thread->GetID(), result)) + return false; + return result.Succeeded(); + } else if (command.GetArgumentCount() == 1) { + all_threads = ::strcmp(command.GetArgumentAtIndex(0), "all") == 0; + m_unique_stacks = ::strcmp(command.GetArgumentAtIndex(0), "unique") == 0; + } + + // Use tids instead of ThreadSPs to prevent deadlocking problems which + // result from JIT-ing code while iterating over the (locked) ThreadSP + // list. + std::vector<lldb::tid_t> tids; + + if (all_threads || m_unique_stacks) { + Process *process = m_exe_ctx.GetProcessPtr(); + + for (ThreadSP thread_sp : process->Threads()) + tids.push_back(thread_sp->GetID()); + } else { + const size_t num_args = command.GetArgumentCount(); + Process *process = m_exe_ctx.GetProcessPtr(); + + std::lock_guard<std::recursive_mutex> guard( + process->GetThreadList().GetMutex()); + + for (size_t i = 0; i < num_args; i++) { + bool success; + + uint32_t thread_idx = StringConvert::ToUInt32( + command.GetArgumentAtIndex(i), 0, 0, &success); + if (!success) { + result.AppendErrorWithFormat("invalid thread specification: \"%s\"\n", + command.GetArgumentAtIndex(i)); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ThreadSP thread = + process->GetThreadList().FindThreadByIndexID(thread_idx); + + if (!thread) { + result.AppendErrorWithFormat("no thread with index: \"%s\"\n", + command.GetArgumentAtIndex(i)); + result.SetStatus(eReturnStatusFailed); + return false; + } + + tids.push_back(thread->GetID()); + } + } + + if (m_unique_stacks) { + // Iterate over threads, finding unique stack buckets. + std::set<UniqueStack> unique_stacks; + for (const lldb::tid_t &tid : tids) { + if (!BucketThread(tid, unique_stacks, result)) { + return false; + } + } + + // Write the thread id's and unique call stacks to the output stream + Stream &strm = result.GetOutputStream(); + Process *process = m_exe_ctx.GetProcessPtr(); + for (const UniqueStack &stack : unique_stacks) { + // List the common thread ID's + const std::vector<uint32_t> &thread_index_ids = + stack.GetUniqueThreadIndexIDs(); + strm.Format("{0} thread(s) ", thread_index_ids.size()); + for (const uint32_t &thread_index_id : thread_index_ids) { + strm.Format("#{0} ", thread_index_id); + } + strm.EOL(); + + // List the shared call stack for this set of threads + uint32_t representative_thread_id = stack.GetRepresentativeThread(); + ThreadSP thread = process->GetThreadList().FindThreadByIndexID( + representative_thread_id); + if (!HandleOneThread(thread->GetID(), result)) { + return false; + } + } + } else { + uint32_t idx = 0; + for (const lldb::tid_t &tid : tids) { + if (idx != 0 && m_add_return) + result.AppendMessage(""); + + if (!HandleOneThread(tid, result)) + return false; + + ++idx; + } + } + return result.Succeeded(); + } + +protected: + // Override this to do whatever you need to do for one thread. + // + // If you return false, the iteration will stop, otherwise it will proceed. + // The result is set to m_success_return (defaults to + // eReturnStatusSuccessFinishResult) before the iteration, so you only need + // to set the return status in HandleOneThread if you want to indicate an + // error. If m_add_return is true, a blank line will be inserted between each + // of the listings (except the last one.) + + virtual bool HandleOneThread(lldb::tid_t, CommandReturnObject &result) = 0; + + bool BucketThread(lldb::tid_t tid, std::set<UniqueStack> &unique_stacks, + CommandReturnObject &result) { + // Grab the corresponding thread for the given thread id. + Process *process = m_exe_ctx.GetProcessPtr(); + Thread *thread = process->GetThreadList().FindThreadByID(tid).get(); + if (thread == nullptr) { + result.AppendErrorWithFormatv("Failed to process thread #{0}.\n", tid); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Collect the each frame's address for this call-stack + std::stack<lldb::addr_t> stack_frames; + const uint32_t frame_count = thread->GetStackFrameCount(); + for (uint32_t frame_index = 0; frame_index < frame_count; frame_index++) { + const lldb::StackFrameSP frame_sp = + thread->GetStackFrameAtIndex(frame_index); + const lldb::addr_t pc = frame_sp->GetStackID().GetPC(); + stack_frames.push(pc); + } + + uint32_t thread_index_id = thread->GetIndexID(); + UniqueStack new_unique_stack(stack_frames, thread_index_id); + + // Try to match the threads stack to and existing entry. + std::set<UniqueStack>::iterator matching_stack = + unique_stacks.find(new_unique_stack); + if (matching_stack != unique_stacks.end()) { + matching_stack->AddThread(thread_index_id); + } else { + unique_stacks.insert(new_unique_stack); + } + return true; + } + + ReturnStatus m_success_return = eReturnStatusSuccessFinishResult; + bool m_unique_stacks = false; + bool m_add_return = true; +}; + +// CommandObjectThreadBacktrace +#define LLDB_OPTIONS_thread_backtrace +#include "CommandOptions.inc" + +class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'c': { + int32_t input_count = 0; + if (option_arg.getAsInteger(0, m_count)) { + m_count = UINT32_MAX; + error.SetErrorStringWithFormat( + "invalid integer value for option '%c'", short_option); + } else if (input_count < 0) + m_count = UINT32_MAX; + } break; + case 's': + if (option_arg.getAsInteger(0, m_start)) + error.SetErrorStringWithFormat( + "invalid integer value for option '%c'", short_option); + break; + case 'e': { + bool success; + m_extended_backtrace = + OptionArgParser::ToBoolean(option_arg, false, &success); + if (!success) + error.SetErrorStringWithFormat( + "invalid boolean value for option '%c'", short_option); + } break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_count = UINT32_MAX; + m_start = 0; + m_extended_backtrace = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_thread_backtrace_options); + } + + // Instance variables to hold the values for command options. + uint32_t m_count; + uint32_t m_start; + bool m_extended_backtrace; + }; + + CommandObjectThreadBacktrace(CommandInterpreter &interpreter) + : CommandObjectIterateOverThreads( + interpreter, "thread backtrace", + "Show thread call stacks. Defaults to the current thread, thread " + "indexes can be specified as arguments.\n" + "Use the thread-index \"all\" to see all threads.\n" + "Use the thread-index \"unique\" to see threads grouped by unique " + "call stacks.\n" + "Use 'settings set frame-format' to customize the printing of " + "frames in the backtrace and 'settings set thread-format' to " + "customize the thread header.", + nullptr, + eCommandRequiresProcess | eCommandRequiresThread | + eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused), + m_options() {} + + ~CommandObjectThreadBacktrace() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + void DoExtendedBacktrace(Thread *thread, CommandReturnObject &result) { + SystemRuntime *runtime = thread->GetProcess()->GetSystemRuntime(); + if (runtime) { + Stream &strm = result.GetOutputStream(); + const std::vector<ConstString> &types = + runtime->GetExtendedBacktraceTypes(); + for (auto type : types) { + ThreadSP ext_thread_sp = runtime->GetExtendedBacktraceThread( + thread->shared_from_this(), type); + if (ext_thread_sp && ext_thread_sp->IsValid()) { + const uint32_t num_frames_with_source = 0; + const bool stop_format = false; + if (ext_thread_sp->GetStatus(strm, m_options.m_start, + m_options.m_count, + num_frames_with_source, + stop_format)) { + DoExtendedBacktrace(ext_thread_sp.get(), result); + } + } + } + } + } + + bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { + ThreadSP thread_sp = + m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); + if (!thread_sp) { + result.AppendErrorWithFormat( + "thread disappeared while computing backtraces: 0x%" PRIx64 "\n", + tid); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Thread *thread = thread_sp.get(); + + Stream &strm = result.GetOutputStream(); + + // Only dump stack info if we processing unique stacks. + const bool only_stacks = m_unique_stacks; + + // Don't show source context when doing backtraces. + const uint32_t num_frames_with_source = 0; + const bool stop_format = true; + if (!thread->GetStatus(strm, m_options.m_start, m_options.m_count, + num_frames_with_source, stop_format, only_stacks)) { + result.AppendErrorWithFormat( + "error displaying backtrace for thread: \"0x%4.4x\"\n", + thread->GetIndexID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (m_options.m_extended_backtrace) { + DoExtendedBacktrace(thread, result); + } + + return true; + } + + CommandOptions m_options; +}; + +enum StepScope { eStepScopeSource, eStepScopeInstruction }; + +static constexpr OptionEnumValueElement g_tri_running_mode[] = { + {eOnlyThisThread, "this-thread", "Run only this thread"}, + {eAllThreads, "all-threads", "Run all threads"}, + {eOnlyDuringStepping, "while-stepping", + "Run only this thread while stepping"} }; + +static constexpr OptionEnumValues TriRunningModes() { + return OptionEnumValues(g_tri_running_mode); +} + +#define LLDB_OPTIONS_thread_step_scope +#include "CommandOptions.inc" + +class ThreadStepScopeOptionGroup : public OptionGroup { +public: + ThreadStepScopeOptionGroup() : OptionGroup() { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~ThreadStepScopeOptionGroup() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_thread_step_scope_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option + = g_thread_step_scope_options[option_idx].short_option; + + switch (short_option) { + case 'a': { + bool success; + bool avoid_no_debug = + OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error.SetErrorStringWithFormat( + "invalid boolean value for option '%c'", short_option); + else { + m_step_in_avoid_no_debug = + avoid_no_debug ? eLazyBoolYes : eLazyBoolNo; + } + } break; + + case 'A': { + bool success; + bool avoid_no_debug = + OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error.SetErrorStringWithFormat( + "invalid boolean value for option '%c'", short_option); + else { + m_step_out_avoid_no_debug = + avoid_no_debug ? eLazyBoolYes : eLazyBoolNo; + } + } break; + + case 'c': + if (option_arg.getAsInteger(0, m_step_count)) + error.SetErrorStringWithFormat("invalid step count '%s'", + option_arg.str().c_str()); + break; + + case 'm': { + auto enum_values = GetDefinitions()[option_idx].enum_values; + m_run_mode = (lldb::RunMode)OptionArgParser::ToOptionEnum( + option_arg, enum_values, eOnlyDuringStepping, error); + } break; + + case 'e': + if (option_arg == "block") { + m_end_line_is_block_end = true; + break; + } + if (option_arg.getAsInteger(0, m_end_line)) + error.SetErrorStringWithFormat("invalid end line number '%s'", + option_arg.str().c_str()); + break; + + case 'r': + m_avoid_regexp.clear(); + m_avoid_regexp.assign(option_arg); + break; + + case 't': + m_step_in_target.clear(); + m_step_in_target.assign(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_step_in_avoid_no_debug = eLazyBoolCalculate; + m_step_out_avoid_no_debug = eLazyBoolCalculate; + m_run_mode = eOnlyDuringStepping; + + // Check if we are in Non-Stop mode + TargetSP target_sp = + execution_context ? execution_context->GetTargetSP() : TargetSP(); + if (target_sp && target_sp->GetNonStopModeEnabled()) + m_run_mode = eOnlyThisThread; + + m_avoid_regexp.clear(); + m_step_in_target.clear(); + m_step_count = 1; + m_end_line = LLDB_INVALID_LINE_NUMBER; + m_end_line_is_block_end = false; + } + + // Instance variables to hold the values for command options. + LazyBool m_step_in_avoid_no_debug; + LazyBool m_step_out_avoid_no_debug; + RunMode m_run_mode; + std::string m_avoid_regexp; + std::string m_step_in_target; + uint32_t m_step_count; + uint32_t m_end_line; + bool m_end_line_is_block_end; +}; + +class CommandObjectThreadStepWithTypeAndScope : public CommandObjectParsed { +public: + + CommandObjectThreadStepWithTypeAndScope(CommandInterpreter &interpreter, + const char *name, const char *help, + const char *syntax, + StepType step_type, + StepScope step_scope) + : CommandObjectParsed(interpreter, name, help, syntax, + eCommandRequiresProcess | eCommandRequiresThread | + eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused), + m_step_type(step_type), m_step_scope(step_scope), m_options(), + m_class_options("scripted step", 'C') { + CommandArgumentEntry arg; + CommandArgumentData thread_id_arg; + + // Define the first (and only) variant of this arg. + thread_id_arg.arg_type = eArgTypeThreadID; + thread_id_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(thread_id_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + + if (step_type == eStepTypeScripted) { + m_all_options.Append(&m_class_options, LLDB_OPT_SET_1, LLDB_OPT_SET_1); + } + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectThreadStepWithTypeAndScope() override = default; + + Options *GetOptions() override { + return &m_all_options; + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + bool synchronous_execution = m_interpreter.GetSynchronous(); + + const uint32_t num_threads = process->GetThreadList().GetSize(); + Thread *thread = nullptr; + + if (command.GetArgumentCount() == 0) { + thread = GetDefaultThread(); + + if (thread == nullptr) { + result.AppendError("no selected thread in process"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + const char *thread_idx_cstr = command.GetArgumentAtIndex(0); + uint32_t step_thread_idx = + StringConvert::ToUInt32(thread_idx_cstr, LLDB_INVALID_INDEX32); + if (step_thread_idx == LLDB_INVALID_INDEX32) { + result.AppendErrorWithFormat("invalid thread index '%s'.\n", + thread_idx_cstr); + result.SetStatus(eReturnStatusFailed); + return false; + } + thread = + process->GetThreadList().FindThreadByIndexID(step_thread_idx).get(); + if (thread == nullptr) { + result.AppendErrorWithFormat( + "Thread index %u is out of range (valid values are 0 - %u).\n", + step_thread_idx, num_threads); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (m_step_type == eStepTypeScripted) { + if (m_class_options.GetClassName().empty()) { + result.AppendErrorWithFormat("empty class name for scripted step."); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (!GetDebugger().GetScriptInterpreter()->CheckObjectExists( + m_class_options.GetClassName().c_str())) { + result.AppendErrorWithFormat( + "class for scripted step: \"%s\" does not exist.", + m_class_options.GetClassName().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (m_options.m_end_line != LLDB_INVALID_LINE_NUMBER && + m_step_type != eStepTypeInto) { + result.AppendErrorWithFormat( + "end line option is only valid for step into"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const bool abort_other_plans = false; + const lldb::RunMode stop_other_threads = m_options.m_run_mode; + + // This is a bit unfortunate, but not all the commands in this command + // object support only while stepping, so I use the bool for them. + bool bool_stop_other_threads; + if (m_options.m_run_mode == eAllThreads) + bool_stop_other_threads = false; + else if (m_options.m_run_mode == eOnlyDuringStepping) + bool_stop_other_threads = + (m_step_type != eStepTypeOut && m_step_type != eStepTypeScripted); + else + bool_stop_other_threads = true; + + ThreadPlanSP new_plan_sp; + Status new_plan_status; + + if (m_step_type == eStepTypeInto) { + StackFrame *frame = thread->GetStackFrameAtIndex(0).get(); + assert(frame != nullptr); + + if (frame->HasDebugInformation()) { + AddressRange range; + SymbolContext sc = frame->GetSymbolContext(eSymbolContextEverything); + if (m_options.m_end_line != LLDB_INVALID_LINE_NUMBER) { + Status error; + if (!sc.GetAddressRangeFromHereToEndLine(m_options.m_end_line, range, + error)) { + result.AppendErrorWithFormat("invalid end-line option: %s.", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else if (m_options.m_end_line_is_block_end) { + Status error; + Block *block = frame->GetSymbolContext(eSymbolContextBlock).block; + if (!block) { + result.AppendErrorWithFormat("Could not find the current block."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + AddressRange block_range; + Address pc_address = frame->GetFrameCodeAddress(); + block->GetRangeContainingAddress(pc_address, block_range); + if (!block_range.GetBaseAddress().IsValid()) { + result.AppendErrorWithFormat( + "Could not find the current block address."); + result.SetStatus(eReturnStatusFailed); + return false; + } + lldb::addr_t pc_offset_in_block = + pc_address.GetFileAddress() - + block_range.GetBaseAddress().GetFileAddress(); + lldb::addr_t range_length = + block_range.GetByteSize() - pc_offset_in_block; + range = AddressRange(pc_address, range_length); + } else { + range = sc.line_entry.range; + } + + new_plan_sp = thread->QueueThreadPlanForStepInRange( + abort_other_plans, range, + frame->GetSymbolContext(eSymbolContextEverything), + m_options.m_step_in_target.c_str(), stop_other_threads, + new_plan_status, m_options.m_step_in_avoid_no_debug, + m_options.m_step_out_avoid_no_debug); + + if (new_plan_sp && !m_options.m_avoid_regexp.empty()) { + ThreadPlanStepInRange *step_in_range_plan = + static_cast<ThreadPlanStepInRange *>(new_plan_sp.get()); + step_in_range_plan->SetAvoidRegexp(m_options.m_avoid_regexp.c_str()); + } + } else + new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction( + false, abort_other_plans, bool_stop_other_threads, new_plan_status); + } else if (m_step_type == eStepTypeOver) { + StackFrame *frame = thread->GetStackFrameAtIndex(0).get(); + + if (frame->HasDebugInformation()) + new_plan_sp = thread->QueueThreadPlanForStepOverRange( + abort_other_plans, + frame->GetSymbolContext(eSymbolContextEverything).line_entry, + frame->GetSymbolContext(eSymbolContextEverything), + stop_other_threads, new_plan_status, + m_options.m_step_out_avoid_no_debug); + else + new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction( + true, abort_other_plans, bool_stop_other_threads, new_plan_status); + } else if (m_step_type == eStepTypeTrace) { + new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction( + false, abort_other_plans, bool_stop_other_threads, new_plan_status); + } else if (m_step_type == eStepTypeTraceOver) { + new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction( + true, abort_other_plans, bool_stop_other_threads, new_plan_status); + } else if (m_step_type == eStepTypeOut) { + new_plan_sp = thread->QueueThreadPlanForStepOut( + abort_other_plans, nullptr, false, bool_stop_other_threads, eVoteYes, + eVoteNoOpinion, thread->GetSelectedFrameIndex(), new_plan_status, + m_options.m_step_out_avoid_no_debug); + } else if (m_step_type == eStepTypeScripted) { + new_plan_sp = thread->QueueThreadPlanForStepScripted( + abort_other_plans, m_class_options.GetClassName().c_str(), + m_class_options.GetStructuredData(), + bool_stop_other_threads, new_plan_status); + } else { + result.AppendError("step type is not supported"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // If we got a new plan, then set it to be a master plan (User level Plans + // should be master plans so that they can be interruptible). Then resume + // the process. + + if (new_plan_sp) { + new_plan_sp->SetIsMasterPlan(true); + new_plan_sp->SetOkayToDiscard(false); + + if (m_options.m_step_count > 1) { + if (!new_plan_sp->SetIterationCount(m_options.m_step_count)) { + result.AppendWarning( + "step operation does not support iteration count."); + } + } + + process->GetThreadList().SetSelectedThreadByID(thread->GetID()); + + const uint32_t iohandler_id = process->GetIOHandlerID(); + + StreamString stream; + Status error; + if (synchronous_execution) + error = process->ResumeSynchronous(&stream); + else + error = process->Resume(); + + if (!error.Success()) { + result.AppendMessage(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // There is a race condition where this thread will return up the call + // stack to the main command handler and show an (lldb) prompt before + // HandlePrivateEvent (from PrivateStateThread) has a chance to call + // PushProcessIOHandler(). + process->SyncIOHandler(iohandler_id, std::chrono::seconds(2)); + + if (synchronous_execution) { + // If any state changed events had anything to say, add that to the + // result + if (stream.GetSize() > 0) + result.AppendMessage(stream.GetString()); + + process->GetThreadList().SetSelectedThreadByID(thread->GetID()); + result.SetDidChangeProcessState(true); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.SetStatus(eReturnStatusSuccessContinuingNoResult); + } + } else { + result.SetError(new_plan_status); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + +protected: + StepType m_step_type; + StepScope m_step_scope; + ThreadStepScopeOptionGroup m_options; + OptionGroupPythonClassWithDict m_class_options; + OptionGroupOptions m_all_options; +}; + +// CommandObjectThreadContinue + +class CommandObjectThreadContinue : public CommandObjectParsed { +public: + CommandObjectThreadContinue(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "thread continue", + "Continue execution of the current target process. One " + "or more threads may be specified, by default all " + "threads continue.", + nullptr, + eCommandRequiresThread | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) { + CommandArgumentEntry arg; + CommandArgumentData thread_idx_arg; + + // Define the first (and only) variant of this arg. + thread_idx_arg.arg_type = eArgTypeThreadIndex; + thread_idx_arg.arg_repetition = eArgRepeatPlus; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(thread_idx_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectThreadContinue() override = default; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + bool synchronous_execution = m_interpreter.GetSynchronous(); + + Process *process = m_exe_ctx.GetProcessPtr(); + if (process == nullptr) { + result.AppendError("no process exists. Cannot continue"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + StateType state = process->GetState(); + if ((state == eStateCrashed) || (state == eStateStopped) || + (state == eStateSuspended)) { + const size_t argc = command.GetArgumentCount(); + if (argc > 0) { + // These two lines appear at the beginning of both blocks in this + // if..else, but that is because we need to release the lock before + // calling process->Resume below. + std::lock_guard<std::recursive_mutex> guard( + process->GetThreadList().GetMutex()); + const uint32_t num_threads = process->GetThreadList().GetSize(); + std::vector<Thread *> resume_threads; + for (auto &entry : command.entries()) { + uint32_t thread_idx; + if (entry.ref().getAsInteger(0, thread_idx)) { + result.AppendErrorWithFormat( + "invalid thread index argument: \"%s\".\n", entry.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + Thread *thread = + process->GetThreadList().FindThreadByIndexID(thread_idx).get(); + + if (thread) { + resume_threads.push_back(thread); + } else { + result.AppendErrorWithFormat("invalid thread index %u.\n", + thread_idx); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (resume_threads.empty()) { + result.AppendError("no valid thread indexes were specified"); + result.SetStatus(eReturnStatusFailed); + return false; + } else { + if (resume_threads.size() == 1) + result.AppendMessageWithFormat("Resuming thread: "); + else + result.AppendMessageWithFormat("Resuming threads: "); + + for (uint32_t idx = 0; idx < num_threads; ++idx) { + Thread *thread = + process->GetThreadList().GetThreadAtIndex(idx).get(); + std::vector<Thread *>::iterator this_thread_pos = + find(resume_threads.begin(), resume_threads.end(), thread); + + if (this_thread_pos != resume_threads.end()) { + resume_threads.erase(this_thread_pos); + if (!resume_threads.empty()) + result.AppendMessageWithFormat("%u, ", thread->GetIndexID()); + else + result.AppendMessageWithFormat("%u ", thread->GetIndexID()); + + const bool override_suspend = true; + thread->SetResumeState(eStateRunning, override_suspend); + } else { + thread->SetResumeState(eStateSuspended); + } + } + result.AppendMessageWithFormat("in process %" PRIu64 "\n", + process->GetID()); + } + } else { + // These two lines appear at the beginning of both blocks in this + // if..else, but that is because we need to release the lock before + // calling process->Resume below. + std::lock_guard<std::recursive_mutex> guard( + process->GetThreadList().GetMutex()); + const uint32_t num_threads = process->GetThreadList().GetSize(); + Thread *current_thread = GetDefaultThread(); + if (current_thread == nullptr) { + result.AppendError("the process doesn't have a current thread"); + result.SetStatus(eReturnStatusFailed); + return false; + } + // Set the actions that the threads should each take when resuming + for (uint32_t idx = 0; idx < num_threads; ++idx) { + Thread *thread = process->GetThreadList().GetThreadAtIndex(idx).get(); + if (thread == current_thread) { + result.AppendMessageWithFormat("Resuming thread 0x%4.4" PRIx64 + " in process %" PRIu64 "\n", + thread->GetID(), process->GetID()); + const bool override_suspend = true; + thread->SetResumeState(eStateRunning, override_suspend); + } else { + thread->SetResumeState(eStateSuspended); + } + } + } + + StreamString stream; + Status error; + if (synchronous_execution) + error = process->ResumeSynchronous(&stream); + else + error = process->Resume(); + + // We should not be holding the thread list lock when we do this. + if (error.Success()) { + result.AppendMessageWithFormat("Process %" PRIu64 " resuming\n", + process->GetID()); + if (synchronous_execution) { + // If any state changed events had anything to say, add that to the + // result + if (stream.GetSize() > 0) + result.AppendMessage(stream.GetString()); + + result.SetDidChangeProcessState(true); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.SetStatus(eReturnStatusSuccessContinuingNoResult); + } + } else { + result.AppendErrorWithFormat("Failed to resume process: %s\n", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat( + "Process cannot be continued from its current state (%s).\n", + StateAsCString(state)); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } +}; + +// CommandObjectThreadUntil + +static constexpr OptionEnumValueElement g_duo_running_mode[] = { + {eOnlyThisThread, "this-thread", "Run only this thread"}, + {eAllThreads, "all-threads", "Run all threads"} }; + +static constexpr OptionEnumValues DuoRunningModes() { + return OptionEnumValues(g_duo_running_mode); +} + +#define LLDB_OPTIONS_thread_until +#include "CommandOptions.inc" + +class CommandObjectThreadUntil : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + uint32_t m_thread_idx; + uint32_t m_frame_idx; + + CommandOptions() + : Options(), m_thread_idx(LLDB_INVALID_THREAD_ID), + m_frame_idx(LLDB_INVALID_FRAME_ID) { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'a': { + lldb::addr_t tmp_addr = OptionArgParser::ToAddress( + execution_context, option_arg, LLDB_INVALID_ADDRESS, &error); + if (error.Success()) + m_until_addrs.push_back(tmp_addr); + } break; + case 't': + if (option_arg.getAsInteger(0, m_thread_idx)) { + m_thread_idx = LLDB_INVALID_INDEX32; + error.SetErrorStringWithFormat("invalid thread index '%s'", + option_arg.str().c_str()); + } + break; + case 'f': + if (option_arg.getAsInteger(0, m_frame_idx)) { + m_frame_idx = LLDB_INVALID_FRAME_ID; + error.SetErrorStringWithFormat("invalid frame index '%s'", + option_arg.str().c_str()); + } + break; + case 'm': { + auto enum_values = GetDefinitions()[option_idx].enum_values; + lldb::RunMode run_mode = (lldb::RunMode)OptionArgParser::ToOptionEnum( + option_arg, enum_values, eOnlyDuringStepping, error); + + if (error.Success()) { + if (run_mode == eAllThreads) + m_stop_others = false; + else + m_stop_others = true; + } + } break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_thread_idx = LLDB_INVALID_THREAD_ID; + m_frame_idx = 0; + m_stop_others = false; + m_until_addrs.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_thread_until_options); + } + + uint32_t m_step_thread_idx; + bool m_stop_others; + std::vector<lldb::addr_t> m_until_addrs; + + // Instance variables to hold the values for command options. + }; + + CommandObjectThreadUntil(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "thread until", + "Continue until a line number or address is reached by the " + "current or specified thread. Stops when returning from " + "the current function as a safety measure. " + "The target line number(s) are given as arguments, and if more than one" + " is provided, stepping will stop when the first one is hit.", + nullptr, + eCommandRequiresThread | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData line_num_arg; + + // Define the first (and only) variant of this arg. + line_num_arg.arg_type = eArgTypeLineNum; + line_num_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(line_num_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectThreadUntil() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + bool synchronous_execution = m_interpreter.GetSynchronous(); + + Target *target = &GetSelectedTarget(); + + Process *process = m_exe_ctx.GetProcessPtr(); + if (process == nullptr) { + result.AppendError("need a valid process to step"); + result.SetStatus(eReturnStatusFailed); + } else { + Thread *thread = nullptr; + std::vector<uint32_t> line_numbers; + + if (command.GetArgumentCount() >= 1) { + size_t num_args = command.GetArgumentCount(); + for (size_t i = 0; i < num_args; i++) { + uint32_t line_number; + line_number = StringConvert::ToUInt32(command.GetArgumentAtIndex(i), + UINT32_MAX); + if (line_number == UINT32_MAX) { + result.AppendErrorWithFormat("invalid line number: '%s'.\n", + command.GetArgumentAtIndex(i)); + result.SetStatus(eReturnStatusFailed); + return false; + } else + line_numbers.push_back(line_number); + } + } else if (m_options.m_until_addrs.empty()) { + result.AppendErrorWithFormat("No line number or address provided:\n%s", + GetSyntax().str().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_thread_idx == LLDB_INVALID_THREAD_ID) { + thread = GetDefaultThread(); + } else { + thread = process->GetThreadList() + .FindThreadByIndexID(m_options.m_thread_idx) + .get(); + } + + if (thread == nullptr) { + const uint32_t num_threads = process->GetThreadList().GetSize(); + result.AppendErrorWithFormat( + "Thread index %u is out of range (valid values are 0 - %u).\n", + m_options.m_thread_idx, num_threads); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const bool abort_other_plans = false; + + StackFrame *frame = + thread->GetStackFrameAtIndex(m_options.m_frame_idx).get(); + if (frame == nullptr) { + result.AppendErrorWithFormat( + "Frame index %u is out of range for thread %u.\n", + m_options.m_frame_idx, m_options.m_thread_idx); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ThreadPlanSP new_plan_sp; + Status new_plan_status; + + if (frame->HasDebugInformation()) { + // Finally we got here... Translate the given line number to a bunch + // of addresses: + SymbolContext sc(frame->GetSymbolContext(eSymbolContextCompUnit)); + LineTable *line_table = nullptr; + if (sc.comp_unit) + line_table = sc.comp_unit->GetLineTable(); + + if (line_table == nullptr) { + result.AppendErrorWithFormat("Failed to resolve the line table for " + "frame %u of thread index %u.\n", + m_options.m_frame_idx, + m_options.m_thread_idx); + result.SetStatus(eReturnStatusFailed); + return false; + } + + LineEntry function_start; + uint32_t index_ptr = 0, end_ptr; + std::vector<addr_t> address_list; + + // Find the beginning & end index of the + AddressRange fun_addr_range = sc.function->GetAddressRange(); + Address fun_start_addr = fun_addr_range.GetBaseAddress(); + line_table->FindLineEntryByAddress(fun_start_addr, function_start, + &index_ptr); + + Address fun_end_addr(fun_start_addr.GetSection(), + fun_start_addr.GetOffset() + + fun_addr_range.GetByteSize()); + + bool all_in_function = true; + + line_table->FindLineEntryByAddress(fun_end_addr, function_start, + &end_ptr); + + for (uint32_t line_number : line_numbers) { + uint32_t start_idx_ptr = index_ptr; + while (start_idx_ptr <= end_ptr) { + LineEntry line_entry; + const bool exact = false; + start_idx_ptr = sc.comp_unit->FindLineEntry( + start_idx_ptr, line_number, sc.comp_unit, exact, &line_entry); + if (start_idx_ptr == UINT32_MAX) + break; + + addr_t address = + line_entry.range.GetBaseAddress().GetLoadAddress(target); + if (address != LLDB_INVALID_ADDRESS) { + if (fun_addr_range.ContainsLoadAddress(address, target)) + address_list.push_back(address); + else + all_in_function = false; + } + start_idx_ptr++; + } + } + + for (lldb::addr_t address : m_options.m_until_addrs) { + if (fun_addr_range.ContainsLoadAddress(address, target)) + address_list.push_back(address); + else + all_in_function = false; + } + + if (address_list.empty()) { + if (all_in_function) + result.AppendErrorWithFormat( + "No line entries matching until target.\n"); + else + result.AppendErrorWithFormat( + "Until target outside of the current function.\n"); + + result.SetStatus(eReturnStatusFailed); + return false; + } + + new_plan_sp = thread->QueueThreadPlanForStepUntil( + abort_other_plans, &address_list.front(), address_list.size(), + m_options.m_stop_others, m_options.m_frame_idx, new_plan_status); + if (new_plan_sp) { + // User level plans should be master plans so they can be interrupted + // (e.g. by hitting a breakpoint) and other plans executed by the + // user (stepping around the breakpoint) and then a "continue" will + // resume the original plan. + new_plan_sp->SetIsMasterPlan(true); + new_plan_sp->SetOkayToDiscard(false); + } else { + result.SetError(new_plan_status); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + result.AppendErrorWithFormat( + "Frame index %u of thread %u has no debug information.\n", + m_options.m_frame_idx, m_options.m_thread_idx); + result.SetStatus(eReturnStatusFailed); + return false; + } + + process->GetThreadList().SetSelectedThreadByID(m_options.m_thread_idx); + + StreamString stream; + Status error; + if (synchronous_execution) + error = process->ResumeSynchronous(&stream); + else + error = process->Resume(); + + if (error.Success()) { + result.AppendMessageWithFormat("Process %" PRIu64 " resuming\n", + process->GetID()); + if (synchronous_execution) { + // If any state changed events had anything to say, add that to the + // result + if (stream.GetSize() > 0) + result.AppendMessage(stream.GetString()); + + result.SetDidChangeProcessState(true); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.SetStatus(eReturnStatusSuccessContinuingNoResult); + } + } else { + result.AppendErrorWithFormat("Failed to resume process: %s.\n", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectThreadSelect + +class CommandObjectThreadSelect : public CommandObjectParsed { +public: + CommandObjectThreadSelect(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "thread select", + "Change the currently selected thread.", nullptr, + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused) { + CommandArgumentEntry arg; + CommandArgumentData thread_idx_arg; + + // Define the first (and only) variant of this arg. + thread_idx_arg.arg_type = eArgTypeThreadIndex; + thread_idx_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(thread_idx_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectThreadSelect() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Process *process = m_exe_ctx.GetProcessPtr(); + if (process == nullptr) { + result.AppendError("no process"); + result.SetStatus(eReturnStatusFailed); + return false; + } else if (command.GetArgumentCount() != 1) { + result.AppendErrorWithFormat( + "'%s' takes exactly one thread index argument:\nUsage: %s\n", + m_cmd_name.c_str(), m_cmd_syntax.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + uint32_t index_id = + StringConvert::ToUInt32(command.GetArgumentAtIndex(0), 0, 0); + + Thread *new_thread = + process->GetThreadList().FindThreadByIndexID(index_id).get(); + if (new_thread == nullptr) { + result.AppendErrorWithFormat("invalid thread #%s.\n", + command.GetArgumentAtIndex(0)); + result.SetStatus(eReturnStatusFailed); + return false; + } + + process->GetThreadList().SetSelectedThreadByID(new_thread->GetID(), true); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + + return result.Succeeded(); + } +}; + +// CommandObjectThreadList + +class CommandObjectThreadList : public CommandObjectParsed { +public: + CommandObjectThreadList(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "thread list", + "Show a summary of each thread in the current target process. " + "Use 'settings set thread-format' to customize the individual " + "thread listings.", + "thread list", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) {} + + ~CommandObjectThreadList() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Stream &strm = result.GetOutputStream(); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + Process *process = m_exe_ctx.GetProcessPtr(); + const bool only_threads_with_stop_reason = false; + const uint32_t start_frame = 0; + const uint32_t num_frames = 0; + const uint32_t num_frames_with_source = 0; + process->GetStatus(strm); + process->GetThreadStatus(strm, only_threads_with_stop_reason, start_frame, + num_frames, num_frames_with_source, false); + return result.Succeeded(); + } +}; + +// CommandObjectThreadInfo +#define LLDB_OPTIONS_thread_info +#include "CommandOptions.inc" + +class CommandObjectThreadInfo : public CommandObjectIterateOverThreads { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_json_thread = false; + m_json_stopinfo = false; + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + const int short_option = m_getopt_table[option_idx].val; + Status error; + + switch (short_option) { + case 'j': + m_json_thread = true; + break; + + case 's': + m_json_stopinfo = true; + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_thread_info_options); + } + + bool m_json_thread; + bool m_json_stopinfo; + }; + + CommandObjectThreadInfo(CommandInterpreter &interpreter) + : CommandObjectIterateOverThreads( + interpreter, "thread info", "Show an extended summary of one or " + "more threads. Defaults to the " + "current thread.", + "thread info", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_options() { + m_add_return = false; + } + + ~CommandObjectThreadInfo() override = default; + + Options *GetOptions() override { return &m_options; } + + bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { + ThreadSP thread_sp = + m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); + if (!thread_sp) { + result.AppendErrorWithFormat("thread no longer exists: 0x%" PRIx64 "\n", + tid); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Thread *thread = thread_sp.get(); + + Stream &strm = result.GetOutputStream(); + if (!thread->GetDescription(strm, eDescriptionLevelFull, + m_options.m_json_thread, + m_options.m_json_stopinfo)) { + result.AppendErrorWithFormat("error displaying info for thread: \"%d\"\n", + thread->GetIndexID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + return true; + } + + CommandOptions m_options; +}; + +// CommandObjectThreadException + +class CommandObjectThreadException : public CommandObjectIterateOverThreads { + public: + CommandObjectThreadException(CommandInterpreter &interpreter) + : CommandObjectIterateOverThreads( + interpreter, "thread exception", + "Display the current exception object for a thread. Defaults to " + "the current thread.", + "thread exception", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) {} + + ~CommandObjectThreadException() override = default; + + bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { + ThreadSP thread_sp = + m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); + if (!thread_sp) { + result.AppendErrorWithFormat("thread no longer exists: 0x%" PRIx64 "\n", + tid); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Stream &strm = result.GetOutputStream(); + ValueObjectSP exception_object_sp = thread_sp->GetCurrentException(); + if (exception_object_sp) { + exception_object_sp->Dump(strm); + } + + ThreadSP exception_thread_sp = thread_sp->GetCurrentExceptionBacktrace(); + if (exception_thread_sp && exception_thread_sp->IsValid()) { + const uint32_t num_frames_with_source = 0; + const bool stop_format = false; + exception_thread_sp->GetStatus(strm, 0, UINT32_MAX, + num_frames_with_source, stop_format); + } + + return true; + } +}; + +// CommandObjectThreadReturn +#define LLDB_OPTIONS_thread_return +#include "CommandOptions.inc" + +class CommandObjectThreadReturn : public CommandObjectRaw { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_from_expression(false) { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'x': { + bool success; + bool tmp_value = + OptionArgParser::ToBoolean(option_arg, false, &success); + if (success) + m_from_expression = tmp_value; + else { + error.SetErrorStringWithFormat( + "invalid boolean value '%s' for 'x' option", + option_arg.str().c_str()); + } + } break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_from_expression = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_thread_return_options); + } + + bool m_from_expression; + + // Instance variables to hold the values for command options. + }; + + CommandObjectThreadReturn(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "thread return", + "Prematurely return from a stack frame, " + "short-circuiting execution of newer frames " + "and optionally yielding a specified value. Defaults " + "to the exiting the current stack " + "frame.", + "thread return", + eCommandRequiresFrame | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused), + m_options() { + CommandArgumentEntry arg; + CommandArgumentData expression_arg; + + // Define the first (and only) variant of this arg. + expression_arg.arg_type = eArgTypeExpression; + expression_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(expression_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectThreadReturn() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + // I am going to handle this by hand, because I don't want you to have to + // say: + // "thread return -- -5". + if (command.startswith("-x")) { + if (command.size() != 2U) + result.AppendWarning("Return values ignored when returning from user " + "called expressions"); + + Thread *thread = m_exe_ctx.GetThreadPtr(); + Status error; + error = thread->UnwindInnermostExpression(); + if (!error.Success()) { + result.AppendErrorWithFormat("Unwinding expression failed - %s.", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } else { + bool success = + thread->SetSelectedFrameByIndexNoisily(0, result.GetOutputStream()); + if (success) { + m_exe_ctx.SetFrameSP(thread->GetSelectedFrame()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat( + "Could not select 0th frame after unwinding expression."); + result.SetStatus(eReturnStatusFailed); + } + } + return result.Succeeded(); + } + + ValueObjectSP return_valobj_sp; + + StackFrameSP frame_sp = m_exe_ctx.GetFrameSP(); + uint32_t frame_idx = frame_sp->GetFrameIndex(); + + if (frame_sp->IsInlined()) { + result.AppendError("Don't know how to return from inlined frames."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!command.empty()) { + Target *target = m_exe_ctx.GetTargetPtr(); + EvaluateExpressionOptions options; + + options.SetUnwindOnError(true); + options.SetUseDynamic(eNoDynamicValues); + + ExpressionResults exe_results = eExpressionSetupError; + exe_results = target->EvaluateExpression(command, frame_sp.get(), + return_valobj_sp, options); + if (exe_results != eExpressionCompleted) { + if (return_valobj_sp) + result.AppendErrorWithFormat( + "Error evaluating result expression: %s", + return_valobj_sp->GetError().AsCString()); + else + result.AppendErrorWithFormat( + "Unknown error evaluating result expression."); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + Status error; + ThreadSP thread_sp = m_exe_ctx.GetThreadSP(); + const bool broadcast = true; + error = thread_sp->ReturnFromFrame(frame_sp, return_valobj_sp, broadcast); + if (!error.Success()) { + result.AppendErrorWithFormat( + "Error returning from frame %d of thread %d: %s.", frame_idx, + thread_sp->GetIndexID(), error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + + CommandOptions m_options; +}; + +// CommandObjectThreadJump +#define LLDB_OPTIONS_thread_jump +#include "CommandOptions.inc" + +class CommandObjectThreadJump : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_filenames.Clear(); + m_line_num = 0; + m_line_offset = 0; + m_load_addr = LLDB_INVALID_ADDRESS; + m_force = false; + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + const int short_option = m_getopt_table[option_idx].val; + Status error; + + switch (short_option) { + case 'f': + m_filenames.AppendIfUnique(FileSpec(option_arg)); + if (m_filenames.GetSize() > 1) + return Status("only one source file expected."); + break; + case 'l': + if (option_arg.getAsInteger(0, m_line_num)) + return Status("invalid line number: '%s'.", option_arg.str().c_str()); + break; + case 'b': + if (option_arg.getAsInteger(0, m_line_offset)) + return Status("invalid line offset: '%s'.", option_arg.str().c_str()); + break; + case 'a': + m_load_addr = OptionArgParser::ToAddress(execution_context, option_arg, + LLDB_INVALID_ADDRESS, &error); + break; + case 'r': + m_force = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_thread_jump_options); + } + + FileSpecList m_filenames; + uint32_t m_line_num; + int32_t m_line_offset; + lldb::addr_t m_load_addr; + bool m_force; + }; + + CommandObjectThreadJump(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "thread jump", + "Sets the program counter to a new address.", "thread jump", + eCommandRequiresFrame | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_options() {} + + ~CommandObjectThreadJump() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + RegisterContext *reg_ctx = m_exe_ctx.GetRegisterContext(); + StackFrame *frame = m_exe_ctx.GetFramePtr(); + Thread *thread = m_exe_ctx.GetThreadPtr(); + Target *target = m_exe_ctx.GetTargetPtr(); + const SymbolContext &sym_ctx = + frame->GetSymbolContext(eSymbolContextLineEntry); + + if (m_options.m_load_addr != LLDB_INVALID_ADDRESS) { + // Use this address directly. + Address dest = Address(m_options.m_load_addr); + + lldb::addr_t callAddr = dest.GetCallableLoadAddress(target); + if (callAddr == LLDB_INVALID_ADDRESS) { + result.AppendErrorWithFormat("Invalid destination address."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!reg_ctx->SetPC(callAddr)) { + result.AppendErrorWithFormat("Error changing PC value for thread %d.", + thread->GetIndexID()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + // Pick either the absolute line, or work out a relative one. + int32_t line = (int32_t)m_options.m_line_num; + if (line == 0) + line = sym_ctx.line_entry.line + m_options.m_line_offset; + + // Try the current file, but override if asked. + FileSpec file = sym_ctx.line_entry.file; + if (m_options.m_filenames.GetSize() == 1) + file = m_options.m_filenames.GetFileSpecAtIndex(0); + + if (!file) { + result.AppendErrorWithFormat( + "No source file available for the current location."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + std::string warnings; + Status err = thread->JumpToLine(file, line, m_options.m_force, &warnings); + + if (err.Fail()) { + result.SetError(err); + return false; + } + + if (!warnings.empty()) + result.AppendWarning(warnings.c_str()); + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + + CommandOptions m_options; +}; + +// Next are the subcommands of CommandObjectMultiwordThreadPlan + +// CommandObjectThreadPlanList +#define LLDB_OPTIONS_thread_plan_list +#include "CommandOptions.inc" + +class CommandObjectThreadPlanList : public CommandObjectIterateOverThreads { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { + // Keep default values of all options in one place: OptionParsingStarting + // () + OptionParsingStarting(nullptr); + } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'i': + m_internal = true; + break; + case 'v': + m_verbose = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_verbose = false; + m_internal = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_thread_plan_list_options); + } + + // Instance variables to hold the values for command options. + bool m_verbose; + bool m_internal; + }; + + CommandObjectThreadPlanList(CommandInterpreter &interpreter) + : CommandObjectIterateOverThreads( + interpreter, "thread plan list", + "Show thread plans for one or more threads. If no threads are " + "specified, show the " + "current thread. Use the thread-index \"all\" to see all threads.", + nullptr, + eCommandRequiresProcess | eCommandRequiresThread | + eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused), + m_options() {} + + ~CommandObjectThreadPlanList() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { + ThreadSP thread_sp = + m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); + if (!thread_sp) { + result.AppendErrorWithFormat("thread no longer exists: 0x%" PRIx64 "\n", + tid); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Thread *thread = thread_sp.get(); + + Stream &strm = result.GetOutputStream(); + DescriptionLevel desc_level = eDescriptionLevelFull; + if (m_options.m_verbose) + desc_level = eDescriptionLevelVerbose; + + thread->DumpThreadPlans(&strm, desc_level, m_options.m_internal, true); + return true; + } + + CommandOptions m_options; +}; + +class CommandObjectThreadPlanDiscard : public CommandObjectParsed { +public: + CommandObjectThreadPlanDiscard(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "thread plan discard", + "Discards thread plans up to and including the " + "specified index (see 'thread plan list'.) " + "Only user visible plans can be discarded.", + nullptr, + eCommandRequiresProcess | eCommandRequiresThread | + eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused) { + CommandArgumentEntry arg; + CommandArgumentData plan_index_arg; + + // Define the first (and only) variant of this arg. + plan_index_arg.arg_type = eArgTypeUnsignedInteger; + plan_index_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(plan_index_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectThreadPlanDiscard() override = default; + + bool DoExecute(Args &args, CommandReturnObject &result) override { + Thread *thread = m_exe_ctx.GetThreadPtr(); + if (args.GetArgumentCount() != 1) { + result.AppendErrorWithFormat("Too many arguments, expected one - the " + "thread plan index - but got %zu.", + args.GetArgumentCount()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + bool success; + uint32_t thread_plan_idx = + StringConvert::ToUInt32(args.GetArgumentAtIndex(0), 0, 0, &success); + if (!success) { + result.AppendErrorWithFormat( + "Invalid thread index: \"%s\" - should be unsigned int.", + args.GetArgumentAtIndex(0)); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (thread_plan_idx == 0) { + result.AppendErrorWithFormat( + "You wouldn't really want me to discard the base thread plan."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (thread->DiscardUserThreadPlansUpToIndex(thread_plan_idx)) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } else { + result.AppendErrorWithFormat( + "Could not find User thread plan with index %s.", + args.GetArgumentAtIndex(0)); + result.SetStatus(eReturnStatusFailed); + return false; + } + } +}; + +// CommandObjectMultiwordThreadPlan + +class CommandObjectMultiwordThreadPlan : public CommandObjectMultiword { +public: + CommandObjectMultiwordThreadPlan(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "plan", + "Commands for managing thread plans that control execution.", + "thread plan <subcommand> [<subcommand objects]") { + LoadSubCommand( + "list", CommandObjectSP(new CommandObjectThreadPlanList(interpreter))); + LoadSubCommand( + "discard", + CommandObjectSP(new CommandObjectThreadPlanDiscard(interpreter))); + } + + ~CommandObjectMultiwordThreadPlan() override = default; +}; + +// CommandObjectMultiwordThread + +CommandObjectMultiwordThread::CommandObjectMultiwordThread( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "thread", "Commands for operating on " + "one or more threads in " + "the current process.", + "thread <subcommand> [<subcommand-options>]") { + LoadSubCommand("backtrace", CommandObjectSP(new CommandObjectThreadBacktrace( + interpreter))); + LoadSubCommand("continue", + CommandObjectSP(new CommandObjectThreadContinue(interpreter))); + LoadSubCommand("list", + CommandObjectSP(new CommandObjectThreadList(interpreter))); + LoadSubCommand("return", + CommandObjectSP(new CommandObjectThreadReturn(interpreter))); + LoadSubCommand("jump", + CommandObjectSP(new CommandObjectThreadJump(interpreter))); + LoadSubCommand("select", + CommandObjectSP(new CommandObjectThreadSelect(interpreter))); + LoadSubCommand("until", + CommandObjectSP(new CommandObjectThreadUntil(interpreter))); + LoadSubCommand("info", + CommandObjectSP(new CommandObjectThreadInfo(interpreter))); + LoadSubCommand( + "exception", + CommandObjectSP(new CommandObjectThreadException(interpreter))); + LoadSubCommand("step-in", + CommandObjectSP(new CommandObjectThreadStepWithTypeAndScope( + interpreter, "thread step-in", + "Source level single step, stepping into calls. Defaults " + "to current thread unless specified.", + nullptr, eStepTypeInto, eStepScopeSource))); + + LoadSubCommand("step-out", + CommandObjectSP(new CommandObjectThreadStepWithTypeAndScope( + interpreter, "thread step-out", + "Finish executing the current stack frame and stop after " + "returning. Defaults to current thread unless specified.", + nullptr, eStepTypeOut, eStepScopeSource))); + + LoadSubCommand("step-over", + CommandObjectSP(new CommandObjectThreadStepWithTypeAndScope( + interpreter, "thread step-over", + "Source level single step, stepping over calls. Defaults " + "to current thread unless specified.", + nullptr, eStepTypeOver, eStepScopeSource))); + + LoadSubCommand("step-inst", + CommandObjectSP(new CommandObjectThreadStepWithTypeAndScope( + interpreter, "thread step-inst", + "Instruction level single step, stepping into calls. " + "Defaults to current thread unless specified.", + nullptr, eStepTypeTrace, eStepScopeInstruction))); + + LoadSubCommand("step-inst-over", + CommandObjectSP(new CommandObjectThreadStepWithTypeAndScope( + interpreter, "thread step-inst-over", + "Instruction level single step, stepping over calls. " + "Defaults to current thread unless specified.", + nullptr, eStepTypeTraceOver, eStepScopeInstruction))); + + LoadSubCommand( + "step-scripted", + CommandObjectSP(new CommandObjectThreadStepWithTypeAndScope( + interpreter, "thread step-scripted", + "Step as instructed by the script class passed in the -C option. " + "You can also specify a dictionary of key (-k) and value (-v) pairs " + "that will be used to populate an SBStructuredData Dictionary, which " + "will be passed to the constructor of the class implementing the " + "scripted step. See the Python Reference for more details.", + nullptr, eStepTypeScripted, eStepScopeSource))); + + LoadSubCommand("plan", CommandObjectSP(new CommandObjectMultiwordThreadPlan( + interpreter))); +} + +CommandObjectMultiwordThread::~CommandObjectMultiwordThread() = default; diff --git a/lldb/source/Commands/CommandObjectThread.h b/lldb/source/Commands/CommandObjectThread.h new file mode 100644 index 0000000000000..77729ceecd631 --- /dev/null +++ b/lldb/source/Commands/CommandObjectThread.h @@ -0,0 +1,25 @@ +//===-- CommandObjectThread.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_CommandObjectThread_h_ +#define liblldb_CommandObjectThread_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +class CommandObjectMultiwordThread : public CommandObjectMultiword { +public: + CommandObjectMultiwordThread(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordThread() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectThread_h_ diff --git a/lldb/source/Commands/CommandObjectType.cpp b/lldb/source/Commands/CommandObjectType.cpp new file mode 100644 index 0000000000000..5e31fd5e8bcee --- /dev/null +++ b/lldb/source/Commands/CommandObjectType.cpp @@ -0,0 +1,3049 @@ +//===-- CommandObjectType.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 "CommandObjectType.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/IOHandler.h" +#include "lldb/DataFormatters/DataVisualization.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupFormat.h" +#include "lldb/Interpreter/OptionValueBoolean.h" +#include "lldb/Interpreter/OptionValueLanguage.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/StringList.h" + +#include "llvm/ADT/STLExtras.h" + +#include <algorithm> +#include <cctype> +#include <functional> +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +class ScriptAddOptions { +public: + TypeSummaryImpl::Flags m_flags; + StringList m_target_types; + bool m_regex; + ConstString m_name; + std::string m_category; + + ScriptAddOptions(const TypeSummaryImpl::Flags &flags, bool regx, + ConstString name, std::string catg) + : m_flags(flags), m_regex(regx), m_name(name), m_category(catg) {} + + typedef std::shared_ptr<ScriptAddOptions> SharedPointer; +}; + +class SynthAddOptions { +public: + bool m_skip_pointers; + bool m_skip_references; + bool m_cascade; + bool m_regex; + StringList m_target_types; + std::string m_category; + + SynthAddOptions(bool sptr, bool sref, bool casc, bool regx, std::string catg) + : m_skip_pointers(sptr), m_skip_references(sref), m_cascade(casc), + m_regex(regx), m_target_types(), m_category(catg) {} + + typedef std::shared_ptr<SynthAddOptions> SharedPointer; +}; + +static bool WarnOnPotentialUnquotedUnsignedType(Args &command, + CommandReturnObject &result) { + if (command.empty()) + return false; + + for (auto entry : llvm::enumerate(command.entries().drop_back())) { + if (entry.value().ref() != "unsigned") + continue; + auto next = command.entries()[entry.index() + 1].ref(); + if (next == "int" || next == "short" || next == "char" || next == "long") { + result.AppendWarningWithFormat( + "unsigned %s being treated as two types. if you meant the combined " + "type " + "name use quotes, as in \"unsigned %s\"\n", + next.str().c_str(), next.str().c_str()); + return true; + } + } + return false; +} + +#define LLDB_OPTIONS_type_summary_add +#include "CommandOptions.inc" + +class CommandObjectTypeSummaryAdd : public CommandObjectParsed, + public IOHandlerDelegateMultiline { +private: + class CommandOptions : public Options { + public: + CommandOptions(CommandInterpreter &interpreter) : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override; + + void OptionParsingStarting(ExecutionContext *execution_context) override; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_summary_add_options); + } + + // Instance variables to hold the values for command options. + + TypeSummaryImpl::Flags m_flags; + bool m_regex; + std::string m_format_string; + ConstString m_name; + std::string m_python_script; + std::string m_python_function; + bool m_is_add_script; + std::string m_category; + }; + + CommandOptions m_options; + + Options *GetOptions() override { return &m_options; } + + bool Execute_ScriptSummary(Args &command, CommandReturnObject &result); + + bool Execute_StringSummary(Args &command, CommandReturnObject &result); + +public: + enum SummaryFormatType { eRegularSummary, eRegexSummary, eNamedSummary }; + + CommandObjectTypeSummaryAdd(CommandInterpreter &interpreter); + + ~CommandObjectTypeSummaryAdd() override = default; + + void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { + static const char *g_summary_addreader_instructions = + "Enter your Python command(s). Type 'DONE' to end.\n" + "def function (valobj,internal_dict):\n" + " \"\"\"valobj: an SBValue which you want to provide a summary " + "for\n" + " internal_dict: an LLDB support object not to be used\"\"\"\n"; + + StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + if (output_sp && interactive) { + output_sp->PutCString(g_summary_addreader_instructions); + output_sp->Flush(); + } + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &data) override { + StreamFileSP error_sp = io_handler.GetErrorStreamFileSP(); + +#ifndef LLDB_DISABLE_PYTHON + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + if (interpreter) { + StringList lines; + lines.SplitIntoLines(data); + if (lines.GetSize() > 0) { + ScriptAddOptions *options_ptr = + ((ScriptAddOptions *)io_handler.GetUserData()); + if (options_ptr) { + ScriptAddOptions::SharedPointer options( + options_ptr); // this will ensure that we get rid of the pointer + // when going out of scope + + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + if (interpreter) { + std::string funct_name_str; + if (interpreter->GenerateTypeScriptFunction(lines, + funct_name_str)) { + if (funct_name_str.empty()) { + error_sp->Printf("unable to obtain a valid function name from " + "the script interpreter.\n"); + error_sp->Flush(); + } else { + // now I have a valid function name, let's add this as script + // for every type in the list + + TypeSummaryImplSP script_format; + script_format = std::make_shared<ScriptSummaryFormat>( + options->m_flags, funct_name_str.c_str(), + lines.CopyList(" ").c_str()); + + Status error; + + for (const std::string &type_name : options->m_target_types) { + CommandObjectTypeSummaryAdd::AddSummary( + ConstString(type_name), script_format, + (options->m_regex + ? CommandObjectTypeSummaryAdd::eRegexSummary + : CommandObjectTypeSummaryAdd::eRegularSummary), + options->m_category, &error); + if (error.Fail()) { + error_sp->Printf("error: %s", error.AsCString()); + error_sp->Flush(); + } + } + + if (options->m_name) { + CommandObjectTypeSummaryAdd::AddSummary( + options->m_name, script_format, + CommandObjectTypeSummaryAdd::eNamedSummary, + options->m_category, &error); + if (error.Fail()) { + CommandObjectTypeSummaryAdd::AddSummary( + options->m_name, script_format, + CommandObjectTypeSummaryAdd::eNamedSummary, + options->m_category, &error); + if (error.Fail()) { + error_sp->Printf("error: %s", error.AsCString()); + error_sp->Flush(); + } + } else { + error_sp->Printf("error: %s", error.AsCString()); + error_sp->Flush(); + } + } else { + if (error.AsCString()) { + error_sp->Printf("error: %s", error.AsCString()); + error_sp->Flush(); + } + } + } + } else { + error_sp->Printf("error: unable to generate a function.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf("error: no script interpreter.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf("error: internal synchronization information " + "missing or invalid.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf("error: empty function, didn't add python command.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf( + "error: script interpreter missing, didn't add python command.\n"); + error_sp->Flush(); + } +#endif // LLDB_DISABLE_PYTHON + io_handler.SetIsDone(true); + } + + static bool AddSummary(ConstString type_name, lldb::TypeSummaryImplSP entry, + SummaryFormatType type, std::string category, + Status *error = nullptr); + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override; +}; + +static const char *g_synth_addreader_instructions = + "Enter your Python command(s). Type 'DONE' to end.\n" + "You must define a Python class with these methods:\n" + " def __init__(self, valobj, dict):\n" + " def num_children(self):\n" + " def get_child_at_index(self, index):\n" + " def get_child_index(self, name):\n" + " def update(self):\n" + " '''Optional'''\n" + "class synthProvider:\n"; + +#define LLDB_OPTIONS_type_synth_add +#include "CommandOptions.inc" + +class CommandObjectTypeSynthAdd : public CommandObjectParsed, + public IOHandlerDelegateMultiline { +private: + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + bool success; + + switch (short_option) { + case 'C': + m_cascade = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error.SetErrorStringWithFormat("invalid value for cascade: %s", + option_arg.str().c_str()); + break; + case 'P': + handwrite_python = true; + break; + case 'l': + m_class_name = std::string(option_arg); + is_class_based = true; + break; + case 'p': + m_skip_pointers = true; + break; + case 'r': + m_skip_references = true; + break; + case 'w': + m_category = std::string(option_arg); + break; + case 'x': + m_regex = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_cascade = true; + m_class_name = ""; + m_skip_pointers = false; + m_skip_references = false; + m_category = "default"; + is_class_based = false; + handwrite_python = false; + m_regex = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_synth_add_options); + } + + // Instance variables to hold the values for command options. + + bool m_cascade; + bool m_skip_references; + bool m_skip_pointers; + std::string m_class_name; + bool m_input_python; + std::string m_category; + bool is_class_based; + bool handwrite_python; + bool m_regex; + }; + + CommandOptions m_options; + + Options *GetOptions() override { return &m_options; } + + bool Execute_HandwritePython(Args &command, CommandReturnObject &result); + + bool Execute_PythonClass(Args &command, CommandReturnObject &result); + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + WarnOnPotentialUnquotedUnsignedType(command, result); + + if (m_options.handwrite_python) + return Execute_HandwritePython(command, result); + else if (m_options.is_class_based) + return Execute_PythonClass(command, result); + else { + result.AppendError("must either provide a children list, a Python class " + "name, or use -P and type a Python class " + "line-by-line"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { + StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + if (output_sp && interactive) { + output_sp->PutCString(g_synth_addreader_instructions); + output_sp->Flush(); + } + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &data) override { + StreamFileSP error_sp = io_handler.GetErrorStreamFileSP(); + +#ifndef LLDB_DISABLE_PYTHON + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + if (interpreter) { + StringList lines; + lines.SplitIntoLines(data); + if (lines.GetSize() > 0) { + SynthAddOptions *options_ptr = + ((SynthAddOptions *)io_handler.GetUserData()); + if (options_ptr) { + SynthAddOptions::SharedPointer options( + options_ptr); // this will ensure that we get rid of the pointer + // when going out of scope + + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + if (interpreter) { + std::string class_name_str; + if (interpreter->GenerateTypeSynthClass(lines, class_name_str)) { + if (class_name_str.empty()) { + error_sp->Printf( + "error: unable to obtain a proper name for the class.\n"); + error_sp->Flush(); + } else { + // everything should be fine now, let's add the synth provider + // class + + SyntheticChildrenSP synth_provider; + synth_provider = std::make_shared<ScriptedSyntheticChildren>( + SyntheticChildren::Flags() + .SetCascades(options->m_cascade) + .SetSkipPointers(options->m_skip_pointers) + .SetSkipReferences(options->m_skip_references), + class_name_str.c_str()); + + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory( + ConstString(options->m_category.c_str()), category); + + Status error; + + for (const std::string &type_name : options->m_target_types) { + if (!type_name.empty()) { + if (!CommandObjectTypeSynthAdd::AddSynth( + ConstString(type_name), synth_provider, + options->m_regex + ? CommandObjectTypeSynthAdd::eRegexSynth + : CommandObjectTypeSynthAdd::eRegularSynth, + options->m_category, &error)) { + error_sp->Printf("error: %s\n", error.AsCString()); + error_sp->Flush(); + break; + } + } else { + error_sp->Printf("error: invalid type name.\n"); + error_sp->Flush(); + break; + } + } + } + } else { + error_sp->Printf("error: unable to generate a class.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf("error: no script interpreter.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf("error: internal synchronization data missing.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf("error: empty function, didn't add python command.\n"); + error_sp->Flush(); + } + } else { + error_sp->Printf( + "error: script interpreter missing, didn't add python command.\n"); + error_sp->Flush(); + } + +#endif // LLDB_DISABLE_PYTHON + io_handler.SetIsDone(true); + } + +public: + enum SynthFormatType { eRegularSynth, eRegexSynth }; + + CommandObjectTypeSynthAdd(CommandInterpreter &interpreter); + + ~CommandObjectTypeSynthAdd() override = default; + + static bool AddSynth(ConstString type_name, lldb::SyntheticChildrenSP entry, + SynthFormatType type, std::string category_name, + Status *error); +}; + +// CommandObjectTypeFormatAdd + +#define LLDB_OPTIONS_type_format_add +#include "CommandOptions.inc" + +class CommandObjectTypeFormatAdd : public CommandObjectParsed { +private: + class CommandOptions : public OptionGroup { + public: + CommandOptions() : OptionGroup() {} + + ~CommandOptions() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_format_add_options); + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_cascade = true; + m_skip_pointers = false; + m_skip_references = false; + m_regex = false; + m_category.assign("default"); + m_custom_type_name.clear(); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + const int short_option = + g_type_format_add_options[option_idx].short_option; + bool success; + + switch (short_option) { + case 'C': + m_cascade = OptionArgParser::ToBoolean(option_value, true, &success); + if (!success) + error.SetErrorStringWithFormat("invalid value for cascade: %s", + option_value.str().c_str()); + break; + case 'p': + m_skip_pointers = true; + break; + case 'w': + m_category.assign(option_value); + break; + case 'r': + m_skip_references = true; + break; + case 'x': + m_regex = true; + break; + case 't': + m_custom_type_name.assign(option_value); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + // Instance variables to hold the values for command options. + + bool m_cascade; + bool m_skip_references; + bool m_skip_pointers; + bool m_regex; + std::string m_category; + std::string m_custom_type_name; + }; + + OptionGroupOptions m_option_group; + OptionGroupFormat m_format_options; + CommandOptions m_command_options; + + Options *GetOptions() override { return &m_option_group; } + +public: + CommandObjectTypeFormatAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type format add", + "Add a new formatting style for a type.", nullptr), + m_option_group(), m_format_options(eFormatInvalid), + m_command_options() { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlus; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + + SetHelpLong( + R"( +The following examples of 'type format add' refer to this code snippet for context: + + typedef int Aint; + typedef float Afloat; + typedef Aint Bint; + typedef Afloat Bfloat; + + Aint ix = 5; + Bint iy = 5; + + Afloat fx = 3.14; + BFloat fy = 3.14; + +Adding default formatting: + +(lldb) type format add -f hex AInt +(lldb) frame variable iy + +)" + " Produces hexadecimal display of iy, because no formatter is available for Bint and \ +the one for Aint is used instead." + R"( + +To prevent this use the cascade option '-C no' to prevent evaluation of typedef chains: + + +(lldb) type format add -f hex -C no AInt + +Similar reasoning applies to this: + +(lldb) type format add -f hex -C no float -p + +)" + " All float values and float references are now formatted as hexadecimal, but not \ +pointers to floats. Nor will it change the default display for Afloat and Bfloat objects."); + + // Add the "--format" to all options groups + m_option_group.Append(&m_format_options, + OptionGroupFormat::OPTION_GROUP_FORMAT, + LLDB_OPT_SET_1); + m_option_group.Append(&m_command_options); + m_option_group.Finalize(); + } + + ~CommandObjectTypeFormatAdd() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1) { + result.AppendErrorWithFormat("%s takes one or more args.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const Format format = m_format_options.GetFormat(); + if (format == eFormatInvalid && + m_command_options.m_custom_type_name.empty()) { + result.AppendErrorWithFormat("%s needs a valid format.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + TypeFormatImplSP entry; + + if (m_command_options.m_custom_type_name.empty()) + entry = std::make_shared<TypeFormatImpl_Format>( + format, TypeFormatImpl::Flags() + .SetCascades(m_command_options.m_cascade) + .SetSkipPointers(m_command_options.m_skip_pointers) + .SetSkipReferences(m_command_options.m_skip_references)); + else + entry = std::make_shared<TypeFormatImpl_EnumType>( + ConstString(m_command_options.m_custom_type_name.c_str()), + TypeFormatImpl::Flags() + .SetCascades(m_command_options.m_cascade) + .SetSkipPointers(m_command_options.m_skip_pointers) + .SetSkipReferences(m_command_options.m_skip_references)); + + // now I have a valid format, let's add it to every type + + TypeCategoryImplSP category_sp; + DataVisualization::Categories::GetCategory( + ConstString(m_command_options.m_category), category_sp); + if (!category_sp) + return false; + + WarnOnPotentialUnquotedUnsignedType(command, result); + + for (auto &arg_entry : command.entries()) { + if (arg_entry.ref().empty()) { + result.AppendError("empty typenames not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ConstString typeCS(arg_entry.ref()); + if (m_command_options.m_regex) { + RegularExpression typeRX(arg_entry.ref()); + if (!typeRX.IsValid()) { + result.AppendError( + "regex format error (maybe this is not really a regex?)"); + result.SetStatus(eReturnStatusFailed); + return false; + } + category_sp->GetRegexTypeSummariesContainer()->Delete(typeCS); + category_sp->GetRegexTypeFormatsContainer()->Add(std::move(typeRX), + entry); + } else + category_sp->GetTypeFormatsContainer()->Add(std::move(typeCS), entry); + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } +}; + +#define LLDB_OPTIONS_type_formatter_delete +#include "CommandOptions.inc" + +class CommandObjectTypeFormatterDelete : public CommandObjectParsed { +protected: + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'a': + m_delete_all = true; + break; + case 'w': + m_category = std::string(option_arg); + break; + case 'l': + m_language = Language::GetLanguageTypeFromString(option_arg); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_delete_all = false; + m_category = "default"; + m_language = lldb::eLanguageTypeUnknown; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_formatter_delete_options); + } + + // Instance variables to hold the values for command options. + + bool m_delete_all; + std::string m_category; + lldb::LanguageType m_language; + }; + + CommandOptions m_options; + uint32_t m_formatter_kind_mask; + + Options *GetOptions() override { return &m_options; } + +public: + CommandObjectTypeFormatterDelete(CommandInterpreter &interpreter, + uint32_t formatter_kind_mask, + const char *name, const char *help) + : CommandObjectParsed(interpreter, name, help, nullptr), m_options(), + m_formatter_kind_mask(formatter_kind_mask) { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlain; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + } + + ~CommandObjectTypeFormatterDelete() override = default; + +protected: + virtual bool FormatterSpecificDeletion(ConstString typeCS) { return false; } + + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc != 1) { + result.AppendErrorWithFormat("%s takes 1 arg.\n", m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *typeA = command.GetArgumentAtIndex(0); + ConstString typeCS(typeA); + + if (!typeCS) { + result.AppendError("empty typenames not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_delete_all) { + DataVisualization::Categories::ForEach( + [this, typeCS](const lldb::TypeCategoryImplSP &category_sp) -> bool { + category_sp->Delete(typeCS, m_formatter_kind_mask); + return true; + }); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } + + bool delete_category = false; + bool extra_deletion = false; + + if (m_options.m_language != lldb::eLanguageTypeUnknown) { + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory(m_options.m_language, + category); + if (category) + delete_category = category->Delete(typeCS, m_formatter_kind_mask); + extra_deletion = FormatterSpecificDeletion(typeCS); + } else { + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory( + ConstString(m_options.m_category.c_str()), category); + if (category) + delete_category = category->Delete(typeCS, m_formatter_kind_mask); + extra_deletion = FormatterSpecificDeletion(typeCS); + } + + if (delete_category || extra_deletion) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } else { + result.AppendErrorWithFormat("no custom formatter for %s.\n", typeA); + result.SetStatus(eReturnStatusFailed); + return false; + } + } +}; + +#define LLDB_OPTIONS_type_formatter_clear +#include "CommandOptions.inc" + +class CommandObjectTypeFormatterClear : public CommandObjectParsed { +private: + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'a': + m_delete_all = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_delete_all = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_formatter_clear_options); + } + + // Instance variables to hold the values for command options. + bool m_delete_all; + }; + + CommandOptions m_options; + uint32_t m_formatter_kind_mask; + + Options *GetOptions() override { return &m_options; } + +public: + CommandObjectTypeFormatterClear(CommandInterpreter &interpreter, + uint32_t formatter_kind_mask, + const char *name, const char *help) + : CommandObjectParsed(interpreter, name, help, nullptr), m_options(), + m_formatter_kind_mask(formatter_kind_mask) {} + + ~CommandObjectTypeFormatterClear() override = default; + +protected: + virtual void FormatterSpecificDeletion() {} + + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (m_options.m_delete_all) { + DataVisualization::Categories::ForEach( + [this](const TypeCategoryImplSP &category_sp) -> bool { + category_sp->Clear(m_formatter_kind_mask); + return true; + }); + } else { + lldb::TypeCategoryImplSP category; + if (command.GetArgumentCount() > 0) { + const char *cat_name = command.GetArgumentAtIndex(0); + ConstString cat_nameCS(cat_name); + DataVisualization::Categories::GetCategory(cat_nameCS, category); + } else { + DataVisualization::Categories::GetCategory(ConstString(nullptr), + category); + } + category->Clear(m_formatter_kind_mask); + } + + FormatterSpecificDeletion(); + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +// CommandObjectTypeFormatDelete + +class CommandObjectTypeFormatDelete : public CommandObjectTypeFormatterDelete { +public: + CommandObjectTypeFormatDelete(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterDelete( + interpreter, + eFormatCategoryItemValue | eFormatCategoryItemRegexValue, + "type format delete", + "Delete an existing formatting style for a type.") {} + + ~CommandObjectTypeFormatDelete() override = default; +}; + +// CommandObjectTypeFormatClear + +class CommandObjectTypeFormatClear : public CommandObjectTypeFormatterClear { +public: + CommandObjectTypeFormatClear(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterClear( + interpreter, + eFormatCategoryItemValue | eFormatCategoryItemRegexValue, + "type format clear", "Delete all existing format styles.") {} +}; + +#define LLDB_OPTIONS_type_formatter_list +#include "CommandOptions.inc" + +template <typename FormatterType> +class CommandObjectTypeFormatterList : public CommandObjectParsed { + typedef typename FormatterType::SharedPointer FormatterSharedPointer; + + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_category_regex("", ""), + m_category_language(lldb::eLanguageTypeUnknown, + lldb::eLanguageTypeUnknown) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + switch (short_option) { + case 'w': + m_category_regex.SetCurrentValue(option_arg); + m_category_regex.SetOptionWasSet(); + break; + case 'l': + error = m_category_language.SetValueFromString(option_arg); + if (error.Success()) + m_category_language.SetOptionWasSet(); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_category_regex.Clear(); + m_category_language.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_formatter_list_options); + } + + // Instance variables to hold the values for command options. + + OptionValueString m_category_regex; + OptionValueLanguage m_category_language; + }; + + CommandOptions m_options; + + Options *GetOptions() override { return &m_options; } + +public: + CommandObjectTypeFormatterList(CommandInterpreter &interpreter, + const char *name, const char *help) + : CommandObjectParsed(interpreter, name, help, nullptr), m_options() { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatOptional; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + } + + ~CommandObjectTypeFormatterList() override = default; + +protected: + virtual bool FormatterSpecificList(CommandReturnObject &result) { + return false; + } + + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + std::unique_ptr<RegularExpression> category_regex; + std::unique_ptr<RegularExpression> formatter_regex; + + if (m_options.m_category_regex.OptionWasSet()) { + category_regex.reset(new RegularExpression( + m_options.m_category_regex.GetCurrentValueAsRef())); + if (!category_regex->IsValid()) { + result.AppendErrorWithFormat( + "syntax error in category regular expression '%s'", + m_options.m_category_regex.GetCurrentValueAsRef().str().c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (argc == 1) { + const char *arg = command.GetArgumentAtIndex(0); + formatter_regex.reset( + new RegularExpression(llvm::StringRef::withNullAsEmpty(arg))); + if (!formatter_regex->IsValid()) { + result.AppendErrorWithFormat("syntax error in regular expression '%s'", + arg); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + bool any_printed = false; + + auto category_closure = [&result, &formatter_regex, &any_printed]( + const lldb::TypeCategoryImplSP &category) -> void { + result.GetOutputStream().Printf( + "-----------------------\nCategory: %s%s\n-----------------------\n", + category->GetName(), category->IsEnabled() ? "" : " (disabled)"); + + TypeCategoryImpl::ForEachCallbacks<FormatterType> foreach; + foreach + .SetExact([&result, &formatter_regex, &any_printed]( + ConstString name, + const FormatterSharedPointer &format_sp) -> bool { + if (formatter_regex) { + bool escape = true; + if (name.GetStringRef() == formatter_regex->GetText()) { + escape = false; + } else if (formatter_regex->Execute(name.GetStringRef())) { + escape = false; + } + + if (escape) + return true; + } + + any_printed = true; + result.GetOutputStream().Printf("%s: %s\n", name.AsCString(), + format_sp->GetDescription().c_str()); + return true; + }); + + foreach + .SetWithRegex([&result, &formatter_regex, &any_printed]( + const RegularExpression ®ex, + const FormatterSharedPointer &format_sp) -> bool { + if (formatter_regex) { + bool escape = true; + if (regex.GetText() == formatter_regex->GetText()) { + escape = false; + } else if (formatter_regex->Execute(regex.GetText())) { + escape = false; + } + + if (escape) + return true; + } + + any_printed = true; + result.GetOutputStream().Printf("%s: %s\n", + regex.GetText().str().c_str(), + format_sp->GetDescription().c_str()); + return true; + }); + + category->ForEach(foreach); + }; + + if (m_options.m_category_language.OptionWasSet()) { + lldb::TypeCategoryImplSP category_sp; + DataVisualization::Categories::GetCategory( + m_options.m_category_language.GetCurrentValue(), category_sp); + if (category_sp) + category_closure(category_sp); + } else { + DataVisualization::Categories::ForEach( + [&category_regex, &category_closure]( + const lldb::TypeCategoryImplSP &category) -> bool { + if (category_regex) { + bool escape = true; + if (category->GetName() == category_regex->GetText()) { + escape = false; + } else if (category_regex->Execute( + llvm::StringRef::withNullAsEmpty( + category->GetName()))) { + escape = false; + } + + if (escape) + return true; + } + + category_closure(category); + + return true; + }); + + any_printed = FormatterSpecificList(result) | any_printed; + } + + if (any_printed) + result.SetStatus(eReturnStatusSuccessFinishResult); + else { + result.GetOutputStream().PutCString("no matching results found.\n"); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + return result.Succeeded(); + } +}; + +// CommandObjectTypeFormatList + +class CommandObjectTypeFormatList + : public CommandObjectTypeFormatterList<TypeFormatImpl> { +public: + CommandObjectTypeFormatList(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterList(interpreter, "type format list", + "Show a list of current formats.") {} +}; + +#ifndef LLDB_DISABLE_PYTHON + +// CommandObjectTypeSummaryAdd + +#endif // LLDB_DISABLE_PYTHON + +Status CommandObjectTypeSummaryAdd::CommandOptions::SetOptionValue( + uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + const int short_option = m_getopt_table[option_idx].val; + bool success; + + switch (short_option) { + case 'C': + m_flags.SetCascades(OptionArgParser::ToBoolean(option_arg, true, &success)); + if (!success) + error.SetErrorStringWithFormat("invalid value for cascade: %s", + option_arg.str().c_str()); + break; + case 'e': + m_flags.SetDontShowChildren(false); + break; + case 'h': + m_flags.SetHideEmptyAggregates(true); + break; + case 'v': + m_flags.SetDontShowValue(true); + break; + case 'c': + m_flags.SetShowMembersOneLiner(true); + break; + case 's': + m_format_string = std::string(option_arg); + break; + case 'p': + m_flags.SetSkipPointers(true); + break; + case 'r': + m_flags.SetSkipReferences(true); + break; + case 'x': + m_regex = true; + break; + case 'n': + m_name.SetString(option_arg); + break; + case 'o': + m_python_script = option_arg; + m_is_add_script = true; + break; + case 'F': + m_python_function = option_arg; + m_is_add_script = true; + break; + case 'P': + m_is_add_script = true; + break; + case 'w': + m_category = std::string(option_arg); + break; + case 'O': + m_flags.SetHideItemNames(true); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; +} + +void CommandObjectTypeSummaryAdd::CommandOptions::OptionParsingStarting( + ExecutionContext *execution_context) { + m_flags.Clear().SetCascades().SetDontShowChildren().SetDontShowValue(false); + m_flags.SetShowMembersOneLiner(false) + .SetSkipPointers(false) + .SetSkipReferences(false) + .SetHideItemNames(false); + + m_regex = false; + m_name.Clear(); + m_python_script = ""; + m_python_function = ""; + m_format_string = ""; + m_is_add_script = false; + m_category = "default"; +} + +#ifndef LLDB_DISABLE_PYTHON + +bool CommandObjectTypeSummaryAdd::Execute_ScriptSummary( + Args &command, CommandReturnObject &result) { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1 && !m_options.m_name) { + result.AppendErrorWithFormat("%s takes one or more args.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + TypeSummaryImplSP script_format; + + if (!m_options.m_python_function + .empty()) // we have a Python function ready to use + { + const char *funct_name = m_options.m_python_function.c_str(); + if (!funct_name || !funct_name[0]) { + result.AppendError("function name empty.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + std::string code = + (" " + m_options.m_python_function + "(valobj,internal_dict)"); + + script_format = std::make_shared<ScriptSummaryFormat>( + m_options.m_flags, funct_name, code.c_str()); + + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + + if (interpreter && !interpreter->CheckObjectExists(funct_name)) + result.AppendWarningWithFormat( + "The provided function \"%s\" does not exist - " + "please define it before attempting to use this summary.\n", + funct_name); + } else if (!m_options.m_python_script + .empty()) // we have a quick 1-line script, just use it + { + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + if (!interpreter) { + result.AppendError("script interpreter missing - unable to generate " + "function wrapper.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + StringList funct_sl; + funct_sl << m_options.m_python_script.c_str(); + std::string funct_name_str; + if (!interpreter->GenerateTypeScriptFunction(funct_sl, funct_name_str)) { + result.AppendError("unable to generate function wrapper.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (funct_name_str.empty()) { + result.AppendError( + "script interpreter failed to generate a valid function name.\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + std::string code = " " + m_options.m_python_script; + + script_format = std::make_shared<ScriptSummaryFormat>( + m_options.m_flags, funct_name_str.c_str(), code.c_str()); + } else { + // Use an IOHandler to grab Python code from the user + ScriptAddOptions *options = + new ScriptAddOptions(m_options.m_flags, m_options.m_regex, + m_options.m_name, m_options.m_category); + + for (auto &entry : command.entries()) { + if (entry.ref().empty()) { + result.AppendError("empty typenames not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + options->m_target_types << entry.ref(); + } + + m_interpreter.GetPythonCommandsFromIOHandler( + " ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + options); // Baton for the "io_handler" that will be passed back into + // our IOHandlerDelegate functions + result.SetStatus(eReturnStatusSuccessFinishNoResult); + + return result.Succeeded(); + } + + // if I am here, script_format must point to something good, so I can add + // that as a script summary to all interested parties + + Status error; + + for (auto &entry : command.entries()) { + CommandObjectTypeSummaryAdd::AddSummary( + ConstString(entry.ref()), script_format, + (m_options.m_regex ? eRegexSummary : eRegularSummary), + m_options.m_category, &error); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (m_options.m_name) { + AddSummary(m_options.m_name, script_format, eNamedSummary, + m_options.m_category, &error); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.AppendError("added to types, but not given a name"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + return result.Succeeded(); +} + +#endif // LLDB_DISABLE_PYTHON + +bool CommandObjectTypeSummaryAdd::Execute_StringSummary( + Args &command, CommandReturnObject &result) { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1 && !m_options.m_name) { + result.AppendErrorWithFormat("%s takes one or more args.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!m_options.m_flags.GetShowMembersOneLiner() && + m_options.m_format_string.empty()) { + result.AppendError("empty summary strings not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const char *format_cstr = (m_options.m_flags.GetShowMembersOneLiner() + ? "" + : m_options.m_format_string.c_str()); + + // ${var%S} is an endless recursion, prevent it + if (strcmp(format_cstr, "${var%S}") == 0) { + result.AppendError("recursive summary not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + std::unique_ptr<StringSummaryFormat> string_format( + new StringSummaryFormat(m_options.m_flags, format_cstr)); + if (!string_format) { + result.AppendError("summary creation failed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (string_format->m_error.Fail()) { + result.AppendErrorWithFormat("syntax error: %s", + string_format->m_error.AsCString("<unknown>")); + result.SetStatus(eReturnStatusFailed); + return false; + } + lldb::TypeSummaryImplSP entry(string_format.release()); + + // now I have a valid format, let's add it to every type + Status error; + for (auto &arg_entry : command.entries()) { + if (arg_entry.ref().empty()) { + result.AppendError("empty typenames not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + ConstString typeCS(arg_entry.ref()); + + AddSummary(typeCS, entry, + (m_options.m_regex ? eRegexSummary : eRegularSummary), + m_options.m_category, &error); + + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + if (m_options.m_name) { + AddSummary(m_options.m_name, entry, eNamedSummary, m_options.m_category, + &error); + if (error.Fail()) { + result.AppendError(error.AsCString()); + result.AppendError("added to types, but not given a name"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); +} + +CommandObjectTypeSummaryAdd::CommandObjectTypeSummaryAdd( + CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type summary add", + "Add a new summary style for a type.", nullptr), + IOHandlerDelegateMultiline("DONE"), m_options(interpreter) { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlus; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + + SetHelpLong( + R"( +The following examples of 'type summary add' refer to this code snippet for context: + + struct JustADemo + { + int* ptr; + float value; + JustADemo(int p = 1, float v = 0.1) : ptr(new int(p)), value(v) {} + }; + JustADemo demo_instance(42, 3.14); + + typedef JustADemo NewDemo; + NewDemo new_demo_instance(42, 3.14); + +(lldb) type summary add --summary-string "the answer is ${*var.ptr}" JustADemo + + Subsequently displaying demo_instance with 'frame variable' or 'expression' will display "the answer is 42" + +(lldb) type summary add --summary-string "the answer is ${*var.ptr}, and the question is ${var.value}" JustADemo + + Subsequently displaying demo_instance with 'frame variable' or 'expression' will display "the answer is 42 and the question is 3.14" + +)" + "Alternatively, you could define formatting for all pointers to integers and \ +rely on that when formatting JustADemo to obtain the same result:" + R"( + +(lldb) type summary add --summary-string "${var%V} -> ${*var}" "int *" +(lldb) type summary add --summary-string "the answer is ${var.ptr}, and the question is ${var.value}" JustADemo + +)" + "Type summaries are automatically applied to derived typedefs, so the examples \ +above apply to both JustADemo and NewDemo. The cascade option can be used to \ +suppress this behavior:" + R"( + +(lldb) type summary add --summary-string "${var.ptr}, ${var.value},{${var.byte}}" JustADemo -C no + + The summary will now be used for values of JustADemo but not NewDemo. + +)" + "By default summaries are shown for pointers and references to values of the \ +specified type. To suppress formatting for pointers use the -p option, or apply \ +the corresponding -r option to suppress formatting for references:" + R"( + +(lldb) type summary add -p -r --summary-string "${var.ptr}, ${var.value},{${var.byte}}" JustADemo + +)" + "One-line summaries including all fields in a type can be inferred without supplying an \ +explicit summary string by passing the -c option:" + R"( + +(lldb) type summary add -c JustADemo +(lldb) frame variable demo_instance +(ptr=<address>, value=3.14) + +)" + "Type summaries normally suppress the nested display of individual fields. To \ +supply a summary to supplement the default structure add the -e option:" + R"( + +(lldb) type summary add -e --summary-string "*ptr = ${*var.ptr}" JustADemo + +)" + "Now when displaying JustADemo values the int* is displayed, followed by the \ +standard LLDB sequence of children, one per line:" + R"( + +*ptr = 42 { + ptr = <address> + value = 3.14 +} + +)" + "You can also add summaries written in Python. These scripts use lldb public API to \ +gather information from your variables and produce a meaningful summary. To start a \ +multi-line script use the -P option. The function declaration will be displayed along with \ +a comment describing the two arguments. End your script with the word 'DONE' on a line by \ +itself:" + R"( + +(lldb) type summary add JustADemo -P +def function (valobj,internal_dict): +"""valobj: an SBValue which you want to provide a summary for +internal_dict: an LLDB support object not to be used""" + value = valobj.GetChildMemberWithName('value'); + return 'My value is ' + value.GetValue(); + DONE + +Alternatively, the -o option can be used when providing a simple one-line Python script: + +(lldb) type summary add JustADemo -o "value = valobj.GetChildMemberWithName('value'); return 'My value is ' + value.GetValue();")"); +} + +bool CommandObjectTypeSummaryAdd::DoExecute(Args &command, + CommandReturnObject &result) { + WarnOnPotentialUnquotedUnsignedType(command, result); + + if (m_options.m_is_add_script) { +#ifndef LLDB_DISABLE_PYTHON + return Execute_ScriptSummary(command, result); +#else + result.AppendError("python is disabled"); + result.SetStatus(eReturnStatusFailed); + return false; +#endif // LLDB_DISABLE_PYTHON + } + + return Execute_StringSummary(command, result); +} + +static bool FixArrayTypeNameWithRegex(ConstString &type_name) { + llvm::StringRef type_name_ref(type_name.GetStringRef()); + + if (type_name_ref.endswith("[]")) { + std::string type_name_str(type_name.GetCString()); + type_name_str.resize(type_name_str.length() - 2); + if (type_name_str.back() != ' ') + type_name_str.append(" \\[[0-9]+\\]"); + else + type_name_str.append("\\[[0-9]+\\]"); + type_name.SetCString(type_name_str.c_str()); + return true; + } + return false; +} + +bool CommandObjectTypeSummaryAdd::AddSummary(ConstString type_name, + TypeSummaryImplSP entry, + SummaryFormatType type, + std::string category_name, + Status *error) { + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory(ConstString(category_name.c_str()), + category); + + if (type == eRegularSummary) { + if (FixArrayTypeNameWithRegex(type_name)) + type = eRegexSummary; + } + + if (type == eRegexSummary) { + RegularExpression typeRX(type_name.GetStringRef()); + if (!typeRX.IsValid()) { + if (error) + error->SetErrorString( + "regex format error (maybe this is not really a regex?)"); + return false; + } + + category->GetRegexTypeSummariesContainer()->Delete(type_name); + category->GetRegexTypeSummariesContainer()->Add(std::move(typeRX), entry); + + return true; + } else if (type == eNamedSummary) { + // system named summaries do not exist (yet?) + DataVisualization::NamedSummaryFormats::Add(type_name, entry); + return true; + } else { + category->GetTypeSummariesContainer()->Add(std::move(type_name), entry); + return true; + } +} + +// CommandObjectTypeSummaryDelete + +class CommandObjectTypeSummaryDelete : public CommandObjectTypeFormatterDelete { +public: + CommandObjectTypeSummaryDelete(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterDelete( + interpreter, + eFormatCategoryItemSummary | eFormatCategoryItemRegexSummary, + "type summary delete", "Delete an existing summary for a type.") {} + + ~CommandObjectTypeSummaryDelete() override = default; + +protected: + bool FormatterSpecificDeletion(ConstString typeCS) override { + if (m_options.m_language != lldb::eLanguageTypeUnknown) + return false; + return DataVisualization::NamedSummaryFormats::Delete(typeCS); + } +}; + +class CommandObjectTypeSummaryClear : public CommandObjectTypeFormatterClear { +public: + CommandObjectTypeSummaryClear(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterClear( + interpreter, + eFormatCategoryItemSummary | eFormatCategoryItemRegexSummary, + "type summary clear", "Delete all existing summaries.") {} + +protected: + void FormatterSpecificDeletion() override { + DataVisualization::NamedSummaryFormats::Clear(); + } +}; + +// CommandObjectTypeSummaryList + +class CommandObjectTypeSummaryList + : public CommandObjectTypeFormatterList<TypeSummaryImpl> { +public: + CommandObjectTypeSummaryList(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterList(interpreter, "type summary list", + "Show a list of current summaries.") {} + +protected: + bool FormatterSpecificList(CommandReturnObject &result) override { + if (DataVisualization::NamedSummaryFormats::GetCount() > 0) { + result.GetOutputStream().Printf("Named summaries:\n"); + DataVisualization::NamedSummaryFormats::ForEach( + [&result](ConstString name, + const TypeSummaryImplSP &summary_sp) -> bool { + result.GetOutputStream().Printf( + "%s: %s\n", name.AsCString(), + summary_sp->GetDescription().c_str()); + return true; + }); + return true; + } + return false; + } +}; + +// CommandObjectTypeCategoryDefine +#define LLDB_OPTIONS_type_category_define +#include "CommandOptions.inc" + +class CommandObjectTypeCategoryDefine : public CommandObjectParsed { + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_define_enabled(false, false), + m_cate_language(eLanguageTypeUnknown, eLanguageTypeUnknown) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'e': + m_define_enabled.SetValueFromString(llvm::StringRef("true")); + break; + case 'l': + error = m_cate_language.SetValueFromString(option_arg); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_define_enabled.Clear(); + m_cate_language.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_category_define_options); + } + + // Instance variables to hold the values for command options. + + OptionValueBoolean m_define_enabled; + OptionValueLanguage m_cate_language; + }; + + CommandOptions m_options; + + Options *GetOptions() override { return &m_options; } + +public: + CommandObjectTypeCategoryDefine(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type category define", + "Define a new category as a source of formatters.", + nullptr), + m_options() { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlus; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + } + + ~CommandObjectTypeCategoryDefine() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1) { + result.AppendErrorWithFormat("%s takes 1 or more args.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + for (auto &entry : command.entries()) { + TypeCategoryImplSP category_sp; + if (DataVisualization::Categories::GetCategory(ConstString(entry.ref()), + category_sp) && + category_sp) { + category_sp->AddLanguage(m_options.m_cate_language.GetCurrentValue()); + if (m_options.m_define_enabled.GetCurrentValue()) + DataVisualization::Categories::Enable(category_sp, + TypeCategoryMap::Default); + } + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +// CommandObjectTypeCategoryEnable +#define LLDB_OPTIONS_type_category_enable +#include "CommandOptions.inc" + +class CommandObjectTypeCategoryEnable : public CommandObjectParsed { + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'l': + if (!option_arg.empty()) { + m_language = Language::GetLanguageTypeFromString(option_arg); + if (m_language == lldb::eLanguageTypeUnknown) + error.SetErrorStringWithFormat("unrecognized language '%s'", + option_arg.str().c_str()); + } + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_language = lldb::eLanguageTypeUnknown; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_category_enable_options); + } + + // Instance variables to hold the values for command options. + + lldb::LanguageType m_language; + }; + + CommandOptions m_options; + + Options *GetOptions() override { return &m_options; } + +public: + CommandObjectTypeCategoryEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type category enable", + "Enable a category as a source of formatters.", + nullptr), + m_options() { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlus; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + } + + ~CommandObjectTypeCategoryEnable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1 && m_options.m_language == lldb::eLanguageTypeUnknown) { + result.AppendErrorWithFormat("%s takes arguments and/or a language", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (argc == 1 && strcmp(command.GetArgumentAtIndex(0), "*") == 0) { + DataVisualization::Categories::EnableStar(); + } else if (argc > 0) { + for (int i = argc - 1; i >= 0; i--) { + const char *typeA = command.GetArgumentAtIndex(i); + ConstString typeCS(typeA); + + if (!typeCS) { + result.AppendError("empty category name not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + DataVisualization::Categories::Enable(typeCS); + lldb::TypeCategoryImplSP cate; + if (DataVisualization::Categories::GetCategory(typeCS, cate) && cate) { + if (cate->GetCount() == 0) { + result.AppendWarning("empty category enabled (typo?)"); + } + } + } + } + + if (m_options.m_language != lldb::eLanguageTypeUnknown) + DataVisualization::Categories::Enable(m_options.m_language); + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +// CommandObjectTypeCategoryDelete + +class CommandObjectTypeCategoryDelete : public CommandObjectParsed { +public: + CommandObjectTypeCategoryDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type category delete", + "Delete a category and all associated formatters.", + nullptr) { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlus; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + } + + ~CommandObjectTypeCategoryDelete() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1) { + result.AppendErrorWithFormat("%s takes 1 or more arg.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + bool success = true; + + // the order is not relevant here + for (int i = argc - 1; i >= 0; i--) { + const char *typeA = command.GetArgumentAtIndex(i); + ConstString typeCS(typeA); + + if (!typeCS) { + result.AppendError("empty category name not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + if (!DataVisualization::Categories::Delete(typeCS)) + success = false; // keep deleting even if we hit an error + } + if (success) { + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } else { + result.AppendError("cannot delete one or more categories\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + } +}; + +// CommandObjectTypeCategoryDisable +#define LLDB_OPTIONS_type_category_disable +#include "CommandOptions.inc" + +class CommandObjectTypeCategoryDisable : public CommandObjectParsed { + class CommandOptions : public Options { + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'l': + if (!option_arg.empty()) { + m_language = Language::GetLanguageTypeFromString(option_arg); + if (m_language == lldb::eLanguageTypeUnknown) + error.SetErrorStringWithFormat("unrecognized language '%s'", + option_arg.str().c_str()); + } + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_language = lldb::eLanguageTypeUnknown; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_category_disable_options); + } + + // Instance variables to hold the values for command options. + + lldb::LanguageType m_language; + }; + + CommandOptions m_options; + + Options *GetOptions() override { return &m_options; } + +public: + CommandObjectTypeCategoryDisable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type category disable", + "Disable a category as a source of formatters.", + nullptr), + m_options() { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlus; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + } + + ~CommandObjectTypeCategoryDisable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1 && m_options.m_language == lldb::eLanguageTypeUnknown) { + result.AppendErrorWithFormat("%s takes arguments and/or a language", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (argc == 1 && strcmp(command.GetArgumentAtIndex(0), "*") == 0) { + DataVisualization::Categories::DisableStar(); + } else if (argc > 0) { + // the order is not relevant here + for (int i = argc - 1; i >= 0; i--) { + const char *typeA = command.GetArgumentAtIndex(i); + ConstString typeCS(typeA); + + if (!typeCS) { + result.AppendError("empty category name not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + DataVisualization::Categories::Disable(typeCS); + } + } + + if (m_options.m_language != lldb::eLanguageTypeUnknown) + DataVisualization::Categories::Disable(m_options.m_language); + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +// CommandObjectTypeCategoryList + +class CommandObjectTypeCategoryList : public CommandObjectParsed { +public: + CommandObjectTypeCategoryList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type category list", + "Provide a list of all existing categories.", + nullptr) { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatOptional; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + } + + ~CommandObjectTypeCategoryList() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + std::unique_ptr<RegularExpression> regex; + + if (argc == 1) { + const char *arg = command.GetArgumentAtIndex(0); + regex.reset(new RegularExpression(llvm::StringRef::withNullAsEmpty(arg))); + if (!regex->IsValid()) { + result.AppendErrorWithFormat( + "syntax error in category regular expression '%s'", arg); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else if (argc != 0) { + result.AppendErrorWithFormat("%s takes 0 or one arg.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + DataVisualization::Categories::ForEach( + [®ex, &result](const lldb::TypeCategoryImplSP &category_sp) -> bool { + if (regex) { + bool escape = true; + if (regex->GetText() == category_sp->GetName()) { + escape = false; + } else if (regex->Execute(llvm::StringRef::withNullAsEmpty( + category_sp->GetName()))) { + escape = false; + } + + if (escape) + return true; + } + + result.GetOutputStream().Printf( + "Category: %s\n", category_sp->GetDescription().c_str()); + + return true; + }); + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +// CommandObjectTypeFilterList + +class CommandObjectTypeFilterList + : public CommandObjectTypeFormatterList<TypeFilterImpl> { +public: + CommandObjectTypeFilterList(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterList(interpreter, "type filter list", + "Show a list of current filters.") {} +}; + +#ifndef LLDB_DISABLE_PYTHON + +// CommandObjectTypeSynthList + +class CommandObjectTypeSynthList + : public CommandObjectTypeFormatterList<SyntheticChildren> { +public: + CommandObjectTypeSynthList(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterList( + interpreter, "type synthetic list", + "Show a list of current synthetic providers.") {} +}; + +#endif // LLDB_DISABLE_PYTHON + +// CommandObjectTypeFilterDelete + +class CommandObjectTypeFilterDelete : public CommandObjectTypeFormatterDelete { +public: + CommandObjectTypeFilterDelete(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterDelete( + interpreter, + eFormatCategoryItemFilter | eFormatCategoryItemRegexFilter, + "type filter delete", "Delete an existing filter for a type.") {} + + ~CommandObjectTypeFilterDelete() override = default; +}; + +#ifndef LLDB_DISABLE_PYTHON + +// CommandObjectTypeSynthDelete + +class CommandObjectTypeSynthDelete : public CommandObjectTypeFormatterDelete { +public: + CommandObjectTypeSynthDelete(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterDelete( + interpreter, + eFormatCategoryItemSynth | eFormatCategoryItemRegexSynth, + "type synthetic delete", + "Delete an existing synthetic provider for a type.") {} + + ~CommandObjectTypeSynthDelete() override = default; +}; + +#endif // LLDB_DISABLE_PYTHON + +// CommandObjectTypeFilterClear + +class CommandObjectTypeFilterClear : public CommandObjectTypeFormatterClear { +public: + CommandObjectTypeFilterClear(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterClear( + interpreter, + eFormatCategoryItemFilter | eFormatCategoryItemRegexFilter, + "type filter clear", "Delete all existing filter.") {} +}; + +#ifndef LLDB_DISABLE_PYTHON +// CommandObjectTypeSynthClear + +class CommandObjectTypeSynthClear : public CommandObjectTypeFormatterClear { +public: + CommandObjectTypeSynthClear(CommandInterpreter &interpreter) + : CommandObjectTypeFormatterClear( + interpreter, + eFormatCategoryItemSynth | eFormatCategoryItemRegexSynth, + "type synthetic clear", + "Delete all existing synthetic providers.") {} +}; + +bool CommandObjectTypeSynthAdd::Execute_HandwritePython( + Args &command, CommandReturnObject &result) { + SynthAddOptions *options = new SynthAddOptions( + m_options.m_skip_pointers, m_options.m_skip_references, + m_options.m_cascade, m_options.m_regex, m_options.m_category); + + for (auto &entry : command.entries()) { + if (entry.ref().empty()) { + result.AppendError("empty typenames not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + options->m_target_types << entry.ref(); + } + + m_interpreter.GetPythonCommandsFromIOHandler( + " ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + options); // Baton for the "io_handler" that will be passed back into our + // IOHandlerDelegate functions + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); +} + +bool CommandObjectTypeSynthAdd::Execute_PythonClass( + Args &command, CommandReturnObject &result) { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1) { + result.AppendErrorWithFormat("%s takes one or more args.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_class_name.empty() && !m_options.m_input_python) { + result.AppendErrorWithFormat("%s needs either a Python class name or -P to " + "directly input Python code.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + SyntheticChildrenSP entry; + + ScriptedSyntheticChildren *impl = new ScriptedSyntheticChildren( + SyntheticChildren::Flags() + .SetCascades(m_options.m_cascade) + .SetSkipPointers(m_options.m_skip_pointers) + .SetSkipReferences(m_options.m_skip_references), + m_options.m_class_name.c_str()); + + entry.reset(impl); + + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + + if (interpreter && + !interpreter->CheckObjectExists(impl->GetPythonClassName())) + result.AppendWarning("The provided class does not exist - please define it " + "before attempting to use this synthetic provider"); + + // now I have a valid provider, let's add it to every type + + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory( + ConstString(m_options.m_category.c_str()), category); + + Status error; + + for (auto &arg_entry : command.entries()) { + if (arg_entry.ref().empty()) { + result.AppendError("empty typenames not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ConstString typeCS(arg_entry.ref()); + if (!AddSynth(typeCS, entry, + m_options.m_regex ? eRegexSynth : eRegularSynth, + m_options.m_category, &error)) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); +} + +CommandObjectTypeSynthAdd::CommandObjectTypeSynthAdd( + CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type synthetic add", + "Add a new synthetic provider for a type.", nullptr), + IOHandlerDelegateMultiline("DONE"), m_options() { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlus; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); +} + +bool CommandObjectTypeSynthAdd::AddSynth(ConstString type_name, + SyntheticChildrenSP entry, + SynthFormatType type, + std::string category_name, + Status *error) { + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory(ConstString(category_name.c_str()), + category); + + if (type == eRegularSynth) { + if (FixArrayTypeNameWithRegex(type_name)) + type = eRegexSynth; + } + + if (category->AnyMatches(type_name, eFormatCategoryItemFilter | + eFormatCategoryItemRegexFilter, + false)) { + if (error) + error->SetErrorStringWithFormat("cannot add synthetic for type %s when " + "filter is defined in same category!", + type_name.AsCString()); + return false; + } + + if (type == eRegexSynth) { + RegularExpression typeRX(type_name.GetStringRef()); + if (!typeRX.IsValid()) { + if (error) + error->SetErrorString( + "regex format error (maybe this is not really a regex?)"); + return false; + } + + category->GetRegexTypeSyntheticsContainer()->Delete(type_name); + category->GetRegexTypeSyntheticsContainer()->Add(std::move(typeRX), entry); + + return true; + } else { + category->GetTypeSyntheticsContainer()->Add(std::move(type_name), entry); + return true; + } +} + +#endif // LLDB_DISABLE_PYTHON +#define LLDB_OPTIONS_type_filter_add +#include "CommandOptions.inc" + +class CommandObjectTypeFilterAdd : public CommandObjectParsed { +private: + class CommandOptions : public Options { + typedef std::vector<std::string> option_vector; + + public: + CommandOptions() : Options() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + bool success; + + switch (short_option) { + case 'C': + m_cascade = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error.SetErrorStringWithFormat("invalid value for cascade: %s", + option_arg.str().c_str()); + break; + case 'c': + m_expr_paths.push_back(option_arg); + has_child_list = true; + break; + case 'p': + m_skip_pointers = true; + break; + case 'r': + m_skip_references = true; + break; + case 'w': + m_category = std::string(option_arg); + break; + case 'x': + m_regex = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_cascade = true; + m_skip_pointers = false; + m_skip_references = false; + m_category = "default"; + m_expr_paths.clear(); + has_child_list = false; + m_regex = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_filter_add_options); + } + + // Instance variables to hold the values for command options. + + bool m_cascade; + bool m_skip_references; + bool m_skip_pointers; + bool m_input_python; + option_vector m_expr_paths; + std::string m_category; + bool has_child_list; + bool m_regex; + + typedef option_vector::iterator ExpressionPathsIterator; + }; + + CommandOptions m_options; + + Options *GetOptions() override { return &m_options; } + + enum FilterFormatType { eRegularFilter, eRegexFilter }; + + bool AddFilter(ConstString type_name, TypeFilterImplSP entry, + FilterFormatType type, std::string category_name, + Status *error) { + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory( + ConstString(category_name.c_str()), category); + + if (type == eRegularFilter) { + if (FixArrayTypeNameWithRegex(type_name)) + type = eRegexFilter; + } + + if (category->AnyMatches(type_name, eFormatCategoryItemSynth | + eFormatCategoryItemRegexSynth, + false)) { + if (error) + error->SetErrorStringWithFormat("cannot add filter for type %s when " + "synthetic is defined in same " + "category!", + type_name.AsCString()); + return false; + } + + if (type == eRegexFilter) { + RegularExpression typeRX(type_name.GetStringRef()); + if (!typeRX.IsValid()) { + if (error) + error->SetErrorString( + "regex format error (maybe this is not really a regex?)"); + return false; + } + + category->GetRegexTypeFiltersContainer()->Delete(type_name); + category->GetRegexTypeFiltersContainer()->Add(std::move(typeRX), entry); + + return true; + } else { + category->GetTypeFiltersContainer()->Add(std::move(type_name), entry); + return true; + } + } + +public: + CommandObjectTypeFilterAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "type filter add", + "Add a new filter for a type.", nullptr), + m_options() { + CommandArgumentEntry type_arg; + CommandArgumentData type_style_arg; + + type_style_arg.arg_type = eArgTypeName; + type_style_arg.arg_repetition = eArgRepeatPlus; + + type_arg.push_back(type_style_arg); + + m_arguments.push_back(type_arg); + + SetHelpLong( + R"( +The following examples of 'type filter add' refer to this code snippet for context: + + class Foo { + int a; + int b; + int c; + int d; + int e; + int f; + int g; + int h; + int i; + } + Foo my_foo; + +Adding a simple filter: + +(lldb) type filter add --child a --child g Foo +(lldb) frame variable my_foo + +)" + "Produces output where only a and g are displayed. Other children of my_foo \ +(b, c, d, e, f, h and i) are available by asking for them explicitly:" + R"( + +(lldb) frame variable my_foo.b my_foo.c my_foo.i + +)" + "The formatting option --raw on frame variable bypasses the filter, showing \ +all children of my_foo as if no filter was defined:" + R"( + +(lldb) frame variable my_foo --raw)"); + } + + ~CommandObjectTypeFilterAdd() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + const size_t argc = command.GetArgumentCount(); + + if (argc < 1) { + result.AppendErrorWithFormat("%s takes one or more args.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_expr_paths.empty()) { + result.AppendErrorWithFormat("%s needs one or more children.\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + TypeFilterImplSP entry(new TypeFilterImpl( + SyntheticChildren::Flags() + .SetCascades(m_options.m_cascade) + .SetSkipPointers(m_options.m_skip_pointers) + .SetSkipReferences(m_options.m_skip_references))); + + // go through the expression paths + CommandOptions::ExpressionPathsIterator begin, + end = m_options.m_expr_paths.end(); + + for (begin = m_options.m_expr_paths.begin(); begin != end; begin++) + entry->AddExpressionPath(*begin); + + // now I have a valid provider, let's add it to every type + + lldb::TypeCategoryImplSP category; + DataVisualization::Categories::GetCategory( + ConstString(m_options.m_category.c_str()), category); + + Status error; + + WarnOnPotentialUnquotedUnsignedType(command, result); + + for (auto &arg_entry : command.entries()) { + if (arg_entry.ref().empty()) { + result.AppendError("empty typenames not allowed"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ConstString typeCS(arg_entry.ref()); + if (!AddFilter(typeCS, entry, + m_options.m_regex ? eRegexFilter : eRegularFilter, + m_options.m_category, &error)) { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } +}; + +// "type lookup" +#define LLDB_OPTIONS_type_lookup +#include "CommandOptions.inc" + +class CommandObjectTypeLookup : public CommandObjectRaw { +protected: + // this function is allowed to do a more aggressive job at guessing languages + // than the expression parser is comfortable with - so leave the original + // call alone and add one that is specific to type lookup + lldb::LanguageType GuessLanguage(StackFrame *frame) { + lldb::LanguageType lang_type = lldb::eLanguageTypeUnknown; + + if (!frame) + return lang_type; + + lang_type = frame->GuessLanguage(); + if (lang_type != lldb::eLanguageTypeUnknown) + return lang_type; + + Symbol *s = frame->GetSymbolContext(eSymbolContextSymbol).symbol; + if (s) + lang_type = s->GetMangled().GuessLanguage(); + + return lang_type; + } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() + : OptionGroup(), m_show_help(false), m_language(eLanguageTypeUnknown) {} + + ~CommandOptions() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_type_lookup_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + + const int short_option = g_type_lookup_options[option_idx].short_option; + + switch (short_option) { + case 'h': + m_show_help = true; + break; + + case 'l': + m_language = Language::GetLanguageTypeFromString(option_value); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_show_help = false; + m_language = eLanguageTypeUnknown; + } + + // Options table: Required for subclasses of Options. + + bool m_show_help; + lldb::LanguageType m_language; + }; + + OptionGroupOptions m_option_group; + CommandOptions m_command_options; + +public: + CommandObjectTypeLookup(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "type lookup", + "Lookup types and declarations in the current target, " + "following language-specific naming conventions.", + "type lookup <type-specifier>", + eCommandRequiresTarget), + m_option_group(), m_command_options() { + m_option_group.Append(&m_command_options); + m_option_group.Finalize(); + } + + ~CommandObjectTypeLookup() override = default; + + Options *GetOptions() override { return &m_option_group; } + + llvm::StringRef GetHelpLong() override { + if (!m_cmd_help_long.empty()) + return m_cmd_help_long; + + StreamString stream; + Language::ForEach([&](Language *lang) { + if (const char *help = lang->GetLanguageSpecificTypeLookupHelp()) + stream.Printf("%s\n", help); + return true; + }); + + m_cmd_help_long = stream.GetString(); + return m_cmd_help_long; + } + + bool DoExecute(llvm::StringRef raw_command_line, + CommandReturnObject &result) override { + if (raw_command_line.empty()) { + result.SetError( + "type lookup cannot be invoked without a type name as argument"); + return false; + } + + auto exe_ctx = GetCommandInterpreter().GetExecutionContext(); + m_option_group.NotifyOptionParsingStarting(&exe_ctx); + + OptionsWithRaw args(raw_command_line); + const char *name_of_type = args.GetRawPart().c_str(); + + if (args.HasArgs()) + if (!ParseOptionsAndNotify(args.GetArgs(), result, m_option_group, + exe_ctx)) + return false; + + ExecutionContextScope *best_scope = exe_ctx.GetBestExecutionContextScope(); + + bool any_found = false; + + std::vector<Language *> languages; + + bool is_global_search = false; + LanguageType guessed_language = lldb::eLanguageTypeUnknown; + + if ((is_global_search = + (m_command_options.m_language == eLanguageTypeUnknown))) { + Language::ForEach([&](Language *lang) { + languages.push_back(lang); + return true; + }); + } else { + languages.push_back(Language::FindPlugin(m_command_options.m_language)); + } + + // This is not the most efficient way to do this, but we support very few + // languages so the cost of the sort is going to be dwarfed by the actual + // lookup anyway + if (StackFrame *frame = m_exe_ctx.GetFramePtr()) { + guessed_language = GuessLanguage(frame); + if (guessed_language != eLanguageTypeUnknown) { + llvm::sort( + languages.begin(), languages.end(), + [guessed_language](Language *lang1, Language *lang2) -> bool { + if (!lang1 || !lang2) + return false; + LanguageType lt1 = lang1->GetLanguageType(); + LanguageType lt2 = lang2->GetLanguageType(); + if (lt1 == guessed_language) + return true; // make the selected frame's language come first + if (lt2 == guessed_language) + return false; // make the selected frame's language come first + return (lt1 < lt2); // normal comparison otherwise + }); + } + } + + bool is_first_language = true; + + for (Language *language : languages) { + if (!language) + continue; + + if (auto scavenger = language->GetTypeScavenger()) { + Language::TypeScavenger::ResultSet search_results; + if (scavenger->Find(best_scope, name_of_type, search_results) > 0) { + for (const auto &search_result : search_results) { + if (search_result && search_result->IsValid()) { + any_found = true; + search_result->DumpToStream(result.GetOutputStream(), + this->m_command_options.m_show_help); + } + } + } + } + // this is "type lookup SomeName" and we did find a match, so get out + if (any_found && is_global_search) + break; + else if (is_first_language && is_global_search && + guessed_language != lldb::eLanguageTypeUnknown) { + is_first_language = false; + result.GetOutputStream().Printf( + "no type was found in the current language %s matching '%s'; " + "performing a global search across all languages\n", + Language::GetNameForLanguageType(guessed_language), name_of_type); + } + } + + if (!any_found) + result.AppendMessageWithFormat("no type was found matching '%s'\n", + name_of_type); + + result.SetStatus(any_found ? lldb::eReturnStatusSuccessFinishResult + : lldb::eReturnStatusSuccessFinishNoResult); + return true; + } +}; + +template <typename FormatterType> +class CommandObjectFormatterInfo : public CommandObjectRaw { +public: + typedef std::function<typename FormatterType::SharedPointer(ValueObject &)> + DiscoveryFunction; + CommandObjectFormatterInfo(CommandInterpreter &interpreter, + const char *formatter_name, + DiscoveryFunction discovery_func) + : CommandObjectRaw(interpreter, "", "", "", + eCommandRequiresFrame), + m_formatter_name(formatter_name ? formatter_name : ""), + m_discovery_function(discovery_func) { + StreamString name; + name.Printf("type %s info", formatter_name); + SetCommandName(name.GetString()); + StreamString help; + help.Printf("This command evaluates the provided expression and shows " + "which %s is applied to the resulting value (if any).", + formatter_name); + SetHelp(help.GetString()); + StreamString syntax; + syntax.Printf("type %s info <expr>", formatter_name); + SetSyntax(syntax.GetString()); + } + + ~CommandObjectFormatterInfo() override = default; + +protected: + bool DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + TargetSP target_sp = GetDebugger().GetSelectedTarget(); + Thread *thread = GetDefaultThread(); + if (!thread) { + result.AppendError("no default thread"); + result.SetStatus(lldb::eReturnStatusFailed); + return false; + } + + StackFrameSP frame_sp = thread->GetSelectedFrame(); + ValueObjectSP result_valobj_sp; + EvaluateExpressionOptions options; + lldb::ExpressionResults expr_result = target_sp->EvaluateExpression( + command, frame_sp.get(), result_valobj_sp, options); + if (expr_result == eExpressionCompleted && result_valobj_sp) { + result_valobj_sp = + result_valobj_sp->GetQualifiedRepresentationIfAvailable( + target_sp->GetPreferDynamicValue(), + target_sp->GetEnableSyntheticValue()); + typename FormatterType::SharedPointer formatter_sp = + m_discovery_function(*result_valobj_sp); + if (formatter_sp) { + std::string description(formatter_sp->GetDescription()); + result.GetOutputStream() + << m_formatter_name << " applied to (" + << result_valobj_sp->GetDisplayTypeName().AsCString("<unknown>") + << ") " << command << " is: " << description << "\n"; + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); + } else { + result.GetOutputStream() + << "no " << m_formatter_name << " applies to (" + << result_valobj_sp->GetDisplayTypeName().AsCString("<unknown>") + << ") " << command << "\n"; + result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult); + } + return true; + } else { + result.AppendError("failed to evaluate expression"); + result.SetStatus(lldb::eReturnStatusFailed); + return false; + } + } + +private: + std::string m_formatter_name; + DiscoveryFunction m_discovery_function; +}; + +class CommandObjectTypeFormat : public CommandObjectMultiword { +public: + CommandObjectTypeFormat(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "type format", + "Commands for customizing value display formats.", + "type format [<sub-command-options>] ") { + LoadSubCommand( + "add", CommandObjectSP(new CommandObjectTypeFormatAdd(interpreter))); + LoadSubCommand("clear", CommandObjectSP( + new CommandObjectTypeFormatClear(interpreter))); + LoadSubCommand("delete", CommandObjectSP(new CommandObjectTypeFormatDelete( + interpreter))); + LoadSubCommand( + "list", CommandObjectSP(new CommandObjectTypeFormatList(interpreter))); + LoadSubCommand( + "info", CommandObjectSP(new CommandObjectFormatterInfo<TypeFormatImpl>( + interpreter, "format", + [](ValueObject &valobj) -> TypeFormatImpl::SharedPointer { + return valobj.GetValueFormat(); + }))); + } + + ~CommandObjectTypeFormat() override = default; +}; + +#ifndef LLDB_DISABLE_PYTHON + +class CommandObjectTypeSynth : public CommandObjectMultiword { +public: + CommandObjectTypeSynth(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "type synthetic", + "Commands for operating on synthetic type representations.", + "type synthetic [<sub-command-options>] ") { + LoadSubCommand("add", + CommandObjectSP(new CommandObjectTypeSynthAdd(interpreter))); + LoadSubCommand( + "clear", CommandObjectSP(new CommandObjectTypeSynthClear(interpreter))); + LoadSubCommand("delete", CommandObjectSP(new CommandObjectTypeSynthDelete( + interpreter))); + LoadSubCommand( + "list", CommandObjectSP(new CommandObjectTypeSynthList(interpreter))); + LoadSubCommand( + "info", + CommandObjectSP(new CommandObjectFormatterInfo<SyntheticChildren>( + interpreter, "synthetic", + [](ValueObject &valobj) -> SyntheticChildren::SharedPointer { + return valobj.GetSyntheticChildren(); + }))); + } + + ~CommandObjectTypeSynth() override = default; +}; + +#endif // LLDB_DISABLE_PYTHON + +class CommandObjectTypeFilter : public CommandObjectMultiword { +public: + CommandObjectTypeFilter(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "type filter", + "Commands for operating on type filters.", + "type synthetic [<sub-command-options>] ") { + LoadSubCommand( + "add", CommandObjectSP(new CommandObjectTypeFilterAdd(interpreter))); + LoadSubCommand("clear", CommandObjectSP( + new CommandObjectTypeFilterClear(interpreter))); + LoadSubCommand("delete", CommandObjectSP(new CommandObjectTypeFilterDelete( + interpreter))); + LoadSubCommand( + "list", CommandObjectSP(new CommandObjectTypeFilterList(interpreter))); + } + + ~CommandObjectTypeFilter() override = default; +}; + +class CommandObjectTypeCategory : public CommandObjectMultiword { +public: + CommandObjectTypeCategory(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "type category", + "Commands for operating on type categories.", + "type category [<sub-command-options>] ") { + LoadSubCommand( + "define", + CommandObjectSP(new CommandObjectTypeCategoryDefine(interpreter))); + LoadSubCommand( + "enable", + CommandObjectSP(new CommandObjectTypeCategoryEnable(interpreter))); + LoadSubCommand( + "disable", + CommandObjectSP(new CommandObjectTypeCategoryDisable(interpreter))); + LoadSubCommand( + "delete", + CommandObjectSP(new CommandObjectTypeCategoryDelete(interpreter))); + LoadSubCommand("list", CommandObjectSP( + new CommandObjectTypeCategoryList(interpreter))); + } + + ~CommandObjectTypeCategory() override = default; +}; + +class CommandObjectTypeSummary : public CommandObjectMultiword { +public: + CommandObjectTypeSummary(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "type summary", + "Commands for editing variable summary display options.", + "type summary [<sub-command-options>] ") { + LoadSubCommand( + "add", CommandObjectSP(new CommandObjectTypeSummaryAdd(interpreter))); + LoadSubCommand("clear", CommandObjectSP(new CommandObjectTypeSummaryClear( + interpreter))); + LoadSubCommand("delete", CommandObjectSP(new CommandObjectTypeSummaryDelete( + interpreter))); + LoadSubCommand( + "list", CommandObjectSP(new CommandObjectTypeSummaryList(interpreter))); + LoadSubCommand( + "info", CommandObjectSP(new CommandObjectFormatterInfo<TypeSummaryImpl>( + interpreter, "summary", + [](ValueObject &valobj) -> TypeSummaryImpl::SharedPointer { + return valobj.GetSummaryFormat(); + }))); + } + + ~CommandObjectTypeSummary() override = default; +}; + +// CommandObjectType + +CommandObjectType::CommandObjectType(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "type", + "Commands for operating on the type system.", + "type [<sub-command-options>]") { + LoadSubCommand("category", + CommandObjectSP(new CommandObjectTypeCategory(interpreter))); + LoadSubCommand("filter", + CommandObjectSP(new CommandObjectTypeFilter(interpreter))); + LoadSubCommand("format", + CommandObjectSP(new CommandObjectTypeFormat(interpreter))); + LoadSubCommand("summary", + CommandObjectSP(new CommandObjectTypeSummary(interpreter))); +#ifndef LLDB_DISABLE_PYTHON + LoadSubCommand("synthetic", + CommandObjectSP(new CommandObjectTypeSynth(interpreter))); +#endif // LLDB_DISABLE_PYTHON + LoadSubCommand("lookup", + CommandObjectSP(new CommandObjectTypeLookup(interpreter))); +} + +CommandObjectType::~CommandObjectType() = default; diff --git a/lldb/source/Commands/CommandObjectType.h b/lldb/source/Commands/CommandObjectType.h new file mode 100644 index 0000000000000..ebb19039e5033 --- /dev/null +++ b/lldb/source/Commands/CommandObjectType.h @@ -0,0 +1,29 @@ +//===-- CommandObjectType.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_CommandObjectType_h_ +#define liblldb_CommandObjectType_h_ + + + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/lldb-types.h" + +namespace lldb_private { + +class CommandObjectType : public CommandObjectMultiword { +public: + CommandObjectType(CommandInterpreter &interpreter); + + ~CommandObjectType() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectType_h_ diff --git a/lldb/source/Commands/CommandObjectVersion.cpp b/lldb/source/Commands/CommandObjectVersion.cpp new file mode 100644 index 0000000000000..904baf5b7d446 --- /dev/null +++ b/lldb/source/Commands/CommandObjectVersion.cpp @@ -0,0 +1,35 @@ +//===-- CommandObjectVersion.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 "CommandObjectVersion.h" + +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectVersion + +CommandObjectVersion::CommandObjectVersion(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "version", + "Show the LLDB debugger version.", "version") {} + +CommandObjectVersion::~CommandObjectVersion() {} + +bool CommandObjectVersion::DoExecute(Args &args, CommandReturnObject &result) { + if (args.GetArgumentCount() == 0) { + result.AppendMessageWithFormat("%s\n", lldb_private::GetVersion()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("the version command takes no arguments."); + result.SetStatus(eReturnStatusFailed); + } + return true; +} diff --git a/lldb/source/Commands/CommandObjectVersion.h b/lldb/source/Commands/CommandObjectVersion.h new file mode 100644 index 0000000000000..30f44aeb16581 --- /dev/null +++ b/lldb/source/Commands/CommandObjectVersion.h @@ -0,0 +1,30 @@ +//===-- CommandObjectVersion.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_CommandObjectVersion_h_ +#define liblldb_CommandObjectVersion_h_ + +#include "lldb/Interpreter/CommandObject.h" + +namespace lldb_private { + +// CommandObjectVersion + +class CommandObjectVersion : public CommandObjectParsed { +public: + CommandObjectVersion(CommandInterpreter &interpreter); + + ~CommandObjectVersion() override; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectVersion_h_ diff --git a/lldb/source/Commands/CommandObjectWatchpoint.cpp b/lldb/source/Commands/CommandObjectWatchpoint.cpp new file mode 100644 index 0000000000000..44dfb29b19b53 --- /dev/null +++ b/lldb/source/Commands/CommandObjectWatchpoint.cpp @@ -0,0 +1,1149 @@ +//===-- CommandObjectWatchpoint.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 "CommandObjectWatchpoint.h" +#include "CommandObjectWatchpointCommand.h" + +#include <vector> + +#include "llvm/ADT/StringRef.h" + +#include "lldb/Breakpoint/Watchpoint.h" +#include "lldb/Breakpoint/WatchpointList.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectVariable.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/StreamString.h" + +using namespace lldb; +using namespace lldb_private; + +static void AddWatchpointDescription(Stream *s, Watchpoint *wp, + lldb::DescriptionLevel level) { + s->IndentMore(); + wp->GetDescription(s, level); + s->IndentLess(); + s->EOL(); +} + +static bool CheckTargetForWatchpointOperations(Target *target, + CommandReturnObject &result) { + bool process_is_valid = + target->GetProcessSP() && target->GetProcessSP()->IsAlive(); + if (!process_is_valid) { + result.AppendError("Thre's no process or it is not alive."); + result.SetStatus(eReturnStatusFailed); + return false; + } + // Target passes our checks, return true. + return true; +} + +// Equivalent class: {"-", "to", "To", "TO"} of range specifier array. +static const char *RSA[4] = {"-", "to", "To", "TO"}; + +// Return the index to RSA if found; otherwise -1 is returned. +static int32_t WithRSAIndex(llvm::StringRef Arg) { + + uint32_t i; + for (i = 0; i < 4; ++i) + if (Arg.find(RSA[i]) != llvm::StringRef::npos) + return i; + return -1; +} + +// Return true if wp_ids is successfully populated with the watch ids. False +// otherwise. +bool CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs( + Target *target, Args &args, std::vector<uint32_t> &wp_ids) { + // Pre-condition: args.GetArgumentCount() > 0. + if (args.GetArgumentCount() == 0) { + if (target == nullptr) + return false; + WatchpointSP watch_sp = target->GetLastCreatedWatchpoint(); + if (watch_sp) { + wp_ids.push_back(watch_sp->GetID()); + return true; + } else + return false; + } + + llvm::StringRef Minus("-"); + std::vector<llvm::StringRef> StrRefArgs; + llvm::StringRef first; + llvm::StringRef second; + size_t i; + int32_t idx; + // Go through the arguments and make a canonical form of arg list containing + // only numbers with possible "-" in between. + for (auto &entry : args.entries()) { + if ((idx = WithRSAIndex(entry.ref())) == -1) { + StrRefArgs.push_back(entry.ref()); + continue; + } + // The Arg contains the range specifier, split it, then. + std::tie(first, second) = entry.ref().split(RSA[idx]); + if (!first.empty()) + StrRefArgs.push_back(first); + StrRefArgs.push_back(Minus); + if (!second.empty()) + StrRefArgs.push_back(second); + } + // Now process the canonical list and fill in the vector of uint32_t's. If + // there is any error, return false and the client should ignore wp_ids. + uint32_t beg, end, id; + size_t size = StrRefArgs.size(); + bool in_range = false; + for (i = 0; i < size; ++i) { + llvm::StringRef Arg = StrRefArgs[i]; + if (in_range) { + // Look for the 'end' of the range. Note StringRef::getAsInteger() + // returns true to signify error while parsing. + if (Arg.getAsInteger(0, end)) + return false; + // Found a range! Now append the elements. + for (id = beg; id <= end; ++id) + wp_ids.push_back(id); + in_range = false; + continue; + } + if (i < (size - 1) && StrRefArgs[i + 1] == Minus) { + if (Arg.getAsInteger(0, beg)) + return false; + // Turn on the in_range flag, we are looking for end of range next. + ++i; + in_range = true; + continue; + } + // Otherwise, we have a simple ID. Just append it. + if (Arg.getAsInteger(0, beg)) + return false; + wp_ids.push_back(beg); + } + + // It is an error if after the loop, we're still in_range. + return !in_range; +} + +// CommandObjectWatchpointList + +// CommandObjectWatchpointList::Options +#pragma mark List::CommandOptions +#define LLDB_OPTIONS_watchpoint_list +#include "CommandOptions.inc" + +#pragma mark List + +class CommandObjectWatchpointList : public CommandObjectParsed { +public: + CommandObjectWatchpointList(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "watchpoint list", + "List all watchpoints at configurable levels of detail.", nullptr, + eCommandRequiresTarget), + m_options() { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeWatchpointID, + eArgTypeWatchpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointList() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), + m_level(lldb::eDescriptionLevelBrief) // Watchpoint List defaults to + // brief descriptions + {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'b': + m_level = lldb::eDescriptionLevelBrief; + break; + case 'f': + m_level = lldb::eDescriptionLevelFull; + break; + case 'v': + m_level = lldb::eDescriptionLevelVerbose; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_level = lldb::eDescriptionLevelFull; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_watchpoint_list_options); + } + + // Instance variables to hold the values for command options. + + lldb::DescriptionLevel m_level; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + + if (target->GetProcessSP() && target->GetProcessSP()->IsAlive()) { + uint32_t num_supported_hardware_watchpoints; + Status error = target->GetProcessSP()->GetWatchpointSupportInfo( + num_supported_hardware_watchpoints); + if (error.Success()) + result.AppendMessageWithFormat( + "Number of supported hardware watchpoints: %u\n", + num_supported_hardware_watchpoints); + } + + const WatchpointList &watchpoints = target->GetWatchpointList(); + + std::unique_lock<std::recursive_mutex> lock; + target->GetWatchpointList().GetListMutex(lock); + + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendMessage("No watchpoints currently set."); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } + + Stream &output_stream = result.GetOutputStream(); + + if (command.GetArgumentCount() == 0) { + // No watchpoint selected; show info about all currently set watchpoints. + result.AppendMessage("Current watchpoints:"); + for (size_t i = 0; i < num_watchpoints; ++i) { + Watchpoint *wp = watchpoints.GetByIndex(i).get(); + AddWatchpointDescription(&output_stream, wp, m_options.m_level); + } + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular watchpoints selected; enable them. + std::vector<uint32_t> wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs( + target, command, wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + const size_t size = wp_ids.size(); + for (size_t i = 0; i < size; ++i) { + Watchpoint *wp = watchpoints.FindByID(wp_ids[i]).get(); + if (wp) + AddWatchpointDescription(&output_stream, wp, m_options.m_level); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectWatchpointEnable +#pragma mark Enable + +class CommandObjectWatchpointEnable : public CommandObjectParsed { +public: + CommandObjectWatchpointEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "enable", + "Enable the specified disabled watchpoint(s). If " + "no watchpoints are specified, enable all of them.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeWatchpointID, + eArgTypeWatchpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointEnable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + if (!CheckTargetForWatchpointOperations(target, result)) + return false; + + std::unique_lock<std::recursive_mutex> lock; + target->GetWatchpointList().GetListMutex(lock); + + const WatchpointList &watchpoints = target->GetWatchpointList(); + + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendError("No watchpoints exist to be enabled."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + // No watchpoint selected; enable all currently set watchpoints. + target->EnableAllWatchpoints(); + result.AppendMessageWithFormat("All watchpoints enabled. (%" PRIu64 + " watchpoints)\n", + (uint64_t)num_watchpoints); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular watchpoints selected; enable them. + std::vector<uint32_t> wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs( + target, command, wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + int count = 0; + const size_t size = wp_ids.size(); + for (size_t i = 0; i < size; ++i) + if (target->EnableWatchpointByID(wp_ids[i])) + ++count; + result.AppendMessageWithFormat("%d watchpoints enabled.\n", count); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + + return result.Succeeded(); + } +}; + +// CommandObjectWatchpointDisable +#pragma mark Disable + +class CommandObjectWatchpointDisable : public CommandObjectParsed { +public: + CommandObjectWatchpointDisable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "watchpoint disable", + "Disable the specified watchpoint(s) without " + "removing it/them. If no watchpoints are " + "specified, disable them all.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeWatchpointID, + eArgTypeWatchpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointDisable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + if (!CheckTargetForWatchpointOperations(target, result)) + return false; + + std::unique_lock<std::recursive_mutex> lock; + target->GetWatchpointList().GetListMutex(lock); + + const WatchpointList &watchpoints = target->GetWatchpointList(); + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendError("No watchpoints exist to be disabled."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + // No watchpoint selected; disable all currently set watchpoints. + if (target->DisableAllWatchpoints()) { + result.AppendMessageWithFormat("All watchpoints disabled. (%" PRIu64 + " watchpoints)\n", + (uint64_t)num_watchpoints); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("Disable all watchpoints failed\n"); + result.SetStatus(eReturnStatusFailed); + } + } else { + // Particular watchpoints selected; disable them. + std::vector<uint32_t> wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs( + target, command, wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + int count = 0; + const size_t size = wp_ids.size(); + for (size_t i = 0; i < size; ++i) + if (target->DisableWatchpointByID(wp_ids[i])) + ++count; + result.AppendMessageWithFormat("%d watchpoints disabled.\n", count); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + + return result.Succeeded(); + } +}; + +// CommandObjectWatchpointDelete +#pragma mark Delete + +class CommandObjectWatchpointDelete : public CommandObjectParsed { +public: + CommandObjectWatchpointDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "watchpoint delete", + "Delete the specified watchpoint(s). If no " + "watchpoints are specified, delete them all.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeWatchpointID, + eArgTypeWatchpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointDelete() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + if (!CheckTargetForWatchpointOperations(target, result)) + return false; + + std::unique_lock<std::recursive_mutex> lock; + target->GetWatchpointList().GetListMutex(lock); + + const WatchpointList &watchpoints = target->GetWatchpointList(); + + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendError("No watchpoints exist to be deleted."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + if (!m_interpreter.Confirm( + "About to delete all watchpoints, do you want to do that?", + true)) { + result.AppendMessage("Operation cancelled..."); + } else { + target->RemoveAllWatchpoints(); + result.AppendMessageWithFormat("All watchpoints removed. (%" PRIu64 + " watchpoints)\n", + (uint64_t)num_watchpoints); + } + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular watchpoints selected; delete them. + std::vector<uint32_t> wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs( + target, command, wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + int count = 0; + const size_t size = wp_ids.size(); + for (size_t i = 0; i < size; ++i) + if (target->RemoveWatchpointByID(wp_ids[i])) + ++count; + result.AppendMessageWithFormat("%d watchpoints deleted.\n", count); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + + return result.Succeeded(); + } +}; + +// CommandObjectWatchpointIgnore + +#pragma mark Ignore::CommandOptions +#define LLDB_OPTIONS_watchpoint_ignore +#include "CommandOptions.inc" + +class CommandObjectWatchpointIgnore : public CommandObjectParsed { +public: + CommandObjectWatchpointIgnore(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "watchpoint ignore", + "Set ignore count on the specified watchpoint(s). " + "If no watchpoints are specified, set them all.", + nullptr, eCommandRequiresTarget), + m_options() { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeWatchpointID, + eArgTypeWatchpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointIgnore() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_ignore_count(0) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'i': + if (option_arg.getAsInteger(0, m_ignore_count)) + error.SetErrorStringWithFormat("invalid ignore count '%s'", + option_arg.str().c_str()); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_ignore_count = 0; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_watchpoint_ignore_options); + } + + // Instance variables to hold the values for command options. + + uint32_t m_ignore_count; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + if (!CheckTargetForWatchpointOperations(target, result)) + return false; + + std::unique_lock<std::recursive_mutex> lock; + target->GetWatchpointList().GetListMutex(lock); + + const WatchpointList &watchpoints = target->GetWatchpointList(); + + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendError("No watchpoints exist to be ignored."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + target->IgnoreAllWatchpoints(m_options.m_ignore_count); + result.AppendMessageWithFormat("All watchpoints ignored. (%" PRIu64 + " watchpoints)\n", + (uint64_t)num_watchpoints); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular watchpoints selected; ignore them. + std::vector<uint32_t> wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs( + target, command, wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + int count = 0; + const size_t size = wp_ids.size(); + for (size_t i = 0; i < size; ++i) + if (target->IgnoreWatchpointByID(wp_ids[i], m_options.m_ignore_count)) + ++count; + result.AppendMessageWithFormat("%d watchpoints ignored.\n", count); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectWatchpointModify + +#pragma mark Modify::CommandOptions +#define LLDB_OPTIONS_watchpoint_modify +#include "CommandOptions.inc" + +#pragma mark Modify + +class CommandObjectWatchpointModify : public CommandObjectParsed { +public: + CommandObjectWatchpointModify(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "watchpoint modify", + "Modify the options on a watchpoint or set of watchpoints in the " + "executable. " + "If no watchpoint is specified, act on the last created " + "watchpoint. " + "Passing an empty argument clears the modification.", + nullptr, eCommandRequiresTarget), + m_options() { + CommandArgumentEntry arg; + CommandObject::AddIDsArgumentData(arg, eArgTypeWatchpointID, + eArgTypeWatchpointIDRange); + // Add the entry for the first argument for this command to the object's + // arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointModify() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_condition(), m_condition_passed(false) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'c': + m_condition = option_arg; + m_condition_passed = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_condition.clear(); + m_condition_passed = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_watchpoint_modify_options); + } + + // Instance variables to hold the values for command options. + + std::string m_condition; + bool m_condition_passed; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + if (!CheckTargetForWatchpointOperations(target, result)) + return false; + + std::unique_lock<std::recursive_mutex> lock; + target->GetWatchpointList().GetListMutex(lock); + + const WatchpointList &watchpoints = target->GetWatchpointList(); + + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendError("No watchpoints exist to be modified."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + WatchpointSP wp_sp = target->GetLastCreatedWatchpoint(); + wp_sp->SetCondition(m_options.m_condition.c_str()); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + // Particular watchpoints selected; set condition on them. + std::vector<uint32_t> wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs( + target, command, wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + int count = 0; + const size_t size = wp_ids.size(); + for (size_t i = 0; i < size; ++i) { + WatchpointSP wp_sp = watchpoints.FindByID(wp_ids[i]); + if (wp_sp) { + wp_sp->SetCondition(m_options.m_condition.c_str()); + ++count; + } + } + result.AppendMessageWithFormat("%d watchpoints modified.\n", count); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectWatchpointSetVariable +#pragma mark SetVariable + +class CommandObjectWatchpointSetVariable : public CommandObjectParsed { +public: + CommandObjectWatchpointSetVariable(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "watchpoint set variable", + "Set a watchpoint on a variable. " + "Use the '-w' option to specify the type of watchpoint and " + "the '-s' option to specify the byte size to watch for. " + "If no '-w' option is specified, it defaults to write. " + "If no '-s' option is specified, it defaults to the variable's " + "byte size. " + "Note that there are limited hardware resources for watchpoints. " + "If watchpoint setting fails, consider disable/delete existing " + "ones " + "to free up resources.", + nullptr, + eCommandRequiresFrame | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_option_group(), m_option_watchpoint() { + SetHelpLong( + R"( +Examples: + +(lldb) watchpoint set variable -w read_write my_global_var + +)" + " Watches my_global_var for read/write access, with the region to watch \ +corresponding to the byte size of the data type."); + + CommandArgumentEntry arg; + CommandArgumentData var_name_arg; + + // Define the only variant of this arg. + var_name_arg.arg_type = eArgTypeVarName; + var_name_arg.arg_repetition = eArgRepeatPlain; + + // Push the variant into the argument entry. + arg.push_back(var_name_arg); + + // Push the data for the only argument into the m_arguments vector. + m_arguments.push_back(arg); + + // Absorb the '-w' and '-s' options into our option group. + m_option_group.Append(&m_option_watchpoint, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectWatchpointSetVariable() override = default; + + Options *GetOptions() override { return &m_option_group; } + +protected: + static size_t GetVariableCallback(void *baton, const char *name, + VariableList &variable_list) { + size_t old_size = variable_list.GetSize(); + Target *target = static_cast<Target *>(baton); + if (target) + target->GetImages().FindGlobalVariables(ConstString(name), UINT32_MAX, + variable_list); + return variable_list.GetSize() - old_size; + } + + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = GetDebugger().GetSelectedTarget().get(); + StackFrame *frame = m_exe_ctx.GetFramePtr(); + + // If no argument is present, issue an error message. There's no way to + // set a watchpoint. + if (command.GetArgumentCount() <= 0) { + result.GetErrorStream().Printf("error: required argument missing; " + "specify your program variable to watch " + "for\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // If no '-w' is specified, default to '-w write'. + if (!m_option_watchpoint.watch_type_specified) { + m_option_watchpoint.watch_type = OptionGroupWatchpoint::eWatchWrite; + } + + // We passed the sanity check for the command. Proceed to set the + // watchpoint now. + lldb::addr_t addr = 0; + size_t size = 0; + + VariableSP var_sp; + ValueObjectSP valobj_sp; + Stream &output_stream = result.GetOutputStream(); + + // A simple watch variable gesture allows only one argument. + if (command.GetArgumentCount() != 1) { + result.GetErrorStream().Printf( + "error: specify exactly one variable to watch for\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Things have checked out ok... + Status error; + uint32_t expr_path_options = + StackFrame::eExpressionPathOptionCheckPtrVsMember | + StackFrame::eExpressionPathOptionsAllowDirectIVarAccess; + valobj_sp = frame->GetValueForVariableExpressionPath( + command.GetArgumentAtIndex(0), eNoDynamicValues, expr_path_options, + var_sp, error); + + if (!valobj_sp) { + // Not in the frame; let's check the globals. + + VariableList variable_list; + ValueObjectList valobj_list; + + Status error(Variable::GetValuesForVariableExpressionPath( + command.GetArgumentAtIndex(0), + m_exe_ctx.GetBestExecutionContextScope(), GetVariableCallback, target, + variable_list, valobj_list)); + + if (valobj_list.GetSize()) + valobj_sp = valobj_list.GetValueObjectAtIndex(0); + } + + CompilerType compiler_type; + + if (valobj_sp) { + AddressType addr_type; + addr = valobj_sp->GetAddressOf(false, &addr_type); + if (addr_type == eAddressTypeLoad) { + // We're in business. + // Find out the size of this variable. + size = m_option_watchpoint.watch_size == 0 + ? valobj_sp->GetByteSize() + : m_option_watchpoint.watch_size; + } + compiler_type = valobj_sp->GetCompilerType(); + } else { + const char *error_cstr = error.AsCString(nullptr); + if (error_cstr) + result.GetErrorStream().Printf("error: %s\n", error_cstr); + else + result.GetErrorStream().Printf("error: unable to find any variable " + "expression path that matches '%s'\n", + command.GetArgumentAtIndex(0)); + return false; + } + + // Now it's time to create the watchpoint. + uint32_t watch_type = m_option_watchpoint.watch_type; + + error.Clear(); + Watchpoint *wp = + target->CreateWatchpoint(addr, size, &compiler_type, watch_type, error) + .get(); + if (wp) { + wp->SetWatchSpec(command.GetArgumentAtIndex(0)); + wp->SetWatchVariable(true); + if (var_sp && var_sp->GetDeclaration().GetFile()) { + StreamString ss; + // True to show fullpath for declaration file. + var_sp->GetDeclaration().DumpStopContext(&ss, true); + wp->SetDeclInfo(ss.GetString()); + } + output_stream.Printf("Watchpoint created: "); + wp->GetDescription(&output_stream, lldb::eDescriptionLevelFull); + output_stream.EOL(); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat( + "Watchpoint creation failed (addr=0x%" PRIx64 ", size=%" PRIu64 + ", variable expression='%s').\n", + addr, (uint64_t)size, command.GetArgumentAtIndex(0)); + if (error.AsCString(nullptr)) + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } + +private: + OptionGroupOptions m_option_group; + OptionGroupWatchpoint m_option_watchpoint; +}; + +// CommandObjectWatchpointSetExpression +#pragma mark Set + +class CommandObjectWatchpointSetExpression : public CommandObjectRaw { +public: + CommandObjectWatchpointSetExpression(CommandInterpreter &interpreter) + : CommandObjectRaw( + interpreter, "watchpoint set expression", + "Set a watchpoint on an address by supplying an expression. " + "Use the '-w' option to specify the type of watchpoint and " + "the '-s' option to specify the byte size to watch for. " + "If no '-w' option is specified, it defaults to write. " + "If no '-s' option is specified, it defaults to the target's " + "pointer byte size. " + "Note that there are limited hardware resources for watchpoints. " + "If watchpoint setting fails, consider disable/delete existing " + "ones " + "to free up resources.", + "", + eCommandRequiresFrame | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_option_group(), m_option_watchpoint() { + SetHelpLong( + R"( +Examples: + +(lldb) watchpoint set expression -w write -s 1 -- foo + 32 + + Watches write access for the 1-byte region pointed to by the address 'foo + 32')"); + + CommandArgumentEntry arg; + CommandArgumentData expression_arg; + + // Define the only variant of this arg. + expression_arg.arg_type = eArgTypeExpression; + expression_arg.arg_repetition = eArgRepeatPlain; + + // Push the only variant into the argument entry. + arg.push_back(expression_arg); + + // Push the data for the only argument into the m_arguments vector. + m_arguments.push_back(arg); + + // Absorb the '-w' and '-s' options into our option group. + m_option_group.Append(&m_option_watchpoint, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectWatchpointSetExpression() override = default; + + // Overrides base class's behavior where WantsCompletion = + // !WantsRawCommandString. + bool WantsCompletion() override { return true; } + + Options *GetOptions() override { return &m_option_group; } + +protected: + bool DoExecute(llvm::StringRef raw_command, + CommandReturnObject &result) override { + auto exe_ctx = GetCommandInterpreter().GetExecutionContext(); + m_option_group.NotifyOptionParsingStarting( + &exe_ctx); // This is a raw command, so notify the option group + + Target *target = GetDebugger().GetSelectedTarget().get(); + StackFrame *frame = m_exe_ctx.GetFramePtr(); + + OptionsWithRaw args(raw_command); + + llvm::StringRef expr = args.GetRawPart(); + + if (args.HasArgs()) + if (!ParseOptionsAndNotify(args.GetArgs(), result, m_option_group, + exe_ctx)) + return false; + + // If no argument is present, issue an error message. There's no way to + // set a watchpoint. + if (raw_command.trim().empty()) { + result.GetErrorStream().Printf("error: required argument missing; " + "specify an expression to evaulate into " + "the address to watch for\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + // If no '-w' is specified, default to '-w write'. + if (!m_option_watchpoint.watch_type_specified) { + m_option_watchpoint.watch_type = OptionGroupWatchpoint::eWatchWrite; + } + + // We passed the sanity check for the command. Proceed to set the + // watchpoint now. + lldb::addr_t addr = 0; + size_t size = 0; + + ValueObjectSP valobj_sp; + + // Use expression evaluation to arrive at the address to watch. + EvaluateExpressionOptions options; + options.SetCoerceToId(false); + options.SetUnwindOnError(true); + options.SetKeepInMemory(false); + options.SetTryAllThreads(true); + options.SetTimeout(llvm::None); + + ExpressionResults expr_result = + target->EvaluateExpression(expr, frame, valobj_sp, options); + if (expr_result != eExpressionCompleted) { + result.GetErrorStream().Printf( + "error: expression evaluation of address to watch failed\n"); + result.GetErrorStream() << "expression evaluated: \n" << expr << "\n"; + result.SetStatus(eReturnStatusFailed); + return false; + } + + // Get the address to watch. + bool success = false; + addr = valobj_sp->GetValueAsUnsigned(0, &success); + if (!success) { + result.GetErrorStream().Printf( + "error: expression did not evaluate to an address\n"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_option_watchpoint.watch_size != 0) + size = m_option_watchpoint.watch_size; + else + size = target->GetArchitecture().GetAddressByteSize(); + + // Now it's time to create the watchpoint. + uint32_t watch_type = m_option_watchpoint.watch_type; + + // Fetch the type from the value object, the type of the watched object is + // the pointee type + /// of the expression, so convert to that if we found a valid type. + CompilerType compiler_type(valobj_sp->GetCompilerType()); + + Status error; + Watchpoint *wp = + target->CreateWatchpoint(addr, size, &compiler_type, watch_type, error) + .get(); + if (wp) { + Stream &output_stream = result.GetOutputStream(); + output_stream.Printf("Watchpoint created: "); + wp->GetDescription(&output_stream, lldb::eDescriptionLevelFull); + output_stream.EOL(); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Watchpoint creation failed (addr=0x%" PRIx64 + ", size=%" PRIu64 ").\n", + addr, (uint64_t)size); + if (error.AsCString(nullptr)) + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + + return result.Succeeded(); + } + +private: + OptionGroupOptions m_option_group; + OptionGroupWatchpoint m_option_watchpoint; +}; + +// CommandObjectWatchpointSet +#pragma mark Set + +class CommandObjectWatchpointSet : public CommandObjectMultiword { +public: + CommandObjectWatchpointSet(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "watchpoint set", "Commands for setting a watchpoint.", + "watchpoint set <subcommand> [<subcommand-options>]") { + + LoadSubCommand( + "variable", + CommandObjectSP(new CommandObjectWatchpointSetVariable(interpreter))); + LoadSubCommand( + "expression", + CommandObjectSP(new CommandObjectWatchpointSetExpression(interpreter))); + } + + ~CommandObjectWatchpointSet() override = default; +}; + +// CommandObjectMultiwordWatchpoint +#pragma mark MultiwordWatchpoint + +CommandObjectMultiwordWatchpoint::CommandObjectMultiwordWatchpoint( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "watchpoint", + "Commands for operating on watchpoints.", + "watchpoint <subcommand> [<command-options>]") { + CommandObjectSP list_command_object( + new CommandObjectWatchpointList(interpreter)); + CommandObjectSP enable_command_object( + new CommandObjectWatchpointEnable(interpreter)); + CommandObjectSP disable_command_object( + new CommandObjectWatchpointDisable(interpreter)); + CommandObjectSP delete_command_object( + new CommandObjectWatchpointDelete(interpreter)); + CommandObjectSP ignore_command_object( + new CommandObjectWatchpointIgnore(interpreter)); + CommandObjectSP command_command_object( + new CommandObjectWatchpointCommand(interpreter)); + CommandObjectSP modify_command_object( + new CommandObjectWatchpointModify(interpreter)); + CommandObjectSP set_command_object( + new CommandObjectWatchpointSet(interpreter)); + + list_command_object->SetCommandName("watchpoint list"); + enable_command_object->SetCommandName("watchpoint enable"); + disable_command_object->SetCommandName("watchpoint disable"); + delete_command_object->SetCommandName("watchpoint delete"); + ignore_command_object->SetCommandName("watchpoint ignore"); + command_command_object->SetCommandName("watchpoint command"); + modify_command_object->SetCommandName("watchpoint modify"); + set_command_object->SetCommandName("watchpoint set"); + + LoadSubCommand("list", list_command_object); + LoadSubCommand("enable", enable_command_object); + LoadSubCommand("disable", disable_command_object); + LoadSubCommand("delete", delete_command_object); + LoadSubCommand("ignore", ignore_command_object); + LoadSubCommand("command", command_command_object); + LoadSubCommand("modify", modify_command_object); + LoadSubCommand("set", set_command_object); +} + +CommandObjectMultiwordWatchpoint::~CommandObjectMultiwordWatchpoint() = default; diff --git a/lldb/source/Commands/CommandObjectWatchpoint.h b/lldb/source/Commands/CommandObjectWatchpoint.h new file mode 100644 index 0000000000000..f21796e6bc8dd --- /dev/null +++ b/lldb/source/Commands/CommandObjectWatchpoint.h @@ -0,0 +1,33 @@ +//===-- CommandObjectWatchpoint.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_CommandObjectWatchpoint_h_ +#define liblldb_CommandObjectWatchpoint_h_ + + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/OptionGroupWatchpoint.h" +#include "lldb/Interpreter/Options.h" + +namespace lldb_private { + +// CommandObjectMultiwordWatchpoint + +class CommandObjectMultiwordWatchpoint : public CommandObjectMultiword { +public: + CommandObjectMultiwordWatchpoint(CommandInterpreter &interpreter); + + ~CommandObjectMultiwordWatchpoint() override; + + static bool VerifyWatchpointIDs(Target *target, Args &args, + std::vector<uint32_t> &wp_ids); +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectWatchpoint_h_ diff --git a/lldb/source/Commands/CommandObjectWatchpointCommand.cpp b/lldb/source/Commands/CommandObjectWatchpointCommand.cpp new file mode 100644 index 0000000000000..5683381efc858 --- /dev/null +++ b/lldb/source/Commands/CommandObjectWatchpointCommand.cpp @@ -0,0 +1,662 @@ +//===-- CommandObjectWatchpointCommand.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 <vector> + +#include "CommandObjectWatchpoint.h" +#include "CommandObjectWatchpointCommand.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Breakpoint/Watchpoint.h" +#include "lldb/Core/IOHandler.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/State.h" + +using namespace lldb; +using namespace lldb_private; + +// FIXME: "script-type" needs to have its contents determined dynamically, so +// somebody can add a new scripting language to lldb and have it pickable here +// without having to change this enumeration by hand and rebuild lldb proper. +static constexpr OptionEnumValueElement g_script_option_enumeration[] = { + { + eScriptLanguageNone, + "command", + "Commands are in the lldb command interpreter language", + }, + { + eScriptLanguagePython, + "python", + "Commands are in the Python language.", + }, + { + eSortOrderByName, + "default-script", + "Commands are in the default scripting language.", + }, +}; + +static constexpr OptionEnumValues ScriptOptionEnum() { + return OptionEnumValues(g_script_option_enumeration); +} + +#define LLDB_OPTIONS_watchpoint_command_add +#include "CommandOptions.inc" + +class CommandObjectWatchpointCommandAdd : public CommandObjectParsed, + public IOHandlerDelegateMultiline { +public: + CommandObjectWatchpointCommandAdd(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "add", + "Add a set of LLDB commands to a watchpoint, to be " + "executed whenever the watchpoint is hit.", + nullptr, eCommandRequiresTarget), + IOHandlerDelegateMultiline("DONE", + IOHandlerDelegate::Completion::LLDBCommand), + m_options() { + SetHelpLong( + R"( +General information about entering watchpoint commands +------------------------------------------------------ + +)" + "This command will prompt for commands to be executed when the specified \ +watchpoint is hit. Each command is typed on its own line following the '> ' \ +prompt until 'DONE' is entered." + R"( + +)" + "Syntactic errors may not be detected when initially entered, and many \ +malformed commands can silently fail when executed. If your watchpoint commands \ +do not appear to be executing, double-check the command syntax." + R"( + +)" + "Note: You may enter any debugger command exactly as you would at the debugger \ +prompt. There is no limit to the number of commands supplied, but do NOT enter \ +more than one command per line." + R"( + +Special information about PYTHON watchpoint commands +---------------------------------------------------- + +)" + "You may enter either one or more lines of Python, including function \ +definitions or calls to functions that will have been imported by the time \ +the code executes. Single line watchpoint commands will be interpreted 'as is' \ +when the watchpoint is hit. Multiple lines of Python will be wrapped in a \ +generated function, and a call to the function will be attached to the watchpoint." + R"( + +This auto-generated function is passed in three arguments: + + frame: an lldb.SBFrame object for the frame which hit the watchpoint. + + wp: the watchpoint that was hit. + +)" + "When specifying a python function with the --python-function option, you need \ +to supply the function name prepended by the module name:" + R"( + + --python-function myutils.watchpoint_callback + +The function itself must have the following prototype: + +def watchpoint_callback(frame, wp): + # Your code goes here + +)" + "The arguments are the same as the arguments passed to generated functions as \ +described above. Note that the global variable 'lldb.frame' will NOT be updated when \ +this function is called, so be sure to use the 'frame' argument. The 'frame' argument \ +can get you to the thread via frame.GetThread(), the thread can get you to the \ +process via thread.GetProcess(), and the process can get you back to the target \ +via process.GetTarget()." + R"( + +)" + "Important Note: As Python code gets collected into functions, access to global \ +variables requires explicit scoping using the 'global' keyword. Be sure to use correct \ +Python syntax, including indentation, when entering Python watchpoint commands." + R"( + +Example Python one-line watchpoint command: + +(lldb) watchpoint command add -s python 1 +Enter your Python command(s). Type 'DONE' to end. +> print "Hit this watchpoint!" +> DONE + +As a convenience, this also works for a short Python one-liner: + +(lldb) watchpoint command add -s python 1 -o 'import time; print time.asctime()' +(lldb) run +Launching '.../a.out' (x86_64) +(lldb) Fri Sep 10 12:17:45 2010 +Process 21778 Stopped +* thread #1: tid = 0x2e03, 0x0000000100000de8 a.out`c + 7 at main.c:39, stop reason = watchpoint 1.1, queue = com.apple.main-thread + 36 + 37 int c(int val) + 38 { + 39 -> return val + 3; + 40 } + 41 + 42 int main (int argc, char const *argv[]) + +Example multiple line Python watchpoint command, using function definition: + +(lldb) watchpoint command add -s python 1 +Enter your Python command(s). Type 'DONE' to end. +> def watchpoint_output (wp_no): +> out_string = "Hit watchpoint number " + repr (wp_no) +> print out_string +> return True +> watchpoint_output (1) +> DONE + +Example multiple line Python watchpoint command, using 'loose' Python: + +(lldb) watchpoint command add -s p 1 +Enter your Python command(s). Type 'DONE' to end. +> global wp_count +> wp_count = wp_count + 1 +> print "Hit this watchpoint " + repr(wp_count) + " times!" +> DONE + +)" + "In this case, since there is a reference to a global variable, \ +'wp_count', you will also need to make sure 'wp_count' exists and is \ +initialized:" + R"( + +(lldb) script +>>> wp_count = 0 +>>> quit() + +)" + "Final Note: A warning that no watchpoint command was generated when there \ +are no syntax errors may indicate that a function was declared but never called."); + + CommandArgumentEntry arg; + CommandArgumentData wp_id_arg; + + // Define the first (and only) variant of this arg. + wp_id_arg.arg_type = eArgTypeWatchpointID; + wp_id_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(wp_id_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointCommandAdd() override = default; + + Options *GetOptions() override { return &m_options; } + + void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { + StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + if (output_sp && interactive) { + output_sp->PutCString( + "Enter your debugger command(s). Type 'DONE' to end.\n"); + output_sp->Flush(); + } + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &line) override { + io_handler.SetIsDone(true); + + // The WatchpointOptions object is owned by the watchpoint or watchpoint + // location + WatchpointOptions *wp_options = + (WatchpointOptions *)io_handler.GetUserData(); + if (wp_options) { + std::unique_ptr<WatchpointOptions::CommandData> data_up( + new WatchpointOptions::CommandData()); + if (data_up) { + data_up->user_source.SplitIntoLines(line); + auto baton_sp = std::make_shared<WatchpointOptions::CommandBaton>( + std::move(data_up)); + wp_options->SetCallback(WatchpointOptionsCallbackFunction, baton_sp); + } + } + } + + void CollectDataForWatchpointCommandCallback(WatchpointOptions *wp_options, + CommandReturnObject &result) { + m_interpreter.GetLLDBCommandsFromIOHandler( + "> ", // Prompt + *this, // IOHandlerDelegate + true, // Run IOHandler in async mode + wp_options); // Baton for the "io_handler" that will be passed back into + // our IOHandlerDelegate functions + } + + /// Set a one-liner as the callback for the watchpoint. + void SetWatchpointCommandCallback(WatchpointOptions *wp_options, + const char *oneliner) { + std::unique_ptr<WatchpointOptions::CommandData> data_up( + new WatchpointOptions::CommandData()); + + // It's necessary to set both user_source and script_source to the + // oneliner. The former is used to generate callback description (as in + // watchpoint command list) while the latter is used for Python to + // interpret during the actual callback. + data_up->user_source.AppendString(oneliner); + data_up->script_source.assign(oneliner); + data_up->stop_on_error = m_options.m_stop_on_error; + + auto baton_sp = + std::make_shared<WatchpointOptions::CommandBaton>(std::move(data_up)); + wp_options->SetCallback(WatchpointOptionsCallbackFunction, baton_sp); + } + + static bool + WatchpointOptionsCallbackFunction(void *baton, + StoppointCallbackContext *context, + lldb::user_id_t watch_id) { + bool ret_value = true; + if (baton == nullptr) + return true; + + WatchpointOptions::CommandData *data = + (WatchpointOptions::CommandData *)baton; + StringList &commands = data->user_source; + + if (commands.GetSize() > 0) { + ExecutionContext exe_ctx(context->exe_ctx_ref); + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + CommandReturnObject result; + Debugger &debugger = target->GetDebugger(); + // Rig up the results secondary output stream to the debugger's, so the + // output will come out synchronously if the debugger is set up that + // way. + + StreamSP output_stream(debugger.GetAsyncOutputStream()); + StreamSP error_stream(debugger.GetAsyncErrorStream()); + result.SetImmediateOutputStream(output_stream); + result.SetImmediateErrorStream(error_stream); + + CommandInterpreterRunOptions options; + options.SetStopOnContinue(true); + options.SetStopOnError(data->stop_on_error); + options.SetEchoCommands(false); + options.SetPrintResults(true); + options.SetPrintErrors(true); + options.SetAddToHistory(false); + + debugger.GetCommandInterpreter().HandleCommands(commands, &exe_ctx, + options, result); + result.GetImmediateOutputStream()->Flush(); + result.GetImmediateErrorStream()->Flush(); + } + } + return ret_value; + } + + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_use_commands(false), m_use_script_language(false), + m_script_language(eScriptLanguageNone), m_use_one_liner(false), + m_one_liner(), m_function_name() {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'o': + m_use_one_liner = true; + m_one_liner = option_arg; + break; + + case 's': + m_script_language = (lldb::ScriptLanguage)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, + eScriptLanguageNone, error); + + m_use_script_language = (m_script_language == eScriptLanguagePython || + m_script_language == eScriptLanguageDefault); + break; + + case 'e': { + bool success = false; + m_stop_on_error = + OptionArgParser::ToBoolean(option_arg, false, &success); + if (!success) + error.SetErrorStringWithFormat( + "invalid value for stop-on-error: \"%s\"", + option_arg.str().c_str()); + } break; + + case 'F': + m_use_one_liner = false; + m_use_script_language = true; + m_function_name.assign(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_use_commands = true; + m_use_script_language = false; + m_script_language = eScriptLanguageNone; + + m_use_one_liner = false; + m_stop_on_error = true; + m_one_liner.clear(); + m_function_name.clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_watchpoint_command_add_options); + } + + // Instance variables to hold the values for command options. + + bool m_use_commands; + bool m_use_script_language; + lldb::ScriptLanguage m_script_language; + + // Instance variables to hold the values for one_liner options. + bool m_use_one_liner; + std::string m_one_liner; + bool m_stop_on_error; + std::string m_function_name; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + + const WatchpointList &watchpoints = target->GetWatchpointList(); + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendError("No watchpoints exist to have commands added"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (!m_options.m_use_script_language && + !m_options.m_function_name.empty()) { + result.AppendError("need to enable scripting to have a function run as a " + "watchpoint command"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + std::vector<uint32_t> valid_wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command, + valid_wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + const size_t count = valid_wp_ids.size(); + for (size_t i = 0; i < count; ++i) { + uint32_t cur_wp_id = valid_wp_ids.at(i); + if (cur_wp_id != LLDB_INVALID_WATCH_ID) { + Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get(); + // Sanity check wp first. + if (wp == nullptr) + continue; + + WatchpointOptions *wp_options = wp->GetOptions(); + // Skip this watchpoint if wp_options is not good. + if (wp_options == nullptr) + continue; + + // If we are using script language, get the script interpreter in order + // to set or collect command callback. Otherwise, call the methods + // associated with this object. + if (m_options.m_use_script_language) { + // Special handling for one-liner specified inline. + if (m_options.m_use_one_liner) { + GetDebugger().GetScriptInterpreter()->SetWatchpointCommandCallback( + wp_options, m_options.m_one_liner.c_str()); + } + // Special handling for using a Python function by name instead of + // extending the watchpoint callback data structures, we just + // automatize what the user would do manually: make their watchpoint + // command be a function call + else if (!m_options.m_function_name.empty()) { + std::string oneliner(m_options.m_function_name); + oneliner += "(frame, wp, internal_dict)"; + GetDebugger().GetScriptInterpreter()->SetWatchpointCommandCallback( + wp_options, oneliner.c_str()); + } else { + GetDebugger() + .GetScriptInterpreter() + ->CollectDataForWatchpointCommandCallback(wp_options, result); + } + } else { + // Special handling for one-liner specified inline. + if (m_options.m_use_one_liner) + SetWatchpointCommandCallback(wp_options, + m_options.m_one_liner.c_str()); + else + CollectDataForWatchpointCommandCallback(wp_options, result); + } + } + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + +// CommandObjectWatchpointCommandDelete + +class CommandObjectWatchpointCommandDelete : public CommandObjectParsed { +public: + CommandObjectWatchpointCommandDelete(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "delete", + "Delete the set of commands from a watchpoint.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg; + CommandArgumentData wp_id_arg; + + // Define the first (and only) variant of this arg. + wp_id_arg.arg_type = eArgTypeWatchpointID; + wp_id_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(wp_id_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointCommandDelete() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + + const WatchpointList &watchpoints = target->GetWatchpointList(); + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendError("No watchpoints exist to have commands deleted"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + result.AppendError( + "No watchpoint specified from which to delete the commands"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + std::vector<uint32_t> valid_wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command, + valid_wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + const size_t count = valid_wp_ids.size(); + for (size_t i = 0; i < count; ++i) { + uint32_t cur_wp_id = valid_wp_ids.at(i); + if (cur_wp_id != LLDB_INVALID_WATCH_ID) { + Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get(); + if (wp) + wp->ClearCallback(); + } else { + result.AppendErrorWithFormat("Invalid watchpoint ID: %u.\n", cur_wp_id); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + return result.Succeeded(); + } +}; + +// CommandObjectWatchpointCommandList + +class CommandObjectWatchpointCommandList : public CommandObjectParsed { +public: + CommandObjectWatchpointCommandList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "list", + "List the script or set of commands to be executed " + "when the watchpoint is hit.", + nullptr, eCommandRequiresTarget) { + CommandArgumentEntry arg; + CommandArgumentData wp_id_arg; + + // Define the first (and only) variant of this arg. + wp_id_arg.arg_type = eArgTypeWatchpointID; + wp_id_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg.push_back(wp_id_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg); + } + + ~CommandObjectWatchpointCommandList() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = &GetSelectedTarget(); + + const WatchpointList &watchpoints = target->GetWatchpointList(); + size_t num_watchpoints = watchpoints.GetSize(); + + if (num_watchpoints == 0) { + result.AppendError("No watchpoints exist for which to list commands"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (command.GetArgumentCount() == 0) { + result.AppendError( + "No watchpoint specified for which to list the commands"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + std::vector<uint32_t> valid_wp_ids; + if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command, + valid_wp_ids)) { + result.AppendError("Invalid watchpoints specification."); + result.SetStatus(eReturnStatusFailed); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + const size_t count = valid_wp_ids.size(); + for (size_t i = 0; i < count; ++i) { + uint32_t cur_wp_id = valid_wp_ids.at(i); + if (cur_wp_id != LLDB_INVALID_WATCH_ID) { + Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get(); + + if (wp) { + const WatchpointOptions *wp_options = wp->GetOptions(); + if (wp_options) { + // Get the callback baton associated with the current watchpoint. + const Baton *baton = wp_options->GetBaton(); + if (baton) { + result.GetOutputStream().Printf("Watchpoint %u:\n", cur_wp_id); + result.GetOutputStream().IndentMore(); + baton->GetDescription(&result.GetOutputStream(), + eDescriptionLevelFull); + result.GetOutputStream().IndentLess(); + } else { + result.AppendMessageWithFormat( + "Watchpoint %u does not have an associated command.\n", + cur_wp_id); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("Invalid watchpoint ID: %u.\n", + cur_wp_id); + result.SetStatus(eReturnStatusFailed); + } + } + } + + return result.Succeeded(); + } +}; + +// CommandObjectWatchpointCommand + +CommandObjectWatchpointCommand::CommandObjectWatchpointCommand( + CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "command", + "Commands for adding, removing and examining LLDB commands " + "executed when the watchpoint is hit (watchpoint 'commands').", + "command <sub-command> [<sub-command-options>] <watchpoint-id>") { + CommandObjectSP add_command_object( + new CommandObjectWatchpointCommandAdd(interpreter)); + CommandObjectSP delete_command_object( + new CommandObjectWatchpointCommandDelete(interpreter)); + CommandObjectSP list_command_object( + new CommandObjectWatchpointCommandList(interpreter)); + + add_command_object->SetCommandName("watchpoint command add"); + delete_command_object->SetCommandName("watchpoint command delete"); + list_command_object->SetCommandName("watchpoint command list"); + + LoadSubCommand("add", add_command_object); + LoadSubCommand("delete", delete_command_object); + LoadSubCommand("list", list_command_object); +} + +CommandObjectWatchpointCommand::~CommandObjectWatchpointCommand() = default; diff --git a/lldb/source/Commands/CommandObjectWatchpointCommand.h b/lldb/source/Commands/CommandObjectWatchpointCommand.h new file mode 100644 index 0000000000000..f2f15ef50b0f5 --- /dev/null +++ b/lldb/source/Commands/CommandObjectWatchpointCommand.h @@ -0,0 +1,31 @@ +//===-- CommandObjectWatchpointCommand.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_CommandObjectWatchpointCommand_h_ +#define liblldb_CommandObjectWatchpointCommand_h_ + + + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/lldb-types.h" + +namespace lldb_private { + +// CommandObjectMultiwordWatchpoint + +class CommandObjectWatchpointCommand : public CommandObjectMultiword { +public: + CommandObjectWatchpointCommand(CommandInterpreter &interpreter); + + ~CommandObjectWatchpointCommand() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectWatchpointCommand_h_ diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td new file mode 100644 index 0000000000000..87f5506c305fb --- /dev/null +++ b/lldb/source/Commands/Options.td @@ -0,0 +1,1127 @@ +include "OptionsBase.td" + +let Command = "target modules dump symtab" in { + def tm_sort : Option<"sort", "s">, Group<1>, + Desc<"Supply a sort order when dumping the symbol table.">, + EnumArg<"SortOrder", "OptionEnumValues(g_sort_option_enumeration)">; +} + +let Command = "help" in { + def help_hide_aliases : Option<"hide-aliases", "a">, + Desc<"Hide aliases in the command list.">; + def help_hide_user : Option<"hide-user-commands", "u">, + Desc<"Hide user-defined commands from the list.">; + def help_show_hidden : Option<"show-hidden-commands", "h">, + Desc<"Include commands prefixed with an underscore.">; +} + +let Command = "settings set" in { + def setset_global : Option<"global", "g">, Arg<"Filename">, + Completion<"DiskFile">, + Desc<"Apply the new value to the global default value.">; + def setset_force : Option<"force", "f">, + Desc<"Force an empty value to be accepted as the default.">; +} + +let Command = "settings write" in { + def setwrite_file : Option<"file", "f">, Required, Arg<"Filename">, + Completion<"DiskFile">, + Desc<"The file into which to write the settings.">; + def setwrite_append : Option<"append", "a">, + Desc<"Append to saved settings file if it exists.">; +} + +let Command = "settings read" in { + def setread_file : Option<"file", "f">, Required, Arg<"Filename">, + Completion<"DiskFile">, + Desc<"The file from which to read the settings.">; +} + +let Command = "breakpoint list" in { + // FIXME: We need to add an "internal" command, and then add this sort of + // thing to it. But I need to see it for now, and don't want to wait. + def blist_internal : Option<"internal", "i">, + Desc<"Show debugger internal breakpoints">; + def blist_brief : Option<"brief", "b">, Group<1>, + Desc<"Give a brief description of the breakpoint (no location info).">; + def blist_full : Option<"full", "f">, Group<2>, + Desc<"Give a full description of the breakpoint and its locations.">; + def blist_verbose : Option<"verbose", "v">, Group<3>, + Desc<"Explain everything we know about the breakpoint (for debugging " + "debugger bugs).">; + def blist_dummy_bp : Option<"dummy-breakpoints", "D">, + Desc<"List Dummy breakpoints - i.e. breakpoints set before a file is " + "provided, which prime new targets.">; +} + +let Command = "breakpoint modify" in { + def breakpoint_modify_ignore_count : Option<"ignore-count", "i">, Group<1>, + Arg<"Count">, + Desc<"Set the number of times this breakpoint is skipped before stopping.">; + def breakpoint_modify_one_shot : Option<"one-shot", "o">, Group<1>, + Arg<"Boolean">, + Desc<"The breakpoint is deleted the first time it stop causes a stop.">; + def breakpoint_modify_thread_index : Option<"thread-index", "x">, Group<1>, + Arg<"ThreadIndex">, Desc<"The breakpoint stops only for the thread whose " + "index matches this argument.">; + def breakpoint_modify_thread_id : Option<"thread-id", "t">, Group<1>, + Arg<"ThreadID">, Desc<"The breakpoint stops only for the thread whose TID " + "matches this argument.">; + def breakpoint_modify_thread_name : Option<"thread-name", "T">, Group<1>, + Arg<"ThreadName">, Desc<"The breakpoint stops only for the thread whose " + "thread name matches this argument.">; + def breakpoint_modify_queue_name : Option<"queue-name", "q">, Group<1>, + Arg<"QueueName">, Desc<"The breakpoint stops only for threads in the queue " + "whose name is given by this argument.">; + def breakpoint_modify_condition : Option<"condition", "c">, Group<1>, + Arg<"Expression">, Desc<"The breakpoint stops only if this condition " + "expression evaluates to true.">; + def breakpoint_modify_auto_continue : Option<"auto-continue", "G">, Group<1>, + Arg<"Boolean">, + Desc<"The breakpoint will auto-continue after running its commands.">; + def breakpoint_modify_enable : Option<"enable", "e">, Group<2>, + Desc<"Enable the breakpoint.">; + def breakpoint_modify_disable : Option<"disable", "d">, Group<3>, + Desc<"Disable the breakpoint.">; + def breakpoint_modify_command : Option<"command", "C">, Group<4>, + Arg<"Command">, + Desc<"A command to run when the breakpoint is hit, can be provided more " + "than once, the commands will get run in order left to right.">; +} + +let Command = "breakpoint dummy" in { + def breakpoint_dummy_options_dummy_breakpoints : + Option<"dummy-breakpoints", "D">, Group<1>, + Desc<"Act on Dummy breakpoints - i.e. breakpoints set before a file is " + "provided, which prime new targets.">; +} + +let Command = "breakpoint set" in { + def breakpoint_set_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, Groups<[1,2,3,4,5,6,7,8,9,11]>, // *not* in group 10 + Desc<"Set the breakpoint only in this shared library. Can repeat this " + "option multiple times to specify multiple shared libraries.">; + def breakpoint_set_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_set_file : Option<"file", "f">, Arg<"Filename">, + Completion<"SourceFile">, Groups<[1,3,4,5,6,7,8,9,11]>, + Desc<"Specifies the source file in which to set this breakpoint. Note, by " + "default lldb only looks for files that are #included if they use the " + "standard include file extensions. To set breakpoints on .c/.cpp/.m/.mm " + "files that are #included, set target.inline-breakpoint-strategy to " + "\"always\".">; + def breakpoint_set_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, + Required, + Desc<"Specifies the line number on which to set this breakpoint.">; + def breakpoint_set_address : Option<"address", "a">, Group<2>, + Arg<"AddressOrExpression">, Required, + Desc<"Set the breakpoint at the specified address. If the address maps " + "uniquely toa particular binary, then the address will be converted to " + "a \"file\"address, so that the breakpoint will track that binary+offset " + "no matter where the binary eventually loads. Alternately, if you also " + "specify the module - with the -s option - then the address will be " + "treated as a file address in that module, and resolved accordingly. " + "Again, this will allow lldb to track that offset on subsequent reloads. " + " The module need not have been loaded at the time you specify this " + "breakpoint, and will get resolved when the module is loaded.">; + def breakpoint_set_name : Option<"name", "n">, Group<3>, Arg<"FunctionName">, + Completion<"Symbol">, Required, + Desc<"Set the breakpoint by function name. Can be repeated multiple times " + "to makeone breakpoint for multiple names">; + def breakpoint_set_source_regexp_function : + Option<"source-regexp-function", "X">, Group<9>, Arg<"FunctionName">, + Completion<"Symbol">, + Desc<"When used with '-p' limits the source regex to source contained in " + "the namedfunctions. Can be repeated multiple times.">; + def breakpoint_set_fullname : Option<"fullname", "F">, Group<4>, + Arg<"FullName">, Required, Completion<"Symbol">, + Desc<"Set the breakpoint by fully qualified function names. For C++ this " + "means namespaces and all arguments, and for Objective-C this means a full " + "functionprototype with class and selector. Can be repeated multiple times" + " to make one breakpoint for multiple names.">; + def breakpoint_set_selector : Option<"selector", "S">, Group<5>, + Arg<"Selector">, Required, + Desc<"Set the breakpoint by ObjC selector name. Can be repeated multiple " + "times tomake one breakpoint for multiple Selectors.">; + def breakpoint_set_method : Option<"method", "M">, Group<6>, Arg<"Method">, + Required, Desc<"Set the breakpoint by C++ method names. Can be repeated " + "multiple times tomake one breakpoint for multiple methods.">; + def breakpoint_set_func_regex : Option<"func-regex", "r">, Group<7>, + Arg<"RegularExpression">, Required, Desc<"Set the breakpoint by function " + "name, evaluating a regular-expression to findthe function name(s).">; + def breakpoint_set_basename : Option<"basename", "b">, Group<8>, + Arg<"FunctionName">, Required, Completion<"Symbol">, + Desc<"Set the breakpoint by function basename (C++ namespaces and arguments" + " will beignored). Can be repeated multiple times to make one breakpoint " + "for multiplesymbols.">; + def breakpoint_set_source_pattern_regexp : + Option<"source-pattern-regexp", "p">, Group<9>, Arg<"RegularExpression">, + Required, Desc<"Set the breakpoint by specifying a regular expression which" + " is matched against the source text in a source file or files specified " + "with the -f can be specified more than once. If no source files " + "are specified, uses the current \"default source file\". If you want to " + "match against all source files, pass the \"--all-files\" option.">; + def breakpoint_set_all_files : Option<"all-files", "A">, Group<9>, + Desc<"All files are searched for source pattern matches.">; + def breakpoint_set_language_exception : Option<"language-exception", "E">, + Group<10>, Arg<"Language">, Required, + Desc<"Set the breakpoint on exceptions thrown by the specified language " + "(without options, on throw but not catch.)">; + def breakpoint_set_on_throw : Option<"on-throw", "w">, Group<10>, + Arg<"Boolean">, Desc<"Set the breakpoint on exception throW.">; + def breakpoint_set_on_catch : Option<"on-catch", "h">, Group<10>, + Arg<"Boolean">, Desc<"Set the breakpoint on exception catcH.">; + def breakpoint_set_language : Option<"language", "L">, GroupRange<3, 8>, + Arg<"Language">, + Desc<"Specifies the Language to use when interpreting the breakpoint's " + "expression (note: currently only implemented for setting breakpoints on " + "identifiers). If not set the target.language setting is used.">; + def breakpoint_set_skip_prologue : Option<"skip-prologue", "K">, + Arg<"Boolean">, Groups<[1,3,4,5,6,7,8]>, + Desc<"sKip the prologue if the breakpoint is at the beginning of a " + "function. If not set the target.skip-prologue setting is used.">; + def breakpoint_set_breakpoint_name : Option<"breakpoint-name", "N">, + Arg<"BreakpointName">, + Desc<"Adds this to the list of names for this breakpoint.">; + def breakpoint_set_address_slide : Option<"address-slide", "R">, + Arg<"Address">, Groups<[1,3,4,5,6,7,8]>, + Desc<"Add the specified offset to whatever address(es) the breakpoint " + "resolves to. At present this applies the offset directly as given, and " + "doesn't try to align it to instruction boundaries.">; + def breakpoint_set_move_to_nearest_code : Option<"move-to-nearest-code", "m">, + Groups<[1, 9]>, Arg<"Boolean">, + Desc<"Move breakpoints to nearest code. If not set the " + "target.move-to-nearest-codesetting is used.">; + /* Don't add this option till it actually does something useful... + def breakpoint_set_exception_typename : Option<"exception-typename", "O">, + Arg<"TypeName">, Desc<"The breakpoint will only stop if an " + "exception Object of this type is thrown. Can be repeated multiple times " + "to stop for multiple object types">; + */ +} + +let Command = "breakpoint clear" in { + def breakpoint_clear_file : Option<"file", "f">, Group<1>, Arg<"Filename">, + Completion<"SourceFile">, + Desc<"Specify the breakpoint by source location in this particular file.">; + def breakpoint_clear_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, + Required, + Desc<"Specify the breakpoint by source location at this particular line.">; +} + +let Command = "breakpoint delete" in { + def breakpoint_delete_force : Option<"force", "f">, Group<1>, + Desc<"Delete all breakpoints without querying for confirmation.">; + def breakpoint_delete_dummy_breakpoints : Option<"dummy-breakpoints", "D">, + Group<1>, Desc<"Delete Dummy breakpoints - i.e. breakpoints set before a " + "file is provided, which prime new targets.">; +} + +let Command = "breakpoint name" in { + def breakpoint_name_name : Option<"name", "N">, Group<1>, + Arg<"BreakpointName">, Desc<"Specifies a breakpoint name to use.">; + def breakpoint_name_breakpoint_id : Option<"breakpoint-id", "B">, Group<2>, + Arg<"BreakpointID">, Desc<"Specify a breakpoint ID to use.">; + def breakpoint_name_dummy_breakpoints : Option<"dummy-breakpoints", "D">, + Group<3>, Desc<"Operate on Dummy breakpoints - i.e. breakpoints set before " + "a file is provided, which prime new targets.">; + def breakpoint_name_help_string : Option<"help-string", "H">, Group<4>, + Arg<"None">, Desc<"A help string describing the purpose of this name.">; +} + +let Command = "breakpoint access" in { + def breakpoint_access_allow_list : Option<"allow-list", "L">, Group<1>, + Arg<"Boolean">, Desc<"Determines whether the breakpoint will show up in " + "break list if not referred to explicitly.">; + def breakpoint_access_allow_disable : Option<"allow-disable", "A">, Group<2>, + Arg<"Boolean">, Desc<"Determines whether the breakpoint can be disabled by " + "name or when all breakpoints are disabled.">; + def breakpoint_access_allow_delete : Option<"allow-delete", "D">, Group<3>, + Arg<"Boolean">, Desc<"Determines whether the breakpoint can be deleted by " + "name or when all breakpoints are deleted.">; +} + +let Command = "breakpoint read" in { + def breakpoint_read_file : Option<"file", "f">, Arg<"Filename">, Required, + Completion<"DiskFile">, + Desc<"The file from which to read the breakpoints.">; + def breakpoint_read_breakpoint_name : Option<"breakpoint-name", "N">, + Arg<"BreakpointName">, Desc<"Only read in breakpoints with this name.">; +} + +let Command = "breakpoint write" in { + def breakpoint_write_file : Option<"file", "f">, Arg<"Filename">, Required, + Completion<"DiskFile">, + Desc<"The file into which to write the breakpoints.">; + def breakpoint_write_append : Option<"append", "a">, + Desc<"Append to saved breakpoints file if it exists.">; +} + +let Command = "breakpoint command add" in { + def breakpoint_add_one_liner : Option<"one-liner", "o">, Group<1>, + Arg<"OneLiner">, Desc<"Specify a one-line breakpoint command inline. Be " + "sure to surround it with quotes.">; + def breakpoint_add_stop_on_error : Option<"stop-on-error", "e">, + Arg<"Boolean">, Desc<"Specify whether breakpoint command execution should " + "terminate on error.">; + def breakpoint_add_script_type : Option<"script-type", "s">, + EnumArg<"None", "ScriptOptionEnum()">, + Desc<"Specify the language for the commands - if none is specified, the " + "lldb command interpreter will be used.">; + def breakpoint_add_python_function : Option<"python-function", "F">, + Group<2>, Arg<"PythonFunction">, + Desc<"Give the name of a Python function to run as command for this " + "breakpoint. Be sure to give a module name if appropriate.">; + def breakpoint_add_dummy_breakpoints : Option<"dummy-breakpoints", "D">, + Desc<"Sets Dummy breakpoints - i.e. breakpoints set before a file is " + "provided, which prime new targets.">; +} + +let Command = "breakpoint command delete" in { + def breakpoint_command_delete_dummy_breakpoints : + Option<"dummy-breakpoints", "D">, Group<1>, + Desc<"Delete commands from Dummy breakpoints - i.e. breakpoints set before " + "a file is provided, which prime new targets.">; +} + +let Command = "disassemble" in { + def disassemble_options_bytes : Option<"bytes", "b">, + Desc<"Show opcode bytes when disassembling.">; + def disassemble_options_context : Option<"context", "C">, Arg<"NumLines">, + Desc<"Number of context lines of source to show.">; + def disassemble_options_mixed : Option<"mixed", "m">, + Desc<"Enable mixed source and assembly display.">; + def disassemble_options_raw : Option<"raw", "r">, + Desc<"Print raw disassembly with no symbol information.">; + def disassemble_options_plugin : Option<"plugin", "P">, Arg<"Plugin">, + Desc<"Name of the disassembler plugin you want to use.">; + def disassemble_options_flavor : Option<"flavor", "F">, + Arg<"DisassemblyFlavor">, Desc<"Name of the disassembly flavor you want to " + "use. Currently the only valid options are default, and for Intel " + "architectures, att and intel.">; + def disassemble_options_arch : Option<"arch", "A">, Arg<"Architecture">, + Desc<"Specify the architecture to use from cross disassembly.">; + def disassemble_options_start_address : Option<"start-address", "s">, + Groups<[1,2]>, Arg<"AddressOrExpression">, Required, + Desc<"Address at which to start disassembling.">; + def disassemble_options_end_address : Option<"end-address", "e">, Group<1>, + Arg<"AddressOrExpression">, Desc<"Address at which to end disassembling.">; + def disassemble_options_count : Option<"count", "c">, Groups<[2,3,4,5]>, + Arg<"NumLines">, Desc<"Number of instructions to display.">; + def disassemble_options_name : Option<"name", "n">, Group<3>, + Arg<"FunctionName">, Completion<"Symbol">, + Desc<"Disassemble entire contents of the given function name.">; + def disassemble_options_frame : Option<"frame", "f">, Group<4>, + Desc<"Disassemble from the start of the current frame's function.">; + def disassemble_options_pc : Option<"pc", "p">, Group<5>, + Desc<"Disassemble around the current pc.">; + def disassemble_options_line : Option<"line", "l">, Group<6>, + Desc<"Disassemble the current frame's current source line instructions if" + "there is debug line table information, else disassemble around the pc.">; + def disassemble_options_address : Option<"address", "a">, Group<7>, + Arg<"AddressOrExpression">, + Desc<"Disassemble function containing this address.">; +} + +let Command = "expression" in { + def expression_options_all_threads : Option<"all-threads", "a">, + Groups<[1,2]>, Arg<"Boolean">, Desc<"Should we run all threads if the " + "execution doesn't complete on one thread.">; + def expression_options_ignore_breakpoints : Option<"ignore-breakpoints", "i">, + Groups<[1,2]>, Arg<"Boolean">, + Desc<"Ignore breakpoint hits while running expressions">; + def expression_options_timeout : Option<"timeout", "t">, Groups<[1,2]>, + Arg<"UnsignedInteger">, + Desc<"Timeout value (in microseconds) for running the expression.">; + def expression_options_unwind_on_error : Option<"unwind-on-error", "u">, + Groups<[1,2]>, Arg<"Boolean">, + Desc<"Clean up program state if the expression causes a crash, or raises a " + "signal. Note, unlike gdb hitting a breakpoint is controlled by another " + "option (-i).">; + def expression_options_debug : Option<"debug", "g">, Groups<[1,2]>, + Desc<"When specified, debug the JIT code by setting a breakpoint on the " + "first instruction and forcing breakpoints to not be ignored (-i0) and no " + "unwinding to happen on error (-u0).">; + def expression_options_language : Option<"language", "l">, Groups<[1,2]>, + Arg<"Language">, Desc<"Specifies the Language to use when parsing the " + "expression. If not set the target.language setting is used.">; + def expression_options_apply_fixits : Option<"apply-fixits", "X">, + Groups<[1,2]>, Arg<"Language">, Desc<"If true, simple fix-it hints will be " + "automatically applied to the expression.">; + def expression_options_description_verbosity : + Option<"description-verbosity", "v">, Group<1>, + OptionalEnumArg<"DescriptionVerbosity", "DescriptionVerbosityTypes()">, + Desc<"How verbose should the output of this expression be, if the object " + "description is asked for.">; + def expression_options_top_level : Option<"top-level", "p">, Groups<[1,2]>, + Desc<"Interpret the expression as a complete translation unit, without " + "injecting it into the local context. Allows declaration of persistent, " + "top-level entities without a $ prefix.">; + def expression_options_allow_jit : Option<"allow-jit", "j">, Groups<[1,2]>, + Arg<"Boolean">, + Desc<"Controls whether the expression can fall back to being JITted if it's" + "not supported by the interpreter (defaults to true).">; +} + +let Command = "frame diag" in { + def frame_diag_register : Option<"register", "r">, Group<1>, + Arg<"RegisterName">, Desc<"A register to diagnose.">; + def frame_diag_address : Option<"address", "a">, Group<1>, Arg<"Address">, + Desc<"An address to diagnose.">; + def frame_diag_offset : Option<"offset", "o">, Group<1>, Arg<"Offset">, + Desc<"An optional offset. Requires --register.">; +} + +let Command = "frame select" in { + def frame_select_relative : Option<"relative", "r">, Group<1>, Arg<"Offset">, + Desc<"A relative frame index offset from the current frame index.">; +} + +let Command = "frame recognizer add" in { + def frame_recognizer_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Name of the module or shared library that this recognizer applies " + "to.">; + def frame_recognizer_function : Option<"function", "n">, Arg<"Name">, + Completion<"Symbol">, + Desc<"Name of the function that this recognizer applies to.">; + def frame_recognizer_python_class : Option<"python-class", "l">, Group<2>, + Arg<"PythonClass">, + Desc<"Give the name of a Python class to use for this frame recognizer.">; + def frame_recognizer_regex : Option<"regex", "x">, + Desc<"Function name and module name are actually regular expressions.">; +} + +let Command = "history" in { + def history_count : Option<"count", "c">, Group<1>, Arg<"UnsignedInteger">, + Desc<"How many history commands to print.">; + def history_start_index : Option<"start-index", "s">, Group<1>, + Arg<"UnsignedInteger">, Desc<"Index at which to start printing history " + "commands (or end to mean tail mode).">; + def history_end_index : Option<"end-index", "e">, Group<1>, + Arg<"UnsignedInteger">, + Desc<"Index at which to stop printing history commands.">; + def history_clear : Option<"clear", "C">, Group<2>, + Desc<"Clears the current command history.">; +} + +let Command = "log" in { + def log_file : Option<"file", "f">, Group<1>, Arg<"Filename">, + Desc<"Set the destination file to log to.">; + def log_threadsafe : Option<"threadsafe", "t">, Group<1>, + Desc<"Enable thread safe logging to avoid interweaved log lines.">; + def log_verbose : Option<"verbose", "v">, Group<1>, + Desc<"Enable verbose logging.">; + def log_sequence : Option<"sequence", "s">, Group<1>, + Desc<"Prepend all log lines with an increasing integer sequence id.">; + def log_timestamp : Option<"timestamp", "T">, Group<1>, + Desc<"Prepend all log lines with a timestamp.">; + def log_pid_tid : Option<"pid-tid", "p">, Group<1>, + Desc<"Prepend all log lines with the process and thread ID that generates " + "the log line.">; + def log_thread_name : Option<"thread-name", "n">, Group<1>, + Desc<"Prepend all log lines with the thread name for the thread that " + "generates the log line.">; + + def log_stack : Option<"stack", "S">, Group<1>, + Desc<"Append a stack backtrace to each log line.">; + def log_append : Option<"append", "a">, Group<1>, + Desc<"Append to the log file instead of overwriting.">; + def log_file_function : Option<"file-function", "F">, Group<1>, + Desc<"Prepend the names of files and function that generate the logs.">; +} + +let Command = "reproducer" in { + def reproducer_provider : Option<"provider", "p">, Group<1>, + EnumArg<"None", "ReproducerProviderType()">, + Required, Desc<"The reproducer provider to dump.">; + def reproducer_file : Option<"file", "f">, Group<1>, Arg<"Filename">, + Desc<"The reproducer path. If a reproducer is replayed and no path is " + "provided, that reproducer is dumped.">; +} + +let Command = "memory read" in { + def memory_read_num_per_line : Option<"num-per-line", "l">, Group<1>, + Arg<"NumberPerLine">, Desc<"The number of items per line to display.">; + def memory_read_binary : Option<"binary", "b">, Group<2>, + Desc<"If true, memory will be saved as binary. If false, the memory is " + "saved save as an ASCII dump that uses the format, size, count and number " + "per line settings.">; + def memory_read_type : Option<"type", "t">, Groups<[3,4]>, Arg<"Name">, + Required, Desc<"The name of a type to view memory as.">; + def memory_read_language : Option<"language", "x">, Group<4>, Arg<"Language">, + Desc<"The language of the type to view memory as.">; + def memory_read_offset : Option<"offset", "E">, Group<3>, Arg<"Count">, + Desc<"How many elements of the specified type to skip before starting to " + "display data.">; + def memory_read_force : Option<"force", "r">, Groups<[1,2,3]>, + Desc<"Necessary if reading over target.max-memory-read-size bytes.">; +} + +let Command = "memory find" in { + def memory_find_expression : Option<"expression", "e">, Group<1>, + Arg<"Expression">, Required, + Desc<"Evaluate an expression to obtain a byte pattern.">; + def memory_find_string : Option<"string", "s">, Group<2>, Arg<"Name">, + Required, Desc<"Use text to find a byte pattern.">; + def memory_find_count : Option<"count", "c">, Arg<"Count">, + Desc<"How many times to perform the search.">; + def memory_find_dump_offset : Option<"dump-offset", "o">, Arg<"Offset">, + Desc<"When dumping memory for a match, an offset from the match location to" + " start dumping from.">; +} + +let Command = "memory write" in { + def memory_write_infile : Option<"infile", "i">, Group<1>, Arg<"Filename">, + Required, Desc<"Write memory using the contents of a file.">; + def memory_write_offset : Option<"offset", "o">, Group<1>, Arg<"Offset">, + Desc<"Start writing bytes from an offset within the input file.">; +} + +let Command = "register read" in { + def register_read_alternate : Option<"alternate", "A">, + Desc<"Display register names using the alternate register name if there " + "is one.">; + def register_read_set : Option<"set", "s">, Group<1>, Arg<"Index">, + Desc<"Specify which register sets to dump by index.">; + def register_read_all : Option<"all", "a">, Group<2>, + Desc<"Show all register sets.">; +} + +let Command = "source" in { + def source_stop_on_error : Option<"stop-on-error", "e">, Arg<"Boolean">, + Desc<"If true, stop executing commands on error.">; + def source_stop_on_continue : Option<"stop-on-continue", "c">, Arg<"Boolean">, + Desc<"If true, stop executing commands on continue.">; + def source_silent_run : Option<"silent-run", "s">, Arg<"Boolean">, + Desc<"If true don't echo commands while executing.">; +} + +let Command = "alias" in { + def alias_help : Option<"help", "h">, Arg<"HelpText">, + Desc<"Help text for this command">; + def alias_long_help : Option<"long-help", "H">, Arg<"HelpText">, + Desc<"Long help text for this command">; +} + +let Command = "regex" in { + def regex_help : Option<"help", "h">, Group<1>, Arg<"None">, + Desc<"The help text to display for this command.">; + def regex_syntax : Option<"syntax", "s">, Group<1>, Arg<"None">, + Desc<"A syntax string showing the typical usage syntax.">; +} + +let Command = "permissions" in { + def permissions_permissions_value : Option<"permissions-value", "v">, + Arg<"PermissionsNumber">, + Desc<"Give out the numeric value for permissions (e.g. 757)">; + def permissions_permissions_string : Option<"permissions-string", "s">, + Arg<"PermissionsString">, + Desc<"Give out the string value for permissions (e.g. rwxr-xr--).">; + def permissions_user_read : Option<"user-read", "r">, + Desc<"Allow user to read.">; + def permissions_user_write : Option<"user-write", "w">, + Desc<"Allow user to write.">; + def permissions_user_exec : Option<"user-exec", "x">, + Desc<"Allow user to execute.">; + def permissions_group_read : Option<"group-read", "R">, + Desc<"Allow group to read.">; + def permissions_group_write : Option<"group-write", "W">, + Desc<"Allow group to write.">; + def permissions_group_exec : Option<"group-exec", "X">, + Desc<"Allow group to execute.">; + def permissions_world_read : Option<"world-read", "d">, + Desc<"Allow world to read.">; + def permissions_world_write : Option<"world-write", "t">, + Desc<"Allow world to write.">; + def permissions_world_exec : Option<"world-exec", "e">, + Desc<"Allow world to execute.">; +} + +let Command = "platform fread" in { + def platform_fread_offset : Option<"offset", "o">, Group<1>, Arg<"Index">, + Desc<"Offset into the file at which to start reading.">; + def platform_fread_count : Option<"count", "c">, Group<1>, Arg<"Count">, + Desc<"Number of bytes to read from the file.">; +} + +let Command = "platform fwrite" in { + def platform_fwrite_offset : Option<"offset", "o">, Group<1>, Arg<"Index">, + Desc<"Offset into the file at which to start reading.">; + def platform_fwrite_data : Option<"data", "d">, Group<1>, Arg<"Value">, + Desc<"Text to write to the file.">; +} + +let Command = "platform process list" in { + def platform_process_list_pid : Option<"pid", "p">, Group<1>, Arg<"Pid">, + Desc<"List the process info for a specific process ID.">; + def platform_process_list_name : Option<"name", "n">, Group<2>, + Arg<"ProcessName">, Required, + Desc<"Find processes with executable basenames that match a string.">; + def platform_process_list_ends_with : Option<"ends-with", "e">, Group<3>, + Arg<"ProcessName">, Required, + Desc<"Find processes with executable basenames that end with a string.">; + def platform_process_list_starts_with : Option<"starts-with", "s">, Group<4>, + Arg<"ProcessName">, Required, + Desc<"Find processes with executable basenames that start with a string.">; + def platform_process_list_contains : Option<"contains", "c">, Group<5>, + Arg<"ProcessName">, Required, + Desc<"Find processes with executable basenames that contain a string.">; + def platform_process_list_regex : Option<"regex", "r">, Group<6>, + Arg<"RegularExpression">, Required, + Desc<"Find processes with executable basenames that match a regular " + "expression.">; + def platform_process_list_parent : Option<"parent", "P">, GroupRange<2, 6>, + Arg<"Pid">, Desc<"Find processes that have a matching parent process ID.">; + def platform_process_list_uid : Option<"uid", "u">, GroupRange<2, 6>, + Arg<"UnsignedInteger">, Validator<"&posix_validator">, + Desc<"Find processes that have a matching user ID.">; + def platform_process_list_euid : Option<"euid", "U">, GroupRange<2, 6>, + Arg<"UnsignedInteger">, Validator<"&posix_validator">, + Desc<"Find processes that have a matching effective user ID.">; + def platform_process_list_gid : Option<"gid", "g">, GroupRange<2, 6>, + Arg<"UnsignedInteger">, Validator<"&posix_validator">, + Desc<"Find processes that have a matching group ID.">; + def platform_process_list_egid : Option<"egid", "G">, GroupRange<2, 6>, + Arg<"UnsignedInteger">, Validator<"&posix_validator">, + Desc<"Find processes that have a matching effective group ID.">; + def platform_process_list_arch : Option<"arch", "a">, GroupRange<2, 6>, + Arg<"Architecture">, + Desc<"Find processes that have a matching architecture.">; + def platform_process_list_show_args : Option<"show-args", "A">, + GroupRange<1, 6>, + Desc<"Show process arguments instead of the process executable basename.">; + def platform_process_list_all_users: Option<"all-users", "x">, + GroupRange<1,6>, + Desc<"Show processes matching all user IDs.">; + def platform_process_list_verbose : Option<"verbose", "v">, GroupRange<1, 6>, + Desc<"Enable verbose output.">; +} + +let Command = "platform process attach" in { + def platform_process_attach_plugin : Option<"plugin", "P">, Arg<"Plugin">, + Desc<"Name of the process plugin you want to use.">; + def platform_process_attach_pid : Option<"pid", "p">, Group<1>, Arg<"Pid">, + Desc<"The process ID of an existing process to attach to.">; + def platform_process_attach_name : Option<"name", "n">, Group<2>, + Arg<"ProcessName">, Desc<"The name of the process to attach to.">; + def platform_process_attach_waitfor : Option<"waitfor", "w">, Group<2>, + Desc<"Wait for the process with <process-name> to launch.">; +} + +let Command = "platform shell" in { + def platform_shell_timeout : Option<"timeout", "t">, Arg<"Value">, + Desc<"Seconds to wait for the remote host to finish running the command.">; +} + +let Command = "process attach" in { + def process_attach_continue : Option<"continue", "c">, + Desc<"Immediately continue the process once attached.">; + def process_attach_plugin : Option<"plugin", "P">, Arg<"Plugin">, + Desc<"Name of the process plugin you want to use.">; + def process_attach_pid : Option<"pid", "p">, Group<1>, Arg<"Pid">, + Desc<"The process ID of an existing process to attach to.">; + def process_attach_name : Option<"name", "n">, Group<2>, Arg<"ProcessName">, + Desc<"The name of the process to attach to.">; + def process_attach_include_existing : Option<"include-existing", "i">, + Group<2>, Desc<"Include existing processes when doing attach -w.">; + def process_attach_waitfor : Option<"waitfor", "w">, Group<2>, + Desc<"Wait for the process with <process-name> to launch.">; +} + +let Command = "process continue" in { + def process_continue_ignore_count : Option<"ignore-count", "i">, + Arg<"UnsignedInteger">, Desc<"Ignore <N> crossings of the breakpoint (if it" + " exists) for the currently selected thread.">; +} + +let Command = "process detach" in { + def process_detach_keep_stopped : Option<"keep-stopped", "s">, Group<1>, + Arg<"Boolean">, Desc<"Whether or not the process should be kept stopped on" + " detach (if possible).">; +} + +let Command = "process connect" in { + def process_connect_plugin : Option<"plugin", "p">, Arg<"Plugin">, + Desc<"Name of the process plugin you want to use.">; +} + +let Command = "process load" in { + def process_load_install : Option<"install", "i">, OptionalArg<"Path">, + Desc<"Install the shared library to the target. If specified without an " + "argument then the library will installed in the current working " + "directory.">; +} + +let Command = "process handle" in { + def process_handle_stop : Option<"stop", "s">, Group<1>, Arg<"Boolean">, + Desc<"Whether or not the process should be stopped if the signal is " + "received.">; + def process_handle_notify : Option<"notify", "n">, Group<1>, Arg<"Boolean">, + Desc<"Whether or not the debugger should notify the user if the signal is " + "received.">; + def process_handle_pass : Option<"pass", "p">, Group<1>, Arg<"Boolean">, + Desc<"Whether or not the signal should be passed to the process.">; +} + +let Command = "script import" in { + def script_import_allow_reload : Option<"allow-reload", "r">, Group<1>, + Desc<"Allow the script to be loaded even if it was already loaded before. " + "This argument exists for backwards compatibility, but reloading is always " + "allowed, whether you specify it or not.">; +} + +let Command = "script add" in { + def script_add_function : Option<"function", "f">, Group<1>, + Arg<"PythonFunction">, + Desc<"Name of the Python function to bind to this command name.">; + def script_add_class : Option<"class", "c">, Group<2>, Arg<"PythonClass">, + Desc<"Name of the Python class to bind to this command name.">; + def script_add_help : Option<"help", "h">, Group<1>, Arg<"HelpText">, + Desc<"The help text to display for this command.">; + def script_add_synchronicity : Option<"synchronicity", "s">, + EnumArg<"ScriptedCommandSynchronicity", "ScriptSynchroType()">, + Desc<"Set the synchronicity of this command's executions with regard to " + "LLDB event system.">; +} +let Command = "source info" in { + def source_info_count : Option<"count", "c">, Arg<"Count">, + Desc<"The number of line entries to display.">; + def source_info_shlib : Option<"shlib", "s">, Groups<[1,2]>, Arg<"ShlibName">, + Completion<"Module">, Desc<"Look up the source in the given module or " + "shared library (can be specified more than once).">; + def source_info_file : Option<"file", "f">, Group<1>, Arg<"Filename">, + Completion<"SourceFile">, Desc<"The file from which to display source.">; + def source_info_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, + Desc<"The line number at which to start the displaying lines.">; + def source_info_end_line : Option<"end-line", "e">, Group<1>, Arg<"LineNum">, + Desc<"The line number at which to stop displaying lines.">; + def source_info_name : Option<"name", "n">, Group<2>, Arg<"Symbol">, + Completion<"Symbol">, + Desc<"The name of a function whose source to display.">; + def source_info_address : Option<"address", "a">, Group<3>, + Arg<"AddressOrExpression">, Desc<"Lookup the address and display the source" + " information for the corresponding file and line.">; +} + +let Command = "source list" in { + def source_list_count : Option<"count", "c">, Arg<"Count">, + Desc<"The number of source lines to display.">; + def source_list_shlib : Option<"shlib", "s">, Groups<[1,2]>, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Look up the source file in the given shared library.">; + def source_list_show_breakpoints : Option<"show-breakpoints", "b">, + Desc<"Show the line table locations from the debug information that " + "indicate valid places to set source level breakpoints.">; + def source_list_file : Option<"file", "f">, Group<1>, Arg<"Filename">, + Completion<"SourceFile">, Desc<"The file from which to display source.">; + def source_list_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, + Desc<"The line number at which to start the display source.">; + def source_list_name : Option<"name", "n">, Group<2>, Arg<"Symbol">, + Completion<"Symbol">, + Desc<"The name of a function whose source to display.">; + def source_list_address : Option<"address", "a">, Group<3>, + Arg<"AddressOrExpression">, Desc<"Lookup the address and display the source" + " information for the corresponding file and line.">; + def source_list_reverse : Option<"reverse", "r">, Group<4>, Desc<"Reverse the" + " listing to look backwards from the last displayed block of source.">; +} + +let Command = "target dependents" in { + def dependents_no_dependents : Option<"no-dependents", "d">, Group<1>, + OptionalEnumArg<"Value", "OptionEnumValues(g_dependents_enumaration)">, + Desc<"Whether or not to load dependents when creating a target. If the " + "option is not specified, the value is implicitly 'default'. If the " + "option is specified but without a value, the value is implicitly " + "'true'.">; +} + +let Command = "target modules dump" in { + def target_modules_dump_verbose : Option<"verbose", "v">, + Desc<"Enable verbose dump.">; +} + +let Command = "target modules list" in { + def target_modules_list_address : Option<"address", "a">, Group<1>, + Arg<"AddressOrExpression">, Desc<"Display the image at this address.">; + def target_modules_list_arch : Option<"arch", "A">, Group<1>, + OptionalArg<"Width">, Desc<"Display the architecture when listing images.">; + def target_modules_list_triple : Option<"triple", "t">, Group<1>, + OptionalArg<"Width">, Desc<"Display the triple when listing images.">; + def target_modules_list_header : Option<"header", "h">, Group<1>, + Desc<"Display the image base address as a load address if debugging, a file" + " address otherwise.">; + def target_modules_list_offset : Option<"offset", "o">, Group<1>, + Desc<"Display the image load address offset from the base file address " + "(the slide amount).">; + def target_modules_list_uuid : Option<"uuid", "u">, Group<1>, + Desc<"Display the UUID when listing images.">; + def target_modules_list_fullpath : Option<"fullpath", "f">, Group<1>, + OptionalArg<"Width">, + Desc<"Display the fullpath to the image object file.">; + def target_modules_list_directory : Option<"directory", "d">, Group<1>, + OptionalArg<"Width">, Desc<"Display the directory with optional width for " + "the image object file.">; + def target_modules_list_basename : Option<"basename", "b">, Group<1>, + OptionalArg<"Width">, Desc<"Display the basename with optional width for " + "the image object file.">; + def target_modules_list_symfile : Option<"symfile", "s">, Group<1>, + OptionalArg<"Width">, Desc<"Display the fullpath to the image symbol file " + "with optional width.">; + def target_modules_list_symfile_unique : Option<"symfile-unique", "S">, + Group<1>, OptionalArg<"Width">, Desc<"Display the symbol file with optional" + " width only if it is different from the executable object file.">; + def target_modules_list_mod_time : Option<"mod-time", "m">, Group<1>, + OptionalArg<"Width">, Desc<"Display the modification time with optional " + "width of the module.">; + def target_modules_list_ref_count : Option<"ref-count", "r">, Group<1>, + OptionalArg<"Width">, Desc<"Display the reference count if the module is " + "still in the shared module cache.">; + def target_modules_list_pointer : Option<"pointer", "p">, Group<1>, + OptionalArg<"None">, Desc<"Display the module pointer.">; + def target_modules_list_global : Option<"global", "g">, Group<1>, + Desc<"Display the modules from the global module list, not just the " + "current target.">; +} + +let Command = "target modules show unwind" in { + def target_modules_show_unwind_name : Option<"name", "n">, Group<1>, + Arg<"FunctionName">, + Desc<"Show unwind instructions for a function or symbol name.">; + def target_modules_show_unwind_address : Option<"address", "a">, Group<2>, + Arg<"AddressOrExpression">, Desc<"Show unwind instructions for a function " + "or symbol containing an address">; +} + +let Command = "target modules lookup" in { + def target_modules_lookup_address : Option<"address", "a">, Group<1>, + Arg<"AddressOrExpression">, Required, Desc<"Lookup an address in one or " + "more target modules.">; + def target_modules_lookup_offset : Option<"offset", "o">, Group<1>, + Arg<"Offset">, Desc<"When looking up an address subtract <offset> from any " + "addresses before doing the lookup.">; + // FIXME: re-enable regex for types when the LookupTypeInModule actually uses + // the regex option by adding to group 6. + def target_modules_lookup_regex : Option<"regex", "r">, Groups<[2,4,5]>, + Desc<"The <name> argument for name lookups are regular expressions.">; + def target_modules_lookup_symbol : Option<"symbol", "s">, Group<2>, + Arg<"Symbol">, Required, Desc<"Lookup a symbol by name in the symbol tables" + " in one or more target modules.">; + def target_modules_lookup_file : Option<"file", "f">, Group<3>, + Arg<"Filename">, Required, Desc<"Lookup a file by fullpath or basename in " + "one or more target modules.">; + def target_modules_lookup_line : Option<"line", "l">, Group<3>, + Arg<"LineNum">, Desc<"Lookup a line number in a file (must be used in " + "conjunction with --file).">; + def target_modules_lookup_no_inlines : Option<"no-inlines", "i">, + GroupRange<3,5>, + Desc<"Ignore inline entries (must be used in conjunction with --file or " + "--function).">; + def target_modules_lookup_function : Option<"function", "F">, Group<4>, + Arg<"FunctionName">, Required, Desc<"Lookup a function by name in the debug" + " symbols in one or more target modules.">; + def target_modules_lookup_name : Option<"name", "n">, Group<5>, + Arg<"FunctionOrSymbol">, Required, Desc<"Lookup a function or symbol by " + "name in one or more target modules.">; + def target_modules_lookup_type : Option<"type", "t">, Group<6>, Arg<"Name">, + Required, Desc<"Lookup a type by name in the debug symbols in one or more " + "target modules.">; + def target_modules_lookup_verbose : Option<"verbose", "v">, + Desc<"Enable verbose lookup information.">; + def target_modules_lookup_all : Option<"all", "A">, Desc<"Print all matches, " + "not just the best match, if a best match is available.">; +} + +let Command = "target stop hook add" in { + def target_stop_hook_add_one_liner : Option<"one-liner", "o">, + Arg<"OneLiner">, Desc<"Add a command for the stop hook. Can be specified " + "more than once, and commands will be run in the order they appear.">; + def target_stop_hook_add_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Set the module within which the stop-hook is to be run.">; + def target_stop_hook_add_thread_index : Option<"thread-index", "x">, + Arg<"ThreadIndex">, Desc<"The stop hook is run only for the thread whose " + "index matches this argument.">; + def target_stop_hook_add_thread_id : Option<"thread-id", "t">, + Arg<"ThreadID">, Desc<"The stop hook is run only for the thread whose TID " + "matches this argument.">; + def target_stop_hook_add_thread_name : Option<"thread-name", "T">, + Arg<"ThreadName">, Desc<"The stop hook is run only for the thread whose " + "thread name matches this argument.">; + def target_stop_hook_add_queue_name : Option<"queue-name", "q">, + Arg<"QueueName">, Desc<"The stop hook is run only for threads in the queue " + "whose name is given by this argument.">; + def target_stop_hook_add_file : Option<"file", "f">, Group<1>, + Arg<"Filename">, Desc<"Specify the source file within which the stop-hook " + "is to be run.">, Completion<"SourceFile">; + def target_stop_hook_add_start_line : Option<"start-line", "l">, Group<1>, + Arg<"LineNum">, Desc<"Set the start of the line range for which the " + "stop-hook is to be run.">; + def target_stop_hook_add_end_line : Option<"end-line", "e">, Group<1>, + Arg<"LineNum">, Desc<"Set the end of the line range for which the stop-hook" + " is to be run.">; + def target_stop_hook_add_classname : Option<"classname", "c">, Group<2>, + Arg<"ClassName">, + Desc<"Specify the class within which the stop-hook is to be run.">; + def target_stop_hook_add_name : Option<"name", "n">, Group<3>, + Arg<"FunctionName">, Desc<"Set the function name within which the stop hook" + " will be run.">, Completion<"Symbol">; + def target_stop_hook_add_auto_continue : Option<"auto-continue", "G">, + Arg<"Boolean">, Desc<"The breakpoint will auto-continue after running its" + " commands.">; +} + +let Command = "thread backtrace" in { + def thread_backtrace_count : Option<"count", "c">, Group<1>, Arg<"Count">, + Desc<"How many frames to display (-1 for all)">; + def thread_backtrace_start : Option<"start", "s">, Group<1>, + Arg<"FrameIndex">, Desc<"Frame in which to start the backtrace">; + def thread_backtrace_extended : Option<"extended", "e">, Group<1>, + Arg<"Boolean">, Desc<"Show the extended backtrace, if available">; +} + +let Command = "thread step scope" in { + def thread_step_scope_step_in_avoids_no_debug : + Option<"step-in-avoids-no-debug", "a">, Group<1>, Arg<"Boolean">, + Desc<"A boolean value that sets whether stepping into functions will step " + "over functions with no debug information.">; + def thread_step_scope_step_out_avoids_no_debug : + Option<"step-out-avoids-no-debug", "A">, Group<1>, Arg<"Boolean">, + Desc<"A boolean value, if true stepping out of functions will continue to" + " step out till it hits a function with debug information.">; + def thread_step_scope_count : Option<"count", "c">, Group<1>, Arg<"Count">, + Desc<"How many times to perform the stepping operation - currently only " + "supported for step-inst and next-inst.">; + def thread_step_scope_end_linenumber : Option<"end-linenumber", "e">, + Group<1>, Arg<"LineNum">, Desc<"The line at which to stop stepping - " + "defaults to the next line and only supported for step-in and step-over." + " You can also pass the string 'block' to step to the end of the current" + " block. This is particularly use in conjunction with --step-target to" + " step through a complex calling sequence.">; + def thread_step_scope_run_mode : Option<"run-mode", "m">, Group<1>, + EnumArg<"RunMode", "TriRunningModes()">, Desc<"Determine how to run other " + "threads while stepping the current thread.">; + def thread_step_scope_step_over_regexp : Option<"step-over-regexp", "r">, + Group<1>, Arg<"RegularExpression">, Desc<"A regular expression that defines" + "function names to not to stop at when stepping in.">; + def thread_step_scope_step_in_target : Option<"step-in-target", "t">, + Group<1>, Arg<"FunctionName">, Desc<"The name of the directly called " + "function step in should stop at when stepping into.">; + def thread_step_scope_python_class : Option<"python-class", "C">, Group<2>, + Arg<"PythonClass">, Desc<"The name of the class that will manage this step " + "- only supported for Scripted Step.">; +} + +let Command = "thread until" in { + def thread_until_frame : Option<"frame", "f">, Group<1>, Arg<"FrameIndex">, + Desc<"Frame index for until operation - defaults to 0">; + def thread_until_thread : Option<"thread", "t">, Group<1>, Arg<"ThreadIndex">, + Desc<"Thread index for the thread for until operation">; + def thread_until_run_mode : Option<"run-mode", "m">, Group<1>, + EnumArg<"RunMode", "DuoRunningModes()">, Desc<"Determine how to run other" + "threads while stepping this one">; + def thread_until_address : Option<"address", "a">, Group<1>, + Arg<"AddressOrExpression">, Desc<"Run until we reach the specified address," + "or leave the function - can be specified multiple times.">; +} + +let Command = "thread info" in { + def thread_info_json : Option<"json", "j">, Desc<"Display the thread info in" + " JSON format.">; + def thread_info_stop_info : Option<"stop-info", "s">, Desc<"Display the " + "extended stop info in JSON format.">; +} + +let Command = "thread return" in { + def thread_return_from_expression : Option<"from-expression", "x">, + Desc<"Return from the innermost expression evaluation.">; +} + +let Command = "thread jump" in { + def thread_jump_file : Option<"file", "f">, Group<1>, Arg<"Filename">, + Completion<"SourceFile">, Desc<"Specifies the source file to jump to.">; + def thread_jump_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, + Required, Desc<"Specifies the line number to jump to.">; + def thread_jump_by : Option<"by", "b">, Group<2>, Arg<"Offset">, Required, + Desc<"Jumps by a relative line offset from the current line.">; + def thread_jump_address : Option<"address", "a">, Group<3>, + Arg<"AddressOrExpression">, Required, Desc<"Jumps to a specific address.">; + def thread_jump_force : Option<"force", "r">, Groups<[1,2,3]>, + Desc<"Allows the PC to leave the current function.">; +} + +let Command = "thread plan list" in { + def thread_plan_list_verbose : Option<"verbose", "v">, Group<1>, + Desc<"Display more information about the thread plans">; + def thread_plan_list_internal : Option<"internal", "i">, Group<1>, + Desc<"Display internal as well as user thread plans">; +} + +let Command = "type summary add" in { + def type_summary_add_category : Option<"category", "w">, Arg<"Name">, + Desc<"Add this to the given category instead of the default one.">; + def type_summary_add_cascade : Option<"cascade", "C">, Arg<"Boolean">, + Desc<"If true, cascade through typedef chains.">; + def type_summary_add_no_value : Option<"no-value", "v">, + Desc<"Don't show the value, just show the summary, for this type.">; + def type_summary_add_skip_pointers : Option<"skip-pointers", "p">, + Desc<"Don't use this format for pointers-to-type objects.">; + def type_summary_add_skip_references : Option<"skip-references", "r">, + Desc<"Don't use this format for references-to-type objects.">; + def type_summary_add_regex : Option<"regex", "x">, + Desc<"Type names are actually regular expressions.">; + def type_summary_add_inline_children : Option<"inline-children", "c">, + Group<1>, Required, + Desc<"If true, inline all child values into summary string.">; + def type_summary_add_omit_names : Option<"omit-names", "O">, Group<1>, + Desc<"If true, omit value names in the summary display.">; + def type_summary_add_summary_string : Option<"summary-string", "s">, Group<2>, + Arg<"SummaryString">, Required, + Desc<"Summary string used to display text and object contents.">; + def type_summary_add_python_script : Option<"python-script", "o">, Group<3>, + Arg<"PythonScript">, + Desc<"Give a one-liner Python script as part of the command.">; + def type_summary_add_python_function : Option<"python-function", "F">, + Group<3>, Arg<"PythonFunction">, + Desc<"Give the name of a Python function to use for this type.">; + def type_summary_add_input_python : Option<"input-python", "P">, Group<3>, + Desc<"Input Python code to use for this type manually.">; + def type_summary_add_expand : Option<"expand", "e">, Groups<[2,3]>, + Desc<"Expand aggregate data types to show children on separate lines.">; + def type_summary_add_hide_empty : Option<"hide-empty", "h">, Groups<[2,3]>, + Desc<"Do not expand aggregate data types with no children.">; + def type_summary_add_name : Option<"name", "n">, Groups<[2,3]>, Arg<"Name">, + Desc<"A name for this summary string.">; +} + +let Command = "type synth add" in { + def type_synth_add_cascade : Option<"cascade", "C">, Arg<"Boolean">, + Desc<"If true, cascade through typedef chains.">; + def type_synth_add_skip_pointers : Option<"skip-pointers", "p">, + Desc<"Don't use this format for pointers-to-type objects.">; + def type_synth_add_skip_references : Option<"skip-references", "r">, + Desc<"Don't use this format for references-to-type objects.">; + def type_synth_add_category : Option<"category", "w">, Arg<"Name">, + Desc<"Add this to the given category instead of the default one.">; + def type_synth_add_python_class : Option<"python-class", "l">, Group<2>, + Arg<"PythonClass">, + Desc<"Use this Python class to produce synthetic children.">; + def type_synth_add_input_python : Option<"input-python", "P">, Group<3>, + Desc<"Type Python code to generate a class that provides synthetic " + "children.">; + def type_synth_add_regex : Option<"regex", "x">, + Desc<"Type names are actually regular expressions.">; +} + +let Command = "type format add" in { + def type_format_add_category : Option<"category", "w">, Arg<"Name">, + Desc<"Add this to the given category instead of the default one.">; + def type_format_add_cascade : Option<"cascade", "C">, Arg<"Boolean">, + Desc<"If true, cascade through typedef chains.">; + def type_format_add_skip_pointers : Option<"skip-pointers", "p">, + Desc<"Don't use this format for pointers-to-type objects.">; + def type_format_add_skip_references : Option<"skip-references", "r">, + Desc<"Don't use this format for references-to-type objects.">; + def type_format_add_regex : Option<"regex", "x">, + Desc<"Type names are actually regular expressions.">; + def type_format_add_type : Option<"type", "t">, Group<2>, Arg<"Name">, + Desc<"Format variables as if they were of this type.">; +} + +let Command = "type formatter delete" in { + def type_formatter_delete_all : Option<"all", "a">, Group<1>, + Desc<"Delete from every category.">; + def type_formatter_delete_category : Option<"category", "w">, Group<2>, + Arg<"Name">, Desc<"Delete from given category.">; + def type_formatter_delete_language : Option<"language", "l">, Group<3>, + Arg<"Language">, Desc<"Delete from given language's category.">; +} + +let Command = "type formatter clear" in { + def type_formatter_clear_all : Option<"all", "a">, + Desc<"Clear every category.">; +} + +let Command = "type formatter list" in { + def type_formatter_list_category_regex : Option<"category-regex", "w">, + Group<1>, Arg<"Name">, Desc<"Only show categories matching this filter.">; + def type_formatter_list_language : Option<"language", "l">, Group<2>, + Arg<"Language">, Desc<"Only show the category for a specific language.">; +} + +let Command = "type category define" in { + def type_category_define_enabled : Option<"enabled", "e">, + Desc<"If specified, this category will be created enabled.">; + def type_category_define_language : Option<"language", "l">, Arg<"Language">, + Desc<"Specify the language that this category is supported for.">; +} + +let Command = "type category enable" in { + def type_category_enable_language : Option<"language", "l">, Arg<"Language">, + Desc<"Enable the category for this language.">; +} + +let Command = "type category disable" in { + def type_category_disable_language : Option<"language", "l">, Arg<"Language">, + Desc<"Enable the category for this language.">; +} + +let Command = "type filter add" in { + def type_filter_add_cascade : Option<"cascade", "C">, Arg<"Boolean">, + Desc<"If true, cascade through typedef chains.">; + def type_filter_add_skip_pointers : Option<"skip-pointers", "p">, + Desc<"Don't use this format for pointers-to-type objects.">; + def type_filter_add_skip_references : Option<"skip-references", "r">, + Desc<"Don't use this format for references-to-type objects.">; + def type_filter_add_category : Option<"category", "w">, Arg<"Name">, + Desc<"Add this to the given category instead of the default one.">; + def type_filter_add_child : Option<"child", "c">, Arg<"ExpressionPath">, + Desc<"Include this expression path in the synthetic view.">; + def type_filter_add_regex : Option<"regex", "x">, + Desc<"Type names are actually regular expressions.">; +} + +let Command = "type lookup" in { + def type_lookup_show_help : Option<"show-help", "h">, + Desc<"Display available help for types">; + def type_lookup_language : Option<"language", "l">, Arg<"Language">, + Desc<"Which language's types should the search scope be">; +} + +let Command = "watchpoint list" in { + def watchpoint_list_brief : Option<"brief", "b">, Group<1>, Desc<"Give a " + "brief description of the watchpoint (no location info).">; + def watchpoint_list_full : Option<"full", "f">, Group<2>, Desc<"Give a full " + "description of the watchpoint and its locations.">; + def watchpoint_list_verbose : Option<"verbose", "v">, Group<3>, Desc<"Explain" + "everything we know about the watchpoint (for debugging debugger bugs).">; +} + +let Command = "watchpoint ignore" in { + def watchpoint_ignore_ignore_count : Option<"ignore-count", "i">, + Arg<"Count">, Required, Desc<"Set the number of times this watchpoint is" + " skipped before stopping.">; +} + +let Command = "watchpoint modify" in { + def watchpoint_modify_condition : Option<"condition", "c">, Arg<"Expression">, + Desc<"The watchpoint stops only if this condition expression evaluates " + "to true.">; +} + +let Command = "watchpoint command add" in { + def watchpoint_command_add_one_liner : Option<"one-liner", "o">, Group<1>, + Arg<"OneLiner">, Desc<"Specify a one-line watchpoint command inline. Be " + "sure to surround it with quotes.">; + def watchpoint_command_add_stop_on_error : Option<"stop-on-error", "e">, + Arg<"Boolean">, Desc<"Specify whether watchpoint command execution should " + "terminate on error.">; + def watchpoint_command_add_script_type : Option<"script-type", "s">, + EnumArg<"None", "ScriptOptionEnum()">, Desc<"Specify the language for the" + " commands - if none is specified, the lldb command interpreter will be " + "used.">; + def watchpoint_command_add_python_function : Option<"python-function", "F">, + Group<2>, Arg<"PythonFunction">, Desc<"Give the name of a Python function " + "to run as command for this watchpoint. Be sure to give a module name if " + "appropriate.">; +} diff --git a/lldb/source/Commands/OptionsBase.td b/lldb/source/Commands/OptionsBase.td new file mode 100644 index 0000000000000..f6967f067bf40 --- /dev/null +++ b/lldb/source/Commands/OptionsBase.td @@ -0,0 +1,178 @@ + +// The fields below describe how the fields of `OptionDefinition` struct are +// initialized by different definitions in the Options.td and this file. +//////////////////////////////////////////////////////////////////////////////// +// Field: usage_mask +// Default value: LLDB_OPT_SET_ALL (Option allowed in all groups) +// Set by: +// - `Group`: Sets a single group to this option. +// Example: def foo : Option<"foo", "f">, Group<1>; +// - `Groups`: Sets a given list of group numbers. +// Example: def foo : Option<"foo", "f">, Groups<[1,4,6]>; +// - `GroupRange`: Sets an interval of groups. Start and end are inclusive. +// Example: def foo : Option<"foo", "f">, GroupRange<1, 4>; +// Sets group 1, 2, 3, 4 for the option. +//////////////////////////////////////////////////////////////////////////////// +// Field: required +// Default value: false (Not required) +// Set by: +// - `Required`: Marks the option as required. +// Example: def foo : Option<"foo", "f">, Required; +//////////////////////////////////////////////////////////////////////////////// +// Field: long_option +// Default value: not available (has to be defined in Option) +// Set by: +// - `Option` constructor: Already set by constructor. +// Example: def foo : Option<"long-option", "l"> +// ^ +// long option value +//////////////////////////////////////////////////////////////////////////////// +// Field: short_option +// Default value: not available (has to be defined in Option) +// Set by: +// - `Option` constructor: Already set by constructor. +// Example: def foo : Option<"long-option", "l"> +// ^ +// short option +//////////////////////////////////////////////////////////////////////////////// +// Field: option_has_arg +// Default value: OptionParser::eNoArgument (No argument allowed) +// Set by: +// - `OptionalArg`: Sets the argument type and marks it as optional. +// - `Arg`: Sets the argument type and marks it as required. +// - `EnumArg`: Sets the argument type to an enum and marks it as required. +// - `OptionalEnumArg`: Same as EnumArg but marks it as optional. +// See argument_type field for more info. +//////////////////////////////////////////////////////////////////////////////// +// Field: validator +// Default value: 0 (No validator for option) +// Set by: +// - `Validator`: Sets the value to a given validator (which has to exist in +// the surrounding code. +//////////////////////////////////////////////////////////////////////////////// +// Field: enum_values +// Default value: {} (No enum associated with this option) +// Set by: +// - `OptionalEnumArg`: +// - `EnumArg`: Sets the argument type and assigns it a enum holding the valid +// values. The enum needs to be a variable in the including code. +// Marks the option as required (see option_has_arg). +// Example: def foo : Option<"foo", "f">, +// EnumArg<"SortOrder", +// "OptionEnumValues(g_sort_option_enumeration)">; +//////////////////////////////////////////////////////////////////////////////// +// Field: completion_type +// Default value: CommandCompletions::eNoCompletion (no tab completion) +// Set by: +// - `Completion`: Gives the option a single completion kind. +// Example: def foo : Option<"foo", "f">, +// Completion<"DiskFile">; +// Sets the completion to eDiskFileCompletion +// +// - `Completions`: Sets a given kinds of completions. +// Example: def foo : Option<"foo", "f">, +// Completions<["DiskFile", "DiskDirectory"]>; +// Sets the completion to +// `eDiskFileCompletion | eDiskDirectoryCompletion`. +//////////////////////////////////////////////////////////////////////////////// +// Field: argument_type +// Default value: eArgTypeNone +// Set by: +// - `OptionalArg`: Sets the argument type and marks it as optional. +// Example: def foo : Option<"foo", "f">, OptionalArg<"Pid">; +// Sets the argument type to eArgTypePid and marks option as +// optional (see option_has_arg). +// - `Arg`: Sets the argument type and marks it as required. +// Example: def foo : Option<"foo", "f">, Arg<"Pid">; +// Sets the argument type to eArgTypePid and marks option as +// required (see option_has_arg). +// - `OptionalEnumArg`: +// - `EnumArg`: Sets the argument type and assigns it a enum holding the valid +// values. The enum needs to be a variable in the including code. +// Marks the option as required (see option_has_arg). +// Example: def foo : Option<"foo", "f">, +// EnumArg<"SortOrder", +// "OptionEnumValues(g_sort_option_enumeration)">; +// Use `OptionalEnumArg` for having an option enum argument. +//////////////////////////////////////////////////////////////////////////////// +// Field: usage_text +// Default value: "" +// Set by: +// - `Desc`: Sets the description for the given option. +// Example: def foo : Option<"foo", "f">, Desc<"does nothing.">; +// Sets the description to "does nothing.". + +// Base class for all options. +class Option<string fullname, string shortname> { + string FullName = fullname; + string ShortName = shortname; + // The full associated command/subcommand such as "settings set". + string Command; +} + +// Moves the option into a list of option groups. +class Groups<list<int> groups> { + list<int> Groups = groups; +} + +// Moves the option in all option groups in a range. +// Start and end values are inclusive. +class GroupRange<int start, int end> { + int GroupStart = start; + int GroupEnd = end; +} +// Moves the option in a single option group. +class Group<int group> { + int GroupStart = group; + int GroupEnd = group; +} + +// Sets the description for the option that should be +// displayed to the user. +class Desc<string description> { + string Description = description; +} + +// Marks the option as required when calling the +// associated command. +class Required { + bit Required = 1; +} + +// Gives the option an optional argument. +class OptionalArg<string type> { + string ArgType = type; + bit OptionalArg = 1; +} + +// Gives the option an required argument. +class Arg<string type> { + string ArgType = type; +} + +// Gives the option an required argument. +class EnumArg<string type, string enum> { + string ArgType = type; + string ArgEnum = enum; +} + +// Gives the option an required argument. +class OptionalEnumArg<string type, string enum> { + string ArgType = type; + string ArgEnum = enum; + bit OptionalArg = 1; +} + +// Sets the available completions for the given option. +class Completions<list<string> completions> { + list<string> Completions = completions; +} +// Sets a single completion for the given option. +class Completion<string completion> { + list<string> Completions = [completion]; +} + +// Sets the validator for a given option. +class Validator<string validator> { + string Validator = validator; +} |
