[4/4] gdb/mi: extend -file-list-exec-source-files command

Message ID bd432df6bbeee80737a07a7bfee3bb0cc0b6a985.1619456691.git.andrew.burgess@embecosm.com
State New
Headers show
Series
  • New option for 'info sources', also better MI support
Related show

Commit Message

Andrew Burgess April 26, 2021, 5:07 p.m.
This commit extends the -file-list-exec-source-files command to
support all of the features that the cli 'info sources' supports.

As this is MI the output of -file-list-exec-source-files should be
backward compatible, though I have added an extra field into the
output format, but I believe this is acceptable as a well behaving
front end is free to ignore a field it doesn't understand.

As an example, the previous output might look like this:

  ^done,files=[{file="foo.c",fullname="/home/foo.c"}]

While the new output would look like:

  ^done,files=[{file="foo.c",fullname="/home/foo.c",debug-fully-read="true"}]

With the new field indicating that the source file 'foo.c' is from a
compilation unit that GDB has fully read in.

This would allow a front end to recreate GDB's cli output format where
the source files are split into two groups based on whether the debug
information for the containing compilation unit has been fully read or
not.

While -file-list-exec-source-files previously took no arguments, after
this commit the command can _optionally_ take some arguments, as these
are optional this should not break backward compatibility.

gdb/ChangeLog:

	* NEWS: Mention changes to -file-list-exec-source-files.
	* mi/mi-cmd-file.c (print_partial_file_name): Delete.
	(mi_cmd_file_list_exec_source_files): Rewrite to make use of
	info_sources_worker.
	* symtab.c (struct output_source_filename_data) <output>: Rewrite
	comment, add additional arguments to declaration.
	(output_source_filename_data::output): Take extra arguments,
	produce output through current_uiout, and structure for MI.
	(info_sources_worker): New function, the implementation is taken
	from info_sources_command, but modified so produce output through
	current_uiout.
	(info_sources_command): The second half of this function has gone
	to become info_sources_worker.
	* symtab.h (info_sources_worker): Declare.

gdb/doc/ChangeLog:

	* gdb.texinfo (GDB/MI File Commands): Document extensions to
	-file-list-exec-source-files.

gdb/testsuite/ChangeLog:

	* gdb.dwarf2/dw2-filename.exp: Update expected results.
	* gdb.mi/mi-file.exp: Likewise.
	* gdb.mi/mi-info-sources-base.c: New file.
	* gdb.mi/mi-info-sources.c: New file.
	* gdb.mi/mi-info-sources.exp: New file.
---
 gdb/ChangeLog                               |  17 ++
 gdb/NEWS                                    |  11 ++
 gdb/doc/ChangeLog                           |   5 +
 gdb/doc/gdb.texinfo                         | 122 ++++++++++++--
 gdb/mi/mi-cmd-file.c                        |  84 +++++-----
 gdb/symtab.c                                | 150 ++++++++++++-----
 gdb/symtab.h                                |  24 +++
 gdb/testsuite/ChangeLog                     |   8 +
 gdb/testsuite/gdb.dwarf2/dw2-filename.exp   |   2 +-
 gdb/testsuite/gdb.mi/mi-file.exp            |   2 +-
 gdb/testsuite/gdb.mi/mi-info-sources-base.c |  24 +++
 gdb/testsuite/gdb.mi/mi-info-sources.c      |  25 +++
 gdb/testsuite/gdb.mi/mi-info-sources.exp    | 177 ++++++++++++++++++++
 13 files changed, 554 insertions(+), 97 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/mi-info-sources-base.c
 create mode 100644 gdb/testsuite/gdb.mi/mi-info-sources.c
 create mode 100644 gdb/testsuite/gdb.mi/mi-info-sources.exp

-- 
2.25.4

Comments

Lancelot SIX via Gdb-patches April 26, 2021, 5:39 p.m. | #1
> From: Andrew Burgess <andrew.burgess@embecosm.com>

> Date: Mon, 26 Apr 2021 18:07:03 +0100

> 

> --- a/gdb/NEWS

> +++ b/gdb/NEWS

> @@ -31,6 +31,17 @@

>      equivalent of the CLI's "break -qualified" and "dprintf

>      -qualified".

>  

> +  ** '-file-list-exec-source-files' can now accept a regular

> +     expression for filtering, and accepts the flags --dirname,

> +     --basename and --group-by-binary, making this command equivalent

> +     of the cli 'info sources' command.

> +

> +     In the non '--group-by-binary' mode an extra field

> +     'debug-fully-read' has been added to each source file tuple to

> +     indicate if the debug information for the compilation unit

