Patchwork [v3] Add debuginfod support to GDB

login
register
mail settings
Submitter Aaron Merey
Date Feb. 10, 2020, 11:51 p.m.
Message ID <CAJDtP-RqdfyS9L6xSu6zazrs2U-3FjLZTj9bfcMNidLgUtUKUA@mail.gmail.com>
Download mbox | patch
Permalink /patch/37890/
State New
Headers show

Comments

Aaron Merey - Feb. 10, 2020, 11:51 p.m.
Hi Simon,

Thanks for the feedback, I've updated the patch with your suggestions
(attached) and I've included some additional comments below.

On Fri, Feb 7, 2020 at 3:39 PM Simon Marchi <simark@simark.ca> wrote:
> I think the cancellation is not quite right, although I have not
> tried it yet.  The problem is that we don't seem to differentiate
> user cancellation from other errors.
>
> When long running operations are cancelled in GDB, that is done by
> throwing an exception.  For example, if you process a lot of data,
> you can call the QUIT macro in your loop such that an exception will
> be thrown if the quit flag (set by the SIGINT handler) is set.
>
> Here, if the user does ctrl-c during a download, the download will
> be interrupted and debuginfod_find_debuginfo will return some error
> code.  We will then simply return an error to the caller.  It will
> look as if the query failed, which is different from user interruption.
>
> If we return a non-zero value in the progress function, we should note
> it somewhere, such that when debuginfod_find_debuginfo returns, we can
> throw a "quit" exception.  The usual place to save that would be in some
> void* user data passed to debuginfod_find_debuginfo which would also be
> passed to the progress function, but unfortunately the library does not
> provide that.

I agree that there's an ambiguity here. To fix this I changed the
progress function so that it now preserves quit_flag's value after
calling check_quit_flag and detecting that the user interrupted a
download. The GDB interpreter will now see that SIGINT was raised,
process it like usual and print "Quit" by the prompt. Hopefully this
sufficiently distinguishes a user interrupt from other errors.

I do see the point you make about how the debuginfod library
might handle these things internally. Another possibility would
be for debuginfod_find_* to return something like -EINTR when
the progressfn triggers a download cancelation.

> > +set port [exec sh -c "while true; do PORT=`expr '(' \$RANDOM % 1000 ')' + 9000`; ss -atn | fgrep \":\$PORT\" || break; done; echo \$PORT"]
>
> Hmmm, this opens the door to flakiness due to TOCTOU.
>
> Would it be feasible instead to ask debuginfod to bind to port 0 (which would make the kernel
> choose any free port) and then read the port number from debuginfod's stdout?  It seems like
> it already partly works, when I start debuginfod with `-p 0`, it binds to random ipv4 and ipv6
> ports.  However it prints:
>
>   started http server on IPv4 IPv6 port=0
>
> But I guess it would not be too difficult in that case to fetch the effective port and print that.
> Also, the ipv4 and ipv6 port numbers are different, so you would need to print both.

To fix this TOCTOU concern I changed the test driver to iterate through
ports starting at 8000 and attempt to start up a debuginfod instance
until a successful start up is detected from the server's output via Expect.

> Just wondering, does the server output something when it's done initializing?  would it be
> possible and simpler to read its stdout and wait for that message?  That's the kind of thing
> that should be relatively easy with Expect.  You would have to start the process with Expect's
> `spawn` procedure and await its output using the `expect` procedure.

Before running the tests we want to make sure the server has found the
necessary debug and source files. Asynchronous threads scan for these
files and they do not necessarily find them before debuginfod
initializes and begins accepting requests. Therefore we have to query
the server for its metrics (part of the debuginfod web API) which
eventually indicate that the correct amount of files were found during
scanning.

Aaron

Patch

From 88c491a68f8fcf6258815787a3f2c0b09e35b90a Mon Sep 17 00:00:00 2001
From: Aaron Merey <amerey@redhat.com>
Date: Thu, 6 Feb 2020 14:05:05 -0500
Subject: [PATCH] Add debuginfod support to GDB

debuginfod is a lightweight web service that indexes ELF/DWARF debugging
resources by build-id and serves them over HTTP.

This patch enables GDB to query debuginfod servers for separate debug
files and source code when it is otherwise not able to find them.

GDB can be built with debuginfod using the --with-debuginfod configure
option.

This requires that libdebuginfod be installed and found at configure time.

debuginfod is packaged with elfutils, starting with version 0.178.

For more information see https://sourceware.org/elfutils/.

Tested on x86_64 Fedora 31.

