mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-12-28 01:50:48 +00:00
Currently, GDB internally uses the term "location" for both the
location specification the user input (linespec, explicit location, or
an address location), and for actual resolved locations, like the
breakpoint locations, or the result of decoding a location spec to
SaLs. This is expecially confusing in the breakpoints module, as
struct breakpoint has these two fields:
breakpoint::location;
breakpoint::loc;
"location" is the location spec, and "loc" is the resolved locations.
And then, we have a method called "locations()", which returns the
resolved locations as range...
The location spec type is presently called event_location:
/* Location we used to set the breakpoint. */
event_location_up location;
and it is described like this:
/* The base class for all an event locations used to set a stop event
in the inferior. */
struct event_location
{
and even that is incorrect... Location specs are used for finding
actual locations in the program in scenarios that have nothing to do
with stop events. E.g., "list" works with location specs.
To clean all this confusion up, this patch renames "event_location" to
"location_spec" throughout, and then all the variables that hold a
location spec, they are renamed to include "spec" in their name, like
e.g., "location" -> "locspec". Similarly, functions that work with
location specs, and currently have just "location" in their name are
renamed to include "spec" in their name too.
Change-Id: I5814124798aa2b2003e79496e78f95c74e5eddca
541 lines
14 KiB
C
541 lines
14 KiB
C
/* Everything about catch/throw catchpoints, for GDB.
|
||
|
||
Copyright (C) 1986-2022 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 "defs.h"
|
||
#include "arch-utils.h"
|
||
#include <ctype.h>
|
||
#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<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)
|
||
{
|
||
struct frame_info *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<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 (self->pattern->exec (name, 0, NULL, 0) != 0)
|
||
bs->stop = 0;
|
||
}
|
||
}
|
||
|
||
/* Implement the 're_set' method. */
|
||
|
||
void
|
||
exception_catchpoint::re_set ()
|
||
{
|
||
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
|
||
{
|
||
struct explicit_location explicit_loc;
|
||
|
||
initialize_explicit_location (&explicit_loc);
|
||
explicit_loc.function_name
|
||
= ASTRDUP (exception_functions[kind].function);
|
||
location_spec_up locspec = new_explicit_location_spec (&explicit_loc);
|
||
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 ");
|
||
uiout->field_signed ("bkptno", number);
|
||
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<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);
|
||
}
|