|  | /* debuginfod utilities for GDB. | 
|  | Copyright (C) 2020-2023 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 "defs.h" | 
|  | #include "diagnostics.h" | 
|  | #include <errno.h> | 
|  | #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 "cli-out.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<char> *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<char> *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<char> *destname) | 
|  | { | 
|  | return scoped_fd (-ENOSYS); | 
|  | } | 
|  |  | 
|  | #define NO_IMPL _("Support for debuginfod is not compiled into GDB.") | 
|  |  | 
|  | #else | 
|  | #include <elfutils/debuginfod.h> | 
|  |  | 
|  | struct user_data | 
|  | { | 
|  | user_data (const char *desc, const char *fname) | 
|  | : desc (desc), fname (fname) | 
|  | { } | 
|  |  | 
|  | const char * const desc; | 
|  | const char * const fname; | 
|  | ui_out::progress_update progress; | 
|  | }; | 
|  |  | 
|  | /* Convert SIZE into a unit suitable for use with progress updates. | 
|  | SIZE should in given in bytes and will be converted into KB, MB, GB | 
|  | or remain unchanged. UNIT will be set to "B", "KB", "MB" or "GB" | 
|  | accordingly.  */ | 
|  |  | 
|  | static const char * | 
|  | get_size_and_unit (double &size) | 
|  | { | 
|  | if (size < 1024) | 
|  | /* If size is less than 1 KB then set unit to B.  */ | 
|  | return "B"; | 
|  |  | 
|  | size /= 1024; | 
|  | if (size < 1024) | 
|  | /* If size is less than 1 MB then set unit to KB.  */ | 
|  | return "K"; | 
|  |  | 
|  | size /= 1024; | 
|  | if (size < 1024) | 
|  | /* If size is less than 1 GB then set unit to MB.  */ | 
|  | return "M"; | 
|  |  | 
|  | size /= 1024; | 
|  | return "G"; | 
|  | } | 
|  |  | 
|  | static int | 
|  | progressfn (debuginfod_client *c, long cur, long total) | 
|  | { | 
|  | user_data *data = static_cast<user_data *> (debuginfod_get_user_data (c)); | 
|  | gdb_assert (data != nullptr); | 
|  |  | 
|  | string_file styled_fname (current_uiout->can_emit_style_escape ()); | 
|  | fprintf_styled (&styled_fname, file_name_style.style (), "%s", | 
|  | data->fname); | 
|  |  | 
|  | if (check_quit_flag ()) | 
|  | { | 
|  | gdb_printf ("Cancelling download of %s %s...\n", | 
|  | data->desc, styled_fname.c_str ()); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (debuginfod_verbose == 0) | 
|  | return 0; | 
|  |  | 
|  | /* Print progress update.  Include the transfer size if available.  */ | 
|  | if (total > 0) | 
|  | { | 
|  | /* Transfer size is known.  */ | 
|  | double howmuch = (double) cur / (double) total; | 
|  |  | 
|  | if (howmuch >= 0.0 && howmuch <= 1.0) | 
|  | { | 
|  | double d_total = (double) total; | 
|  | const char *unit =  get_size_and_unit (d_total); | 
|  | std::string msg = string_printf ("Downloading %0.2f %s %s %s", | 
|  | d_total, unit, data->desc, | 
|  | styled_fname.c_str ()); | 
|  | data->progress.update_progress (msg, unit, howmuch, d_total); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::string msg = string_printf ("Downloading %s %s", | 
|  | data->desc, styled_fname.c_str ()); | 
|  | data->progress.update_progress (msg); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Cleanup ARG, which is a debuginfod_client pointer.  */ | 
|  |  | 
|  | static void | 
|  | cleanup_debuginfod_client (void *arg) | 
|  | { | 
|  | debuginfod_client *client = static_cast<debuginfod_client *> (arg); | 
|  | debuginfod_end (client); | 
|  | } | 
|  |  | 
|  | /* Return a pointer to the single global debuginfod_client, initialising it | 
|  | first if needed.  */ | 
|  |  | 
|  | static debuginfod_client * | 
|  | get_debuginfod_client () | 
|  | { | 
|  | static debuginfod_client *global_client = nullptr; | 
|  |  | 
|  | if (global_client == nullptr) | 
|  | { | 
|  | global_client = debuginfod_begin (); | 
|  |  | 
|  | if (global_client != nullptr) | 
|  | { | 
|  | /* It is important that we cleanup the debuginfod_client object | 
|  | before calling exit.  Some of the libraries used by debuginfod | 
|  | make use of at_exit handlers to perform cleanup. | 
|  |  | 
|  | If we wrapped the debuginfod_client in a unique_ptr and relied | 
|  | on its destructor to cleanup then this would be run as part of | 
|  | the global C++ object destructors, which is after the at_exit | 
|  | handlers, which is too late. | 
|  |  | 
|  | So instead, we make use of GDB's final cleanup mechanism.  */ | 
|  | make_final_cleanup (cleanup_debuginfod_client, global_client); | 
|  | debuginfod_set_progressfn (global_client, progressfn); | 
|  | } | 
|  | } | 
|  |  | 
|  | return global_client; | 
|  | } | 
|  |  | 
|  | /* 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; | 
|  | } | 
|  |  | 
|  | /* Print the result of the most recent attempted download.  */ | 
|  |  | 
|  | static void | 
|  | print_outcome (int fd, const char *desc, const char *fname) | 
|  | { | 
|  | if (fd < 0 && fd != -ENOENT) | 
|  | gdb_printf (_("Download failed: %s.  Continuing without %s %ps.\n"), | 
|  | safe_strerror (-fd), | 
|  | desc, | 
|  | styled_string (file_name_style.style (), fname)); | 
|  | } | 
|  |  | 
|  | /* 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<char> *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; | 
|  | scoped_fd fd; | 
|  | gdb::optional<target_terminal::scoped_restore_terminal_state> term_state; | 
|  |  | 
|  | { | 
|  | user_data data ("source file", srcpath); | 
|  |  | 
|  | debuginfod_set_user_data (c, &data); | 
|  | if (target_supports_terminal_ours ()) | 
|  | { | 
|  | term_state.emplace (); | 
|  | target_terminal::ours (); | 
|  | } | 
|  |  | 
|  | fd = scoped_fd (debuginfod_find_source (c, | 
|  | build_id, | 
|  | build_id_len, | 
|  | srcpath, | 
|  | &dname)); | 
|  | debuginfod_set_user_data (c, nullptr); | 
|  | } | 
|  |  | 
|  | print_outcome (fd.get (), "source file", 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<char> *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; | 
|  | scoped_fd fd; | 
|  | gdb::optional<target_terminal::scoped_restore_terminal_state> term_state; | 
|  |  | 
|  | { | 
|  | user_data data ("separate debug info for", filename); | 
|  |  | 
|  | debuginfod_set_user_data (c, &data); | 
|  | if (target_supports_terminal_ours ()) | 
|  | { | 
|  | term_state.emplace (); | 
|  | target_terminal::ours (); | 
|  | } | 
|  |  | 
|  | fd = scoped_fd (debuginfod_find_debuginfo (c, build_id, build_id_len, | 
|  | &dname)); | 
|  | debuginfod_set_user_data (c, nullptr); | 
|  | } | 
|  |  | 
|  | print_outcome (fd.get (), "separate debug info for", 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<char> *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; | 
|  | scoped_fd fd; | 
|  | gdb::optional<target_terminal::scoped_restore_terminal_state> term_state; | 
|  |  | 
|  | { | 
|  | user_data data ("executable for", filename); | 
|  |  | 
|  | debuginfod_set_user_data (c, &data); | 
|  | if (target_supports_terminal_ours ()) | 
|  | { | 
|  | term_state.emplace (); | 
|  | target_terminal::ours (); | 
|  | } | 
|  |  | 
|  | fd = scoped_fd (debuginfod_find_executable (c, build_id, build_id_len, | 
|  | &dname)); | 
|  | debuginfod_set_user_data (c, nullptr); | 
|  | } | 
|  |  | 
|  | print_outcome (fd.get (), "executable for", 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); | 
|  | } |