]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb/python: add Corefile.mapped_files method
authorAndrew Burgess <aburgess@redhat.com>
Mon, 25 Aug 2025 15:48:22 +0000 (16:48 +0100)
committerAndrew Burgess <aburgess@redhat.com>
Mon, 6 Oct 2025 15:56:56 +0000 (16:56 +0100)
Add a new Corefile.mapped_files method which returns a list of
gdb.CorefileMappedFile objects.

Each gdb.CorefileMappedFile object represents a file that was mapped
into the process when the core file was created.

A gdb.CorefileMappedFile has attributes:

  + filename  -- A string, the name of the mapped file.
  + build_id -- A string or None, the build-id of the mapped file if
                GDB could find it (None if not).
  + is_main_executable -- A boolean, True if this mapping is the main
                          executable.
  + regions -- A list containing the regions of this file that were
               mapped into the process.

The 'regions' list is a list of gdb.CorefileMappedFileRegion objects,
each of these objects has the following attributes:

  + start -- the start address within the inferior.
  + end -- the end address within the inferior.
  + file_offset -- the offset within the mapped file for this mapping.

There are docs and tests.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32844

Approved-By: Tom Tromey <tom@tromey.com>
gdb/NEWS
gdb/doc/python.texi
gdb/python/py-corefile.c
gdb/testsuite/gdb.python/py-corefile.exp
gdb/testsuite/gdb.python/py-corefile.py [new file with mode: 0644]

index ebd0e0632275ece306b9c4dc178767372a63da0c..b0146852c5c101eabc4abac2035a4a837756d437 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -87,7 +87,18 @@ qExecAndArgs
      has an attribute Corefile.filename, the file name of the loaded
      core file, and a method Corefile.is_valid(), which returns False
      when a Corefile object becomes invalid (e.g. when the core file
-     is unloaded).
+     is unloaded).  There is also Corefile.mapped_files() which
+     returns a list of CorefileMappedFile objects, representing files
+     that were mapped into the core file when it was created.
+
+  ** New gdb.CorefileMappedFile type representing a file that was
+     mapped when the core file was created.  Has read-only attributes
+     filename (string), build_id (string), is_main_executable
+     (boolean), and regions (list of CorefileMappedFileRegion objects).
+
+  ** New gdb.CorefileMappedFileRegion type, which represents a mapped
+     region of a file (see gdb.CorefileMappedFile above).  Has
+     read-only attributes start, end, and file_offset.
 
   ** New Inferior.corefile attribute.  This read only attribute
      contains the gdb.Corefile object if a core file is loaded into
index c671d2313454705c7907201a30105447ace61aed..ed1131736672cf6f73f2a623f459a8e8185c9dea 100644 (file)
@@ -8927,12 +8927,73 @@ it is invalid at the time the method is called, or the attribute
 accessed.
 @end defun
 
+@defun Corefile.mapped_files ()
+Return a list of @code{gdb.CorefileMappedFile} (see below) objects
+representing files that were mapped into the process when the core
+file was created.  This information is read from the @samp{NT_FILE}
+core file note on Linux.  Not every target supports accessing this
+information, for targets without support, an empty list will be
+returned.
+@end defun
+
 One may add arbitrary attributes to @code{gdb.Corefile} objects in the
 usual Python way.  This is useful if, for example, one needs to do
 some extra record keeping associated with the corefile.
 @xref{choosing attribute names}, for guidance on selecting a suitable
 name for new attributes.
 
