diff mbox

[doc,RFA] Add "skip regexp"

Message ID 001a113452243e6481052bece0b3@google.com
State New
Headers show

Commit Message

Doug Evans Feb. 17, 2016, 1:07 a.m. UTC
Eli: Heads up, rewritten docs, needing re-approval.

Pedro Alves writes:
  > On 02/02/2016 01:03 AM, Doug Evans wrote:

  > > Hi.

  > >

  > > The "skip" command is great, but it can be really cumbersome to use  

with
  > > c++.

  > >

  > > E.g., consider stepping into functions that take std::string arguments.

  > >

  > >    foo (bar ("abc"));

  > >

  > > where bar takes a std::string argument.

  > >

  > > There are four functions you generally always want to skip in this case

  > > (cut-n-pasted from my gdb session):

  > >

  > > std::allocator<char>::allocator (this=0x7fffffffe6bf)

  > > std::basic_string<char, std::char_traits<char>, std::allocator<char>

  > >> ::basic_string (this=0x7fffffffe6b0, __s=0x400ad1 "abc", __a=...)

  > > std::basic_string<char, std::char_traits<char>, std::allocator<char>

  > >> ::~basic_string (this=0x7fffffffe6b0, __in_chrg=<optimized out>)

  > > std::allocator<char>::~allocator (this=0x7fffffffe6bf,  

__in_chrg=<optimized
  > > out>)

  > >

  > > With this patch one can specify the skip as:

  > >

  > > skip regexp ^std::(allocator|basic_string)<.*>::~?\1 *\(

  > >

  > > Skipping every templated class constructor/destructor in std

  > > could be specified as:

  > >

  > > skip regexp ^std::([a-zA-z0-9_]+)<.*>::~?\1 *\(

  >

  > I think this is great.  Thanks for tackling this.  This was

  > originally discussed in the context of the original

  > implementation, but was left out:

  >

  >  https://sourceware.org/bugzilla/show_bug.cgi?id=8287

  >

  > I wonder about the UI though.  The command name doesn't make it

  > clear, so I was wondered whether "skip regexp" also matches the

  > filename the function is implemented in.  I figured out it doesn't

  > from the documentation, but it got to me consider how we'd extend

  > the UI if/when we want to skip files with a regexp too, as

  > then "skip regexp" becomes ambiguous.  Maybe "skip rfunction" instead,

  > and then we'd have "skip rfile" ?


I started out with something like that, but regular expressions
of files runs into basenames_may_differ, and I didn't want to get
bogged down with how to deal with that (I don't want to give up
basenames_may_differ here, and that suggests separating the directory
regexp from the filename regexp). I hadn't thought of having
a name like "rfunction" though. Fine by me if it's ok with everyone else.

  > Also, ot asking you to implement this, just thinking out loud, but

  > I also wondered if it wouldn't have made more sense to be able

  > to mix file and function names, using command options, rather than

  > have distinct skip types.  Like:

  >

  >  skip -file foo.c -function foo

  >  skip -rfile libfoo/*.c -rfunction ^foo_.*

  >

  > I guess we could still have that and leave "skip function", etc.

  > for compatibility.


IWBN alright, but then (IMO) we have to go all out into supporting
embedded spaces, quoting, etc. If we're ok with imposing what buildargv
will require on users then ok, I don't want to have to code anything else.
It will impose on users various quoting rules (e.g., \ becomes \\,
and so on) whereas "skip rfunction blah" can just say that blah is
taken "as is" (setting aside leading/trailing whitespace which I'm ok with).

Plus we'll have to think about the syntax for file regular expressions.
Supporting foo.*/bar.* is out IMO, we need -rdirectory foo.* -rfile bar.*.
As discussed on IRC, glob-style patterns for file names is ok with you,
so the patch below uses -gfile.
I know we talked about having -file take either,
but it was simpler/clearer to distinguish -file from -gfile.

  > > +skip regexp regular-expression

  > > +  A variation of "skip function" where the function name is specified

  > > +  as a regular expression.

  > > +

  >

  >

  >

  > > +If you wanted to skip every C++ constructor and destructor in the

  > > @code{std}

  > > +namespace you could do:

  >

  > Using past tense reads a bit odd to me.  I'd suggest:

  >

  >  If you want to skip every C++ constructor and destructor in the

  >  @code{std} namespace you can do:

  >

  > >

  > > +/* 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");

  > > +    }

  >

  > Line breaks before return.

  >

  > > +}

  > > +

  > > +/* 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;

  > > +}

  > > +

  >

  > Seems like all these three functions could be replaced by

  > a single:

  >

  > static struct skiplist_entry *

  > make_skiplist_entry (enum skip_kind kind, const char *text)

  > {

  >    struct skiplist_entry *e = XCNEW (struct skiplist_entry);

  >

  >   e->kind = kind;

  >   e->text = xstrdup (text);

  >   e->enabled = 1;

  >

  >   return e;

  > }


The functions in earlier versions of the patch didn't all
take the same arguments, and I like the extra bit of
abstraction. But ok.

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

	Extend "skip" command to support -file, -gfile, -function, -rfunction.
	* NEWS: Document new features.
	* skip.c: #include "fnmatch.h", "gdb_regex.h".
	(skiplist_entry) <file>: Renamed from filename.
	<function>: Renamed from function_name.
	<file_is_glob, function_is_regexp>: New members.
	<compiled_function_regexp, compiled_function_regexp_is_valid>:
	New members.
	(make_skip_entry): New function.
	(free_skiplist_entry, free_skiplist_entry_cleanup): New functions.
	(make_free_skiplist_entry_cleanup): New function.
	(skip_file_command): Update.
	(skip_function, skip_function_command): Update.
	(compile_skip_regexp): New functions.
	(skip_command): Add support for new options.
	(skip_info): Update.
	(skip_file_p, skip_gfile_p): New functions.
	(skip_function_p, skip_rfunction_p): New functions.
	(function_name_is_marked_for_skip): Update and simplify.
	(_initialize_step_skip): Update.
	* symtab.c: #include "fnmatch.h".
	(compare_glob_filenames_for_search): New function.
	* symtab.h (compare_glob_filenames_for_search): Declare.
	* utils.c (count_path_elements): New function.
	(strip_leading_path_elements): New function.
	* utils.h (count_path_elements): Declare.
	(strip_leading_path_elements): Declare.

	doc/
	* gdb.texinfo (Skipping Over Functions and Files): Document new
	options to "skip" command.  Update docs of output of "info skip".

	testsuite/
	* gdb.base/skip.c (test_skip): New function.
	(end_test_skip_file_and_function): New function.
	(test_skip_file_and_function): New function.
	* gdb.base/skip1.c (test_skip): New function.
	(skip1_test_skip_file_and_function): New function.
	* gdb.base/skip.exp: Add tests for new skip options.
	* gdb.perf/skip-command.cc: New file.
	* gdb.perf/skip-command.exp: New file.
	* gdb.perf/skip-command.py: New file.

Comments

Eli Zaretskii Feb. 17, 2016, 4:07 p.m. UTC | #1
> Date: Wed, 17 Feb 2016 01:07:38 +0000
> From: Doug Evans <dje@google.com>
> Cc: gdb-patches@sourceware.org
> 
> Eli: Heads up, rewritten docs, needing re-approval.

Thanks for the heads-up.

> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -127,6 +127,14 @@ show max-value-size
>     allocate for value contents.  Prevents incorrect programs from
>     causing GDB to allocate overly large buffers.  Default is 64k.
> 
> +skip -file file
> +skip -gfile file-glob-pattern
> +skip -function function
> +skip -rfunction regular-expression
> +  A generalized form of the skip command, with new support for
> +  glob-style file names and regular expressions for function names.
> +  Additionally, a file spec and a function spec may now be combined.
> +

This part is OK.

> +@item -gfile @var{file-glob-pattern}
> +@itemx -gfi @var{file-glob-pattern}
> +Functions in files matching @var{file-glob-pattern} will be skipped
> +over when stepping.
> +
> +@smallexample
> +(gdb) skip -gfi utils/*.c
> +@end smallexample

Is there a way to protect wildcard characters?  If so, I think we
should mention it.

> +For example, there is generally no need to step into C++ std::string

C@t{++}, and "std::string" should be in @code.

Also, this description should have a couple of @cindex entries, as
readers are likely to look for it without remembering that the
command's name is "skip".

The documentation is OK with those fixed.

Thanks.
Simon Marchi Feb. 29, 2016, 6:55 p.m. UTC | #2
On 16-02-16 08:07 PM, Doug Evans wrote:
> +# Test -fi + -fu.
> +
> +if ![runto_main] {
> +    fail "Can't run to main"
> +    return
> +}
> +
> +set test "step using -fi + -fu"
> +gdb_test_no_output "skip delete"
> +gdb_test "skip -fi skip1.c -fu test_skip" \
> +    "Function test_skip in file skip1.c will be skipped when stepping\."
> +gdb_breakpoint "test_skip_file_and_function"
> +gdb_breakpoint "end_test_skip_file_and_function"
> +gdb_test "call test_skip_file_and_function ()" "silently stop."

Hi Doug,

I just saw a failure of this test on a target that doesn't have inferior calls.  It
seems to me like the function call isn't fundamental to the test and it could be
avoided by organizing things differently.  What do you think?

Otherwise, we would need to add a

  if [target_info exists gdb,cannot_call_functions] {

Simon
diff mbox

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 482bec6..9175ddbf 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -127,6 +127,14 @@  show max-value-size
    allocate for value contents.  Prevents incorrect programs from
    causing GDB to allocate overly large buffers.  Default is 64k.

+skip -file file
+skip -gfile file-glob-pattern
+skip -function function
+skip -rfunction regular-expression
+  A generalized form of the skip command, with new support for
+  glob-style file names and regular expressions for function names.
+  Additionally, a file spec and a function spec may now be combined.
+
  * 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 9db234e..2509293 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -5528,7 +5528,8 @@  default is @code{on}.

  The program you are debugging may contain some functions which are
  uninteresting to debug.  The @code{skip} command lets you tell  
@value{GDBN} to
-skip a function or all functions in a file when stepping.
+skip a function, all functions in a file or a particular function in
+a particular file when stepping.

  For example, consider the following C function:

@@ -5555,13 +5556,73 @@  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}), regular expression that matches the function's
+name, file name or a @code{glob}-style pattern that matches the file name.
+
+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.
+See for example @samp{man 7 glob} on @sc{gnu}/Linux systems for a
+description of @code{glob}-style patterns.
+
+@table @code
+@kindex skip
+@item skip @r{[}@var{options}@r{]}
+The basic form of the @code{skip} command takes zero or more options
+that specify what to skip.
+The @var{options} argument is any useful combination of the following:

  @table @code
+@item -file @var{file}
+@itemx -fi @var{file}
+Functions in @var{file} will be skipped over when stepping.
+
+@item -gfile @var{file-glob-pattern}
+@itemx -gfi @var{file-glob-pattern}
+Functions in files matching @var{file-glob-pattern} will be skipped
+over when stepping.
+
+@smallexample
+(gdb) skip -gfi utils/*.c
+@end smallexample
+
+@item -function @var{linespec}
+@itemx -fu @var{linespec}
+Functions named by @var{linespec} or the function containing the line
+named by @var{linespec} will be skipped over when stepping.
+@xref{Specify Location}.
+
+@item -rfunction @var{regexp}
+@itemx -rfu @var{regexp}
+Functions whose name matches @var{regexp} 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.
+
+@smallexample
+(gdb) skip -rfu ^std::(allocator|basic_string)<.*>::~?\1 *\(
+@end smallexample
+
+If you want to skip every templated C++ constructor and destructor
+in the @code{std} namespace you can do:
+
+@smallexample
+(gdb) skip -rfu ^std::([a-zA-z0-9_]+)<.*>::~?\1 *\(
+@end smallexample
+@end table
+
+If no options are specified, the function you're currently debugging
+will be skipped.
+
  @kindex skip function
-@item skip @r{[}@var{linespec}@r{]}
-@itemx skip function @r{[}@var{linespec}@r{]}
+@item skip function @r{[}@var{linespec}@r{]}
  After running this command, the function named by @var{linespec} or the
  function containing the line named by @var{linespec} will be skipped over  
when
  stepping.  @xref{Specify Location}.
@@ -5577,6 +5638,11 @@  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.
  @end table
@@ -5594,20 +5660,21 @@  print a table with details about all functions and  
files marked for skipping.
  @table @emph
  @item Identifier
  A number identifying this skip.
-@item Type
-The type of this skip, either @samp{function} or @samp{file}.
  @item Enabled or Disabled
-Enabled skips are marked with @samp{y}.  Disabled skips are marked with  
@samp{n}.
-@item Address
-For function skips, this column indicates the address in memory of the  
function
-being skipped.  If you've set a function skip on a function which has not  
yet
-been loaded, this field will contain @samp{<PENDING>}.  Once a shared  
library
-which has the function is loaded, @code{info skip} will show the function's
-address here.
-@item What
-For file skips, this field contains the filename being skipped.  For  
functions
-skips, this field contains the function name and its line number in the  
file
-where it is defined.
+Enabled skips are marked with @samp{y}.
+Disabled skips are marked with @samp{n}.
+@item Glob
+If the file name is a @samp{glob} pattern this is @samp{y}.
+Otherwise it is @samp{n}.
+@item File
+The name or @samp{glob} pattern of the file to be skipped.
+If no file is specified this is @samp{<none>}.
+@item RE
+If the function name is a @samp{regular expression} this is @samp{y}.
+Otherwise it is @samp{n}.
+@item Function
+The name or regular expression of the function to skip.
+If no function is specified this is @samp{<none>}.
  @end table

  @kindex skip delete
diff --git a/gdb/skip.c b/gdb/skip.c
index d90910d..15681cc 100644
--- a/gdb/skip.c
+++ b/gdb/skip.c
@@ -32,19 +32,35 @@ 
  #include "breakpoint.h" /* for get_sal_arch () */
  #include "source.h"
  #include "filenames.h"
+#include "fnmatch.h"
+#include "gdb_regex.h"

  struct skiplist_entry
  {
    int number;

-  /* NULL if this isn't a skiplist entry for an entire file.
+  /* Non-zero if FILE is a glob-style pattern.
+     Otherewise it is the plain file name (possibly with directories).  */
+  int file_is_glob;
+
+  /* The name of the file or NULL.
       The skiplist entry owns this pointer.  */
-  char *filename;
+  char *file;
+
+  /* Non-zero if FUNCTION is a regexp.
+     Otherwise it is a plain function name (possibly with arguments,
+     for C++).  */
+  int function_is_regexp;

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

    int enabled;

@@ -52,7 +68,6 @@  struct skiplist_entry
  };

  static void add_skiplist_entry (struct skiplist_entry *e);
-static void skip_function (const char *name);

  static struct skiplist_entry *skiplist_entry_chain;
  static int skiplist_entry_count;
@@ -65,10 +80,62 @@  static int skiplist_entry_count;
         E ? (TMP = E->next, 1) : 0;       \
         E = TMP)

+/* Create a skip object.  */
+
+static struct skiplist_entry *
+make_skip_entry (int file_is_glob, const char *file,
+		 int function_is_regexp, const char *function)
+{
+  struct skiplist_entry *e = XCNEW (struct skiplist_entry);
+
+  gdb_assert (file != NULL || function != NULL);
+  if (file_is_glob)
+    gdb_assert (file != NULL);
+  if (function_is_regexp)
+    gdb_assert (function != NULL);
+
+  if (file != NULL)
+    e->file = xstrdup (file);
+  if (function != NULL)
+    e->function = xstrdup (function);
+  e->file_is_glob = file_is_glob;
+  e->function_is_regexp = function_is_regexp;
+  e->enabled = 1;
+
+  return e;
+}
+
+/* Free a skiplist entry.  */
+
+static void
+free_skiplist_entry (struct skiplist_entry *e)
+{
+  xfree (e->file);
+  xfree (e->function);
+  if (e->function_is_regexp && e->compiled_function_regexp_is_valid)
+    regfree (&e->compiled_function_regexp);
+  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;

@@ -85,37 +152,31 @@  skip_file_command (char *arg, int from_tty)
        filename = symtab_to_fullname (symtab);
      }
    else
-    {
-      symtab = lookup_symtab (arg);
-      if (symtab == NULL)
-	{
-	  fprintf_filtered (gdb_stderr, _("No source file named %s.\n"), arg);
-	  if (!nquery (_("\
-Ignore file pending future shared library load? ")))
-	    return;
-	}
-      /* Do not use SYMTAB's filename, later loaded shared libraries may  
match
-         given ARG but not SYMTAB's filename.  */
-      filename = arg;
-    }
+    filename = arg;

-  e = XCNEW (struct skiplist_entry);
-  e->filename = xstrdup (filename);
-  e->enabled = 1;
-
-  add_skiplist_entry (e);
+  add_skiplist_entry (make_skip_entry (0, filename, 0, NULL));

    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_command (char *arg, int from_tty)
+skip_function (const char *name)
  {
-  const char *name = NULL;
+  add_skiplist_entry (make_skip_entry (0, NULL, 0, name));
+
+  printf_filtered (_("Function %s will be skipped when stepping.\n"),  
name);
+}

+static void
+skip_function_command (char *arg, int from_tty)
+{
    /* Default to the current function if no argument is given.  */
    if (arg == NULL)
      {
+      const char *name = NULL;
        CORE_ADDR pc;

        if (!last_displayed_sal_is_valid ())
@@ -128,25 +189,169 @@  skip_function_command (char *arg, int from_tty)
  		  paddress (get_current_arch (), pc));
  	}
        skip_function (name);
+      return;
      }
