diff --git a/config/debuginfod.m4 b/config/debuginfod.m4 new file mode 100644 --- /dev/null +++ b/config/debuginfod.m4 @@ -0,0 +1,38 @@ +dnl Copyright (C) 1997-2019 Free Software Foundation, Inc. +dnl This file is free software, distributed under the terms of the GNU +dnl General Public License. As a special exception to the GNU General +dnl Public License, this file may be distributed as part of a program +dnl that contains a configuration script generated by Autoconf, under +dnl the same distribution terms as the rest of that program. + +AC_DEFUN([AC_DEBUGINFOD], +[ +# Enable debuginfod +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 "${with_debuginfod}" = no; then + AC_MSG_WARN([debuginfod support disabled; some features may be unavailable.]) +else + AC_CHECK_LIB([debuginfod], [debuginfod_begin], [have_debuginfod_lib=yes]) + AC_CHECK_DECL([debuginfod_begin], [have_debuginfod_h=yes], [], + [#include ]) + if test "x$have_debuginfod_lib" = "xyes" -a \ + "x$have_debuginfod_h" = "xyes"; then + AC_DEFINE([HAVE_LIBDEBUGINFOD], [1], + [Define to 1 if debuginfod is enabled.]) + AC_SUBST([LIBDEBUGINFOD], ["-ldebuginfod"]) + else + AC_SUBST([LIBDEBUGINFOD], []) + if test "$with_debuginfod" = yes; then + AC_MSG_ERROR([debuginfod is missing or unusable]) + else + AC_MSG_WARN([debuginfod is missing or unusable; some features may be unavailable.]) + fi + fi +fi +]) diff --git a/gdb/Makefile.in b/gdb/Makefile.in --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -612,7 +612,8 @@ CLIBS = $(SIM) $(READLINE) $(OPCODES) $(BFD) $(LIBCTF) $(ZLIB) \ @LIBS@ @GUILE_LIBS@ @PYTHON_LIBS@ \ $(LIBEXPAT) $(LIBLZMA) $(LIBBABELTRACE) $(LIBIPT) \ $(LIBIBERTY) $(WIN32LIBS) $(LIBGNU) $(LIBICONV) $(LIBMPFR) \ - $(SRCHIGH_LIBS) $(LIBXXHASH) $(PTHREAD_LIBS) + $(SRCHIGH_LIBS) $(LIBXXHASH) $(PTHREAD_LIBS) \ + @LIBDEBUGINFOD@ CDEPS = $(NAT_CDEPS) $(SIM) $(BFD) $(READLINE_DEPS) $(LIBCTF) \ $(OPCODES) $(INTL_DEPS) $(LIBIBERTY) $(CONFIG_DEPS) $(LIBGNU) @@ -1016,6 +1017,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 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -932,6 +932,20 @@ SH-5/SH64 running OpenBSD SH-5/SH64 support in sh*-*-openbsd* manual for a further description of this feature. +* 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. + * New features in the GDB remote stub, GDBserver ** GDBserver is now able to start inferior processes with a diff --git a/gdb/README b/gdb/README --- 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 --- a/gdb/config.in +++ b/gdb/config.in @@ -230,6 +230,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 --- a/gdb/configure +++ b/gdb/configure @@ -758,6 +758,7 @@ REPORT_BUGS_TEXI REPORT_BUGS_TO PKGVERSION CODESIGN_CERT +LIBDEBUGINFOD HAVE_NATIVE_GCORE_TARGET TARGET_OBS subdirs @@ -875,6 +876,7 @@ enable_64_bit_bfd enable_gdbmi enable_tui enable_gdbtk +with_debuginfod with_libunwind_ia64 with_curses enable_profiling @@ -1611,6 +1613,8 @@ Optional Packages: do not restrict auto-loaded files locations --with-rpm query rpm database for missing debuginfos (yes/no, def. auto=librpm.so) + --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 @@ -2271,6 +2275,52 @@ rm -f conftest.val } # ac_fn_c_compute_int +# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES +# --------------------------------------------- +# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR +# accordingly. +ac_fn_c_check_decl () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + as_decl_name=`echo $2|sed 's/ *(.*//'` + as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 +$as_echo_n "checking whether $as_decl_name is declared... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +#ifndef $as_decl_name +#ifdef __cplusplus + (void) $as_decl_use; +#else + (void) $as_decl_name; +#endif +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_decl + # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly @@ -2449,52 +2499,6 @@ $as_echo "$ac_res" >&6; } } # ac_fn_c_check_type -# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES -# --------------------------------------------- -# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR -# accordingly. -ac_fn_c_check_decl () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - as_decl_name=`echo $2|sed 's/ *(.*//'` - as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 -$as_echo_n "checking whether $as_decl_name is declared... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -int -main () -{ -#ifndef $as_decl_name -#ifdef __cplusplus - (void) $as_decl_use; -#else - (void) $as_decl_name; -#endif -#endif - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - eval "$3=yes" -else - eval "$3=no" -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_check_decl - # ac_fn_cxx_try_link LINENO # ------------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. @@ -6658,8 +6662,8 @@ $as_echo_n "checking specific librpm version... " >&6; } if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error "cannot run test program while cross compiling -See \`config.log' for more details." "$LINENO" 5; } +as_fn_error $? "cannot run test program while cross compiling +See \`config.log' for more details" "$LINENO" 5; } else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -6830,7 +6834,7 @@ $as_echo "#define HAVE_LIBRPM 1" >>confdefs.h $as_echo "no" >&6; } LIBS="$save_LIBS" if $DLOPEN_REQUIRE; then - as_fn_error "Specific name $LIBRPM was requested but it could not be opened." "$LINENO" 5 + as_fn_error $? "Specific name $LIBRPM was requested but it could not be opened." "$LINENO" 5 fi @@ -6845,7 +6849,7 @@ if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then set dummy ${ac_tool_prefix}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 test "${ac_cv_path_PKG_CONFIG+set}" = set; then : +if ${ac_cv_path_PKG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $PKG_CONFIG in @@ -6859,7 +6863,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_PKG_CONFIG="$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 @@ -6888,7 +6892,7 @@ if test -z "$ac_cv_path_PKG_CONFIG"; then 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 test "${ac_cv_path_ac_pt_PKG_CONFIG+set}" = set; then : +if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_PKG_CONFIG in @@ -6902,7 +6906,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_PKG_CONFIG="$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 @@ -7086,7 +7090,7 @@ $as_echo "#define HAVE_LIBRPM 1" >>confdefs.h LIBS="$LIBS $RPM_LIBS" else if $RPM_REQUIRE; then - as_fn_error "$RPM_PKG_ERRORS" "$LINENO" 5 + as_fn_error $? "$RPM_PKG_ERRORS" "$LINENO" 5 else { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $RPM_PKG_ERRORS" >&5 $as_echo "$as_me: WARNING: $RPM_PKG_ERRORS" >&2;} @@ -7332,8 +7336,92 @@ $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 + +# Enable debuginfod + +# 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 "${with_debuginfod}" = no; then + { $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;} +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for debuginfod_begin in -ldebuginfod" >&5 +$as_echo_n "checking for debuginfod_begin in -ldebuginfod... " >&6; } +if ${ac_cv_lib_debuginfod_debuginfod_begin+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldebuginfod $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char debuginfod_begin (); +int +main () +{ +return debuginfod_begin (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_debuginfod_debuginfod_begin=yes +else + ac_cv_lib_debuginfod_debuginfod_begin=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_debuginfod_debuginfod_begin" >&5 +$as_echo "$ac_cv_lib_debuginfod_debuginfod_begin" >&6; } +if test "x$ac_cv_lib_debuginfod_debuginfod_begin" = xyes; then : + have_debuginfod_lib=yes +fi + + ac_fn_c_check_decl "$LINENO" "debuginfod_begin" "ac_cv_have_decl_debuginfod_begin" "#include +" +if test "x$ac_cv_have_decl_debuginfod_begin" = xyes; then : + have_debuginfod_h=yes +fi + + if test "x$have_debuginfod_lib" = "xyes" -a \ + "x$have_debuginfod_h" = "xyes"; then + +$as_echo "#define HAVE_LIBDEBUGINFOD 1" >>confdefs.h + + LIBDEBUGINFOD="-ldebuginfod" + else + + if test "$with_debuginfod" = yes; then + as_fn_error $? "debuginfod is missing or unusable" "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: debuginfod is missing or unusable; some features may be unavailable." >&5 +$as_echo "$as_me: WARNING: debuginfod is missing or unusable; some features may be unavailable." >&2;} + fi + fi +fi + + +# Libunwind support for ia64. # Check whether --with-libunwind-ia64 was given. if test "${with_libunwind_ia64+set}" = set; then : @@ -16441,7 +16529,7 @@ _ACEOF for ac_header in selinux/selinux.h do : ac_fn_c_check_header_mongrel "$LINENO" "selinux/selinux.h" "ac_cv_header_selinux_selinux_h" "$ac_includes_default" -if test "x$ac_cv_header_selinux_selinux_h" = x""yes; then : +if test "x$ac_cv_header_selinux_selinux_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_SELINUX_SELINUX_H 1 _ACEOF @@ -16452,7 +16540,7 @@ done { $as_echo "$as_me:${as_lineno-$LINENO}: checking for security_get_boolean_active in -lselinux" >&5 $as_echo_n "checking for security_get_boolean_active in -lselinux... " >&6; } -if test "${ac_cv_lib_selinux_security_get_boolean_active+set}" = set; then : +if ${ac_cv_lib_selinux_security_get_boolean_active+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS @@ -16486,7 +16574,7 @@ LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_selinux_security_get_boolean_active" >&5 $as_echo "$ac_cv_lib_selinux_security_get_boolean_active" >&6; } -if test "x$ac_cv_lib_selinux_security_get_boolean_active" = x""yes; then : +if test "x$ac_cv_lib_selinux_security_get_boolean_active" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBSELINUX 1 _ACEOF diff --git a/gdb/configure.ac b/gdb/configure.ac --- a/gdb/configure.ac +++ b/gdb/configure.ac @@ -18,6 +18,8 @@ dnl along with this program. If not, see . dnl Process this file with autoconf to produce a configure script. +m4_include(../config/debuginfod.m4) + AC_INIT(main.c) AC_CONFIG_HEADERS(config.h:config.in, [echo > stamp-h]) AM_MAINTAINER_MODE @@ -516,8 +518,10 @@ case $host_os in enable_gdbtk=no ;; esac -# Libunwind support for ia64. +# Handle optional debuginfod support +AC_DEBUGINFOD +# 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 --- /dev/null +++ b/gdb/debuginfod-support.c @@ -0,0 +1,155 @@ +/* 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 . */ + +#include "defs.h" +#include +#include "cli/cli-style.h" +#include "gdbsupport/scoped_fd.h" +#include "debuginfod-support.h" + +#ifndef HAVE_LIBDEBUGINFOD +scoped_fd +debuginfod_source_query (const unsigned char *build_id, + int build_id_len, + const char *srcpath, + gdb::unique_xmalloc_ptr *destname) +{ + return scoped_fd (-ENOSYS); +} + +scoped_fd +debuginfod_debuginfo_query (const unsigned char *build_id, + int build_id_len, + const char *filename, + gdb::unique_xmalloc_ptr *destname) +{ + return scoped_fd (-ENOSYS); +} +#else +#include + +/* TODO: Use debuginfod API extensions instead of these globals. */ +static std::string desc; +static std::string fname; +static bool has_printed; + +static int +progressfn (debuginfod_client *c, long cur, long total) +{ + if (check_quit_flag ()) + { + printf_filtered ("Cancelling download of %s %ps...\n", + desc.c_str (), + styled_string (file_name_style.style (), fname.c_str ())); + return 1; + } + + if (!has_printed && total != 0) + { + /* Print this message only once. */ + has_printed = true; + printf_filtered ("Downloading %s %ps...\n", + desc.c_str (), + styled_string (file_name_style.style (), fname.c_str ())); + } + + return 0; +} + +static debuginfod_client * +debuginfod_init () +{ + debuginfod_client *c = debuginfod_begin (); + + if (c != nullptr) + debuginfod_set_progressfn (c, progressfn); + + return c; +} + +/* See debuginfod-support.h */ + +scoped_fd +debuginfod_source_query (const unsigned char *build_id, + int build_id_len, + const char *srcpath, + gdb::unique_xmalloc_ptr *destname) +{ + if (getenv (DEBUGINFOD_URLS_ENV_VAR) == NULL) + return scoped_fd (-ENOSYS); + + debuginfod_client *c = debuginfod_init (); + + if (c == nullptr) + return scoped_fd (-ENOMEM); + + desc = std::string ("source file"); + fname = std::string (srcpath); + has_printed = false; + + scoped_fd fd (debuginfod_find_source (c, + build_id, + build_id_len, + srcpath, + nullptr)); + + /* TODO: Add 'set debug debuginfod' command to control when error messages are shown. */ + if (fd.get () < 0 && fd.get () != -ENOENT) + printf_filtered (_("Download failed: %s. Continuing without source file %ps.\n"), + safe_strerror (-fd.get ()), + styled_string (file_name_style.style (), srcpath)); + else + destname->reset (xstrdup (srcpath)); + + debuginfod_end (c); + return fd; +} + +/* See debuginfod-support.h */ + +scoped_fd +debuginfod_debuginfo_query (const unsigned char *build_id, + int build_id_len, + const char *filename, + gdb::unique_xmalloc_ptr *destname) +{ + if (getenv (DEBUGINFOD_URLS_ENV_VAR) == NULL) + return scoped_fd (-ENOSYS); + + debuginfod_client *c = debuginfod_init (); + + if (c == nullptr) + return scoped_fd (-ENOMEM); + + desc = std::string ("separate debug info for"); + fname = std::string (filename); + has_printed = false; + char *dname = nullptr; + + scoped_fd fd (debuginfod_find_debuginfo (c, build_id, build_id_len, &dname)); + + if (fd.get () < 0 && fd.get () != -ENOENT) + printf_filtered (_("Download failed: %s. Continuing without debug info for %ps.\n"), + safe_strerror (-fd.get ()), + styled_string (file_name_style.style (), filename)); + + destname->reset (dname); + debuginfod_end (c); + return fd; +} +#endif diff --git a/gdb/debuginfod-support.h b/gdb/debuginfod-support.h new file mode 100644 --- /dev/null +++ b/gdb/debuginfod-support.h @@ -0,0 +1,62 @@ +/* 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 . */ + +#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 DESTNAME. If GDB is not built with debuginfod, this + function returns -ENOSYS. */ + +extern scoped_fd +debuginfod_source_query (const unsigned char *build_id, + int build_id_len, + const char *src_path, + gdb::unique_xmalloc_ptr *destname); + +/* Query debuginfod servers for a debug info 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. + + FILENAME should be the name or path of the main binary associated with + the separate debug info. It is used for printing messages to the user. + + If the file is successfully retrieved, its path on the local machine + is stored in DESTNAME. If GDB is not built with debuginfod, this + function returns -ENOSYS. */ + +extern scoped_fd +debuginfod_debuginfo_query (const unsigned char *build_id, + int build_id_len, + const char *filename, + gdb::unique_xmalloc_ptr *destname); + +#endif /* DEBUGINFOD_SUPPORT_H */ diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -37726,6 +37726,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 --- 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. @@ -2717,6 +2718,29 @@ dwarf2_get_dwz_file (struct dwarf2_per_objfile *dwarf2_per_objfile) dwz_bfd.reset (nullptr); } + if (dwz_bfd == nullptr) + { + gdb::unique_xmalloc_ptr alt_filename; + const char *origname = dwarf2_per_objfile->objfile->original_name; + + scoped_fd fd (debuginfod_debuginfo_query (buildid, + buildid_len, + origname, + &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) + warning (_("File \"%s\" from debuginfod cannot be opened as bfd"), + alt_filename.get ()); + else if (!build_id_verify (dwz_bfd.get (), buildid_len, buildid)) + dwz_bfd.reset (nullptr); + } + } + if (dwz_bfd == NULL) dwz_bfd = build_id_to_debug_bfd (buildid_len, buildid, NULL); diff --git a/gdb/elfread.c b/gdb/elfread.c --- 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; @@ -1313,12 +1315,42 @@ elf_symfile_read (struct objfile *objfile, symfile_add_flags symfile_flags) symbol_file_add_separate (debug_bfd.get (), debugfile.c_str (), symfile_flags, objfile); } - /* Check if any separate debug info has been extracted out. */ - else if (bfd_get_section_by_name (objfile->obfd, ".gnu_debuglink") - != NULL) - debug_print_missing (objfile_name (objfile), build_id_filename.get ()); else - has_dwarf2 = false; + { + has_dwarf2 = false; + const struct bfd_build_id *build_id = build_id_bfd_shdr_get (objfile->obfd); + + if (build_id != nullptr) + { + gdb::unique_xmalloc_ptr symfile_path; + scoped_fd fd (debuginfod_debuginfo_query (build_id->data, + build_id->size, + objfile->original_name, + &symfile_path)); + + if (fd.get () >= 0) + { + /* File successfully retrieved from server. */ + gdb_bfd_ref_ptr debug_bfd (symfile_bfd_open (symfile_path.get ())); + + if (debug_bfd == nullptr) + warning (_("File \"%s\" from debuginfod cannot be opened as bfd"), + objfile->original_name); + else if (build_id_verify (debug_bfd.get (), build_id->size, build_id->data)) + { + symbol_file_add_separate (debug_bfd.get (), symfile_path.get (), + symfile_flags, objfile); + has_dwarf2 = true; + } + } + } + /* Check if any separate debug info has been extracted out. */ + else if (bfd_get_section_by_name (objfile->obfd, ".gnu_debuglink") + != NULL) + debug_print_missing (objfile_name (objfile), build_id_filename.get ()); + else + has_dwarf2 = false; + } } /* Read the CTF section only if there is no DWARF info. */ diff --git a/gdb/source.c b/gdb/source.c --- a/gdb/source.c +++ b/gdb/source.c @@ -47,6 +47,8 @@ #include "gdbsupport/pathstuff.h" #include "source-cache.h" #include "cli/cli-style.h" +#include "build-id.h" +#include "debuginfod-support.h" #define OPEN_MODE (O_RDONLY | O_BINARY) #define FDOPEN_MODE FOPEN_RB @@ -1122,6 +1124,34 @@ 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_ABSOLUTE_PATH (s->filename)) + srcpath = s->filename; + else if (SYMTAB_DIRNAME (s) != nullptr) + { + srcpath = SYMTAB_DIRNAME (s); + srcpath += SLASH_STRING; + srcpath += s->filename; + } + + const struct bfd_build_id *build_id = build_id_bfd_shdr_get (ofp->obfd); + + /* Query debuginfod for the source file. */ + if (build_id != nullptr) + fd = debuginfod_source_query (build_id->data, + build_id->size, + srcpath.c_str (), + &fullname); + } + } + 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 --- /dev/null +++ b/gdb/testsuite/gdb.debuginfod/fetch_src_and_symbols.exp @@ -0,0 +1,215 @@ +# 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 . + +# 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" \ + [eval exec $GDB $INTERNAL_GDBFLAGS --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\.\.\.*" diff --git a/gdb/testsuite/gdb.debuginfod/main.c b/gdb/testsuite/gdb.debuginfod/main.c new file mode 100644 --- /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 . */ + +/* Dummy main function. */ + +int +main() +{ + asm ("main_label: .globl main_label"); + return 0; +} diff --git a/gdb/top.c b/gdb/top.c --- a/gdb/top.c +++ b/gdb/top.c @@ -1513,6 +1513,17 @@ This GDB was configured as follows:\n\ --without-python\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\