+The @code{Corefile.mapped_files ()} method returns a list of
+@code{gdb.CorefileMappedFile} objects.  Each of these objects
+represents a file that was fully, or partially, mapped into the
+processes address space when the core file was created.
+
+A @code{gdb.CorefileMappedFile} object has the following attributes:
+
+@defvar CorefileMappedFile.filename
+This read only attribute contains a non-empty string, the file name of
+the mapped file.
+@end defvar
+
+@defvar CorefileMappedFile.build_id
+This read only attribute contains a non-empty string or @code{None}.
+This is the build-id of the mapped file extracted from the core file,
+or @code{None} if there was no build-id, or @value{GDBN} was unable to
+extract the build-id.
+@end defvar
+
+@defvar CorefileMappedFile.is_main_executable
+This read only attribute is @code{True} if @value{GDBN} believes this
+mapping represents the main executable for which this core file was
+created.  This will be @code{False} for all other mappings.
+@end defvar
+
+@defvar CorefileMappedFile.regions
+This read only attribute contains a list of
+@code{gdb.CorefileMappedFileRegion} objects.  Each of these objects
+describes a region of the file that was mapped into the process when
+the core file was created, further details are given below.
+@end defvar
+
+The @code{gdb.CorefileMappedFileRegion} object describes which part of
+a file that was mapped into a process when the core file was created.
+
+A @code{gdb.CorefileMappedFile} object has the following attributes:
+
+@defvar CorefileMappedFileRegion.start
+This read only attribute contains the start address of this mapping
+within the inferior.
+@end defvar
+
+@defvar CorefileMappedFileRegion.end
+This read only attribute contains end address of this mapping within
+the inferior.
+@end defvar
+
+@defvar CorefileMappedFileRegion.file_offset
+This read only attribute contains the offset within the mapped file
+for this mapping.
+@end defvar
+
 @node Python Auto-loading
 @subsection Python Auto-loading
 @cindex Python auto-loading
index bda93fa13257828085a2a984e78b102341252b65..7c9a4478f243a3b404206f72584b70b2f40b63ed 100644 (file)
@@ -21,6 +21,8 @@
 #include "progspace.h"
 #include "observable.h"
 #include "inferior.h"
+#include "gdbcore.h"
+#include "gdbsupport/rsp-low.h"
 
 /* A gdb.Corefile object.  */
 
@@ -37,11 +39,61 @@ struct corefile_object
   /* Dictionary holding user-added attributes.  This is the __dict__
      attribute of the object.  This is an owning reference.  */
   PyObject *dict;
+
+  /* A Tuple of gdb.CorefileMappedFile objects.  This tuple is only created
+     the first time the user calls gdb.Corefile.mapped_files(), the result
+     is cached here.  If this pointer is not NULL then this is an owning
+     pointer (i.e. this owns a reference to the Tuple).  */
+  PyObject *mapped_files;
 };
 
 extern PyTypeObject corefile_object_type
     CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("corefile_object");
 
+/* A gdb.CorefileMapped object.  */
+
+struct corefile_mapped_file_object
+{
+  PyObject_HEAD
+
+  /* The name of a file that was mapped when the core file was created.
+     This is a 'str' object.  */
+  PyObject *filename;
+
+  /* The build-id of a file that was mapped when the core file was
+     created.  This is either a 'str' if the file had a build-id, or
+     'None' if there was no build-id for this file.  */
+  PyObject *build_id;
+
+  /* A List of gdb.CorefileMappedFileRegion objects.  */
+  PyObject *regions;
+
+  /* True if this represents the main executable from which the core file
+     was created.  */
+  bool is_main_exec_p;
+};
+
+extern PyTypeObject corefile_mapped_file_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("corefile_mapped_file_object");
+
+/* A gdb.CorefileMappedFileRegion object.  */
+
+struct corefile_mapped_file_region_object
+{
+  PyObject_HEAD
+
+  /* The start and end addresses for this mapping, these are addresses
+     within the inferior's address space.  */
+  CORE_ADDR start;
+  CORE_ADDR end;
+
+  /* The offset within the mapped file for this mapping.  */
+  ULONGEST file_offset;
+};
+
+extern PyTypeObject corefile_mapped_file_region_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("corefile_mapped_file_region_object");
+
 /* Clear the inferior pointer in a Corefile object OBJ when an inferior is
    deleted.  */
 
@@ -94,6 +146,7 @@ gdbpy_core_file_from_inferior (inferior *inf)
         cfpy_dealloc will be called, which requires that the 'inferior' be
         set to NULL.  */
       object->inferior = nullptr;
+      object->mapped_files = nullptr;
       object->dict = PyDict_New ();
       if (object->dict == nullptr)
        return nullptr;
