File gdb-add-rpm-suggestion-script.patch of Package gdb
From FEDORA_PATCHES Mon Sep 17 00:00:00 2001
From: Andrew Burgess <aburgess@redhat.com>
Date: Thu, 7 Mar 2024 15:14:23 +0000
Subject: gdb-add-rpm-suggestion-script.patch
;; Not a backport. Add a new script which hooks into GDB and suggests
;; RPMs to install when GDB finds an objfile with no debug info.
gdb: add script which will suggest debuginfo RPMs to install
This script hooks into GDB's missing debug info Python API and
suggests debuginfo RPMs to install.
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -92,6 +92,7 @@ PYTHON_FILE_LIST = \
gdb/command/missing_files.py \
gdb/command/pretty_printers.py \
gdb/command/prompt.py \
+ gdb/command/rpm-suggestions.py \
gdb/command/type_printers.py \
gdb/command/unwinders.py \
gdb/command/xmethods.py \
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -186,6 +186,7 @@ software in general. We will miss him.
* Trace File Format:: @value{GDBN} trace file format
* Index Section Format:: .gdb_index section format
* Debuginfod:: Download debugging resources with @code{debuginfod}
+* RPM Suggestions:: RPM Suggestions from GDB
* Man Pages:: Manual pages
* Copying:: GNU General Public License says
how you can copy and share @value{GDBN}
@@ -50588,6 +50589,111 @@ Show the current verbosity setting.
@end table
+@node RPM Suggestions
+@appendix Receiving RPM installation suggestions
+@cindex rpm suggestions
+
+When @value{GDBN} loads an executable, or shared library, and cannot
+find the corresponding debug information, @value{GDBN} will check to
+see if an RPM is available which could provide the missing debug
+information. If a suitable RPM is found then @value{GDBN} will print
+a hint before the next prompt is displayed:
+
+@smallexample
+(@value{GDBP}) file /bin/ls
+Reading symbols from /bin/ls...
+Reading symbols from .gnu_debugdata for /usr/bin/ls...
+(No debugging symbols found in .gnu_debugdata for /usr/bin/ls)
+Missing rpms, try: dnf --enablerepo='*debug*' install coreutils-debuginfo-9.3-7.fc39.x86_64
+(@value{GDBP})
+@end smallexample
+
+In this case, installing @file{coreutils-debuginfo-9.3-7.fc39.x86_64}
+will provide the missing debug information for @file{/bin/ls}. It is
+up to you to install the suggested package, if possible, and after
+that reload the executable in @value{GDBN} so that the newly installed
+debug information can be found.
+
+The RPM suggestion feature can be disabled:
+
+@table @code
+@kindex set rpm-suggestion enabled
+@kindex show rpm-suggestion enabled
+@cindex rpm suggestions, disabling
+@item set rpm-suggestion enabled @r{[}on@r{|}off@r{]}
+@itemx show rpm-suggestion enabled
+When @samp{on} @value{GDBN} will make RPM suggestions where possible.
+When @samp{off} all RPM suggestion will be disabled.
+@end table
+
+When opening a core file (@pxref{core-file command}), it may be the
+case that not only is the debug information missing, but the
+corresponding executable itself is missing. For example, if a core
+file is copied from one machine to another in order to debug.
+
+In this case @value{GDBN} is able to suggest a command which will
+install the missing executable based on the build-id of the
+executable. For example:
+
+@smallexample
+(@value{GDBP}) core-file /tmp/core.5489
+warning: Can't open file /usr/bin/sl during file-backed mapping note processing
+[New LWP 5489]
+Core was generated by `sl'.
+Program terminated with signal SIGQUIT, Quit.
+#0 0x00007f1b41ced1a7 in ?? ()
+Missing file(s), try: dnf --enablerepo='*debug*' install /usr/lib/.build-id/33/2f1a8e56693960e3beb2d70cd79ddfec451cc3 /usr/lib/debug/.build-id/33/2f1a8e56693960e3beb2d70cd79ddfec451cc3.debug
+(@value{GDBP})
+@end smallexample
+
+The core file was generated from the @file{/usr/bin/sl} binary, which
+is not present on the machine opening the core file. @value{GDBN} has
+suggested a command, based on the build-id of the binary, which was
+extracted from the core file, that would install both the missing
+binary, and the corresponding debug information.
+
+Unfortunately, @value{GDBN} doesn't know if the suggested command will
+actually find a matching RPM or not. Querying the RPM database to see
+which packages, if any, will provide a file with the given build-id,
+is rather slow. As @file{/usr/bin/sl} is available in an RPM, then
+the above command will succeed.
+
+It is possible to have @value{GDBN} check to see if there is a package
+available before making the suggestion, but this is significantly
+slower. To enable this mode use the following command:
+
+@table @code
+@kindex set rpm-suggestion build-id-mode
+@kindex show rpm-suggestion build-id-mode
+@cindex rpm suggestions, build-id-mode
+@item set rpm-suggestion build-id-mode @r{[}fast@r{|}slow@r{]}
+@itemx show rpm-suggestion build-id-mode
+When set to @samp{fast}, which is the default, @value{GDBN} will offer
+suggestions based on the build-id of any missing executables or shared
+libraries while opening a core file. This is fast, but @value{GDBN}
+has not checked if there is a package available that can supply the
+required file, so running the suggested command might not install any
+packages.
+
+When set to @samp{slow}, each time @value{GDBN} encounters an
+executable, or shared library, that is missing, @value{GDBN} will
+check to see if there is an RPM available that will supply the missing
+binary. If a suitable RPM is found then @value{GDBN} will offer a
+command which will install the missing RPM. If no suitable RPM is
+found then @value{GDBN} will make no suggestions.
+@end table
+
+It is possible to review all of the previous RPM suggestions that
+@value{GDBN} has made using the following command:
+
+@table @code
+@kindex info rpm-suggestions
+@cindex rpm suggestions, listing
+@item info rpm-suggestions
+List all of the RPM suggestions @value{GDBN} has made since the
+executable was last changed.
+@end table
+
@node Man Pages
@appendix Manual pages
@cindex Man pages
diff --git a/gdb/python/lib/gdb/command/rpm-suggestions.py b/gdb/python/lib/gdb/command/rpm-suggestions.py
new file mode 100644
--- /dev/null
+++ b/gdb/python/lib/gdb/command/rpm-suggestions.py
@@ -0,0 +1,525 @@
+# Copyright 2023 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/>.
+
+import gdb
+import gdb.missing_debug
+import gdb.missing_objfile
+
+# These modules are all system modules, and should be available on any
+# correctly setup Python install.
+import sys
+import os
+import subprocess
+import re
+from threading import Thread
+import time
+
+try:
+ import rpm
+except ModuleNotFoundError:
+ # The "RPM suggestions" mechanism will not work without the (python)
+ # rpm module. Inform the user of this, but wait to do so until
+ # just prior to printing the GDB prompt. If we do it right away,
+ # the message typically appears before the version and copyright
+ # info, which is easily missed by many users. Additionally, it
+ # seems that several other packages which parse GDB version info
+ # are confused by an early error message regarding a missing
+ # python3-rpm package, so waiting to print the error allows those
+ # applications to work as they used to.
+ def before_prompt():
+ print(
+ ("\nUnable to load the Python 'rpm' module. Lack of this module disables\n"
+ "the RPM suggestions mechanism which recommends shell commands for\n"
+ "installing missing debuginfo packages. To enable this functionality,\n"
+ "please install the python3-rpm package."),
+ file=sys.stderr
+ )
+ gdb.events.before_prompt.disconnect(before_prompt)
+
+ gdb.events.before_prompt.connect(before_prompt)
+
+ # Implement 'info rpm-suggestions' when the 'rpm' module is not
+ # available. Just throws an error.
+ def info_rpm_suggestions():
+ raise gdb.GdbError("rpm-suggestions are disabled as the Python 'rpm' module is not installed")
+else:
+ # Track all the RPMs suggested during a single debug session so we
+ # don't suggest the same RPM twice. This is only cleared when the
+ # main executable is changed.
+ __missing_rpms = {}
+
+ # Track any missing RPMs that have been discovered since the last time
+ # the prompt was displayed. RPMs in here are also present in the
+ # __MISSING_RPMS dictionary, but this dictionary is cleared each time
+ # the prompt is shown.
+ __suggest_rpms = {}
+
+ # Track all the build-ids suggested during a single debug session so we
+ # don't suggest installing using the same build-id twice. This is only
+ # cleared when the main executable is changed.
+ __missing_build_ids = {}
+
+ # Track any build-ids that have been discovered missing since the last
+ # time the prompt was displayed. Build-ids in here are also present in
+ # the __MISSING_BUILD_IDS dictionary, but this dictionary is cleared
+ # each time the prompt is shown.
+ __suggest_build_ids = {}
+
+ # The build-id to RPM lookup is very slow. This cache maps
+ # build-ids to the set of RPM we can suggest installing. The key
+ # is the build-id string, and the value is a list of RPM names, or
+ # None if there was an error with the build-id to RPM lookup.
+ #
+ # This cache is never cleared, even when the executable is
+ # changed. The build-ids should be unique, so a build-id lookup
+ # should be good for the lifetime of the session.
+ __build_id_lookup_cache = {}
+
+ # When searching for an RPM given a build-id, if the search takes
+ # too long, then a message is printed to the user. We only print
+ # the message once between each GDB prompt. This flag is set True
+ # when the message is printed, and reset to False when a prompt is
+ # displayed.
+ __searching_message_printed = False
+
+ # Add a suggestion to install RPM_NAME (the full name of an RPM)
+ # to the list of suggestions to make the next time a prompt is
+ # displayed.
+ def add_rpm_suggestion(rpm_name):
+ global __missing_rpms
+ global __suggest_rpms
+
+ if not rpm_name in __missing_rpms:
+ __suggest_rpms[rpm_name] = True
+ __missing_rpms[rpm_name] = True
+
+ # Return True if RPM_NAME is installed, where RPM_NAME is the full
+ # name of an RPM.
+ def is_rpm_installed(ts, rpm_name):
+ res = ts.dbMatch(rpm.RPMDBI_LABEL, rpm_name)
+ return len(res) > 0
+
+ # Add a suggestion to install RPMs based on BUILD_ID, a string
+ # containing a build-id, to the list of suggestions to make the next
+ # time a prompt is displayed.
+ def add_build_id_suggestion(build_id):
+ global __missing_build_ids
+ global __suggest_build_ids
+
+ if not build_id in __missing_build_ids:
+ __suggest_build_ids[build_id] = True
+ __missing_build_ids[build_id] = True
+
+ # Return true if '/usr/lib' is in the debug-file-directory list.
+ # System packages install their debug information into /usr/lib,
+ # so if GDB isn't looking in that directory, then there's no
+ # reason to try and figure out a suitable RPM to install.
+ def using_suitable_debug_file_directory():
+ debug_file_directories = gdb.parameter("debug-file-directory")
+ for d in debug_file_directories.split(os.pathsep):
+ if d[:8] == "/usr/lib":
+ return True
+ return False
+
+ # Return True if rpm-suggestion is disabled for any reason.
+ def rpm_suggestion_is_disabled():
+ # If /usr/lib/ is not being used to find debug information
+ # then there's no point offering any RPMs as GDB would not
+ # find the newly installed content.
+ if not using_suitable_debug_file_directory():
+ return True
+
+ # Is 'rpm-suggestion enabled' set to 'off'?
+ if not param_rpm_suggestion_enabled.value:
+ return True
+
+ return False
+
+
+ # Lookup RPMs that might provide the debug information for FILENAME,
+ # which is a string containing the path to an object file GDB could
+ # not find any debug information for.
+ #
+ # If a possible RPM is found then this is added to the globals
+ # __MISSING_RPMS and __SUGGEST_RPMS, which are used elsewhere in this
+ # script.
+ def find_debug_suggestions(filename):
+ if rpm_suggestion_is_disabled():
+ return
+
+ ts = rpm.TransactionSet()
+ mi = ts.dbMatch(rpm.RPMDBI_BASENAMES, filename)
+ for h in mi:
+ # Build the debuginfo package name.
+ obj = h.format("%{name}-debuginfo-%{version}-%{release}.%{arch}")
+ rpm_name = str(obj)
+
+ # Check to see if the package is installed.
+ if is_rpm_installed(ts, rpm_name):
+ continue
+
+ add_rpm_suggestion(rpm_name)
+
+
+ # Return a string which is the filename of the filename
+ # corresponding to BUILD_ID in one of the two locations under
+ # /usr/lib.
+ #
+ # When DEBUG_P is True, return a filename within:
+ # /usr/lib/debug/.build-id/ and when DEBUG_P is False, return a
+ # filename within: /usr/lib/.build-id/.
+ #
+ # The SUFFIX string is appended to the generated filename.
+ def build_id_to_usr_lib_filename(build_id, debug_p, suffix = ""):
+ key = build_id[:2]
+ rst = build_id[2:]
+
+ filename = '/usr/lib'
+ if debug_p:
+ filename += '/debug'
+ filename += '/.build-id/' + key + '/' + rst + suffix
+
+ return filename
+
+ # A regexp object used to match against the output of `dnf`. This
+ # is initialised the first time it is needed.
+ find_objfile_suggestions_re = None
+
+ # Given BUILD_ID, a string containing a build-id, run a `dnf`
+ # command to figure out if any RPMs can provide a file with that
+ # build-id.
+ #
+ # If any suitable RPMs are found then `add_rpm_suggestion` is called
+ # to register the suggestion.
+ #
+ # This function is pretty slow, which is a shame, as the results
+ # returned are much nicer than just telling the user to try the
+ # lookup command for themselves.
+ def find_objfile_suggestions_in_thread(build_id):
+ global find_objfile_suggestions_re
+
+ if find_objfile_suggestions_re is None:
+ find_objfile_suggestions_re = re.compile("^(.*)-debuginfo-(.*) : Debug information for package (.*)$")
+
+ result = subprocess.run(['dnf', "--enablerepo=*debug*", '--nogpgcheck', '-C', 'provides',
+ build_id_to_usr_lib_filename(build_id, True)],
+ capture_output=True, timeout=30)
+
+ lines = result.stdout.decode('utf-8').splitlines()
+ ts = rpm.TransactionSet()
+
+ for l in lines:
+ m = find_objfile_suggestions_re.match(l)
+ if not m:
+ continue
+ if m.group(1) != m.group(3):
+ return
+
+ main_rpm = m.group(1) + '-' + m.group(2)
+ dbg_rpm = m.group(1) + '-debuginfo-' + m.group(2)
+
+ if not is_rpm_installed(ts, main_rpm):
+ add_rpm_suggestion(main_rpm)
+
+ if not is_rpm_installed(ts, dbg_rpm):
+ add_rpm_suggestion(dbg_rpm)
+
+ return
+
+ # Call `find_objfile_suggestions_in_thread` is a separate thread,
+ # then wait for the thread to complete. Don't touch any shared
+ # state while waiting for the thread to complete. There's no
+ # locking on any of our caches, and the worker thread will add RPM
+ # suggestions as it wants.
+ #
+ # If thre thread takes too long to complete then print a message
+ # to the user telling them what's going on.
+ def find_objfile_suggestions_slow_core(build_id):
+ global __searching_message_printed
+
+ thread = Thread(target = find_objfile_suggestions_in_thread, args = (build_id, ))
+ thread.start()
+ start = time.time_ns()
+
+ while thread.is_alive ():
+ time.sleep(0.05)
+ now = time.time_ns ()
+ if not __searching_message_printed and (now - start > 1000000000):
+ print("Searching for packages to install that could improve debugging...")
+ __searching_message_printed = True
+
+ thread.join()
+
+
+ # Given BUILD_ID, a string containing a build-id, lookup suitable
+ # RPMs that could be installed to provide a file with the required
+ # build-id.
+ #
+ # Any suitable RPMs are recorded by calling `add_rpm_suggestion`, and
+ # will be printed before the next prompt.
+ def find_objfile_suggestions_slow(build_id):
+ global __build_id_lookup_cache
+ global __suggest_rpms
+
+ # The code to lookup an RPM given only a build-id is pretty
+ # slow. Cache the results to try and reduce the UI delays.
+ if build_id in __build_id_lookup_cache:
+ rpms = __build_id_lookup_cache[build_id]
+ if rpms is not None:
+ for r in rpms:
+ add_rpm_suggestion(r)
+ return
+
+ # Make sure the cache entry exists before we do the lookup.
+ # If, for any reason, the lookup raises an exception, then
+ # having a cache entry will prevent us retrying this lookup in
+ # the future.
+ __build_id_lookup_cache[build_id] = None
+
+ # Now do the lookup. This is the slow part.
+ find_objfile_suggestions_slow_core(build_id)
+
+ # Fill in the cache, for a given build-id which RPMs were
+ # suggested.
+ rpms = []
+ for r in __suggest_rpms:
+ rpms.append(r)
+ __build_id_lookup_cache[build_id] = rpms
+
+
+ # Given BUILD_ID, a string containing a build-id, just record that we
+ # should advise the user to try installing RPMs based on this build-id.
+ def find_objfile_suggestions_fast(build_id):
+ add_build_id_suggestion(build_id)
+
+ # Given BUILD_ID, a string containing a build-id, which GDB has failed
+ # to find, possibly record some information so that we can, at the next
+ # prompt, give some RPM installation advice to the user.
+ #
+ # We have two different strategies for RPM lookup based on a build-id,
+ # one approach is that we actually lookup the RPMs and only suggest
+ # something if there is a suitable RPM. However, this is pretty slow,
+ # and users will probably find this annoying.
+ #
+ # So we also have a fast way. With this approach we just record the
+ # build-id that was missing and tell the user to try installing based on
+ # the build-id. The downside with this is that if there is no RPM for
+ # that build-id, then the user will try the command, but nothing will
+ # install.
+ def find_objfile_suggestions(build_id):
+ if rpm_suggestion_is_disabled():
+ return
+
+ if param_rpm_suggestion_build_id_mode.fast_mode():
+ find_objfile_suggestions_fast(build_id)
+ else:
+ find_objfile_suggestions_slow(build_id)
+
+ # A missing debug handler class. Just forwards the name of the
+ # objfile for which we are missing debug information to
+ # find_debug_suggestions.
+ class RPM_MissingDebugHandler(gdb.missing_debug.MissingDebugHandler):
+ def __init__(self):
+ super().__init__("rpm-suggestions")
+
+ def __call__(self, objfile):
+ find_debug_suggestions(objfile.filename)
+ return False
+
+ # A missing objfile handler class. Just forwards the build-id of
+ # the objfile that is missing to find_objfile_suggestions.
+ class RPM_MissingObjfileHandler(gdb.missing_objfile.MissingObjfileHandler):
+ def __init__(self):
+ super().__init__("rpm-suggestions")
+
+ def __call__(self, pspace, build_id, filename):
+ find_objfile_suggestions(build_id)
+ return False
+
+ # Take a non-empty list of RPM names and print a command line a
+ # user could run to install these RPMs.
+ def print_rpm_suggestions(rpm_name_list):
+ print("Missing rpms, try: dnf --enablerepo='*debug*' install " + ' '.join(rpm_name_list))
+
+ # Take a non-empty list of build-id strings and print a series of
+ # lines that a user could run to instll the RPMs that provide
+ # files with this build-id.
+ #
+ # The printed commands will also install the corresponding debug
+ # packages for the executable with the given build-id.
+ def print_build_id_suggestions(build_id_list):
+ for build_id in build_id_list:
+ print("Missing file(s), try: dnf --enablerepo='*debug*' install "
+ + build_id_to_usr_lib_filename(build_id, False)
+ + ' '
+ + build_id_to_usr_lib_filename(build_id, True, ".debug"))
+
+ # Called before GDB displays its prompt. If the global __SUGGEST_RPMS
+ # dictionary is not empty, then this hook prints the keys of this
+ # dictionary as strings which are the names of RPMs. This hook formats
+ # each RPM name into a suggested 'dnf install' command and suggests this
+ # to the user.
+ #
+ # Additionally, if the global __SUGGEST_BUILD_IDS dictionary is not
+ # empty, then this hook uses the keys of this dictionary as build-ids
+ # that were found to be missing, and formats these into some file based
+ # 'dnf install' suggestions to the user.
+ def before_prompt():
+ global __suggest_rpms
+ global __suggest_build_ids
+ global __searching_message_printed
+
+ # We allow the searching message to be printed just once
+ # between prompts.
+ __searching_message_printed = False
+
+ if len(__suggest_rpms) > 0:
+ print_rpm_suggestions(__suggest_rpms.keys())
+ __suggest_rpms = {}
+
+ if len(__suggest_build_ids) > 0:
+ print_build_id_suggestions(__suggest_build_ids.keys())
+ __suggest_build_ids = {}
+
+ # Called when the executable within a progrm space is changed. Clear
+ # the lists of RPM suggestions. We only clear the previous suggestion
+ # list when the executable really changes. If the user simply
+ # recompiles the executable, then we don't both clearing this list.
+ def executable_changed_handler(event):
+ global __missing_rpms
+ global __suggest_rpms
+ global __suggest_build_ids
+ global __missing_build_ids
+
+ if not event.reload:
+ __missing_rpms = {}
+ __suggest_rpms = {}
+ __missing_build_ids = {}
+ __suggest_build_ids = {}
+
+
+ # Attach to the required GDB events.
+ gdb.events.executable_changed.connect(executable_changed_handler)
+ gdb.events.before_prompt.connect(before_prompt)
+
+ # Register the missing debug and missing objfile handlers with GDB.
+ gdb.missing_debug.register_handler(None, RPM_MissingDebugHandler())
+ gdb.missing_objfile.register_handler(None, RPM_MissingObjfileHandler())
+
+ # Implement the core of 'info rpm-suggestions'. Reprint all rpm
+ # suggestions.
+ def info_rpm_suggestions():
+ global __missing_rpms
+ global __missing_build_ids
+
+ if len(__missing_rpms) == 0 and len(__missing_build_ids) == 0:
+ print("No RPM suggestions have been made so far.")
+ return
+
+ if len(__missing_rpms) > 0:
+ print_rpm_suggestions(__missing_rpms.keys())
+ if len(__missing_build_ids) > 0:
+ print_build_id_suggestions(__missing_build_ids.keys())
+
+####################################################################
+# The following code is outside the 'else' block of the attempt to #
+# load the 'rpm' module. Nothing after this point depends on the #
+# 'rpm' module. #
+####################################################################
+
+# The 'set rpm-suggestion' prefix command.
+class rpm_suggestion_set_prefix(gdb.Command):
+ """Prefix command for 'set' rpm-suggestion related commands."""
+
+ def __init__(self):
+ super().__init__("set rpm-suggestion", gdb.COMMAND_NONE,
+ gdb.COMPLETE_NONE, True)
+
+# The 'show rpm-suggestion' prefix command.
+class rpm_suggestion_show_prefix(gdb.Command):
+ """Prefix command for 'show' rpm-suggestion related commands."""
+
+ def __init__(self):
+ super().__init__("show rpm-suggestion", gdb.COMMAND_NONE,
+ gdb.COMPLETE_NONE, True)
+
+# The 'set/show rpm-suggestion enabled' command.
+class rpm_suggestion_enabled(gdb.Parameter):
+ """
+ When 'on' GDB will search for RPMS that might provide additional
+ debug information, or provide missing executables or shared
+ libraries (when opening a core file), and will make suggestions
+ about what should be installed.
+
+ When 'off' GDB will not make these suggestions.
+ """
+
+ set_doc = "Set whether to perform rpm-suggestion."
+ show_doc = "Show whether rpm-suggestion is enabled."
+
+ def __init__(self):
+ super().__init__("rpm-suggestion enabled", gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)
+ self.value = True
+
+# The 'set/show rpm-suggestion enabled' command.
+class rpm_suggestion_build_id_mode(gdb.Parameter):
+ """
+ When set to 'fast' (the default), GDB doesn't try to map a build-id to
+ an actual RPM, instead, GDB just suggests a command based on the
+ build-id which might install some RPMs, if there are any RPMs that
+ supply that build-id. However, it is equally possible that there are no
+ suitable RPMs, and nothing will install. This approach has almost zero
+ overhead.
+
+ When set to 'slow', GDB first does the build-id to RPM check itself, and
+ only if something is found are RPMs installation commands suggested.
+ The suggested command will include the name of the RPM to install. This
+ approach is considerably slower as querying the RPM database for the RPM
+ that supplies a specific file is slow.
+ """
+
+ set_doc = "Set how build-id based rpm suggestions should be performed."
+ show_doc = "Show how build-id based rpm suggestions shoud be performed."
+
+ def __init__(self):
+ super().__init__("rpm-suggestion build-id-mode",
+ gdb.COMMAND_NONE, gdb.PARAM_ENUM, ["fast", "slow"])
+ self.value = "fast"
+
+ def fast_mode(self):
+ return self.value == "fast"
+
+# The 'info rpm-suggestions' command.
+class rpm_suggestion_info(gdb.Command):
+ """Relist any RPM installation suggestions that have been made
+ since the executable was last changed."""
+ def __init__(self):
+ super().__init__("info rpm-suggestions", gdb.COMMAND_NONE, gdb.COMPLETE_NONE)
+
+ def invoke(self, args, from_tty):
+ if args != "":
+ raise gdb.GdbError("unexpected arguments: %s" % args)
+
+ info_rpm_suggestions ()
+
+
+# Create the 'set/show rpm-suggestion' commands.
+rpm_suggestion_set_prefix()
+rpm_suggestion_show_prefix()
+param_rpm_suggestion_enabled = rpm_suggestion_enabled()
+param_rpm_suggestion_build_id_mode = rpm_suggestion_build_id_mode()
+
+# Create the 'info rpm-suggestions' commands.
+rpm_suggestion_info()
diff --git a/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib-lib.c b/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib-lib.c
new file mode 100644
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib-lib.c
@@ -0,0 +1,21 @@
+/* Copyright 2010 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/>. */
+
+void
+lib (void)
+{
+}
diff --git a/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib-main.c b/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib-main.c
new file mode 100644
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib-main.c
@@ -0,0 +1,25 @@
+/* Copyright 2010 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/>. */
+
+extern void lib (void);
+
+int
+main (void)
+{
+ lib ();
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib.exp b/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib.exp
new file mode 100644
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gcore-buildid-exec-but-not-solib.exp
@@ -0,0 +1,104 @@
+# Copyright 2016 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/>.
+
+require allow_shlib_tests
+
+set testfile "gcore-buildid-exec-but-not-solib"
+set srcmainfile ${testfile}-main.c
+set srclibfile ${testfile}-lib.c
+set libfile [standard_output_file ${testfile}-lib.so]
+set objfile [standard_output_file ${testfile}-main.o]
+set executable ${testfile}-main
+set binfile [standard_output_file ${executable}]
+set gcorefile [standard_output_file ${executable}.gcore]
+set outdir [file dirname $binfile]
+
+if { [gdb_compile_shlib ${srcdir}/${subdir}/${srclibfile} ${libfile} "debug additional_flags=-Wl,--build-id"] != ""
+ || [gdb_compile ${srcdir}/${subdir}/${srcmainfile} ${objfile} object {debug}] != "" } {
+ unsupported "-Wl,--build-id compilation failed"
+ return -1
+}
+set opts [list debug shlib=${libfile} "additional_flags=-Wl,--build-id"]
+if { [gdb_compile ${objfile} ${binfile} executable $opts] != "" } {
+ unsupported "-Wl,--build-id compilation failed"
+ return -1
+}
+
+clean_restart $executable
+gdb_load_shlib $libfile
+
+# Does this gdb support gcore?
+set test "help gcore"
+gdb_test_multiple $test $test {
+ -re "Undefined command: .gcore.*\r\n$gdb_prompt $" {
+ # gcore command not supported -- nothing to test here.
+ unsupported "gdb does not support gcore on this target"
+ return -1;
+ }
+ -re "Save a core file .*\r\n$gdb_prompt $" {
+ pass $test
+ }
+}
+
+if { ![runto lib] } then {
+ return -1
+}
+
+set escapedfilename [string_to_regexp ${gcorefile}]
+
+set test "save a corefile"
+gdb_test_multiple "gcore ${gcorefile}" $test {
+ -re "Saved corefile ${escapedfilename}\r\n$gdb_prompt $" {
+ pass $test
+ }
+ -re "Can't create a corefile\r\n$gdb_prompt $" {
+ unsupported $test
+ return -1
+ }
+}
+
+# Now restart gdb and load the corefile.
+
+clean_restart $executable
+gdb_load_shlib $libfile
+
+set buildid [build_id_debug_filename_get $libfile]
+
+regsub {\.debug$} $buildid {} buildid
+
+set debugdir [standard_output_file ${testfile}-debugdir]
+file delete -force -- $debugdir
+
+file mkdir $debugdir/[file dirname $libfile]
+file copy $libfile $debugdir/${libfile}
+
+file mkdir $debugdir/[file dirname $buildid]
+file copy $libfile $debugdir/${buildid}
+
+remote_exec build "ln -s /lib ${debugdir}/"
+remote_exec build "ln -s /lib64 ${debugdir}/"
+# /usr is not needed, all the libs are in /lib64: libm.so.6 libc.so.6 ld-linux-x86-64.so.2
+
+gdb_test_no_output "set solib-absolute-prefix $debugdir" \
+ "set solib-absolute-prefix"
+
+gdb_test_no_output "set debug-file-directory $debugdir" "set debug-file-directory"
+
+gdb_test "core ${gcorefile}" "Core was generated by .*" "re-load generated corefile"
+
+gdb_test "frame" "#0 \[^\r\n\]* lib .*" "library got loaded"
+
+gdb_test "bt"
+gdb_test "info shared"
diff --git a/gdb/testsuite/gdb.base/gdbinit-history.exp b/gdb/testsuite/gdb.base/gdbinit-history.exp
--- a/gdb/testsuite/gdb.base/gdbinit-history.exp
+++ b/gdb/testsuite/gdb.base/gdbinit-history.exp
@@ -179,7 +179,8 @@ proc test_empty_history_filename { } {
global env
global gdb_prompt
- set common_history [list "set height 0" "set width 0"]
+ set common_history [list "set height 0" "set width 0" \
+ "set rpm-suggestion enabled off"]
set test_dir [standard_output_file history_test]
remote_exec host "mkdir -p $test_dir"
diff --git a/gdb/testsuite/gdb.base/rhbz981154-misleading-yum-install-warning.exp b/gdb/testsuite/gdb.base/rhbz981154-misleading-yum-install-warning.exp
new file mode 100644
--- /dev/null
+++ b/gdb/testsuite/gdb.base/rhbz981154-misleading-yum-install-warning.exp
@@ -0,0 +1,60 @@
+# Copyright (C) 2014 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/>.
+
+# Create a core file, then hide the executable. Restart GDB and load
+# the core file. Check GDB gives a message suggesting a 'dnf' command
+# to try and install the executable based on its build-id.
+
+standard_testfile "normal.c"
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+ return -1
+}
+
+# Get the build-id of the file.
+set build_id_debug_file [build_id_debug_filename_get $binfile]
+regsub -all ".debug$" $build_id_debug_file "" build_id_without_debug
+
+# Run to main.
+if { ![runto_main] } {
+ return -1
+}
+
+# We first need to generate a corefile.
+set corefilename "[standard_output_file gcore.test]"
+if { ![gdb_gcore_cmd "$corefilename" "save corefile"] } {
+ untested "could not generate a corefile"
+ return -1
+}
+
+# Move the binfile to a temporary name.
+remote_exec build "mv $binfile ${binfile}.old"
+
+# Reinitialize GDB and see if we get a dnf suggestion.
+clean_restart
+
+gdb_test "set rpm-suggestion enabled on" "" \
+ "turn on rpm-suggestion feature"
+
+# GDB only makes build-id based RPM suggestions if /usr/lib is in
+# the debug-file-directory list, the reason being that system RPMs
+# will always install under this location. If GDB is not looking
+# here then there's no point making suggestions.
+gdb_test "set debug-file-directory /usr/lib/" "" \
+ "set debug-file-directory"
+
+gdb_test "core-file [standard_output_file gcore.test]" \
+ "Missing file\\(s\\), try: dnf --enablerepo='\\*debug\\*' install [string_to_regexp /usr/lib/$build_id_without_debug] [string_to_regexp /usr/lib/debug/$build_id_debug_file]" \
+ "test first yum/dnf warning"
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.py b/gdb/testsuite/gdb.python/py-missing-debug.py
--- a/gdb/testsuite/gdb.python/py-missing-debug.py
+++ b/gdb/testsuite/gdb.python/py-missing-debug.py
@@ -19,6 +19,13 @@ from enum import Enum
import gdb
from gdb.missing_debug import MissingDebugHandler
+# This is a RHEL/Fedora work around: There's already a
+# missing-debug-info handler registered for these versions of GDB.
+# Discard the handler now so that the tests will pass (the tests
+# assume no handler is currently registered).
+gdb.missing_debug_handlers = []
+
+
# A global log that is filled in by instances of the LOG_HANDLER class
# when they are called.
handler_call_log = []
@@ -118,4 +125,7 @@ def register(name, locus=None):
rhandler = exception_handler()
handler_obj = handler()
+# Discard the rpm-suggestion handler.
+gdb.missing_file_handlers = []
+
print("Success")
diff --git a/gdb/testsuite/gdb.python/py-missing-objfile.py b/gdb/testsuite/gdb.python/py-missing-objfile.py
--- a/gdb/testsuite/gdb.python/py-missing-objfile.py
+++ b/gdb/testsuite/gdb.python/py-missing-objfile.py
@@ -164,4 +164,7 @@ def register(name, locus=None):
rhandler = exception_handler()
handler_obj = handler()
+# Discard the rpm-suggestion handler.
+gdb.missing_file_handlers = []
+
print("Success")
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -255,7 +255,8 @@ if ![info exists INTERNAL_GDBFLAGS] {
"-nx" \
"-q" \
{-iex "set height 0"} \
- {-iex "set width 0"}]]
+ {-iex "set width 0"} \
+ {-iex "set rpm-suggestion enabled off"}]]
# If DEBUGINFOD_URLS is set, gdb will try to download sources and
# debug info for f.i. system libraries. Prevent this.
@@ -2610,6 +2611,18 @@ proc default_gdb_start { } {
}
}
+ # Turn off the missing debug info messages as the testsuite does
+ # not expect them.
+ send_gdb "set rpm-suggestion enabled off\n"
+ gdb_expect 10 {
+ -re "$gdb_prompt $" {
+ verbose "Disabled the missing debug info messages." 2
+ }
+ timeout {
+ warning "Could not disable the missing debug info messages."
+ }
+ }
+
gdb_debug_init
return 0
}
diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -321,6 +321,17 @@ proc default_mi_gdb_start { { flags {} } } {
warning "Couldn't set the width to 0."
}
}
+ # Turn off the missing debug info messages as the testsuite does
+ # not expect them.
+ send_gdb "190-gdb-set rpm-suggestion enabled off\n"
+ gdb_expect 10 {
+ -re ".*190-gdb-set rpm-suggestion enabled off\r\n190\\\^done\r\n$mi_gdb_prompt$" {
+ verbose "Disabled the missing debug info messages." 2
+ }
+ timeout {
+ warning "Could not disable the missing debug info messages."
+ }
+ }
if { $separate_inferior_pty } {
mi_create_inferior_pty