diff options
Diffstat (limited to 'gdb/python/py-corefile.c')
-rw-r--r-- | gdb/python/py-corefile.c | 684 |
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 */ +}; |