gdb/ChangeLog:
2020-02-10  Aaron Merey  <amerey@redhat.com>

        * Makefile.in: Handle optional debuginfod support.
        * NEWS: Update.
        * README: Add --with-debuginfod summary.
        * config.in: Regenerate.
        * configure: Regenerate.
        * configure.ac: Handle optional debuginfod support.
        * debuginfod-support.c: debuginfod helper functions.
        * debuginfod-support.h: Ditto.
        * doc/gdb.texinfo: Add --with-debuginfod to configure options
        summary.
        * dwarf2read.c (dwarf2_get_dwz_file): Query debuginfod servers
        when a dwz file cannot be found.
        * elfread.c (elf_symfile_read): Query debuginfod servers when a
        debuginfo file cannot be found.
        * source.c (open_source_file): Query debuginfod servers when a
        source file cannot be found.
        * top.c (print_gdb_configuration): Include
        --{with,without}-debuginfod in the output.

gdb/testsuite/ChangeLog:
2020-02-10  Aaron Merey  <amerey@redhat.com>

        * gdb.debuginfod: New directory for debuginfod tests.
        * gdb.debuginfod/main.c: New test file.
        * gdb.debuginfod/fetch_src_and_symbols.exp: New tests.
---
 gdb/Makefile.in                               |   7 +-
 gdb/NEWS                                      |  14 ++
 gdb/README                                    |   9 +
 gdb/config.in                                 |   3 +
 gdb/configure                                 |  95 +++++++-
 gdb/configure.ac                              |  34 ++-
 gdb/debuginfod-support.c                      | 112 +++++++++
 gdb/debuginfod-support.h                      |  55 +++++
 gdb/doc/gdb.texinfo                           |   8 +
 gdb/dwarf2read.c                              |  19 ++
 gdb/elfread.c                                 |  27 ++-
 gdb/source.c                                  |  35 +++
 .../gdb.debuginfod/fetch_src_and_symbols.exp  | 214 ++++++++++++++++++
 gdb/testsuite/gdb.debuginfod/main.c           |  25 ++
 gdb/top.c                                     |  10 +
 15 files changed, 660 insertions(+), 7 deletions(-)
 create mode 100644 gdb/debuginfod-support.c
 create mode 100644 gdb/debuginfod-support.h
 create mode 100644 gdb/testsuite/gdb.debuginfod/fetch_src_and_symbols.exp
 create mode 100644 gdb/testsuite/gdb.debuginfod/main.c

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 45d1586e85..e31493df9e 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -591,7 +591,8 @@  INTERNAL_CFLAGS_BASE = \
 	$(GDB_CFLAGS) $(OPCODES_CFLAGS) $(READLINE_CFLAGS) $(ZLIBINC) \
 	$(BFD_CFLAGS) $(INCLUDE_CFLAGS) $(LIBDECNUMBER_CFLAGS) \
 	$(INTL_CFLAGS) $(INCGNU) $(INCSUPPORT) $(ENABLE_CFLAGS) \
-	$(INTERNAL_CPPFLAGS) $(SRCHIGH_CFLAGS) $(TOP_CFLAGS) $(PTHREAD_CFLAGS)
+	$(INTERNAL_CPPFLAGS) $(SRCHIGH_CFLAGS) $(TOP_CFLAGS) $(PTHREAD_CFLAGS) \
+	@DEBUGINFOD_CFLAGS@
 INTERNAL_WARN_CFLAGS = $(INTERNAL_CFLAGS_BASE) $(GDB_WARN_CFLAGS)
 INTERNAL_CFLAGS = $(INTERNAL_WARN_CFLAGS) $(GDB_WERROR_CFLAGS)
 
@@ -616,7 +617,8 @@  CLIBS = $(SIM) $(READLINE) $(OPCODES) $(BFD) $(LIBCTF) $(ZLIB) \
 	@LIBS@ @GUILE_LIBS@ @PYTHON_LIBS@ \
 	$(LIBEXPAT) $(LIBLZMA) $(LIBBABELTRACE) $(LIBIPT) \
 	$(WIN32LIBS) $(LIBGNU) $(LIBICONV) \
-	$(LIBMPFR) $(SRCHIGH_LIBS) $(LIBXXHASH) $(PTHREAD_LIBS)
+	$(LIBMPFR) $(SRCHIGH_LIBS) $(LIBXXHASH) $(PTHREAD_LIBS) \
+	@DEBUGINFOD_LIBS@
 CDEPS = $(NAT_CDEPS) $(SIM) $(BFD) $(READLINE_DEPS) $(LIBCTF) \
 	$(OPCODES) $(INTL_DEPS) $(LIBIBERTY) $(CONFIG_DEPS) $(LIBGNU) \
 	$(LIBSUPPORT)