-  else
+
+  skip_function (arg);
+}
+
+/* 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->function_is_regexp && e->function != NULL);
+
+  code = regcomp (&e->compiled_function_regexp, e->function, flags);
+  if (code != 0)
      {
-      if (lookup_symbol (arg, NULL, VAR_DOMAIN, NULL).symbol == NULL)
-        {
-	  fprintf_filtered (gdb_stderr,
-			    _("No function found named %s.\n"), arg);
+      char *err = get_regcomp_error (code, &e->compiled_function_regexp);

-	  if (nquery (_("\
-Ignore function pending future shared library load? ")))
-	    {
-	      /* Add the unverified skiplist entry.  */
-	      skip_function (arg);
-	    }
+      make_cleanup (xfree, err);
+      error (_("%s: %s"), message, err);
+    }
+  e->compiled_function_regexp_is_valid = 1;
+}
+
+/* Process "skip ..." that does not match "skip file" or "skip function".   
*/
+
+static void
+skip_command (char *arg, int from_tty)
+{
+  const char *file = NULL;
+  const char *gfile = NULL;
+  const char *function = NULL;
+  const char *rfunction = NULL;
+  char **argv;
+  struct cleanup *cleanups;
+  struct skiplist_entry *e;
+  int i;
+
+  if (arg == NULL)
+    {
+      skip_function_command (arg, from_tty);
+      return;
+    }
+
+  argv = buildargv (arg);
+  cleanups = make_cleanup_freeargv (argv);
+
+  for (i = 0; argv[i] != NULL; ++i)
+    {
+      const char *p = argv[i];
+      const char *value = argv[i + 1];
+
+      if (strcmp (p, "-fi") == 0
+	  || strcmp (p, "-file") == 0)
+	{
+	  if (value == NULL)
+	    error (_("Missing value for %s option."), p);
+	  file = value;
+	  ++i;
+	}
+      else if (strcmp (p, "-gfi") == 0
+	       || strcmp (p, "-gfile") == 0)
+	{
+	  if (value == NULL)
+	    error (_("Missing value for %s option."), p);
+	  gfile = value;
+	  ++i;
+	}
+      else if (strcmp (p, "-fu") == 0
+	       || strcmp (p, "-function") == 0)
+	{
+	  if (value == NULL)
+	    error (_("Missing value for %s option."), p);
+	  function = value;
+	  ++i;
+	}
+      else if (strcmp (p, "-rfu") == 0
+	       || strcmp (p, "-rfunction") == 0)
+	{
+	  if (value == NULL)
+	    error (_("Missing value for %s option."), p);
+	  rfunction = value;
+	  ++i;
+	}
+      else if (*p == '-')
+	error (_("Invalid skip option: %s"), p);
+      else if (i == 0)
+	{
+	  /* Assume the user entered "skip FUNCTION-NAME".
+	     FUNCTION-NAME may be `foo (int)', and therefore we pass the
+	     complete original arg to skip_function command as if the user
+	     typed "skip function arg".  */
+	  do_cleanups (cleanups);
+	  skip_function_command (arg, from_tty);
  	  return;
  	}
+      else
+	error (_("Invalid argument: %s"), p);
+    }
+
+  if (file != NULL && gfile != NULL)
+    error (_("Cannot specify both -file and -gfile."));
+
+  if (function != NULL && rfunction != NULL)
+    error (_("Cannot specify both -function and -rfunction."));
+
+  /* This shouldn't happen as "skip" by itself gets punted to
+     skip_function_command.  */
+  gdb_assert (file != NULL || gfile != NULL
+	      || function != NULL || rfunction != NULL);
+
+  e = make_skip_entry (gfile != NULL, file ? file : gfile,
+		       rfunction != NULL, function ? function : rfunction);
+  if (rfunction != NULL)
+    {
+      struct cleanup *rf_cleanups = make_free_skiplist_entry_cleanup (e);

-      skip_function (arg);
+      compile_skip_regexp (e, _("regexp"));
+      discard_cleanups (rf_cleanups);
      }
+  add_skiplist_entry (e);
+
+  /* I18N concerns drive some of the choices here (we can't piece together
+     the output too much).  OTOH we want to keep this simple.  Therefore  
the
+     only polish we add to the output is to append "(s)" to "File" or
+     "Function" if they're a glob/regexp.  */
+  {
+    const char *file_to_print = file != NULL ? file : gfile;
+    const char *function_to_print = function != NULL ? function :  
rfunction;
+    const char *file_text = gfile != NULL ? _("File(s)") : _("File");
+    const char *lower_file_text = gfile != NULL ? _("file(s)") : _("file");
+    const char *function_text
+      = rfunction != NULL ? _("Function(s)") : _("Function");
+
+    if (function_to_print == NULL)
+      {
+	printf_filtered (_("%s %s will be skipped when stepping.\n"),
+			 file_text, file_to_print);
+      }
+    else if (file_to_print == NULL)
+      {
+	printf_filtered (_("%s %s will be skipped when stepping.\n"),
+			 function_text, function_to_print);
+      }
+    else
+      {
+	printf_filtered (_("%s %s in %s %s will be skipped"
+			   " when stepping.\n"),
+			 function_text, function_to_print,
+			 lower_file_text, file_to_print);
+      }
+  }
+
+  do_cleanups (cleanups);
  }

  static void