> +     containing this source file has been fully read or not, the

> +     values for this field are 'true' or 'false'.


This part is OK.

> +With no arguments this command returns a list of source files.  Each

> +source file is represented by a tuple with the fields; @var{file},

> +@var{fullname}, and @var{debug-fully-read}.  The @var{file} is the

> +display name for the file, while @var{fullname} is the absolute path

> +to the file.                                           ^^^^^^^^^^^^^

   ^^^^^^^^^^^
GNU Coding Standards frown on calling "path" anything that is not a
PATH-style directory list.  Please use "absolute name of the file"
instead.

>                The @var{fullname} field can be elided if the absolute

> +path to the source file can't be computed.


Likewise.

> +If @code{--dirname} is provided then @var{regexp} is matched only

> +against the directory name of each source file.  If @code{--basename}

> +is provided then @var{regexp} is matched against the source files

> +basename.

   ^^^^^^^^
"basenames", in plural.

> +If @code{--group-by-binary} is used then the format of the results is

> +changed.  The results will now be a list of tuples, with each tuple

> +representing an object file (executable or shared libraries) loaded

> +into @value{GDBN}.  The fields of these tuples are; @var{filename},

> +@var{debug-fully-read}, and @var{sources}.  The @var{filename} is the

> +absolute path to the object file, @var{debug-fully-read} is a string,

   ^^^^^^^^^^^^^
"path" again

Thanks.

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index f84ea0427a7..288c399747e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -31,6 +31,17 @@ 
     equivalent of the CLI's "break -qualified" and "dprintf
     -qualified".
 
+  ** '-file-list-exec-source-files' can now accept a regular
+     expression for filtering, and accepts the flags --dirname,
+     --basename and --group-by-binary, making this command equivalent
+     of the cli 'info sources' command.
+
+     In the non '--group-by-binary' mode an extra field
+     'debug-fully-read' has been added to each source file tuple to
+     indicate if the debug information for the compilation unit
+     containing this source file has been fully read or not, the
+     values for this field are 'true' or 'false'.
+
 * GDB now supports core file debugging for x86_64 Cygwin programs.
 
 * GDB will now look for the .gdbinit file in a config directory before
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 03bf603ad42..8da63959ede 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -35625,18 +35625,63 @@ 
 
 
 @subheading The @code{-file-list-exec-source-files} Command
+@kindex info sources
 @findex -file-list-exec-source-files
 
 @subsubheading Synopsis
 
 @smallexample
- -file-list-exec-source-files
-@end smallexample
-
-List the source files for the current executable.
+-file-list-exec-source-files @r{[} @var{--group-by-binary} @r{]}
+                             @r{[} @var{--dirname} @r{|} @var{--basename} @r{]}
+                             @r{[} -- @r{]}
+                             @r{[} @var{regexp} @r{]}
+@end smallexample
+
+This commands returns information about the source files @value{GDBN}
+knows about, it will output both the filename and fullname (absolute
+file name) of a source file, though the fullname can be elided if this
+information is not known.
+
+With no arguments this command returns a list of source files.  Each
+source file is represented by a tuple with the fields; @var{file},
+@var{fullname}, and @var{debug-fully-read}.  The @var{file} is the
+display name for the file, while @var{fullname} is the absolute path
+to the file.  The @var{fullname} field can be elided if the absolute
+path to the source file can't be computed. The field
+@var{debug-fully-read} will be a string, either @code{true} or
+@code{false}.  When @code{true} this indicates the full debug
+information for the compilation unit describing this file has been
+read in.  When @code{false} the full debug information has not yet
+been read in.  While reading in the full debug information it is
+possible that @value{GDBN} could become aware of additional source
+files.
 
-It will always output both the filename and fullname (absolute file
-name) of a source file.
+The optional @var{regexp} can be used to filter the list of source
+files returned.  The @var{regexp} will be matched against the full
+source file name.  The matching is case-sensitive, except on operating
+systems that have case-insensitive filesystem (e.g.,
+MS-Windows). @samp{--} can be used before @var{regexp} to prevent
+@value{GDBN} interpreting @var{regexp} as a command option (e.g. if
+@var{regexp} starts with @samp{-}).
+
+If @code{--dirname} is provided then @var{regexp} is matched only
+against the directory name of each source file.  If @code{--basename}
+is provided then @var{regexp} is matched against the source files
+basename.  Only one of @code{--dirname} or @code{--basename} may be
+given, and if either is given then @var{regexp} is required.
+
+If @code{--group-by-binary} is used then the format of the results is
+changed.  The results will now be a list of tuples, with each tuple
+representing an object file (executable or shared libraries) loaded
+into @value{GDBN}.  The fields of these tuples are; @var{filename},
+@var{debug-fully-read}, and @var{sources}.  The @var{filename} is the
+absolute path to the object file, @var{debug-fully-read} is a string,
+either @code{true} or @code{false} indicating if the full debug
+information for this object file has been read in or not.  The
+@var{sources} is a list or tuples, with each tuple describing a single
+source file with the same fields as described previously.  The
+@var{sources} list can be empty for object files that have no debug
+information.
 
 @subsubheading @value{GDBN} Command
 
