diff mbox

[doc,RFA] Add "skip regexp"

Message ID 047d7b2e15110c831f052acd5987@google.com
State New
Headers show

Commit Message

Doug Evans Feb. 2, 2016, 6:05 p.m. UTC
Eli Zaretskii writes:
  > > Date: Tue, 02 Feb 2016 01:03:19 +0000
  > > From: Doug Evans <dje@google.com>
  > >
  > > With this patch one can specify the skip as:
  > >
  > > skip regexp ^std::(allocator|basic_string)<.*>::~?\1 *\(
  >
  > Thanks.
  >
  > > 2016-02-01  Doug Evans  <dje@google.com>
  > >
  > > 	New command "skip regexp regular-expression".
  > > 	* NEWS: Document the new feature.
  > > 	* skip.c (skip_kind): New enum.
  > > 	(skiplist_entry) <filename,function_name>: Delete.
  > > 	<kind,text,regex,regex_valid>: New members.
  > > 	(skiplist_entry_kind_name): New function.
  > > 	(make_skip_file, make_skip_function, make_skip_regexp): New function.
  > > 	(free_skiplist_entry, free_skiplist_entry_cleanup): New functions.
  > > 	(make_free_skiplist_entry_cleanup): New function.
  > > 	(skip_file_command): Update.
  > > 	(skip_function): Update.
  > > 	(compile_skip_regexp, skip_regexp_command): New functions.
  > > 	(skip_info): Update.
  > > 	(sal_and_fullname): New struct.
  > > 	(skip_file_p, skip_function_p, skip_regexp_p): New functions.
  > > 	(function_name_is_marked_for_skip): Update and simplify.
  > > 	(_initialize_step_skip): Add "skip regexp" command.
  > >
  > > 	doc/
  > > 	* gdb.texinfo (Skipping Over Functions and Files): Document
  > > 	"skip regexp".
  >
  > The documentation parts are approved, with the following nit:
  >
  > > +Functions may be skipped by providing either a function name, linespec
  > > +(@pxref{Specify Location}), file name, or regular expression of the
  > > +function's name.                          ^^^^^^^^^^^^^^^^^^^^^^^^^
  >    ^^^^^^^^^^^^^^^
  > "regular expression that matches the function's name" is more accurate
  > (and you also use it elsewhere in the patch).
  >
  > Otherwise, fine with me, thanks.

Thanks.

I included an older version of the perf testcase in my previous email.
This is an updated patch.

2016-02-02  Doug Evans  <dje@google.com>

	New command "skip regexp regular-expression".
	* NEWS: Document the new feature.
	* skip.c (skip_kind): New enum.
	(skiplist_entry) <filename,function_name>: Delete.
	<kind,text,regex,regex_valid>: New members.
	(skiplist_entry_kind_name): New function.
	(make_skip_file, make_skip_function, make_skip_regexp): New function.
	(free_skiplist_entry, free_skiplist_entry_cleanup): New functions.
	(make_free_skiplist_entry_cleanup): New function.
	(skip_file_command): Update.
	(skip_function): Update.
	(compile_skip_regexp, skip_regexp_command): New functions.
	(skip_info): Update.
	(sal_and_fullname): New struct.
	(skip_file_p, skip_function_p, skip_regexp_p): New functions.
	(function_name_is_marked_for_skip): Update and simplify.
	(_initialize_step_skip): Add "skip regexp" command.

	doc/
	* gdb.texinfo (Skipping Over Functions and Files): Document
	"skip regexp".

	testsuite/
	* gdb.base/skip.exp: Add tests for "skip regexp".
	* gdb.perf/skip-command.cc: New file.
	* gdb.perf/skip-command.exp: New file.
	* gdb.perf/skip-command.py: New file.
diff mbox

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 962eae4..388daaa 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -125,6 +125,10 @@  show max-value-size
    allocate for value contents.  Prevents incorrect programs from
    causing GDB to allocate overly large buffers.  Default is 64k.

+skip regexp regular-expression
+  A variation of "skip function" where the function name is specified
+  as a regular expression.
+
  * The "disassemble" command accepts a new modifier: /s.
    It prints mixed source+disassembly like /m with two differences:
    - disassembled instructions are now printed in program order, and
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 2d09d13..f249e3a 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -5555,8 +5555,9 @@  A more flexible solution is to execute @kbd{skip  
boring}.  This instructs
  @code{step} at line 103, you'll step over @code{boring} and directly into
  @code{foo}.

-You can also instruct @value{GDBN} to skip all functions in a file, with,  
for
-example, @code{skip file boring.c}.
+Functions may be skipped by providing either a function name, linespec
+(@pxref{Specify Location}), file name, or regular expression that matches
+the function's name.

  @table @code
  @kindex skip function
@@ -5577,8 +5578,42 @@  will be skipped.
  After running this command, any function whose source lives in  
@var{filename}
  will be skipped over when stepping.

+@smallexample
+(gdb) skip file boring.c
+File boring.c will be skipped when stepping.
+@end smallexample
+
  If you do not specify @var{filename}, functions whose source lives in the  
file
  you're currently debugging will be skipped.
+
+@kindex skip regexp
+@item skip regexp @var{regular-expression}
+After running this command, any function whose name matches
+@var{regular-expression} will be skipped over when stepping.
+
+This form is useful for complex function names.
+For example, there is generally no need to step into C++ std::string
+constructors or destructors.  Plus with C++ templates it can be hard to
+write out the full name of the function, and often it doesn't matter what
+the template arguments are.  Specifying the function to be skipped as a
+regular expression makes this easier.
+
+On Posix systems the form of the regular expression is
+``Extended Regular Expressions''.  See for example @samp{man 7 regex}
+on @sc{gnu}/Linux systems.  On non-Posix systems the form of the regular
+expression is whatever is provided by the @code{regcomp} function of
+the underlying system.
+
+@smallexample
+(gdb) skip regexp ^std::(allocator|basic_string)<.*>::~?\1 *\(
+@end smallexample
+
+If you wanted to skip every templated C++ constructor and destructor
+in the @code{std} namespace you could do:
+
+@smallexample
+(gdb) skip regexp ^std::([a-zA-z0-9_]+)<.*>::~?\1 *\(
+@end smallexample
  @end table

  Skips can be listed, deleted, disabled, and enabled, much like breakpoints.
@@ -5595,7 +5630,7 @@  print a table with details about all functions and  
files marked for skipping.
  @item Identifier
  A number identifying this skip.
  @item Type
-The type of this skip, either @samp{function} or @samp{file}.
+The type of this skip, either @samp{function}, @samp{file} or  
@samp{regexp}.
  @item Enabled or Disabled
  Enabled skips are marked with @samp{y}.  Disabled skips are marked with  
@samp{n}.
  @item Address
diff --git a/gdb/skip.c b/gdb/skip.c
index d90910d..31e240c 100644
--- a/gdb/skip.c
+++ b/gdb/skip.c
@@ -32,19 +32,31 @@ 
  #include "breakpoint.h" /* for get_sal_arch () */
  #include "source.h"
  #include "filenames.h"
+#include "gdb_regex.h"
+
+enum skip_kind
+{
+  SKIP_FILE,
+  SKIP_FUNCTION,
+  SKIP_REGEXP
+};

  struct skiplist_entry
  {
    int number;

-  /* NULL if this isn't a skiplist entry for an entire file.
+  enum skip_kind kind;
+
+  /* The text provided by the user.
       The skiplist entry owns this pointer.  */
-  char *filename;
+  char *text;

-  /* The name of the marked-for-skip function, if this is a skiplist
-     entry for a function.
+  /* If this is a regexp, the compiled form.
       The skiplist entry owns this pointer.  */
-  char *function_name;
+  regex_t regex;
+
+  /* Non-zero if regex has been compiled.  */
+  int regex_valid;

    int enabled;

@@ -65,10 +77,101 @@  static int skiplist_entry_count;
         E ? (TMP = E->next, 1) : 0;       \
         E = TMP)

+/* Return the name of the skiplist entry kind.  */
+
+static const char *
+skiplist_entry_kind_name (struct skiplist_entry *e)
+{
+  switch (e->kind)
+    {
+    case SKIP_FILE: return "file";
+    case SKIP_FUNCTION: return "function";
+    case SKIP_REGEXP: return "regexp";
+    default:
+      gdb_assert_not_reached ("bad skiplist_entry kind");
+    }
+}
+
+/* Create a SKIP_FILE object. */
+
+static struct skiplist_entry *
+make_skip_file (const char *name)
+{
+  struct skiplist_entry *e = XCNEW (struct skiplist_entry);
+
+  e->kind = SKIP_FILE;
+  e->text = xstrdup (name);
+  e->enabled = 1;
+
+  return e;
+}
+
+/* Create a SKIP_FUNCTION object. */
+
+static struct skiplist_entry *
+make_skip_function (const char *name)
+{
+  struct skiplist_entry *e = XCNEW (struct skiplist_entry);
+
+  e->kind = SKIP_FUNCTION;
+  e->enabled = 1;
+  e->text = xstrdup (name);
+
+  return e;
+}
+
+/* Create a SKIP_REGEXP object.
+   The regexp is not parsed, the caller must do that afterwards.  */
+
+static struct skiplist_entry *
+make_skip_regexp (const char *regexp)
+{
+  struct skiplist_entry *e = XCNEW (struct skiplist_entry);
+
+  e->kind = SKIP_REGEXP;
+  e->enabled = 1;
+  e->text = xstrdup (regexp);
+
+  return e;
+}
+
+/* Free a skiplist entry.  */
+
+static void
+free_skiplist_entry (struct skiplist_entry *e)
+{
+  xfree (e->text);
+  switch (e->kind)
+    {
+    case SKIP_REGEXP:
+      if (e->regex_valid)
+	regfree (&e->regex);
+      break;
+    default:
+      break;
+    }
+  xfree (e);
+}
+
+/* Wrapper to free_skiplist_entry for use as a cleanup.  */
+
+static void
+free_skiplist_entry_cleanup (void *e)
+{
+  free_skiplist_entry ((struct skiplist_entry *) e);
+}
+
+/* Create a cleanup to free skiplist entry E.  */
+
+static struct cleanup *
+make_free_skiplist_entry_cleanup (struct skiplist_entry *e)
+{
+  return make_cleanup (free_skiplist_entry_cleanup, e);
+}
+
  static void
  skip_file_command (char *arg, int from_tty)
  {
-  struct skiplist_entry *e;
    struct symtab *symtab;
    const char *filename = NULL;

@@ -99,15 +202,22 @@  Ignore file pending future shared library load? ")))
        filename = arg;
      }

-  e = XCNEW (struct skiplist_entry);
-  e->filename = xstrdup (filename);
-  e->enabled = 1;
-
-  add_skiplist_entry (e);
+  add_skiplist_entry (make_skip_file (filename));

    printf_filtered (_("File %s will be skipped when stepping.\n"),  
filename);
  }

+/* Create a skiplist entry for the given function NAME and add it to the
+   list.  */
+
+static void
+skip_function (const char *name)
+{
+  add_skiplist_entry (make_skip_function (name));
+
+  printf_filtered (_("Function %s will be skipped when stepping.\n"),  
name);
+}
+
  static void
  skip_function_command (char *arg, int from_tty)
  {
@@ -149,6 +259,54 @@  Ignore function pending future shared library  
load? ")))
      }
  }

+/* Compile the regexp in E.
+   An error is thrown if there's an error.
+   MESSAGE is used as a prefix of the error message.  */
+
+static void
+compile_skip_regexp (struct skiplist_entry *e, const char *message)
+{
+  int code;
+  int flags = REG_NOSUB;
+
+#ifdef REG_EXTENDED
+  flags |= REG_EXTENDED;
+#endif
+
+  gdb_assert (e->kind == SKIP_REGEXP);
+
+  code = regcomp (&e->regex, e->text, flags);
+  if (code != 0)
+    {
+      char *err = get_regcomp_error (code, &e->regex);
+
+      make_cleanup (xfree, err);
+      error (("%s: %s"), message, err);
+    }
+  e->regex_valid = 1;
+}
+
+static void
+skip_regexp_command (char *arg, int from_tty)
+{
+  struct skiplist_entry *e;
+  struct cleanup *cleanups;
+
+  /* If no argument was given, try to default to the last
+     displayed codepoint.  */
+  if (arg == NULL)
+    error (_("Missing regexp."));
+
+  e = make_skip_regexp (arg);
+  cleanups = make_free_skiplist_entry_cleanup (e);
+  compile_skip_regexp (e, _("regexp"));
+  discard_cleanups (cleanups);
+  add_skiplist_entry (e);
+
+  printf_filtered (_("Functions matching regexp %s will be skipped"
+		     " when stepping.\n"), arg);
+}
+
  static void
  skip_info (char *arg, int from_tty)
  {
@@ -199,23 +357,15 @@  Not skipping any files or functions.\n"));
  							 "blklst-entry");
        ui_out_field_int (current_uiout, "number", e->number);              
/* 1 */

-      if (e->function_name != NULL)
-	ui_out_field_string (current_uiout, "type", "function");         /* 2 */
-      else if (e->filename != NULL)
-	ui_out_field_string (current_uiout, "type", "file");             /* 2 */
-      else
-	internal_error (__FILE__, __LINE__, _("\
-Skiplist entry should have either a filename or a function name."));
+      ui_out_field_string (current_uiout, "type",
+			   skiplist_entry_kind_name (e));                /* 2 */

        if (e->enabled)
  	ui_out_field_string (current_uiout, "enabled", "y");             /* 3 */
        else
  	ui_out_field_string (current_uiout, "enabled", "n");             /* 3 */

-      if (e->function_name != NULL)
-	ui_out_field_string (current_uiout, "what", e->function_name);	 /* 4 */
-      else if (e->filename != NULL)
-	ui_out_field_string (current_uiout, "what", e->filename);	 /* 4 */
+      ui_out_field_string (current_uiout, "what", e->text);               
/* 4 */

        ui_out_text (current_uiout, "\n");
        do_cleanups (entry_chain);
@@ -273,9 +423,7 @@  skip_delete_command (char *arg, int from_tty)
  	else
  	  skiplist_entry_chain = e->next;

-	xfree (e->function_name);
-	xfree (e->filename);
-	xfree (e);
+	free_skiplist_entry (e);
          found = 1;
        }
      else