@@ -177,14 +382,17 @@  Not skipping any files or functions.\n"));
        return;
      }

-  tbl_chain = make_cleanup_ui_out_table_begin_end (current_uiout, 4,
+  tbl_chain = make_cleanup_ui_out_table_begin_end (current_uiout, 6,
  						   num_printable_entries,
  						   "SkiplistTable");

-  ui_out_table_header (current_uiout, 7, ui_left, "number", "Num");       
/* 1 */
-  ui_out_table_header (current_uiout, 14, ui_left, "type", "Type");       
/* 2 */
-  ui_out_table_header (current_uiout, 3, ui_left, "enabled", "Enb");      
/* 3 */
-  ui_out_table_header (current_uiout, 40, ui_noalign, "what", "What");    
/* 4 */
+  ui_out_table_header (current_uiout, 5, ui_left, "number", "Num");   /* 1  
*/
+  ui_out_table_header (current_uiout, 3, ui_left, "enabled", "Enb");  /* 2  
*/
+  ui_out_table_header (current_uiout, 4, ui_right, "regexp", "Glob"); /* 3  
*/
+  ui_out_table_header (current_uiout, 20, ui_left, "file", "File");   /* 4  
*/
+  ui_out_table_header (current_uiout, 2, ui_right, "regexp", "RE");   /* 5  
*/
+  ui_out_table_header (current_uiout, 40, ui_noalign,
+		       "function", "Function"); /* 6 */
    ui_out_table_body (current_uiout);

    ALL_SKIPLIST_ENTRIES (e)
@@ -197,25 +405,27 @@  Not skipping any files or functions.\n"));

        entry_chain = make_cleanup_ui_out_tuple_begin_end (current_uiout,
  							 "blklst-entry");
-      ui_out_field_int (current_uiout, "number", e->number);              
/* 1 */
+      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 */
+      if (e->enabled)
+	ui_out_field_string (current_uiout, "enabled", "y"); /* 2 */
        else
-	internal_error (__FILE__, __LINE__, _("\
-Skiplist entry should have either a filename or a function name."));
+	ui_out_field_string (current_uiout, "enabled", "n"); /* 2 */

-      if (e->enabled)
-	ui_out_field_string (current_uiout, "enabled", "y");             /* 3 */
+      if (e->file_is_glob)
+	ui_out_field_string (current_uiout, "regexp", "y"); /* 3 */
        else
-	ui_out_field_string (current_uiout, "enabled", "n");             /* 3 */
+	ui_out_field_string (current_uiout, "regexp", "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, "file",
+			   e->file ? e->file : "<none>"); /* 4 */
+      if (e->function_is_regexp)
+	ui_out_field_string (current_uiout, "regexp", "y"); /* 5 */
+      else
+	ui_out_field_string (current_uiout, "regexp", "n"); /* 5 */
+
+      ui_out_field_string (current_uiout, "function",
+			   e->function ? e->function : "<none>"); /* 6 */

        ui_out_text (current_uiout, "\n");
        do_cleanups (entry_chain);
@@ -273,9 +483,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 +495,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 +518,98 @@  add_skiplist_entry (struct skiplist_entry *e)
      }
  }