@@ -35645,13 +35690,66 @@ 
 
 @subsubheading Example
 @smallexample
-(gdb)
+(@value{GDBP})
 -file-list-exec-source-files
-^done,files=[
-@{file=foo.c,fullname=/home/foo.c@},
-@{file=/home/bar.c,fullname=/home/bar.c@},
-@{file=gdb_could_not_find_fullpath.c@}]
-(gdb)
+^done,files=[@{file="foo.c",fullname="/home/foo.c",debug-fully-read="true"@},
+             @{file="/home/bar.c",fullname="/home/bar.c",debug-fully-read="true"@},
+             @{file="gdb_could_not_find_fullpath.c",debug-fully-read="true"@}]
+(@value{GDBP})
+-file-list-exec-source-files
+^done,files=[@{file="test.c",
+              fullname="/tmp/info-sources/test.c",
+              debug-fully-read="true"@},
+             @{file="/usr/include/stdc-predef.h",
+              fullname="/usr/include/stdc-predef.h",
+              debug-fully-read="true"@},
+             @{file="header.h",
+              fullname="/tmp/info-sources/header.h",
+              debug-fully-read="true"@},
+             @{file="helper.c",
+              fullname="/tmp/info-sources/helper.c",
+              debug-fully-read="true"@}]
+(@value{GDBP})
+-file-list-exec-source-files -- \\.c
+^done,files=[@{file="test.c",
+              fullname="/tmp/info-sources/test.c",
+              debug-fully-read="true"@},
+             @{file="helper.c",
+              fullname="/tmp/info-sources/helper.c",
+              debug-fully-read="true"@}]
+(@value{GDBP})
+-file-list-exec-source-files --group-by-binary
+^done,files=[@{filename="/tmp/info-sources/test.x",
+              debug-fully-read="true",
+              sources=[@{file="test.c",
+                        fullname="/tmp/info-sources/test.c",
+                        debug-fully-read="true"@},
+                       @{file="/usr/include/stdc-predef.h",
+                        fullname="/usr/include/stdc-predef.h",
+                        debug-fully-read="true"@},
+                       @{file="header.h",
+                        fullname="/tmp/info-sources/header.h",
+                        debug-fully-read="true"@}]@},
+             @{filename="/lib64/ld-linux-x86-64.so.2",
+              debug-fully-read="true",
+              sources=[]@},
+             @{filename="system-supplied DSO at 0x7ffff7fcf000",
+              debug-fully-read="true",
+              sources=[]@},
+             @{filename="/tmp/info-sources/libhelper.so",
+              debug-fully-read="true",
+              sources=[@{file="helper.c",
+                        fullname="/tmp/info-sources/helper.c",
+                        debug-fully-read="true"@},
+                       @{file="/usr/include/stdc-predef.h",
+                        fullname="/usr/include/stdc-predef.h",
+                        debug-fully-read="true"@},
+                       @{file="header.h",
+                        fullname="/tmp/info-sources/header.h",
+                        debug-fully-read="true"@}]@},
+             @{filename="/lib64/libc.so.6",
+              debug-fully-read="true",
+              sources=[]@}]
 @end smallexample
 
 @subheading The @code{-file-list-shared-libraries} Command
diff --git a/gdb/mi/mi-cmd-file.c b/gdb/mi/mi-cmd-file.c
index 430449c919e..f4c1443c3e4 100644
--- a/gdb/mi/mi-cmd-file.c
+++ b/gdb/mi/mi-cmd-file.c
@@ -62,54 +62,62 @@  mi_cmd_file_list_exec_source_file (const char *command, char **argv, int argc)
 		       COMPUNIT_MACRO_TABLE (SYMTAB_COMPUNIT (st.symtab)) != NULL);
 }
 