@@ -287,22 +435,6 @@  skip_delete_command (char *arg, int from_tty)
      error (_("No skiplist entries found with number %s."), arg);
  }

-/* Create a skiplist entry for the given function NAME and add it to the
-   list.  */
-
-static void
-skip_function (const char *name)
-{
-  struct skiplist_entry *e = XCNEW (struct skiplist_entry);
-
-  e->enabled = 1;
-  e->function_name = xstrdup (name);
-
-  add_skiplist_entry (e);
-
-  printf_filtered (_("Function %s will be skipped when stepping.\n"),  
name);
-}
-
  /* Add the given skiplist entry to our list, and set the entry's number.   
*/

  static void
@@ -326,6 +458,73 @@  add_skiplist_entry (struct skiplist_entry *e)
      }
  }

+/* The file-based location where we're stopped.
+   This simplifies calling skip_file_p because we only want to call
+   symtab_to_fullname once.  */
+
+struct sal_and_fullname
+{
+  const struct symtab_and_line *function_sal;
+  int searched_for_fullname;
+  const char *fullname;
+};
+
+/* Return non-zero if we're stopped at a file to be skipped.  */
+
+static int
+skip_file_p (struct skiplist_entry *e, struct sal_and_fullname  
*file_location)
+{
+  const struct symtab_and_line *function_sal = file_location->function_sal;
+
+  gdb_assert (e->kind == SKIP_FILE);
+
+  /* Check first sole SYMTAB->FILENAME.  It does not need to be
+     a substring of symtab_to_fullname as it may contain "./" etc.  */
+  if (function_sal->symtab != NULL
+      && compare_filenames_for_search (function_sal->symtab->filename,
+				       e->text))
+    return 1;
+
+  /* Before we invoke realpath, which can get expensive when many
+     files are involved, do a quick comparison of the basenames.  */
+  if (!basenames_may_differ
+      && (function_sal->symtab == NULL
+	  || filename_cmp (lbasename (function_sal->symtab->filename),
+			   lbasename (e->text)) != 0))
+    return 0;
+
+  /* Get the filename corresponding to this FUNCTION_SAL, if we haven't
+     yet.  */
+  if (!file_location->searched_for_fullname)
+    {
+      if (function_sal->symtab != NULL)
+	file_location->fullname = symtab_to_fullname (function_sal->symtab);
+      file_location->searched_for_fullname = 1;
+    }
+  if (file_location->fullname != NULL
+      && compare_filenames_for_search (file_location->fullname, e->text))
+    return 1;
+
+  return 0;
+}
+
+/* Return non-zero if we're stopped at a function to be skipped.  */
+
+static int
+skip_function_p (struct skiplist_entry *e, const char *function_name)
+{
+  gdb_assert (e->kind == SKIP_FUNCTION);
+  return strcmp_iw (function_name, e->text) == 0;
+}
+
+/* Return non-zero if we're stopped at a function regexp to be skipped.  */
+
+static int
+skip_regexp_p (struct skiplist_entry *e, const char *function_name)
+{
+  gdb_assert (e->kind == SKIP_REGEXP);
+  return regexec (&e->regex, function_name, 0, NULL, 0) == 0;
+}

  /* See skip.h.  */