@@ -990,6 +992,7 @@  COMMON_SFILES = \
 	dbxread.c \
 	dcache.c \
 	debug.c \
+	debuginfod-support.c \
 	dictionary.c \
 	disasm.c \
 	disasm-selftests.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index d4e2e70f38..2f9e7756b0 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,20 @@ 
 
 *** Changes since GDB 9
 
+* GDB now supports debuginfod, an HTTP server for distributing ELF/DWARF
+  debugging information as well as source code.
+
+  When built with debuginfod, GDB can automatically query debuginfod
+  servers for the separate debug files and source code of the executable
+  being debugged.
+
+  To build GDB with debuginfod, pass --with-debuginfod to configure (this
+  requires libdebuginfod, the debuginfod client library).
+
+  debuginfod is distributed with elfutils, starting with version 0.178.
+
+  You can get the latest version from https://sourceware.org/elfutils.
+
 * Debugging MS-Windows processes now sets $_exitsignal when the
   inferior is terminated by a signal, instead of setting $_exitcode.
 
diff --git a/gdb/README b/gdb/README
index be7fdcb65d..840cf1f85c 100644
--- a/gdb/README
+++ b/gdb/README
@@ -432,6 +432,15 @@  more obscure GDB `configure' options are not listed here.
      Use the curses library instead of the termcap library, for
      text-mode terminal operations.
 
+`--with-debuginfod'
+     Build GDB with libdebuginfod, the debuginfod client library.  Used
+     to automatically fetch source files and separate debug files from
+     debuginfod servers using the associated executable's build ID.
+     Enabled by default if libdebuginfod is installed and found at
+     configure time.  debuginfod is packaged with elfutils, starting
+     with version 0.178.  You can get the latest version from
+     'https://sourceware.org/elfutils/'.
+
 `--with-libunwind-ia64'
      Use the libunwind library for unwinding function call stack on ia64
      target platforms.
diff --git a/gdb/config.in b/gdb/config.in
index cb886ba8e1..5939b8e436 100644
--- a/gdb/config.in
+++ b/gdb/config.in
@@ -227,6 +227,9 @@ 
 /* Define if you have the babeltrace library. */
 #undef HAVE_LIBBABELTRACE
 
+/* Define to 1 if debuginfod is enabled. */
+#undef HAVE_LIBDEBUGINFOD
+
 /* Define if you have the expat library. */
 #undef HAVE_LIBEXPAT
 
diff --git a/gdb/configure b/gdb/configure
index 72ffad8d37..cdacc1dd8e 100755
--- a/gdb/configure
+++ b/gdb/configure
@@ -719,7 +719,6 @@  GUILE_LIBS
 GUILE_CPPFLAGS
 GUILD_TARGET_FLAG
 GUILD
-pkg_config_prog_path
 HAVE_PYTHON_FALSE
 HAVE_PYTHON_TRUE
 PYTHON_LIBS
@@ -758,6 +757,9 @@  REPORT_BUGS_TEXI
 REPORT_BUGS_TO
 PKGVERSION
 CODESIGN_CERT
+DEBUGINFOD_CFLAGS
+DEBUGINFOD_LIBS
+pkg_config_prog_path
 HAVE_NATIVE_GCORE_TARGET
 TARGET_OBS
 subdirs
@@ -869,6 +871,7 @@  enable_64_bit_bfd
 enable_gdbmi
 enable_tui
 enable_gdbtk
+with_debuginfod
 with_libunwind_ia64
 with_curses
 enable_profiling
@@ -1598,6 +1601,8 @@  Optional Packages:
                           [--with-auto-load-dir]
   --without-auto-load-safe-path
                           do not restrict auto-loaded files locations
+  --with-debuginfod       Enable debuginfo lookups with debuginfod
+                          (auto/yes/no)
   --with-libunwind-ia64   use libunwind frame unwinding for ia64 targets
   --with-curses           use the curses library instead of the termcap
                           library
@@ -6824,8 +6829,94 @@  $as_echo "$as_me: WARNING: gdbtk isn't supported on $host; disabling" >&2;}
     enable_gdbtk=no ;;
 esac
 
-# Libunwind support for ia64.
+# Handle optional debuginfod support
+
+# Check whether --with-debuginfod was given.
+if test "${with_debuginfod+set}" = set; then :
+  withval=$with_debuginfod;
+else
+  with_debuginfod=auto
+fi
 
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to use debuginfod" >&5
+$as_echo_n "checking whether to use debuginfod... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_debuginfod" >&5
+$as_echo "$with_debuginfod" >&6; }
+
+if test "x$with_debuginfod" != xno; then
+  # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_pkg_config_prog_path+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $pkg_config_prog_path in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_pkg_config_prog_path="$pkg_config_prog_path" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_pkg_config_prog_path="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_path_pkg_config_prog_path" && ac_cv_path_pkg_config_prog_path="missing"
+  ;;
+esac
+fi
+pkg_config_prog_path=$ac_cv_path_pkg_config_prog_path
+if test -n "$pkg_config_prog_path"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $pkg_config_prog_path" >&5
+$as_echo "$pkg_config_prog_path" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  if test "${pkg_config_prog_path}" != "missing"; then
+    ${pkg_config_prog_path} --exists --atleast-version=0.178 libdebuginfod
+    if test $? = 0; then
+      DEBUGINFOD_LIBS=`${pkg_config_prog_path} --libs libdebuginfod`
+
+      DEBUGINFOD_CFLAGS=`${pkg_config_prog_path} --cflags libdebuginfod`
+
+
+$as_echo "#define HAVE_LIBDEBUGINFOD 1" >>confdefs.h
+
+    else
+      if test "x$with_debuginfod" = xyes; then
+        as_fn_error $? "\"--with-debuginfod was given, but libdebuginfod is missing or unusable.\"" "$LINENO" 5
+      else
+        { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: libdebuginfod is missing or unusable; some features may be unavailable." >&5
+$as_echo "$as_me: WARNING: libdebuginfod is missing or unusable; some features may be unavailable." >&2;}
+      fi
+    fi
+  else
+    if test "x$with_debuginfod" = xyes; then
+      as_fn_error $? "pkg-config missing or unusable; cannot find libdebuginfod" "$LINENO" 5
+    else
+      { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: pkg-config missing or unusable; cannot find libdebuginfod" >&5
+$as_echo "$as_me: WARNING: pkg-config missing or unusable; cannot find libdebuginfod" >&2;}
+    fi
+  fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: debuginfod support disabled; some features may be unavailable." >&5
+$as_echo "$as_me: WARNING: debuginfod support disabled; some features may be unavailable." >&2;}
+fi
+
+# Libunwind support for ia64.
 
 # Check whether --with-libunwind-ia64 was given.
 if test "${with_libunwind_ia64+set}" = set; then :
diff --git a/gdb/configure.ac b/gdb/configure.ac
index 0ca169101b..e9b3133940 100644
--- a/gdb/configure.ac
+++ b/gdb/configure.ac
@@ -323,8 +323,40 @@  case $host_os in
     enable_gdbtk=no ;;
 esac
 
-# Libunwind support for ia64.
+# Handle optional debuginfod support
+AC_ARG_WITH([debuginfod],
+  AC_HELP_STRING([--with-debuginfod], [Enable debuginfo lookups with debuginfod (auto/yes/no)]),
+  [], [with_debuginfod=auto])
+AC_MSG_CHECKING([whether to use debuginfod])
+AC_MSG_RESULT([$with_debuginfod])
+
+if test "x$with_debuginfod" != xno; then
+  AC_PATH_PROG(pkg_config_prog_path, pkg-config, missing)
+  if test "${pkg_config_prog_path}" != "missing"; then
+    ${pkg_config_prog_path} --exists --atleast-version=0.178 libdebuginfod
+    if test $? = 0; then
+      AC_SUBST([DEBUGINFOD_LIBS], [`${pkg_config_prog_path} --libs libdebuginfod`])
+      AC_SUBST([DEBUGINFOD_CFLAGS], [`${pkg_config_prog_path} --cflags libdebuginfod`])
+      AC_DEFINE([HAVE_LIBDEBUGINFOD], [1], [Define to 1 if debuginfod is enabled.])
+    else
+      if test "x$with_debuginfod" = xyes; then
+        AC_MSG_ERROR(["--with-debuginfod was given, but libdebuginfod is missing or unusable."])
+      else
+        AC_MSG_WARN([libdebuginfod is missing or unusable; some features may be unavailable.])
+      fi
+    fi
+  else
+    if test "x$with_debuginfod" = xyes; then
+      AC_MSG_ERROR([--with-debuginfod was given, but pkg-config missing or unusable; cannot find libdebuginfod])
+    else
+      AC_MSG_WARN([pkg-config missing or unusable; debuginfod support disabled.])
+    fi
+  fi
+else
+  AC_MSG_WARN([debuginfod support disabled; some features may be unavailable.])
+fi
 
+# Libunwind support for ia64.
 AC_ARG_WITH(libunwind-ia64,
 AS_HELP_STRING([--with-libunwind-ia64],
 	       [use libunwind frame unwinding for ia64 targets]),,
diff --git a/gdb/debuginfod-support.c b/gdb/debuginfod-support.c
new file mode 100644
index 0000000000..09704ca712
--- /dev/null
+++ b/gdb/debuginfod-support.c
@@ -0,0 +1,112 @@ 
+/* debuginfod utilities for GDB.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include "defs.h"
+#include "debuginfod-support.h"
+
+#ifndef HAVE_LIBDEBUGINFOD
+int
+debuginfod_source_query (const unsigned char *build_id __attribute__((unused)),
+                         int build_id_len __attribute__((unused)),
+                         const char *srcpath __attribute__((unused)),
+                         gdb::unique_xmalloc_ptr<char> *filename __attribute__((unused)))
+{
+  return -ENOSYS;
+}
+
+int
+debuginfod_debuginfo_query (const unsigned char *build_id __attribute__((unused)),
+                            int build_id_len __attribute__((unused)),
+                            gdb::unique_xmalloc_ptr<char> *filename __attribute__((unused)))
+{
+  return -ENOSYS;
+}
+#else
+#include <elfutils/debuginfod.h>
+
+static int
+progressfn (debuginfod_client *c,
+             long a __attribute__((unused)),
+             long b __attribute__((unused)))
+{
+  int quit_flag = check_quit_flag ();
+
+  /* Avoid swallowing quit_flag's current value.  */
+  if (quit_flag)
+    set_quit_flag ();
+
+  return quit_flag;
+}
+
+static debuginfod_client *
+debuginfod_init ()
+{
+  debuginfod_client *c = debuginfod_begin ();
+
+  if (c != nullptr)
+    debuginfod_set_progressfn (c, progressfn);
+
+  return c;
+}
+
+/* See debuginfod-support.h  */
+
+int
+debuginfod_source_query (const unsigned char *build_id,
+                         int build_id_len,
+                         const char *srcpath,
+                         gdb::unique_xmalloc_ptr<char> *filename)
+{
+  debuginfod_client *c = debuginfod_init ();
+
+  if (c == nullptr)
+    return -ENOMEM;
+
+  char *fname = NULL;
+  int fd = debuginfod_find_source (c,
+                                   build_id,
+                                   build_id_len,
+                                   srcpath,
+                                   &fname);
+  debuginfod_end (c);
+  filename->reset (fname);
+
+  return fd;
+}
+
+/* See debuginfod-support.h  */
+
+int
+debuginfod_debuginfo_query (const unsigned char *build_id,
+                            int build_id_len,
+                            gdb::unique_xmalloc_ptr<char> *filename)
+{
+  debuginfod_client *c = debuginfod_init ();
+
+  if (c == nullptr)
+    return -ENOMEM;
+
+  char *fname = NULL;
+  int fd = debuginfod_find_debuginfo (c, build_id, build_id_len, &fname);
+  debuginfod_end (c);
+  filename->reset (fname);
+
+  return fd;
+}
+#endif
diff --git a/gdb/debuginfod-support.h b/gdb/debuginfod-support.h
new file mode 100644
index 0000000000..c5e7b65721
--- /dev/null
+++ b/gdb/debuginfod-support.h
@@ -0,0 +1,55 @@ 
+/* debuginfod utilities for GDB.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef DEBUGINFOD_SUPPORT_H
+#define DEBUGINFOD_SUPPORT_H
+
+/* Query debuginfod servers for a source file associated with an
+   executable with BUILD_ID. BUILD_ID can be given as a binary blob or
+   a null-terminated string. If given as a binary blob, BUILD_ID_LEN
+   should be the number of bytes. If given as a null-terminated string,
+   BUILD_ID_LEN should be 0.
+
+   SRC_PATH should be the source file's absolute path that includes the
+   compilation directory of the CU associated with the source file.
+   For example if a CU's compilation directory is `/my/build` and the
+   source file path is `/my/source/foo.c`, then SRC_PATH should be
+   `/my/build/../source/foo.c`.
+
+   If the file is successfully retrieved, its path on the local machine
+   is stored in FILENAME. If GDB is not built with debuginfod, this
+   function returns -ENOSYS.  */
+
+extern int debuginfod_source_query (const unsigned char *build_id,
+                                    int build_id_len,
+                                    const char *src_path,
+                                    gdb::unique_xmalloc_ptr<char> *filename);
+
+/* Query debuginfod servers for a debuginfo file with BUILD_ID.
+   BUILD_ID can be given as a binary blob or a null-terminated string.
+   If given as a binary blob, BUILD_ID_LEN should be the number of bytes.
+   If given as a null-terminated string, BUILD_ID_LEN should be 0.
+
+   If the file is successfully retrieved, its path on the local machine
+   is stored in FILENAME. If GDB is not built with debuginfod, this
+   function returns -ENOSYS.  */
+
+extern int debuginfod_debuginfo_query (const unsigned char *build_id,
+                                       int build_id_len,
+                                       gdb::unique_xmalloc_ptr<char> *filename);
+#endif /* DEBUGINFOD_SUPPORT_H */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index a2866a2833..9d2922be34 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -37794,6 +37794,14 @@  supported).
 Use the curses library instead of the termcap library, for text-mode
 terminal operations.
 
+@item --with-debuginfod
+Build @value{GDBN} with libdebuginfod, the debuginfod client library.
+Used to automatically fetch source files and separate debug files from
+debuginfod servers using the associated executable's build ID. Enabled
+by default if libdebuginfod is installed and found at configure time.
+debuginfod is packaged with elfutils, starting with version 0.178. You
+can get the latest version from `https://sourceware.org/elfutils/'.
+
 @item --with-libunwind-ia64
 Use the libunwind library for unwinding function call stack on ia64
 target platforms.  See http://www.nongnu.org/libunwind/index.html for
