mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
6cce025114
This commit updates GDB so that thread or inferior specific breakpoints are only inserted into the program space in which the specific thread or inferior is running. In terms of implementation, getting this basically working is easy enough, now that a breakpoint's thread or inferior field is setup prior to GDB looking for locations, we can easily use this information to find a suitable program_space and pass this to as a filter when creating the sals. Or we could if breakpoint_ops::create_sals_from_location_spec allowed us to pass in a filter program_space. So, this commit extends breakpoint_ops::create_sals_from_location_spec to take a program_space argument, and uses this to filter the set of returned sals. This accounts for about half the change in this patch. The second set of changes starts from breakpoint_set_thread and breakpoint_set_inferior, this is called when the thread or inferior for a breakpoint changes, e.g. from the Python API. Previously this call would never result in the locations of a breakpoint changing, after all, locations were inserted in every program space, and we just use the thread or inferior variable to decide when we should stop. Now though, changing a breakpoint's thread or inferior can mean we need to figure out a new set of breakpoint locations. To support this I've added a new breakpoint_re_set_one function, which is like breakpoint_re_set, but takes a single breakpoint, and just updates the locations for that one breakpoint. We only need to call this function if the program_space in which a breakpoint's thread (or inferior) is running actually changes. If the program_space does change then we call the new breakpoint_re_set_one function passing in the program_space which should be used to filter the new locations (or nullptr to indicate we should set locations in all program spaces). This filter program_space needs to propagate down to all the re_set methods, this accounts for the remaining half of the changes in this patch. There were a couple of existing tests that created thread or inferior specific breakpoints and then checked the 'info breakpoints' output, these needed updating. These were: gdb.mi/user-selected-context-sync.exp gdb.multi/bp-thread-specific.exp gdb.multi/multi-target-continue.exp gdb.multi/multi-target-ping-pong-next.exp gdb.multi/tids.exp gdb.mi/new-ui-bp-deleted.exp gdb.multi/inferior-specific-bp.exp gdb.multi/pending-bp-del-inferior.exp I've also added some additional tests to: gdb.multi/pending-bp.exp I've updated the documentation and added a NEWS entry. Reviewed-By: Eli Zaretskii <eliz@gnu.org>
528 lines
14 KiB
C
528 lines
14 KiB
C
/* Everything about catch/throw catchpoints, for GDB.
|
||
|
||
Copyright (C) 1986-2024 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 <http://www.gnu.org/licenses/>. */
|
||
|
||
#include "arch-utils.h"
|
||
#include <ctype.h>
|
||
#include "breakpoint.h"
|
||
#include "exceptions.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 (pspace);
|
||
}
|
||
|
||
void re_set (program_space *pspace) override;
|
||
enum print_stop_action print_it (const bpstat *bs) const override;
|
||
bool print_one (const 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;
|
||
|
||
/* 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<compiled_regex> pattern;
|
||
};
|
||
|
||
/* See breakpoint.h. */
|
||
|
||
bool
|
||
is_exception_catchpoint (breakpoint *bp)
|
||
{
|
||
return dynamic_cast<exception_catchpoint *> (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)
|
||
{
|
||
std::string type_name;
|
||
|
||
this->breakpoint::check_status (bs);
|
||
if (!bs->stop)
|
||
return;
|
||
|
||
if (this->pattern == NULL)
|
||
return;
|
||
|
||
const char *name = nullptr;
|
||
gdb::unique_xmalloc_ptr<char> 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 (this->pattern->exec (name, 0, NULL, 0) != 0)
|
||
bs->stop = false;
|
||
}
|
||
}
|
||
|
||
/* Implement the 're_set' method. */
|
||
|
||
void
|
||
exception_catchpoint::re_set (program_space *pspace)
|
||
{
|
||
std::vector<symtab_and_line> 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 (const 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<exception_catchpoint> 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);
|
||
}
|