| /* 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 */ |
| }; |