diff --git a/gdb/dwarf2read.c b/gdb/dwarf2read.c
index dafe01d94a..50224dabda 100644
--- a/gdb/dwarf2read.c
+++ b/gdb/dwarf2read.c
@@ -77,6 +77,7 @@ 
 #include "gdbsupport/selftest.h"
 #include "rust-lang.h"
 #include "gdbsupport/pathstuff.h"
+#include "debuginfod-support.h"
 
 /* When == 1, print basic high level tracing messages.
    When > 1, be more verbose.
@@ -2746,6 +2747,24 @@  dwarf2_get_dwz_file (struct dwarf2_per_objfile *dwarf2_per_objfile)
   if (dwz_bfd == NULL)
     dwz_bfd = build_id_to_debug_bfd (buildid_len, buildid);
 
+  if (dwz_bfd == nullptr)
+    {
+      gdb::unique_xmalloc_ptr<char> alt_filename;
+      scoped_fd fd (debuginfod_debuginfo_query (buildid,
+                                                buildid_len,
+                                                &alt_filename));
+
+      if (fd.get () >= 0)
+        {
+          /* File successfully retrieved from server.  */
+          dwz_bfd = gdb_bfd_open (alt_filename.get (), gnutarget, -1);
+
+          if (dwz_bfd != nullptr
+              && !build_id_verify (dwz_bfd.get (), buildid_len, buildid))
+            dwz_bfd.reset (nullptr);
+        }
+    }
+
   if (dwz_bfd == NULL)
     error (_("could not find '.gnu_debugaltlink' file for %s"),
 	   objfile_name (dwarf2_per_objfile->objfile));
