/* debuginfod utilities for GDB. Copyright (C) 2020-2022 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 "diagnostics.h" #include #include "gdbsupport/scoped_fd.h" #include "debuginfod-support.h" #include "gdbsupport/gdb_optional.h" #include "cli/cli-cmds.h" #include "cli/cli-style.h" #include "target.h" /* Set/show debuginfod commands. */ static cmd_list_element *set_debuginfod_prefix_list; static cmd_list_element *show_debuginfod_prefix_list; static const char debuginfod_on[] = "on"; static const char debuginfod_off[] = "off"; static const char debuginfod_ask[] = "ask"; static const char *debuginfod_enabled_enum[] = { debuginfod_on, debuginfod_off, debuginfod_ask, nullptr }; static const char *debuginfod_enabled = #if defined(HAVE_LIBDEBUGINFOD) debuginfod_ask; #else debuginfod_off; #endif static unsigned int debuginfod_verbose = 1; #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); } scoped_fd debuginfod_exec_query (const unsigned char *build_id, int build_id_len, const char *filename, gdb::unique_xmalloc_ptr *destname) { return scoped_fd (-ENOSYS); } #define NO_IMPL _("Support for debuginfod is not compiled into GDB.") #else #include struct user_data { user_data (const char *desc, const char *fname) : desc (desc), fname (fname), has_printed (false) { } const char * const desc; const char * const fname; bool has_printed; }; /* Deleter for a debuginfod_client. */ struct debuginfod_client_deleter { void operator() (debuginfod_client *c) { debuginfod_end (c); } }; using debuginfod_client_up = std::unique_ptr; static int progressfn (debuginfod_client *c, long cur, long total) { user_data *data = static_cast (debuginfod_get_user_data (c)); gdb_assert (data != nullptr); if (check_quit_flag ()) { gdb_printf ("Cancelling download of %s %ps...\n", data->desc, styled_string (file_name_style.style (), data->fname)); return 1; } if (!data->has_printed) { /* Include the transfer size, if available. */ if (total > 0) { float size = 1.0f * total / 1024; const char *unit = "KB"; /* If size is greater than 0.01 MB, set unit to MB. */ if (size > 10.24) { size /= 1024; unit = "MB"; } gdb_printf ("Downloading %.2f %s %s %ps...\n", size, unit, data->desc, styled_string (file_name_style.style (), data->fname)); } else gdb_printf ("Downloading %s %ps...\n", data->desc, styled_string (file_name_style.style (), data->fname)); data->has_printed = true; } return 0; } static debuginfod_client * get_debuginfod_client () { static debuginfod_client_up global_client; if (global_client == nullptr) { global_client.reset (debuginfod_begin ()); if (global_client != nullptr) debuginfod_set_progressfn (global_client.get (), progressfn); } return global_client.get (); } /* Check if debuginfod is enabled. If configured to do so, ask the user whether to enable debuginfod. */ static bool debuginfod_is_enabled () { const char *urls = skip_spaces (getenv (DEBUGINFOD_URLS_ENV_VAR)); if (debuginfod_enabled == debuginfod_off || urls == nullptr || *urls == '\0') return false; if (debuginfod_enabled == debuginfod_ask) { gdb_printf (_("\nThis GDB supports auto-downloading debuginfo " \ "from the following URLs:\n")); gdb::string_view url_view (urls); while (true) { size_t off = url_view.find_first_not_of (' '); if (off == gdb::string_view::npos) break; url_view = url_view.substr (off); /* g++ 11.2.1 on s390x, g++ 11.3.1 on ppc64le and g++ 11 on hppa seem convinced url_view might be of SIZE_MAX length. And so complains because the length of an array can only be PTRDIFF_MAX. */ DIAGNOSTIC_PUSH DIAGNOSTIC_IGNORE_STRINGOP_OVERREAD off = url_view.find_first_of (' '); DIAGNOSTIC_POP gdb_printf (_(" <%ps>\n"), styled_string (file_name_style.style (), gdb::to_string (url_view.substr (0, off)).c_str ())); if (off == gdb::string_view::npos) break; url_view = url_view.substr (off); } int resp = nquery (_("Enable debuginfod for this session? ")); if (!resp) { gdb_printf (_("Debuginfod has been disabled.\nTo make this " \ "setting permanent, add \'set debuginfod " \ "enabled off\' to .gdbinit.\n")); debuginfod_enabled = debuginfod_off; return false; } gdb_printf (_("Debuginfod has been enabled.\nTo make this " \ "setting permanent, add \'set debuginfod enabled " \ "on\' to .gdbinit.\n")); debuginfod_enabled = debuginfod_on; } return true; } /* 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 (!debuginfod_is_enabled ()) return scoped_fd (-ENOSYS); debuginfod_client *c = get_debuginfod_client (); if (c == nullptr) return scoped_fd (-ENOMEM); char *dname = nullptr; user_data data ("source file", srcpath); debuginfod_set_user_data (c, &data); gdb::optional term_state; if (target_supports_terminal_ours ()) { term_state.emplace (); target_terminal::ours (); } scoped_fd fd (debuginfod_find_source (c, build_id, build_id_len, srcpath, &dname)); debuginfod_set_user_data (c, nullptr); if (fd.get () < 0 && fd.get () != -ENOENT) gdb_printf (_("Download failed: %s. Continuing without source file %ps.\n"), safe_strerror (-fd.get ()), styled_string (file_name_style.style (), srcpath)); if (fd.get () >= 0) destname->reset (dname); 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 (!debuginfod_is_enabled ()) return scoped_fd (-ENOSYS); debuginfod_client *c = get_debuginfod_client (); if (c == nullptr) return scoped_fd (-ENOMEM); char *dname = nullptr; user_data data ("separate debug info for", filename); debuginfod_set_user_data (c, &data); gdb::optional term_state; if (target_supports_terminal_ours ()) { term_state.emplace (); target_terminal::ours (); } scoped_fd fd (debuginfod_find_debuginfo (c, build_id, build_id_len, &dname)); debuginfod_set_user_data (c, nullptr); if (fd.get () < 0 && fd.get () != -ENOENT) gdb_printf (_("Download failed: %s. Continuing without debug info for %ps.\n"), safe_strerror (-fd.get ()), styled_string (file_name_style.style (), filename)); if (fd.get () >= 0) destname->reset (dname); return fd; } /* See debuginfod-support.h */ scoped_fd debuginfod_exec_query (const unsigned char *build_id, int build_id_len, const char *filename, gdb::unique_xmalloc_ptr *destname) { if (!debuginfod_is_enabled ()) return scoped_fd (-ENOSYS); debuginfod_client *c = get_debuginfod_client (); if (c == nullptr) return scoped_fd (-ENOMEM); char *dname = nullptr; user_data data ("executable for", filename); debuginfod_set_user_data (c, &data); gdb::optional term_state; if (target_supports_terminal_ours ()) { term_state.emplace (); target_terminal::ours (); } scoped_fd fd (debuginfod_find_executable (c, build_id, build_id_len, &dname)); debuginfod_set_user_data (c, nullptr); if (fd.get () < 0 && fd.get () != -ENOENT) gdb_printf (_("Download failed: %s. " \ "Continuing without executable for %ps.\n"), safe_strerror (-fd.get ()), styled_string (file_name_style.style (), filename)); if (fd.get () >= 0) destname->reset (dname); return fd; } #endif /* Set callback for "set debuginfod enabled". */ static void set_debuginfod_enabled (const char *value) { #if defined(HAVE_LIBDEBUGINFOD) debuginfod_enabled = value; #else /* Disabling debuginfod when gdb is not built with it is a no-op. */ if (value != debuginfod_off) error (NO_IMPL); #endif } /* Get callback for "set debuginfod enabled". */ static const char * get_debuginfod_enabled () { return debuginfod_enabled; } /* Show callback for "set debuginfod enabled". */ static void show_debuginfod_enabled (ui_file *file, int from_tty, cmd_list_element *cmd, const char *value) { gdb_printf (file, _("Debuginfod functionality is currently set to " "\"%s\".\n"), debuginfod_enabled); } /* Set callback for "set debuginfod urls". */ static void set_debuginfod_urls (const std::string &urls) { #if defined(HAVE_LIBDEBUGINFOD) if (setenv (DEBUGINFOD_URLS_ENV_VAR, urls.c_str (), 1) != 0) warning (_("Unable to set debuginfod URLs: %s"), safe_strerror (errno)); #else error (NO_IMPL); #endif } /* Get callback for "set debuginfod urls". */ static const std::string& get_debuginfod_urls () { static std::string urls; #if defined(HAVE_LIBDEBUGINFOD) const char *envvar = getenv (DEBUGINFOD_URLS_ENV_VAR); if (envvar != nullptr) urls = envvar; else urls.clear (); #endif return urls; } /* Show callback for "set debuginfod urls". */ static void show_debuginfod_urls (ui_file *file, int from_tty, cmd_list_element *cmd, const char *value) { if (value[0] == '\0') gdb_printf (file, _("Debuginfod URLs have not been set.\n")); else gdb_printf (file, _("Debuginfod URLs are currently set to:\n%s\n"), value); } /* Show callback for "set debuginfod verbose". */ static void show_debuginfod_verbose_command (ui_file *file, int from_tty, cmd_list_element *cmd, const char *value) { gdb_printf (file, _("Debuginfod verbose output is set to %s.\n"), value); } /* Register debuginfod commands. */ void _initialize_debuginfod (); void _initialize_debuginfod () { /* set/show debuginfod */ add_setshow_prefix_cmd ("debuginfod", class_run, _("Set debuginfod options."), _("Show debuginfod options."), &set_debuginfod_prefix_list, &show_debuginfod_prefix_list, &setlist, &showlist); add_setshow_enum_cmd ("enabled", class_run, debuginfod_enabled_enum, _("Set whether to use debuginfod."), _("Show whether to use debuginfod."), _("\ When on, enable the use of debuginfod to download missing debug info and\n\ source files."), set_debuginfod_enabled, get_debuginfod_enabled, show_debuginfod_enabled, &set_debuginfod_prefix_list, &show_debuginfod_prefix_list); /* set/show debuginfod urls */ add_setshow_string_noescape_cmd ("urls", class_run, _("\ Set the list of debuginfod server URLs."), _("\ Show the list of debuginfod server URLs."), _("\ Manage the space-separated list of debuginfod server URLs that GDB will query \ when missing debuginfo, executables or source files.\nThe default value is \ copied from the DEBUGINFOD_URLS environment variable."), set_debuginfod_urls, get_debuginfod_urls, show_debuginfod_urls, &set_debuginfod_prefix_list, &show_debuginfod_prefix_list); /* set/show debuginfod verbose */ add_setshow_zuinteger_cmd ("verbose", class_support, &debuginfod_verbose, _("\ Set verbosity of debuginfod output."), _("\ Show debuginfod debugging."), _("\ When set to a non-zero value, display verbose output for each debuginfod \ query.\nTo disable, set to zero. Verbose output is displayed by default."), nullptr, show_debuginfod_verbose_command, &set_debuginfod_prefix_list, &show_debuginfod_prefix_list); }