@@ -168,6 +221,123 @@ cfpy_is_valid (PyObject *self, PyObject *args)
   Py_RETURN_TRUE;
 }
 
+/* Implement gdb.Corefile.mapped_files ().  Return a List of
+   gdb.CorefileMappedFile objects.  The list is created the first time
+   this method is called, and then cached within the gdb.Corefile object,
+   future calls just return a reference to the same list.  */
+
+static PyObject *
+cfpy_mapped_files (PyObject *self, PyObject *args)
+{
+  corefile_object *obj = (corefile_object *) self;
+
+  CFPY_REQUIRE_VALID (obj);
+
+  /* If we have already created the List then just return another reference
+     to the existing list.  */
+  if (obj->mapped_files != nullptr)
+    {
+      Py_INCREF (obj->mapped_files);
+      return obj->mapped_files;
+    }
+
+  /* Get all the mapping data from GDB.  */
+  std::vector<core_mapped_file> mapped_files;
+  try
+    {
+      mapped_files
+       = gdb_read_core_file_mappings (obj->inferior->arch (),
+                                      current_program_space->core_bfd ());
+    }
+  catch (const gdb_exception &except)
+    {
+      return gdbpy_handle_gdb_exception (nullptr, except);
+    }
+
+  /* Create a new list to hold the results.  */
+  gdbpy_ref<> tuple (PyTuple_New (mapped_files.size ()));
+  if (tuple == nullptr)
+    return nullptr;
+
+  /* Create each gdb.CorefileMappedFile object.  */
+  Py_ssize_t tuple_idx = 0;
+  for (const core_mapped_file &file : mapped_files)
+    {
+      /* The filename 'str' object.  */
+      gdbpy_ref<> filename
+       = host_string_to_python_string (file.filename.c_str ());
+      if (filename == nullptr)
+       return nullptr;
+
+      /* The build-id object.  Either a 'str' or 'None'.  */
+      gdbpy_ref<> build_id;
+      if (file.build_id != nullptr)
+       {
+         std::string hex_form = bin2hex (file.build_id->data,
+                                         file.build_id->size);
+
+         build_id
+           = host_string_to_python_string (hex_form.c_str ());
+         if (build_id == nullptr)
+           return nullptr;
+       }
+      else
+       build_id = gdbpy_ref<>::new_reference (Py_None);
+
+      /* List to hold all the gdb.CorefileMappedFileRegion objects.  */
+      gdbpy_ref<> regions (PyTuple_New (file.regions.size ()));
+      if (regions == nullptr)
+       return nullptr;
+
+      /* Create all the gdb.CorefileMappedFileRegion objects.  */
+      Py_ssize_t regions_idx = 0;
+      for (const core_mapped_file::region &r : file.regions)
+       {
+         /* Actually create the object.  */
+         gdbpy_ref<corefile_mapped_file_region_object> region_obj
+           (PyObject_New (corefile_mapped_file_region_object,
+                          &corefile_mapped_file_region_object_type));
+         if (region_obj == nullptr)
+           return nullptr;
+
+         /* Initialise the object.  */
+         region_obj->start = r.start;
+         region_obj->end = r.end;
+         region_obj->file_offset = r.file_ofs;
+
+         /* Add to the gdb.CorefileMappedFileRegion list.  */
+         if (PyTuple_SetItem (regions.get (), regions_idx++,
+                              (PyObject *) region_obj.release ()) < 0)
+           return nullptr;
+       }
+
+      /* Actually create the gdb.CorefileMappedFile object.  */
+      gdbpy_ref<corefile_mapped_file_object> entry
+       (PyObject_New (corefile_mapped_file_object,
+                      &corefile_mapped_file_object_type));
+      if (entry == nullptr)
+       return nullptr;
+
+      /* Initialise the object.  */
+      entry->filename = filename.release ();
+      entry->build_id = build_id.release ();
+      entry->regions = regions.release ();
+      entry->is_main_exec_p = file.is_main_exec;
+
+      /* Add to the gdb.CorefileMappedFile list.  */
+      if (PyTuple_SetItem (tuple.get (), tuple_idx++,
+                          (PyObject *) entry.release ()) < 0)
+       return nullptr;
+    }
+
+  /* No errors.  Move the reference currently in LIST into the Corefile
+     object itself.  Then create a new reference and hand this back to the
+     user.  */
+  obj->mapped_files = tuple.release ();
+  Py_INCREF (obj->mapped_files);
+  return obj->mapped_files;
+}
+
 /* Callback from gdb::observers::core_file_changed.  The core file in
    PSPACE has been changed.  */
 