diff --git a/gdb/elfread.c b/gdb/elfread.c
index 453bca527e..90be5b10fb 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -49,6 +49,8 @@ 
 #include "mdebugread.h"
 #include "ctfread.h"
 #include "gdbsupport/gdb_string_view.h"
+#include "gdbsupport/scoped_fd.h"
+#include "debuginfod-support.h"
 
 /* Forward declarations.  */
 extern const struct sym_fns elf_sym_fns_gdb_index;
@@ -1316,8 +1318,29 @@  elf_symfile_read (struct objfile *objfile, symfile_add_flags symfile_flags)
 	  symbol_file_add_separate (debug_bfd.get (), debugfile.c_str (),
 				    symfile_flags, objfile);
 	}
-	else
-	  has_dwarf2 = false;
+      else
+        {
+          has_dwarf2 = false;
+          const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd);
+
+          if (build_id != nullptr)
+            {
+              gdb::unique_xmalloc_ptr<char> symfile_path;
+              scoped_fd fd (debuginfod_debuginfo_query (build_id->data,
+                                                        build_id->size,
+                                                        &symfile_path));
+
+              if (fd.get () >= 0)
+                {
+                  /* File successfully retrieved from server.  */
+                  gdb_bfd_ref_ptr debug_bfd (symfile_bfd_open (symfile_path.get ()));
+
+                  symbol_file_add_separate (debug_bfd.get (), symfile_path.get (),
+                                            symfile_flags, objfile);
+                  has_dwarf2 = true;
+                }
+            }
+        }
     }
 
   /* Read the CTF section only if there is no DWARF info.  */