-/* A callback for map_partial_symbol_filenames.  */
-
-static void
-print_partial_file_name (const char *filename, const char *fullname)
-{
-  struct ui_out *uiout = current_uiout;
-
-  uiout->begin (ui_out_type_tuple, NULL);
-
-  uiout->field_string ("file", filename);
-
-  if (fullname)
-    uiout->field_string ("fullname", fullname);
-
-  uiout->end (ui_out_type_tuple);
-}
+/* Implement -file-list-exec-source-files command.  */
 
 void
 mi_cmd_file_list_exec_source_files (const char *command, char **argv, int argc)
 {
-  struct ui_out *uiout = current_uiout;
-
-  if (!mi_valid_noargs ("-file-list-exec-source-files", argc, argv))
-    error (_("-file-list-exec-source-files: Usage: No args"));
-
-  /* Print the table header.  */
-  uiout->begin (ui_out_type_list, "files");
-
-  /* Look at all of the file symtabs.  */
-  for (objfile *objfile : current_program_space->objfiles ())
+  enum opt
     {
-      for (compunit_symtab *cu : objfile->compunits ())
+      GROUP_BY_BINARY_OPT,
+      MATCH_BASENAME_OPT,
+      MATCH_DIRNAME_OPT
+    };
+  static const struct mi_opt opts[] =
+  {
+    {"-group-by-binary", GROUP_BY_BINARY_OPT, 0},
+    {"-basename", MATCH_BASENAME_OPT, 0},
+    {"-dirname", MATCH_DIRNAME_OPT, 0},
+    { 0, 0, 0 }
+  };
+
+  /* Parse arguments.  */
+  int oind = 0;
+  char *oarg;
+
+  bool group_by_binary = false;
+  bool match_on_basename = false;
+  bool match_on_dirname = false;
+
+  while (1)
+    {
+      int opt = mi_getopt ("-file-list-exec-source-files", argc, argv,
+			   opts, &oind, &oarg);
+      if (opt < 0)
+	break;
+      switch ((enum opt) opt)
 	{
-	  for (symtab *s : compunit_filetabs (cu))
-	    {
-	      uiout->begin (ui_out_type_tuple, NULL);
-
-	      uiout->field_string ("file", symtab_to_filename_for_display (s));
-	      uiout->field_string ("fullname", symtab_to_fullname (s));
-
-	      uiout->end (ui_out_type_tuple);
-	    }
+	case GROUP_BY_BINARY_OPT:
+	  group_by_binary = true;
+	  break;
+	case MATCH_BASENAME_OPT:
+	  match_on_basename = true;
+	  break;
+	case MATCH_DIRNAME_OPT:
+	  match_on_dirname = true;
+	  break;
 	}
     }
 
-  map_symbol_filenames (print_partial_file_name, true /*need_fullname*/);
+  if ((argc - oind > 1) || (match_on_basename && match_on_dirname))
+    error (_("-file-list-exec-source-files: Usage: [--group-by-binary] [--basename | --dirname] [--] REGEXP"));
+
+  const char *regexp = nullptr;
+  if (argc - oind == 1)
+    regexp = argv[oind];
 
-  uiout->end (ui_out_type_list);
+  info_sources_worker (group_by_binary, match_on_basename,
+		       match_on_dirname, regexp);
 }
 
 /* See mi-cmds.h.  */
diff --git a/gdb/symtab.c b/gdb/symtab.c
index d8d0667dfdb..e1a96fbb0c0 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -4246,9 +4246,14 @@  struct output_source_filename_data
     m_filename_seen_cache.clear ();
   }
 
-  /* Worker for sources_info.  Force line breaks at ,'s.
-     NAME is the name to print.  */
-  void output (const char *name);
+  /* Worker for sources_info, outputs the file name formatted for either
+     cli or mi (based on the current_uiout).  In cli mode displays
+     FULLNAME with a comma separating this name from any previously
+     printed name (line breaks are added at the comma).  In MI mode
+     outputs a tuple containing DISP_NAME (the files display name),
+     FULLNAME, and EXPANDED_P (true when this file is from a fully
+     expanded symtab, otherwise false).  */
+  void output (const char *disp_name, const char *fullname, bool expanded_p);
 
   /* Prints the header messages for the source files that will be printed
      with the matching info present in the current object state.
@@ -4260,7 +4265,7 @@  struct output_source_filename_data
      quick_symbol_functions::map_symbol_filenames.  */
   void operator() (const char *filename, const char *fullname)
   {
-    output (fullname != nullptr ? fullname : filename);
+    output (filename, fullname, false);
   }
 
   /* Return true if at least one filename has been printed (after a call to
@@ -4306,7 +4311,9 @@  struct output_source_filename_data
 /* See comment is class declaration above.  */
 
 void