@@ -333,8 +532,7 @@  int
  function_name_is_marked_for_skip (const char *function_name,
  				  const struct symtab_and_line *function_sal)
  {
-  int searched_for_fullname = 0;
-  const char *fullname = NULL;
+  struct sal_and_fullname file_location = { function_sal, 0, NULL };
    struct skiplist_entry *e;

    if (function_name == NULL)
@@ -345,39 +543,22 @@  function_name_is_marked_for_skip (const char  
*function_name,
        if (!e->enabled)
  	continue;

-      /* Does the pc we're stepping into match e's stored pc? */
-      if (e->function_name != NULL
-	  && strcmp_iw (function_name, e->function_name) == 0)
-	return 1;
-
-      if (e->filename != NULL)
+      switch (e->kind)
  	{
-	  /* Check first sole SYMTAB->FILENAME.  It does not need to be
-	     a substring of symtab_to_fullname as it may contain "./" etc.  */
-	  if (function_sal->symtab != NULL
-	      && compare_filenames_for_search (function_sal->symtab->filename,
-					       e->filename))
+	case SKIP_FILE:
+	  if (skip_file_p (e, &file_location))
  	    return 1;
-
-	  /* Before we invoke realpath, which can get expensive when many
-	     files are involved, do a quick comparison of the basenames.  */
-	  if (!basenames_may_differ
-	      && (function_sal->symtab == NULL
-	          || filename_cmp (lbasename (function_sal->symtab->filename),
-				   lbasename (e->filename)) != 0))
-	    continue;
-
-	  /* Get the filename corresponding to this FUNCTION_SAL, if we haven't
-	     yet.  */
-	  if (!searched_for_fullname)
-	    {
-	      if (function_sal->symtab != NULL)
-		fullname = symtab_to_fullname (function_sal->symtab);
-	      searched_for_fullname = 1;
-	    }
-	  if (fullname != NULL
-	      && compare_filenames_for_search (fullname, e->filename))
+	  break;
+	case SKIP_FUNCTION:
+	  if (skip_function_p (e, function_name))
  	    return 1;
+	  break;
+	case SKIP_REGEXP:
+	  if (skip_regexp_p (e, function_name))
+	    return 1;
+	  break;
+	default:
+	  gdb_assert_not_reached ("bad skiplist_entry kind");
  	}
      }

@@ -416,6 +597,12 @@  If no function name is given, skip the current  
function."),
  	       &skiplist);
    set_cmd_completer (c, location_completer);

