/* Python interface to blocks. Copyright (C) 2008-2024 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 . */ #include "block.h" #include "dictionary.h" #include "symtab.h" #include "python-internal.h" #include "objfiles.h" struct block_object { PyObject_HEAD /* The GDB block structure that represents a frame's code block. */ const struct block *block; /* The backing object file. There is no direct relationship in GDB between a block and an object file. When a block is created also store a pointer to the object file for later use. */ struct objfile *objfile; }; struct block_syms_iterator_object { PyObject_HEAD /* The block. */ const struct block *block; /* The iterator for that block. */ struct block_iterator iter; /* Has the iterator been initialized flag. */ int initialized_p; /* Pointer back to the original source block object. Needed to check if the block is still valid, and has not been invalidated when an object file has been freed. */ block_object *source; }; /* Require a valid block. All access to block_object->block should be gated by this call. */ #define BLPY_REQUIRE_VALID(block_obj, block) \ do { \ block = block_object_to_block (block_obj); \ if (block == NULL) \ { \ PyErr_SetString (PyExc_RuntimeError, \ _("Block is invalid.")); \ return NULL; \ } \ } while (0) /* Require a valid block. This macro is called during block iterator creation, and at each next call. */ #define BLPY_ITER_REQUIRE_VALID(block_obj) \ do { \ if (block_obj->block == NULL) \ { \ PyErr_SetString (PyExc_RuntimeError, \ _("Source block for iterator is invalid.")); \ return NULL; \ } \ } while (0) extern PyTypeObject block_syms_iterator_object_type CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("block_syms_iterator_object"); static const registry::key blpy_objfile_data_key; static PyObject * blpy_iter (PyObject *self) { block_syms_iterator_object *block_iter_obj; const struct block *block = NULL; BLPY_REQUIRE_VALID (self, block); block_iter_obj = PyObject_New (block_syms_iterator_object, &block_syms_iterator_object_type); if (block_iter_obj == NULL) return NULL; block_iter_obj->block = block; block_iter_obj->initialized_p = 0; Py_INCREF (self); block_iter_obj->source = (block_object *) self; return (PyObject *) block_iter_obj; } static PyObject * blpy_get_start (PyObject *self, void *closure) { const struct block *block = NULL; BLPY_REQUIRE_VALID (self, block); return gdb_py_object_from_ulongest (block->start ()).release (); } static PyObject * blpy_get_end (PyObject *self, void *closure) { const struct block *block = NULL; BLPY_REQUIRE_VALID (self, block); return gdb_py_object_from_ulongest (block->end ()).release (); } static PyObject * blpy_get_function (PyObject *self, void *closure) { struct symbol *sym; const struct block *block; BLPY_REQUIRE_VALID (self, block); sym = block->function (); if (sym) return symbol_to_symbol_object (sym); Py_RETURN_NONE; } static PyObject * blpy_get_superblock (PyObject *self, void *closure) { const struct block *block; const struct block *super_block; block_object *self_obj = (block_object *) self; BLPY_REQUIRE_VALID (self, block); super_block = block->superblock (); if (super_block) return block_to_block_object (super_block, self_obj->objfile); Py_RETURN_NONE; } /* Return the global block associated to this block. */ static PyObject * blpy_get_global_block (PyObject *self, void *closure) { const struct block *block; const struct block *global_block; block_object *self_obj = (block_object *) self; BLPY_REQUIRE_VALID (self, block); global_block = block->global_block (); return block_to_block_object (global_block, self_obj->objfile); } /* Return the static block associated to this block. Return None if we cannot get the static block (this is the global block). */ static PyObject * blpy_get_static_block (PyObject *self, void *closure) { const struct block *block; const struct block *static_block; block_object *self_obj = (block_object *) self; BLPY_REQUIRE_VALID (self, block); if (block->superblock () == NULL) Py_RETURN_NONE; static_block = block->static_block (); return block_to_block_object (static_block, self_obj->objfile); } /* Implementation of gdb.Block.is_global (self) -> Boolean. Returns True if this block object is a global block. */ static PyObject * blpy_is_global (PyObject *self, void *closure) { const struct block *block; BLPY_REQUIRE_VALID (self, block); if (block->superblock ()) Py_RETURN_FALSE; Py_RETURN_TRUE; } /* Implementation of gdb.Block.is_static (self) -> Boolean. Returns True if this block object is a static block. */ static PyObject * blpy_is_static (PyObject *self, void *closure) { const struct block *block; BLPY_REQUIRE_VALID (self, block); if (block->superblock () != NULL && block->superblock ()->superblock () == NULL) Py_RETURN_TRUE; Py_RETURN_FALSE; } /* Given a string, returns the gdb.Symbol representing that symbol in this block. If such a symbol does not exist, returns NULL with a Python exception. */ static PyObject * blpy_getitem (PyObject *self, PyObject *key) { const struct block *block; BLPY_REQUIRE_VALID (self, block); gdb::unique_xmalloc_ptr name = python_string_to_host_string (key); if (name == nullptr) return nullptr; lookup_name_info lookup_name (name.get(), symbol_name_match_type::FULL); /* We use an iterator instead of block_lookup_symbol so that we can look up symbols irrespective of the domain, matching the iterator. It would be confusing if the iterator returns symbols you can't find via getitem. */ for (struct symbol *sym : block_iterator_range (block, &lookup_name)) { /* Just stop at the first match */ return symbol_to_symbol_object (sym); } PyErr_SetObject (PyExc_KeyError, key); return nullptr; } /* Deleter function for the hash table. */ static void block_object_del (void *obj) { block_object *block = (block_object *) obj; block->block = nullptr; block->objfile = nullptr; } /* Hash function for the hash table. */ static hashval_t block_object_hash (const void *obj) { const block_object *block = (const block_object *) obj; return htab_hash_pointer (block->block); } /* Equality function for the hash table. Note that searches must be done with a plain block. */ static int block_object_eq (const void *a, const void *b) { const block_object *blocka = (const block_object *) a; const block *blockb = (const block *) b; return blocka->block == blockb; } /* Called when a gdb.Block is destroyed. This removes it from the hash. */ static void blpy_dealloc (PyObject *obj) { block_object *block = (block_object *) obj; if (block->objfile != nullptr) { htab_t table = blpy_objfile_data_key.get (block->objfile); hashval_t hash = block_object_hash (block); /* This will clear the contents of the block as a side effect. */ htab_remove_elt_with_hash (table, block->block, hash); } Py_TYPE (obj)->tp_free (obj); } /* Create a new block object (gdb.Block) that encapsulates the struct block object from GDB. */ PyObject * block_to_block_object (const struct block *block, struct objfile *objfile) { htab_t table = blpy_objfile_data_key.get (objfile); if (table == nullptr) { table = htab_create_alloc (10, block_object_hash, block_object_eq, block_object_del, xcalloc, xfree); blpy_objfile_data_key.set (objfile, table); } hashval_t hash = htab_hash_pointer (block); block_object *result = (block_object *) htab_find_with_hash (table, block, hash); if (result != nullptr) { PyObject *py_result = (PyObject *) result; Py_INCREF (py_result); return py_result; } result = PyObject_New (block_object, &block_object_type); result->block = block; result->objfile = objfile; void **slot = htab_find_slot_with_hash (table, block, hash, INSERT); *slot = result; return (PyObject *) result; } /* Return struct block reference that is wrapped by this object. */ const struct block * block_object_to_block (PyObject *obj) { if (! PyObject_TypeCheck (obj, &block_object_type)) return NULL; return ((block_object *) obj)->block; } /* Return a reference to the block iterator. */ static PyObject * blpy_block_syms_iter (PyObject *self) { block_syms_iterator_object *iter_obj = (block_syms_iterator_object *) self; BLPY_ITER_REQUIRE_VALID (iter_obj->source); Py_INCREF (self); return self; } /* Return the next symbol in the iteration through the block's dictionary. */ static PyObject * blpy_block_syms_iternext (PyObject *self) { block_syms_iterator_object *iter_obj = (block_syms_iterator_object *) self; struct symbol *sym; BLPY_ITER_REQUIRE_VALID (iter_obj->source); if (!iter_obj->initialized_p) { sym = block_iterator_first (iter_obj->block, &(iter_obj->iter)); iter_obj->initialized_p = 1; } else sym = block_iterator_next (&(iter_obj->iter)); if (sym == NULL) { PyErr_SetString (PyExc_StopIteration, _("Symbol is null.")); return NULL; } return symbol_to_symbol_object (sym); } static void blpy_block_syms_dealloc (PyObject *obj) { block_syms_iterator_object *iter_obj = (block_syms_iterator_object *) obj; Py_XDECREF (iter_obj->source); Py_TYPE (obj)->tp_free (obj); } /* Implementation of gdb.Block.is_valid (self) -> Boolean. Returns True if this block object still exists in GDB. */ static PyObject * blpy_is_valid (PyObject *self, PyObject *args) { const struct block *block; block = block_object_to_block (self); if (block == NULL) Py_RETURN_FALSE; Py_RETURN_TRUE; } /* Implementation of gdb.BlockIterator.is_valid (self) -> Boolean. Returns True if this block iterator object still exists in GDB */ static PyObject * blpy_iter_is_valid (PyObject *self, PyObject *args) { block_syms_iterator_object *iter_obj = (block_syms_iterator_object *) self; if (iter_obj->source->block == NULL) Py_RETURN_FALSE; Py_RETURN_TRUE; } /* __repr__ implementation for gdb.Block. */ static PyObject * blpy_repr (PyObject *self) { const auto block = block_object_to_block (self); if (block == nullptr) return gdb_py_invalid_object_repr (self); const auto name = block->function () ? block->function ()->print_name () : ""; std::string str; unsigned int written_symbols = 0; const int len = mdict_size (block->multidict ()); static constexpr int SYMBOLS_TO_SHOW = 5; for (struct symbol *symbol : block_iterator_range (block)) { if (written_symbols == SYMBOLS_TO_SHOW) { const int remaining = len - SYMBOLS_TO_SHOW; if (remaining == 1) str += string_printf ("... (%d more symbol)", remaining); else str += string_printf ("... (%d more symbols)", remaining); break; } str += symbol->print_name (); if (++written_symbols < len) str += ", "; } return PyUnicode_FromFormat ("<%s %s {%s}>", Py_TYPE (self)->tp_name, name, str.c_str ()); } /* Hash function for block objects. */ static Py_hash_t blpy_hash (PyObject *self) { /* Python doesn't really expose its pointer hash function, so we use our own. */ Py_hash_t result = (Py_hash_t) htab_hash_pointer (self); /* -1 has a special meaning for Python. */ if (result == -1) result = -2; return result; } /* Implements the equality comparison for Block objects. All other comparison operators will throw NotImplemented, as they aren't valid for blocks. */ static PyObject * blpy_richcompare (PyObject *self, PyObject *other, int op) { if (!PyObject_TypeCheck (other, &block_object_type) || (op != Py_EQ && op != Py_NE)) { Py_INCREF (Py_NotImplemented); return Py_NotImplemented; } bool expected = self == other; bool equal = op == Py_EQ; return PyBool_FromLong (equal == expected); } static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_blocks (void) { block_object_type.tp_new = PyType_GenericNew; if (gdbpy_type_ready (&block_object_type) < 0) return -1; block_syms_iterator_object_type.tp_new = PyType_GenericNew; if (gdbpy_type_ready (&block_syms_iterator_object_type) < 0) return -1; return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_blocks); static PyMethodDef block_object_methods[] = { { "is_valid", blpy_is_valid, METH_NOARGS, "is_valid () -> Boolean.\n\ Return true if this block is valid, false if not." }, {NULL} /* Sentinel */ }; static gdb_PyGetSetDef block_object_getset[] = { { "start", blpy_get_start, NULL, "Start address of the block.", NULL }, { "end", blpy_get_end, NULL, "End address of the block.", NULL }, { "function", blpy_get_function, NULL, "Symbol that names the block, or None.", NULL }, { "superblock", blpy_get_superblock, NULL, "Block containing the block, or None.", NULL }, { "global_block", blpy_get_global_block, NULL, "Block containing the global block.", NULL }, { "static_block", blpy_get_static_block, NULL, "Block containing the static block.", NULL }, { "is_static", blpy_is_static, NULL, "Whether this block is a static block.", NULL }, { "is_global", blpy_is_global, NULL, "Whether this block is a global block.", NULL }, { NULL } /* Sentinel */ }; static PyMappingMethods block_object_as_mapping = { NULL, blpy_getitem, NULL }; PyTypeObject block_object_type = { PyVarObject_HEAD_INIT (NULL, 0) "gdb.Block", /*tp_name*/ sizeof (block_object), /*tp_basicsize*/ 0, /*tp_itemsize*/ blpy_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ blpy_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ &block_object_as_mapping, /*tp_as_mapping*/ blpy_hash, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "GDB block object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ blpy_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ blpy_iter, /* tp_iter */ 0, /* tp_iternext */ block_object_methods, /* tp_methods */ 0, /* tp_members */ block_object_getset /* tp_getset */ }; static PyMethodDef block_iterator_object_methods[] = { { "is_valid", blpy_iter_is_valid, METH_NOARGS, "is_valid () -> Boolean.\n\ Return true if this block iterator is valid, false if not." }, {NULL} /* Sentinel */ }; PyTypeObject block_syms_iterator_object_type = { PyVarObject_HEAD_INIT (NULL, 0) "gdb.BlockIterator", /*tp_name*/ sizeof (block_syms_iterator_object), /*tp_basicsize*/ 0, /*tp_itemsize*/ blpy_block_syms_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 block syms iterator object", /*tp_doc */ 0, /*tp_traverse */ 0, /*tp_clear */ 0, /*tp_richcompare */ 0, /*tp_weaklistoffset */ blpy_block_syms_iter, /*tp_iter */ blpy_block_syms_iternext, /*tp_iternext */ block_iterator_object_methods /*tp_methods */ };