-output_source_filename_data::output (const char *name)
+output_source_filename_data::output (const char *disp_name,
+				     const char *fullname,
+				     bool expanded_p)
 {
   /* Since a single source file can result in several partial symbol
      tables, we need to avoid printing it more than once.  Note: if
@@ -4318,7 +4325,7 @@  output_source_filename_data::output (const char *name)
      symtabs; it doesn't hurt to check.  */
 
   /* Was NAME already seen?  */
-  if (m_filename_seen_cache.seen (name))
+  if (m_filename_seen_cache.seen (fullname))
     {
       /* Yes; don't print it again.  */
       return;
@@ -4332,25 +4339,43 @@  output_source_filename_data::output (const char *name)
 
       if (m_partial_match.dirname)
 	{
-	  dirname = ldirname (name);
+	  dirname = ldirname (fullname);
 	  to_match = dirname.c_str ();
 	}
       else if (m_partial_match.basename)
-	to_match = lbasename (name);
+	to_match = lbasename (fullname);
       else
-	to_match = name;
+	to_match = fullname;
 
       if (m_c_regexp->exec (to_match, 0, NULL, 0) != 0)
 	return;
     }
 
+  ui_out_emit_tuple ui_emitter (current_uiout, nullptr);
+
   /* Print it and reset *FIRST.  */
   if (!m_first)
-    printf_filtered (", ");
+    current_uiout->text (", ");
   m_first = false;
 
   wrap_here ("");
-  fputs_styled (name, file_name_style.style (), gdb_stdout);
+  if (current_uiout->is_mi_like_p ())
+    {
+      current_uiout->field_string ("file", disp_name,
+				   file_name_style.style ());
+      if (fullname != nullptr)
+	current_uiout->field_string ("fullname", fullname,
+				     file_name_style.style ());
+      current_uiout->field_string ("debug-fully-read",
+				   (expanded_p ? "true" : "false"));
+    }
+  else
+    {
+      if (fullname == nullptr)
+	fullname = disp_name;
+      current_uiout->field_string ("fullname", fullname,
+				   file_name_style.style ());
+    }
 }
 
 /* See comment is class declaration above.  */
@@ -4433,55 +4458,59 @@  info_sources_command_completer (cmd_list_element *ignore,
     return;
 }
 
-/* Implement the 'info sources' command.  */
+/* See symtab.h.  */
 
-static void
-info_sources_command (const char *args, int from_tty)
+void
+info_sources_worker (bool group_by_binary,
+		     bool match_on_basename,
+		     bool match_on_dirname,
+		     const char *regexp)
 {
-  if (!have_full_symbols () && !have_partial_symbols ())
-    error (_("No symbol table is loaded.  Use the \"file\" command."));
+  /* Check for these error conditions before calling this function.  */
+  gdb_assert (!(match_on_basename && match_on_dirname));
+  gdb_assert (((match_on_basename || match_on_dirname) && regexp != nullptr)
+	      || !(match_on_basename || match_on_dirname));
 
-  struct filename_grouping_opts grp_opts;
-  struct filename_partial_match_opts match_opts;
-  auto group = make_info_sources_options_def_group (&match_opts, &grp_opts);
-  gdb::option::process_options
-    (&args, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR, group);
-
-  if (match_opts.dirname && match_opts.basename)
-    error (_("You cannot give both -basename and -dirname to 'info sources'."));
-
-  bool group_by_binary = grp_opts.group_by_binary;
+  struct output_source_filename_data data (regexp, match_on_dirname,
+					   match_on_basename);
 
-  const char *regex = nullptr;
-  if (args != nullptr && *args != '\000')
-    regex = args;
-
-  if ((match_opts.dirname || match_opts.basename) && regex == nullptr)
-    error (_("Missing REGEXP for 'info sources'."));
-
-  struct output_source_filename_data data (regex, match_opts.dirname,
-					   match_opts.basename);
+  struct ui_out *uiout = current_uiout;
+  ui_out_emit_list results_emitter (uiout, "files");
+  gdb::optional<ui_out_emit_tuple> output_tuple;
+  gdb::optional<ui_out_emit_list> sources_list;
 
   if (!group_by_binary)
-    data.print_header (_("Source files for which symbols have been read in:\n"));
+    {
+      if (!uiout->is_mi_like_p ())
+	data.print_header (_("Source files for which symbols have been read in:\n"));
+    }
 
   for (objfile *objfile : current_program_space->objfiles ())
     {
       if (group_by_binary)
 	{
-	  printf_filtered ("%s:\n", objfile_name (objfile));
+	  output_tuple.emplace (uiout, nullptr);
+	  uiout->field_string ("filename", objfile_name (objfile));
+	  uiout->text (":\n");
 	  bool debug_fully_readin = !objfile->has_unexpanded_symbols ();
 	  if (!debug_fully_readin)
-	    printf_filtered ("(Full debug information has not yet been read for this file.)\n");
-	  printf_filtered ("\n");
+	    uiout->text ("(Full debug information has not yet been read "
+			 " for this file.)\n");
+	  if (uiout->is_mi_like_p ())
+	    current_uiout->field_string ("debug-fully-read",
+					 (debug_fully_readin
+					  ? "true" : "false"));
+	  uiout->text ("\n");
+	  sources_list.emplace (uiout, "sources");
 	}
 
       for (compunit_symtab *cu : objfile->compunits ())
 	{
 	  for (symtab *s : compunit_filetabs (cu))
 	    {
+	      const char *file = symtab_to_filename_for_display (s);
 	      const char *fullname = symtab_to_fullname (s);
-	      data.output (fullname);
+	      data.output (file, fullname, true);
 	    }
 	}
 
@@ -4489,22 +4518,53 @@  info_sources_command (const char *args, int from_tty)
 	{
 	  objfile->map_symbol_filenames (data, true /* need_fullname */);
 	  if (data.printed_filename_p ())
-	    printf_filtered ("\n\n");
+	    uiout->text ("\n\n");
 	  data.reset_output ();
+	  sources_list.reset ();
+	  output_tuple.reset ();
 	}
     }
 
   if (!group_by_binary)
     {
-      printf_filtered ("\n\n");
-      data.print_header (_("Source files for which symbols will be read in on demand:\n"));
-
+      uiout->text ("\n\n");
+      if (!uiout->is_mi_like_p ())
+	data.print_header (_("Source files for which symbols will be read in on demand:\n"));
       data.reset_output ();
       map_symbol_filenames (data, true /*need_fullname*/);
-      printf_filtered ("\n");
+      uiout->text ("\n");
     }
 }
 