+  c = add_cmd ("regexp", class_breakpoint,
+	       skip_regexp_command, _("\
+Ignore a function while stepping.\n\
+Usage: skip regexp [FUNCTION-NAME-REGEXP]."),
+	       &skiplist);
+
    add_cmd ("enable", class_breakpoint, skip_enable_command, _("\
  Enable skip entries.  You can specify numbers (e.g. \"skip enable 1 3\"), \
  ranges (e.g. \"skip enable 4-8\"), or both (e.g. \"skip enable 1 3  
4-8\").\n\n\
diff --git a/gdb/testsuite/gdb.base/skip.exp  
b/gdb/testsuite/gdb.base/skip.exp
index 9fa4acf..cb88829 100644
--- a/gdb/testsuite/gdb.base/skip.exp
+++ b/gdb/testsuite/gdb.base/skip.exp
@@ -31,6 +31,11 @@  gdb_test "skip file" "No default file now." "skip file  
(no default file)"
  gdb_test "skip function" "No default function now."
  gdb_test "skip" "No default function now." "skip (no default function)"

+#
+# Regexps don't have a default, but we can still test an elided arg.
+#
+gdb_test "skip regexp" "Missing regexp."
+
  if ![runto_main] { fail "skip tests suppressed" }

  #
@@ -51,6 +56,12 @@  gdb_test "skip file skip1.c" "File .*$srcfile1 will be  
skipped when stepping\."
  gdb_test "skip function baz" "Function baz will be skipped when stepping\."

  #
+# Create a regexp of skipping baz, disabled until we need it so as to not
+# interfere with "skip function baz"
+gdb_test "skip regexp ^b.z$" "Functions matching regexp \\^b\\.z\\$ will  
be skipped when stepping."
+gdb_test "skip disable 5"
+
+#
  # Test bad skiplist entry modification commands
  #
  gdb_test "skip enable 999" "No skiplist entries found with number 999."
@@ -73,7 +84,8 @@  gdb_test "info skip" \
  1\\s+file\\s+y\\s+.*$srcfile\\s*
  2\\s+function\\s+y\\s+main\\s*
  3\\s+file\\s+y\\s+$srcfile1\\s*
-4\\s+function\\s+y\\s+baz\\s*"
+4\\s+function\\s+y\\s+baz\\s*
+5\\s+regexp\\s+n\\s+\\^b\\.z\\$\\s*"

  #
  # Right now, we have an outstanding skiplist entry on both source
@@ -96,7 +108,8 @@  gdb_test "info skip" \
    "Num\\s+Type\\s+Enb\\s+What\\s*
  2\\s+function\\s+y\\s+main\\s*
  3\\s+file\\s+y\\s+$srcfile1\\s*
-4\\s+function\\s+y\\s+baz\\s*" \
+4\\s+function\\s+y\\s+baz\\s*
+5\\s+regexp\\s+n\\s+\\^b\\.z\\$\\s*" \
      "info skip (delete 1)"

  if ![runto_main] { fail "skip tests suppressed" }
@@ -123,6 +136,22 @@  gdb_test "step" ".*" "$test (4)"; # Return from bar()
  gdb_test "step" "main \\(\\) at.*" "$test (5)"

  #
+# Repeat, but replace "skip function baz" (#4) with its regexp (#5).
+#
+gdb_test "skip disable 4"
+gdb_test "skip enable 5"
+if ![runto_main] { fail "skip tests suppressed" }
+set test "step using regexp for baz"
+gdb_test "step" "bar \\(\\) at.*" "$test (1)"
+gdb_test "step" ".*" "$test (2)"; # Return from foo()
+gdb_test "step" "foo \\(\\) at.*" "$test (3)"
+gdb_test "step" ".*" "$test (4)"; # Return from bar()
+gdb_test "step" "main \\(\\) at.*" "$test (5)"
+# Restore.
+gdb_test "skip enable 4"
+gdb_test "skip disable 5"
+
+#
  # Enable skiplist entry 3 and make sure we step over it like before.
  #
  gdb_test "skip enable 3"
@@ -140,7 +169,8 @@  gdb_test "info skip" \
    "Num\\s+Type\\s+Enb\\s+What\\s*
  2\\s+function\\s+n\\s+main\\s*
  3\\s+file\\s+n\\s+$srcfile1\\s*
-4\\s+function\\s+n\\s+baz\\s*" \
+4\\s+function\\s+n\\s+baz\\s*
+5\\s+regexp\\s+n\\s+\\^b\\.z\\$\\s*" \
    "info skip after disabling all"

  gdb_test "skip enable"
@@ -148,7 +178,8 @@  gdb_test "info skip" \
    "Num\\s+Type\\s+Enb\\s+What\\s*
  2\\s+function\\s+y\\s+main\\s*
  3\\s+file\\s+y\\s+$srcfile1\\s*
-4\\s+function\\s+y\\s+baz\\s*" \
+4\\s+function\\s+y\\s+baz\\s*
+5\\s+regexp\\s+y\\s+\\^b\\.z\\$\\s*" \
    "info skip after enabling all"

  gdb_test "skip disable 4 2-3"
@@ -156,7 +187,8 @@  gdb_test "info skip" \
    "Num\\s+Type\\s+Enb\\s+What\\s*
  2\\s+function\\s+n\\s+main\\s*
  3\\s+file\\s+n\\s+$srcfile1\\s*
-4\\s+function\\s+n\\s+baz\\s*" \
+4\\s+function\\s+n\\s+baz\\s*
+5\\s+regexp\\s+y\\s+\\^b\\.z\\$\\s*" \
    "info skip after disabling 4 2-3"

  gdb_test "skip enable 2-3"
@@ -164,7 +196,8 @@  gdb_test "info skip" \
    "Num\\s+Type\\s+Enb\\s+What\\s*
  2\\s+function\\s+y\\s+main\\s*
  3\\s+file\\s+y\\s+$srcfile1\\s*
-4\\s+function\\s+n\\s+baz\\s*" \
+4\\s+function\\s+n\\s+baz\\s*
+5\\s+regexp\\s+y\\s+\\^b\\.z\\$\\s*" \
    "info skip after enabling 2-3"

  gdb_test "info skip 2-3" \
@@ -173,7 +206,7 @@  gdb_test "info skip 2-3" \
  3\\s+file\\s+y\\s+$srcfile1\\s*" \
    "info skip 2-3"

-gdb_test "skip delete 2 3"
+gdb_test "skip delete 2 3 5"
  gdb_test "info skip" \
    "4\\s+function\\s+n\\s+baz\\s*" \
    "info skip after deleting 2 3"
diff --git a/gdb/testsuite/gdb.perf/skip-command.cc  
b/gdb/testsuite/gdb.perf/skip-command.cc
new file mode 100644
index 0000000..5820ef7
--- /dev/null
+++ b/gdb/testsuite/gdb.perf/skip-command.cc
@@ -0,0 +1,46 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+   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/>.   
*/
+
+volatile int flag;
+
+int
+func ()
+{
+  return 42;
+}
+
+class c
+{
+ public:
+  int _x;
+  c () : _x (42) {}
+};
+
+void
+call_me (int x, c y)
+{
+}
+
+int
+main ()
+{
+  while (flag)
+    {
+      call_me (func (), c ());
+    }
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.perf/skip-command.exp  
b/gdb/testsuite/gdb.perf/skip-command.exp
new file mode 100644
index 0000000..2a6fc81
--- /dev/null
+++ b/gdb/testsuite/gdb.perf/skip-command.exp
@@ -0,0 +1,130 @@ 
+# Copyright (C) 2016 Free Software Foundation, Inc.
+
+# 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/>.
+
+# This test case is to test the speed of GDB when it is single-stepping
+# with skip directives active. There's no need to test skip directives that
+# match functions we're stepping through. That's not the interesting case.
+# The interesting case is where there are 100s or more classes or  
libraries,
+# each providing their own set of skip directives.
+#
+# Parameters:
+# SKIP_STEP_COUNT: the number of single steps GDB performs
+# SKIP_DIRECTIVE_COUNT: the number of skip directives to create
+
+load_lib perftest.exp
+
+if [skip_perf_tests] {
+    return 0
+}
+
+standard_testfile .cc skip-funcs.cc
+set executable $testfile
+set skip_func_file [standard_output_file $srcfile2]
+set expfile $testfile.exp
+
+# make check-perf RUNTESTFLAGS='skip-command.exp SKIP_STEP_COUNT=1000 ...'
+if ![info exists SKIP_STEP_COUNT] {
+    set SKIP_STEP_COUNT 1000
+}
+if ![info exists SKIP_DIRECTIVE_COUNT] {
+    set SKIP_DIRECTIVE_COUNT 1000
+}
+
+proc delete_all_skips { } {
+    # FIXME: skip currently doesn't ask for confirmation
+    # FIXME: "skip delete" with no skips ->
+    #   "No skiplist entries found with number (null)."
+    gdb_test_no_output "set confirm off"
+    gdb_test "skip delete" ""
+    gdb_test_no_output "set confirm on"
+}
+
+proc install_skips { kind text nr_skips } {
+    global gdb_prompt
+    set test "install_skips"
+    delete_all_skips
+     for { set i 0 } { $i < $nr_skips } { incr i } {
+	gdb_test "skip $kind [format $text $i]" ""
+    }
+    # There could be 1000's of these, which can overflow the buffer.
+    # However, it's good to have this in the log, so we go to the effort
+    # to read it all in.
+    gdb_test_multiple "info skip" $test {
+	-re "\[^\r\n\]*\r\n" { exp_continue }
+	-re "\[\r\n\]*$gdb_prompt $" {
+	    pass $test
+	}
+	timeout {
+	    fail "$test (timeout)"
+	}
+    }
+}
+
+proc write_skip_func_source { file_name func_name_prefix nr_funcs } {
+    set f [open $file_name "w"]
+    puts $f "// DO NOT EDIT, machine generated file.  See  
skip-command.exp."
+    for { set i 0 } { $i < $nr_funcs } { incr i } {
+	set func_name [format "${func_name_prefix}_%02d" $i]
+	puts $f "int $func_name () { return 0; }"
+    }
+    close $f
+}
+
+proc run_skip_bench { kind text } {
+    global SKIP_STEP_COUNT SKIP_DIRECTIVE_COUNT
+
+    if ![runto_main] {
+	fail "Can't run to main"
+	return -1
+    }
+
+    gdb_test_no_output "set variable flag = 1"
+
+    for { set i 0 } { $i < 5 } { incr i } {
+	set nr_skips [expr $i * $SKIP_DIRECTIVE_COUNT]
+	install_skips $kind $text $nr_skips
+	gdb_test_no_output "python SkipCommand\(\"skip-$kind-$nr_skips\",  
${SKIP_STEP_COUNT}\).run()"
+    }
+
+    gdb_test "set variable flag = 0"
+}
+
+PerfTest::assemble {
+    global srcdir subdir srcfile binfile skip_func_file
+    global SKIP_DIRECTIVE_COUNT
+
+    write_skip_func_source $skip_func_file "skip_func" [expr 4 *  
$SKIP_DIRECTIVE_COUNT]
+    if { [gdb_compile [list "$srcdir/$subdir/$srcfile" $skip_func_file]  
${binfile} executable {c++ debug}] != "" } {
+	return -1
+    }
+    return 0
+} {
+    global binfile
+    clean_restart $binfile
+    return 0
+} {
+    global SKIP_STEP_COUNT SKIP_DIRECTIVE_COUNT
+
+    with_test_prefix "time_skip_func" {
+	# N.B. The function name must match the ones in skip-command.cc.
+	run_skip_bench "function" "skip_func_%02d"
+    }
+
+    with_test_prefix "time_skip_constructor" {
+	run_skip_bench "regexp" "^(skip_class_%02d)::\\1 *\\("
+    }
+
+    return 0
+}
diff --git a/gdb/testsuite/gdb.perf/skip-command.py  
b/gdb/testsuite/gdb.perf/skip-command.py
new file mode 100644
index 0000000..12c28f2
--- /dev/null
+++ b/gdb/testsuite/gdb.perf/skip-command.py
@@ -0,0 +1,34 @@ 
+# Copyright (C) 2016 Free Software Foundation, Inc.
+
+# 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/>.
+
+from perftest import perftest
+
+class SkipCommand (perftest.TestCaseWithBasicMeasurements):
+    def __init__(self, name, step):
+        super (SkipCommand, self).__init__ (name)
+        self.step = step
+
+    def warm_up(self):
+        for _ in range(0, 10):
+            gdb.execute("step", False, True)
+
+    def _run(self, r):
+        for _ in range(0, r):
+            gdb.execute("step", False, True)
+
+    def execute_test(self):
+        for i in range(1, 5):
+            func = lambda: self._run(i * self.step)
+            self.measure.measure(func, i * self.step)