@@ -191,6 +361,7 @@ cfpy_dealloc (PyObject *obj)
   gdb_assert (corefile->inferior == nullptr);
 
   Py_XDECREF (corefile->dict);
+  Py_XDECREF (corefile->mapped_files);
 
   Py_TYPE (obj)->tp_free (obj);
 }
@@ -215,6 +386,114 @@ cfpy_repr (PyObject *self)
 
 \f
 
+/* Called when a gdb.CorefileMappedFile is destroyed.  */
+
+static void
+cfmfpy_dealloc (PyObject *obj)
+{
+  corefile_mapped_file_object *mapped_file
+    = (corefile_mapped_file_object *) obj;
+
+  Py_XDECREF (mapped_file->filename);
+  Py_XDECREF (mapped_file->build_id);
+  Py_XDECREF (mapped_file->regions);
+
+  Py_TYPE (obj)->tp_free (obj);
+}
+
+/* Read the gdb.CorefileMappedFile.filename attribute.  */
+
+static PyObject *
+cfmfpy_get_filename (PyObject *self, void *closure)
+{
+  corefile_mapped_file_object *obj
+    = (corefile_mapped_file_object *) self;
+
+  gdb_assert (obj->filename != nullptr);
+
+  Py_INCREF (obj->filename);
+  return obj->filename;
+}
+
+/* Read the gdb.CorefileMappedFile.build_id attribute.  */
+
+static PyObject *
+cfmfpy_get_build_id (PyObject *self, void *closure)
+{
+  corefile_mapped_file_object *obj
+    = (corefile_mapped_file_object *) self;
+
+  gdb_assert (obj->build_id != nullptr);
+
+  Py_INCREF (obj->build_id);
+  return obj->build_id;
+}
+
+/* Read the gdb.CorefileMappedFile.regions attribute.  */
+
+static PyObject *
+cfmfpy_get_regions (PyObject *self, void *closure)
+{
+  corefile_mapped_file_object *obj
+    = (corefile_mapped_file_object *) self;
+
+  gdb_assert (obj->regions != nullptr);
+
+  Py_INCREF (obj->regions);
+  return obj->regions;
+}
+
+/* Read the gdb.CorefileMappedFile.is_main_executable attribute.  */
+
+static PyObject *
+cfmf_is_main_exec (PyObject *self, void *closure)
+{
+  corefile_mapped_file_object *obj
+    = (corefile_mapped_file_object *) self;
+
+  if (obj->is_main_exec_p)
+    Py_RETURN_TRUE;
+  else
+    Py_RETURN_FALSE;
+}
+
+\f
+
+/* Read the gdb.CorefileMappedFileRegion.start attribute.  */
+
+static PyObject *
+cfmfrpy_get_start (PyObject *self, void *closure)
+{
+  corefile_mapped_file_region_object *obj
+    = (corefile_mapped_file_region_object *) self;
+
+  return gdb_py_object_from_ulongest (obj->start).release ();
+}
+
+/* Read the gdb.CorefileMappedFileRegion.end attribute.  */
+
+static PyObject *
+cfmfrpy_get_end (PyObject *self, void *closure)
+{
+  corefile_mapped_file_region_object *obj
+    = (corefile_mapped_file_region_object *) self;
+
+  return gdb_py_object_from_ulongest (obj->end).release ();
+}
+
+/* Read the gdb.CorefileMappedFileRegion.file_offset attribute.  */
+
+static PyObject *
+cfmfrpy_get_file_offset (PyObject *self, void *closure)
+{
+  corefile_mapped_file_region_object *obj
+    = (corefile_mapped_file_region_object *) self;
+
+  return gdb_py_object_from_ulongest (obj->file_offset).release ();
+}
+
+\f
+
 static int
 gdbpy_initialize_corefile ()
 {
@@ -224,6 +503,12 @@ gdbpy_initialize_corefile ()
   if (gdbpy_type_ready (&corefile_object_type) < 0)
     return -1;
 
+  if (gdbpy_type_ready (&corefile_mapped_file_object_type) < 0)
+    return -1;
+
+  if (gdbpy_type_ready (&corefile_mapped_file_region_object_type) < 0)
+    return -1;
+
   return 0;
 }
 
