https://github.com/jimingham updated https://github.com/llvm/llvm-project/pull/156067
>From fb2e2dffcb6472711b1d009a77849cfc76fbbfa0 Mon Sep 17 00:00:00 2001 From: Jim Ingham <jing...@apple.com> Date: Thu, 28 Aug 2025 15:50:47 -0700 Subject: [PATCH] breakpoint add - all subcommands implemented. Testsuite clean but no new tests. --- .../Interpreter/CommandOptionArgumentTable.h | 17 + .../Interpreter/OptionValueFileColonLine.h | 4 + lldb/include/lldb/lldb-enumerations.h | 18 + .../Python/lldbsuite/test/lldbutil.py | 58 +- .../Commands/CommandObjectBreakpoint.cpp | 1447 +++++++++++++++-- lldb/source/Commands/Options.td | 126 ++ lldb/source/Interpreter/Options.cpp | 4 +- .../command-wrong-subcommand-error-msg.test | 2 +- 8 files changed, 1571 insertions(+), 105 deletions(-) diff --git a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h index 4face717531b1..8ae074c5314b3 100644 --- a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h +++ b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h @@ -154,6 +154,21 @@ static constexpr OptionEnumValueElement g_running_mode[] = { "Run only this thread while stepping"}, }; +static constexpr OptionEnumValueElement g_exception_stage[] = { + {lldb::eExceptionStageThrow, "throw", "Stop when the exception is thrown."}, + {lldb::eExceptionStageReThrow, "re-throw", "Stop when the exception is re-thrown."}, + {lldb::eExceptionStageCatch, "catch", "Stop when the exception is caught."}, +}; + +static constexpr OptionEnumValueElement g_name_match_style[] = { + {lldb::eNameMatchStyleAuto, "auto", "Match against the leaf nodes of the identifier, or against methods or selectors."}, + {lldb::eNameMatchStyleFull, "full", "Match the full identifier name."}, + {lldb::eNameMatchStyleBase, "base", "Match against the leaf node of the identifier."}, + {lldb::eNameMatchStyleMethod, "method", "Match only against method names."}, + {lldb::eNameMatchStyleSelector, "selector", "Match only against selector names."}, + {lldb::eNameMatchStyleRegex, "regex", "Match the identifier using a regular expression."}, +}; + static constexpr OptionEnumValueElement g_completion_type[] = { {lldb::eNoCompletion, "none", "No completion."}, {lldb::eSourceFileCompletion, "source-file", "Completes to a source file."}, @@ -316,6 +331,8 @@ static constexpr CommandObject::ArgumentTableEntry g_argument_table[] = { { lldb::eArgTypeCPUFeatures, "cpu-features", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The CPU feature string." }, { lldb::eArgTypeManagedPlugin, "managed-plugin", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "Plugins managed by the PluginManager" }, { lldb::eArgTypeProtocol, "protocol", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of the protocol." }, + { lldb::eArgTypeExceptionStage, "exception-stage", lldb::CompletionType::eNoCompletion, g_exception_stage, { nullptr, false }, "Specify at which stage of the exception raise to stop." }, + { lldb::eArgTypeNameMatchStyle, "match-style", lldb::CompletionType::eNoCompletion, g_name_match_style, { nullptr, false }, "Specify the kind of match to use when looking up names." }, // clang-format on }; diff --git a/lldb/include/lldb/Interpreter/OptionValueFileColonLine.h b/lldb/include/lldb/Interpreter/OptionValueFileColonLine.h index 70f035da649e7..edfaab8b27e98 100644 --- a/lldb/include/lldb/Interpreter/OptionValueFileColonLine.h +++ b/lldb/include/lldb/Interpreter/OptionValueFileColonLine.h @@ -40,6 +40,10 @@ class OptionValueFileColonLine : m_line_number = LLDB_INVALID_LINE_NUMBER; m_column_number = LLDB_INVALID_COLUMN_NUMBER; } + + void SetFile(const FileSpec &file_spec) { m_file_spec = file_spec; } + void SetLine(uint32_t line) { m_line_number = line; } + void SetColumn(uint32_t column) { m_column_number = column; } void AutoComplete(CommandInterpreter &interpreter, CompletionRequest &request) override; diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index fec9fdef44df9..3e68101e36d05 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -668,6 +668,8 @@ enum CommandArgumentType { eArgTypeCPUFeatures, eArgTypeManagedPlugin, eArgTypeProtocol, + eArgTypeExceptionStage, + eArgTypeNameMatchStyle, eArgTypeLastArg // Always keep this entry as the last entry in this // enumeration!! }; @@ -1399,6 +1401,22 @@ enum StopDisassemblyType { eStopDisassemblyTypeAlways }; +enum ExceptionStage { + eExceptionStageCreate = (1 << 0), + eExceptionStageThrow = (1 << 1), + eExceptionStageReThrow = (1 << 2), + eExceptionStageCatch = (1 << 3) +}; + +enum NameMatchStyle { + eNameMatchStyleAuto = eFunctionNameTypeAuto, + eNameMatchStyleFull = eFunctionNameTypeFull, + eNameMatchStyleBase = eFunctionNameTypeBase, + eNameMatchStyleMethod = eFunctionNameTypeMethod, + eNameMatchStyleSelector = eFunctionNameTypeSelector, + eNameMatchStyleRegex = eFunctionNameTypeSelector << 1 +}; + } // namespace lldb #endif // LLDB_LLDB_ENUMERATIONS_H diff --git a/lldb/packages/Python/lldbsuite/test/lldbutil.py b/lldb/packages/Python/lldbsuite/test/lldbutil.py index b8a78b71f5ec1..929d6915b4255 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbutil.py +++ b/lldb/packages/Python/lldbsuite/test/lldbutil.py @@ -319,13 +319,24 @@ def sort_stopped_threads( # Utility functions for setting breakpoints # ================================================== +g_use_break_add = True +def set_use_break_add(use_it): + global g_use_break_add + g_use_break_add = use_it + +def get_use_break_add(): + global g_use_break_add + return g_use_break_add def run_break_set_by_script( test, class_name, extra_options=None, num_expected_locations=1 ): """Set a scripted breakpoint. Check that it got the right number of locations.""" test.assertTrue(class_name is not None, "Must pass in a class name.") - command = "breakpoint set -P " + class_name + if get_use_break_add(): + command = f"breakpoint add scripted -P {class_name}" + else: + command = "breakpoint set -P " + class_name if extra_options is not None: command += " " + extra_options @@ -333,7 +344,6 @@ def run_break_set_by_script( check_breakpoint_result(test, break_results, num_locations=num_expected_locations) return get_bpno_from_match(break_results) - def run_break_set_by_file_and_line( test, file_name, @@ -353,10 +363,16 @@ def run_break_set_by_file_and_line( If loc_exact is true, we check that there is one location, and that location must be at the input file and line number. """ - if file_name is None: - command = "breakpoint set -l %d" % (line_number) + if get_use_break_add(): + if file_name is None: + command = f"breakpoint add file {line_number} " + else: + command = f"breakpoint add file -f {file_name} -l {line_number} " else: - command = 'breakpoint set -f "%s" -l %d' % (file_name, line_number) + if file_name is None: + command = "breakpoint set -l %d" % (line_number) + else: + command = 'breakpoint set -f "%s" -l %d' % (file_name, line_number) if module_name: command += " --shlib '%s'" % (module_name) @@ -395,7 +411,10 @@ def run_break_set_by_symbol( If sym_exact is true, then the output symbol must match the input exactly, otherwise we do a substring match. """ - command = 'breakpoint set -n "%s"' % (symbol) + if get_use_break_add(): + command = f"breakpoint add name" + else: + command = 'breakpoint set -n "%s"' % (symbol) if module_name: command += " --shlib '%s'" % (module_name) @@ -403,6 +422,9 @@ def run_break_set_by_symbol( if extra_options: command += " " + extra_options + if get_use_break_add(): + command += f" -- '{symbol}'" + break_results = run_break_set_command(test, command) if num_expected_locations == 1 and sym_exact: @@ -426,7 +448,10 @@ def run_break_set_by_selector( ): """Set a breakpoint by selector. Common options are the same as run_break_set_by_file_and_line.""" - command = 'breakpoint set -S "%s"' % (selector) + if get_use_break_add(): + command = f"breakpoint add name --match-style selector '{selector}'" + else: + command = 'breakpoint set -S "%s"' % (selector) if module_name: command += ' --shlib "%s"' % (module_name) @@ -458,7 +483,10 @@ def run_break_set_by_regexp( ): """Set a breakpoint by regular expression match on symbol name. Common options are the same as run_break_set_by_file_and_line.""" - command = 'breakpoint set -r "%s"' % (regexp) + if get_use_break_add(): + command = f"breakpoint add name --match-style regex '{regexp}'" + else: + command = 'breakpoint set -r "%s"' % (regexp) if extra_options: command += " " + extra_options @@ -473,10 +501,16 @@ def run_break_set_by_source_regexp( test, regexp, extra_options=None, num_expected_locations=-1 ): """Set a breakpoint by source regular expression. Common options are the same as run_break_set_by_file_and_line.""" - command = 'breakpoint set -p "%s"' % (regexp) + if get_use_break_add(): + command = "breakpoint add pattern" + else: + command = 'breakpoint set -p "%s"' % (regexp) if extra_options: command += " " + extra_options + if get_use_break_add(): + command += f" -- {regexp}" + break_results = run_break_set_command(test, command) check_breakpoint_result(test, break_results, num_locations=num_expected_locations) @@ -493,7 +527,11 @@ def run_break_set_by_file_colon_line( extra_options=None, num_expected_locations=-1, ): - command = 'breakpoint set -y "%s"' % (specifier) + if get_use_break_add(): + command = f"breakpoint add file '{specifier}'" + else: + command = 'breakpoint set -y "%s"' % (specifier) + if extra_options: command += " " + extra_options diff --git a/lldb/source/Commands/CommandObjectBreakpoint.cpp b/lldb/source/Commands/CommandObjectBreakpoint.cpp index 38ec375c03070..4306289e7b924 100644 --- a/lldb/source/Commands/CommandObjectBreakpoint.cpp +++ b/lldb/source/Commands/CommandObjectBreakpoint.cpp @@ -200,41 +200,1361 @@ class lldb_private::BreakpointOptionGroup : public OptionGroup { BreakpointOptions m_bp_opts; }; +// This is the Breakpoint Names option group - used to add Names to breakpoints +// while making them. Not to be confused with the "Breakpoint Name" option +// group which is the common options of various "breakpoint name" commands. +#define LLDB_OPTIONS_breakpoint_names +#include "CommandOptions.inc" + +class BreakpointNamesOptionGroup : public OptionGroup { +public: + BreakpointNamesOptionGroup() = default; + + ~BreakpointNamesOptionGroup() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return g_breakpoint_names_options; + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + const int short_option = + GetDefinitions()[option_idx].short_option; + const char *long_option = + GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'N': + if (BreakpointID::StringIsBreakpointName(option_value, error)) + m_breakpoint_names.push_back(std::string(option_value)); + else + error = Status::FromError( + CreateOptionParsingError(option_value, short_option, + long_option, "Invalid breakpoint name")); + break; + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_breakpoint_names.clear(); + } + + const std::vector<std::string> &GetBreakpointNames() { + return m_breakpoint_names; + } + +protected: + std::vector<std::string> m_breakpoint_names; +}; + #define LLDB_OPTIONS_breakpoint_dummy #include "CommandOptions.inc" -class BreakpointDummyOptionGroup : public OptionGroup { +class BreakpointDummyOptionGroup : public OptionGroup { +public: + BreakpointDummyOptionGroup() = default; + + ~BreakpointDummyOptionGroup() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_dummy_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = + g_breakpoint_dummy_options[option_idx].short_option; + + switch (short_option) { + case 'D': + m_use_dummy = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_use_dummy = false; + } + + bool m_use_dummy; +}; + +#pragma mark AddAddress::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_address +#include "CommandOptions.inc" + +#pragma mark Add Address + +static bool CopyOverBreakpointOptions(BreakpointSP bp_sp, + BreakpointOptionGroup &bp_opts, + const std::vector<std::string> &bp_names, + CommandReturnObject &result) { + assert(bp_sp && "CopyOverBreakpointOptions called with no breakpoint"); + + bp_sp->GetOptions().CopyOverSetOptions(bp_opts.GetBreakpointOptions()); + Target &target = bp_sp->GetTarget(); + if (!bp_names.empty()) { + Status name_error; + for (auto name : bp_names) { + target.AddNameToBreakpoint(bp_sp, name.c_str(), name_error); + if (name_error.Fail()) { + result.AppendErrorWithFormat("Invalid breakpoint name: %s", + name.c_str()); + target.RemoveBreakpointByID(bp_sp->GetID()); + return false; + } + } + } + return true; +} + +static llvm::Expected<LanguageType> GetExceptionLanguageForLanguage( + llvm::StringRef lang_name, char short_option = '\0', + llvm::StringRef long_option = {}) { + LanguageType language = Language::GetLanguageTypeFromString(lang_name); + LanguageType exception_language = eLanguageTypeUnknown; + + llvm::StringRef error_context; + switch (language) { + case eLanguageTypeC89: + case eLanguageTypeC: + case eLanguageTypeC99: + case eLanguageTypeC11: + exception_language = eLanguageTypeC; + break; + case eLanguageTypeC_plus_plus: + case eLanguageTypeC_plus_plus_03: + case eLanguageTypeC_plus_plus_11: + case eLanguageTypeC_plus_plus_14: + exception_language = eLanguageTypeC_plus_plus; + break; + case eLanguageTypeObjC_plus_plus: + error_context = + "Set exception breakpoints separately for c++ and objective-c"; + break; + case eLanguageTypeUnknown: + error_context = "Unknown language type for exception breakpoint"; + break; + default: + if (Language *languagePlugin = Language::FindPlugin(language)) { + if (languagePlugin->SupportsExceptionBreakpointsOnThrow() || + languagePlugin->SupportsExceptionBreakpointsOnCatch()) { + exception_language = language; + break; + } + } + error_context = "Unsupported language type for exception breakpoint"; + } + if (!error_context.empty()) + return CreateOptionParsingError( + lang_name, short_option, long_option, error_context); + return exception_language; +} + +static bool GetDefaultFile(ExecutionContext exe_ctx, FileSpec &file, + std::string &error_msg) { + // First use the Source Manager's default file. Then use the current stack + // frame's file. + if (!exe_ctx.HasTargetScope()) { + error_msg = "Can't get a default file with no target."; + return false; + } + Target &target = exe_ctx.GetTargetRef(); + + if (auto maybe_file_and_line = + target.GetSourceManager().GetDefaultFileAndLine()) { + file = maybe_file_and_line->support_file_sp->GetSpecOnly(); + return true; + } + + StackFrame *cur_frame = exe_ctx.GetFramePtr(); + if (cur_frame == nullptr) { + error_msg = + "No selected frame to use to find the default file."; + return false; + } + if (!cur_frame->HasDebugInformation()) { + error_msg = "Cannot use the selected frame to find the default " + "file, it has no debug info."; + return false; + } + + const SymbolContext &sc = + cur_frame->GetSymbolContext(eSymbolContextLineEntry); + if (sc.line_entry.GetFile()) { + file = sc.line_entry.GetFile(); + } else { + error_msg = "Can't find the file for the selected frame to " + "use as the default file."; + return false; + } + return true; +} + +static bool GetDefaultFile(ExecutionContext exe_ctx, FileSpec &file, + CommandReturnObject &result) { + std::string error_msg; + if (!GetDefaultFile(exe_ctx, file, error_msg)) { + result.AppendError(error_msg); + return false; + } + return true; +} + +static Status CompleteLineEntry(ExecutionContext &exe_ctx, + OptionValueFileColonLine &line_entry) { + Status error; + uint32_t line_num = line_entry.GetLineNumber(); + if (!line_entry.GetFileSpec()) { + FileSpec default_file_spec; + std::string error_msg; + if (!GetDefaultFile(exe_ctx, default_file_spec, error_msg)) { + error.FromErrorStringWithFormatv("Couldn't get default file for " + "line {0}: {1}", line_num, error_msg); + return error; + } + line_entry.SetFile(default_file_spec); + } + return error; +} + +class CommandObjectBreakpointAddAddress : public CommandObjectParsed { +public: + CommandObjectBreakpointAddAddress(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint add address", + "Add breakpoints by raw address", + nullptr) { + CommandArgumentData bp_id_arg; + + // Define the first (and only) variant of this arg. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Finalize(); + + AddSimpleArgumentList(eArgTypeAddress, eArgRepeatPlus); + } + + ~CommandObjectBreakpointAddAddress() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + 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 = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'H': + m_hardware = true; + break; + + case 's': + if (m_modules.GetSize() == 0) + m_modules.AppendIfUnique(FileSpec(option_arg)); + else + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + "Only one shared library can be " + "specified for address breakpoints.")); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_modules.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_address_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; //FIXME - this can go in the "modify" options. + FileSpecList m_modules; + + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + // We've already asserted that there can only be one entry in m_modules: + const ExecutionContext &exe_ctx = m_interpreter.GetExecutionContext(); + // We don't set address breakpoints in the dummy target. + if (!exe_ctx.HasTargetScope() || exe_ctx.GetTargetPtr()->IsDummyTarget()) { + result.AppendError("can't set address breakpoints without a real target."); + return; + } + // Commands can't set internal breakpoints: + const bool internal = false; + + Target &target = exe_ctx.GetTargetRef(); + + FileSpec module_spec; + bool has_module = false; + if (m_options.m_modules.GetSize() != 0) { + has_module = true; + module_spec = m_options.m_modules.GetFileSpecAtIndex(0); + } + BreakpointSP bp_sp; + // Let's process the arguments first so we can short-circuit if there are + // any errors: + std::vector<lldb::addr_t> bp_addrs; + for (const Args::ArgEntry &arg_entry : command) { + Address bp_address; + Status error; + lldb::addr_t bp_load_addr = OptionArgParser::ToAddress(&exe_ctx, arg_entry.ref(), + LLDB_INVALID_ADDRESS, &error); + if (error.Fail()) { + result.AppendErrorWithFormatv("invalid argument value '{0}': {1}", + arg_entry.ref(), error); + return; + } + bp_addrs.push_back(bp_load_addr); + } + for (auto bp_addr : bp_addrs) { + if (has_module) + bp_sp = target.CreateAddressInModuleBreakpoint( + bp_addr, internal, module_spec, m_options.m_hardware); + else + // ENHANCEMENT: we should see if bp_addr is in a single loaded module, + // and pass that module in if it is. + bp_sp = target.CreateBreakpoint(bp_addr, internal, + m_options.m_hardware); + } + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), + result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/ false); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddException::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_exception +#include "CommandOptions.inc" + +#pragma mark Add Exception + +class CommandObjectBreakpointAddException : public CommandObjectParsed { +public: + CommandObjectBreakpointAddException(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint add exception", + "Add breakpoints on language exceptions. If no language is " + "specified, break on exceptions for all supported languages", + nullptr) { + // Define the first (and only) variant of this arg. + AddSimpleArgumentList(eArgTypeLanguage, eArgRepeatStar); + + // Next add all the options. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddException() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + 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 = GetDefinitions()[option_idx].short_option; + + switch (short_option) { + case 'E': { + uint32_t this_val = (uint32_t)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, + eExceptionStageThrow, error); + if (error.Fail()) + return error; + m_exception_stage |= this_val; + } + break; + case 'H': + m_hardware = true; + break; + + case 'O': + m_exception_extra_args.AppendArgument("-O"); + m_exception_extra_args.AppendArgument(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_exception_extra_args.Clear(); + m_exception_stage = eExceptionStageThrow; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_exception_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; //FIXME - this can go in the "modify" options. + Args m_exception_extra_args; + uint32_t m_exception_stage = eExceptionStageThrow; + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + BreakpointSP bp_sp; + LanguageType exception_language = eLanguageTypeUnknown; + + if (command.size() == 0) { + result.AppendError("no languages specified."); + } else if (command.size() > 1) { + result.AppendError("can only set exception breakpoints on one language at a time."); + } else { + llvm::Expected<LanguageType> language + = GetExceptionLanguageForLanguage(command[0].ref()); + if (language) + exception_language = *language; + else { + result.SetError(language.takeError()); + return; + } + } + Status precond_error; + const bool internal = false; + bool catch_bp = (m_options.m_exception_stage & eExceptionStageCatch) != 0; + bool throw_bp = (m_options.m_exception_stage & eExceptionStageThrow) != 0; + bp_sp = target.CreateExceptionBreakpoint( + exception_language, catch_bp, + throw_bp, internal, &m_options.m_exception_extra_args, + &precond_error); + if (precond_error.Fail()) { + result.AppendErrorWithFormat( + "Error setting extra exception arguments: %s", + precond_error.AsCString()); + target.RemoveBreakpointByID(bp_sp->GetID()); + return; + } + + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), + result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/ false); + // Note, we don't print a "got no locations" warning for exception + // breakpoints. They can get set in the dummy target, and we won't know + // how to actually set the breakpoint till we know what version of the + // relevant LanguageRuntime gets loaded. + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddFile::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_file +#include "CommandOptions.inc" + +#pragma mark Add File + +class CommandObjectBreakpointAddFile : public CommandObjectParsed { +public: + CommandObjectBreakpointAddFile(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint add file", + "Add breakpoints on lines in specified source files", + nullptr) { + CommandArgumentEntry arg1; + CommandArgumentData linespec_arg; + CommandArgumentData no_arg; + + // Any number of linespecs in group 1: + linespec_arg.arg_type = eArgTypeFileLineColumn; + linespec_arg.arg_repetition = eArgRepeatPlus; + linespec_arg.arg_opt_set_association = LLDB_OPT_SET_1; + + arg1.push_back(linespec_arg); + + // Leave arg2 empty, there are no arguments to this variant. + CommandArgumentEntry arg2; + no_arg.arg_type = eArgTypeNone; + no_arg.arg_repetition = eArgRepeatOptional; + no_arg.arg_opt_set_association = LLDB_OPT_SET_2; + + arg2.push_back(linespec_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + + // Define the first (and only) variant of this arg. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1|LLDB_OPT_SET_2); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1|LLDB_OPT_SET_2); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddFile() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + 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 = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'f': + m_cur_value.SetFile(FileSpec(option_arg)); + break; + case 'l': + uint32_t line_num; + if (option_arg.getAsInteger(0, line_num)) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_int_parsing_error_message)); + else { + // The line number is the only required part of the options for a + // specifying the location - since we will fill in the file with the + // default file. So when we see a new line, the old line entry we + // were building is done. If we haven't gotten a file, try to fill + // in the default file, and then finish up this linespec and start + // the next one. + if (m_cur_value.GetLineNumber() != LLDB_INVALID_LINE_NUMBER) { + // FIXME: It should be possible to create a breakpoint with a list + // of file, line, column values. But for now we can only create one, + // so return an error here. The commented out code is what we + // will do when I come back to add that capability. + return Status::FromErrorString("Can only specify one file and line " + "pair at a time."); +#if 0 // This code will be appropriate once we have a resolver that can take + // more than one linespec at a time. + error = CompleteLineEntry(*execution_context, m_cur_value); + if (error.Fail()) + return error; + + m_line_specs.push_back(m_cur_value); + m_cur_value.Clear(); +#endif + } + m_cur_value.SetLine(line_num); + + } + break; + case 'c': + uint32_t column_num; + if (option_arg.getAsInteger(0, column_num)) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_int_parsing_error_message)); + else + m_cur_value.SetColumn(column_num); + break; + case 'K': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (value) + m_skip_prologue = eLazyBoolYes; + else + m_skip_prologue = eLazyBoolNo; + + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + } break; + case 'm': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (value) + m_move_to_nearest_code = eLazyBoolYes; + else + m_move_to_nearest_code = eLazyBoolNo; + + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + } break; + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + case 'H': + m_hardware = true; + break; + case 'S': { + lldb::addr_t tmp_offset_addr; + tmp_offset_addr = OptionArgParser::ToAddress(execution_context, + option_arg, 0, &error); + if (error.Success()) + m_offset_addr = tmp_offset_addr; + } break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_line_specs.clear(); + m_cur_value.Clear(); + m_skip_prologue = eLazyBoolCalculate; + m_modules.Clear(); + m_move_to_nearest_code = eLazyBoolCalculate; + m_offset_addr = 0; + } + + Status OptionParsingFinished(ExecutionContext *execution_context) override + { + // We were supplied at least a line from the options, so fill in the + // default file if needed. + if (m_cur_value.GetLineNumber() != LLDB_INVALID_LINE_NUMBER) { + Status error = CompleteLineEntry(*execution_context, m_cur_value); + if (error.Fail()) + return error; + m_line_specs.push_back(m_cur_value); + m_cur_value.Clear(); + } + return {}; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_file_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; //FIXME - this can go in the "modify" options. + std::vector<OptionValueFileColonLine> m_line_specs; + LazyBool m_skip_prologue = eLazyBoolCalculate; + OptionValueFileColonLine m_cur_value; + FileSpecList m_modules; + LazyBool m_move_to_nearest_code = eLazyBoolCalculate; + lldb::addr_t m_offset_addr = 0; + + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + bool internal = false; + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + // FIXME: At present we can only make file & line breakpoints for one file + // and line pair. It wouldn't be hard to extend that, but I'm not adding + // features at this point so I'll leave that for a future patch. For now, + // flag this as an error. + + // I'm leaving this as a loop since that's how it should be when we can + // do more than one linespec at a time. + FileSpec default_file; + for (const Args::ArgEntry &this_arg : command) { + OptionValueFileColonLine value; + uint32_t line_value = LLDB_INVALID_LINE_NUMBER; + if (!this_arg.ref().getAsInteger(0, line_value)) { + // The argument is a plain number. Treat that as a line number, and + // allow it if we can find a default file & line. + std::string error_msg; + if (!GetDefaultFile(m_exe_ctx, default_file, error_msg)) { + result.AppendErrorWithFormatv("Couldn't find default file for line " + "input: {0} - {1}", line_value, error_msg); + return; + } + value.SetLine(line_value); + value.SetFile(default_file); + } else { + Status error = value.SetValueFromString(this_arg.c_str()); + if (error.Fail()) { + result.AppendErrorWithFormatv("Failed to parse linespec: {0}", error); + return; + } + } + m_options.m_line_specs.push_back(value); + } + + if (m_options.m_line_specs.size() != 1) { + result.AppendError("Can only make file and line breakpoints with one " + "specification at a time."); + return; + } + + BreakpointSP bp_sp; + // Only check for inline functions if + LazyBool check_inlines = eLazyBoolCalculate; + + OptionValueFileColonLine &this_spec = m_options.m_line_specs[0]; + bp_sp = target.CreateBreakpoint( + &(m_options.m_modules), this_spec.GetFileSpec(), this_spec.GetLineNumber(), + this_spec.GetColumnNumber(), m_options.m_offset_addr, check_inlines, + m_options.m_skip_prologue, internal, m_options.m_hardware, + m_options.m_move_to_nearest_code); + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), + result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/ false); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + // Don't print out this warning for exception breakpoints. They can + // get set before the target is set, but we won't know how to actually + // set the breakpoint till we run. + if (bp_sp->GetNumLocations() == 0) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddName::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_name +#include "CommandOptions.inc" + +#pragma mark Add Name + +class CommandObjectBreakpointAddName : public CommandObjectParsed { +public: + CommandObjectBreakpointAddName(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint add name", + "Add breakpoints matching function or symbol names", + nullptr) { + // FIXME: Add a completer that's aware of the name match style. + // Define the first (and only) variant of this arg. + AddSimpleArgumentList(eArgTypeFunctionOrSymbol, eArgRepeatPlus); + + // Now add all the options groups. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddName() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + 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 = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'f': + m_files.AppendIfUnique(FileSpec(option_arg)); + break; + case 'K': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + else { + if (value) + m_skip_prologue = eLazyBoolYes; + else + m_skip_prologue = eLazyBoolNo; + } + } break; + case 'L': { + m_language = Language::GetLanguageTypeFromString(option_arg); + if (m_language == eLanguageTypeUnknown) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_language_parsing_error_message)); + } + break; + case 'm': { + uint32_t this_val = (uint32_t)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, + eNameMatchStyleAuto, error); + if (error.Fail()) + return error; + m_lookup_style = (NameMatchStyle) this_val; + } + break; + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + case 'H': + m_hardware = true; + break; + case 'S': { + lldb::addr_t tmp_offset_addr; + tmp_offset_addr = OptionArgParser::ToAddress(execution_context, + option_arg, 0, &error); + if (error.Success()) + m_offset_addr = tmp_offset_addr; + } break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_skip_prologue = eLazyBoolCalculate; + m_files.Clear(); + m_language = eLanguageTypeUnknown; + m_modules.Clear(); + m_offset_addr = 0; + m_lookup_style = eNameMatchStyleAuto; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_name_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; //FIXME - this can go in the "modify" options. + LazyBool m_skip_prologue = eLazyBoolCalculate; + FileSpecList m_modules; + LanguageType m_language = eLanguageTypeUnknown; + FileSpecList m_files; + LazyBool m_move_to_nearest_code = eLazyBoolCalculate; + lldb::addr_t m_offset_addr = 0; + NameMatchStyle m_lookup_style = eNameMatchStyleAuto; + + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + const bool internal = false; + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + // Parse the argument list - this is a simple list of names. + std::vector<std::string> func_names; + for (const Args::ArgEntry &this_arg : command) { + func_names.push_back(this_arg.ref().str()); + } + BreakpointSP bp_sp; + if (!(m_options.m_lookup_style&eNameMatchStyleRegex)) + bp_sp = target.CreateBreakpoint( + &m_options.m_modules, &m_options.m_files, func_names, + (FunctionNameType) m_options.m_lookup_style, + m_options.m_language, m_options.m_offset_addr, m_options.m_skip_prologue, + internal, m_options.m_hardware); + else { + if (func_names.size() != 1) { + result.AppendError("Can only set function regular expression " + "breakpoints on one regex at a time."); + return; + } + std::string &func_regexp = func_names[0]; + RegularExpression regexp(func_regexp); + if (llvm::Error err = regexp.GetError()) { + result.AppendErrorWithFormat( + "Function name regular expression could not be compiled: %s", + llvm::toString(std::move(err)).c_str()); + // Check if the incorrect regex looks like a globbing expression and + // warn the user about it. + if (!func_regexp.empty()) { + if (func_regexp[0] == '*' || + func_regexp[0] == '?') + result.AppendWarning( + "Function name regex does not accept glob patterns."); + } + return; + } + + bp_sp = target.CreateFuncRegexBreakpoint( + &(m_options.m_modules), &(m_options.m_files), std::move(regexp), + m_options.m_language, m_options.m_skip_prologue, internal, + m_options.m_hardware); + } + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), + result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/ false); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + if (bp_sp->GetNumLocations() == 0) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddPattern::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_pattern +#include "CommandOptions.inc" + +#pragma mark Add Pattern + +class CommandObjectBreakpointAddPattern : public CommandObjectRaw { +public: + CommandObjectBreakpointAddPattern(CommandInterpreter &interpreter) + : CommandObjectRaw( + interpreter, "breakpoint add pattern", + "Add breakpoints matching patterns in the source text", + "breakpoint add pattern [options] -- <pattern>") { + AddSimpleArgumentList(eArgTypeRegularExpression, eArgRepeatPlain); + // Now add all the options groups. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddPattern() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + 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 = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'a': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + else + m_all_files = value; + } break; + case 'f': + m_files.AppendIfUnique(FileSpec(option_arg)); + break; + case 'm': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + else { + if (value) + m_move_to_nearest_code = eLazyBoolYes; + else + m_move_to_nearest_code = eLazyBoolNo; + } + } break; + case 'n': + m_func_names.insert(option_arg.str()); + break; + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + case 'H': + m_hardware = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_skip_prologue = eLazyBoolCalculate; + m_modules.Clear(); + m_files.Clear(); + m_func_names.clear(); + m_all_files = false; + m_move_to_nearest_code = eLazyBoolCalculate; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_pattern_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; //FIXME - this can go in the "modify" options. + LazyBool m_skip_prologue = eLazyBoolCalculate; + FileSpecList m_modules; + FileSpecList m_files; + std::unordered_set<std::string> m_func_names; + bool m_all_files = false; + LazyBool m_move_to_nearest_code = eLazyBoolCalculate; + + }; + +protected: + void DoExecute(llvm::StringRef command, CommandReturnObject &result) override { + const bool internal = false; + ExecutionContext exe_ctx = GetCommandInterpreter().GetExecutionContext(); + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + m_all_options.NotifyOptionParsingStarting(&exe_ctx); + + if (command.empty()) { + result.AppendError("no pattern to seek."); + return; + } + + OptionsWithRaw args(command); + llvm::StringRef expr = args.GetRawPart(); + + if (args.HasArgs()) { + if (!ParseOptionsAndNotify(args.GetArgs(), result, m_all_options, exe_ctx)) + return; + } + llvm::StringRef pattern = args.GetRawPart(); + if (pattern.empty()) { + result.AppendError("no pattern to seek"); + return; + } + printf("Pattern: '%s'\n", pattern.str().c_str()); + + BreakpointSP bp_sp; + const size_t num_files = m_options.m_files.GetSize(); + + if (num_files == 0 && !m_options.m_all_files) { + FileSpec file; + if (!GetDefaultFile(m_exe_ctx, file, result)) { + result.AppendError( + "No files provided and could not find default file."); + return; + } else { + m_options.m_files.Append(file); + } + } + + RegularExpression regexp(pattern); + if (llvm::Error err = regexp.GetError()) { + result.AppendErrorWithFormat( + "Source text regular expression could not be compiled: \"%s\"", + llvm::toString(std::move(err)).c_str()); + return; + } + bp_sp = target.CreateSourceRegexBreakpoint( + &(m_options.m_modules), &(m_options.m_files), + m_options.m_func_names, std::move(regexp), internal, + m_options.m_hardware, m_options.m_move_to_nearest_code); + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), + result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/ false); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + // Don't print out this warning for exception breakpoints. They can + // get set before the target is set, but we won't know how to actually + // set the breakpoint till we run. + if (bp_sp->GetNumLocations() == 0) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddScripted::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_scripted +#include "CommandOptions.inc" + +#pragma mark Add Scripted + +class CommandObjectBreakpointAddScripted : public CommandObjectParsed { public: - BreakpointDummyOptionGroup() = default; + CommandObjectBreakpointAddScripted(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint add scripted", + "Add breakpoints using a scripted breakpoint resolver.", + nullptr), + m_python_class_options("scripted breakpoint", true, 'P') { + // We're picking up all the normal options, commands and disable. + m_all_options.Append(&m_python_class_options, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_1); + // Define the first (and only) variant of this arg. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } - ~BreakpointDummyOptionGroup() override = default; + ~CommandObjectBreakpointAddScripted() override = default; - llvm::ArrayRef<OptionDefinition> GetDefinitions() override { - return llvm::ArrayRef(g_breakpoint_dummy_options); - } + Options *GetOptions() override { return &m_all_options; } - Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, - ExecutionContext *execution_context) override { - Status error; - const int short_option = - g_breakpoint_dummy_options[option_idx].short_option; + class CommandOptions : public OptionGroup { + public: + CommandOptions() = default; - switch (short_option) { - case 'D': - m_use_dummy = true; - break; - default: - llvm_unreachable("Unimplemented option"); + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + + switch (short_option) { + case 'f': + m_files.Append(FileSpec(option_arg)); + break; + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + case 'H': + m_hardware = true; + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; } - return error; - } + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_files.Clear(); + m_modules.Clear(); + } + - void OptionParsingStarting(ExecutionContext *execution_context) override { - m_use_dummy = false; + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_scripted_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; //FIXME - this can go in the "modify" options. + FileSpecList m_files; + FileSpecList m_modules; + + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + + BreakpointSP bp_sp; + Status error; + bp_sp = target.CreateScriptedBreakpoint( + m_python_class_options.GetName().c_str(), &(m_options.m_modules), + &(m_options.m_files), false, m_options.m_hardware, + m_python_class_options.GetStructuredData(), &error); + if (error.Fail()) { + result.AppendErrorWithFormat( + "Error setting extra exception arguments: %s", error.AsCString()); + target.RemoveBreakpointByID(bp_sp->GetID()); + return; + } + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), + result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/ false); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + // Don't print out this warning for exception breakpoints. They can + // get set before the target is set, but we won't know how to actually + // set the breakpoint till we run. + if (bp_sp->GetNumLocations() == 0) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } } - bool m_use_dummy; +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + OptionGroupPythonClassWithDict m_python_class_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark Add::CommandOptions +#define LLDB_OPTIONS_breakpoint_add +#include "CommandOptions.inc" + +#pragma mark Add + +class CommandObjectBreakpointAdd : public CommandObjectMultiword { +public: + CommandObjectBreakpointAdd(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "add", "Commands to add breakpoints of various types") { + SetHelpLong( + R"( +Access the breakpoint search kernels built into lldb. Along with specifying the +search kernel, each breakpoint add operation can specify a common set of +"reaction" options for each breakpoint. The reaction options can also be +modified after breakpoint creation using the "breakpoint modify" command. + )"); + CommandObjectSP address_command_object( + new CommandObjectBreakpointAddAddress(interpreter)); + CommandObjectSP exception_command_object( + new CommandObjectBreakpointAddException(interpreter)); + CommandObjectSP file_command_object( + new CommandObjectBreakpointAddFile(interpreter)); + CommandObjectSP name_command_object( + new CommandObjectBreakpointAddName(interpreter)); + CommandObjectSP pattern_command_object( + new CommandObjectBreakpointAddPattern(interpreter)); + CommandObjectSP scripted_command_object( + new CommandObjectBreakpointAddScripted(interpreter)); + + LoadSubCommand("address", address_command_object); + LoadSubCommand("exception", exception_command_object); + LoadSubCommand("file", file_command_object); + LoadSubCommand("name", name_command_object); + LoadSubCommand("pattern", pattern_command_object); + LoadSubCommand("scripted", scripted_command_object); + } }; #define LLDB_OPTIONS_breakpoint_set @@ -313,42 +1633,12 @@ class CommandObjectBreakpointSet : public CommandObjectParsed { break; case 'E': { - LanguageType language = Language::GetLanguageTypeFromString(option_arg); - - llvm::StringRef error_context; - switch (language) { - case eLanguageTypeC89: - case eLanguageTypeC: - case eLanguageTypeC99: - case eLanguageTypeC11: - m_exception_language = eLanguageTypeC; - break; - case eLanguageTypeC_plus_plus: - case eLanguageTypeC_plus_plus_03: - case eLanguageTypeC_plus_plus_11: - case eLanguageTypeC_plus_plus_14: - m_exception_language = eLanguageTypeC_plus_plus; - break; - case eLanguageTypeObjC_plus_plus: - error_context = - "Set exception breakpoints separately for c++ and objective-c"; - break; - case eLanguageTypeUnknown: - error_context = "Unknown language type for exception breakpoint"; - break; - default: - if (Language *languagePlugin = Language::FindPlugin(language)) { - if (languagePlugin->SupportsExceptionBreakpointsOnThrow() || - languagePlugin->SupportsExceptionBreakpointsOnCatch()) { - m_exception_language = language; - break; - } - } - error_context = "Unsupported language type for exception breakpoint"; - } - if (!error_context.empty()) - error = Status::FromError(CreateOptionParsingError( - option_arg, short_option, long_option, error_context)); + llvm::Expected<LanguageType> language = + GetExceptionLanguageForLanguage(option_arg, short_option, long_option); + if (language) + m_exception_language = *language; + else + error = Status::FromError(language.takeError()); } break; case 'f': @@ -608,7 +1898,7 @@ class CommandObjectBreakpointSet : public CommandObjectParsed { FileSpec file; const size_t num_files = m_options.m_filenames.GetSize(); if (num_files == 0) { - if (!GetDefaultFile(target, file, result)) { + if (!GetDefaultFile(m_exe_ctx, file, result)) { result.AppendError("No file supplied and no default file available."); return; } @@ -694,7 +1984,7 @@ class CommandObjectBreakpointSet : public CommandObjectParsed { if (num_files == 0 && !m_options.m_all_files) { FileSpec file; - if (!GetDefaultFile(target, file, result)) { + if (!GetDefaultFile(m_exe_ctx, file, result)) { result.AppendError( "No files provided and could not find default file."); return; @@ -789,39 +2079,6 @@ class CommandObjectBreakpointSet : public CommandObjectParsed { } private: - bool GetDefaultFile(Target &target, FileSpec &file, - CommandReturnObject &result) { - // First use the Source Manager's default file. Then use the current stack - // frame's file. - if (auto maybe_file_and_line = - target.GetSourceManager().GetDefaultFileAndLine()) { - file = maybe_file_and_line->support_file_sp->GetSpecOnly(); - return true; - } - - StackFrame *cur_frame = m_exe_ctx.GetFramePtr(); - if (cur_frame == nullptr) { - result.AppendError( - "No selected frame to use to find the default file."); - return false; - } - if (!cur_frame->HasDebugInformation()) { - result.AppendError("Cannot use the selected frame to find the default " - "file, it has no debug info."); - return false; - } - - const SymbolContext &sc = - cur_frame->GetSymbolContext(eSymbolContextLineEntry); - if (sc.line_entry.GetFile()) { - file = sc.line_entry.GetFile(); - } else { - result.AppendError("Can't find the file for the selected frame to " - "use as the default file."); - return false; - } - return true; - } BreakpointOptionGroup m_bp_opts; BreakpointDummyOptionGroup m_dummy_options; @@ -2410,6 +3667,8 @@ CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint( new CommandObjectBreakpointDelete(interpreter)); CommandObjectSP set_command_object( new CommandObjectBreakpointSet(interpreter)); + CommandObjectSP add_command_object( + new CommandObjectBreakpointAdd(interpreter)); CommandObjectSP command_command_object( new CommandObjectBreakpointCommand(interpreter)); CommandObjectSP modify_command_object( @@ -2427,6 +3686,7 @@ CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint( clear_command_object->SetCommandName("breakpoint clear"); delete_command_object->SetCommandName("breakpoint delete"); set_command_object->SetCommandName("breakpoint set"); + add_command_object->SetCommandName("breakpoint add"); command_command_object->SetCommandName("breakpoint command"); modify_command_object->SetCommandName("breakpoint modify"); name_command_object->SetCommandName("breakpoint name"); @@ -2439,6 +3699,7 @@ CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint( LoadSubCommand("clear", clear_command_object); LoadSubCommand("delete", delete_command_object); LoadSubCommand("set", set_command_object); + LoadSubCommand("add", add_command_object); LoadSubCommand("command", command_command_object); LoadSubCommand("modify", modify_command_object); LoadSubCommand("name", name_command_object); diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 3dbf65b0c02ff..cc6bd3d8c5aad 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -126,6 +126,14 @@ let Command = "breakpoint dummy" in { "provided, which prime new targets.">; } +let Command = "breakpoint names" in { + def breakpoint_name_option_names : + Option<"breakpoint-name", "N">, Group<1>, + Arg<"BreakpointName">, + Desc<"Adds this name to the list of names for this breakpoint. " + "Can be specified more than once.">; +} + let Command = "breakpoint set" in { def breakpoint_set_shlib : Option<"shlib", "s">, Arg<"ShlibName">, Completion<"Module">, Groups<[1,2,3,4,5,6,7,8,9,11,12]>, // *not* in group 10 @@ -235,6 +243,124 @@ let Command = "breakpoint set" in { */ } +let Command = "breakpoint add address" in { + def breakpoint_add_address_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Set the breakpoint at an address relative to sections in this shared" + " library.">; + def breakpoint_add_address_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; +} + +let Command = "breakpoint add exception" in { + def breakpoint_add_exception_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_exception_typename : Option<"exception-typename", "O">, + Arg<"TypeName">, Desc<"The breakpoint will only stop if an " + "exception Object of this type is thrown. Can be repeated multiple times " + "to stop for multiple object types">; + def breakpoint_add_exception_stage : Option<"exception-stage", "E">, + Arg<"ExceptionStage">, + Desc<"Stop only at the specified exception stage. Can be specified more " + "than once to create a mask of stages.">; +} + +let Command = "breakpoint add file" in { + def breakpoint_add_file_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_file_line : Option<"line", "l">, Group<2>, Arg<"LineNum">, + Required, + Desc<"Specifies the line number on which to set this breakpoint.">; + def breakpoint_add_file_filename : Option<"filename", "f">, Group<2>, + Arg<"Filename">, Completion<"SourceFile">, Desc<"The file in which to seek " + "the specified source line.">; + def breakpoint_add_file_column : Option<"column", "u">, Arg<"ColumnNum">, Group<2>, + Desc<"Specifies the column number on which to set this breakpoint.">; + def breakpoint_add_file_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Set the breakpoint only in this shared library. Can repeat this " + "option multiple times to specify multiple shared libraries.">; + def breakpoint_add_file_move_to_nearest_code : Option<"move-to-nearest-code", "m">, + Arg<"Boolean">, + Desc<"Move breakpoints to nearest code. If not set the " + "target.move-to-nearest-code setting is used.">; + def breakpoint_add_file_address_slide : Option<"address-slide", "S">, + Arg<"Offset">, + Desc<"Add the specified offset to whatever address(es) the breakpoint " + "resolves to. At present this applies the offset directly as given, and " + "doesn't try to align it to instruction boundaries.">; + def breakpoint_add_file_skip_prologue : Option<"skip-prologue", "K">, + Arg<"Boolean">, + Desc<"Skip the prologue if the breakpoint is at the beginning of a " + "function. If not set the target.skip-prologue setting is used.">; +} + +let Command = "breakpoint add name" in { + def breakpoint_add_name_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_name_address_slide : Option<"address-slide", "S">, + Arg<"Offset">, + Desc<"Add the specified offset to whatever address(es) the breakpoint " + "resolves to. At present this applies the offset directly as given, and " + "doesn't try to align it to instruction boundaries.">; + def breakpoint_add_name_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Search for names only in this shared library. Can repeat this " + "option multiple times to specify multiple shared libraries.">; + def breakpoint_add_name_filename : Option<"filename", "f">, + Arg<"Filename">, Completion<"SourceFile">, Desc<"Only search for functions " + "defined in the given source file. Can be specified more than once.">; + def breakpoint_add_name_skip_prologue : Option<"skip-prologue", "K">, + Arg<"Boolean">, + Desc<"Skip the prologue if the breakpoint is at the beginning of a " + "function. If not set the target.skip-prologue setting is used.">; + def breakpoint_add_name_match_style : Option<"match-style", "m">, + Arg<"NameMatchStyle">, + Desc<"The style of matching to do when looking for candidate symbols - " + "auto if not specified.">; + def breakpoint_add_name_language : Option<"name-language", "L">, + Arg<"Language">, + Desc<"Only consider indentifiers from the given language when looking for " + "match candidates.">; +} + +let Command = "breakpoint add pattern" in { + def breakpoint_add_pattern_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_pattern_move_to_nearest_code : Option<"move-to-nearest-code", "m">, + Arg<"Boolean">, + Desc<"Move breakpoints to nearest code. If not set the " + "target.move-to-nearest-code setting is used.">; + def breakpoint_add_pattern_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Search for pattern matches only in this shared library. Can repeat this " + "option multiple times to specify multiple shared libraries.">; + def breakpoint_add_pattern_filename : Option<"filename", "f">, Group<1>, + Arg<"Filename">, Completion<"SourceFile">, Desc<"Limit the pattern search " + "to the specified source file. Can be specified more than once.">; + def breakpoint_add_pattern_name : Option<"name", "n">, Arg<"FunctionName">, + Completion<"Symbol">, Group<1>, + Desc<"Search for pattern matches only in functions matching the specified " + "function name using the 'auto' match style. Can be specified more than " + "once, and composes with the filename option.">; + def breakpoint_add_pattern_all_files : Option<"all-files", "a">, Group<2>, + Desc<"All files are searched for source pattern matches, limited by the " + "shlib argument.">; +} + +let Command = "breakpoint add scripted" in { + def breakpoint_add_scripted_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_scripted_filename : Option<"filename", "f">, + Arg<"Filename">, Completion<"SourceFile">, Desc<"The files in which to apply " + "the scripted resolver callback. Can repeat the option multiple times.">; + def breakpoint_add_scripted_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"The module in which to apply the scripted resolver callback. Can " + "repeat this option multiple times to specify multiple shared libraries.">; +} + + let Command = "breakpoint clear" in { def breakpoint_clear_file : Option<"file", "f">, Group<1>, Arg<"Filename">, Completion<"SourceFile">, diff --git a/lldb/source/Interpreter/Options.cpp b/lldb/source/Interpreter/Options.cpp index ec725428483ff..d0e978b7f740c 100644 --- a/lldb/source/Interpreter/Options.cpp +++ b/lldb/source/Interpreter/Options.cpp @@ -1401,7 +1401,9 @@ llvm::Error lldb_private::CreateOptionParsingError( llvm::StringRef long_option, llvm::StringRef additional_context) { std::string buffer; llvm::raw_string_ostream stream(buffer); - stream << "Invalid value ('" << option_arg << "') for -" << short_option; + stream << "Invalid value ('" << option_arg << "')"; + if (short_option) + stream << " for -" << short_option; if (!long_option.empty()) stream << " (" << long_option << ")"; if (!additional_context.empty()) diff --git a/lldb/test/Shell/Commands/command-wrong-subcommand-error-msg.test b/lldb/test/Shell/Commands/command-wrong-subcommand-error-msg.test index 445f8d1c8361c..b2dd64446221c 100644 --- a/lldb/test/Shell/Commands/command-wrong-subcommand-error-msg.test +++ b/lldb/test/Shell/Commands/command-wrong-subcommand-error-msg.test @@ -4,5 +4,5 @@ # RUN: not %lldb -b -o 'breakpoint foo' %t.out -o exit 2>&1 | FileCheck %s --check-prefix BP-MSG # RUN: not %lldb -b -o 'watchpoint set foo' %t.out -o exit 2>&1 | FileCheck %s --check-prefix WP-MSG # CHECK: at main.c:2:21 -# BP-MSG: "foo" is not a valid subcommand of "breakpoint". Valid subcommands are: clear, command, delete, disable, enable, and others. Use "help breakpoint" to find out more. +# BP-MSG: "foo" is not a valid subcommand of "breakpoint". Valid subcommands are: add, clear, command, delete, disable, enable, and others. Use "help breakpoint" to find out more. # WP-MSG: "foo" is not a valid subcommand of "watchpoint set". Valid subcommands are: expression, variable. Use "help watchpoint set" to find out more. \ No newline at end of file _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits