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
openSUSE Build Service is sponsored by