diff --git a/gdb/source.c b/gdb/source.c
index 327e9c1229..f4036a3bc1 100644
--- a/gdb/source.c
+++ b/gdb/source.c
@@ -48,6 +48,8 @@ 
 #include "source-cache.h"
 #include "cli/cli-style.h"
 #include "observable.h"
+#include "build-id.h"
+#include "debuginfod-support.h"
 
 #define OPEN_MODE (O_RDONLY | O_BINARY)
 #define FDOPEN_MODE FOPEN_RB
@@ -1153,6 +1155,39 @@  open_source_file (struct symtab *s)
   s->fullname = NULL;
   scoped_fd fd = find_and_open_source (s->filename, SYMTAB_DIRNAME (s),
 				       &fullname);
+
+  if (fd.get () < 0)
+    {
+      if (SYMTAB_COMPUNIT (s) != nullptr)
+        {
+          const objfile *ofp = COMPUNIT_OBJFILE (SYMTAB_COMPUNIT (s));
+
+          std::string srcpath;
+          if (IS_DIR_SEPARATOR (s->filename[0]))
+            srcpath = s->filename;
+          else
+            {
+              srcpath = SYMTAB_DIRNAME (s);
+              srcpath += SLASH_STRING;
+              srcpath += s->filename;
+            }
+
+          const struct bfd_build_id *build_id = build_id_bfd_get (ofp->obfd);
+
+          if (build_id != nullptr)
+            {
+              /* Query debuginfod for the source file.  */
+              scoped_fd src_fd (debuginfod_source_query (build_id->data,
+                                                         build_id->size,
+                                                         srcpath.c_str (),
+                                                         &fullname));
+
+              s->fullname = fullname.release ();
+              return src_fd;
+            }
+        }
+    }
+
   s->fullname = fullname.release ();
   return fd;
 }
