This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[10/10] RFC: implement "catch throw REGEXP"


Once upon a time, gdb documented that "catch catch" and "catch throw"
could filter based on the exception object's type.  This was never
actually implemented (to my knowledge) and was eventually removed from
the documentation.

Users occasionally run across this documentation and ask that this
feature be restored.  This is PR c++/13588.

This patch implements something similar, allowing a regular expression
to be passed to the C++ exception catchpoint commands.  Then, only
exception objects whose types match this regular expression cause a
stop.

This requires the libstdc++ SDT probes.

Built and regtested on x86-64 Fedora 16, using both libstdc++ variants.
New test included.

Tom

	PR c++/13588:
	* NEWS: Update.
	* break-catch-throw.c (struct exception_catchpoint)
	<exception_rx, pattern>: New fields.
	(fetch_probe_arguments, dtor_exception_catchpoint)
	(check_status_exception_catchpoint)
	(print_one_detail_exception_catchpoint): New functions.
	(handle_gnu_v3_exceptions): Add "except_rx" argument.
	Compile regular expression if needed.
	(extract_exception_regexp): New function.
	(catch_exception_command_1): Use extract_exception_regexp.
	(compute_exception): Use fetch_probe_arguments.
	(initialize_throw_catchpoint_ops): Set dtor, print_one_detail,
	and check_status fields.
	* cp-abi.c (cplus_typename_from_type_info): New function.
	* cp-abi.h (cplus_typename_from_type_info): Declare.
	(struct cp_abi_ops) <get_typename_from_type_info>: New field.
	* gdb_regex.h (compile_rx_or_error): Declare.
	* gnu-v3-abi.c (gnuv3_get_typename_from_type_info): Update
	comment.
	(init_gnuv3_ops): Set get_type_from_type_info field.
	* probe.c (compile_rx_or_error): Move...
	* utils.c (compile_rx_or_error): ... here.

	* gdb.texinfo (Set Catchpoints): Document regexp syntax for
	exception catchpoints.

	* gdb.cp/exceptprint.exp: Add regexp catchpoint tests.
---
 gdb/NEWS                             |    3 +
 gdb/break-catch-throw.c              |  223 +++++++++++++++++++++++++++++-----
 gdb/cp-abi.c                         |   11 ++
 gdb/cp-abi.h                         |    8 ++
 gdb/doc/gdb.texinfo                  |   15 ++-
 gdb/gdb_regex.h                      |    2 +
 gdb/gnu-v3-abi.c                     |    4 +-
 gdb/probe.c                          |   24 ----
 gdb/testsuite/gdb.cp/exceptprint.exp |   21 +++
 gdb/utils.c                          |   24 ++++
 10 files changed, 275 insertions(+), 60 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 4ed6d11..1a18de4 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -41,6 +41,9 @@ Lynx 178 PowerPC		powerpc-*-lynx*178
 * The new convenience variable $_exception holds the exception being
   thrown or caught at an exception-related catchpoint.
 
+* The exception-related catchpoints, like "catch throw", now accept a
+  regular expression which can be used to filter exceptions by type.
+
 * Python scripting
 
   ** Vectors can be created with gdb.Type.vector.
diff --git a/gdb/break-catch-throw.c b/gdb/break-catch-throw.c
index ed236ef..fb24725 100644
--- a/gdb/break-catch-throw.c
+++ b/gdb/break-catch-throw.c
@@ -34,6 +34,8 @@
 #include "probe.h"
 #include "objfiles.h"
 #include "cp-abi.h"
+#include "gdb_regex.h"
+#include "cp-support.h"
 
 /* Enums for exception-handling support.  */
 enum exception_event_kind
@@ -81,8 +83,60 @@ struct exception_catchpoint
   /* The kind of exception catchpoint.  */
 
   enum exception_event_kind kind;
+
+  /* If non-NULL, an xmalloc'd string holding the source form of the
+     regular expression to match against.  */
+
+  char *exception_rx;
+
+  /* If non-NULL, an xmalloc'd, compiled regular expression which is
+     used to determine which exceptions to stop on.  */
+
+  regex_t *pattern;
 };
 
