aboutsummaryrefslogtreecommitdiff
path: root/gdb/python/py-corefile.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/python/py-corefile.c')
-rw-r--r--gdb/python/py-corefile.c684
1 files changed, 684 insertions, 0 deletions
diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c
new file mode 100644
index 0000000..97f4242
--- /dev/null
+++ b/gdb/python/py-corefile.c
@@ -0,0 +1,684 @@
+/* Python interface to core files.
+
+ Copyright (C) 2025 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "python-internal.h"
+#include "progspace.h"
+#include "observable.h"
+#include "inferior.h"
+#include "gdbcore.h"
+#include "gdbsupport/rsp-low.h"
+
+/* A gdb.Corefile object. */
+
+struct corefile_object
+{
+ PyObject_HEAD
+
+ /* The inferior this core file is attached to. This will be set to NULL
+ when the inferior is deleted, or if a different core file is loaded
+ for the inferior. When this is NULL the gdb.Corefile object is
+ considered invalid.*/
+ struct inferior *inferior;
+
+ /* 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. */
+
+struct inferior_corefile_deleter
+{
+ void operator() (corefile_object *obj)
+ {
+ if (!gdb_python_initialized)
+ return;
+
+ gdbpy_enter enter_py;
+
+ /* When OBJECT goes out of scope this will Py_DECREF on OBJ. */
+ gdbpy_ref<corefile_object> object (obj);
+
+ /* Clearing the inferior pointer marks the gdb.Corefile as invalid. */
+ object->inferior = nullptr;
+ }
+};
+
+/* Store a gdb.Corefile object in an inferior's registry. */
+
+static const registry<inferior>::key<corefile_object,
+ inferior_corefile_deleter>
+ cfpy_inferior_corefile_data_key;
+
+/* See python-internal.h. */
+
+gdbpy_ref<>
+gdbpy_core_file_from_inferior (inferior *inf)
+{
+ gdb_assert (inf != nullptr);
+ gdb_assert (inf->pspace != nullptr);
+
+ if (get_inferior_core_bfd (inf) == nullptr)
+ return gdbpy_ref<>::new_reference (Py_None);
+
+ PyObject *result = (PyObject *) cfpy_inferior_corefile_data_key.get (inf);
+ if (result == nullptr)
+ {
+ gdbpy_ref<corefile_object> object
+ (PyObject_New (corefile_object, &corefile_object_type));
+ if (object == nullptr)
+ return nullptr;
+
+ /* Ensure the 'inferior' field is set to NULL. If the PyDict_New
+ call fails then the gdb.Corefile will be discarded and
+ 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;
+
+ /* Now that the gdb.Corefile has been successfully initialised and we
+ know that it is going to be passed back to the user, move it out
+ of the invalid state by setting the 'inferior' field to a non NULL
+ value. */
+ object->inferior = inf;
+ cfpy_inferior_corefile_data_key.set (inf, object.get ());
+ result = (PyObject *) object.release ();
+ }
+
+ return gdbpy_ref<>::new_reference (result);
+}
+
+/* Return true if OBJ is valid. */
+
+static bool
+cfpy_corefile_object_is_valid (const corefile_object *obj)
+{
+ if (obj->inferior == nullptr)
+ return false;
+
+ return get_inferior_core_bfd (obj->inferior) != nullptr;
+}
+
+/* Require that COREFILE_OBJ be a valid core file. A valid core file
+ object has a valid program space, and the program space has a core file
+ loaded into it. */
+#define CFPY_REQUIRE_VALID(corefile_obj) \
+ do { \
+ if (!cfpy_corefile_object_is_valid (corefile_obj)) \
+ { \
+ PyErr_SetString (PyExc_RuntimeError, \
+ _("Corefile no longer exists.")); \
+ return nullptr; \
+ } \
+ } while (0)
+
+/* Read the gdb.Corefile.filename attribute. */
+
+static PyObject *
+cfpy_get_filename (PyObject *self, void *closure)
+{
+ corefile_object *obj = (corefile_object *) self;
+
+ CFPY_REQUIRE_VALID (obj);
+
+ /* If the program space's core file had been cleared, then this Corefile
+ object would have been invalidated. */
+ bfd *abfd = get_inferior_core_bfd (obj->inferior);
+ gdb_assert (abfd != nullptr);
+
+ return host_string_to_python_string (bfd_get_filename (abfd)).release ();
+}
+
+/* Implementation of gdb.Corefile.is_valid (self) -> Boolean.
+ Returns True if this core file object is associated with a program space
+ that still exists, an the program space still has a core file loaded. */
+
+static PyObject *
+cfpy_is_valid (PyObject *self, PyObject *args)
+{
+ corefile_object *obj = (corefile_object *) self;
+
+ if (!cfpy_corefile_object_is_valid (obj))
+ Py_RETURN_FALSE;
+
+ 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 (),
+ get_inferior_core_bfd (obj->inferior));
+ }
+ 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. */
+
+static void
+cfpy_corefile_changed (inferior *inf)
+{
+ cfpy_inferior_corefile_data_key.clear (inf);
+}
+
+/* Called when a gdb.Corefile is destroyed. */
+
+static void
+cfpy_dealloc (PyObject *obj)
+{
+ corefile_object *corefile = (corefile_object *) obj;
+
+ /* Every gdb.Corefile is cached in an inferior's registry. The only way
+ for a gdb.Corefile to be deallocated is to remove the object reference
+ from the registry (and dec its ref count), but before we do that, we
+ set the object's inferior pointer to NULL. */
+ gdb_assert (corefile->inferior == nullptr);
+
+ Py_XDECREF (corefile->dict);
+ Py_XDECREF (corefile->mapped_files);
+
+ Py_TYPE (obj)->tp_free (obj);
+}
+
+/* __repr__ implementation for gdb.Corefile. */
+
+static PyObject *
+cfpy_repr (PyObject *self)
+{
+ corefile_object *obj = (corefile_object *) self;
+
+ if (!cfpy_corefile_object_is_valid (obj))
+ return gdb_py_invalid_object_repr (self);
+
+ bfd *core_bfd = get_inferior_core_bfd (obj->inferior);
+ gdb_assert (core_bfd != nullptr);
+ return PyUnicode_FromFormat ("<%s inferior=%d filename='%s'>",
+ Py_TYPE (self)->tp_name,
+ obj->inferior->num,
+ bfd_get_filename (core_bfd));
+}
+
+
+
+/* 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;
+}
+
+
+
+/* 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 ();
+}
+
+
+
+static int
+gdbpy_initialize_corefile ()
+{
+ gdb::observers::core_file_changed.attach (cfpy_corefile_changed,
+ "py-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;
+}
+
+GDBPY_INITIALIZE_FILE (gdbpy_initialize_corefile);
+
+
+
+static gdb_PyGetSetDef corefile_getset[] =
+{
+ { "__dict__", gdb_py_generic_dict, nullptr,
+ "The __dict__ for the gdb.Corefile.", &corefile_object_type },
+ { "filename", cfpy_get_filename, nullptr,
+ "The filename of a valid Corefile object.", nullptr },
+ { nullptr }
+};
+
+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 }
+};
+
+PyTypeObject corefile_object_type =
+{
+ PyVarObject_HEAD_INIT (nullptr, 0)
+ "gdb.Corefile", /*tp_name*/
+ sizeof (corefile_object), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ cfpy_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ cfpy_repr, /*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 object", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ corefile_object_methods, /* tp_methods */
+ 0, /* tp_members */
+ corefile_getset, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ offsetof (corefile_object, dict), /* tp_dictoffset */
+ 0, /* tp_init */
+ 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 */
+};