@@ -245,6 +530,10 @@ static PyMethodDef corefile_object_methods[] =
   { "is_valid", cfpy_is_valid, METH_NOARGS,
     "is_valid () -> Boolean.\n\
 Return true if this Corefile is valid, false if not." },
+  { "mapped_files", cfpy_mapped_files, METH_NOARGS,
+    "mapped_files () -> List of mapping tuples.\n\
+Return a list of tuples.  Each tuple represents a mapping from the\
+core file." },
   { nullptr }
 };
 
@@ -289,3 +578,111 @@ PyTypeObject corefile_object_type =
   0,                             /* tp_alloc */
   0,                             /* tp_new */
 };
+
+static gdb_PyGetSetDef corefile_mapped_file_object_getset[] =
+{
+  { "filename", cfmfpy_get_filename, nullptr,
+    "The filename of a CorefileMappedFile object.", nullptr },
+  { "build_id", cfmfpy_get_build_id, nullptr,
+    "The build-id of a CorefileMappedFile object or None.", nullptr },
+  { "regions", cfmfpy_get_regions, nullptr,
+    "The list of regions from a CorefileMappedFile object.", nullptr },
+  { "is_main_executable", cfmf_is_main_exec, nullptr,
+    "True for the main executable mapping, otherwise False.", nullptr },
+  { nullptr }
+};
+
+PyTypeObject corefile_mapped_file_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.CorefileMappedFile",      /*tp_name*/
+  sizeof (corefile_mapped_file_object),        /*tp_basicsize*/
+  0,                             /*tp_itemsize*/
+  cfmfpy_dealloc,                /*tp_dealloc*/
+  0,                             /*tp_print*/
+  0,                             /*tp_getattr*/
+  0,                             /*tp_setattr*/
+  0,                             /*tp_compare*/
+  0,                             /*tp_repr*/
+  0,                             /*tp_as_number*/
+  0,                             /*tp_as_sequence*/
+  0,                             /*tp_as_mapping*/
+  0,                             /*tp_hash */
+  0,                             /*tp_call*/
+  0,                             /*tp_str*/
+  0,                             /*tp_getattro*/
+  0,                             /*tp_setattro*/
+  0,                             /*tp_as_buffer*/
+  Py_TPFLAGS_DEFAULT,            /*tp_flags*/
+  "GDB corefile mapped file object",   /* tp_doc */
+  0,                             /* tp_traverse */
+  0,                             /* tp_clear */
+  0,                             /* tp_richcompare */
+  0,                             /* tp_weaklistoffset */
+  0,                             /* tp_iter */
+  0,                             /* tp_iternext */
+  0,                             /* tp_methods */
+  0,                             /* tp_members */
+  corefile_mapped_file_object_getset,  /* tp_getset */
+  0,                             /* tp_base */
+  0,                             /* tp_dict */
+  0,                             /* tp_descr_get */
+  0,                             /* tp_descr_set */
+  0,                             /* tp_dictoffset */
+  0,                             /* tp_init */
+  0,                             /* tp_alloc */
+  0,                             /* tp_new */
+};
+
+static gdb_PyGetSetDef corefile_mapped_file_region_object_getset[] =
+{
+  { "start", cfmfrpy_get_start, nullptr,
+    "The start address of a CorefileMappedFileRegion object.", nullptr },
+  { "end", cfmfrpy_get_end, nullptr,
+    "The end address of a CorefileMappedFileRegion object.", nullptr },
+  { "file_offset", cfmfrpy_get_file_offset, nullptr,
+    "The file offset of a CorefileMappedFileRegion object.", nullptr },
+  { nullptr }
+};
+
+PyTypeObject corefile_mapped_file_region_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.CorefileMappedFileRegion",  /*tp_name*/
+  sizeof (corefile_mapped_file_region_object), /*tp_basicsize*/
+  0,                             /*tp_itemsize*/
+  0,                             /*tp_dealloc*/
+  0,                             /*tp_print*/
+  0,                             /*tp_getattr*/
+  0,                             /*tp_setattr*/
+  0,                             /*tp_compare*/
+  0,                             /*tp_repr*/
+  0,                             /*tp_as_number*/
+  0,                             /*tp_as_sequence*/
+  0,                             /*tp_as_mapping*/
+  0,                             /*tp_hash */
+  0,                             /*tp_call*/
+  0,                             /*tp_str*/
+  0,                             /*tp_getattro*/
+  0,                             /*tp_setattro*/
+  0,                             /*tp_as_buffer*/
+  Py_TPFLAGS_DEFAULT,            /*tp_flags*/
+  "GDB corefile mapped file region object",    /* tp_doc */
+  0,                             /* tp_traverse */
+  0,                             /* tp_clear */
+  0,                             /* tp_richcompare */
+  0,                             /* tp_weaklistoffset */
+  0,                             /* tp_iter */
+  0,                             /* tp_iternext */
+  0,                             /* tp_methods */
+  0,                             /* tp_members */
+  corefile_mapped_file_region_object_getset,   /* tp_getset */
+  0,                             /* tp_base */
+  0,                             /* tp_dict */
+  0,                             /* tp_descr_get */
+  0,                             /* tp_descr_set */
+  0,                             /* tp_dictoffset */
+  0,                             /* tp_init */
+  0,                             /* tp_alloc */
+  0,                             /* tp_new */
+};
index e9254bd9e7852b6e87e9152a030cc089678f4719..3b57cc0e250cd129a6065455b856f8e633b3ba36 100644 (file)
@@ -176,3 +176,61 @@ with_test_prefix "remove second inferior" {
     gdb_test "python print(core1.is_valid())" "^True" \
        "check inferior 1 core file is still valid"
 }