+
+
+/* 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 probe *pc_probe;
+  const struct sym_probe_fns *pc_probe_fns;
+  unsigned n_args;
+
+  pc_probe = find_probe_by_pc (pc);
+  if (pc_probe == NULL
+      || strcmp (pc_probe->provider, "libstdcxx") != 0
+      || (strcmp (pc_probe->name, "catch") != 0
+	  && strcmp (pc_probe->name, "throw") != 0
+	  && strcmp (pc_probe->name, "rethrow") != 0))
+    error (_("not stopped at a C++ exception catchpoint"));
+
+  gdb_assert (pc_probe->objfile != NULL);
+  gdb_assert (pc_probe->objfile->sf != NULL);
+  gdb_assert (pc_probe->objfile->sf->sym_probe_fns != NULL);
+
+  pc_probe_fns = pc_probe->objfile->sf->sym_probe_fns;
+  n_args = pc_probe_fns->sym_get_probe_argument_count (pc_probe);
+  if (n_args < 2)
+    error (_("C++ exception catchpoint has too few arguments"));
+
+  if (arg0 != NULL)
+    *arg0 = pc_probe_fns->sym_evaluate_probe_argument (pc_probe, 0);
+  *arg1 = pc_probe_fns->sym_evaluate_probe_argument (pc_probe, 1);
+
+  if ((arg0 != NULL && *arg0 == NULL) || *arg1 == NULL)
+    error (_("error computing probe argument at c++ exception catchpoint"));
+}
+
+
+
 /* A helper function that returns a value indicating the kind of the
    exception catchpoint B.  */
 
@@ -94,6 +148,60 @@ classify_exception_breakpoint (struct breakpoint *b)
   return cp->kind;
 }
 
+/* Implement the 'dtor' method.  */
+
+static void
+dtor_exception_catchpoint (struct breakpoint *self)
+{
+  struct exception_catchpoint *cp = (struct exception_catchpoint *) self;
+
+  xfree (cp->exception_rx);
+  if (cp->pattern != NULL)
+    regfree (cp->pattern);
+  bkpt_breakpoint_ops.dtor (self);
+}
+
+/* Implement the 'check_status' method.  */
+
+static void
+check_status_exception_catchpoint (struct bpstats *bs)
+{
+  struct exception_catchpoint *self
+    = (struct exception_catchpoint *) bs->breakpoint_at;
+  char *typename = NULL;
+  volatile struct gdb_exception e;
+
+  bkpt_breakpoint_ops.check_status (bs);
+  if (bs->stop == 0)
+    return;
+
+  if (self->pattern == NULL)
+    return;
+
+  TRY_CATCH (e, RETURN_MASK_ERROR)
+    {
+      struct value *typeinfo_arg;
+      char *canon;
+
+      fetch_probe_arguments (NULL, &typeinfo_arg);
+      typename = cplus_typename_from_type_info (typeinfo_arg);
+
+      canon = cp_canonicalize_string (typename);
+      if (canon != NULL)
+	{
+	  xfree (typename);
+	  typename = canon;
+	}
+    }
+
+  if (e.reason < 0)
+    exception_print (gdb_stderr, e);
+  else if (regexec (self->pattern, typename, 0, NULL, 0) != 0)
+    bs->stop = 0;
+
+  xfree (typename);
+}
+
 /* Implement the 're_set' method.  */
 
 static void
@@ -208,6 +316,23 @@ print_one_exception_catchpoint (struct breakpoint *b,
     }
 }
 