+/* Return non-zero if we're stopped at a file to be skipped.  */
+
+static int
+skip_file_p (struct skiplist_entry *e,
+	     const struct symtab_and_line *function_sal)
+{
+  gdb_assert (e->file != NULL && !e->file_is_glob);
+
+  if (function_sal->symtab == NULL)
+    return 0;
+
+  /* Check first sole SYMTAB->FILENAME.  It may not be a substring of
+     symtab_to_fullname as it may contain "./" etc.  */
+  if (compare_filenames_for_search (function_sal->symtab->filename,  
e->file))
+    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
+      && filename_cmp (lbasename (function_sal->symtab->filename),
+		       lbasename (e->file)) != 0)
+    return 0;
+
+  /* Note: symtab_to_fullname caches its result, thus we don't have to.  */
+  {
+    const char *fullname = symtab_to_fullname (function_sal->symtab);
+
+    if (compare_filenames_for_search (fullname, e->file))
+      return 1;
+  }
+
+  return 0;
+}
+
+/* Return non-zero if we're stopped at a globbed file to be skipped.  */
+
+static int
+skip_gfile_p (struct skiplist_entry *e,
+	      const struct symtab_and_line *function_sal)
+{
+  gdb_assert (e->file != NULL && e->file_is_glob);
+
+  if (function_sal->symtab == NULL)
+    return 0;
+
+  /* Check first sole SYMTAB->FILENAME.  It may not be a substring of
+     symtab_to_fullname as it may contain "./" etc.  */
+  if (gdb_filename_fnmatch (e->file, function_sal->symtab->filename,
+			    FNM_FILE_NAME | FNM_NOESCAPE) == 0)
+    return 1;
+
+  /* Before we invoke symtab_to_fullname, which is expensive, do a quick
+     comparison of the basenames.
+     Note that we assume that lbasename works with glob-style patterns.
+     If the basename of the glob pattern is something like "*.c" then this
+     isn't much of a win.  Oh well.  */
+  if (!basenames_may_differ
+      && gdb_filename_fnmatch (lbasename (e->file),
+			       lbasename (function_sal->symtab->filename),
+			       FNM_FILE_NAME | FNM_NOESCAPE) != 0)
+    return 0;
+
+  /* Note: symtab_to_fullname caches its result, thus we don't have to.  */
+  {
+    const char *fullname = symtab_to_fullname (function_sal->symtab);
+
+    if (compare_glob_filenames_for_search (fullname, e->file))
+      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->function != NULL && !e->function_is_regexp);
+  return strcmp_iw (function_name, e->function) == 0;
+}
+
+/* Return non-zero if we're stopped at a function regexp to be skipped.  */
+
+static int
+skip_rfunction_p (struct skiplist_entry *e, const char *function_name)
+{
+  gdb_assert (e->function != NULL && e->function_is_regexp
+	      && e->compiled_function_regexp_is_valid);
+  return (regexec (&e->compiled_function_regexp, function_name, 0, NULL, 0)
+	  == 0);
+}

  /* See skip.h.  */