+/* Implement the 'info sources' command.  */
+
+static void
+info_sources_command (const char *args, int from_tty)
+{
+  if (!have_full_symbols () && !have_partial_symbols ())
+    error (_("No symbol table is loaded.  Use the \"file\" command."));
+
+  struct filename_grouping_opts grp_opts;
+  struct filename_partial_match_opts match_opts;
+  auto group = make_info_sources_options_def_group (&match_opts, &grp_opts);
+  gdb::option::process_options
+    (&args, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR, group);
+
+  if (match_opts.dirname && match_opts.basename)
+    error (_("You cannot give both -basename and -dirname to 'info sources'."));
+
+  const char *regex = nullptr;
+  if (args != NULL && *args != '\000')
+    regex = args;
+
+  if ((match_opts.dirname || match_opts.basename) && regex == nullptr)
+    error (_("Missing REGEXP for 'info sources'."));
+
+  info_sources_worker (grp_opts.group_by_binary,
+		       match_opts.basename, match_opts.dirname,
+		       regex);
+}
+
 /* Compare FILE against all the entries of FILENAMES.  If BASENAMES is
    true compare only lbasename of FILENAMES.  */
 
diff --git a/gdb/symtab.h b/gdb/symtab.h
index efdbada9761..5f767297bb6 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -2384,4 +2384,28 @@  class symbol_searcher
   std::vector<bound_minimal_symbol> m_minimal_symbols;
 };
 
+/* Perform the core of the 'info sources' command.  REGEXP is the regular
+   expression to filter on and can be nullptr if no regular expression
+   filtering is required.
+
+   MATCH_ON_BASENAME and MATCH_ON_DIRNAME control which part of the
+   full filename to match REGEXP against.  If REGEXP is not nullptr and
+   both of the MATCH_ON_* variables are false then matching is done against
+   the full filename.  Only one of the MATCH_ON_* variables is allowed to
+   be true.  If either of these is true then REGEXP must not be nullptr.
+
+   When GROUP_BY_BINARY is true then the results are grouped based on the
+   binary that contains the source file, otherwise the results are just
+   returned in two lists, source files for objfiles where the full debug
+   has been read, and source files for objfiles that have not had the full
+   debug read.
+
+   The output is written to the current_uiout either in CLI or MI style as
+   appropriate.  */
+
+extern void info_sources_worker (bool group_by_binary,
+				 bool match_on_basename,
+				 bool match_on_dirname,
+				 const char *regexp);
+
 #endif /* !defined(SYMTAB_H) */
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-filename.exp b/gdb/testsuite/gdb.dwarf2/dw2-filename.exp
index 3e60f7d2bc9..bd303c8547b 100644
--- a/gdb/testsuite/gdb.dwarf2/dw2-filename.exp
+++ b/gdb/testsuite/gdb.dwarf2/dw2-filename.exp
@@ -37,7 +37,7 @@  clean_restart ${testfile}
 # the full path to that file.  What we want to verify, most of all,
 # is that the file and fullname fields are now inverted.
 gdb_test "interpreter-exec mi -file-list-exec-source-files" \