+/* Implement the 'print_one_detail' method.  */
+
+static void
+print_one_detail_exception_catchpoint (const struct breakpoint *b,
+				       struct ui_out *uiout)
+{
+  const struct exception_catchpoint *cp
+    = (const struct exception_catchpoint *) b;
+
+  if (cp->exception_rx != NULL)
+    {
+      ui_out_text (uiout, _("\tmatching: "));
+      ui_out_field_string (uiout, "regexp", cp->exception_rx);
+      ui_out_text (uiout, "\n");
+    }
+}
+
 static void
 print_mention_exception_catchpoint (struct breakpoint *b)
 {
@@ -252,22 +377,77 @@ print_recreate_exception_catchpoint (struct breakpoint *b,
 }
 
 static void
-handle_gnu_v3_exceptions (int tempflag, char *cond_string,
+handle_gnu_v3_exceptions (int tempflag, char *except_rx, char *cond_string,
 			  enum exception_event_kind ex_event, int from_tty)
 {
   struct exception_catchpoint *cp;
+  struct cleanup *cleanup = make_cleanup (null_cleanup, NULL);
+  regex_t *pattern = NULL;
+
+  if (except_rx != NULL)
+    {
+      pattern = XNEW (regex_t);
+      make_cleanup (xfree, pattern);
+
+      compile_rx_or_error (pattern, except_rx,
+			   _("invalid type-matching regexp"));
+    }
 
   cp = XCNEW (struct exception_catchpoint);
+  make_cleanup (xfree, cp);
+
   init_catchpoint (&cp->base, get_current_arch (), tempflag, cond_string,
 		   &gnu_v3_exception_catchpoint_ops);
   /* We need to reset 'type' in order for code in breakpoint.c to do
      the right thing.  */
   cp->base.type = bp_breakpoint;
   cp->kind = ex_event;
+  cp->exception_rx = except_rx;
+  cp->pattern = pattern;
 
   re_set_exception_catchpoint (&cp->base);
 
   install_breakpoint (0, &cp->base, 1);
+  discard_cleanups (cleanup);
+}
+
+/* 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 char *
+extract_exception_regexp (char **string)
+{
+  char *start;
+  char *last, *last_space;
+
+  start = skip_spaces (*string);
+
+  last = start;
+  last_space = start;
+  while (*last != '\0')
+    {
+      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 savestring (start, last_space - start);
+  return NULL;
 }
 
 /* Deal with "catch catch", "catch throw", and "catch rethrow"
@@ -277,12 +457,17 @@ static void
 catch_exception_command_1 (enum exception_event_kind ex_event, char *arg,
 			   int tempflag, int from_tty)
 {
+  char *except_rx;
   char *cond_string = NULL;
+  struct cleanup *cleanup;
 
   if (!arg)
     arg = "";
   arg = skip_spaces (arg);
 
+  except_rx = extract_exception_regexp (&arg);
+  cleanup = make_cleanup (xfree, except_rx);
+
   cond_string = ep_parse_optional_if_clause (&arg);
 
   if ((*arg != '\0') && !isspace (*arg))
@@ -293,7 +478,10 @@ catch_exception_command_1 (enum exception_event_kind ex_event, char *arg,
       && ex_event != EX_EVENT_RETHROW)
     error (_("Unsupported or unknown exception event; cannot catch it"));
 
-  handle_gnu_v3_exceptions (tempflag, cond_string, ex_event, from_tty);
+  handle_gnu_v3_exceptions (tempflag, except_rx, cond_string,
+			    ex_event, from_tty);
+
+  discard_cleanups (cleanup);
 }
 
 /* Implementation of "catch catch" command.  */
@@ -335,36 +523,10 @@ catch_rethrow_command (char *arg, int from_tty,
 static struct value *
 compute_exception (struct gdbarch *argc, struct internalvar *var, void *ignore)
 {
-  struct frame_info *frame = get_selected_frame (_("No frame selected"));
-  CORE_ADDR pc = get_frame_pc (frame);
-  struct probe *pc_probe;
-  const struct sym_probe_fns *pc_probe_fns;
-  unsigned n_args;
   struct value *arg0, *arg1;
   struct type *obj_type;
 
-  pc_probe = find_probe_by_pc (pc);
-  if (pc_probe == NULL
-      || strcmp (pc_probe->provider, "libstdcxx") != 0
-      || (strcmp (pc_probe->name, "catch") != 0
-	  && strcmp (pc_probe->name, "throw") != 0
-	  && strcmp (pc_probe->name, "rethrow") != 0))
-    error (_("not stopped at a C++ exception catchpoint"));
-
-  gdb_assert (pc_probe->objfile != NULL);
-  gdb_assert (pc_probe->objfile->sf != NULL);
-  gdb_assert (pc_probe->objfile->sf->sym_probe_fns != NULL);
-
-  pc_probe_fns = pc_probe->objfile->sf->sym_probe_fns;
-  n_args = pc_probe_fns->sym_get_probe_argument_count (pc_probe);
-  if (n_args < 2)
-    error (_("C++ exception catchpoint has too few arguments"));
-
-  arg0 = pc_probe_fns->sym_evaluate_probe_argument (pc_probe, 0);
-  arg1 = pc_probe_fns->sym_evaluate_probe_argument (pc_probe, 1);
-
-  if (arg0 == NULL || arg1 == NULL)
-    error (_("error computing probe argument at c++ exception catchpoint"));
+  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
@@ -394,11 +556,14 @@ initialize_throw_catchpoint_ops (void)
   /* GNU v3 exception catchpoints.  */
   ops = &gnu_v3_exception_catchpoint_ops;
   *ops = bkpt_breakpoint_ops;
+  ops->dtor = dtor_exception_catchpoint;
   ops->re_set = re_set_exception_catchpoint;
   ops->print_it = print_it_exception_catchpoint;
   ops->print_one = print_one_exception_catchpoint;
   ops->print_mention = print_mention_exception_catchpoint;
   ops->print_recreate = print_recreate_exception_catchpoint;
+  ops->print_one_detail = print_one_detail_exception_catchpoint;
+  ops->check_status = check_status_exception_catchpoint;
 }
 
 initialize_file_ftype _initialize_break_catch_throw;
diff --git a/gdb/cp-abi.c b/gdb/cp-abi.c
index 821c951..b5cfebd 100644
--- a/gdb/cp-abi.c
+++ b/gdb/cp-abi.c
@@ -209,6 +209,17 @@ cplus_type_from_type_info (struct value *value)
   return (*current_cp_abi.get_type_from_type_info) (value);
 }
 
+/* See cp-abi.h.  */
+
+char *
+cplus_typename_from_type_info (struct value *value)
+{
+  if (current_cp_abi.get_typename_from_type_info == NULL)
+    error (_("GDB cannot find the type name "
+	     "from a std::type_info on this target"));
+  return (*current_cp_abi.get_typename_from_type_info) (value);
+}
+
 int
 cp_pass_by_reference (struct type *type)
 {
diff --git a/gdb/cp-abi.h b/gdb/cp-abi.h
index 0b954de..f6d47ba 100644
--- a/gdb/cp-abi.h
+++ b/gdb/cp-abi.h
@@ -194,6 +194,13 @@ extern struct type *cplus_typeid_type (struct gdbarch *gdbarch);
 
 extern struct type *cplus_type_from_type_info (struct value *value);
 
+/* Given a value which holds a pointer to a std::type_info, return the
+   name of the type which that type_info represents.  Throw an
+   exception if the type name cannot be found.  The result is
+   xmalloc'd and must be freed by the caller.  */
+
+extern char *cplus_typename_from_type_info (struct value *value);
+
 /* Determine if we are currently in a C++ thunk.  If so, get the
    address of the routine we are thunking to and continue to there
    instead.  */
@@ -238,6 +245,7 @@ struct cp_abi_ops
   struct value *(*get_typeid) (struct value *value);
   struct type *(*get_typeid_type) (struct gdbarch *gdbarch);
   struct type *(*get_type_from_type_info) (struct value *value);
+  char *(*get_typename_from_type_info) (struct value *value);
   CORE_ADDR (*skip_trampoline) (struct frame_info *, CORE_ADDR);
   int (*pass_by_reference) (struct type *type);
 };
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 217e2ff..29edfad 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -4054,12 +4054,15 @@ shared library.  Use the @code{catch} command to set a catchpoint.
 Stop when @var{event} occurs.  @var{event} can be any of the following:
 
 @table @code
-@item throw
-@itemx rethrow
-@itemx catch
+@item throw @r{[}@var{regexp}@r{]}
+@itemx rethrow @r{[}@var{regexp}@r{]}
+@itemx catch @r{[}@var{regexp}@r{]}
 @cindex stop on C@t{++} exceptions
 The throwing, re-throwing, or catching of a C@t{++} exception.
 
+If @var{regexp} is given, then only exceptions whose type matches the
+regular expression will be caught.
+
 @vindex $_exception@r{, convenience variable}
 The convenience variable @code{$_exception} is available at an
 exception-related catchpoint, on some systems.  This holds the
@@ -4075,9 +4078,9 @@ systems using the @samp{gnu-v3} C@t{++} ABI (@pxref{ABI}) are
 supported.
 
 @item
-The @code{$_exception} convenience variable relies on the presence of
-some SDT probes in @code{libstdc++}.  If these probes are not present,
-then this variable cannot be used.
+The regular expression feature and the @code{$_exception} convenience
+variable rely on the presence of some SDT probes in @code{libstdc++}.
+If these probes are not present, then these features cannot be used.
 
 @item
 The @code{$_exception} convenience variable is only valid at the
diff --git a/gdb/gdb_regex.h b/gdb/gdb_regex.h
index 14475fc..cf91f19 100644
--- a/gdb/gdb_regex.h
+++ b/gdb/gdb_regex.h
@@ -30,5 +30,7 @@
 /* From utils.c.  */
 struct cleanup *make_regfree_cleanup (regex_t *);
 char *get_regcomp_error (int, regex_t *);
+struct cleanup *compile_rx_or_error (regex_t *pattern, const char *rx,
+				     const char *message);
 
 #endif /* not GDB_REGEX_H */
diff --git a/gdb/gnu-v3-abi.c b/gdb/gnu-v3-abi.c
index 24dbee2..44cfbf3 100644
--- a/gdb/gnu-v3-abi.c
+++ b/gdb/gnu-v3-abi.c
@@ -1138,7 +1138,7 @@ gnuv3_get_typeid (struct value *value)
   return result;
 }
 
-/* Get the type name given a type_info object.  */
+/* Implement the 'get_typename_from_type_info' method.  */
 
 static char *
 gnuv3_get_typename_from_type_info (struct value *type_info_ptr)
@@ -1356,6 +1356,8 @@ init_gnuv3_ops (void)
   gnu_v3_abi_ops.get_typeid = gnuv3_get_typeid;
   gnu_v3_abi_ops.get_typeid_type = gnuv3_get_typeid_type;
   gnu_v3_abi_ops.get_type_from_type_info = gnuv3_get_type_from_type_info;
+  gnu_v3_abi_ops.get_typename_from_type_info
+    = gnuv3_get_typename_from_type_info;
   gnu_v3_abi_ops.skip_trampoline = gnuv3_skip_trampoline;
   gnu_v3_abi_ops.pass_by_reference = gnuv3_pass_by_reference;
 }