@@ -333,8 +617,6 @@  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 skiplist_entry *e;

    if (function_name == NULL)
@@ -342,43 +624,49 @@  function_name_is_marked_for_skip (const char  
*function_name,

    ALL_SKIPLIST_ENTRIES (e)
      {
+      int skip_by_file = 0;
+      int skip_by_function = 0;
+
        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)
+      if (e->file != NULL)
  	{
-	  /* 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))
-	    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 (e->file_is_glob)
+	    {
+	      if (skip_gfile_p (e, function_sal))
+		skip_by_file = 1;
+	    }
+	  else
  	    {
-	      if (function_sal->symtab != NULL)
-		fullname = symtab_to_fullname (function_sal->symtab);
-	      searched_for_fullname = 1;
+	      if (skip_file_p (e, function_sal))
+		skip_by_file = 1;
  	    }
-	  if (fullname != NULL
-	      && compare_filenames_for_search (fullname, e->filename))
+	}
+      if (e->function != NULL)
+	{
+	  if (e->function_is_regexp)
+	    {
+	      if (skip_rfunction_p (e, function_name))
+		skip_by_function = 1;
+	    }
+	  else
+	    {
+	      if (skip_function_p (e, function_name))
+		skip_by_function = 1;
+	    }
+	}
+
+      /* If both file and function must match, make sure we don't errantly
+	 exit if only one of them match.  */
+      if (e->file != NULL && e->function != NULL)
+	{
+	  if (skip_by_file && skip_by_function)
  	    return 1;
  	}
+      /* Only one of file/function is specified.  */
+      else if (skip_by_file || skip_by_function)
+	return 1;
      }

    return 0;
@@ -396,22 +684,31 @@  _initialize_step_skip (void)
    skiplist_entry_chain = 0;
    skiplist_entry_count = 0;

-  add_prefix_cmd ("skip", class_breakpoint, skip_function_command, _("\
+  add_prefix_cmd ("skip", class_breakpoint, skip_command, _("\
  Ignore a function while stepping.\n\
-Usage: skip [FUNCTION NAME]\n\
-If no function name is given, ignore the current function."),
+\n\
+Usage: skip [FUNCTION-NAME]\n\
+       skip [<file-spec>] [<function-spec>]\n\
+If no arguments are given, ignore the current function.\n\
+\n\
+<file-spec> is one of:\n\
+       -fi|-file FILE-NAME\n\
+       -gfi|-gfile GLOB-FILE-PATTERN\n\
+<function-spec> is one of:\n\
+       -fu|-function FUNCTION-NAME\n\
+       -rfu|-rfunction FUNCTION-NAME-REGULAR-EXPRESSION"),
                    &skiplist, "skip ", 1, &cmdlist);

    c = add_cmd ("file", class_breakpoint, skip_file_command, _("\
  Ignore a file while stepping.\n\
-Usage: skip file [FILENAME]\n\
+Usage: skip file [FILE-NAME]\n\
  If no filename is given, ignore the current file."),
  	       &skiplist);
    set_cmd_completer (c, filename_completer);

    c = add_cmd ("function", class_breakpoint, skip_function_command, _("\
  Ignore a function while stepping.\n\
-Usage: skip function [FUNCTION NAME]\n\
+Usage: skip function [FUNCTION-NAME]\n\
  If no function name is given, skip the current function."),
  	       &skiplist);
    set_cmd_completer (c, location_completer);
diff --git a/gdb/symtab.c b/gdb/symtab.c
index 95b3a11..e06104b 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -41,7 +41,7 @@ 
  #include "p-lang.h"
  #include "addrmap.h"
  #include "cli/cli-utils.h"
-
+#include "fnmatch.h"
  #include "hashtab.h"

  #include "gdb_obstack.h"
@@ -342,6 +342,40 @@  compare_filenames_for_search (const char *filename,  
const char *search_name)
  	      && STRIP_DRIVE_SPEC (filename) == &filename[len - search_len]));
  }