-         ".*{file=\"file1\\.txt\",fullname=\".+file1\\.txt\"}.*"
+    ".*{file=\"file1\\.txt\",fullname=\".+file1\\.txt\",debug-fully-read=\"\[^\"\]+\"}.*"
 
 # And `info sources' should return the fullname incl. the directories.
 gdb_test "info sources" {[/]file1\.txt.*}
diff --git a/gdb/testsuite/gdb.mi/mi-file.exp b/gdb/testsuite/gdb.mi/mi-file.exp
index 15d7d9f0944..4cae33c017e 100644
--- a/gdb/testsuite/gdb.mi/mi-file.exp
+++ b/gdb/testsuite/gdb.mi/mi-file.exp
@@ -68,7 +68,7 @@  proc test_file_list_exec_source_files {} {
 
     # get the path and absolute path to the current executable
     mi_gdb_test "222-file-list-exec-source-files" \
-	    "222\\\^done,files=\\\[\{file=\".*${srcfile}\",fullname=\"$fullname_syntax${srcfile}\"\}.*]" \
+	    "222\\\^done,files=\\\[\{file=\".*${srcfile}\",fullname=\"$fullname_syntax${srcfile}\",debug-fully-read=\"\[^\"\]+\"\}.*]" \
               "Getting a list of source files."
 }
 