diff --git a/gdb/probe.c b/gdb/probe.c
index dfb6e7e..05bdd1b 100644
--- a/gdb/probe.c
+++ b/gdb/probe.c
@@ -229,30 +229,6 @@ find_probe_by_pc (CORE_ADDR pc)
 
 
 
-/* A helper function for collect_probes that compiles a regexp and
-   throws an exception on error.  This installs a cleanup to free the
-   resulting pattern on success.  If RX is NULL, this does nothing.  */
-
-static void
-compile_rx_or_error (regex_t *pattern, const char *rx, const char *message)
-{
-  int code;
-
-  if (!rx)
-    return;
-
-  code = regcomp (pattern, rx, REG_NOSUB);
-  if (code == 0)
-    make_regfree_cleanup (pattern);
-  else
-    {
-      char *err = get_regcomp_error (code, pattern);
-
-      make_cleanup (xfree, err);
-      error (("%s: %s"), message, err);
-    }
-}
-
 /* Make a vector of probes matching OBJNAME, PROVIDER, and PROBE_NAME.
    If POPS is not NULL, only probes of this certain probe_ops will match.
    Each argument is a regexp, or NULL, which matches anything.  */
diff --git a/gdb/testsuite/gdb.cp/exceptprint.exp b/gdb/testsuite/gdb.cp/exceptprint.exp
index 6e03fd9..d9f4fd5 100644
--- a/gdb/testsuite/gdb.cp/exceptprint.exp
+++ b/gdb/testsuite/gdb.cp/exceptprint.exp
@@ -71,3 +71,24 @@ do_exceptprint_tests string "$hex \"hi bob\""
 do_exceptprint_tests int 23
 do_exceptprint_tests struct "{mv = 77}"
 do_exceptprint_tests "reference to struct" "{mv = 77}"