+
+# Test the Corefile.mapped_files() API.  The Python script that is
+# sourced here implements 'info proc mappings' in Python using the
+# mapped_files API.  The output from the built-in command, and the
+# Python command should be identical.
+with_test_prefix "test mapped files data" {
+    clean_restart
+
+    set remote_python_file \
+       [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+    # Load the Python script into GDB.
+    gdb_test "source $remote_python_file" "^Success" \
+       "source python script"
+
+    # Load the core file.
+    gdb_test "core-file $corefile" ".*" \
+       "load core file"
+
+    # Two files to write the output to.
+    set out_1 [standard_output_file ${gdb_test_file_name}-out-1.txt]
+    set out_2 [standard_output_file ${gdb_test_file_name}-out-2.txt]
+
+    # Run the built-in command, then the new Python command, capture
+    # the output.
+    gdb_test "pipe info proc mappings | tee $out_1" ".*" \
+       "capture built-in mappings output"
+    gdb_test "pipe info proc py-mappings | tee $out_2" ".*" \
+       "capture Python based mappings data"
+
+    # Check the output is identical.
+    gdb_test "shell diff -s $out_1 $out_2" \
+       "Files \[^\r\n\]+-out-1.txt and \[^\r\n\]+-out-2.txt are identical" \
+       "diff input and output one"
+
+    # Check build-ids within the core file mapping data.
+    gdb_test "check-build-ids" "^PASS"
+
+    # Check the is_main_executable flag in the mapping data.
+    gdb_test "check-main-executable" "^PASS"
+
+    # Check that the mapped files "list" is actually an immutable
+    # tuple.
+    gdb_test_no_output "python core = gdb.selected_inferior().corefile"
+    gdb_test_no_output "python mapped_files = core.mapped_files()"
+    gdb_test "python print(type(mapped_files))" \
+       "^<class 'tuple'>"
+    gdb_test "python mapped_files\[0\] = None" \
+       "'tuple' object does not support item assignment"
+    gdb_test "python print(mapped_files\[0\] is None)" "^False"
+
+    # And same for the list of regions for a mapped file.
+    gdb_test_no_output "python regions = mapped_files\[0\].regions"
+    gdb_test "python print(type(regions))" \
+       "^<class 'tuple'>"
+    gdb_test "python regions\[0\] = None" \
+       "'tuple' object does not support item assignment"
+}
diff --git a/gdb/testsuite/gdb.python/py-corefile.py b/gdb/testsuite/gdb.python/py-corefile.py
new file mode 100644 (file)
index 0000000..cffd037
--- /dev/null
@@ -0,0 +1,144 @@
+# 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/>.
+
+import pathlib
+
+
+class Mapping:
+    def __init__(self, mapping, region):
+        self._mapping = mapping
+        self._region = region
+
+    @property
+    def start(self):
+        return self._region.start
+
+    @property
+    def end(self):
+        return self._region.end
+
+    @property
+    def offset(self):
+        return self._region.file_offset
+
+    @property
+    def filename(self):
+        return self._mapping.filename
+
+
+def info_proc_mappings():
+    print("Mapped address spaces:")
+    print("")
+    format_str = "%-18s %-18s %-18s %-18s %s "
+    print(format_str % ("Start Addr", "End Addr", "Size", "Offset", "File"))
+
+    core = gdb.selected_inferior().corefile
+    mappings = core.mapped_files()
+
+    result = []
+    for m in mappings:
+        for r in m.regions:
+            result.append(Mapping(m, r))
+
+    result.sort(key=lambda x: x.start)
+    for r in result:
+        sz = r.end - r.start
+        print(
+            format_str
+            % (
+                "0x%016x" % r.start,
+                "0x%016x" % r.end,
+                "0x%-16x" % sz,
+                "0x%-16x" % r.offset,
+                "%s" % r.filename,
+            )
+        )
+
+
+class InfoProcPyMappings(gdb.Command):
+    def __init__(self):
+        gdb.Command.__init__(self, "info proc py-mappings", gdb.COMMAND_DATA)
+
+    def invoke(self, args, from_tty):
+        info_proc_mappings()
+
+
+InfoProcPyMappings()
+
+
+class CheckBuildIds(gdb.Command):
+    def __init__(self):
+        gdb.Command.__init__(self, "check-build-ids", gdb.COMMAND_DATA)
+
+    def invoke(self, args, from_tty):
+        inf = gdb.selected_inferior()
+        objfiles = inf.progspace.objfiles()
+
+        path_to_build_id = {}
+
+        for o in objfiles:
+            if not o.is_file or o.build_id is None:
+                continue
+            p = pathlib.Path(o.filename).resolve()
+            b = o.build_id
+            path_to_build_id[p] = b
+
+        count = 0
+        core_mapped_files = inf.corefile.mapped_files()
+        for m in core_mapped_files:
+            p = pathlib.Path(m.filename).resolve()
+            b = m.build_id
+
+            if p in path_to_build_id:
+                count += 1
+                assert path_to_build_id[p] == b, "build-id mismatch for %s" % p
+
+        assert count > 0, "no mapped files checked"
+
+        print("PASS")
+
+
+CheckBuildIds()
+
+
+class CheckMainExec(gdb.Command):
+    def __init__(self):
+        gdb.Command.__init__(self, "check-main-executable", gdb.COMMAND_DATA)
+
+    def invoke(self, args, from_tty):
+        inf = gdb.selected_inferior()
+        pspace = inf.progspace
+        exec_filename = pathlib.Path(pspace.executable_filename).resolve()
+
+        count = 0
+        core_mapped_files = inf.corefile.mapped_files()
+        for m in core_mapped_files:
+            if not m.is_main_executable:
+                continue
+
+            p = pathlib.Path(m.filename).resolve()
+
+            count += 1
+            assert exec_filename == p, "main exec filename mismatch"
+
+        assert count == 1, "invalid main executable count"
+
+        print("PASS")
+
+
+CheckMainExec()
+
+
+print("Success")