diff --git a/gdb/testsuite/gdb.mi/mi-info-sources-base.c b/gdb/testsuite/gdb.mi/mi-info-sources-base.c
new file mode 100644
index 00000000000..d2e3eb0eace
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-info-sources-base.c
@@ -0,0 +1,24 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2019-2021 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 file was originally copied from gdb.base/info_sources_base.c.  */
+
+void some_other_func (void)
+{
+  return;
+}
+
diff --git a/gdb/testsuite/gdb.mi/mi-info-sources.c b/gdb/testsuite/gdb.mi/mi-info-sources.c
new file mode 100644
index 00000000000..b91b8280c2b
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-info-sources.c
@@ -0,0 +1,25 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2019-2021 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 file was originally copied from gdb.base/info_sources.c.  */
+
+extern void some_other_func (void);
+int main ()
+{
+  some_other_func ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.mi/mi-info-sources.exp b/gdb/testsuite/gdb.mi/mi-info-sources.exp
new file mode 100644
index 00000000000..3c478c875c1
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-info-sources.exp
@@ -0,0 +1,177 @@ 
+# Copyright 2021 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/>.
+
+# Test the -file-list-exec-source-files command.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile .c -base.c
+
+if {[prepare_for_testing $testfile.exp $testfile \
+	 [list $srcfile $srcfile2] debug]} {
+    untested $testfile.exp
+    return -1
+}
+
+mi_clean_restart $binfile
+
+mi_runto_main
+
+# Helper to build expected MI output pattern for a list.  NAME is the
+# name of the list (which can be the empty string) and args is one
+# or more strings representing the fields of the list, which will be
+# joined with a comma.
+#
+# If any of the fields in args matches ".*" then the comma before and
+# after are dropped from the final pattern.
+proc mi_list { name args } {
+    set str ""
+
+    if { $name != "" } {
+	set str "${name}="
+    }
+
+    set pattern ""
+    foreach a $args {
+	if { [string length $pattern] > 0 } {
+	    if { [string range $pattern end-1 end] != ".*" \
+		     && [string range $a 0 1] != ".*" } {
+		set pattern "${pattern},"
+	    }
+	}
+	set pattern "${pattern}${a}"
+    }
+    set str "$str\\\[${pattern}\\\]"
+    return ${str}
+}
+
+# Helper to build expected MI output pattern for a tuple.  NAME is the
+# name of the tuple (which can be the empty string) and args is one
+# or more strings representing the fields of the tuple, which will be
+# joined with a comma.
+#
+# If any of the fields in args matches ".*" then the comma before and
+# after are dropped from the final pattern.
+proc mi_tuple { name args } {
+    set str ""
+
+    if { $name != "" } {
+	set str "${name}="
+    }
+
+    set pattern ""
+    foreach a $args {
+	if { [string length $pattern] > 0 } {
+	    if { [string range $pattern end-1 end] != ".*" \
+		     && [string range $a 0 1] != ".*" } {
+		set pattern "${pattern},"
+	    }
+	}
+	set pattern "${pattern}${a}"
+    }
+    set str "$str\\{${pattern}\\}"
+    return ${str}
+}
+
+# Helper to build expected MI output pattern for a single field.  NAME
+# is the name of the field, and PATTERN matches the fields contents.
+# This proc will add quotes around PATTERN.
+proc mi_field { name pattern } {
+    set str ""
+
+    if { $name != "" } {
+	set str "${name}="
+    }
+
+    set str "$str\"${pattern}\""
+    return ${str}
+}
+
+# Run tests on '-file-list-exec-source-files'.  DEBUG_FULLY_READ is either the string
+# "true" or "false" and indicates if the GDB will have read all the
+# debug for the test program or not yet.
+proc check_info_sources { debug_fully_read } {
+
+    with_test_prefix "debug_read=${debug_fully_read}" {
+
+	if { $debug_fully_read } {
+	    set p [mi_list "files" \
+		       [mi_tuple "" \
+			    [mi_field "file" "\[^\"\]+/mi-info-sources-base\\.c"] \
+			    [mi_field "fullname" "\[^\"\]+/mi-info-sources-base\\.c"] \
+			    [mi_field "debug-fully-read" "${debug_fully_read}"]] \
+		       [mi_tuple "" \
+			    [mi_field "file" "\[^\"\]+/mi-info-sources\\.c"] \
+			    [mi_field "fullname" "\[^\"\]+/mi-info-sources\\.c"] \
+			    [mi_field "debug-fully-read" "true"]]]
+	} else {
+	    set p [mi_list "files" \
+		       [mi_tuple "" \
+			    [mi_field "file" "\[^\"\]+/mi-info-sources\\.c"] \
+			    [mi_field "fullname" "\[^\"\]+/mi-info-sources\\.c"] \
+			    [mi_field "debug-fully-read" "true"]] \
+		       [mi_tuple "" \
+			    [mi_field "file" "\[^\"\]+/mi-info-sources-base\\.c"] \
+			    [mi_field "fullname" "\[^\"\]+/mi-info-sources-base\\.c"] \
+			    [mi_field "debug-fully-read" "${debug_fully_read}"]]]
+	}
+	mi_gdb_test "-file-list-exec-source-files" ".*\\^done,${p}" "-file-list-exec-source-files"
+
+	set p [mi_list "files" \
+		   [mi_tuple "" \
+			[mi_field "file" "\[^\"\]+/mi-info-sources-base\\.c"] \
+			[mi_field "fullname" "\[^\"\]+/mi-info-sources-base\\.c"] \
+			[mi_field "debug-fully-read" "${debug_fully_read}"]]]
+	mi_gdb_test "-file-list-exec-source-files --basename -- base" ".*\\^done,${p}" \
+	    "-file-list-exec-source-files --basename -- base"
+
+	set p [mi_list "files" \
+		   [mi_tuple "" \
+			[mi_field "filename" "\[^\"\]+/mi-info-sources"] \
+			[mi_field "debug-fully-read" "${debug_fully_read}"] \
+			[mi_list "sources" \
+			     ".*" \
+			     [mi_tuple "" \
+				  [mi_field "file" "\[^\"\]+/mi-info-sources\\.c"] \
+				  [mi_field "fullname" "\[^\"\]+/mi-info-sources\\.c"] \
+				  [mi_field "debug-fully-read" "true"]] \
+			     ".*"]]]
+	mi_gdb_test "-file-list-exec-source-files --group-by-binary" \
+	    ".*\\^done,${p}" \
+	    "-file-list-exec-source-files --group-by-binary, look for mi-info-sources.c"
+
+	set p [mi_list "files" \
+		   [mi_tuple "" \
+			[mi_field "filename" "\[^\"\]+/mi-info-sources"] \
+			[mi_field "debug-fully-read" "${debug_fully_read}"] \
+			[mi_list "sources" \
+			     ".*" \
+			     [mi_tuple "" \
+				  [mi_field "file" "\[^\"\]+/mi-info-sources-base\\.c"] \
+				  [mi_field "fullname" "\[^\"\]+/mi-info-sources-base\\.c"] \
+				  [mi_field "debug-fully-read" "${debug_fully_read}"]] \
+			     ".*"]]]
+	mi_gdb_test "-file-list-exec-source-files --group-by-binary" \
+	    ".*\\^done,${p}" \
+	    "-file-list-exec-source-files --group-by-binary, look for mi-info-sources-base.c"
+    }
+}
+
+check_info_sources "false"
+
+mi_continue_to "some_other_func"
+
+check_info_sources "true"