+
+
+delete_breakpoints
+
+if {![runto_main]} {
+    return -1
+}
+
+gdb_test "catch catch int if \$_exception == 23" \
+    "Catchpoint \[0-9\]+ \\(catch\\)" \
+    "catch catch"
+gdb_test "catch throw int if \$_exception == 23" \
+    "Catchpoint \[0-9\]+ \\(throw\\)" \
+    "catch throw"
+gdb_test "catch rethrow int if \$_exception == 23" \
+    "Catchpoint \[0-9\]+ \\(rethrow\\)" \
+    "catch rethrow"
+
+# This tests both the case where the regular expression does not
+# match, and the case where it does.
+do_exceptprint_tests int 23
diff --git a/gdb/utils.c b/gdb/utils.c
index eb99f4b..ea5ed58 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1227,6 +1227,30 @@ get_regcomp_error (int code, regex_t *rx)
   return result;
 }
 
+/* Compile a regexp and throw an exception on error.  This returns a
+   cleanup to free the resulting pattern on success.  If RX is NULL,
+   this does nothing and returns NULL.  */
+
+struct cleanup *
+compile_rx_or_error (regex_t *pattern, const char *rx, const char *message)
+{
+  int code;
+
+  if (!rx)
+    return NULL;
+
+  code = regcomp (pattern, rx, REG_NOSUB);
+  if (code != 0)
+    {
+      char *err = get_regcomp_error (code, pattern);
+
+      make_cleanup (xfree, err);
+      error (("%s: %s"), message, err);
+    }
+
+  return make_regfree_cleanup (pattern);
+}
+
 
 
 /* This function supports the query, nquery, and yquery functions.
-- 
1.7.7.6


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]