| //===-- CommandObjectTarget.cpp -------------------------------------------===// | |
| // | |
| // 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/Address.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/IOHandler.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/ModuleSpec.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Core/Section.h" | |
| #include "lldb/DataFormatters/ValueObjectPrinter.h" | |
| #include "lldb/Host/OptionParser.h" | |
| #include "lldb/Interpreter/CommandInterpreter.h" | |
| #include "lldb/Interpreter/CommandOptionArgumentTable.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/OptionGroupPythonClassWithDict.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/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/ConstString.h" | |
| #include "lldb/Utility/FileSpec.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/ScriptedMetadata.h" | |
| #include "lldb/Utility/State.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include "lldb/Utility/StructuredData.h" | |
| #include "lldb/Utility/Timer.h" | |
| #include "lldb/ValueObject/ValueObjectVariable.h" | |
| #include "lldb/lldb-enumerations.h" | |
| #include "lldb/lldb-forward.h" | |
| #include "lldb/lldb-private-enumerations.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Support/FileSystem.h" | |
| #include "llvm/Support/FormatAdapters.h" | |
| 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>"); | |
| std::string formatted_label = ""; | |
| const std::string &label = target->GetLabel(); | |
| if (!label.empty()) { | |
| formatted_label = " (" + label + ")"; | |
| } | |
| strm.Printf("%starget #%u%s: %s", prefix_cstr ? prefix_cstr : "", target_idx, | |
| formatted_label.data(), exe_path); | |
| uint32_t properties = 0; | |
| if (target_arch.IsValid()) { | |
| strm.PutCString(" ( arch="); | |
| target_arch.DumpTriple(strm.AsRawOstream()); | |
| properties++; | |
| } | |
| PlatformSP platform_sp(target->GetPlatform()); | |
| if (platform_sp) | |
| strm.Format("{0}platform={1}", properties++ > 0 ? ", " : " ( ", | |
| platform_sp->GetName()); | |
| 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; | |
| } | |
| #define LLDB_OPTIONS_target_dependents | |
| #include "CommandOptions.inc" | |
| class OptionGroupDependents : public OptionGroup { | |
| public: | |
| OptionGroupDependents() = default; | |
| ~OptionGroupDependents() override = default; | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(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 = Status::FromErrorStringWithFormat( | |
| "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: | |
| OptionGroupDependents(const OptionGroupDependents &) = delete; | |
| const OptionGroupDependents & | |
| operator=(const OptionGroupDependents &) = delete; | |
| }; | |
| #pragma mark CommandObjectTargetCreate | |
| class CommandObjectTargetCreate : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetCreate(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target create", | |
| "Create a target using the argument as the main executable.", | |
| nullptr), | |
| m_platform_options(true), // Include the --platform option. | |
| m_core_file(LLDB_OPT_SET_1, false, "core", 'c', 0, eArgTypeFilename, | |
| "Fullpath to a core file to use for this target."), | |
| m_label(LLDB_OPT_SET_1, false, "label", 'l', 0, eArgTypeName, | |
| "Optional name for this target.", nullptr), | |
| 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.") { | |
| AddSimpleArgumentList(eArgTypeFilename); | |
| m_option_group.Append(&m_arch_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); | |
| m_option_group.Append(&m_platform_options, LLDB_OPT_SET_ALL, 1); | |
| m_option_group.Append(&m_core_file, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); | |
| m_option_group.Append(&m_label, 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; } | |
| protected: | |
| void 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) { | |
| auto file = FileSystem::Instance().Open( | |
| core_file, lldb_private::File::eOpenOptionReadOnly); | |
| if (!file) { | |
| result.AppendErrorWithFormatv("Cannot open '{0}': {1}.", | |
| core_file.GetPath(), | |
| llvm::toString(file.takeError())); | |
| return; | |
| } | |
| } | |
| if (argc == 1 || core_file || remote_file) { | |
| FileSpec symfile(m_symbol_file.GetOptionValue().GetCurrentValue()); | |
| if (symfile) { | |
| auto file = FileSystem::Instance().Open( | |
| symfile, lldb_private::File::eOpenOptionReadOnly); | |
| if (!file) { | |
| result.AppendErrorWithFormatv("Cannot open '{0}': {1}.", | |
| symfile.GetPath(), | |
| llvm::toString(file.takeError())); | |
| return; | |
| } | |
| } | |
| const char *file_path = command.GetArgumentAtIndex(0); | |
| LLDB_SCOPED_TIMERF("(lldb) target create '%s'", file_path); | |
| 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, &m_platform_options, | |
| target_sp)); | |
| if (!target_sp) { | |
| result.AppendError(error.AsCString()); | |
| return; | |
| } | |
| const llvm::StringRef label = | |
| m_label.GetOptionValue().GetCurrentValueAsRef(); | |
| if (!label.empty()) { | |
| if (auto E = target_sp->SetLabel(label)) | |
| result.SetError(std::move(E)); | |
| else | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| return; | |
| } | |
| llvm::scope_exit on_error( | |
| [&target_list = debugger.GetTargetList(), &target_sp]() { | |
| target_list.DeleteTarget(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(); | |
| FileSpec file_spec; | |
| if (file_path) { | |
| file_spec.SetFile(file_path, FileSpec::Style::native); | |
| FileSystem::Instance().Resolve(file_spec); | |
| // Try to resolve the exe based on PATH and/or platform-specific | |
| // suffixes, but only if using the host platform. | |
| if (platform_sp && platform_sp->IsHost() && | |
| !FileSystem::Instance().Exists(file_spec)) | |
| FileSystem::Instance().ResolveExecutableLocation(file_spec); | |
| } | |
| 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()); | |
| return; | |
| } | |
| } | |
| } 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 (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()); | |
| return; | |
| } | |
| } else { | |
| // If the remote file exists, we can debug reading that out of | |
| // memory. If the platform is already connected to an lldb-server | |
| // then we can at least check the file exists remotely. Otherwise | |
| // we'll just have to trust that it will be there when we do | |
| // process connect. | |
| // I don't do this for the host platform because it seems odd to | |
| // support supplying a remote file but no local file for a local | |
| // debug session. | |
| if (platform_sp->IsHost()) { | |
| result.AppendError("Supply a local file, not a remote file, " | |
| "when debugging on the host."); | |
| return; | |
| } | |
| if (platform_sp->IsConnected() && !platform_sp->GetFileExists(remote_file)) { | |
| result.AppendError("remote --> local transfer without local " | |
| "path is not implemented yet"); | |
| return; | |
| } | |
| // Since there's only a remote file, we need to set the executable | |
| // file spec to the remote one. | |
| ProcessLaunchInfo launch_info = target_sp->GetProcessLaunchInfo(); | |
| launch_info.SetExecutableFile(FileSpec(remote_file), true); | |
| target_sp->SetProcessLaunchInfo(launch_info); | |
| } | |
| } | |
| } else { | |
| result.AppendError("no platform found for target"); | |
| return; | |
| } | |
| } | |
| 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); | |
| } | |
| } | |
| } | |
| 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) { | |
| FileSpec core_file_dir; | |
| core_file_dir.SetDirectory(core_file.GetDirectory()); | |
| target_sp->AppendExecutableSearchPaths(core_file_dir); | |
| ProcessSP process_sp(target_sp->CreateProcess( | |
| GetDebugger().GetListener(), llvm::StringRef(), &core_file, false)); | |
| if (process_sp) { | |
| // Seems weird that we Launch a core file, but that is what we | |
| // do! | |
| { | |
| ElapsedTime load_core_time( | |
| target_sp->GetStatistics().GetLoadCoreTime()); | |
| error = process_sp->LoadCore(); | |
| } | |
| if (error.Fail()) { | |
| result.AppendError(error.AsCString("unknown core file format")); | |
| return; | |
| } else { | |
| result.AppendMessageWithFormatv( | |
| "Core file '{0}' ({1}) was loaded.\n", core_file.GetPath(), | |
| target_sp->GetArchitecture().GetArchitectureName()); | |
| if (auto core_args = process_sp->GetCoreFileArgs()) | |
| core_args->Format(result.GetOutputStream()); | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| on_error.release(); | |
| } | |
| } else { | |
| result.AppendErrorWithFormatv("Unknown core file format '{0}'\n", | |
| core_file.GetPath()); | |
| } | |
| } else { | |
| result.AppendMessageWithFormatv( | |
| "Current executable set to '{0}' ({1}).", | |
| file_spec.GetPath().c_str(), | |
| target_sp->GetArchitecture().GetArchitectureName()); | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| on_error.release(); | |
| } | |
| } else { | |
| result.AppendErrorWithFormat("'%s' takes exactly one executable path " | |
| "argument, or use the --core option", | |
| m_cmd_name.c_str()); | |
| } | |
| } | |
| private: | |
| OptionGroupOptions m_option_group; | |
| OptionGroupArchitecture m_arch_option; | |
| OptionGroupPlatform m_platform_options; | |
| OptionGroupFile m_core_file; | |
| OptionGroupString m_label; | |
| OptionGroupFile m_symbol_file; | |
| OptionGroupFile m_remote_file; | |
| OptionGroupDependents m_add_dependents; | |
| }; | |
| #pragma mark CommandObjectTargetList | |
| 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: | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| 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); | |
| } | |
| }; | |
| #pragma mark CommandObjectTargetSelect | |
| class CommandObjectTargetSelect : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetSelect(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target select", | |
| "Select a target as the current target by target index.", nullptr) { | |
| AddSimpleArgumentList(eArgTypeTargetID); | |
| } | |
| ~CommandObjectTargetSelect() override = default; | |
| protected: | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| if (args.GetArgumentCount() == 1) { | |
| const char *target_identifier = args.GetArgumentAtIndex(0); | |
| uint32_t target_idx = LLDB_INVALID_INDEX32; | |
| TargetList &target_list = GetDebugger().GetTargetList(); | |
| const uint32_t num_targets = target_list.GetNumTargets(); | |
| if (llvm::to_integer(target_identifier, target_idx)) { | |
| if (target_idx < num_targets) { | |
| target_list.SetSelectedTarget(target_idx); | |
| Stream &strm = result.GetOutputStream(); | |
| bool show_stopped_process_status = false; | |
| DumpTargetList(target_list, show_stopped_process_status, strm); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } else { | |
| if (num_targets > 0) { | |
| result.AppendErrorWithFormat( | |
| "index %u is out of range, valid target indexes are 0 - %u", | |
| target_idx, num_targets - 1); | |
| } else { | |
| result.AppendErrorWithFormat( | |
| "index %u is out of range since there are no active targets", | |
| target_idx); | |
| } | |
| } | |
| } else { | |
| for (size_t i = 0; i < num_targets; i++) { | |
| if (TargetSP target_sp = target_list.GetTargetAtIndex(i)) { | |
| const std::string &label = target_sp->GetLabel(); | |
| if (!label.empty() && label == target_identifier) { | |
| target_idx = i; | |
| break; | |
| } | |
| } | |
| } | |
| if (target_idx != LLDB_INVALID_INDEX32) { | |
| target_list.SetSelectedTarget(target_idx); | |
| Stream &strm = result.GetOutputStream(); | |
| bool show_stopped_process_status = false; | |
| DumpTargetList(target_list, show_stopped_process_status, strm); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } else { | |
| result.AppendErrorWithFormat("invalid index string value '%s'", | |
| target_identifier); | |
| } | |
| } | |
| } else { | |
| result.AppendError( | |
| "'target select' takes a single argument: a target index\n"); | |
| } | |
| } | |
| }; | |
| #pragma mark CommandObjectTargetDelete | |
| class CommandObjectTargetDelete : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetDelete(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "target delete", | |
| "Delete one or more targets by target index.", | |
| nullptr), | |
| 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(); | |
| AddSimpleArgumentList(eArgTypeTargetID, eArgRepeatStar); | |
| } | |
| ~CommandObjectTargetDelete() override = default; | |
| Options *GetOptions() override { return &m_option_group; } | |
| protected: | |
| void 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 (size_t 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"); | |
| return; | |
| } | |
| for (auto &entry : args.entries()) { | |
| uint32_t target_idx; | |
| if (entry.ref().getAsInteger(0, target_idx)) { | |
| result.AppendErrorWithFormat("invalid target index '%s'", | |
| entry.c_str()); | |
| return; | |
| } | |
| 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", | |
| target_idx, num_targets - 1); | |
| else | |
| result.AppendErrorWithFormat( | |
| "target index %u is out of range, the only valid index is 0", | |
| target_idx); | |
| return; | |
| } | |
| } else { | |
| target_sp = target_list.GetSelectedTarget(); | |
| if (!target_sp) { | |
| result.AppendErrorWithFormat("no target is currently selected"); | |
| return; | |
| } | |
| 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); | |
| } | |
| OptionGroupOptions m_option_group; | |
| OptionGroupBoolean m_all_option; | |
| OptionGroupBoolean m_cleanup_option; | |
| }; | |
| class CommandObjectTargetShowLaunchEnvironment : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetShowLaunchEnvironment(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target show-launch-environment", | |
| "Shows the environment being passed to the process when launched, " | |
| "taking info account 3 settings: target.env-vars, " | |
| "target.inherit-env and target.unset-env-vars.", | |
| nullptr, eCommandRequiresTarget) {} | |
| ~CommandObjectTargetShowLaunchEnvironment() override = default; | |
| protected: | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| Environment env = target->GetEnvironment(); | |
| std::vector<Environment::value_type *> env_vector; | |
| env_vector.reserve(env.size()); | |
| for (auto &KV : env) | |
| env_vector.push_back(&KV); | |
| std::sort(env_vector.begin(), env_vector.end(), | |
| [](Environment::value_type *a, Environment::value_type *b) { | |
| return a->first() < b->first(); | |
| }); | |
| auto &strm = result.GetOutputStream(); | |
| for (auto &KV : env_vector) | |
| strm.Format("{0}={1}\n", KV->first(), KV->second); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| #pragma mark CommandObjectTargetVariable | |
| 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_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.") { | |
| AddSimpleArgumentList(eArgTypeVarName, eArgRepeatPlus); | |
| 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); | |
| if (llvm::Error error = valobj_sp->Dump(s, options)) | |
| s << "error: " << toString(std::move(error)); | |
| } | |
| 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, | |
| CommandReturnObject &result) { | |
| Stream &s = result.GetOutputStream(); | |
| if (variable_list.Empty()) | |
| return; | |
| if (sc.module_sp) { | |
| if (sc.comp_unit) { | |
| s.Format("Global variables for {0} in {1}:\n", | |
| sc.comp_unit->GetPrimaryFile(), sc.module_sp->GetFileSpec()); | |
| } else { | |
| s.Printf("Global variables for %s\n", | |
| sc.module_sp->GetFileSpec().GetPath().c_str()); | |
| } | |
| } else if (sc.comp_unit) { | |
| s.Format("Global variables for {0}\n", sc.comp_unit->GetPrimaryFile()); | |
| } | |
| for (VariableSP var_sp : variable_list) { | |
| if (!var_sp) | |
| continue; | |
| ValueObjectSP valobj_sp(ValueObjectVariable::Create( | |
| exe_ctx.GetBestExecutionContextScope(), var_sp)); | |
| if (valobj_sp) { | |
| result.GetValueObjectList().Append(valobj_sp); | |
| DumpValueObject(s, var_sp, valobj_sp, var_sp->GetName().GetCString()); | |
| } | |
| } | |
| } | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| const size_t argc = args.GetArgumentCount(); | |
| if (argc > 0) { | |
| for (const Args::ArgEntry &arg : args) { | |
| VariableList variable_list; | |
| ValueObjectList valobj_list; | |
| size_t matches = 0; | |
| bool use_var_name = false; | |
| if (m_option_variable.use_regex) { | |
| RegularExpression regex(arg.ref()); | |
| if (!regex.IsValid()) { | |
| result.GetErrorStream().Printf( | |
| "error: invalid regular expression: '%s'\n", arg.c_str()); | |
| return; | |
| } | |
| use_var_name = true; | |
| target->GetImages().FindGlobalVariables(regex, UINT32_MAX, | |
| variable_list); | |
| matches = variable_list.GetSize(); | |
| } else { | |
| Status error(Variable::GetValuesForVariableExpressionPath( | |
| arg.c_str(), m_exe_ctx.GetBestExecutionContextScope(), | |
| GetVariableCallback, target, variable_list, valobj_list)); | |
| matches = variable_list.GetSize(); | |
| } | |
| if (matches == 0) { | |
| result.AppendErrorWithFormat("can't find global variable '%s'", | |
| arg.c_str()); | |
| return; | |
| } 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(result.GetOutputStream(), var_sp, valobj_sp, | |
| use_var_name ? var_sp->GetName().GetCString() | |
| : arg.c_str()); | |
| } | |
| } | |
| } | |
| } | |
| } 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); | |
| comp_unit = sc.comp_unit; | |
| 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, | |
| result); | |
| success = true; | |
| } | |
| } | |
| } | |
| } | |
| if (!success) { | |
| if (frame) { | |
| if (comp_unit) | |
| result.AppendErrorWithFormatv( | |
| "no global variables in current compile unit: {0}\n", | |
| comp_unit->GetPrimaryFile()); | |
| else | |
| result.AppendErrorWithFormat("no debug information for frame %u", | |
| frame->GetFrameIndex()); | |
| } else | |
| result.AppendError("'target variable' takes one or more global " | |
| "variable names as arguments\n"); | |
| } | |
| } 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", | |
| 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); | |
| } | |
| for (const SymbolContext &sc : sc_list) { | |
| 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, | |
| result); | |
| } 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, result); | |
| } | |
| } | |
| } | |
| } | |
| m_interpreter.PrintWarningsIfNecessary(result.GetOutputStream(), | |
| m_cmd_name); | |
| if (result.GetStatus() != eReturnStatusFailed) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| 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: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| const size_t argc = command.GetArgumentCount(); | |
| if (argc & 1) { | |
| result.AppendError("add requires an even number of arguments\n"); | |
| } 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]) { | |
| LLDB_LOGF(GetLog(LLDBLog::Host), | |
| "target modules search path adding ImageSearchPath " | |
| "pair: '%s' -> '%s'", | |
| from, to); | |
| bool last_pair = ((argc - i) == 2); | |
| target->GetImageSearchPathList().Append( | |
| from, 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"); | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| #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: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| bool notify = true; | |
| target->GetImageSearchPathList().Clear(notify); | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| }; | |
| #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; | |
| void | |
| HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &opt_element_vector) override { | |
| if (!m_exe_ctx.HasTargetScope() || request.GetCursorIndex() != 0) | |
| return; | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| const PathMappingList &list = target->GetImageSearchPathList(); | |
| const size_t num = list.GetSize(); | |
| std::string old_path, new_path; | |
| for (size_t i = 0; i < num; ++i) { | |
| if (!list.GetPathsAtIndex(i, old_path, new_path)) | |
| break; | |
| StreamString strm; | |
| strm << old_path << " -> " << new_path; | |
| request.TryCompleteCurrentArg(std::to_string(i), strm.GetString()); | |
| } | |
| } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| size_t argc = command.GetArgumentCount(); | |
| // check for at least 3 arguments and an odd number of parameters | |
| if (argc >= 3 && argc & 1) { | |
| uint32_t insert_idx; | |
| if (!llvm::to_integer(command.GetArgumentAtIndex(0), insert_idx)) { | |
| result.AppendErrorWithFormat( | |
| "<index> parameter is not an integer: '%s'", | |
| command.GetArgumentAtIndex(0)); | |
| return; | |
| } | |
| // 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(from, 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"); | |
| return; | |
| } | |
| } | |
| } else { | |
| result.AppendError("insert requires at least three arguments\n"); | |
| } | |
| } | |
| }; | |
| #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: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| target->GetImageSearchPathList().Dump(&result.GetOutputStream()); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| #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) { | |
| AddSimpleArgumentList(eArgTypeDirectoryName); | |
| } | |
| ~CommandObjectTargetModulesSearchPathsQuery() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| if (command.GetArgumentCount() != 1) { | |
| result.AppendError("query requires one argument\n"); | |
| return; | |
| } | |
| llvm::StringRef orig(command.GetArgumentAtIndex(0)); | |
| std::string transformed; | |
| if (target->GetImageSearchPathList().RemapPath(orig, transformed)) | |
| result.GetOutputStream().Format("{0}\n", transformed); | |
| else | |
| result.GetOutputStream().Format("{0}\n", orig); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| // 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.AsRawOstream()); | |
| else | |
| arch_strm.PutCString(module->GetArchitecture().GetArchitectureName()); | |
| std::string arch_str = std::string(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); | |
| bool first_module = true; | |
| for (const SymbolContext &sc : sc_list) { | |
| if (!first_module) | |
| strm << "\n\n"; | |
| strm << "Line table for " << sc.comp_unit->GetPrimaryFile() << " 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"; | |
| first_module = false; | |
| } | |
| } | |
| 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.AsRawOstream()); | |
| 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.Format("{0}", fmt_align(file_spec_ptr->GetDirectory(), | |
| llvm::AlignStyle::Left, width)); | |
| else | |
| strm.PutCString(file_spec_ptr->GetDirectory()); | |
| 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.Format("{0}", fmt_align(file_spec_ptr->GetFilename(), | |
| llvm::AlignStyle::Left, width)); | |
| else | |
| strm.PutCString(file_spec_ptr->GetFilename()); | |
| 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) { | |
| std::lock_guard<std::recursive_mutex> guard(module_list.GetMutex()); | |
| const size_t num_modules = module_list.GetSize(); | |
| if (num_modules == 0) | |
| return 0; | |
| size_t num_dumped = 0; | |
| strm.Format("Dumping headers for {0} module(s).\n", num_modules); | |
| strm.IndentMore(); | |
| for (ModuleSP module_sp : module_list.ModulesNoLocking()) { | |
| if (module_sp) { | |
| if (num_dumped++ > 0) { | |
| strm.EOL(); | |
| strm.EOL(); | |
| } | |
| ObjectFile *objfile = module_sp->GetObjectFile(); | |
| if (objfile) | |
| objfile->Dump(&strm); | |
| else { | |
| strm.Format("No object file for module: {0:F}\n", | |
| module_sp->GetFileSpec()); | |
| } | |
| } | |
| } | |
| strm.IndentLess(); | |
| return num_dumped; | |
| } | |
| static void DumpModuleSymtab(CommandInterpreter &interpreter, Stream &strm, | |
| Module *module, SortOrder sort_order, | |
| Mangled::NamePreference name_preference) { | |
| if (!module) | |
| return; | |
| if (Symtab *symtab = module->GetSymtab()) | |
| symtab->Dump(&strm, interpreter.GetExecutionContext().GetTargetPtr(), | |
| sort_order, name_preference); | |
| } | |
| 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()); | |
| section_list->Dump(strm.AsRawOstream(), strm.GetIndentLevel() + 2, | |
| interpreter.GetExecutionContext().GetTargetPtr(), true, | |
| UINT32_MAX); | |
| } | |
| } | |
| } | |
| 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 bool GetSeparateDebugInfoList(StructuredData::Array &list, | |
| Module *module, bool errors_only, | |
| bool load_all_debug_info) { | |
| if (module) { | |
| if (SymbolFile *symbol_file = module->GetSymbolFile(/*can_create=*/true)) { | |
| StructuredData::Dictionary d; | |
| if (symbol_file->GetSeparateDebugInfo(d, errors_only, | |
| load_all_debug_info)) { | |
| list.AddItem( | |
| std::make_shared<StructuredData::Dictionary>(std::move(d))); | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| static void DumpDwoFilesTable(Stream &strm, | |
| StructuredData::Array &dwo_listings) { | |
| strm.PutCString("Dwo ID Err Dwo Path"); | |
| strm.EOL(); | |
| strm.PutCString( | |
| "------------------ --- -----------------------------------------"); | |
| strm.EOL(); | |
| dwo_listings.ForEach([&strm](StructuredData::Object *dwo) { | |
| StructuredData::Dictionary *dict = dwo->GetAsDictionary(); | |
| if (!dict) | |
| return false; | |
| uint64_t dwo_id; | |
| if (dict->GetValueForKeyAsInteger("dwo_id", dwo_id)) | |
| strm.Printf("0x%16.16" PRIx64 " ", dwo_id); | |
| else | |
| strm.PutCString("0x???????????????? "); | |
| llvm::StringRef error; | |
| if (dict->GetValueForKeyAsString("error", error)) | |
| strm << "E " << error; | |
| else { | |
| llvm::StringRef resolved_dwo_path; | |
| if (dict->GetValueForKeyAsString("resolved_dwo_path", | |
| resolved_dwo_path)) { | |
| strm << " " << resolved_dwo_path; | |
| if (resolved_dwo_path.ends_with(".dwp")) { | |
| llvm::StringRef dwo_name; | |
| if (dict->GetValueForKeyAsString("dwo_name", dwo_name)) | |
| strm << "(" << dwo_name << ")"; | |
| } | |
| } | |
| } | |
| strm.EOL(); | |
| return true; | |
| }); | |
| } | |
| static void DumpOsoFilesTable(Stream &strm, | |
| StructuredData::Array &oso_listings) { | |
| strm.PutCString("Mod Time Err Oso Path"); | |
| strm.EOL(); | |
| strm.PutCString("------------------ --- ---------------------"); | |
| strm.EOL(); | |
| oso_listings.ForEach([&strm](StructuredData::Object *oso) { | |
| StructuredData::Dictionary *dict = oso->GetAsDictionary(); | |
| if (!dict) | |
| return false; | |
| uint32_t oso_mod_time; | |
| if (dict->GetValueForKeyAsInteger("oso_mod_time", oso_mod_time)) | |
| strm.Printf("0x%16.16" PRIx32 " ", oso_mod_time); | |
| llvm::StringRef error; | |
| if (dict->GetValueForKeyAsString("error", error)) | |
| strm << "E " << error; | |
| else { | |
| llvm::StringRef oso_path; | |
| if (dict->GetValueForKeyAsString("oso_path", oso_path)) | |
| strm << " " << oso_path; | |
| } | |
| strm.EOL(); | |
| return true; | |
| }); | |
| } | |
| static void | |
| DumpAddress(ExecutionContextScope *exe_scope, const Address &so_addr, | |
| bool verbose, bool all_ranges, Stream &strm, | |
| std::optional<Stream::HighlightSettings> settings = std::nullopt) { | |
| 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, | |
| Address::DumpStyleInvalid, UINT32_MAX, false, settings); | |
| 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, | |
| Address::DumpStyleInvalid, UINT32_MAX, all_ranges, settings); | |
| } | |
| 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, bool all_ranges) { | |
| if (module) { | |
| lldb::addr_t addr = raw_addr - offset; | |
| Address so_addr; | |
| SymbolContext sc; | |
| Target *target = interpreter.GetExecutionContext().GetTargetPtr(); | |
| if (target && target->HasLoadedSections()) { | |
| if (!target->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, all_ranges, strm); | |
| return true; | |
| } | |
| return false; | |
| } | |
| static uint32_t LookupSymbolInModule(CommandInterpreter &interpreter, | |
| Stream &strm, Module *module, | |
| const char *name, bool name_is_regex, | |
| bool verbose, bool all_ranges) { | |
| if (!module) | |
| return 0; | |
| Symtab *symtab = module->GetSymtab(); | |
| if (!symtab) | |
| return 0; | |
| SymbolContext sc; | |
| const bool use_color = interpreter.GetDebugger().GetUseColor(); | |
| 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(); | |
| Stream::HighlightSettings settings( | |
| name, interpreter.GetDebugger().GetRegexMatchAnsiPrefix(), | |
| interpreter.GetDebugger().GetRegexMatchAnsiSuffix()); | |
| for (uint32_t i = 0; i < num_matches; ++i) { | |
| const Symbol *symbol = symtab->SymbolAtIndex(match_indexes[i]); | |
| if (symbol) { | |
| if (symbol->ValueIsAddress()) { | |
| DumpAddress( | |
| interpreter.GetExecutionContext().GetBestExecutionContextScope(), | |
| symbol->GetAddressRef(), verbose, all_ranges, strm, | |
| use_color && name_is_regex | |
| ? std::optional<Stream::HighlightSettings>{settings} | |
| : std::nullopt); | |
| strm.EOL(); | |
| } else { | |
| strm.IndentMore(); | |
| strm.Indent(" Name: "); | |
| strm.PutCStringColorHighlighted( | |
| symbol->GetDisplayName().GetStringRef(), | |
| use_color && name_is_regex | |
| ? std::optional<Stream::HighlightSettings>{settings} | |
| : std::nullopt); | |
| strm.EOL(); | |
| strm.Indent(" Value: "); | |
| strm.Printf("0x%16.16" PRIx64 "\n", symbol->GetRawValue()); | |
| if (symbol->GetByteSizeIsValid()) { | |
| strm.Indent(" Size: "); | |
| strm.Printf("0x%16.16" PRIx64 "\n", symbol->GetByteSize()); | |
| } | |
| strm.IndentLess(); | |
| } | |
| } | |
| } | |
| strm.IndentLess(); | |
| } | |
| return num_matches; | |
| } | |
| static void DumpSymbolContextList( | |
| ExecutionContextScope *exe_scope, Stream &strm, | |
| const SymbolContextList &sc_list, bool verbose, bool all_ranges, | |
| std::optional<Stream::HighlightSettings> settings = std::nullopt) { | |
| strm.IndentMore(); | |
| bool first_module = true; | |
| for (const SymbolContext &sc : sc_list) { | |
| if (!first_module) | |
| strm.EOL(); | |
| Address addr; | |
| if (sc.line_entry.IsValid()) | |
| addr = sc.line_entry.range.GetBaseAddress(); | |
| else if (sc.block && sc.block->GetContainingInlinedBlock()) | |
| sc.block->GetContainingInlinedBlock()->GetStartAddress(addr); | |
| else | |
| addr = sc.GetFunctionOrSymbolAddress(); | |
| DumpAddress(exe_scope, addr, verbose, all_ranges, strm, settings); | |
| first_module = false; | |
| } | |
| strm.IndentLess(); | |
| } | |
| static size_t LookupFunctionInModule(CommandInterpreter &interpreter, | |
| Stream &strm, Module *module, | |
| const char *name, bool name_is_regex, | |
| const ModuleFunctionSearchOptions &options, | |
| bool verbose, bool all_ranges) { | |
| 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, options, sc_list); | |
| } else { | |
| ConstString function_name(name); | |
| module->FindFunctions(function_name, CompilerDeclContext(), | |
| eFunctionNameTypeAuto, options, 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, all_ranges); | |
| } | |
| return num_matches; | |
| } | |
| return 0; | |
| } | |
| static size_t LookupTypeInModule(Target *target, | |
| CommandInterpreter &interpreter, Stream &strm, | |
| Module *module, const char *name_cstr, | |
| bool name_is_regex) { | |
| if (module && name_cstr && name_cstr[0]) { | |
| TypeQuery query(name_cstr); | |
| TypeResults results; | |
| module->FindTypes(query, results); | |
| TypeList type_list; | |
| SymbolContext sc; | |
| if (module) | |
| sc.module_sp = module->shared_from_this(); | |
| // Sort the type results and put the results that matched in \a module | |
| // first if \a module was specified. | |
| sc.SortTypeList(results.GetTypeMap(), type_list); | |
| if (type_list.Empty()) | |
| return 0; | |
| const uint64_t num_matches = type_list.GetSize(); | |
| strm.Indent(); | |
| strm.Printf("%" PRIu64 " match%s found in ", 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, target); | |
| // 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, | |
| target); | |
| typedef_type_sp = typedefed_type_sp; | |
| typedefed_type_sp = typedef_type_sp->GetTypedefType(); | |
| } | |
| strm.EOL(); | |
| } | |
| return type_list.GetSize(); | |
| } | |
| return 0; | |
| } | |
| static size_t LookupTypeHere(Target *target, CommandInterpreter &interpreter, | |
| Stream &strm, Module &module, | |
| const char *name_cstr, bool name_is_regex) { | |
| TypeQuery query(name_cstr); | |
| TypeResults results; | |
| module.FindTypes(query, results); | |
| TypeList type_list; | |
| SymbolContext sc; | |
| sc.module_sp = module.shared_from_this(); | |
| sc.SortTypeList(results.GetTypeMap(), 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, target); | |
| // 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, | |
| target); | |
| 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, bool all_ranges) { | |
| 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, all_ranges); | |
| 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) { | |
| AddSimpleArgumentList(eArgTypeFilename, eArgRepeatStar); | |
| } | |
| ~CommandObjectTargetModulesModuleAutoComplete() override = default; | |
| void | |
| HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &opt_element_vector) override { | |
| lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( | |
| GetCommandInterpreter(), lldb::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) { | |
| AddSimpleArgumentList(eArgTypeSourceFile, eArgRepeatPlus); | |
| } | |
| ~CommandObjectTargetModulesSourceFileAutoComplete() override = default; | |
| void | |
| HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &opt_element_vector) override { | |
| lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( | |
| GetCommandInterpreter(), lldb::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: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| 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"); | |
| } | |
| } 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.AppendWarningWithFormatv( | |
| "unable to find an image that matches '{0}'", 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"); | |
| } | |
| } | |
| }; | |
| #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) {} | |
| ~CommandObjectTargetModulesDumpSymtab() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() = default; | |
| ~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 'm': | |
| m_prefer_mangled.SetCurrentValue(true); | |
| m_prefer_mangled.SetOptionWasSet(); | |
| break; | |
| 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; | |
| m_prefer_mangled.Clear(); | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_target_modules_dump_symtab_options); | |
| } | |
| SortOrder m_sort_order = eSortOrderNone; | |
| OptionValueBoolean m_prefer_mangled = {false, false}; | |
| }; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| uint32_t num_dumped = 0; | |
| Mangled::NamePreference name_preference = | |
| (m_options.m_prefer_mangled ? Mangled::ePreferMangled | |
| : Mangled::ePreferDemangled); | |
| if (command.GetArgumentCount() == 0) { | |
| // Dump all sections for all modules images | |
| const ModuleList &module_list = target->GetImages(); | |
| std::lock_guard<std::recursive_mutex> guard(module_list.GetMutex()); | |
| const size_t num_modules = module_list.GetSize(); | |
| if (num_modules > 0) { | |
| result.GetOutputStream().Format( | |
| "Dumping symbol table for {0} modules.\n", num_modules); | |
| for (ModuleSP module_sp : module_list.ModulesNoLocking()) { | |
| if (num_dumped > 0) { | |
| result.GetOutputStream().EOL(); | |
| result.GetOutputStream().EOL(); | |
| } | |
| if (INTERRUPT_REQUESTED(GetDebugger(), | |
| "Interrupted in dump all symtabs with {0} " | |
| "of {1} dumped.", num_dumped, num_modules)) | |
| break; | |
| num_dumped++; | |
| DumpModuleSymtab(m_interpreter, result.GetOutputStream(), | |
| module_sp.get(), m_options.m_sort_order, | |
| name_preference); | |
| } | |
| } else { | |
| result.AppendError("the target has no associated executable images"); | |
| return; | |
| } | |
| } 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 (ModuleSP module_sp : module_list.Modules()) { | |
| if (module_sp) { | |
| if (num_dumped > 0) { | |
| result.GetOutputStream().EOL(); | |
| result.GetOutputStream().EOL(); | |
| } | |
| if (INTERRUPT_REQUESTED(GetDebugger(), | |
| "Interrupted in dump symtab list with {0} of {1} dumped.", | |
| num_dumped, num_matches)) | |
| break; | |
| num_dumped++; | |
| DumpModuleSymtab(m_interpreter, result.GetOutputStream(), | |
| module_sp.get(), m_options.m_sort_order, | |
| name_preference); | |
| } | |
| } | |
| } else | |
| result.AppendWarningWithFormatv( | |
| "unable to find an image that matches '{0}'", arg_cstr); | |
| } | |
| } | |
| if (num_dumped > 0) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else { | |
| result.AppendError("no matching executable images found"); | |
| } | |
| } | |
| 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: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| uint32_t num_dumped = 0; | |
| if (command.GetArgumentCount() == 0) { | |
| // Dump all sections for all modules images | |
| const size_t num_modules = target->GetImages().GetSize(); | |
| if (num_modules == 0) { | |
| result.AppendError("the target has no associated executable images"); | |
| return; | |
| } | |
| result.GetOutputStream().Format("Dumping sections for {0} modules.\n", | |
| num_modules); | |
| for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { | |
| if (INTERRUPT_REQUESTED(GetDebugger(), | |
| "Interrupted in dump all sections with {0} of {1} dumped", | |
| image_idx, num_modules)) | |
| break; | |
| num_dumped++; | |
| DumpModuleSections( | |
| m_interpreter, result.GetOutputStream(), | |
| target->GetImages().GetModulePointerAtIndex(image_idx)); | |
| } | |
| } 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 (INTERRUPT_REQUESTED(GetDebugger(), | |
| "Interrupted in dump section list with {0} of {1} dumped.", | |
| i, num_matches)) | |
| 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.AppendWarningWithFormatv( | |
| "unable to find an image that matches '{0}'", arg_cstr); | |
| } | |
| } | |
| } | |
| if (num_dumped > 0) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else { | |
| result.AppendError("no matching executable images found"); | |
| } | |
| } | |
| }; | |
| #pragma mark CommandObjectTargetModulesDumpClangAST | |
| // 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 [--filter <name>] [<file1> ...]", | |
| eCommandRequiresTarget), | |
| m_filter(LLDB_OPT_SET_1, false, "filter", 'f', 0, eArgTypeName, | |
| "Dump only the decls whose names contain the specified filter " | |
| "string.", | |
| /*default_value=*/"") { | |
| m_option_group.Append(&m_filter, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); | |
| m_option_group.Finalize(); | |
| } | |
| Options *GetOptions() override { return &m_option_group; } | |
| ~CommandObjectTargetModulesDumpClangAST() override = default; | |
| OptionGroupOptions m_option_group; | |
| OptionGroupString m_filter; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| const ModuleList &module_list = target->GetImages(); | |
| const size_t num_modules = module_list.GetSize(); | |
| if (num_modules == 0) { | |
| result.AppendError("the target has no associated executable images"); | |
| return; | |
| } | |
| llvm::StringRef filter = m_filter.GetOptionValue().GetCurrentValueAsRef(); | |
| if (command.GetArgumentCount() == 0) { | |
| // Dump all ASTs for all modules images | |
| result.GetOutputStream().Format("Dumping clang ast for {0} modules.\n", | |
| num_modules); | |
| for (ModuleSP module_sp : module_list.ModulesNoLocking()) { | |
| if (INTERRUPT_REQUESTED(GetDebugger(), "Interrupted dumping clang ast")) | |
| break; | |
| if (SymbolFile *sf = module_sp->GetSymbolFile()) | |
| sf->DumpClangAST(result.GetOutputStream(), filter, | |
| GetCommandInterpreter().GetDebugger().GetUseColor()); | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| return; | |
| } | |
| // 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.AppendWarningWithFormatv( | |
| "unable to find an image that matches '{0}'", arg.c_str()); | |
| continue; | |
| } | |
| for (size_t i = 0; i < num_matches; ++i) { | |
| if (INTERRUPT_REQUESTED(GetDebugger(), | |
| "Interrupted in dump clang ast list with {0} of {1} dumped.", | |
| i, num_matches)) | |
| break; | |
| Module *m = module_list.GetModulePointerAtIndex(i); | |
| if (SymbolFile *sf = m->GetSymbolFile()) | |
| sf->DumpClangAST(result.GetOutputStream(), filter, | |
| GetCommandInterpreter().GetDebugger().GetUseColor()); | |
| } | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| #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: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| uint32_t num_dumped = 0; | |
| 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.AppendError("the target has no associated executable images"); | |
| return; | |
| } | |
| result.GetOutputStream().Format( | |
| "Dumping debug symbols for {0} modules.\n", num_modules); | |
| for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { | |
| if (INTERRUPT_REQUESTED(GetDebugger(), "Interrupted in dumping all " | |
| "debug symbols with {0} of {1} modules dumped", | |
| num_dumped, num_modules)) | |
| break; | |
| if (DumpModuleSymbolFile(result.GetOutputStream(), module_sp.get())) | |
| num_dumped++; | |
| } | |
| } 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 (INTERRUPT_REQUESTED(GetDebugger(), "Interrupted dumping {0} " | |
| "of {1} requested modules", | |
| i, num_matches)) | |
| break; | |
| Module *module = module_list.GetModulePointerAtIndex(i); | |
| if (module) { | |
| if (DumpModuleSymbolFile(result.GetOutputStream(), module)) | |
| num_dumped++; | |
| } | |
| } | |
| } else | |
| result.AppendWarningWithFormatv( | |
| "unable to find an image that matches '{0}'", arg_cstr); | |
| } | |
| } | |
| if (num_dumped > 0) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else { | |
| result.AppendError("no matching executable images found"); | |
| } | |
| } | |
| }; | |
| #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: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| uint32_t total_num_dumped = 0; | |
| if (command.GetArgumentCount() == 0) { | |
| result.AppendError("file option must be specified"); | |
| return; | |
| } 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()); | |
| size_t num_modules = target_modules.GetSize(); | |
| if (num_modules > 0) { | |
| uint32_t num_dumped = 0; | |
| for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { | |
| if (INTERRUPT_REQUESTED(GetDebugger(), | |
| "Interrupted in dump all line tables with " | |
| "{0} of {1} dumped", num_dumped, | |
| num_modules)) | |
| break; | |
| if (DumpCompileUnitLineTable( | |
| m_interpreter, result.GetOutputStream(), module_sp.get(), | |
| file_spec, | |
| m_options.m_verbose ? eDescriptionLevelFull | |
| : eDescriptionLevelBrief)) | |
| num_dumped++; | |
| } | |
| if (num_dumped == 0) | |
| result.AppendWarningWithFormatv("no source filenames matched '{0}'", | |
| 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"); | |
| } | |
| } | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() { 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::ArrayRef(g_target_modules_dump_options); | |
| } | |
| bool m_verbose; | |
| }; | |
| CommandOptions m_options; | |
| }; | |
| #pragma mark CommandObjectTargetModulesDumpSeparateDebugInfoFiles | |
| #define LLDB_OPTIONS_target_modules_dump_separate_debug_info | |
| #include "CommandOptions.inc" | |
| // Image debug separate debug info dumping command | |
| class CommandObjectTargetModulesDumpSeparateDebugInfoFiles | |
| : public CommandObjectTargetModulesModuleAutoComplete { | |
| public: | |
| CommandObjectTargetModulesDumpSeparateDebugInfoFiles( | |
| CommandInterpreter &interpreter) | |
| : CommandObjectTargetModulesModuleAutoComplete( | |
| interpreter, "target modules dump separate-debug-info", | |
| "List the separate debug info symbol files for one or more target " | |
| "modules.", | |
| nullptr, eCommandRequiresTarget) {} | |
| ~CommandObjectTargetModulesDumpSeparateDebugInfoFiles() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() = default; | |
| ~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_load_all_debug_info.SetCurrentValue(true); | |
| m_load_all_debug_info.SetOptionWasSet(); | |
| break; | |
| case 'j': | |
| m_json.SetCurrentValue(true); | |
| m_json.SetOptionWasSet(); | |
| break; | |
| case 'e': | |
| m_errors_only.SetCurrentValue(true); | |
| m_errors_only.SetOptionWasSet(); | |
| break; | |
| default: | |
| llvm_unreachable("Unimplemented option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| m_json.Clear(); | |
| m_errors_only.Clear(); | |
| m_load_all_debug_info.Clear(); | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_target_modules_dump_separate_debug_info_options); | |
| } | |
| OptionValueBoolean m_json = false; | |
| OptionValueBoolean m_errors_only = false; | |
| OptionValueBoolean m_load_all_debug_info = false; | |
| }; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| uint32_t num_dumped = 0; | |
| StructuredData::Array separate_debug_info_lists_by_module; | |
| 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.AppendError("the target has no associated executable images"); | |
| return; | |
| } | |
| for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { | |
| if (INTERRUPT_REQUESTED( | |
| GetDebugger(), | |
| "Interrupted in dumping all " | |
| "separate debug info with {0} of {1} modules dumped", | |
| num_dumped, num_modules)) | |
| break; | |
| if (GetSeparateDebugInfoList(separate_debug_info_lists_by_module, | |
| module_sp.get(), | |
| bool(m_options.m_errors_only), | |
| bool(m_options.m_load_all_debug_info))) | |
| num_dumped++; | |
| } | |
| } 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 (INTERRUPT_REQUESTED(GetDebugger(), | |
| "Interrupted dumping {0} " | |
| "of {1} requested modules", | |
| i, num_matches)) | |
| break; | |
| Module *module = module_list.GetModulePointerAtIndex(i); | |
| if (GetSeparateDebugInfoList(separate_debug_info_lists_by_module, | |
| module, bool(m_options.m_errors_only), | |
| bool(m_options.m_load_all_debug_info))) | |
| num_dumped++; | |
| } | |
| } else | |
| result.AppendWarningWithFormatv( | |
| "unable to find an image that matches '{0}'", arg_cstr); | |
| } | |
| } | |
| if (num_dumped > 0) { | |
| Stream &strm = result.GetOutputStream(); | |
| // Display the debug info files in some format. | |
| if (m_options.m_json) { | |
| // JSON format | |
| separate_debug_info_lists_by_module.Dump(strm, | |
| /*pretty_print=*/true); | |
| } else { | |
| // Human-readable table format | |
| separate_debug_info_lists_by_module.ForEach( | |
| [&result, &strm](StructuredData::Object *obj) { | |
| if (!obj) { | |
| return false; | |
| } | |
| // Each item in `separate_debug_info_lists_by_module` should be a | |
| // valid structured data dictionary. | |
| StructuredData::Dictionary *separate_debug_info_list = | |
| obj->GetAsDictionary(); | |
| if (!separate_debug_info_list) { | |
| return false; | |
| } | |
| llvm::StringRef type; | |
| llvm::StringRef symfile; | |
| StructuredData::Array *files; | |
| if (!(separate_debug_info_list->GetValueForKeyAsString("type", | |
| type) && | |
| separate_debug_info_list->GetValueForKeyAsString("symfile", | |
| symfile) && | |
| separate_debug_info_list->GetValueForKeyAsArray( | |
| "separate-debug-info-files", files))) { | |
| assert(false); | |
| } | |
| strm << "Symbol file: " << symfile; | |
| strm.EOL(); | |
| strm << "Type: \"" << type << "\""; | |
| strm.EOL(); | |
| if (type == "dwo") { | |
| DumpDwoFilesTable(strm, *files); | |
| } else if (type == "oso") { | |
| DumpOsoFilesTable(strm, *files); | |
| } else { | |
| result.AppendWarningWithFormatv( | |
| "found unsupported debug info type '{0}'", type); | |
| } | |
| return true; | |
| }); | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } else { | |
| result.AppendError("no matching executable images found"); | |
| } | |
| } | |
| 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 " | |
| "[objfile|symtab|sections|ast|symfile|line-table|pcm-info|separate-" | |
| "debug-info] " | |
| "[<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))); | |
| LoadSubCommand("separate-debug-info", | |
| CommandObjectSP( | |
| new CommandObjectTargetModulesDumpSeparateDebugInfoFiles( | |
| 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_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(); | |
| AddSimpleArgumentList(eArgTypePath, eArgRepeatStar); | |
| } | |
| ~CommandObjectTargetModulesAdd() override = default; | |
| Options *GetOptions() override { return &m_option_group; } | |
| protected: | |
| OptionGroupOptions m_option_group; | |
| OptionGroupUUID m_uuid_option_group; | |
| OptionGroupFile m_symbol_file; | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| 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(); | |
| Status error; | |
| if (PluginManager::DownloadObjectAndSymbolFile(module_spec, error)) { | |
| ModuleSP module_sp( | |
| target->GetOrCreateModule(module_spec, true /* notify */)); | |
| if (module_sp) { | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| return; | |
| } 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()); | |
| } | |
| return; | |
| } | |
| } else { | |
| StreamString strm; | |
| module_spec.GetUUID().Dump(strm); | |
| result.AppendErrorWithFormat( | |
| "Unable to locate the executable or symbol file with UUID %s", | |
| strm.GetData()); | |
| result.SetError(std::move(error)); | |
| return; | |
| } | |
| } else { | |
| result.AppendError( | |
| "one or more executable image paths must be specified"); | |
| return; | |
| } | |
| } 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()); | |
| return; | |
| } else { | |
| flush = true; | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } else { | |
| std::string resolved_path = file_spec.GetPath(); | |
| if (resolved_path != entry.ref()) { | |
| result.AppendErrorWithFormat( | |
| "invalid module path '%s' with resolved path '%s'", | |
| entry.ref().str().c_str(), resolved_path.c_str()); | |
| break; | |
| } | |
| result.AppendErrorWithFormat("invalid module path '%s'", | |
| entry.c_str()); | |
| break; | |
| } | |
| } | |
| } | |
| if (flush) { | |
| ProcessSP process = target->GetProcessSP(); | |
| if (process) | |
| process->Flush(); | |
| } | |
| } | |
| }; | |
| 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_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: | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| 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'", | |
| arg_cstr); | |
| } else { | |
| search_using_module_spec = false; | |
| result.AppendErrorWithFormat("no object file for module '%s'", | |
| arg_cstr); | |
| } | |
| } | |
| 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"); | |
| return; | |
| } | |
| } 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"); | |
| return; | |
| } | |
| 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) { | |
| addr_t load_addr; | |
| if (llvm::to_integer(load_addr_cstr, load_addr)) { | |
| SectionSP section_sp( | |
| section_list->FindSectionByName(sect_name)); | |
| if (section_sp) { | |
| if (section_sp->IsThreadSpecific()) { | |
| result.AppendErrorWithFormat( | |
| "thread specific sections are not yet " | |
| "supported (section '%s')", | |
| sect_name); | |
| break; | |
| } else { | |
| if (target->SetSectionLoadAddress(section_sp, | |
| load_addr)) | |
| changed = true; | |
| result.AppendMessageWithFormatv( | |
| "section '{0}' loaded at {1:x}", sect_name, | |
| load_addr); | |
| } | |
| } else { | |
| result.AppendErrorWithFormat("no section found that " | |
| "matches the section " | |
| "name '%s'", | |
| sect_name); | |
| break; | |
| } | |
| } else { | |
| result.AppendErrorWithFormat( | |
| "invalid load address string '%s'", load_addr_cstr); | |
| 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"); | |
| 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; | |
| } | |
| if (set_pc && !file_entry.IsValid()) { | |
| result.AppendError("No entry address in object file"); | |
| return; | |
| } | |
| std::vector<ObjectFile::LoadableData> loadables( | |
| objfile->GetLoadableData(*target)); | |
| if (loadables.size() == 0) { | |
| result.AppendError("No loadable sections"); | |
| return; | |
| } | |
| Status error = process->WriteObjectFile(std::move(loadables)); | |
| if (error.Fail()) { | |
| result.AppendError(error.AsCString()); | |
| return; | |
| } | |
| 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, | |
| file_entry_addr); | |
| } | |
| } | |
| } | |
| } else { | |
| module->GetFileSpec().GetPath(path, sizeof(path)); | |
| result.AppendErrorWithFormat("no sections in object file '%s'", | |
| path); | |
| } | |
| } else { | |
| module->GetFileSpec().GetPath(path, sizeof(path)); | |
| result.AppendErrorWithFormat("no object file for module '%s'", | |
| path); | |
| } | |
| } else { | |
| FileSpec *module_spec_file = module_spec.GetFileSpecPtr(); | |
| if (module_spec_file) { | |
| module_spec_file->GetPath(path, sizeof(path)); | |
| result.AppendErrorWithFormat("invalid module '%s'", path); | |
| } else | |
| result.AppendError("no module spec"); | |
| } | |
| } 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:", 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.AppendMessageWithFormatv("{0}", path); | |
| } | |
| } else { | |
| result.AppendErrorWithFormat( | |
| "no modules were found that match%s%s%s%s", | |
| path[0] ? " file=" : "", path, !uuid_str.empty() ? " uuid=" : "", | |
| uuid_str.c_str()); | |
| } | |
| } | |
| } else { | |
| result.AppendError("either the \"--file <module>\" or the \"--uuid " | |
| "<uuid>\" option must be specified.\n"); | |
| } | |
| if (result.GetStatus() != eReturnStatusFailed) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| 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; | |
| }; | |
| #pragma mark CommandObjectTargetModulesList | |
| // 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() = default; | |
| ~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::ArrayRef(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 = false; | |
| lldb::addr_t m_module_addr = LLDB_INVALID_ADDRESS; | |
| }; | |
| CommandObjectTargetModulesList(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target modules list", | |
| "List current executable and dependent shared library images.", | |
| nullptr, eCommandAllowsDummyTarget) { | |
| AddSimpleArgumentList(eArgTypeModule, eArgRepeatStar); | |
| } | |
| ~CommandObjectTargetModulesList() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| const bool use_global_module_list = m_options.m_use_global_module_list; | |
| // Every code path other than the global module list needs a real target. | |
| if (!use_global_module_list && (!target || target->IsDummyTarget())) { | |
| result.AppendError(GetInvalidTargetDescription()); | |
| return; | |
| } | |
| // 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; | |
| // Dump all sections for all modules images | |
| Stream &strm = result.GetOutputStream(); | |
| if (m_options.m_module_addr != LLDB_INVALID_ADDRESS) { | |
| 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); | |
| } | |
| } else { | |
| result.AppendErrorWithFormat( | |
| "Couldn't find module containing address: 0x%" PRIx64, | |
| m_options.m_module_addr); | |
| } | |
| return; | |
| } | |
| 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 { | |
| for (const Args::ArgEntry &arg : command) { | |
| // Dump specified images (by basename or fullpath) | |
| const size_t num_matches = FindModulesByName( | |
| target, arg.c_str(), module_list, use_global_module_list); | |
| if (num_matches == 0) { | |
| if (argc == 1) { | |
| result.AppendErrorWithFormat("no modules found that match '%s'", | |
| arg.c_str()); | |
| return; | |
| } | |
| } | |
| } | |
| 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"); | |
| } | |
| return; | |
| } | |
| } | |
| 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.GetArchitecture().GetAddressByteSize() * 2; | |
| ObjectFile *objfile = module->GetObjectFile(); | |
| if (objfile) { | |
| Address base_addr(objfile->GetBaseAddress()); | |
| if (base_addr.IsValid()) { | |
| if (target.HasLoadedSections()) { | |
| 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; | |
| char in_shared_cache = 'Y'; | |
| ModuleSP module_sp(module->shared_from_this()); | |
| if (!ModuleList::ModuleIsInCache(module)) | |
| in_shared_cache = 'N'; | |
| 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("{%c %*" PRIu64 "}", in_shared_cache, width, (uint64_t)ref_count); | |
| else | |
| strm.Printf("{%c %" PRIu64 "}", in_shared_cache, (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); | |
| std::optional<addr_t> memory_addr = module->GetMemoryModuleAddress(); | |
| if (memory_addr.has_value()) | |
| strm.Printf("(0x%" PRIx64 ")", memory_addr.value()); | |
| } | |
| 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() = default; | |
| ~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 = std::string(option_arg); | |
| m_type = eLookupTypeAddress; | |
| m_addr = OptionArgParser::ToAddress(execution_context, option_arg, | |
| LLDB_INVALID_ADDRESS, &error); | |
| if (m_addr == LLDB_INVALID_ADDRESS) | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid address string '%s'", option_arg.str().c_str()); | |
| break; | |
| } | |
| case 'n': | |
| m_str = std::string(option_arg); | |
| m_type = eLookupTypeFunctionOrSymbol; | |
| break; | |
| case 'c': | |
| bool value, success; | |
| value = OptionArgParser::ToBoolean(option_arg, false, &success); | |
| if (success) { | |
| m_cached = value; | |
| } else { | |
| return Status::FromErrorStringWithFormatv( | |
| "invalid boolean value '{}' passed for -c option", option_arg); | |
| } | |
| 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; | |
| m_cached = false; | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_target_modules_show_unwind_options); | |
| } | |
| // Instance variables to hold the values for command options. | |
| int m_type = eLookupTypeInvalid; // Should be a eLookupTypeXXX enum after | |
| // parsing options | |
| std::string m_str; // Holds name lookup | |
| lldb::addr_t m_addr = LLDB_INVALID_ADDRESS; // Holds the address to lookup | |
| bool m_cached = true; | |
| }; | |
| CommandObjectTargetModulesShowUnwind(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target modules show-unwind", | |
| "Show synthesized unwind instructions for a function.", nullptr, | |
| eCommandRequiresTarget | eCommandRequiresProcess | | |
| eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) {} | |
| ~CommandObjectTargetModulesShowUnwind() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| protected: | |
| void 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."); | |
| return; | |
| } | |
| ThreadList threads(process->GetThreadList()); | |
| if (threads.GetSize() == 0) { | |
| result.AppendError("the process must be paused to use this command"); | |
| return; | |
| } | |
| ThreadSP thread(threads.GetThreadAtIndex(0)); | |
| if (!thread) { | |
| result.AppendError("the process must be paused to use this command"); | |
| return; | |
| } | |
| SymbolContextList sc_list; | |
| if (m_options.m_type == eLookupTypeFunctionOrSymbol) { | |
| ConstString function_name(m_options.m_str); | |
| ModuleFunctionSearchOptions function_options; | |
| function_options.include_symbols = true; | |
| function_options.include_inlines = false; | |
| target->GetImages().FindFunctions(function_name, eFunctionNameTypeAuto, | |
| function_options, sc_list); | |
| } else if (m_options.m_type == eLookupTypeAddress && target) { | |
| Address addr; | |
| if (target->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."); | |
| return; | |
| } | |
| if (sc_list.GetSize() == 0) { | |
| result.AppendErrorWithFormat("no unwind data found that matches '%s'", | |
| m_options.m_str.c_str()); | |
| return; | |
| } | |
| for (const SymbolContext &sc : sc_list) { | |
| if (sc.symbol == nullptr && sc.function == nullptr) | |
| continue; | |
| if (!sc.module_sp || sc.module_sp->GetObjectFile() == nullptr) | |
| continue; | |
| Address addr = sc.GetFunctionOrSymbolAddress(); | |
| if (!addr.IsValid()) | |
| continue; | |
| ConstString funcname(sc.GetFunctionName()); | |
| if (funcname.IsEmpty()) | |
| continue; | |
| addr_t start_addr = addr.GetLoadAddress(target); | |
| if (abi) | |
| start_addr = abi->FixCodeAddress(start_addr); | |
| UnwindTable &uw_table = sc.module_sp->GetUnwindTable(); | |
| FuncUnwindersSP func_unwinders_sp = | |
| m_options.m_cached | |
| ? uw_table.GetFuncUnwindersContainingAddress(Address(start_addr), | |
| sc) | |
| : uw_table.GetUncachedFuncUnwindersContainingAddress( | |
| Address(start_addr), sc); | |
| if (!func_unwinders_sp) | |
| continue; | |
| result.GetOutputStream().Format( | |
| "UNWIND PLANS for {0}`{1} (start addr {2:x})\n", | |
| sc.module_sp->GetPlatformFileSpec().GetFilename(), funcname, | |
| start_addr); | |
| Args args; | |
| target->GetUserSpecifiedTrapHandlerNames(args); | |
| size_t count = args.GetArgumentCount(); | |
| for (size_t i = 0; i < count; i++) { | |
| const char *trap_func_name = args.GetArgumentAtIndex(i); | |
| if (strcmp(funcname.GetCString(), trap_func_name) == 0) | |
| result.GetOutputStream().Printf( | |
| "This function is " | |
| "treated as a trap handler function via user setting.\n"); | |
| } | |
| PlatformSP platform_sp(target->GetPlatform()); | |
| if (platform_sp) { | |
| const std::vector<std::string> &trap_handler_names( | |
| platform_sp->GetTrapHandlerSymbolNames()); | |
| for (const std::string &trap_name : trap_handler_names) { | |
| if (llvm::StringRef(trap_name) == funcname) { | |
| result.GetOutputStream().Printf( | |
| "This function's " | |
| "name is listed by the platform as a trap handler.\n"); | |
| } | |
| } | |
| } | |
| result.GetOutputStream().Printf("\n"); | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetUnwindPlanAtNonCallSite(*target, *thread)) { | |
| result.GetOutputStream().Format( | |
| "Asynchronous (not restricted to call-sites) UnwindPlan is '{0}'\n", | |
| plan_sp->GetSourceName()); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetUnwindPlanAtCallSite(*target, *thread)) { | |
| result.GetOutputStream().Format( | |
| "Synchronous (restricted to call-sites) UnwindPlan is '{0}'\n", | |
| plan_sp->GetSourceName()); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetUnwindPlanFastUnwind(*target, *thread)) { | |
| result.GetOutputStream().Format("Fast UnwindPlan is '{0}'\n", | |
| plan_sp->GetSourceName()); | |
| } | |
| result.GetOutputStream().Printf("\n"); | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetAssemblyUnwindPlan(*target, *thread)) { | |
| result.GetOutputStream().Printf( | |
| "Assembly language inspection UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetObjectFileUnwindPlan(*target)) { | |
| result.GetOutputStream().Printf("object file UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetObjectFileAugmentedUnwindPlan(*target, | |
| *thread)) { | |
| result.GetOutputStream().Printf("object file augmented UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetEHFrameUnwindPlan(*target)) { | |
| result.GetOutputStream().Printf("eh_frame UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetEHFrameAugmentedUnwindPlan(*target, | |
| *thread)) { | |
| result.GetOutputStream().Printf("eh_frame augmented UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> 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 (std::shared_ptr<const UnwindPlan> 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"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetArmUnwindUnwindPlan(*target)) { | |
| result.GetOutputStream().Printf("ARM.exidx unwind UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetSymbolFileUnwindPlan(*thread)) { | |
| result.GetOutputStream().Printf("Symbol file UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetCompactUnwindUnwindPlan(*target)) { | |
| result.GetOutputStream().Printf("Compact unwind UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (std::shared_ptr<const UnwindPlan> plan_sp = | |
| func_unwinders_sp->GetUnwindPlanFastUnwind(*target, *thread)) { | |
| result.GetOutputStream().Printf("Fast UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| ABISP abi_sp = process->GetABI(); | |
| if (abi_sp) { | |
| if (UnwindPlanSP plan_sp = abi_sp->CreateDefaultUnwindPlan()) { | |
| assert(((!plan_sp || plan_sp->GetRowCount() == 0 || | |
| plan_sp->GetRowAtIndex(0) | |
| ->GetUnspecifiedRegistersAreUndefined())) && | |
| "Default UnwindPlan must set " | |
| "UnspecifiedRegistersAreUndefined to true"); | |
| result.GetOutputStream().Printf("Arch default UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| if (UnwindPlanSP plan_sp = abi_sp->CreateFunctionEntryUnwindPlan()) { | |
| result.GetOutputStream().Printf( | |
| "Arch default at entry point UnwindPlan:\n"); | |
| plan_sp->Dump(result.GetOutputStream(), thread.get(), | |
| LLDB_INVALID_ADDRESS); | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| } | |
| result.GetOutputStream().Printf("\n"); | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| 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() { 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 = Status::FromErrorStringWithFormat( | |
| "invalid offset string '%s'", option_arg.str().c_str()); | |
| break; | |
| case 's': | |
| m_str = std::string(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 = Status::FromErrorStringWithFormat( | |
| "invalid line number string '%s'", option_arg.str().c_str()); | |
| else if (m_line_number == 0) | |
| error = Status::FromErrorString("zero is an invalid line number"); | |
| m_type = eLookupTypeFileLine; | |
| break; | |
| case 'F': | |
| m_str = std::string(option_arg); | |
| m_type = eLookupTypeFunction; | |
| break; | |
| case 'n': | |
| m_str = std::string(option_arg); | |
| m_type = eLookupTypeFunctionOrSymbol; | |
| break; | |
| case 't': | |
| m_str = std::string(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; | |
| case '\x01': | |
| m_all_ranges = 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_all_ranges = false; | |
| m_verbose = false; | |
| m_print_all = false; | |
| } | |
| Status OptionParsingFinished(ExecutionContext *execution_context) override { | |
| Status status; | |
| if (m_all_ranges && !m_verbose) { | |
| status = | |
| Status::FromErrorString("--show-variable-ranges must be used in " | |
| "conjunction with --verbose."); | |
| } | |
| return status; | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(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_all_ranges; // Print all ranges or single range. | |
| 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) { | |
| AddSimpleArgumentList(eArgTypeFilename, eArgRepeatStar); | |
| } | |
| ~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(GetTarget(), 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 false; | |
| } | |
| 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, | |
| m_options.m_all_ranges)) { | |
| 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, | |
| m_options.m_all_ranges)) { | |
| 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, | |
| m_options.m_all_ranges)) { | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| return true; | |
| } | |
| } | |
| break; | |
| case eLookupTypeFunctionOrSymbol: | |
| case eLookupTypeFunction: | |
| if (!m_options.m_str.empty()) { | |
| ModuleFunctionSearchOptions function_options; | |
| function_options.include_symbols = | |
| m_options.m_type == eLookupTypeFunctionOrSymbol; | |
| function_options.include_inlines = m_options.m_include_inlines; | |
| if (LookupFunctionInModule(m_interpreter, result.GetOutputStream(), | |
| module, m_options.m_str.c_str(), | |
| m_options.m_use_regex, function_options, | |
| m_options.m_verbose, | |
| m_options.m_all_ranges)) { | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| return true; | |
| } | |
| } | |
| break; | |
| case eLookupTypeType: | |
| if (!m_options.m_str.empty()) { | |
| if (LookupTypeInModule( | |
| GetTarget(), 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(), | |
| GetCommandInterpreter().GetDebugger().GetUseColor()); | |
| syntax_error = true; | |
| break; | |
| } | |
| result.SetStatus(eReturnStatusFailed); | |
| return false; | |
| } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| bool syntax_error = false; | |
| uint32_t i; | |
| uint32_t num_successful_lookups = 0; | |
| // Dump all sections for all modules images | |
| if (command.GetArgumentCount() == 0) { | |
| // 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; | |
| } | |
| } | |
| // Dump all sections for all other modules | |
| const ModuleList &target_modules = target->GetImages(); | |
| std::lock_guard<std::recursive_mutex> guard(target_modules.GetMutex()); | |
| if (target_modules.GetSize() == 0) { | |
| result.AppendError("the target has no associated executable images"); | |
| return; | |
| } | |
| for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { | |
| if (LookupInModule(m_interpreter, module_sp.get(), result, | |
| syntax_error)) { | |
| result.GetOutputStream().EOL(); | |
| num_successful_lookups++; | |
| } | |
| } | |
| } 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.AppendWarningWithFormatv( | |
| "unable to find an image that matches '{0}'", arg_cstr); | |
| } | |
| } | |
| if (num_successful_lookups > 0) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else | |
| result.SetStatus(eReturnStatusFailed); | |
| } | |
| 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 | |
| CommandObjectTargetModules(const CommandObjectTargetModules &) = delete; | |
| const CommandObjectTargetModules & | |
| operator=(const CommandObjectTargetModules &) = delete; | |
| }; | |
| 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 by using the options " | |
| "to specify a module.", | |
| "target symbols add <cmd-options> [<symfile>]", | |
| eCommandRequiresTarget), | |
| m_file_option( | |
| LLDB_OPT_SET_1, false, "shlib", 's', lldb::eModuleCompletion, | |
| eArgTypeShlibName, | |
| "Locate the debug symbols for the shared library specified by " | |
| "name."), | |
| m_current_frame_option( | |
| LLDB_OPT_SET_2, false, "frame", 'F', | |
| "Locate the debug symbols for the currently selected frame.", false, | |
| true), | |
| m_current_stack_option(LLDB_OPT_SET_2, false, "stack", 'S', | |
| "Locate the debug symbols for every frame in " | |
| "the current call stack.", | |
| 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.Append(&m_current_stack_option, LLDB_OPT_SET_2, | |
| LLDB_OPT_SET_2); | |
| m_option_group.Finalize(); | |
| AddSimpleArgumentList(eArgTypeFilename); | |
| } | |
| ~CommandObjectTargetSymbolsAdd() override = default; | |
| 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) { | |
| result.AppendError( | |
| "one or more executable image paths must be specified"); | |
| return false; | |
| } | |
| 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().SetFilename(symbol_fspec.GetFilename()); | |
| } | |
| // Now module_spec represents a symbol file for a module that might exist | |
| // in the current target. Let's find possible matches. | |
| ModuleList matching_modules; | |
| // First extract all module specs from the symbol file | |
| lldb_private::ModuleSpecList symfile_module_specs = | |
| ObjectFile::GetModuleSpecifications(module_spec.GetSymbolFileSpec(), 0, | |
| 0); | |
| if (symfile_module_specs.GetSize() > 0) { | |
| // 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)) { | |
| 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_modules); | |
| } | |
| } | |
| if (matching_modules.IsEmpty()) { | |
| // 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 && matching_modules.IsEmpty(); ++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_modules); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Just try to match up the file by basename if we have no matches at | |
| // this point. For example, module foo might have symbols in foo.debug. | |
| if (matching_modules.IsEmpty()) | |
| target->GetImages().FindModules(module_spec, matching_modules); | |
| while (matching_modules.IsEmpty()) { | |
| ConstString filename_no_extension( | |
| module_spec.GetFileSpec().GetFileNameStrippingExtension()); | |
| // Empty string returned, let's 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 fewer extension | |
| module_spec.GetFileSpec().SetFilename(filename_no_extension); | |
| target->GetImages().FindModules(module_spec, matching_modules); | |
| } | |
| if (matching_modules.GetSize() > 1) { | |
| result.AppendErrorWithFormat("multiple modules match symbol file '%s', " | |
| "use the --uuid option to resolve the " | |
| "ambiguity", | |
| symfile_path); | |
| return false; | |
| } | |
| if (matching_modules.GetSize() == 1) { | |
| ModuleSP module_sp(matching_modules.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.AppendMessageWithFormatv( | |
| "symbol file '{0}' has been added to '{1}'", 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. | |
| std::list<Status> errors; | |
| module_list.LoadScriptingResourcesInTarget(target, errors); | |
| for (const auto &err : errors) | |
| result.AppendWarning(err.AsCString()); | |
| flush = true; | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| return true; | |
| } | |
| } | |
| // Clear the symbol file spec if anything went wrong | |
| module_sp->SetSymbolFileFileSpec(FileSpec()); | |
| } | |
| StreamString ss_symfile_uuid; | |
| if (module_spec.GetUUID().IsValid()) { | |
| ss_symfile_uuid << " ("; | |
| module_spec.GetUUID().Dump(ss_symfile_uuid); | |
| ss_symfile_uuid << ')'; | |
| } | |
| result.AppendErrorWithFormat( | |
| "symbol file '%s'%s does not match any existing module%s", symfile_path, | |
| ss_symfile_uuid.GetData(), | |
| !llvm::sys::fs::is_regular_file(symbol_fspec.GetPath()) | |
| ? "\n please specify the full path to the symbol file" | |
| : ""); | |
| return false; | |
| } | |
| bool DownloadObjectAndSymbolFile(ModuleSpec &module_spec, | |
| CommandReturnObject &result, bool &flush) { | |
| Status error; | |
| if (PluginManager::DownloadObjectAndSymbolFile(module_spec, error)) { | |
| if (module_spec.GetSymbolFileSpec()) | |
| return AddModuleSymbols(m_exe_ctx.GetTargetPtr(), module_spec, flush, | |
| result); | |
| } else { | |
| result.SetError(std::move(error)); | |
| } | |
| return false; | |
| } | |
| bool AddSymbolsForUUID(CommandReturnObject &result, bool &flush) { | |
| assert(m_uuid_option_group.GetOptionValue().OptionWasSet()); | |
| ModuleSpec module_spec; | |
| module_spec.GetUUID() = | |
| m_uuid_option_group.GetOptionValue().GetCurrentValue(); | |
| if (!DownloadObjectAndSymbolFile(module_spec, result, flush)) { | |
| StreamString error_strm; | |
| error_strm.PutCString("unable to find debug symbols for UUID "); | |
| module_spec.GetUUID().Dump(error_strm); | |
| result.AppendError(error_strm.GetString()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool AddSymbolsForFile(CommandReturnObject &result, bool &flush) { | |
| assert(m_file_option.GetOptionValue().OptionWasSet()); | |
| ModuleSpec module_spec; | |
| module_spec.GetFileSpec() = | |
| m_file_option.GetOptionValue().GetCurrentValue(); | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| 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(); | |
| } | |
| if (!DownloadObjectAndSymbolFile(module_spec, result, flush)) { | |
| StreamString error_strm; | |
| error_strm.PutCString( | |
| "unable to find debug symbols for the executable file "); | |
| error_strm << module_spec.GetFileSpec(); | |
| result.AppendError(error_strm.GetString()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool AddSymbolsForFrame(CommandReturnObject &result, bool &flush) { | |
| assert(m_current_frame_option.GetOptionValue().OptionWasSet()); | |
| Process *process = m_exe_ctx.GetProcessPtr(); | |
| if (!process) { | |
| result.AppendError( | |
| "a process must exist in order to use the --frame option"); | |
| return false; | |
| } | |
| const StateType process_state = process->GetState(); | |
| if (!StateIsStoppedState(process_state, true)) { | |
| result.AppendErrorWithFormat("process is not stopped: %s", | |
| StateAsCString(process_state)); | |
| return false; | |
| } | |
| StackFrame *frame = m_exe_ctx.GetFramePtr(); | |
| if (!frame) { | |
| result.AppendError("invalid current frame"); | |
| return false; | |
| } | |
| ModuleSP frame_module_sp( | |
| frame->GetSymbolContext(eSymbolContextModule).module_sp); | |
| if (!frame_module_sp) { | |
| result.AppendError("frame has no module"); | |
| return false; | |
| } | |
| ModuleSpec module_spec; | |
| module_spec.GetUUID() = frame_module_sp->GetUUID(); | |
| module_spec.GetArchitecture() = frame_module_sp->GetArchitecture(); | |
| module_spec.GetFileSpec() = frame_module_sp->GetPlatformFileSpec(); | |
| if (!DownloadObjectAndSymbolFile(module_spec, result, flush)) { | |
| result.AppendError("unable to find debug symbols for the current frame"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool AddSymbolsForStack(CommandReturnObject &result, bool &flush) { | |
| assert(m_current_stack_option.GetOptionValue().OptionWasSet()); | |
| Process *process = m_exe_ctx.GetProcessPtr(); | |
| if (!process) { | |
| result.AppendError( | |
| "a process must exist in order to use the --stack option"); | |
| return false; | |
| } | |
| const StateType process_state = process->GetState(); | |
| if (!StateIsStoppedState(process_state, true)) { | |
| result.AppendErrorWithFormat("process is not stopped: %s", | |
| StateAsCString(process_state)); | |
| return false; | |
| } | |
| Thread *thread = m_exe_ctx.GetThreadPtr(); | |
| if (!thread) { | |
| result.AppendError("invalid current thread"); | |
| return false; | |
| } | |
| bool symbols_found = false; | |
| uint32_t frame_count = thread->GetStackFrameCount(); | |
| for (uint32_t i = 0; i < frame_count; ++i) { | |
| lldb::StackFrameSP frame_sp = thread->GetStackFrameAtIndex(i); | |
| ModuleSP frame_module_sp( | |
| frame_sp->GetSymbolContext(eSymbolContextModule).module_sp); | |
| if (!frame_module_sp) | |
| continue; | |
| ModuleSpec module_spec; | |
| module_spec.GetUUID() = frame_module_sp->GetUUID(); | |
| module_spec.GetFileSpec() = frame_module_sp->GetPlatformFileSpec(); | |
| module_spec.GetArchitecture() = frame_module_sp->GetArchitecture(); | |
| bool current_frame_flush = false; | |
| if (DownloadObjectAndSymbolFile(module_spec, result, current_frame_flush)) | |
| symbols_found = true; | |
| flush |= current_frame_flush; | |
| } | |
| if (!symbols_found) { | |
| result.AppendError( | |
| "unable to find debug symbols in the current call stack"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void 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 bool stack_option_set = | |
| m_current_stack_option.GetOptionValue().OptionWasSet(); | |
| const size_t argc = args.GetArgumentCount(); | |
| if (argc == 0) { | |
| if (uuid_option_set) | |
| AddSymbolsForUUID(result, flush); | |
| else if (file_option_set) | |
| AddSymbolsForFile(result, flush); | |
| else if (frame_option_set) | |
| AddSymbolsForFrame(result, flush); | |
| else if (stack_option_set) | |
| AddSymbolsForStack(result, flush); | |
| 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; | |
| } | |
| 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'", | |
| entry.c_str(), resolved_symfile_path.c_str()); | |
| break; | |
| } | |
| result.AppendErrorWithFormat("invalid module path '%s'", | |
| entry.c_str()); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (flush) { | |
| Process *process = m_exe_ctx.GetProcessPtr(); | |
| if (process) | |
| process->Flush(); | |
| } | |
| } | |
| OptionGroupOptions m_option_group; | |
| OptionGroupUUID m_uuid_option_group; | |
| OptionGroupFile m_file_option; | |
| OptionGroupBoolean m_current_frame_option; | |
| OptionGroupBoolean m_current_stack_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 | |
| CommandObjectTargetSymbols(const CommandObjectTargetSymbols &) = delete; | |
| const CommandObjectTargetSymbols & | |
| operator=(const CommandObjectTargetSymbols &) = delete; | |
| }; | |
| #pragma mark CommandObjectTargetStopHookAdd | |
| // CommandObjectTargetStopHookAdd | |
| #define LLDB_OPTIONS_target_stop_hook_add | |
| #include "CommandOptions.inc" | |
| class CommandObjectTargetStopHookAdd : public CommandObjectParsed, | |
| public IOHandlerDelegateMultiline { | |
| public: | |
| class CommandOptions : public OptionGroup { | |
| public: | |
| CommandOptions() : m_line_end(UINT_MAX) {} | |
| ~CommandOptions() override = default; | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(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 = | |
| g_target_stop_hook_add_options[option_idx].short_option; | |
| switch (short_option) { | |
| case 'c': | |
| m_class_name = std::string(option_arg); | |
| m_sym_ctx_specified = true; | |
| break; | |
| case 'e': | |
| if (option_arg.getAsInteger(0, m_line_end)) { | |
| error = Status::FromErrorStringWithFormat( | |
| "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 = Status::FromErrorStringWithFormat( | |
| "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 = Status::FromErrorStringWithFormat( | |
| "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 = std::string(option_arg); | |
| m_func_name_type_mask |= eFunctionNameTypeAuto; | |
| m_sym_ctx_specified = true; | |
| break; | |
| case 'f': | |
| m_file_name = std::string(option_arg); | |
| m_sym_ctx_specified = true; | |
| break; | |
| case 's': | |
| m_module_name = std::string(option_arg); | |
| m_sym_ctx_specified = true; | |
| break; | |
| case 't': | |
| if (option_arg.getAsInteger(0, m_thread_id)) | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid thread id string '%s'", option_arg.str().c_str()); | |
| m_thread_specified = true; | |
| break; | |
| case 'T': | |
| m_thread_name = std::string(option_arg); | |
| m_thread_specified = true; | |
| break; | |
| case 'q': | |
| m_queue_name = std::string(option_arg); | |
| m_thread_specified = true; | |
| break; | |
| case 'x': | |
| if (option_arg.getAsInteger(0, m_thread_index)) | |
| error = Status::FromErrorStringWithFormat( | |
| "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(std::string(option_arg)); | |
| break; | |
| case 'I': { | |
| bool value, success; | |
| value = OptionArgParser::ToBoolean(option_arg, false, &success); | |
| if (success) | |
| m_at_initial_stop = value; | |
| else | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid boolean value '%s' passed for -F option", | |
| option_arg.str().c_str()); | |
| } 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 = LLDB_INVALID_LINE_NUMBER; | |
| 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; | |
| m_at_initial_stop = true; | |
| } | |
| std::string m_class_name; | |
| std::string m_function_name; | |
| uint32_t m_line_start = 0; | |
| uint32_t m_line_end = LLDB_INVALID_LINE_NUMBER; | |
| std::string m_file_name; | |
| std::string m_module_name; | |
| uint32_t m_func_name_type_mask = | |
| eFunctionNameTypeAuto; // A pick from lldb::FunctionNameType. | |
| lldb::tid_t m_thread_id = LLDB_INVALID_THREAD_ID; | |
| uint32_t m_thread_index = UINT32_MAX; | |
| std::string m_thread_name; | |
| std::string m_queue_name; | |
| bool m_sym_ctx_specified = false; | |
| bool m_no_inlines = false; | |
| bool m_thread_specified = false; | |
| // Instance variables to hold the values for one_liner options. | |
| bool m_use_one_liner = false; | |
| std::vector<std::string> m_one_liner; | |
| bool m_at_initial_stop; | |
| bool m_auto_continue = false; | |
| }; | |
| CommandObjectTargetStopHookAdd(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "target stop-hook add", | |
| "Add a hook to be executed when the target stops." | |
| "The hook can either be a list of commands or an " | |
| "appropriately defined Python class. You can also " | |
| "add filters so the hook only runs a certain stop " | |
| "points.", | |
| "target stop-hook add", eCommandAllowsDummyTarget), | |
| IOHandlerDelegateMultiline("DONE", | |
| IOHandlerDelegate::Completion::LLDBCommand), | |
| m_python_class_options("scripted stop-hook", true, 'P') { | |
| SetHelpLong( | |
| R"( | |
| Command Based stop-hooks: | |
| ------------------------- | |
| Stop hooks can run a list of lldb commands by providing one or more | |
| --one-liner options. The commands will get run in the order they are added. | |
| Or you can provide no commands, in which case you will enter a command editor | |
| where you can enter the commands to be run. | |
| Python Based Stop Hooks: | |
| ------------------------ | |
| Stop hooks can be implemented with a suitably defined Python class, whose name | |
| is passed in the --python-class option. | |
| When the stop hook is added, the class is initialized by calling: | |
| def __init__(self, target, extra_args, internal_dict): | |
| target: The target that the stop hook is being added to. | |
| extra_args: An SBStructuredData Dictionary filled with the -key -value | |
| option pairs passed to the command. | |
| dict: An implementation detail provided by lldb. | |
| Then when the stop-hook triggers, lldb will run the 'handle_stop' method. | |
| The method has the signature: | |
| def handle_stop(self, exe_ctx, stream): | |
| exe_ctx: An SBExecutionContext for the thread that has stopped. | |
| stream: An SBStream, anything written to this stream will be printed in the | |
| the stop message when the process stops. | |
| Return Value: The method returns "should_stop". If should_stop is false | |
| from all the stop hook executions on threads that stopped | |
| with a reason, then the process will continue. Note that this | |
| will happen only after all the stop hooks are run. | |
| Filter Options: | |
| --------------- | |
| Stop hooks can be set to always run, or to only run when the stopped thread | |
| matches the filter options passed on the command line. The available filter | |
| options include a shared library or a thread or queue specification, | |
| a line range in a source file, a function name or a class name. | |
| )"); | |
| m_all_options.Append(&m_python_class_options, | |
| LLDB_OPT_SET_1 | LLDB_OPT_SET_2, | |
| LLDB_OPT_SET_FROM_TO(4, 6)); | |
| m_all_options.Append(&m_options); | |
| m_all_options.Finalize(); | |
| } | |
| ~CommandObjectTargetStopHookAdd() override = default; | |
| Options *GetOptions() override { return &m_all_options; } | |
| protected: | |
| void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { | |
| if (interactive) { | |
| if (lldb::LockableStreamFileSP output_sp = | |
| io_handler.GetOutputStreamFileSP()) { | |
| LockedStreamFile locked_stream = output_sp->Lock(); | |
| locked_stream.PutCString( | |
| "Enter your stop hook command(s). Type 'DONE' to end.\n"); | |
| } | |
| } | |
| } | |
| void IOHandlerInputComplete(IOHandler &io_handler, | |
| std::string &line) override { | |
| if (m_stop_hook_sp) { | |
| if (line.empty()) { | |
| if (lldb::LockableStreamFileSP error_sp = | |
| io_handler.GetErrorStreamFileSP()) { | |
| LockedStreamFile locked_stream = error_sp->Lock(); | |
| locked_stream.Printf("error: stop hook #%" PRIu64 | |
| " aborted, no commands.\n", | |
| m_stop_hook_sp->GetID()); | |
| } | |
| GetTarget()->UndoCreateStopHook(m_stop_hook_sp->GetID()); | |
| } else { | |
| // The IOHandler editor is only for command lines stop hooks: | |
| Target::StopHookCommandLine *hook_ptr = | |
| static_cast<Target::StopHookCommandLine *>(m_stop_hook_sp.get()); | |
| hook_ptr->SetActionFromString(line); | |
| if (lldb::LockableStreamFileSP output_sp = | |
| io_handler.GetOutputStreamFileSP()) { | |
| LockedStreamFile locked_stream = output_sp->Lock(); | |
| locked_stream.Printf("Stop hook #%" PRIu64 " added.\n", | |
| m_stop_hook_sp->GetID()); | |
| } | |
| } | |
| m_stop_hook_sp.reset(); | |
| } | |
| io_handler.SetIsDone(true); | |
| } | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| m_stop_hook_sp.reset(); | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| Target::StopHookSP new_hook_sp = target->CreateStopHook( | |
| m_python_class_options.GetName().empty() | |
| ? Target::StopHook::StopHookKind::CommandBased | |
| : Target::StopHook::StopHookKind::ScriptBased); | |
| // First step, make the specifier. | |
| std::unique_ptr<SymbolContextSpecifier> specifier_up; | |
| if (m_options.m_sym_ctx_specified) { | |
| specifier_up = | |
| std::make_unique<SymbolContextSpecifier>(target->shared_from_this()); | |
| 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()); | |
| // Should we run at the initial stop: | |
| new_hook_sp->SetRunAtInitialStop(m_options.m_at_initial_stop); | |
| // 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) { | |
| // This is a command line stop hook: | |
| Target::StopHookCommandLine *hook_ptr = | |
| static_cast<Target::StopHookCommandLine *>(new_hook_sp.get()); | |
| hook_ptr->SetActionFromStrings(m_options.m_one_liner); | |
| result.AppendMessageWithFormatv("Stop hook #{0} added.", | |
| new_hook_sp->GetID()); | |
| } else if (!m_python_class_options.GetName().empty()) { | |
| // This is a scripted stop hook: | |
| Target::StopHookScripted *hook_ptr = | |
| static_cast<Target::StopHookScripted *>(new_hook_sp.get()); | |
| ScriptedMetadata scripted_metadata( | |
| m_python_class_options.GetName(), | |
| m_python_class_options.GetStructuredData()); | |
| Status error = hook_ptr->SetScriptCallback(scripted_metadata); | |
| if (error.Success()) | |
| result.AppendMessageWithFormatv("Stop hook #{0} added.", | |
| new_hook_sp->GetID()); | |
| else { | |
| // FIXME: Set the stop hook ID counter back. | |
| result.AppendErrorWithFormat("Couldn't add stop hook: %s", | |
| error.AsCString()); | |
| target->UndoCreateStopHook(new_hook_sp->GetID()); | |
| return; | |
| } | |
| } else { | |
| m_stop_hook_sp = new_hook_sp; | |
| m_interpreter.GetLLDBCommandsFromIOHandler("> ", // Prompt | |
| *this); // IOHandlerDelegate | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| private: | |
| CommandOptions m_options; | |
| OptionGroupPythonClassWithDict m_python_class_options; | |
| OptionGroupOptions m_all_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>]", eCommandAllowsDummyTarget) { | |
| SetHelpLong( | |
| R"( | |
| Deletes the stop hook by index. | |
| At any given stop, all enabled stop hooks that pass the stop filter will | |
| get a chance to run. That means if one stop-hook deletes another stop hook | |
| while executing, the deleted stop hook will still fire for the stop at which | |
| it was deleted. | |
| )"); | |
| AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar); | |
| } | |
| ~CommandObjectTargetStopHookDelete() override = default; | |
| void | |
| HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &opt_element_vector) override { | |
| if (request.GetCursorIndex()) | |
| return; | |
| CommandObject::HandleArgumentCompletion(request, opt_element_vector); | |
| } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| // 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; | |
| } else { | |
| target->RemoveAllStopHooks(); | |
| } | |
| } else { | |
| for (size_t i = 0; i < num_args; i++) { | |
| lldb::user_id_t user_id; | |
| if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) { | |
| result.AppendErrorWithFormat("invalid stop hook id: \"%s\"", | |
| command.GetArgumentAtIndex(i)); | |
| return; | |
| } | |
| if (!target->RemoveStopHookByID(user_id)) { | |
| result.AppendErrorWithFormat("unknown stop hook id: \"%s\"", | |
| command.GetArgumentAtIndex(i)); | |
| return; | |
| } | |
| } | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| }; | |
| #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, | |
| eCommandAllowsDummyTarget), | |
| m_enable(enable) { | |
| AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar); | |
| } | |
| ~CommandObjectTargetStopHookEnableDisable() override = default; | |
| void | |
| HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &opt_element_vector) override { | |
| if (request.GetCursorIndex()) | |
| return; | |
| CommandObject::HandleArgumentCompletion(request, opt_element_vector); | |
| } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| // 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; | |
| if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) { | |
| result.AppendErrorWithFormat("invalid stop hook id: \"%s\"", | |
| command.GetArgumentAtIndex(i)); | |
| return; | |
| } | |
| success = target->SetStopHookActiveStateByID(user_id, m_enable); | |
| if (!success) { | |
| result.AppendErrorWithFormat("unknown stop hook id: \"%s\"", | |
| command.GetArgumentAtIndex(i)); | |
| return; | |
| } | |
| } | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| private: | |
| bool m_enable; | |
| }; | |
| #pragma mark CommandObjectTargetStopHookList | |
| // CommandObjectTargetStopHookList | |
| #define LLDB_OPTIONS_target_stop_hook_list | |
| #include "CommandOptions.inc" | |
| class CommandObjectTargetStopHookList : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetStopHookList(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "target stop-hook list", | |
| "List all stop-hooks.", nullptr, | |
| eCommandAllowsDummyTarget) {} | |
| ~CommandObjectTargetStopHookList() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() = default; | |
| ~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; | |
| default: | |
| llvm_unreachable("Unimplemented option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| m_internal = false; | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_target_stop_hook_list_options); | |
| } | |
| // Instance variables to hold the values for command options. | |
| bool m_internal = false; | |
| }; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| bool printed_hook = false; | |
| for (auto &hook : target->GetStopHooks(m_options.m_internal)) { | |
| if (printed_hook) | |
| result.GetOutputStream().PutCString("\n"); | |
| hook->GetDescription(result.GetOutputStream(), eDescriptionLevelFull); | |
| printed_hook = true; | |
| } | |
| if (!printed_hook) | |
| result.GetOutputStream().PutCString("No stop hooks.\n"); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| private: | |
| CommandOptions m_options; | |
| }; | |
| #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 CommandObjectTargetHookAdd | |
| #define LLDB_OPTIONS_target_hook_add | |
| #include "CommandOptions.inc" | |
| class CommandObjectTargetHookAdd : public CommandObjectParsed, | |
| public IOHandlerDelegateMultiline { | |
| public: | |
| class CommandOptions : public OptionGroup { | |
| public: | |
| CommandOptions() = default; | |
| ~CommandOptions() override = default; | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_target_hook_add_options); | |
| } | |
| Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, | |
| ExecutionContext *execution_context) override { | |
| Status error; | |
| const int short_option = | |
| g_target_hook_add_options[option_idx].short_option; | |
| switch (short_option) { | |
| case 'o': | |
| m_use_one_liner = true; | |
| m_one_liner.push_back(std::string(option_arg)); | |
| break; | |
| case 'L': | |
| m_on_load = true; | |
| break; | |
| case 'u': | |
| m_on_unload = true; | |
| break; | |
| case 'S': | |
| m_on_stop = true; | |
| break; | |
| case 's': | |
| m_module_name = std::string(option_arg); | |
| m_sym_ctx_specified = true; | |
| break; | |
| case 'x': { | |
| uint32_t thread_index; | |
| if (option_arg.getAsInteger(0, thread_index)) | |
| error = Status::FromErrorStringWithFormat("invalid thread index '%s'", | |
| option_arg.str().c_str()); | |
| else | |
| m_thread_index = thread_index; | |
| m_thread_specified = true; | |
| break; | |
| } | |
| case 't': { | |
| lldb::tid_t thread_id; | |
| if (option_arg.getAsInteger(0, thread_id)) | |
| error = Status::FromErrorStringWithFormat("invalid thread id '%s'", | |
| option_arg.str().c_str()); | |
| else | |
| m_thread_id = thread_id; | |
| m_thread_specified = true; | |
| break; | |
| } | |
| case 'T': | |
| m_thread_name = std::string(option_arg); | |
| m_thread_specified = true; | |
| break; | |
| case 'q': | |
| m_queue_name = std::string(option_arg); | |
| m_thread_specified = true; | |
| break; | |
| case 'f': | |
| m_file_name = std::string(option_arg); | |
| m_sym_ctx_specified = true; | |
| break; | |
| case 'l': { | |
| uint32_t line; | |
| if (option_arg.getAsInteger(0, line)) | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid start line number '%s'", option_arg.str().c_str()); | |
| else | |
| m_line_start = line; | |
| m_sym_ctx_specified = true; | |
| break; | |
| } | |
| case 'e': { | |
| uint32_t line; | |
| if (option_arg.getAsInteger(0, line)) | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid end line number '%s'", option_arg.str().c_str()); | |
| else | |
| m_line_end = line; | |
| m_sym_ctx_specified = true; | |
| break; | |
| } | |
| case 'c': | |
| m_class_name = std::string(option_arg); | |
| m_sym_ctx_specified = true; | |
| break; | |
| case 'n': | |
| m_function_name = std::string(option_arg); | |
| 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 = Status::FromErrorStringWithFormat( | |
| "invalid boolean value '%s' passed for -G option", | |
| option_arg.str().c_str()); | |
| break; | |
| } | |
| case 'I': { | |
| bool value, success; | |
| value = OptionArgParser::ToBoolean(option_arg, true, &success); | |
| if (success) | |
| m_at_initial_stop = value; | |
| else | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid boolean value '%s' passed for -I option", | |
| option_arg.str().c_str()); | |
| break; | |
| } | |
| default: | |
| llvm_unreachable("unhandled option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| m_use_one_liner = false; | |
| m_one_liner.clear(); | |
| m_on_load = false; | |
| m_on_unload = false; | |
| m_on_stop = false; | |
| m_sym_ctx_specified = false; | |
| m_thread_specified = false; | |
| m_module_name.clear(); | |
| m_file_name.clear(); | |
| m_class_name.clear(); | |
| m_function_name.clear(); | |
| m_line_start = 0; | |
| m_line_end = UINT_MAX; | |
| m_thread_id = LLDB_INVALID_THREAD_ID; | |
| m_thread_index = UINT32_MAX; | |
| m_thread_name.clear(); | |
| m_queue_name.clear(); | |
| m_auto_continue = false; | |
| m_at_initial_stop = true; | |
| } | |
| std::vector<std::string> m_one_liner; | |
| bool m_use_one_liner = false; | |
| bool m_on_load = false; | |
| bool m_on_unload = false; | |
| bool m_on_stop = false; | |
| // Filter options (for stop trigger). | |
| bool m_sym_ctx_specified = false; | |
| bool m_thread_specified = false; | |
| std::string m_module_name; | |
| std::string m_file_name; | |
| std::string m_class_name; | |
| std::string m_function_name; | |
| uint32_t m_line_start = 0; | |
| uint32_t m_line_end = UINT_MAX; | |
| lldb::tid_t m_thread_id = LLDB_INVALID_THREAD_ID; | |
| uint32_t m_thread_index = UINT32_MAX; | |
| std::string m_thread_name; | |
| std::string m_queue_name; | |
| bool m_auto_continue = false; | |
| bool m_at_initial_stop = true; | |
| }; | |
| CommandObjectTargetHookAdd(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target hook add", | |
| "Add a hook to be executed on target lifecycle events.", | |
| "target hook add", eCommandAllowsDummyTarget), | |
| IOHandlerDelegateMultiline("DONE", | |
| IOHandlerDelegate::Completion::LLDBCommand), | |
| m_python_class_options("scripted hook", false, 'P') { | |
| SetHelpLong(R"help( | |
| Command-based hooks: | |
| -------------------- | |
| Specify which triggers the hook responds to with --on-load (-L), | |
| --on-unload (-u), and/or --on-stop (-S). At least one trigger is required. | |
| Provide commands with --one-liner (-o), or omit -o to enter an interactive | |
| command editor. All commands run for every trigger the hook is signed up | |
| for; there is no per-trigger command list. | |
| Examples: | |
| target hook add -L -o "script print('module loaded')" | |
| target hook add -L -u -o "script print('module event')" | |
| target hook add -S -o "bt" | |
| target hook add -L -u -S -o "script print('all events')" | |
| target hook add -S -s mylib.so -n main -o "bt" | |
| target hook add -S -G true -o "thread info" | |
| Python class hooks: | |
| ------------------- | |
| Provide a Python class with --python-class (-P). The class controls which | |
| events it handles by implementing the corresponding methods; you do not | |
| specify triggers on the command line. Use -k <key> -v <value> to pass | |
| extra_args to the class constructor. | |
| Examples: | |
| target hook add -P mymodule.MyHook | |
| target hook add -P mymodule.MyHook -k verbose -v true | |
| The Python class should implement at least one of these methods: | |
| class MyHook: | |
| def __init__(self, target, extra_args, internal_dict): | |
| self.target = target | |
| def handle_module_loaded(self, stream): | |
| pass | |
| def handle_module_unloaded(self, stream): | |
| pass | |
| def handle_stop(self, exe_ctx, stream): | |
| return True # True = should_stop, False = continue | |
| Filter options: | |
| --------------- | |
| Filters (-s, -f, -l, -e, -c, -n, -x, -t, -T, -q) restrict when the hook | |
| fires. They apply to both command-based and Python class hooks. | |
| )help"); | |
| // Python class options (-P, -k, -v) are placed in Set 2 (dst_mask). | |
| // src_mask must cover Set 1 | Set 2 to match the internal usage masks of | |
| // OptionGroupPythonClassWithDict (class=Set1, key/value=Set2). | |
| // Since -o, -L, -u, -S are Group<1> only, the parser prevents mixing. | |
| m_all_options.Append(&m_python_class_options, | |
| LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_2); | |
| m_all_options.Append(&m_options); | |
| m_all_options.Finalize(); | |
| } | |
| ~CommandObjectTargetHookAdd() override = default; | |
| Options *GetOptions() override { return &m_all_options; } | |
| protected: | |
| void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { | |
| if (interactive) { | |
| if (lldb::LockableStreamFileSP output_sp = | |
| io_handler.GetOutputStreamFileSP()) { | |
| LockedStreamFile locked_stream = output_sp->Lock(); | |
| locked_stream.PutCString( | |
| "Enter your hook command(s). Type 'DONE' to end.\n"); | |
| } | |
| } | |
| } | |
| void IOHandlerInputComplete(IOHandler &io_handler, | |
| std::string &line) override { | |
| if (m_hook_sp) { | |
| if (line.empty()) { | |
| if (lldb::LockableStreamFileSP error_sp = | |
| io_handler.GetErrorStreamFileSP()) { | |
| LockedStreamFile locked_stream = error_sp->Lock(); | |
| locked_stream.Printf("error: hook #%" PRIu64 | |
| " aborted, no commands.\n", | |
| m_hook_sp->GetID()); | |
| } | |
| GetTarget()->UndoCreateHook(m_hook_sp->GetID()); | |
| } else { | |
| auto *hook = static_cast<Target::HookCommandLine *>(m_hook_sp.get()); | |
| hook->SetActionFromString(line); | |
| if (lldb::LockableStreamFileSP output_sp = | |
| io_handler.GetOutputStreamFileSP()) { | |
| LockedStreamFile locked_stream = output_sp->Lock(); | |
| locked_stream.Printf("Hook #%" PRIu64 " added.\n", | |
| m_hook_sp->GetID()); | |
| } | |
| } | |
| m_hook_sp.reset(); | |
| } | |
| io_handler.SetIsDone(true); | |
| } | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| m_hook_sp.reset(); | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| bool is_python_class = !m_python_class_options.GetName().empty(); | |
| // Command-based hooks require at least one explicit trigger. | |
| if (!is_python_class && !m_options.m_on_load && !m_options.m_on_unload && | |
| !m_options.m_on_stop) { | |
| result.AppendError("at least one trigger must be specified: " | |
| "--on-load (-L), --on-unload (-u), or --on-stop (-S)"); | |
| return; | |
| } | |
| Target::Hook::HookKind hook_kind = | |
| is_python_class ? Target::Hook::HookKind::ScriptBased | |
| : Target::Hook::HookKind::CommandBased; | |
| Target::HookSP new_hook_sp = target->CreateHook(hook_kind); | |
| if (!is_python_class) { | |
| // Build trigger mask from explicit command-line flags. | |
| auto *cmd_hook = | |
| static_cast<Target::HookCommandLine *>(new_hook_sp.get()); | |
| uint32_t trigger_mask = 0; | |
| if (m_options.m_on_load) | |
| trigger_mask |= Target::Hook::kModulesLoaded; | |
| if (m_options.m_on_unload) | |
| trigger_mask |= Target::Hook::kModulesUnloaded; | |
| if (m_options.m_on_stop) | |
| trigger_mask |= Target::Hook::kProcessStop; | |
| cmd_hook->SetTriggerMask(trigger_mask); | |
| } | |
| // Python class hooks: triggers are computed in SetScriptCallback based | |
| // on which callback methods the class implements. | |
| // Set up symbol context specifier if filter options were provided. | |
| if (m_options.m_sym_ctx_specified) { | |
| auto specifier_up = | |
| std::make_unique<SymbolContextSpecifier>(target->shared_from_this()); | |
| 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); | |
| new_hook_sp->SetSCSpecifier(specifier_up.release()); | |
| } | |
| // Set up thread specifier. | |
| 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); | |
| new_hook_sp->SetRunAtInitialStop(m_options.m_at_initial_stop); | |
| if (m_options.m_use_one_liner) { | |
| auto *hook = static_cast<Target::HookCommandLine *>(new_hook_sp.get()); | |
| hook->SetActionFromStrings(m_options.m_one_liner); | |
| result.AppendMessageWithFormatv("Hook #{0} added.\n", | |
| new_hook_sp->GetID()); | |
| } else if (!m_python_class_options.GetName().empty()) { | |
| auto *hook = static_cast<Target::HookScripted *>(new_hook_sp.get()); | |
| ScriptedMetadata scripted_metadata( | |
| m_python_class_options.GetName(), | |
| m_python_class_options.GetStructuredData()); | |
| Status callback_error = hook->SetScriptCallback(scripted_metadata); | |
| if (callback_error.Fail()) { | |
| result.AppendErrorWithFormat("couldn't add hook: %s", | |
| callback_error.AsCString()); | |
| target->UndoCreateHook(new_hook_sp->GetID()); | |
| return; | |
| } | |
| result.AppendMessageWithFormatv("Hook #{0} added.\n", | |
| new_hook_sp->GetID()); | |
| } else { | |
| m_hook_sp = new_hook_sp; | |
| m_interpreter.GetLLDBCommandsFromIOHandler("> ", // prompt | |
| *this); // delegate | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| private: | |
| CommandOptions m_options; | |
| OptionGroupPythonClassWithDict m_python_class_options; | |
| OptionGroupOptions m_all_options; | |
| Target::HookSP m_hook_sp; | |
| }; | |
| #pragma mark CommandObjectTargetHookDelete | |
| class CommandObjectTargetHookDelete : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetHookDelete(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "target hook delete", "Delete a hook.", | |
| "target hook delete [<id>]", | |
| eCommandAllowsDummyTarget) { | |
| AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar); | |
| } | |
| ~CommandObjectTargetHookDelete() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| if (command.GetArgumentCount() == 0) { | |
| if (!m_interpreter.Confirm("Delete all hooks?", true)) { | |
| result.SetStatus(eReturnStatusFailed); | |
| return; | |
| } | |
| target->RemoveAllHooks(); | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| return; | |
| } | |
| for (size_t i = 0; i < command.GetArgumentCount(); i++) { | |
| lldb::user_id_t user_id; | |
| if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) { | |
| result.AppendErrorWithFormat("invalid hook id: \"%s\"", | |
| command.GetArgumentAtIndex(i)); | |
| return; | |
| } | |
| if (!target->RemoveHookByID(user_id)) { | |
| result.AppendErrorWithFormat("unknown hook id: \"%s\"", | |
| command.GetArgumentAtIndex(i)); | |
| return; | |
| } | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| }; | |
| #pragma mark CommandObjectTargetHookEnableDisable | |
| class CommandObjectTargetHookEnableDisable : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetHookEnableDisable(CommandInterpreter &interpreter, | |
| bool enable, const char *name, | |
| const char *help, const char *syntax) | |
| : CommandObjectParsed(interpreter, name, help, syntax, | |
| eCommandAllowsDummyTarget), | |
| m_enable(enable) { | |
| AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar); | |
| } | |
| ~CommandObjectTargetHookEnableDisable() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| // No IDs = apply to all hooks. | |
| if (command.GetArgumentCount() == 0) { | |
| target->SetAllHooksEnabledState(m_enable); | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| return; | |
| } | |
| for (size_t i = 0; i < command.GetArgumentCount(); i++) { | |
| lldb::user_id_t user_id; | |
| if (!llvm::to_integer(command.GetArgumentAtIndex(i), user_id)) { | |
| result.AppendErrorWithFormat("invalid hook id: \"%s\"", | |
| command.GetArgumentAtIndex(i)); | |
| return; | |
| } | |
| if (!target->SetHookEnabledStateByID(user_id, m_enable)) { | |
| result.AppendErrorWithFormat("unknown hook id: \"%s\"", | |
| command.GetArgumentAtIndex(i)); | |
| return; | |
| } | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| private: | |
| bool m_enable; | |
| }; | |
| #pragma mark CommandObjectTargetHookModify | |
| #define LLDB_OPTIONS_target_hook_modify | |
| #include "CommandOptions.inc" | |
| /// Modify trigger settings on a hook. Only valid for command-based hooks; | |
| /// scripted hooks derive their triggers from the class methods. | |
| class CommandObjectTargetHookModify : public CommandObjectParsed { | |
| public: | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() = default; | |
| ~CommandOptions() override = default; | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_target_hook_modify_options); | |
| } | |
| Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, | |
| ExecutionContext *execution_context) override { | |
| Status error; | |
| const int short_option = | |
| g_target_hook_modify_options[option_idx].short_option; | |
| switch (short_option) { | |
| case 'e': | |
| m_enable_trigger = option_arg.str(); | |
| break; | |
| case 'd': | |
| m_disable_trigger = option_arg.str(); | |
| break; | |
| default: | |
| llvm_unreachable("unhandled option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| m_enable_trigger.clear(); | |
| m_disable_trigger.clear(); | |
| } | |
| std::string m_enable_trigger; | |
| std::string m_disable_trigger; | |
| }; | |
| CommandObjectTargetHookModify(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "target hook modify", | |
| "Modify trigger settings on a hook.", | |
| "target hook modify [--enable-trigger <name>] " | |
| "[--disable-trigger <name>] [<id>]", | |
| eCommandAllowsDummyTarget) { | |
| AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatOptional); | |
| SetHelpLong(R"help( | |
| Modify trigger settings on command-based hooks. Scripted hooks derive their | |
| triggers from the class methods and cannot be modified. | |
| If no hook ID is given, the last added hook is modified. | |
| Valid trigger names: load, unload, stop. | |
| Examples: | |
| target hook modify --enable-trigger stop 1 | |
| target hook modify --disable-trigger load 1 | |
| target hook modify --enable-trigger stop (modifies last added hook) | |
| )help"); | |
| } | |
| ~CommandObjectTargetHookModify() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| protected: | |
| static uint32_t ParseTriggerName(llvm::StringRef name) { | |
| if (name == "load") | |
| return Target::Hook::kModulesLoaded; | |
| if (name == "unload") | |
| return Target::Hook::kModulesUnloaded; | |
| if (name == "stop") | |
| return Target::Hook::kProcessStop; | |
| return 0; | |
| } | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| if (m_options.m_enable_trigger.empty() && | |
| m_options.m_disable_trigger.empty()) { | |
| result.AppendError("at least one of --enable-trigger or " | |
| "--disable-trigger must be specified"); | |
| return; | |
| } | |
| // Resolve the hook ID. Default to last added if not specified. | |
| Target::HookSP hook_sp; | |
| if (command.GetArgumentCount() == 0) { | |
| size_t num_hooks = target->GetNumHooks(); | |
| if (num_hooks == 0) { | |
| result.AppendError("no hooks exist"); | |
| return; | |
| } | |
| hook_sp = target->GetHookAtIndex(num_hooks - 1); | |
| } else { | |
| lldb::user_id_t user_id; | |
| if (!llvm::to_integer(command.GetArgumentAtIndex(0), user_id)) { | |
| result.AppendErrorWithFormat("invalid hook id: \"%s\"", | |
| command.GetArgumentAtIndex(0)); | |
| return; | |
| } | |
| hook_sp = target->GetHookByID(user_id); | |
| if (!hook_sp) { | |
| result.AppendErrorWithFormat("unknown hook id: \"%s\"", | |
| command.GetArgumentAtIndex(0)); | |
| return; | |
| } | |
| } | |
| // Reject trigger modification on scripted hooks. | |
| if (hook_sp->GetHookKind() != Target::Hook::HookKind::CommandBased) { | |
| result.AppendError("cannot modify triggers on a scripted hook; " | |
| "triggers are determined by the class methods"); | |
| return; | |
| } | |
| auto *cmd_hook = static_cast<Target::HookCommandLine *>(hook_sp.get()); | |
| if (!m_options.m_enable_trigger.empty()) { | |
| uint32_t trigger = ParseTriggerName(m_options.m_enable_trigger); | |
| if (!trigger) { | |
| result.AppendErrorWithFormat("unknown trigger name: \"%s\". " | |
| "Valid names: load, unload, stop", | |
| m_options.m_enable_trigger.c_str()); | |
| return; | |
| } | |
| cmd_hook->AddTrigger(trigger); | |
| } | |
| if (!m_options.m_disable_trigger.empty()) { | |
| uint32_t trigger = ParseTriggerName(m_options.m_disable_trigger); | |
| if (!trigger) { | |
| result.AppendErrorWithFormat("unknown trigger name: \"%s\". " | |
| "Valid names: load, unload, stop", | |
| m_options.m_disable_trigger.c_str()); | |
| return; | |
| } | |
| cmd_hook->RemoveTrigger(trigger); | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| private: | |
| CommandOptions m_options; | |
| }; | |
| #pragma mark CommandObjectTargetHookList | |
| class CommandObjectTargetHookList : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetHookList(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "target hook list", "List all hooks.", | |
| "target hook list", eCommandAllowsDummyTarget) {} | |
| ~CommandObjectTargetHookList() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| size_t num_hooks = target->GetNumHooks(); | |
| if (num_hooks == 0) { | |
| result.GetOutputStream().PutCString("No hooks.\n"); | |
| } else { | |
| for (size_t i = 0; i < num_hooks; i++) { | |
| Target::HookSP hook_sp = target->GetHookAtIndex(i); | |
| if (hook_sp) | |
| hook_sp->GetDescription(result.GetOutputStream(), | |
| eDescriptionLevelFull); | |
| } | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| #pragma mark CommandObjectMultiwordTargetHooks | |
| class CommandObjectMultiwordTargetHooks : public CommandObjectMultiword { | |
| public: | |
| CommandObjectMultiwordTargetHooks(CommandInterpreter &interpreter) | |
| : CommandObjectMultiword( | |
| interpreter, "target hook", | |
| "Commands for operating on target hooks.", | |
| "target hook <subcommand> [<subcommand-options>]") { | |
| LoadSubCommand( | |
| "add", CommandObjectSP(new CommandObjectTargetHookAdd(interpreter))); | |
| LoadSubCommand("delete", CommandObjectSP(new CommandObjectTargetHookDelete( | |
| interpreter))); | |
| LoadSubCommand("disable", | |
| CommandObjectSP(new CommandObjectTargetHookEnableDisable( | |
| interpreter, false, "target hook disable", | |
| "Disable a hook.", "target hook disable [<id> ...]"))); | |
| LoadSubCommand("enable", | |
| CommandObjectSP(new CommandObjectTargetHookEnableDisable( | |
| interpreter, true, "target hook enable", | |
| "Enable a hook.", "target hook enable [<id> ...]"))); | |
| LoadSubCommand( | |
| "list", CommandObjectSP(new CommandObjectTargetHookList(interpreter))); | |
| LoadSubCommand("modify", CommandObjectSP(new CommandObjectTargetHookModify( | |
| interpreter))); | |
| } | |
| ~CommandObjectMultiwordTargetHooks() override = default; | |
| }; | |
| #pragma mark CommandObjectTargetDumpTypesystem | |
| /// Dumps the TypeSystem of the selected Target. | |
| class CommandObjectTargetDumpTypesystem : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetDumpTypesystem(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target dump typesystem", | |
| "Dump the state of the target's internal type system. Intended to " | |
| "be used for debugging LLDB itself.", | |
| nullptr, eCommandRequiresTarget) {} | |
| ~CommandObjectTargetDumpTypesystem() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| // Go over every scratch TypeSystem and dump to the command output. | |
| for (lldb::TypeSystemSP ts : GetTarget()->GetScratchTypeSystems()) | |
| if (ts) | |
| ts->Dump(result.GetOutputStream().AsRawOstream(), "", | |
| GetCommandInterpreter().GetDebugger().GetUseColor()); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| #pragma mark CommandObjectTargetDumpSectionLoadList | |
| /// Dumps the SectionLoadList of the selected Target. | |
| class CommandObjectTargetDumpSectionLoadList : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetDumpSectionLoadList(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target dump section-load-list", | |
| "Dump the state of the target's internal section load list. " | |
| "Intended to be used for debugging LLDB itself.", | |
| nullptr, eCommandRequiresTarget) {} | |
| ~CommandObjectTargetDumpSectionLoadList() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| target->DumpSectionLoadList(result.GetOutputStream()); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| #pragma mark CommandObjectTargetDump | |
| /// Multi-word command for 'target dump'. | |
| class CommandObjectTargetDump : public CommandObjectMultiword { | |
| public: | |
| // Constructors and Destructors | |
| CommandObjectTargetDump(CommandInterpreter &interpreter) | |
| : CommandObjectMultiword( | |
| interpreter, "target dump", | |
| "Commands for dumping information about the target.", | |
| "target dump [typesystem|section-load-list]") { | |
| LoadSubCommand( | |
| "typesystem", | |
| CommandObjectSP(new CommandObjectTargetDumpTypesystem(interpreter))); | |
| LoadSubCommand("section-load-list", | |
| CommandObjectSP(new CommandObjectTargetDumpSectionLoadList( | |
| interpreter))); | |
| } | |
| ~CommandObjectTargetDump() override = default; | |
| }; | |
| #pragma mark CommandObjectTargetFrameProvider | |
| #define LLDB_OPTIONS_target_frame_provider_register | |
| #include "CommandOptions.inc" | |
| class CommandObjectTargetFrameProviderRegister : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetFrameProviderRegister(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target frame-provider register", | |
| "Register frame provider for all threads in this target.", nullptr, | |
| eCommandRequiresTarget), | |
| m_class_options("target frame-provider", true, 'C', 'k', 'v', 0) { | |
| m_all_options.Append(&m_class_options, LLDB_OPT_SET_1 | LLDB_OPT_SET_2, | |
| LLDB_OPT_SET_ALL); | |
| m_all_options.Finalize(); | |
| } | |
| ~CommandObjectTargetFrameProviderRegister() override = default; | |
| Options *GetOptions() override { return &m_all_options; } | |
| std::optional<std::string> GetRepeatCommand(Args ¤t_command_args, | |
| uint32_t index) override { | |
| return std::string(""); | |
| } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| ScriptedMetadataSP metadata_sp = std::make_shared<ScriptedMetadata>( | |
| m_class_options.GetName(), m_class_options.GetStructuredData()); | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| if (!target) | |
| target = &GetDebugger().GetDummyTarget(); | |
| // Create the interface for calling static methods. | |
| ScriptedFrameProviderInterfaceSP interface_sp = | |
| GetDebugger() | |
| .GetScriptInterpreter() | |
| ->CreateScriptedFrameProviderInterface(); | |
| // Create a descriptor from the metadata (applies to all threads by | |
| // default). | |
| ScriptedFrameProviderDescriptor descriptor(metadata_sp); | |
| descriptor.interface_sp = interface_sp; | |
| auto id_or_err = target->AddScriptedFrameProviderDescriptor(descriptor); | |
| if (!id_or_err) { | |
| result.SetError(id_or_err.takeError()); | |
| return; | |
| } | |
| result.AppendMessageWithFormatv( | |
| "successfully registered scripted frame provider '{0}' for target", | |
| m_class_options.GetName().c_str()); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| OptionGroupPythonClassWithDict m_class_options; | |
| OptionGroupOptions m_all_options; | |
| }; | |
| class CommandObjectTargetFrameProviderClear : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetFrameProviderClear(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target frame-provider clear", | |
| "Clear all registered frame providers from this target.", nullptr, | |
| eCommandRequiresTarget) {} | |
| ~CommandObjectTargetFrameProviderClear() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| if (!target) { | |
| result.AppendError("invalid target"); | |
| return; | |
| } | |
| target->ClearScriptedFrameProviderDescriptors(); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| class CommandObjectTargetFrameProviderList : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetFrameProviderList(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target frame-provider list", | |
| "List all registered frame providers for the target.", nullptr, | |
| eCommandRequiresTarget) {} | |
| ~CommandObjectTargetFrameProviderList() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| if (!target) | |
| target = &GetDebugger().GetDummyTarget(); | |
| const auto &descriptors = target->GetScriptedFrameProviderDescriptors(); | |
| if (descriptors.empty()) { | |
| result.AppendMessage("no frame providers registered for this target."); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| return; | |
| } | |
| Stream &strm = result.GetOutputStream(); | |
| strm << llvm::formatv("{0} frame provider(s) registered:\n\n", | |
| descriptors.size()); | |
| for (const auto &entry : descriptors) { | |
| const ScriptedFrameProviderDescriptor &descriptor = entry.second; | |
| descriptor.Dump(&strm); | |
| strm.PutChar('\n'); | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| class CommandObjectTargetFrameProviderRemove : public CommandObjectParsed { | |
| public: | |
| CommandObjectTargetFrameProviderRemove(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "target frame-provider remove", | |
| "Remove a registered frame provider from the target by id.", | |
| "target frame-provider remove <provider-id>", | |
| eCommandRequiresTarget) { | |
| AddSimpleArgumentList(eArgTypeUnsignedInteger, eArgRepeatPlus); | |
| } | |
| ~CommandObjectTargetFrameProviderRemove() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = m_exe_ctx.GetTargetPtr(); | |
| if (!target) | |
| target = &GetDebugger().GetDummyTarget(); | |
| std::vector<uint32_t> removed_provider_ids; | |
| for (size_t i = 0; i < command.GetArgumentCount(); i++) { | |
| uint32_t provider_id = 0; | |
| if (!llvm::to_integer(command[i].ref(), provider_id)) { | |
| result.AppendError("target frame-provider remove requires integer " | |
| "provider id argument"); | |
| return; | |
| } | |
| if (!target->RemoveScriptedFrameProviderDescriptor(provider_id)) { | |
| result.AppendErrorWithFormat( | |
| "no frame provider named '%u' found in target", provider_id); | |
| return; | |
| } | |
| removed_provider_ids.push_back(provider_id); | |
| } | |
| if (size_t num_removed_providers = removed_provider_ids.size()) { | |
| result.AppendMessageWithFormatv( | |
| "Successfully removed {0} frame-providers.", num_removed_providers); | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } else { | |
| result.AppendError("0 frame providers removed.\n"); | |
| } | |
| } | |
| }; | |
| class CommandObjectTargetFrameProvider : public CommandObjectMultiword { | |
| public: | |
| CommandObjectTargetFrameProvider(CommandInterpreter &interpreter) | |
| : CommandObjectMultiword( | |
| interpreter, "target frame-provider", | |
| "Commands for registering and viewing frame providers for the " | |
| "target.", | |
| "target frame-provider [<sub-command-options>] ") { | |
| LoadSubCommand("register", | |
| CommandObjectSP(new CommandObjectTargetFrameProviderRegister( | |
| interpreter))); | |
| LoadSubCommand("clear", | |
| CommandObjectSP( | |
| new CommandObjectTargetFrameProviderClear(interpreter))); | |
| LoadSubCommand( | |
| "list", | |
| CommandObjectSP(new CommandObjectTargetFrameProviderList(interpreter))); | |
| LoadSubCommand( | |
| "remove", CommandObjectSP( | |
| new CommandObjectTargetFrameProviderRemove(interpreter))); | |
| } | |
| ~CommandObjectTargetFrameProvider() 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("dump", | |
| CommandObjectSP(new CommandObjectTargetDump(interpreter))); | |
| LoadSubCommand( | |
| "frame-provider", | |
| CommandObjectSP(new CommandObjectTargetFrameProvider(interpreter))); | |
| LoadSubCommand("list", | |
| CommandObjectSP(new CommandObjectTargetList(interpreter))); | |
| LoadSubCommand("select", | |
| CommandObjectSP(new CommandObjectTargetSelect(interpreter))); | |
| LoadSubCommand("show-launch-environment", | |
| CommandObjectSP(new CommandObjectTargetShowLaunchEnvironment( | |
| interpreter))); | |
| LoadSubCommand( | |
| "stop-hook", | |
| CommandObjectSP(new CommandObjectMultiwordTargetStopHooks(interpreter))); | |
| LoadSubCommand("hook", CommandObjectSP(new CommandObjectMultiwordTargetHooks( | |
| interpreter))); | |
| LoadSubCommand("modules", | |
| CommandObjectSP(new CommandObjectTargetModules(interpreter))); | |
| LoadSubCommand("symbols", | |
| CommandObjectSP(new CommandObjectTargetSymbols(interpreter))); | |
| LoadSubCommand("variable", | |
| CommandObjectSP(new CommandObjectTargetVariable(interpreter))); | |
| } | |
| CommandObjectMultiwordTarget::~CommandObjectMultiwordTarget() = default; |