@@ -54,6 +54,10 @@ show riscv numeric-register-names
(e.g 'x1') or their abi names (e.g. 'ra').
Defaults to 'off', matching the old behaviour (abi names).
+info build-ids
+ List the build-ids for all files associated with the current
+ inferior.
+
* Changed commands
info sharedlibrary
@@ -79,6 +83,10 @@ info sharedlibrary
when output is going to standard output, and False when output is
going to a string.
+ ** gdb.Inferior.build_ids(). This returns a list of (FILENAME,
+ BUILD-ID) tuples. Each tuple is a file, and its build-id, that
+ is associated with the gdb.Inferior object.
+
* Guile API
** New type <gdb:color> for dealing with colors.
@@ -429,3 +429,59 @@ find_objfile_by_build_id (program_space *pspace,
return abfd;
}
+
+/* Implement 'info build-ids'. Print a line containing the build-id and
+ filename for every file with a build-id in the current inferior. */
+static void
+info_build_ids_command (const char *args, int from_tty)
+{
+ std::vector<build_id_and_filename> list
+ = target_gather_build_ids (from_tty);
+
+ if (list.empty ())
+ {
+ gdb_printf ("There are no files with a build-id.\n");
+ return;
+ }
+
+ /* Minimum lengths based on the column heading string. */
+ std::string::size_type longest_build_id = 8;
+ std::string::size_type longest_filename = 4;
+
+ /* Update the column widths based on the content. */
+ for (const auto &it : list)
+ {
+ longest_build_id
+ = std::max (build_id_to_string (it.build_id ()).length (),
+ longest_build_id);
+
+ longest_filename
+ = std::max (longest_filename, it.filename ().length ());
+ }
+
+ /* Now output the table. */
+ ui_out_emit_table emitter (current_uiout, 2, -1, "CoreFileBuildIDs");
+ current_uiout->table_header (longest_build_id, ui_left,
+ "build_id", "Build Id");
+ current_uiout->table_header (longest_filename, ui_left,
+ "objfile", "File");
+ current_uiout->table_body ();
+
+ for (const auto &it : list)
+ {
+ ui_out_emit_tuple tuple_emitter (current_uiout, nullptr);
+ current_uiout->field_string ("build_id",
+ build_id_to_string (it.build_id ()));
+ current_uiout->field_string ("objfile", it.filename (),
+ file_name_style.style ());
+ current_uiout->text ("\n");
+ }
+}
+
+void _initialize_build_id ();
+void
+_initialize_build_id ()
+{
+ add_info ("build-ids", info_build_ids_command, _("\
+List build-ids of files in the current inferior."));
+}
@@ -23,6 +23,33 @@
#include "gdb_bfd.h"
#include "gdbsupport/rsp-low.h"
+/* Structure that associates a build-id with the filename in which the
+ build-id was found. */
+
+struct build_id_and_filename
+{
+ /* Constructor. */
+ build_id_and_filename (const bfd_build_id *build_id, std::string filename)
+ : m_build_id (build_id),
+ m_filename (std::move (filename))
+ { /* Nothing. */ }
+
+ /* The build-id. */
+ const bfd_build_id *build_id () const
+ { return m_build_id; }
+
+ /* The filename. */
+ const std::string &filename () const
+ { return m_filename; }
+
+private:
+ /* A build-id. */
+ const bfd_build_id *m_build_id;
+
+ /* The filename from which, or for which, M_BUILD_ID is associated. */
+ std::string m_filename;
+};
+
/* Locate NT_GNU_BUILD_ID from ABFD and return its content. */
extern const struct bfd_build_id *build_id_bfd_get (bfd *abfd);
@@ -86,6 +86,10 @@ struct mem_range_and_build_id
struct mapped_file_info
{
+ /* A type that maps a string to a build-id. */
+ using string_to_build_id_map
+ = gdb::unordered_map<std::string, const bfd_build_id *>;
+
/* See comment on function definition. */
void add (const char *soname, const char *expected_filename,
@@ -97,6 +101,14 @@ struct mapped_file_info
std::optional <core_target_mapped_file_info>
lookup (const char *filename, const std::optional<CORE_ADDR> &addr);
+ /* Return the map of filename to build-ids extracted from the core
+ file. */
+
+ const string_to_build_id_map &filename_to_build_id_map () const
+ {
+ return m_filename_to_build_id_map;
+ }
+
private:
/* Helper for ::lookup. BUILD_ID is a build-id that was found in
@@ -123,10 +135,6 @@ struct mapped_file_info
return { build_id, {} };
}
- /* A type that maps a string to a build-id. */
- using string_to_build_id_map
- = gdb::unordered_map<std::string, const bfd_build_id *>;
-
/* A type that maps a build-id to a string. */
using build_id_to_string_map
= gdb::unordered_map<const bfd_build_id *, std::string>;
@@ -280,6 +288,11 @@ class core_target final : public process_stratum_target
return m_expected_exec_filename;
}
+ /* Core file version of target_ops::gather_build_ids. Add filenames and
+ build-ids from the core file to LIST. */
+ bool gather_build_ids (std::vector<build_id_and_filename> &list,
+ int from_tty) override;
+
private: /* per-core data */
/* Get rid of the core inferior. */
@@ -321,6 +334,52 @@ class core_target final : public process_stratum_target
std::string m_expected_exec_filename;
};
+/* See class declaration above. */
+
+bool
+core_target::gather_build_ids (std::vector<build_id_and_filename> &list,
+ int from_tty)
+{
+ /* The core file might have the correct path and build-id information for
+ the executable within the mapped files data. If this is the case then
+ we would like to avoid including the executable twice in the output.
+
+ On the flip side, if the core file is missing the build-id data, then
+ we'd still like to include the executable, if the user has gone to the
+ trouble of telling GDB about it.
+
+ Find the executable's build-id now, and compare the value with each
+ build-id from the core-file. If we find a matching build-id from the
+ core file, and if the filename matches the executable's filename,
+ then we avoid adding the executable to LIST. */
+ const bfd_build_id *exec_build_id
+ = build_id_bfd_get (current_program_space->exec_bfd ());
+ const char *exec_filename = current_program_space->exec_filename ();
+
+ for (const auto &it : m_mapped_file_info.filename_to_build_id_map ())
+ {
+ if (exec_build_id != nullptr
+ && exec_filename != nullptr
+ && build_id_equal (exec_build_id, it.second)
+ && strcmp (exec_filename, it.first.c_str ()) == 0)
+ {
+ /* The core file already contains an entry with this build-id and
+ filename. Clear EXEC_FILENAME and EXEC_BUILD_ID so we don't
+ add these to LIST later. */
+ exec_filename = nullptr;
+ exec_build_id = nullptr;
+ }
+
+ list.emplace_back (it.second, std::string (it.first));
+ }
+
+ /* Add the executable to LIST if it wasn't in the core file already. */
+ if (exec_filename != nullptr && exec_build_id != nullptr)
+ list.emplace_back (exec_build_id, std::string (exec_filename));
+
+ return true;
+}
+
core_target::core_target ()
{
/* Find a first arch based on the BFD. We need the initial gdbarch so
@@ -22627,6 +22627,25 @@ Separate Debug Files
value is present in the original built binary with symbols, in its
stripped variant, and in the separate debugging information file.
+@table @code
+@anchor{info build-ids command}
+@kindex info build-ids
+@cindex build-ids, listing
+@item info build-ids
+Print the filename, and build-id, for any file associated with the
+current inferior, which has a build-id. If a file doesn't have a
+build-id, then it will not be included in the output of this command.
+
+The set of files that will be listed by this command varies based on
+the connection type, for native (@pxref{target native command}) and
+remote (@pxref{Remote Debugging}) connections, @value{GDBN} will check
+the current executable, and any loaded shared libraries for build-ids.
+For core file debugging (@pxref{core-file command}), @value{GDBN} will
+check all of the files referenced from the core file for build-ids,
+this will include files that are referenced by the core file, but
+@value{GDBN} was unable to find.
+@end table
+
The debugging information file itself should be an ordinary
executable, containing a full set of linker symbols, sections, and
debugging information. The sections of the debugging information file
@@ -23350,6 +23369,7 @@ Target Commands
see the appropriate section in @ref{Embedded Processors, ,Embedded
Processors}.
+@anchor{target native command}
@item target native
@cindex native target
Setup for local/native process debugging. Useful to make the
@@ -3679,6 +3679,22 @@ Inferiors In Python
string.
@end defun
+@cindex build-ids, listing
+@defun Inferior.build_ids ()
+Return a list of tuples. Each tuple has two elements, the first is a
+filename as a string, and the second is the associated build-id as a
+string.
+
+There will be one tuple for each file associated with the inferior,
+for which @value{GDBN} was able to find a build-id. If @value{GDBN}
+cannot find the build-id for a file, then there will be no tuple for
+that file in the returned list.
+
+This method provides the same information as the @kbd{info build-ids}
+command (@pxref{info build-ids command}), and the results will vary in
+the same way based on the type of connection the inferior has.
+@end defun
+
One may add arbitrary attributes to @code{gdb.Inferior} objects in the
usual Python way. This is useful if, for example, one needs to do
some extra record keeping associated with the inferior.
@@ -80,6 +80,11 @@ struct exec_target final : public target_ops
bool has_memory () override;
gdb::unique_xmalloc_ptr<char> make_corefile_notes (bfd *, int *) override;
int find_memory_regions (find_memory_region_ftype func, void *data) override;
+
+ /* Exec file specific version of target_ops::gather_build_ids. Add the
+ build-id for the executable to LIST. */
+ bool gather_build_ids (std::vector<build_id_and_filename> &list,
+ int from_tty) override;
};
static exec_target exec_ops;
@@ -1065,6 +1070,23 @@ exec_target::find_memory_regions (find_memory_region_ftype func, void *data)
return objfile_find_memory_regions (this, func, data);
}
+/* See class declaration above. */
+
+bool
+exec_target::gather_build_ids (std::vector<build_id_and_filename> &list,
+ int from_tty)
+{
+ const char *filename = current_program_space->exec_filename ();
+
+ const bfd_build_id *build_id
+ = build_id_bfd_get (current_program_space->exec_bfd ());
+
+ if (filename != nullptr && build_id != nullptr)
+ list.emplace_back (build_id, std::string (filename));
+
+ return true;
+}
+
void _initialize_exec ();
void
_initialize_exec ()
@@ -19,6 +19,7 @@
#include "process-stratum-target.h"
#include "inferior.h"
+#include "solib.h"
#include <algorithm>
process_stratum_target::~process_stratum_target ()
@@ -195,6 +196,34 @@ process_stratum_target::find_thread (ptid_t ptid)
/* See process-stratum-target.h. */
+bool
+process_stratum_target::gather_build_ids
+ (std::vector<build_id_and_filename> &list, int from_tty)
+{
+ /* Add FILENAME and BUILD_ID as a new entry to LIST. */
+ auto add_entry = [&] (const char *filename, const bfd_build_id *build_id)
+ {
+ if (filename == nullptr || build_id == nullptr || *filename == '\0')
+ return;
+
+ list.emplace_back (build_id, std::string (filename));
+ };
+
+ /* Add an entry for the executable. */
+ add_entry (current_program_space->exec_filename (),
+ build_id_bfd_get (current_program_space->exec_bfd ()));
+
+ update_solib_list (from_tty);
+
+ /* Add an entry for each shared library. */
+ for (const solib &so : current_program_space->solibs ())
+ add_entry (so.so_name.c_str (), build_id_bfd_get (so.abfd.get ()));
+
+ return true;
+}
+
+/* See process-stratum-target.h. */
+
gdb::unordered_set<process_stratum_target *>
all_non_exited_process_targets ()
{
@@ -79,6 +79,11 @@ class process_stratum_target : public target_ops
target_waitkind fork_kind, bool follow_child,
bool detach_on_fork) override;
+ /* Record filename and build-id for the executable and each shared
+ library of the current process, results are added to LIST. */
+ bool gather_build_ids (std::vector<build_id_and_filename> &list,
+ int from_tty) override;
+
/* True if any thread is, or may be executing. We need to track
this separately because until we fully sync the thread list, we
won't know whether the target is fully stopped, even if we see
@@ -865,6 +865,59 @@ infpy_unset_env (PyObject *obj, PyObject *args, PyObject *kw)
Py_RETURN_NONE;
}
+/* Implement gdb.Inferior.build_ids(). */
+
+static PyObject *
+infpy_build_ids (PyObject *obj)
+{
+ inferior_object *self = (inferior_object *) obj;
+
+ INFPY_REQUIRE_VALID (self);
+
+ scoped_restore_current_inferior_for_memory restore_inferior (self->inferior);
+
+ std::vector<build_id_and_filename> build_id_and_filename_list;
+ try
+ {
+ build_id_and_filename_list = target_gather_build_ids (false);
+ }
+ catch (const gdb_exception &except)
+ {
+ return gdbpy_handle_gdb_exception (nullptr, except);
+ }
+
+ gdbpy_ref<> result_list (PyList_New (0));
+ if (result_list == nullptr)
+ return nullptr;
+
+ for (const auto &it : build_id_and_filename_list)
+ {
+ gdbpy_ref<> tuple (PyTuple_New (2));
+ if (tuple == nullptr)
+ return nullptr;
+
+ gdbpy_ref<> filename_str
+ = host_string_to_python_string (it.filename ().c_str ());
+ if (filename_str == nullptr)
+ return nullptr;
+ if (PyTuple_SetItem (tuple.get (), 0, filename_str.release ()) != 0)
+ return nullptr;
+
+ std::string build_id = build_id_to_string (it.build_id ());
+ gdbpy_ref<> build_id_str
+ = host_string_to_python_string (build_id.c_str ());
+ if (build_id_str == nullptr)
+ return nullptr;
+ if (PyTuple_SetItem (tuple.get (), 1, build_id_str.release ()) != 0)
+ return nullptr;
+
+ if (PyList_Append (result_list.get (), tuple.get ()))
+ return nullptr;
+ }
+
+ return result_list.release ();
+}
+
/* Getter for "arguments". */
static PyObject *
@@ -1106,6 +1159,10 @@ Set an environment variable of this inferior." },
{ "unset_env", (PyCFunction) infpy_unset_env, METH_VARARGS | METH_KEYWORDS,
"unset_env (name) -> None\n\
Unset an environment of this inferior." },
+ { "build_ids", (PyCFunction) infpy_build_ids, METH_NOARGS,
+ "build_ids () -> List\n\
+Return a list of (FILENAME, BUILD_ID) tuples, one entry for each file\n\
+with a build-id in the inferior object." },
{ NULL }
};
@@ -191,6 +191,11 @@ target_debug_print_std_vector_static_tracepoint_marker
(const std::vector<static_tracepoint_marker> &vec)
{ return host_address_to_string (vec.data ()); }
+static std::string
+target_debug_print_std_vector_build_id_and_filename_r
+ (const std::vector<build_id_and_filename> &vec)
+{ return host_address_to_string (vec.data ()); }
+
static std::string
target_debug_print_const_target_desc_p (const target_desc *tdesc)
{ return host_address_to_string (tdesc); }
@@ -203,6 +203,7 @@ struct dummy_target : public target_ops
displaced_step_prepare_status displaced_step_prepare (thread_info *arg0, CORE_ADDR &arg1) override;
displaced_step_finish_status displaced_step_finish (thread_info *arg0, const target_waitstatus &arg1) override;
void displaced_step_restore_all_in_ptid (inferior *arg0, ptid_t arg1) override;
+ bool gather_build_ids (std::vector<build_id_and_filename> &arg0, int arg1) override;
};
struct debug_target : public target_ops
@@ -384,6 +385,7 @@ struct debug_target : public target_ops
displaced_step_prepare_status displaced_step_prepare (thread_info *arg0, CORE_ADDR &arg1) override;
displaced_step_finish_status displaced_step_finish (thread_info *arg0, const target_waitstatus &arg1) override;
void displaced_step_restore_all_in_ptid (inferior *arg0, ptid_t arg1) override;
+ bool gather_build_ids (std::vector<build_id_and_filename> &arg0, int arg1) override;
};
void
@@ -4519,3 +4521,29 @@ debug_target::displaced_step_restore_all_in_ptid (inferior *arg0, ptid_t arg1)
target_debug_print_inferior_p (arg0).c_str (),
target_debug_print_ptid_t (arg1).c_str ());
}
+
+bool
+target_ops::gather_build_ids (std::vector<build_id_and_filename> &arg0, int arg1)
+{
+ return this->beneath ()->gather_build_ids (arg0, arg1);
+}
+
+bool
+dummy_target::gather_build_ids (std::vector<build_id_and_filename> &arg0, int arg1)
+{
+ return false;
+}
+
+bool
+debug_target::gather_build_ids (std::vector<build_id_and_filename> &arg0, int arg1)
+{
+ target_debug_printf_nofunc ("-> %s->gather_build_ids (...)", this->beneath ()->shortname ());
+ bool result
+ = this->beneath ()->gather_build_ids (arg0, arg1);
+ target_debug_printf_nofunc ("<- %s->gather_build_ids (%s, %s) = %s",
+ this->beneath ()->shortname (),
+ target_debug_print_std_vector_build_id_and_filename_r (arg0).c_str (),
+ target_debug_print_int (arg1).c_str (),
+ target_debug_print_bool (result).c_str ());
+ return result;
+}
@@ -4399,6 +4399,24 @@ exists_non_stop_target ()
return false;
}
+/* See target.h. */
+
+std::vector<build_id_and_filename>
+target_gather_build_ids (int from_tty)
+{
+ std::vector<build_id_and_filename> list;
+
+ for (target_ops *t = current_inferior ()->top_target ();
+ t != NULL;
+ t = t->beneath ())
+ {
+ if (t->gather_build_ids (list, from_tty))
+ break;
+ }
+
+ return list;
+}
+
/* Controls if targets can report that they always run in non-stop
mode. This is just for maintainers to use when debugging gdb. */
enum auto_boolean target_non_stop_enabled = AUTO_BOOLEAN_AUTO;
@@ -83,6 +83,7 @@ typedef const gdb_byte const_gdb_byte;
#include "command.h"
#include "disasm-flags.h"
#include "tracepoint.h"
+#include "build-id.h"
#include "gdbsupport/fileio.h"
#include "gdbsupport/x86-xstate.h"
@@ -1399,6 +1400,12 @@ struct target_ops
virtual void displaced_step_restore_all_in_ptid (inferior *parent_inf,
ptid_t child_ptid)
TARGET_DEFAULT_FUNC (default_displaced_step_restore_all_in_ptid);
+
+ /* Add entries to LIST. Return true if all build-ids have been
+ recorded, otherwise, return false. */
+ virtual bool gather_build_ids (std::vector<build_id_and_filename> &list,
+ int from_tty)
+ TARGET_DEFAULT_RETURN (false);
};
/* Deleter for std::unique_ptr. See comments in
@@ -2654,4 +2661,8 @@ extern void target_prepare_to_generate_core (void);
/* See to_done_generating_core. */
extern void target_done_generating_core (void);
+/* See target::gather_build_ids. */
+extern std::vector<build_id_and_filename>
+ target_gather_build_ids (int from_tty);
+
#endif /* GDB_TARGET_H */
new file mode 100644
@@ -0,0 +1,12 @@
+#include <stdlib.h>
+
+extern int foo (int);
+
+int
+foo (int dump_core_p)
+{
+ if (dump_core_p)
+ abort ();
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,15 @@
+extern int foo (int);
+
+#ifdef DUMP_CORE
+static int dump_core_flag = 1;
+#else
+static int dump_core_flag = 0;
+#endif
+
+int
+main (void)
+{
+ int result = foo (dump_core_flag);
+
+ return result;
+}
new file mode 100644
@@ -0,0 +1,428 @@
+# Copyright 2025 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/>.
+
+# Test the 'info build-ids' command. This covers the native/remote
+# target use case, and also the core file use case.
+#
+# This test also includes testing of the Python API as it fits so
+# easily into this script.
+
+require allow_shlib_tests
+
+# Running the tests on a remote hosts means that the filename regexp
+# don't match, we could fix that but, there are also problems with the
+# core file tests. The core files are generated on the build machine,
+# but would then need to be copied to the host to be loaded, at that
+# point, none of the paths in the core file are valid any longer.
+# With all these issues, it's easier to just skip remote hosts.
+require {!is_remote host}
+
+standard_testfile .c -shlib.c
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+# The Python script creates a new command 'info py-build-ids' which
+# produces output identical to 'info build-ids', but uses the Python
+# API to collect the data.
+#
+# In the rest of this script, everywhere that we might use 'info
+# build-ids' we loop over BUILD_ID_CMDS and try each of these, the
+# output should be identical.
+set build_id_cmds { build-ids }
+if { [allow_python_tests] } {
+ lappend build_id_cmds py-build-ids
+}
+
+# Run FILENAME and generate a core file. Ensure that the generated
+# core file has a build-id for FILENAME within it. If the core file
+# cannot be created, or the core file lacks the build-id, then return
+# the empty string, otherwise, return the filename for the generated
+# core file.
+proc generate_core_file { filename } {
+ set corefile [core_find $filename]
+ if {$corefile == ""} {
+ untested "could not generate core file"
+ return ""
+ }
+
+ # Check the corefile has a build-id for the executable.
+ if { [catch "exec [gdb_find_eu-unstrip] -n --core $corefile" output] == 0 } {
+ set line [lindex [split $output "\n"] 0]
+ set binfile_re (?:[string_to_regexp $filename]|\\\[(?:exe|pie)\\\])
+ if { ![regexp "^${::hex}\\+${::hex} \[a-f0-9\]+@${::hex}.*$binfile_re$" $line] } {
+ unsupported "no build-id for executable in corefile"
+ return ""
+ }
+ } else {
+ unsupported "eu-unstrip tool failed"
+ return ""
+ }
+
+ return $corefile
+}
+
+# Build an executable and shared library. Start GDB and check the
+# 'info build-ids' output as the executable is first loaded, then
+# started, and finally, allowed to terminate.
+proc_with_prefix test_exec_targets {} {
+ # Compile shared library.
+ set libname lib$::testfile.so
+ set objlib [standard_output_file $libname]
+ set opts [list debug shlib buildid]
+ if {[build_executable "build shlib" $objlib $::srcfile2 $opts] == -1} {
+ return
+ }
+
+ # Compile main program.
+ set exec_filename $::binfile
+ set opts [list debug shlib=$objlib buildid]
+ if {[build_executable "build exec" $exec_filename $::srcfile $opts] == -1} {
+ return
+ }
+
+ set build_id_lib [get_build_id $objlib]
+ set build_id_exe [get_build_id $exec_filename]
+
+ if { $build_id_exe eq "" || $build_id_exe eq "" } {
+ unsupported "couldn't find required build-ids"
+ return
+ }
+
+ set objlib [gdb_download_shlib $objlib]
+ set objlib_re [string_to_regexp $objlib]
+ if { [is_remote target] } {
+ set objlib_re "(?:target:)?$objlib_re"
+ }
+
+ clean_restart
+
+ if { [allow_python_tests] } {
+ gdb_test_no_output "source $::pyfile" "load python script"
+ }
+
+ # GDB is started, but the inferior has no file loaded yet, so there
+ # are no build-ids to display.
+ foreach cmd $::build_id_cmds {
+ gdb_test "info $cmd" "^There are no files with a build-id\\." \
+ "'info $cmd' when no files are loaded"
+ }
+
+ gdb_file_cmd $exec_filename
+
+ # The inferior now has a file loaded, so that is all that should
+ # appear in the build-ids list.
+ foreach cmd $::build_id_cmds {
+ gdb_test "info $cmd" \
+ [multi_line \
+ "Build Id\\s+File.*" \
+ "$build_id_exe\\s+[string_to_regexp $exec_filename]\\s*"] \
+ "'info $cmd' before starting the inferior"
+ }
+
+ if {![runto_main]} {
+ return
+ }
+
+ # With the inferior started we should now see the build-ids for all
+ # libraries and the executable. We only check for the build-id of the
+ # executable and the shared library we built above though.
+ foreach cmd $::build_id_cmds {
+ set saw_build_id_lib false
+ set saw_build_id_exe false
+
+ gdb_test_multiple "info $cmd" "'info $cmd' with inferior started" {
+ -re "^info $cmd\r\n" {
+ exp_continue
+ }
+
+ -re "^Build Id\\s+File\\s*\r\n" {
+ exp_continue
+ }
+
+ -re "^$build_id_exe\\s+[string_to_regexp $exec_filename]\\s*\r\n" {
+ set saw_build_id_exe true
+ exp_continue
+ }
+
+ -re "^$build_id_lib\\s+$objlib_re\\s*\r\n" {
+ set saw_build_id_lib true
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { $saw_build_id_exe && $saw_build_id_lib } \
+ $gdb_test_name
+ }
+
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+ }
+ }
+
+ gdb_continue_to_end
+
+ # The 'extended-remote' target doesn't unpush itself when the inferior
+ # exits (which makes sense), but as a consequence, the build-ids list
+ # will include the build-ids for the shared libraries that have not
+ # (yet) been cleared from the inferior's library list.
+ if {[target_info gdb_protocol] != "extended-remote"} {
+ # The inferior now has a file loaded, so that is all that should
+ # appear in the build-ids list.
+ foreach cmd $::build_id_cmds {
+ gdb_test "info $cmd" \
+ [multi_line \
+ "Build Id\\s+File.*" \
+ "$build_id_exe\\s+[string_to_regexp $exec_filename]\\s*"] \
+ "'info $cmd' after inferior exits"
+ }
+ }
+}
+
+# Compile an executable and shared library that result in a core file.
+# Load the core file into GDB and check the 'info build-ids' output.
+proc_with_prefix test_core_targets {} {
+ # Compile shared library.
+ set libname lib${::testfile}-core.so
+ set objlib [standard_output_file $libname]
+ set opts [list debug shlib buildid]
+ if {[build_executable "build shlib" $objlib $::srcfile2 $opts] == -1} {
+ return
+ }
+
+ # Compile main program, this time arrange for the executable to dump
+ # core.
+ set exec_filename ${::binfile}-core
+ set opts [list debug shlib=$objlib buildid additional_flags=-DDUMP_CORE]
+ if {[build_executable "build exec" $exec_filename $::srcfile $opts] == -1} {
+ return
+ }
+
+ set build_id_lib [get_build_id $objlib]
+ set build_id_exe [get_build_id $exec_filename]
+
+ set corefile [generate_core_file $exec_filename]
+ if {$corefile == ""} {
+ return
+ }
+
+ clean_restart
+
+ if { [allow_python_tests] } {
+ gdb_test_no_output "source $::pyfile" "load python script"
+ }
+
+ # Load the core file. GDB should auto-load the executable.
+ gdb_test "core-file $corefile" "Program terminated with .*" \
+ "load core file"
+
+ gdb_test "info inferiors" \
+ "\r\n\\*\\s+1\[^\r\n\]+[string_to_regexp $exec_filename]\\s*" \
+ "confirm executable was auto-loaded"
+
+ # The build-ids for all libraries and the executable should now be
+ # visible.
+ foreach cmd $::build_id_cmds {
+ set saw_build_id_lib false
+ set saw_build_id_exe false
+ gdb_test_multiple "info $cmd" "'info $cmd'" {
+ -re "^info $cmd\r\n" {
+ exp_continue
+ }
+
+ -re "^Build Id\\s+File\\s*\r\n" {
+ exp_continue
+ }
+
+ -re "^$build_id_exe\\s+[string_to_regexp $exec_filename]\\s*\r\n" {
+ set saw_build_id_exe true
+ exp_continue
+ }
+
+ -re "^$build_id_lib\\s+[string_to_regexp $objlib]\\s*\r\n" {
+ set saw_build_id_lib true
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { $saw_build_id_exe && $saw_build_id_lib } \
+ $gdb_test_name
+ }
+
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+ }
+ }
+
+ # Re-load the core-file, but first, set the executable to a copy of
+ # the expected executable, but with a different name. Both the
+ # original executable, from the core file, and the new executable,
+ # should show up in the build-ids output.
+
+ set exec_filename_2 [standard_output_file "${::testfile}-core-2"]
+ remote_exec host "cp \"$exec_filename\" \"$exec_filename_2\""
+
+ clean_restart $exec_filename_2
+
+ if { [allow_python_tests] } {
+ gdb_test_no_output "source $::pyfile" "load python script again"
+ }
+
+ # Load the core file. GDB will not auto-load the executable, as we've
+ # already told GDB which executable to load.
+ gdb_test "core-file $corefile" "Program terminated with .*" \
+ "load core file a second time"
+
+ gdb_test "info inferiors" \
+ "\r\n\\*\\s+1\[^\r\n\]+[string_to_regexp $exec_filename_2]\\s*" \
+ "confirm executable is copy of original"
+
+ # The build-ids for all libraries and the executable should now be
+ # visible.
+ foreach cmd $::build_id_cmds {
+ set saw_build_id_lib false
+ set saw_build_id_exe false
+ set saw_build_id_exe_2 false
+ gdb_test_multiple "info $cmd" "'info $cmd' with exec file" {
+ -re "^info $cmd\r\n" {
+ exp_continue
+ }
+
+ -re "^Build Id\\s+File\\s*\r\n" {
+ exp_continue
+ }
+
+ -re "^$build_id_exe\\s+[string_to_regexp $exec_filename]\\s*\r\n" {
+ set saw_build_id_exe true
+ exp_continue
+ }
+
+ -re "^$build_id_exe\\s+[string_to_regexp $exec_filename_2]\\s*\r\n" {
+ set saw_build_id_exe_2 true
+ exp_continue
+ }
+
+ -re "^$build_id_lib\\s+[string_to_regexp $objlib]\\s*\r\n" {
+ set saw_build_id_lib true
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { $saw_build_id_exe \
+ && $saw_build_id_lib \
+ && $saw_build_id_exe_2 } \
+ $gdb_test_name
+ }
+
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+ }
+ }
+}
+
+# Build a new executable and shared library. Generate a core file
+# from the executable, then delete the executable and shared library.
+#
+# Load the core file into GDB and check the 'info build-ids' output.
+# Ensure that the executable and shared library show up in the output,
+# and have the correct build-id.
+proc_with_prefix test_core_targets_missing_files {} {
+ # Compile shared library, but with a different name. We're going to
+ # delete this shared library once the core file has been created.
+ set libname lib${::testfile}-missing.so
+ set objlib [standard_output_file $libname]
+ set opts [list debug shlib buildid]
+ if {[build_executable "build shlib" $objlib $::srcfile2 $opts] == -1} {
+ return
+ }
+
+ # Compile main program, again, this program will dump core, but this
+ # time link against the new shared library.
+ set exec_filename ${::binfile}-missing
+ set opts [list debug shlib=$objlib buildid additional_flags=-DDUMP_CORE]
+ if {[build_executable "build exec for missing lib" $exec_filename $::srcfile $opts] == -1} {
+ return
+ }
+
+ # Grab the build-ids.
+ set build_id_lib [get_build_id $objlib]
+ set build_id_exe [get_build_id $exec_filename]
+
+ # Generate the core files.
+ set corefile [generate_core_file $exec_filename]
+ if {$corefile == ""} {
+ return
+ }
+
+ # Now delete the shared library and executable. The core file will
+ # still reference these files though.
+ remote_exec host "rm \"$objlib\""
+ remote_exec host "rm \"$exec_filename\""
+
+ clean_restart
+
+ if { [allow_python_tests] } {
+ gdb_test_no_output "source $::pyfile" "load python script"
+ }
+
+ # Load the core file. GDB will not auto-load the executable, as we've
+ # already told GDB which executable to load.
+ gdb_test "core-file $corefile" "Program terminated with .*" \
+ "load core file a third time"
+
+ # The build-ids for all libraries and the executable should now be
+ # visible. This includes the build-id for the shared library that is
+ # missing from the filesystem, but which is referenced from the core
+ # file.
+ foreach cmd $::build_id_cmds {
+ set saw_build_id_lib false
+ set saw_build_id_exe false
+ gdb_test_multiple "info $cmd" "'info $cmd'" {
+ -re "^info $cmd\r\n" {
+ exp_continue
+ }
+
+ -re "^Build Id\\s+File\\s*\r\n" {
+ exp_continue
+ }
+
+ -re "^$build_id_exe\\s+[string_to_regexp $exec_filename]\\s*\r\n" {
+ set saw_build_id_exe true
+ exp_continue
+ }
+
+ -re "^$build_id_lib\\s+[string_to_regexp $objlib]\\s*\r\n" {
+ set saw_build_id_lib true
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { $saw_build_id_exe && $saw_build_id_lib } \
+ $gdb_test_name
+ }
+
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+ }
+ }
+}
+
+# Run the tests.
+test_exec_targets
+test_core_targets
+test_core_targets_missing_files
new file mode 100644
@@ -0,0 +1,37 @@
+# Copyright (C) 2025 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/>.
+
+class ShowBuildIds(gdb.Command):
+ def __init__ (self):
+ gdb.Command.__init__ (self, "info py-build-ids", gdb.COMMAND_DATA)
+
+ def invoke(self, args, from_tty):
+ list = gdb.selected_inferior().build_ids()
+
+ if len(list) == 0:
+ print("There are no files with a build-id.")
+ return
+
+ longest_build_id = 0
+ for id_and_filename in list:
+ if len(id_and_filename[1]) > longest_build_id:
+ longest_build_id = len(id_and_filename[1])
+
+ print(f"%-{longest_build_id}s File" % "Build Id")
+ for id_and_filename in list:
+ print(f"{id_and_filename[1]:{longest_build_id}s} {id_and_filename[0]}")
+
+
+ShowBuildIds()