/* Everything about catch/throw catchpoints, for GDB. Copyright (C) 1986-2023 Free Software Foundation, Inc. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "defs.h" #include "arch-utils.h" #include #include "breakpoint.h" #include "gdbcmd.h" #include "inferior.h" #include "annotate.h" #include "valprint.h" #include "cli/cli-utils.h" #include "completer.h" #include "gdbsupport/gdb_obstack.h" #include "mi/mi-common.h" #include "linespec.h" #include "probe.h" #include "objfiles.h" #include "cp-abi.h" #include "gdbsupport/gdb_regex.h" #include "cp-support.h" #include "location.h" #include "cli/cli-decode.h" /* Each spot where we may place an exception-related catchpoint has two names: the SDT probe point and the function name. This structure holds both. */ struct exception_names { /* The name of the probe point to try, in the form accepted by 'parse_probes'. */ const char *probe; /* The name of the corresponding function. */ const char *function; }; /* Names of the probe points and functions on which to break. This is indexed by exception_event_kind. */ static const struct exception_names exception_functions[] = { { "-probe-stap libstdcxx:throw", "__cxa_throw" }, { "-probe-stap libstdcxx:rethrow", "__cxa_rethrow" }, { "-probe-stap libstdcxx:catch", "__cxa_begin_catch" } }; /* The type of an exception catchpoint. Unlike most catchpoints, this one is implemented with code breakpoints, so it inherits struct code_breakpoint, not struct catchpoint. */ struct exception_catchpoint : public code_breakpoint { exception_catchpoint (struct gdbarch *gdbarch, bool temp, const char *cond_string_, enum exception_event_kind kind_, std::string &&except_rx) : code_breakpoint (gdbarch, bp_catchpoint, temp, cond_string_), kind (kind_), exception_rx (std::move (except_rx)), pattern (exception_rx.empty () ? nullptr : new compiled_regex (exception_rx.c_str (), REG_NOSUB, _("invalid type-matching regexp"))) { pspace = current_program_space; re_set (); } void re_set () override; enum print_stop_action print_it (const bpstat *bs) const override; bool print_one (bp_location **) const override; void print_mention () const override; void print_recreate (struct ui_file *fp) const override; void print_one_detail (struct ui_out *) const override; void check_status (struct bpstat *bs) override; struct bp_location *allocate_location () override; /* FIXME this is temporary - until ordinary breakpoints have been converted. */ int resources_needed (const struct bp_location *) override { return 1; } /* The kind of exception catchpoint. */ enum exception_event_kind kind; /* If not empty, a string holding the source form of the regular expression to match against. */ std::string exception_rx; /* If non-NULL, a compiled regular expression which is used to determine which exceptions to stop on. */ std::unique_ptr pattern; }; /* See breakpoint.h. */ bool is_exception_catchpoint (breakpoint *bp) { return dynamic_cast (bp) != nullptr; } /* A helper function that fetches exception probe arguments. This fills in *ARG0 (if non-NULL) and *ARG1 (which must be non-NULL). It will throw an exception on any kind of failure. */ static void fetch_probe_arguments (struct value **arg0, struct value **arg1) { frame_info_ptr frame = get_selected_frame (_("No frame selected")); CORE_ADDR pc = get_frame_pc (frame); struct bound_probe pc_probe; unsigned n_args; pc_probe = find_probe_by_pc (pc); if (pc_probe.prob == NULL) error (_("did not find exception probe (does libstdcxx have SDT probes?)")); if (pc_probe.prob->get_provider () != "libstdcxx" || (pc_probe.prob->get_name () != "catch" && pc_probe.prob->get_name () != "throw" && pc_probe.prob->get_name () != "rethrow")) error (_("not stopped at a C++ exception catchpoint")); n_args = pc_probe.prob->get_argument_count (get_frame_arch (frame)); if (n_args < 2) error (_("C++ exception catchpoint has too few arguments")); if (arg0 != NULL) *arg0 = pc_probe.prob->evaluate_argument (0, frame); *arg1 = pc_probe.prob->evaluate_argument (1, frame); if ((arg0 != NULL && *arg0 == NULL) || *arg1 == NULL) error (_("error computing probe argument at c++ exception catchpoint")); } /* Implement the 'check_status' method. */ void exception_catchpoint::check_status (struct bpstat *bs) { struct exception_catchpoint *self = (struct exception_catchpoint *) bs->breakpoint_at; std::string type_name; this->breakpoint::check_status (bs); if (bs->stop == 0) return; if (self->pattern == NULL) return; const char *name = nullptr; gdb::unique_xmalloc_ptr canon; try { struct value *typeinfo_arg; fetch_probe_arguments (NULL, &typeinfo_arg); type_name = cplus_typename_from_type_info (typeinfo_arg); canon = cp_canonicalize_string (type_name.c_str ()); name = (canon != nullptr ? canon.get () : type_name.c_str ()); } catch (const gdb_exception_error &e) { exception_print (gdb_stderr, e); } if (name != nullptr) { if (self->pattern->exec (name, 0, NULL, 0) != 0) bs->stop = 0; } } /* Implement the 're_set' method. */ void exception_catchpoint::re_set () { std::vector sals; struct program_space *filter_pspace = current_program_space; /* We first try to use the probe interface. */ try { location_spec_up locspec = new_probe_location_spec (exception_functions[kind].probe); sals = parse_probes (locspec.get (), filter_pspace, NULL); } catch (const gdb_exception_error &e) { /* Using the probe interface failed. Let's fallback to the normal catchpoint mode. */ try { location_spec_up locspec = (new_explicit_location_spec_function (exception_functions[kind].function)); sals = this->decode_location_spec (locspec.get (), filter_pspace); } catch (const gdb_exception_error &ex) { /* NOT_FOUND_ERROR just means the breakpoint will be pending, so let it through. */ if (ex.error != NOT_FOUND_ERROR) throw; } } update_breakpoint_locations (this, filter_pspace, sals, {}); } enum print_stop_action exception_catchpoint::print_it (const bpstat *bs) const { struct ui_out *uiout = current_uiout; int bp_temp; annotate_catchpoint (number); maybe_print_thread_hit_breakpoint (uiout); bp_temp = disposition == disp_del; uiout->text (bp_temp ? "Temporary catchpoint " : "Catchpoint "); print_num_locno (bs, uiout); uiout->text ((kind == EX_EVENT_THROW ? " (exception thrown), " : (kind == EX_EVENT_CATCH ? " (exception caught), " : " (exception rethrown), "))); if (uiout->is_mi_like_p ()) { uiout->field_string ("reason", async_reason_lookup (EXEC_ASYNC_BREAKPOINT_HIT)); uiout->field_string ("disp", bpdisp_text (disposition)); } return PRINT_SRC_AND_LOC; } bool exception_catchpoint::print_one (bp_location **last_loc) const { struct value_print_options opts; struct ui_out *uiout = current_uiout; get_user_print_options (&opts); if (opts.addressprint) uiout->field_skip ("addr"); annotate_field (5); switch (kind) { case EX_EVENT_THROW: uiout->field_string ("what", "exception throw"); if (uiout->is_mi_like_p ()) uiout->field_string ("catch-type", "throw"); break; case EX_EVENT_RETHROW: uiout->field_string ("what", "exception rethrow"); if (uiout->is_mi_like_p ()) uiout->field_string ("catch-type", "rethrow"); break; case EX_EVENT_CATCH: uiout->field_string ("what", "exception catch"); if (uiout->is_mi_like_p ()) uiout->field_string ("catch-type", "catch"); break; } return true; } /* Implement the 'print_one_detail' method. */ void exception_catchpoint::print_one_detail (struct ui_out *uiout) const { if (!exception_rx.empty ()) { uiout->text (_("\tmatching: ")); uiout->field_string ("regexp", exception_rx); uiout->text ("\n"); } } void exception_catchpoint::print_mention () const { struct ui_out *uiout = current_uiout; int bp_temp; bp_temp = disposition == disp_del; uiout->message ("%s %d %s", (bp_temp ? _("Temporary catchpoint ") : _("Catchpoint")), number, (kind == EX_EVENT_THROW ? _("(throw)") : (kind == EX_EVENT_CATCH ? _("(catch)") : _("(rethrow)")))); } /* Implement the "print_recreate" method for throw and catch catchpoints. */ void exception_catchpoint::print_recreate (struct ui_file *fp) const { int bp_temp; bp_temp = disposition == disp_del; gdb_printf (fp, bp_temp ? "tcatch " : "catch "); switch (kind) { case EX_EVENT_THROW: gdb_printf (fp, "throw"); break; case EX_EVENT_CATCH: gdb_printf (fp, "catch"); break; case EX_EVENT_RETHROW: gdb_printf (fp, "rethrow"); break; } print_recreate_thread (fp); } /* Implement the "allocate_location" method for throw and catch catchpoints. */ bp_location * exception_catchpoint::allocate_location () { return new bp_location (this, bp_loc_software_breakpoint); } static void handle_gnu_v3_exceptions (int tempflag, std::string &&except_rx, const char *cond_string, enum exception_event_kind ex_event, int from_tty) { struct gdbarch *gdbarch = get_current_arch (); std::unique_ptr cp (new exception_catchpoint (gdbarch, tempflag, cond_string, ex_event, std::move (except_rx))); install_breakpoint (0, std::move (cp), 1); } /* Look for an "if" token in *STRING. The "if" token must be preceded by whitespace. If there is any non-whitespace text between *STRING and the "if" token, then it is returned in a newly-xmalloc'd string. Otherwise, this returns NULL. STRING is updated to point to the "if" token, if it exists, or to the end of the string. */ static std::string extract_exception_regexp (const char **string) { const char *start; const char *last, *last_space; start = skip_spaces (*string); last = start; last_space = start; while (*last != '\0') { const char *if_token = last; /* Check for the "if". */ if (check_for_argument (&if_token, "if", 2)) break; /* No "if" token here. Skip to the next word start. */ last_space = skip_to_space (last); last = skip_spaces (last_space); } *string = last; if (last_space > start) return std::string (start, last_space - start); return std::string (); } /* See breakpoint.h. */ void catch_exception_event (enum exception_event_kind ex_event, const char *arg, bool tempflag, int from_tty) { const char *cond_string = NULL; if (!arg) arg = ""; arg = skip_spaces (arg); std::string except_rx = extract_exception_regexp (&arg); cond_string = ep_parse_optional_if_clause (&arg); if ((*arg != '\0') && !isspace (*arg)) error (_("Junk at end of arguments.")); if (ex_event != EX_EVENT_THROW && ex_event != EX_EVENT_CATCH && ex_event != EX_EVENT_RETHROW) error (_("Unsupported or unknown exception event; cannot catch it")); handle_gnu_v3_exceptions (tempflag, std::move (except_rx), cond_string, ex_event, from_tty); } /* Implementation of "catch catch" command. */ static void catch_catch_command (const char *arg, int from_tty, struct cmd_list_element *command) { bool tempflag = command->context () == CATCH_TEMPORARY; catch_exception_event (EX_EVENT_CATCH, arg, tempflag, from_tty); } /* Implementation of "catch throw" command. */ static void catch_throw_command (const char *arg, int from_tty, struct cmd_list_element *command) { bool tempflag = command->context () == CATCH_TEMPORARY; catch_exception_event (EX_EVENT_THROW, arg, tempflag, from_tty); } /* Implementation of "catch rethrow" command. */ static void catch_rethrow_command (const char *arg, int from_tty, struct cmd_list_element *command) { bool tempflag = command->context () == CATCH_TEMPORARY; catch_exception_event (EX_EVENT_RETHROW, arg, tempflag, from_tty); } /* Implement the 'make_value' method for the $_exception internalvar. */ static struct value * compute_exception (struct gdbarch *argc, struct internalvar *var, void *ignore) { struct value *arg0, *arg1; struct type *obj_type; fetch_probe_arguments (&arg0, &arg1); /* ARG0 is a pointer to the exception object. ARG1 is a pointer to the std::type_info for the exception. Now we find the type from the type_info and cast the result. */ obj_type = cplus_type_from_type_info (arg1); return value_ind (value_cast (make_pointer_type (obj_type, NULL), arg0)); } /* Implementation of the '$_exception' variable. */ static const struct internalvar_funcs exception_funcs = { compute_exception, NULL, }; void _initialize_break_catch_throw (); void _initialize_break_catch_throw () { /* Add catch and tcatch sub-commands. */ add_catch_command ("catch", _("\ Catch an exception, when caught."), catch_catch_command, NULL, CATCH_PERMANENT, CATCH_TEMPORARY); add_catch_command ("throw", _("\ Catch an exception, when thrown."), catch_throw_command, NULL, CATCH_PERMANENT, CATCH_TEMPORARY); add_catch_command ("rethrow", _("\ Catch an exception, when rethrown."), catch_rethrow_command, NULL, CATCH_PERMANENT, CATCH_TEMPORARY); create_internalvar_type_lazy ("_exception", &exception_funcs, NULL); }