diff --git a/gdb/testsuite/gdb.debuginfod/fetch_src_and_symbols.exp b/gdb/testsuite/gdb.debuginfod/fetch_src_and_symbols.exp
new file mode 100644
index 0000000000..482447b5ea
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/fetch_src_and_symbols.exp
@@ -0,0 +1,214 @@ 
+# Copyright 2020 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 debuginfod functionality
+
+standard_testfile main.c
+
+load_lib dwarf.exp
+
+if { [which debuginfod] == 0 } {
+    untested "cannot find debuginfod"
+    return -1
+}
+
+if { [which curl] == 0 } {
+    untested "cannot find curl"
+    return -1
+}
+
+# Skip testing if gdb was not configured with debuginfod
+if { [string first "with-debuginfod" [exec $GDB --configuration]] == -1 } {
+    untested "gdb not configured with debuginfod"
+    return -1
+}
+
+set cache [standard_output_file ".client_cache"]
+set db [standard_output_file ".debuginfod.db"]
+
+# Delete any preexisting test files
+file delete -force $cache
+file delete -force $db
+
+set sourcetmp [standard_output_file tmp-${srcfile}]
+set outputdir [standard_output_file {}]
+
+# Make a copy source file that we can move around
+if { [catch {file copy -force ${srcdir}/${subdir}/${srcfile} \
+              [standard_output_file ${sourcetmp}]}] != 0 } {
+    error "create temporary file"
+    return -1
+}
+
+if { [gdb_compile "$sourcetmp" "$binfile" executable {debug}] != "" } {
+    fail "compile"
+    return -1
+}
+
+setenv DEBUGINFOD_URLS ""
+setenv DEBUGINFOD_TIMEOUT 30
+setenv DEBUGINFOD_CACHE_PATH $cache
+
+# Test that gdb cannot find source without debuginfod
+clean_restart $binfile
+gdb_test_no_output "set substitute-path $outputdir /dev/null"
+gdb_test "list" ".*No such file or directory.*"
+
+# Strip symbols into separate file and move it so gdb cannot find it without debuginfod
+if { [gdb_gnu_strip_debug $binfile ""] != 0 } {
+    fail "strip debuginfo"
+    return -1
+}
+
+set debugdir [standard_output_file "debug"]
+set debuginfo [standard_output_file "fetch_src_and_symbols.debug"]
+
+file mkdir $debugdir
+file rename -force $debuginfo $debugdir
+
+# Test that gdb cannot find symbols without debuginfod
+clean_restart $binfile
+gdb_test "file" ".*No symbol file.*"
+
+# Write some assembly that just has a .gnu_debugaltlink section.
+# Copied from testsuite/gdb.dwarf2/dwzbuildid.exp.
+proc write_just_debugaltlink {filename dwzname buildid} {
+    set asm_file [standard_output_file $filename]
+
+    Dwarf::assemble $asm_file {
+        upvar dwzname dwzname
+        upvar buildid buildid
+
+        gnu_debugaltlink $dwzname $buildid
+
+        # Only the DWARF reader checks .gnu_debugaltlink, so make sure
+        # there is a bit of DWARF in here.
+        cu {} {
+            compile_unit {{language @DW_LANG_C}} {
+            }
+        }
+    }
+}
+
+# Write some DWARF that also sets the buildid.
+# Copied from testsuite/gdb.dwarf2/dwzbuildid.exp.
+proc write_dwarf_file {filename buildid {value 99}} {
+    set asm_file [standard_output_file $filename]
+
+    Dwarf::assemble $asm_file {
+        declare_labels int_label int_label2
+
+        upvar buildid buildid
+        upvar value value
+
+        build_id $buildid
+
+        cu {} {
+            compile_unit {{language @DW_LANG_C}} {
+                int_label2: base_type {
+                    {name int}
+                    {byte_size 4 sdata}
+                    {encoding @DW_ATE_signed}
+                }
+
+                constant {
+                    {name the_int}
+                    {type :$int_label2}
+                    {const_value $value data1}
+                }
+            }
+        }
+    }
+}
+
+set buildid "01234567890abcdef0123456"
+
+write_just_debugaltlink ${binfile}_has_altlink.S ${binfile}_dwz.o $buildid
+write_dwarf_file ${binfile}_dwz.S $buildid
+
+if {[gdb_compile ${binfile}_has_altlink.S ${binfile}_alt.o object nodebug] != ""} {
+    fail "compile main with altlink"
+    return -1
+}
+
+if {[gdb_compile ${binfile}_dwz.S ${binfile}_dwz.o object nodebug] != ""} {
+    fail "compile altlink"
+    return -1
+}
+
+file rename -force ${binfile}_dwz.o $debugdir
+
+# Test that gdb cannot find dwz without debuginfod.
+clean_restart
+gdb_test "file ${binfile}_alt.o" ".*could not find '.gnu_debugaltlink'.*"
+
+# Find an unused port
+set port 7999
+set found 0
+while { ! $found } {
+  incr port
+  if { $port == 65536 } {
+    fail "no available ports"
+    return -1
+  }
+
+  spawn debuginfod -vvvv -d $db -p $port -F $debugdir
+  expect {
+    "started http server on IPv4 IPv6 port=$port" { set found 1 }
+    "failed to bind to port" { kill_wait_spawned_process $spawn_id }
+    timeout {
+      fail "find port timeout"
+      return -1
+    }
+  }
+}
+
+set metrics [list "ready 1" \
+             "thread_work_total{role=\"traverse\"} 1" \
+             "thread_work_pending{role=\"scan\"} 0" \
+             "thread_busy{role=\"scan\"} 0"]
+
+# Check server metrics to confirm init has completed.
+foreach m $metrics {
+  set timelim 20
+  while { $timelim != 0 } {
+    sleep 0.5
+    catch {exec curl -s http://127.0.0.1:$port/metrics} got
+
+    if { [regexp $m $got] } {
+      break
+    }
+
+    incr timelim -1
+  }
+
+  if { $timelim == 0 } {
+    fail "server init timeout"
+    return -1
+  }
+}
+
+# Point the client to the server
+setenv DEBUGINFOD_URLS http://127.0.0.1:$port
+
+# gdb should now find the symbol and source files
+clean_restart $binfile
+gdb_test_no_output "set substitute-path $outputdir /dev/null"
+gdb_test "br main" "Breakpoint 1 at.*file.*"
+gdb_test "l" ".*This program is distributed in the hope.*"
+
+# gdb should now find the debugaltlink file
+clean_restart
+gdb_test "file ${binfile}_alt.o" ".*Reading symbols from ${binfile}_alt.o\.\.\.\[\r\n\]"
diff --git a/gdb/testsuite/gdb.debuginfod/main.c b/gdb/testsuite/gdb.debuginfod/main.c
new file mode 100644
index 0000000000..73abaf58b1
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/main.c
@@ -0,0 +1,25 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2020 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/>.  */
+
+/* Dummy main function.  */
+
+int
+main()
+{
+  asm ("main_label: .globl main_label");
+  return 0;
+}
diff --git a/gdb/top.c b/gdb/top.c
index f702af9acd..1a98ae198c 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -1528,6 +1528,16 @@  This GDB was configured as follows:\n\
 "));
 #endif
 
+#if HAVE_LIBDEBUGINFOD
+  fprintf_filtered (stream, _("\
+             --with-debuginfod\n\
+"));
+#else
+   fprintf_filtered (stream, _("\
+             --without-debuginfod\n\
+"));
+#endif
+
 #if HAVE_GUILE
   fprintf_filtered (stream, _("\
              --with-guile\n\
-- 
2.24.1