+/* Same as compare_filenames_for_search, but for glob-style patterns.
+   Heads up on the order of the arguments.  They match the order of
+   compare_filenames_for_search, but it's the opposite of the order of
+   arguments to gdb_filename_fnmatch.  */
+
+int
+compare_glob_filenames_for_search (const char *filename,
+				   const char *search_name)
+{
+  /* We rely on the property of glob-style patterns with FNM_FILE_NAME that
+     all /s have to be explicitly specified.  */
+  int file_path_elements = count_path_elements (filename);
+  int search_path_elements = count_path_elements (search_name);
+
+  if (search_path_elements > file_path_elements)
+    return 0;
+
+  if (IS_ABSOLUTE_PATH (search_name))
+    {
+      return (search_path_elements == file_path_elements
+	      && gdb_filename_fnmatch (search_name, filename,
+				       FNM_FILE_NAME | FNM_NOESCAPE) == 0);
+    }
+
+  {
+    const char *file_to_compare
+      = strip_leading_path_elements (filename,
+				     file_path_elements - search_path_elements);
+
+    return gdb_filename_fnmatch (search_name, file_to_compare,
+				 FNM_FILE_NAME | FNM_NOESCAPE) == 0;
+  }
+}
+
  /* Check for a symtab of a specific name by searching some symtabs.
     This is a helper function for callbacks of iterate_over_symtabs.

diff --git a/gdb/symtab.h b/gdb/symtab.h
index f7884b9..6f00baf 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -1600,6 +1600,9 @@  extern int basenames_may_differ;
  int compare_filenames_for_search (const char *filename,
  				  const char *search_name);

+int compare_glob_filenames_for_search (const char *filename,
+				       const char *search_name);
+
  int iterate_over_some_symtabs (const char *name,
  			       const char *real_path,
  			       int (*callback) (struct symtab *symtab,
diff --git a/gdb/testsuite/gdb.base/skip.c b/gdb/testsuite/gdb.base/skip.c
index 3568296..b9db2a7 100644
--- a/gdb/testsuite/gdb.base/skip.c
+++ b/gdb/testsuite/gdb.base/skip.c
@@ -20,6 +20,7 @@ 
  int foo (void);
  int bar (void);
  int baz (int);
+void skip1_test_skip_file_and_function (void);

  int
  main ()
@@ -33,3 +34,22 @@  foo ()
  {
    return 0;
  }
+
+static void
+test_skip (void)
+{
+}
+
+static void
+end_test_skip_file_and_function (void)
+{
+  abort ();
+}
+
+void
+test_skip_file_and_function (void)
+{
+  test_skip ();
+  skip1_test_skip_file_and_function ();
+  end_test_skip_file_and_function ();
+}
diff --git a/gdb/testsuite/gdb.base/skip.exp  
b/gdb/testsuite/gdb.base/skip.exp
index 40f3103..220b145 100644
--- a/gdb/testsuite/gdb.base/skip.exp
+++ b/gdb/testsuite/gdb.base/skip.exp
@@ -14,6 +14,7 @@ 
  # along with this program.  If not, see <http://www.gnu.org/licenses/>.

  # This file was written by Justin Lebar. (justin.lebar@gmail.com)
+# And further hacked on by Doug Evans. (dje@google.com)

  if { [prepare_for_testing skip.exp "skip" \
                            {skip.c skip1.c } \
@@ -30,7 +31,24 @@  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)"

-if ![runto_main] { fail "skip tests suppressed" }
+# Test elided args.
+
+gdb_test "skip -fi" "Missing value for -fi option."
+gdb_test "skip -file" "Missing value for -file option."
+gdb_test "skip -fu" "Missing value for -fu option."
+gdb_test "skip -function" "Missing value for -function option."
+gdb_test "skip -rfu" "Missing value for -rfu option."
+gdb_test "skip -rfunction" "Missing value for -rfunction option."
+
+# Test other invalid option combinations.
+
+gdb_test "skip -x" "Invalid skip option: -x"
+gdb_test "skip -rfu foo.* xyzzy" "Invalid argument: xyzzy"
+
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}

  # Test |info skip| with an empty skiplist.

@@ -62,34 +80,39 @@  gdb_test "info skip 999" "No skiplist entries found  
with number 999."
  # Does |info skip| look right?

  gdb_test "info skip" \
-  "Num\\s+Type\\s+Enb\\s+What\\s*
-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*"
+    "Num\\s+Enb\\s+Glob\\s+File\\s+RE\\s+Function\\s*
+1\\s+y\\s+n\\s+.*$srcfile\\s+n\\s+<none>\\s*
+2\\s+y\\s+n\\s+<none>\\s+n\\s+main\\s*
+3\\s+y\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*
+4\\s+y\\s+n\\s+<none>\\s+n\\s+baz\\s*"

  # Right now, we have an outstanding skiplist entry on both source
  # files, so when we step into the first line in main(), we should step
  # right over it and go to the second line of main().

-if ![runto_main] { fail "skip tests suppressed" }
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
  gdb_test "step" ".*" "step in the main"
  gdb_test "bt" "\\s*\\#0\\s+main.*" "step after all ignored"

  # Now remove skip.c from the skiplist.  Our first step should take us
-# into foo(), and our second step should take us to the next line in
-# main().
+# into foo(), and our second step should take us to the next line in  
main().

  gdb_test "skip delete 1"
  # Check that entry 1 is missing from |info skip|
  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*" \
+    "Num\\s+Enb\\s+Glob\\s+File\\s+RE\\s+Function\\s*
+2\\s+y\\s+n\\s+<none>\\s+n\\s+main\\s*
+3\\s+y\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*
+4\\s+y\\s+n\\s+<none>\\s+n\\s+baz\\s*" \
      "info skip (delete 1)"

-if ![runto_main] { fail "skip tests suppressed" }
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
  set test "step after deleting 1"
  gdb_test "step" "foo \\(\\) at.*" "$test (1)"
  gdb_test "step" ".*" "$test (2)" ; # Return from foo()
@@ -100,10 +123,14 @@  gdb_test "step" "main \\(\\) at.*" "$test (3)"

  gdb_test "skip disable 3"
  # Is entry 3 disabled in |info skip|?
-gdb_test "info skip 3" ".*\\n3\\s+file\\s+n.*" \
-  "info skip shows entry as disabled"
+gdb_test "info skip 3" \
+    "3\\s+n\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*" \
+    "info skip shows entry as disabled"

-if ![runto_main] { fail "skip tests suppressed" }
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
  set test "step after disabling 3"
  gdb_test "step" "bar \\(\\) at.*" "$test (1)"
  gdb_test "step" ".*" "$test (2)"; # Return from foo()
@@ -115,9 +142,13 @@  gdb_test "step" "main \\(\\) at.*" "$test (5)"

  gdb_test "skip enable 3"
  # Is entry 3 enabled in |info skip|?
-gdb_test "info skip 3" ".*\\n3\\s+file\\s+y.*" \
-  "info skip shows entry as enabled"
-if ![runto_main] { fail "skip tests suppressed" }
+gdb_test "info skip 3" \
+    "3\\s+y\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*" \
+    "info skip shows entry as enabled"
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
  set test "step after enable 3"
  gdb_test "step" "foo \\(\\) at.*" "$test (1)"
  gdb_test "step" ".*" "$test (2)"; # Return from foo()
@@ -125,47 +156,129 @@  gdb_test "step" "main \\(\\) at.*" "$test (3)"

  gdb_test "skip disable"
  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*" \
-  "info skip after disabling all"
+    "Num\\s+Enb\\s+Glob\\s+File\\s+RE\\s+Function\\s*
+2\\s+n\\s+n\\s+<none>\\s+n\\s+main\\s*
+3\\s+n\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*
+4\\s+n\\s+n\\s+<none>\\s+n\\s+baz\\s*" \
+    "info skip after disabling all"

  gdb_test "skip enable"
  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*" \
-  "info skip after enabling all"
+    "Num\\s+Enb\\s+Glob\\s+File\\s+RE\\s+Function\\s*
+2\\s+y\\s+n\\s+<none>\\s+n\\s+main\\s*
+3\\s+y\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*
+4\\s+y\\s+n\\s+<none>\\s+n\\s+baz\\s*" \
+    "info skip after enabling all"

  gdb_test "skip disable 4 2-3"
  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*" \
-  "info skip after disabling 4 2-3"
+    "Num\\s+Enb\\s+Glob\\s+File\\s+RE\\s+Function\\s*
+2\\s+n\\s+n\\s+<none>\\s+n\\s+main\\s*
+3\\s+n\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*
+4\\s+n\\s+n\\s+<none>\\s+n\\s+baz\\s*" \
+    "info skip after disabling 4 2-3"

  gdb_test "skip enable 2-3"
  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*" \
-  "info skip after enabling 2-3"
+    "Num\\s+Enb\\s+Glob\\s+File\\s+RE\\s+Function\\s*
+2\\s+y\\s+n\\s+<none>\\s+n\\s+main\\s*
+3\\s+y\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*
+4\\s+n\\s+n\\s+<none>\\s+n\\s+baz\\s*" \
+    "info skip after enabling 2-3"

  gdb_test "info skip 2-3" \
-  "Num\\s+Type\\s+Enb\\s+What\\s*
-2\\s+function\\s+y\\s+main\\s*
-3\\s+file\\s+y\\s+$srcfile1\\s*" \
-  "info skip 2-3"
+    "Num\\s+Enb\\s+Glob\\s+File\\s+RE\\s+Function\\s*
+2\\s+y\\s+n\\s+<none>\\s+n\\s+main\\s*
+3\\s+y\\s+n\\s+$srcfile1\\s+n\\s+<none>\\s*" \
+    "info skip 2-3"

  gdb_test "skip delete 2 3"
  gdb_test "info skip" \
-  "4\\s+function\\s+n\\s+baz\\s*" \
-  "info skip after deleting 2 3"
+    "Num\\s+Enb\\s+Glob\\s+File\\s+RE\\s+Function\\s*
+4\\s+n\\s+n\\s+<none>\\s+n\\s+baz\\s*" \
+    "info skip after deleting 2 3"

  gdb_test "skip delete"
  gdb_test "info skip" "Not skipping any files or functions\." \
-  "info skip after deleting all"
+    "info skip after deleting all"
+
+# Now test skip -fi, etc.
+
+# Create a skiplist entry for a specified file and function.
+gdb_test "skip -fi skip1.c" "File .*$srcfile1 will be skipped when  
stepping\."
+gdb_test "skip -gfi sk*1.c" "File\\(s\\) sk\\*1.c will be skipped when  
stepping\."
+gdb_test "skip -fu baz" "Function baz will be skipped when stepping\."
+gdb_test "skip -rfu ^b.z$" "Function\\(s\\) \\^b\\.z\\$ will be skipped  
when stepping."
+
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
+
+set test "step using -fi"
+gdb_test_no_output "skip disable"
+gdb_test_no_output "skip enable 5"
+gdb_test "step" "foo \\(\\) at.*" "$test (1)"
+gdb_test "step" ".*" "$test (2)"; # Return from foo()
+gdb_test "step" "main \\(\\) at.*" "$test (3)"
+
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
+
+set test "step using -gfi"
+gdb_test_no_output "skip disable"
+gdb_test_no_output "skip enable 6"
+gdb_test "step" "foo \\(\\) at.*" "$test (1)"
+gdb_test "step" ".*" "$test (2)"; # Return from foo()
+gdb_test "step" "main \\(\\) at.*" "$test (3)"
+
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
+
+set test "step using -fu for baz"
+gdb_test_no_output "skip disable"
+gdb_test_no_output "skip enable 7"
+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)"
+
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
+
+set test "step using -rfu for baz"
+gdb_test_no_output "skip disable"
+gdb_test_no_output "skip enable 8"
+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)"
+
+# Test -fi + -fu.
+
+if ![runto_main] {
+    fail "Can't run to main"
+    return
+}
+
+set test "step using -fi + -fu"
+gdb_test_no_output "skip delete"
+gdb_test "skip -fi skip1.c -fu test_skip" \
+    "Function test_skip in file skip1.c will be skipped when stepping\."
+gdb_breakpoint "test_skip_file_and_function"
+gdb_breakpoint "end_test_skip_file_and_function"
+gdb_test "call test_skip_file_and_function ()" "silently stop."
+# Verify we can step into skip.c:test_skip but not skip1.c:test_skip.
+gdb_test "step" "test_skip \\(\\) at.*" "$test (1)"
+gdb_test "step" "test_skip_file_and_function \\(\\) at.*" "$test (2)"; #  
Return from test_skip()
+gdb_test "step" "skip1_test_skip_file_and_function \\(\\) at.*" "$test (4)"
+gdb_test "step" ".*" "$test (5)"; # Skip over test_skip()
+gdb_test "step" "test_skip_file_and_function \\(\\) at.*" "$test (6)"; #  
Return from skip1_test_skip_file_and_function()
diff --git a/gdb/testsuite/gdb.base/skip1.c b/gdb/testsuite/gdb.base/skip1.c
index 9a06e62..7d94332 100644
--- a/gdb/testsuite/gdb.base/skip1.c
+++ b/gdb/testsuite/gdb.base/skip1.c
@@ -26,3 +26,14 @@  baz (int a)
  {
    return a + 1;
  }
+
+static void
+test_skip (void)
+{
+}
+
+void
+skip1_test_skip_file_and_function (void)
+{
+  test_skip ();
+}
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..d251725
--- /dev/null
+++ b/gdb/testsuite/gdb.perf/skip-command.exp
@@ -0,0 +1,138 @@ 
+# 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
+    switch $kind {
+	"function" { set skip_arg "-function" }
+	"regexp" { set skip_arg "-rfunction" }
+	default {
+	    perror "bad skip kind: $kind"
+	    return
+	}
+    }
+    for { set i 0 } { $i < $nr_skips } { incr i } {
+	gdb_test "skip $skip_arg [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)
diff --git a/gdb/utils.c b/gdb/utils.c
index e34c12e..977314b 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -3380,6 +3380,83 @@  gdb_filename_fnmatch (const char *pattern, const  
char *string, int flags)
    return fnmatch (pattern, string, flags);
  }

+/* Return the number of path elements in PATH.
+   / = 1
+   /foo = 2
+   /foo/ = 2
+   foo/bar = 2
+   foo/ = 1  */
+
+int
+count_path_elements (const char *path)
+{
+  int count = 0;
+  const char *p = path;
+
+  if (HAS_DRIVE_SPEC (p))
+    {
+      p = STRIP_DRIVE_SPEC (p);
+      ++count;
+    }
+
+  while (*p != '\0')
+    {
+      if (IS_DIR_SEPARATOR (*p))
+	++count;
+      ++p;
+    }
+
+  /* Backup one if last character is /, unless it's the only one.  */
+  if (p > path + 1 && IS_DIR_SEPARATOR (p[-1]))
+    --count;
+
+  /* Add one for the file name, if present.  */
+  if (p > path && !IS_DIR_SEPARATOR (p[-1]))
+    ++count;
+
+  return count;
+}
+
+/* Remove N leading path elements from PATH.
+   N must be non-negative.
+   If PATH has more than N path elements then return NULL.
+   If PATH has exactly N path elements then return "".
+   See count_path_elements for a description of how we do the counting.  */
+
+const char *
+strip_leading_path_elements (const char *path, int n)
+{
+  int i = 0;
+  const char *p = path;
+
+  gdb_assert (n >= 0);
+
+  if (n == 0)
+    return p;
+
+  if (HAS_DRIVE_SPEC (p))
+    {
+      p = STRIP_DRIVE_SPEC (p);
+      ++i;
+    }
+
+  while (i < n)
+    {
+      while (*p != '\0' && !IS_DIR_SEPARATOR (*p))
+	++p;
+      if (*p == '\0')
+	{
+	  if (i + 1 == n)
+	    return "";
+	  return NULL;
+	}
+      ++p;
+      ++i;
+    }
+
+  return p;
+}
+
  /* Provide a prototype to silence -Wmissing-prototypes.  */
  extern initialize_file_ftype _initialize_utils;

diff --git a/gdb/utils.h b/gdb/utils.h
index 4571aaf..0687c86 100644
--- a/gdb/utils.h
+++ b/gdb/utils.h
@@ -135,6 +135,10 @@  extern void substitute_path_component (char **stringp,  
const char *from,
  				       const char *to);

  char *ldirname (const char *filename);
+
+extern int count_path_elements (const char *path);
+
+extern const char *strip_leading_path_elements (const char *path, int n);
  
  /* GDB output, ui_file utilities.  */