| /* Python interface to instruction disassembly. | |
| Copyright (C) 2021-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 <http://www.gnu.org/licenses/>. */ | |
| #include "python-internal.h" | |
| #include "language.h" | |
| #include "dis-asm.h" | |
| #include "arch-utils.h" | |
| #include "charset.h" | |
| #include "disasm.h" | |
| #include "progspace.h" | |
| /* Implement gdb.disassembler.DisassembleInfo type. An object of this type | |
| represents a single disassembler request from GDB. */ | |
| struct disasm_info_object | |
| { | |
| PyObject_HEAD | |
| /* The architecture in which we are disassembling. */ | |
| struct gdbarch *gdbarch; | |
| /* The program_space in which we are disassembling. */ | |
| struct program_space *program_space; | |
| /* Address of the instruction to disassemble. */ | |
| bfd_vma address; | |
| /* The disassemble_info passed from core GDB, this contains the | |
| callbacks necessary to read the instruction from core GDB, and to | |
| print the disassembled instruction. */ | |
| disassemble_info *gdb_info; | |
| /* If copies of this object are created then they are chained together | |
| via this NEXT pointer, this allows all the copies to be invalidated at | |
| the same time as the parent object. */ | |
| struct disasm_info_object *next; | |
| }; | |
| extern PyTypeObject disasm_info_object_type | |
| CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("disasm_info_object"); | |
| /* Implement gdb.disassembler.DisassembleAddressPart type. An object of | |
| this type represents a small part of a disassembled instruction; a part | |
| that is an address that should be printed using a call to GDB's | |
| internal print_address function. */ | |
| struct disasm_addr_part_object | |
| { | |
| PyObject_HEAD | |
| /* The address to be formatted. */ | |
| bfd_vma address; | |
| /* A gdbarch. This is only needed in the case where the user asks for | |
| the DisassemblerAddressPart to be converted to a string. When we | |
| return this part to GDB within a DisassemblerResult then GDB will use | |
| the gdbarch from the initial disassembly request. */ | |
| struct gdbarch *gdbarch; | |
| }; | |
| extern PyTypeObject disasm_addr_part_object_type | |
| CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("disasm_addr_part_object"); | |
| /* Implement gdb.disassembler.DisassembleTextPart type. An object of | |
| this type represents a small part of a disassembled instruction; a part | |
| that is a piece of test along with an associated style. */ | |
| struct disasm_text_part_object | |
| { | |
| PyObject_HEAD | |
| /* The string that is this part. */ | |
| std::string *string; | |
| /* The style to use when displaying this part. */ | |
| enum disassembler_style style; | |
| }; | |
| extern PyTypeObject disasm_text_part_object_type | |
| CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("disasm_text_part_object"); | |
| extern PyTypeObject disasm_part_object_type | |
| CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("PyObject"); | |
| /* Implement gdb.disassembler.DisassemblerResult type, an object that holds | |
| the result of calling the disassembler. This is mostly the length of | |
| the disassembled instruction (in bytes), and the string representing the | |
| disassembled instruction. */ | |
| struct disasm_result_object | |
| { | |
| PyObject_HEAD | |
| /* The length of the disassembled instruction in bytes. */ | |
| int length; | |
| /* A vector containing all the parts of the disassembled instruction. | |
| Each part will be a DisassemblerPart sub-class. */ | |
| std::vector<gdbpy_ref<>> *parts; | |
| }; | |
| extern PyTypeObject disasm_result_object_type | |
| CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("disasm_result_object"); | |
| /* When this is false we fast path out of gdbpy_print_insn, which should | |
| keep the performance impact of the Python disassembler down. This is | |
| set to true from Python by calling gdb.disassembler._set_enabled() when | |
| the user registers a disassembler. */ | |
| static bool python_print_insn_enabled = false; | |
| /* A sub-class of gdb_disassembler that holds a pointer to a Python | |
| DisassembleInfo object. A pointer to an instance of this class is | |
| placed in the application_data field of the disassemble_info that is | |
| used when we call gdbarch_print_insn. */ | |
| struct gdbpy_disassembler : public gdb_disassemble_info | |
| { | |
| /* Constructor. */ | |
| gdbpy_disassembler (disasm_info_object *obj, PyObject *memory_source); | |
| /* Get the DisassembleInfo object pointer. */ | |
| disasm_info_object * | |
| py_disasm_info () const | |
| { | |
| return m_disasm_info_object; | |
| } | |
| /* Callbacks used by disassemble_info. */ | |
| static void memory_error_func (int status, bfd_vma memaddr, | |
| struct disassemble_info *info) noexcept; | |
| static void print_address_func (bfd_vma addr, | |
| struct disassemble_info *info) noexcept; | |
| static int read_memory_func (bfd_vma memaddr, gdb_byte *buff, | |
| unsigned int len, | |
| struct disassemble_info *info) noexcept; | |
| /* Callback used as the disassemble_info's fprintf_func callback. The | |
| DIS_INFO pointer is a pointer to a gdbpy_disassembler object. */ | |
| static int fprintf_func (void *dis_info, const char *format, ...) noexcept | |
| ATTRIBUTE_PRINTF(2,3); | |
| /* Callback used as the disassemble_info's fprintf_styled_func callback. | |
| The DIS_INFO pointer is a pointer to a gdbpy_disassembler. */ | |
| static int fprintf_styled_func (void *dis_info, | |
| enum disassembler_style style, | |
| const char *format, ...) noexcept | |
| ATTRIBUTE_PRINTF(3,4); | |
| /* Helper used by fprintf_func and fprintf_styled_func. This function | |
| creates a new DisassemblerTextPart and adds it to the disassembler's | |
| parts list. The actual disassembler is accessed through DIS_INFO, | |
| which is a pointer to the gdbpy_disassembler object. */ | |
| static int vfprintf_styled_func (void *dis_info, | |
| enum disassembler_style style, | |
| const char *format, va_list args) noexcept | |
| ATTRIBUTE_PRINTF(3,0); | |
| /* Return a reference to an optional that contains the address at which a | |
| memory error occurred. The optional will only have a value if a | |
| memory error actually occurred. */ | |
| const std::optional<CORE_ADDR> &memory_error_address () const | |
| { return m_memory_error_address; } | |
| /* Return the content of the disassembler as a string. The contents are | |
| moved out of the disassembler, so after this call the disassembler | |
| contents have been reset back to empty. */ | |
| std::vector<gdbpy_ref<>> release () | |
| { | |
| return std::move (m_parts); | |
| } | |
| /* If there is a Python exception stored in this disassembler then | |
| restore it (i.e. set the PyErr_* state), clear the exception within | |
| this disassembler, and return true. There must be no current | |
| exception set (i.e. !PyErr_Occurred()) when this function is called, | |
| as any such exception might get lost. | |
| Otherwise, there is no exception stored in this disassembler, return | |
| false. */ | |
| bool restore_exception () | |
| { | |
| gdb_assert (!PyErr_Occurred ()); | |
| if (m_stored_exception.has_value ()) | |
| { | |
| gdbpy_err_fetch ex = std::move (*m_stored_exception); | |
| m_stored_exception.reset (); | |
| ex.restore (); | |
| return true; | |
| } | |
| return false; | |
| } | |
| private: | |
| /* The list of all the parts that make up this disassembled instruction. | |
| This is populated as a result of the callbacks from libopcodes as the | |
| instruction is disassembled. */ | |
| std::vector<gdbpy_ref<>> m_parts; | |
| /* The DisassembleInfo object we are disassembling for. */ | |
| disasm_info_object *m_disasm_info_object; | |
| /* When the user indicates that a memory error has occurred then the | |
| address of the memory error is stored in here. */ | |
| std::optional<CORE_ADDR> m_memory_error_address; | |
| /* When the user calls the builtin_disassemble function, if they pass a | |
| memory source object then a pointer to the object is placed in here, | |
| otherwise, this field is nullptr. */ | |
| PyObject *m_memory_source; | |
| /* Move the exception EX into this disassembler object. */ | |
| void store_exception (gdbpy_err_fetch &&ex) | |
| { | |
| /* The only calls to store_exception are from read_memory_func, which | |
| will return early if there's already an exception stored. */ | |
| gdb_assert (!m_stored_exception.has_value ()); | |
| m_stored_exception.emplace (std::move (ex)); | |
| } | |
| /* Return true if there is an exception stored in this disassembler. */ | |
| bool has_stored_exception () const | |
| { | |
| return m_stored_exception.has_value (); | |
| } | |
| /* Store a single exception. This is used to pass Python exceptions back | |
| from ::memory_read to disasmpy_builtin_disassemble. */ | |
| std::optional<gdbpy_err_fetch> m_stored_exception; | |
| }; | |
| /* Return true if OBJ is still valid, otherwise, return false. A valid OBJ | |
| will have a non-nullptr gdb_info field. */ | |
| static bool | |
| disasm_info_object_is_valid (disasm_info_object *obj) | |
| { | |
| return obj->gdb_info != nullptr; | |
| } | |
| /* Fill in OBJ with all the other arguments. */ | |
| static void | |
| disasm_info_fill (disasm_info_object *obj, struct gdbarch *gdbarch, | |
| program_space *progspace, bfd_vma address, | |
| disassemble_info *di, disasm_info_object *next) | |
| { | |
| obj->gdbarch = gdbarch; | |
| obj->program_space = progspace; | |
| obj->address = address; | |
| obj->gdb_info = di; | |
| obj->next = next; | |
| } | |
| /* Implement DisassembleInfo.__init__. Takes a single argument that must | |
| be another DisassembleInfo object and copies the contents from the | |
| argument into this new object. */ | |
| static int | |
| disasm_info_init (PyObject *self, PyObject *args, PyObject *kwargs) | |
| { | |
| static const char *keywords[] = { "info", NULL }; | |
| PyObject *info_obj; | |
| if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "O!", keywords, | |
| &disasm_info_object_type, | |
| &info_obj)) | |
| return -1; | |
| disasm_info_object *other = (disasm_info_object *) info_obj; | |
| disasm_info_object *info = (disasm_info_object *) self; | |
| disasm_info_fill (info, other->gdbarch, other->program_space, | |
| other->address, other->gdb_info, other->next); | |
| other->next = info; | |
| /* As the OTHER object now holds a pointer to INFO we inc the ref count | |
| on INFO. This stops INFO being deleted until OTHER has gone away. */ | |
| Py_INCREF ((PyObject *) info); | |
| return 0; | |
| } | |
| /* The tp_dealloc callback for the DisassembleInfo type. */ | |
| static void | |
| disasm_info_dealloc (PyObject *self) | |
| { | |
| disasm_info_object *obj = (disasm_info_object *) self; | |
| /* We no longer care about the object our NEXT pointer points at, so we | |
| can decrement its reference count. This macro handles the case when | |
| NEXT is nullptr. */ | |
| Py_XDECREF ((PyObject *) obj->next); | |
| /* Now core deallocation behaviour. */ | |
| Py_TYPE (self)->tp_free (self); | |
| } | |
| /* Implement __repr__ for the DisassembleInfo type. */ | |
| static PyObject * | |
| disasmpy_info_repr (PyObject *self) | |
| { | |
| disasm_info_object *obj = (disasm_info_object *) self; | |
| const char *arch_name | |
| = (gdbarch_bfd_arch_info (obj->gdbarch))->printable_name; | |
| return PyUnicode_FromFormat ("<%s address=%s architecture=%s>", | |
| Py_TYPE (obj)->tp_name, | |
| core_addr_to_string_nz (obj->address), | |
| arch_name); | |
| } | |
| /* Implement DisassembleInfo.is_valid(), really just a wrapper around the | |
| disasm_info_object_is_valid function above. */ | |
| static PyObject * | |
| disasmpy_info_is_valid (PyObject *self, PyObject *args) | |
| { | |
| disasm_info_object *disasm_obj = (disasm_info_object *) self; | |
| if (disasm_info_object_is_valid (disasm_obj)) | |
| Py_RETURN_TRUE; | |
| Py_RETURN_FALSE; | |
| } | |
| /* Set the Python exception to be a gdb.MemoryError object, with ADDRESS | |
| as its payload. */ | |
| static void | |
| disasmpy_set_memory_error_for_address (CORE_ADDR address) | |
| { | |
| PyObject *address_obj = gdb_py_object_from_longest (address).release (); | |
| PyErr_SetObject (gdbpy_gdb_memory_error, address_obj); | |
| } | |
| /* Create a new DisassemblerTextPart and return a gdbpy_ref wrapper for | |
| the new object. STR is the string content of the part and STYLE is the | |
| style to be used when GDB displays this part. */ | |
| static gdbpy_ref<> | |
| make_disasm_text_part (std::string &&str, enum disassembler_style style) | |
| { | |
| PyTypeObject *type = &disasm_text_part_object_type; | |
| disasm_text_part_object *text_part | |
| = (disasm_text_part_object *) type->tp_alloc (type, 0); | |
| text_part->string = new std::string (str); | |
| text_part->style = style; | |
| return gdbpy_ref<> ((PyObject *) text_part); | |
| } | |
| /* Create a new DisassemblerAddressPart and return a gdbpy_ref wrapper for | |
| the new object. GDBARCH is the architecture used when formatting the | |
| address, and ADDRESS is the numerical address to be displayed. */ | |
| static gdbpy_ref<> | |
| make_disasm_addr_part (struct gdbarch *gdbarch, CORE_ADDR address) | |
| { | |
| PyTypeObject *type = &disasm_addr_part_object_type; | |
| disasm_addr_part_object *addr_part | |
| = (disasm_addr_part_object *) type->tp_alloc (type, 0); | |
| addr_part->address = address; | |
| addr_part->gdbarch = gdbarch; | |
| return gdbpy_ref<> ((PyObject *) addr_part); | |
| } | |
| /* Ensure that a gdb.disassembler.DisassembleInfo is valid. */ | |
| #define DISASMPY_DISASM_INFO_REQUIRE_VALID(Info) \ | |
| do { \ | |
| if (!disasm_info_object_is_valid (Info)) \ | |
| { \ | |
| PyErr_SetString (PyExc_RuntimeError, \ | |
| _("DisassembleInfo is no longer valid.")); \ | |
| return nullptr; \ | |
| } \ | |
| } while (0) | |
| /* Implement DisassembleInfo.text_part method. Creates and returns a new | |
| DisassemblerTextPart object. */ | |
| static PyObject * | |
| disasmpy_info_make_text_part (PyObject *self, PyObject *args, | |
| PyObject *kwargs) | |
| { | |
| disasm_info_object *obj = (disasm_info_object *) self; | |
| DISASMPY_DISASM_INFO_REQUIRE_VALID (obj); | |
| static const char *keywords[] = { "style", "string", NULL }; | |
| int style_num; | |
| const char *string; | |
| if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "is", keywords, | |
| &style_num, &string)) | |
| return nullptr; | |
| if (style_num < 0 || style_num > ((int) dis_style_comment_start)) | |
| { | |
| PyErr_SetString (PyExc_ValueError, | |
| _("Invalid disassembler style.")); | |
| return nullptr; | |
| } | |
| if (strlen (string) == 0) | |
| { | |
| PyErr_SetString (PyExc_ValueError, | |
| _("String must not be empty.")); | |
| return nullptr; | |
| } | |
| gdbpy_ref<> text_part | |
| = make_disasm_text_part (std::string (string), | |
| (enum disassembler_style) style_num); | |
| return text_part.release (); | |
| } | |
| /* Implement DisassembleInfo.address_part method. Creates and returns a | |
| new DisassemblerAddressPart object. */ | |
| static PyObject * | |
| disasmpy_info_make_address_part (PyObject *self, PyObject *args, | |
| PyObject *kwargs) | |
| { | |
| disasm_info_object *obj = (disasm_info_object *) self; | |
| DISASMPY_DISASM_INFO_REQUIRE_VALID (obj); | |
| static const char *keywords[] = { "address", NULL }; | |
| CORE_ADDR address; | |
| PyObject *address_object; | |
| if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "O", keywords, | |
| &address_object)) | |
| return nullptr; | |
| if (get_addr_from_python (address_object, &address) < 0) | |
| return nullptr; | |
| return make_disasm_addr_part (obj->gdbarch, address).release (); | |
| } | |
| /* Return a string representation of TEXT_PART. The returned string does | |
| not include any styling. */ | |
| static std::string | |
| disasmpy_part_to_string (const disasm_text_part_object *text_part) | |
| { | |
| gdb_assert (text_part->string != nullptr); | |
| return *(text_part->string); | |
| } | |
| /* Return a string representation of ADDR_PART. The returned string does | |
| not include any styling. */ | |
| static std::string | |
| disasmpy_part_to_string (const disasm_addr_part_object *addr_part) | |
| { | |
| string_file buf; | |
| print_address (addr_part->gdbarch, addr_part->address, &buf); | |
| return buf.release (); | |
| } | |
| /* PARTS is a vector of Python objects, each is a sub-class of | |
| DisassemblerPart. Create a string by concatenating the string | |
| representation of each part, and return this new string. | |
| Converting an address part requires that we call back into GDB core, | |
| which could throw an exception. As such, calls to this function should | |
| be wrapped with a try/catch. */ | |
| static std::string | |
| disasmpy_parts_list_to_string (const std::vector<gdbpy_ref<>> &parts) | |
| { | |
| std::string str; | |
| for (auto p : parts) | |
| { | |
| if (Py_TYPE (p.get ()) == &disasm_text_part_object_type) | |
| { | |
| disasm_text_part_object *text_part | |
| = (disasm_text_part_object *) p.get (); | |
| str += disasmpy_part_to_string (text_part); | |
| } | |
| else | |
| { | |
| gdb_assert (Py_TYPE (p.get ()) == &disasm_addr_part_object_type); | |
| disasm_addr_part_object *addr_part | |
| = (disasm_addr_part_object *) p.get (); | |
| str += disasmpy_part_to_string (addr_part); | |
| } | |
| } | |
| return str; | |
| } | |
| /* Initialise OBJ, a DisassemblerResult object with LENGTH and PARTS. | |
| OBJ might already have been initialised, in which case any existing | |
| content should be discarded before the new PARTS are moved in. */ | |
| static void | |
| disasmpy_init_disassembler_result (disasm_result_object *obj, int length, | |
| std::vector<gdbpy_ref<>> &&parts) | |
| { | |
| if (obj->parts == nullptr) | |
| obj->parts = new std::vector<gdbpy_ref<>>; | |
| else | |
| obj->parts->clear (); | |
| obj->length = length; | |
| *(obj->parts) = std::move (parts); | |
| } | |
| /* Implement gdb.disassembler.builtin_disassemble(). Calls back into GDB's | |
| builtin disassembler. The first argument is a DisassembleInfo object | |
| describing what to disassemble. The second argument is optional and | |
| provides a mechanism to modify the memory contents that the builtin | |
| disassembler will actually disassemble. | |
| Returns an instance of gdb.disassembler.DisassemblerResult, an object | |
| that wraps a disassembled instruction, or it raises a | |
| gdb.MemoryError. */ | |
| static PyObject * | |
| disasmpy_builtin_disassemble (PyObject *self, PyObject *args, PyObject *kw) | |
| { | |
| PyObject *info_obj, *memory_source_obj = nullptr; | |
| static const char *keywords[] = { "info", "memory_source", nullptr }; | |
| if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O!|O", keywords, | |
| &disasm_info_object_type, &info_obj, | |
| &memory_source_obj)) | |
| return nullptr; | |
| disasm_info_object *disasm_info = (disasm_info_object *) info_obj; | |
| DISASMPY_DISASM_INFO_REQUIRE_VALID (disasm_info); | |
| /* Where the result will be written. */ | |
| gdbpy_disassembler disassembler (disasm_info, memory_source_obj); | |
| /* Now actually perform the disassembly. LENGTH is set to the length of | |
| the disassembled instruction, or -1 if there was a memory-error | |
| encountered while disassembling. See below more more details on | |
| handling of -1 return value. */ | |
| int length = gdbarch_print_insn (disasm_info->gdbarch, disasm_info->address, | |
| disassembler.disasm_info ()); | |
| /* It is possible that, while calling a user overridden memory read | |
| function, a Python exception was raised that couldn't be | |
| translated into a standard memory-error. In this case the first such | |
| exception is stored in the disassembler and restored here. */ | |
| if (disassembler.restore_exception ()) | |
| return nullptr; | |
| if (length == -1) | |
| { | |
| /* In an ideal world, every disassembler should always call the | |
| memory error function before returning a status of -1 as the only | |
| error a disassembler should encounter is a failure to read | |
| memory. Unfortunately, there are some disassemblers who don't | |
| follow this rule, and will return -1 without calling the memory | |
| error function. | |
| To make the Python API simpler, we just classify everything as a | |
| memory error, but the message has to be modified for the case | |
| where the disassembler didn't call the memory error function. */ | |
| if (disassembler.memory_error_address ().has_value ()) | |
| { | |
| CORE_ADDR addr = *disassembler.memory_error_address (); | |
| disasmpy_set_memory_error_for_address (addr); | |
| } | |
| else | |
| { | |
| auto content = disassembler.release (); | |
| std::string str; | |
| try | |
| { | |
| str = disasmpy_parts_list_to_string (content); | |
| } | |
| catch (const gdb_exception &except) | |
| { | |
| GDB_PY_HANDLE_EXCEPTION (except); | |
| } | |
| if (!str.empty ()) | |
| PyErr_SetString (gdbpy_gdberror_exc, str.c_str ()); | |
| else | |
| PyErr_SetString (gdbpy_gdberror_exc, | |
| _("Unknown disassembly error.")); | |
| } | |
| return nullptr; | |
| } | |
| /* Instructions are either non-zero in length, or we got an error, | |
| indicated by a length of -1, which we handled above. */ | |
| gdb_assert (length > 0); | |
| /* We should not have seen a memory error in this case. */ | |
| gdb_assert (!disassembler.memory_error_address ().has_value ()); | |
| /* Create a DisassemblerResult containing the results. */ | |
| PyTypeObject *type = &disasm_result_object_type; | |
| gdbpy_ref<disasm_result_object> res | |
| ((disasm_result_object *) type->tp_alloc (type, 0)); | |
| auto content = disassembler.release (); | |
| disasmpy_init_disassembler_result (res.get (), length, std::move (content)); | |
| return reinterpret_cast<PyObject *> (res.release ()); | |
| } | |
| /* Implement gdb._set_enabled function. Takes a boolean parameter, and | |
| sets whether GDB should enter the Python disassembler code or not. | |
| This is called from within the Python code when a new disassembler is | |
| registered. When no disassemblers are registered the global C++ flag | |
| is set to false, and GDB never even enters the Python environment to | |
| check for a disassembler. | |
| When the user registers a new Python disassembler, the global C++ flag | |
| is set to true, and now GDB will enter the Python environment to check | |
| if there's a disassembler registered for the current architecture. */ | |
| static PyObject * | |
| disasmpy_set_enabled (PyObject *self, PyObject *args, PyObject *kw) | |
| { | |
| PyObject *newstate; | |
| static const char *keywords[] = { "state", nullptr }; | |
| if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O", keywords, | |
| &newstate)) | |
| return nullptr; | |
| if (!PyBool_Check (newstate)) | |
| { | |
| PyErr_SetString (PyExc_TypeError, | |
| _("The value passed to `_set_enabled' must be a boolean.")); | |
| return nullptr; | |
| } | |
| python_print_insn_enabled = PyObject_IsTrue (newstate); | |
| Py_RETURN_NONE; | |
| } | |
| /* Implement DisassembleInfo.read_memory(LENGTH, OFFSET). Read LENGTH | |
| bytes at OFFSET from the start of the instruction currently being | |
| disassembled, and return a memory buffer containing the bytes. | |
| OFFSET defaults to zero if it is not provided. LENGTH is required. If | |
| the read fails then this will raise a gdb.MemoryError exception. */ | |
| static PyObject * | |
| disasmpy_info_read_memory (PyObject *self, PyObject *args, PyObject *kw) | |
| { | |
| disasm_info_object *obj = (disasm_info_object *) self; | |
| DISASMPY_DISASM_INFO_REQUIRE_VALID (obj); | |
| gdb_py_longest length, offset = 0; | |
| gdb::unique_xmalloc_ptr<gdb_byte> buffer; | |
| static const char *keywords[] = { "length", "offset", nullptr }; | |
| if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, | |
| GDB_PY_LL_ARG "|" GDB_PY_LL_ARG, | |
| keywords, &length, &offset)) | |
| return nullptr; | |
| /* The apparent address from which we are reading memory. Note that in | |
| some cases GDB actually disassembles instructions from a buffer, so | |
| we might not actually be reading this information directly from the | |
| inferior memory. This is all hidden behind the read_memory_func API | |
| within the disassemble_info structure. */ | |
| CORE_ADDR address = obj->address + offset; | |
| /* Setup a buffer to hold the result. */ | |
| buffer.reset ((gdb_byte *) xmalloc (length)); | |
| /* Read content into BUFFER. If the read fails then raise a memory | |
| error, otherwise, convert BUFFER to a Python memory buffer, and return | |
| it to the user. */ | |
| disassemble_info *info = obj->gdb_info; | |
| if (info->read_memory_func ((bfd_vma) address, buffer.get (), | |
| (unsigned int) length, info) != 0) | |
| { | |
| disasmpy_set_memory_error_for_address (address); | |
| return nullptr; | |
| } | |
| return gdbpy_buffer_to_membuf (std::move (buffer), address, length); | |
| } | |
| /* Implement DisassembleInfo.address attribute, return the address at which | |
| GDB would like an instruction disassembled. */ | |
| static PyObject * | |
| disasmpy_info_address (PyObject *self, void *closure) | |
| { | |
| disasm_info_object *obj = (disasm_info_object *) self; | |
| DISASMPY_DISASM_INFO_REQUIRE_VALID (obj); | |
| return gdb_py_object_from_longest (obj->address).release (); | |
| } | |
| /* Implement DisassembleInfo.architecture attribute. Return the | |
| gdb.Architecture in which we are disassembling. */ | |
| static PyObject * | |
| disasmpy_info_architecture (PyObject *self, void *closure) | |
| { | |
| disasm_info_object *obj = (disasm_info_object *) self; | |
| DISASMPY_DISASM_INFO_REQUIRE_VALID (obj); | |
| return gdbarch_to_arch_object (obj->gdbarch); | |
| } | |
| /* Implement DisassembleInfo.progspace attribute. Return the | |
| gdb.Progspace in which we are disassembling. */ | |
| static PyObject * | |
| disasmpy_info_progspace (PyObject *self, void *closure) | |
| { | |
| disasm_info_object *obj = (disasm_info_object *) self; | |
| DISASMPY_DISASM_INFO_REQUIRE_VALID (obj); | |
| return pspace_to_pspace_object (obj->program_space).release (); | |
| } | |
| /* Helper function called when the libopcodes disassembler produces some | |
| output. FORMAT and ARGS are used to create a string which GDB will | |
| display using STYLE. The string is either added as a new | |
| DisassemblerTextPart to the list of parts being built in the current | |
| gdbpy_disassembler object (accessed through DIS_INFO). Or, if the last | |
| part in the gdbpy_disassembler is a text part in the same STYLE, then | |
| the new string is appended to the previous part. | |
| The merging behaviour make the Python API a little more user friendly, | |
| some disassemblers produce their output character at a time, there's no | |
| particular reason for this, it's just how they are implemented. By | |
| merging parts with the same style we make it easier for the user to | |
| analyse the disassembler output. */ | |
| int | |
| gdbpy_disassembler::vfprintf_styled_func (void *dis_info, | |
| enum disassembler_style style, | |
| const char *format, | |
| va_list args) noexcept | |
| { | |
| gdb_disassemble_info *di = (gdb_disassemble_info *) dis_info; | |
| gdbpy_disassembler *dis | |
| = gdb::checked_static_cast<gdbpy_disassembler *> (di); | |
| if (!dis->m_parts.empty () | |
| && Py_TYPE (dis->m_parts.back ().get ()) == &disasm_text_part_object_type | |
| && (((disasm_text_part_object *) dis->m_parts.back ().get ())->style | |
| == style)) | |
| { | |
| std::string *string | |
| = ((disasm_text_part_object *) dis->m_parts.back ().get ())->string; | |
| string_vappendf (*string, format, args); | |
| } | |
| else | |
| { | |
| std::string str = string_vprintf (format, args); | |
| if (str.size () > 0) | |
| { | |
| gdbpy_ref<> text_part | |
| = make_disasm_text_part (std::move (str), style); | |
| dis->m_parts.emplace_back (std::move (text_part)); | |
| } | |
| } | |
| /* Something non -ve. */ | |
| return 0; | |
| } | |
| /* Disassembler callback for architectures where libopcodes doesn't | |
| created styled output. In these cases we format all the output using | |
| the (default) text style. */ | |
| int | |
| gdbpy_disassembler::fprintf_func (void *dis_info, | |
| const char *format, ...) noexcept | |
| { | |
| va_list args; | |
| va_start (args, format); | |
| vfprintf_styled_func (dis_info, dis_style_text, format, args); | |
| va_end (args); | |
| /* Something non -ve. */ | |
| return 0; | |
| } | |
| /* Disassembler callback for architectures where libopcodes does create | |
| styled output. Just creates a new text part with the given STYLE. */ | |
| int | |
| gdbpy_disassembler::fprintf_styled_func (void *dis_info, | |
| enum disassembler_style style, | |
| const char *format, ...) noexcept | |
| { | |
| va_list args; | |
| va_start (args, format); | |
| vfprintf_styled_func (dis_info, style, format, args); | |
| va_end (args); | |
| /* Something non -ve. */ | |
| return 0; | |
| } | |
| /* This implements the disassemble_info read_memory_func callback and is | |
| called from the libopcodes disassembler when the disassembler wants to | |
| read memory. | |
| From the INFO argument we can find the gdbpy_disassembler object for | |
| which we are disassembling, and from that object we can find the | |
| DisassembleInfo for the current disassembly call. | |
| This function reads the instruction bytes by calling the read_memory | |
| method on the DisassembleInfo object. This method might have been | |
| overridden by user code. | |
| Read LEN bytes from MEMADDR and place them into BUFF. Return 0 on | |
| success (in which case BUFF has been filled), or -1 on error, in which | |
| case the contents of BUFF are undefined. */ | |
| int | |
| gdbpy_disassembler::read_memory_func (bfd_vma memaddr, gdb_byte *buff, | |
| unsigned int len, | |
| struct disassemble_info *info) noexcept | |
| { | |
| gdbpy_disassembler *dis | |
| = static_cast<gdbpy_disassembler *> (info->application_data); | |
| disasm_info_object *obj = dis->py_disasm_info (); | |
| /* If a previous read attempt resulted in an exception, then we don't | |
| allow any further reads to succeed. We only do this check for the | |
| read_memory_func as this is the only one the user can hook into, | |
| thus, this check prevents us calling back into user code if a | |
| previous call has already thrown an error. */ | |
| if (dis->has_stored_exception ()) | |
| return -1; | |
| /* The DisassembleInfo.read_memory method expects an offset from the | |
| address stored within the DisassembleInfo object; calculate that | |
| offset here. */ | |
| gdb_py_longest offset | |
| = (gdb_py_longest) memaddr - (gdb_py_longest) obj->address; | |
| /* Now call the DisassembleInfo.read_memory method. This might have been | |
| overridden by the user. */ | |
| gdbpy_ref<> result_obj = gdbpy_call_method ((PyObject *) obj, "read_memory", | |
| len, offset); | |
| /* Handle any exceptions. */ | |
| if (result_obj == nullptr) | |
| { | |
| /* If we got a gdb.MemoryError then we ignore this and just report | |
| that the read failed to the caller. The caller is then | |
| responsible for calling the memory_error_func if it wants to. | |
| Remember, the disassembler might just be probing to see if these | |
| bytes can be read, if we automatically call the memory error | |
| function, we can end up registering an error prematurely. */ | |
| if (PyErr_ExceptionMatches (gdbpy_gdb_memory_error)) | |
| { | |
| PyErr_Clear (); | |
| return -1; | |
| } | |
| /* For any other exception type we capture the value of the Python | |
| exception and throw it, this will then be caught in | |
| disasmpy_builtin_disassemble, at which point the exception will be | |
| restored. */ | |
| dis->store_exception (gdbpy_err_fetch ()); | |
| return -1; | |
| } | |
| /* Convert the result to a buffer. */ | |
| Py_buffer py_buff; | |
| if (!PyObject_CheckBuffer (result_obj.get ()) | |
| || PyObject_GetBuffer (result_obj.get(), &py_buff, PyBUF_CONTIG_RO) < 0) | |
| { | |
| PyErr_Format (PyExc_TypeError, | |
| _("Result from read_memory is not a buffer")); | |
| dis->store_exception (gdbpy_err_fetch ()); | |
| return -1; | |
| } | |
| /* Wrap PY_BUFF so that it is cleaned up correctly at the end of this | |
| scope. */ | |
| Py_buffer_up buffer_up (&py_buff); | |
| /* Validate that the buffer is the correct length. */ | |
| if (py_buff.len != len) | |
| { | |
| PyErr_Format (PyExc_ValueError, | |
| _("Buffer returned from read_memory is sized %d instead of the expected %d"), | |
| py_buff.len, len); | |
| dis->store_exception (gdbpy_err_fetch ()); | |
| return -1; | |
| } | |
| /* Copy the data out of the Python buffer and return success. */ | |
| const gdb_byte *buffer = (const gdb_byte *) py_buff.buf; | |
| memcpy (buff, buffer, len); | |
| return 0; | |
| } | |
| /* Implement __str__ for the DisassemblerResult type. */ | |
| static PyObject * | |
| disasmpy_result_str (PyObject *self) | |
| { | |
| disasm_result_object *obj = (disasm_result_object *) self; | |
| /* These conditions are all enforced when the DisassemblerResult object | |
| is created. */ | |
| gdb_assert (obj->parts != nullptr); | |
| gdb_assert (obj->parts->size () > 0); | |
| gdb_assert (obj->length > 0); | |
| std::string str; | |
| try | |
| { | |
| str = disasmpy_parts_list_to_string (*obj->parts); | |
| } | |
| catch (const gdb_exception &except) | |
| { | |
| GDB_PY_HANDLE_EXCEPTION (except); | |
| } | |
| return PyUnicode_Decode (str.c_str (), str.size (), | |
| host_charset (), nullptr); | |
| } | |
| /* Implement DisassemblerResult.length attribute, return the length of the | |
| disassembled instruction. */ | |
| static PyObject * | |
| disasmpy_result_length (PyObject *self, void *closure) | |
| { | |
| disasm_result_object *obj = (disasm_result_object *) self; | |
| return gdb_py_object_from_longest (obj->length).release (); | |
| } | |
| /* Implement DisassemblerResult.string attribute, return the content string | |
| of the disassembled instruction. */ | |
| static PyObject * | |
| disasmpy_result_string (PyObject *self, void *closure) | |
| { | |
| return disasmpy_result_str (self); | |
| } | |
| /* Implement DisassemblerResult.parts method. Returns a list of all the | |
| parts that make up this result. There should always be at least one | |
| part, so the returned list should never be empty. */ | |
| static PyObject * | |
| disasmpy_result_parts (PyObject *self, void *closure) | |
| { | |
| disasm_result_object *obj = (disasm_result_object *) self; | |
| /* These conditions are all enforced when the DisassemblerResult object | |
| is created. */ | |
| gdb_assert (obj->parts != nullptr); | |
| gdb_assert (obj->parts->size () > 0); | |
| gdb_assert (obj->length > 0); | |
| gdbpy_ref<> result_list (PyList_New (obj->parts->size ())); | |
| if (result_list == nullptr) | |
| return nullptr; | |
| Py_ssize_t idx = 0; | |
| for (auto p : *obj->parts) | |
| { | |
| gdbpy_ref<> item = gdbpy_ref<>::new_reference (p.get ()); | |
| PyList_SET_ITEM (result_list.get (), idx, item.release ()); | |
| ++idx; | |
| } | |
| /* This should follow naturally from the obj->parts list being | |
| non-empty. */ | |
| gdb_assert (PyList_Size (result_list.get()) > 0); | |
| return result_list.release (); | |
| } | |
| /* Implement DisassemblerResult.__init__. Takes two arguments, an | |
| integer, the length in bytes of the disassembled instruction, and a | |
| string, the disassembled content of the instruction. */ | |
| static int | |
| disasmpy_result_init (PyObject *self, PyObject *args, PyObject *kwargs) | |
| { | |
| static const char *keywords[] = { "length", "string", "parts", NULL }; | |
| int length; | |
| const char *string = nullptr; | |
| PyObject *parts_list = nullptr; | |
| if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "i|zO", keywords, | |
| &length, &string, &parts_list)) | |
| return -1; | |
| if (length <= 0) | |
| { | |
| PyErr_SetString (PyExc_ValueError, | |
| _("Length must be greater than 0.")); | |
| return -1; | |
| } | |
| if (parts_list == Py_None) | |
| parts_list = nullptr; | |
| if (string != nullptr && parts_list != nullptr) | |
| { | |
| PyErr_Format (PyExc_ValueError, | |
| _("Cannot use 'string' and 'parts' when creating %s."), | |
| Py_TYPE (self)->tp_name); | |
| return -1; | |
| } | |
| if (string != nullptr) | |
| { | |
| if (strlen (string) == 0) | |
| { | |
| PyErr_SetString (PyExc_ValueError, | |
| _("String must not be empty.")); | |
| return -1; | |
| } | |
| disasm_result_object *obj = (disasm_result_object *) self; | |
| std::vector<gdbpy_ref<>> content; | |
| gdbpy_ref<> text_part | |
| = make_disasm_text_part (std::string (string), dis_style_text); | |
| content.emplace_back (text_part.release ()); | |
| disasmpy_init_disassembler_result (obj, length, std::move (content)); | |
| } | |
| else | |
| { | |
| if (!PySequence_Check (parts_list)) | |
| { | |
| PyErr_SetString (PyExc_TypeError, | |
| _("'parts' argument is not a sequence")); | |
| return -1; | |
| } | |
| Py_ssize_t parts_count = PySequence_Size (parts_list); | |
| if (parts_count <= 0) | |
| { | |
| PyErr_SetString (PyExc_ValueError, | |
| _("'parts' list must not be empty.")); | |
| return -1; | |
| } | |
| disasm_result_object *obj = (disasm_result_object *) self; | |
| std::vector<gdbpy_ref<>> content (parts_count); | |
| struct gdbarch *gdbarch = nullptr; | |
| for (Py_ssize_t i = 0; i < parts_count; ++i) | |
| { | |
| gdbpy_ref<> part (PySequence_GetItem (parts_list, i)); | |
| if (part == nullptr) | |
| return -1; | |
| if (Py_TYPE (part.get ()) == &disasm_addr_part_object_type) | |
| { | |
| disasm_addr_part_object *addr_part | |
| = (disasm_addr_part_object *) part.get (); | |
| gdb_assert (addr_part->gdbarch != nullptr); | |
| if (gdbarch == nullptr) | |
| gdbarch = addr_part->gdbarch; | |
| else if (addr_part->gdbarch != gdbarch) | |
| { | |
| PyErr_SetString (PyExc_ValueError, | |
| _("Inconsistent gdb.Architectures used " | |
| "in 'parts' sequence.")); | |
| return -1; | |
| } | |
| } | |
| content[i] = std::move (part); | |
| } | |
| disasmpy_init_disassembler_result (obj, length, std::move (content)); | |
| } | |
| return 0; | |
| } | |
| /* Implement __repr__ for the DisassemblerResult type. */ | |
| static PyObject * | |
| disasmpy_result_repr (PyObject *self) | |
| { | |
| disasm_result_object *obj = (disasm_result_object *) self; | |
| gdb_assert (obj->parts != nullptr); | |
| return PyUnicode_FromFormat ("<%s length=%d string=\"%U\">", | |
| Py_TYPE (obj)->tp_name, | |
| obj->length, | |
| disasmpy_result_str (self)); | |
| } | |
| /* Implement memory_error_func callback for disassemble_info. Extract the | |
| underlying DisassembleInfo Python object, and set a memory error on | |
| it. */ | |
| void | |
| gdbpy_disassembler::memory_error_func (int status, bfd_vma memaddr, | |
| struct disassemble_info *info) noexcept | |
| { | |
| gdbpy_disassembler *dis | |
| = static_cast<gdbpy_disassembler *> (info->application_data); | |
| dis->m_memory_error_address.emplace (memaddr); | |
| } | |
| /* Wrapper of print_address. */ | |
| void | |
| gdbpy_disassembler::print_address_func (bfd_vma addr, | |
| struct disassemble_info *info) noexcept | |
| { | |
| gdbpy_disassembler *dis | |
| = static_cast<gdbpy_disassembler *> (info->application_data); | |
| gdbpy_ref<> addr_part | |
| = make_disasm_addr_part (dis->arch (), addr); | |
| dis->m_parts.emplace_back (std::move (addr_part)); | |
| } | |
| /* constructor. */ | |
| gdbpy_disassembler::gdbpy_disassembler (disasm_info_object *obj, | |
| PyObject *memory_source) | |
| : gdb_disassemble_info (obj->gdbarch, | |
| read_memory_func, | |
| memory_error_func, | |
| print_address_func, | |
| fprintf_func, | |
| fprintf_styled_func), | |
| m_disasm_info_object (obj), | |
| m_memory_source (memory_source) | |
| { /* Nothing. */ } | |
| /* A wrapper around a reference to a Python DisassembleInfo object, which | |
| ensures that the object is marked as invalid when we leave the enclosing | |
| scope. | |
| Each DisassembleInfo is created in gdbpy_print_insn, and is done with by | |
| the time that function returns. However, there's nothing to stop a user | |
| caching a reference to the DisassembleInfo, and thus keeping the object | |
| around. | |
| We therefore have the notion of a DisassembleInfo becoming invalid, this | |
| happens when gdbpy_print_insn returns. This class is responsible for | |
| marking the DisassembleInfo as invalid in its destructor. */ | |
| struct scoped_disasm_info_object | |
| { | |
| /* Constructor. */ | |
| scoped_disasm_info_object (struct gdbarch *gdbarch, CORE_ADDR memaddr, | |
| disassemble_info *info) | |
| : m_disasm_info (allocate_disasm_info_object ()) | |
| { | |
| disasm_info_fill (m_disasm_info.get (), gdbarch, current_program_space, | |
| memaddr, info, nullptr); | |
| } | |
| /* Upon destruction mark m_disasm_info as invalid. */ | |
| ~scoped_disasm_info_object () | |
| { | |
| /* Invalidate the original DisassembleInfo object as well as any copies | |
| that the user might have made. */ | |
| for (disasm_info_object *obj = m_disasm_info.get (); | |
| obj != nullptr; | |
| obj = obj->next) | |
| obj->gdb_info = nullptr; | |
| } | |
| /* Return a pointer to the underlying disasm_info_object instance. */ | |
| disasm_info_object * | |
| get () const | |
| { | |
| return m_disasm_info.get (); | |
| } | |
| private: | |
| /* Wrapper around the call to PyObject_New, this wrapper function can be | |
| called from the constructor initialization list, while PyObject_New, a | |
| macro, can't. */ | |
| static disasm_info_object * | |
| allocate_disasm_info_object () | |
| { | |
| return (disasm_info_object *) PyObject_New (disasm_info_object, | |
| &disasm_info_object_type); | |
| } | |
| /* A reference to a gdb.disassembler.DisassembleInfo object. When this | |
| containing instance goes out of scope this reference is released, | |
| however, the user might be holding other references to the | |
| DisassembleInfo object in Python code, so the underlying object might | |
| not be deleted. */ | |
| gdbpy_ref<disasm_info_object> m_disasm_info; | |
| }; | |
| /* See python-internal.h. */ | |
| std::optional<int> | |
| gdbpy_print_insn (struct gdbarch *gdbarch, CORE_ADDR memaddr, | |
| disassemble_info *info) | |
| { | |
| /* Early exit case. This must be done as early as possible, and | |
| definitely before we enter Python environment. The | |
| python_print_insn_enabled flag is set (from Python) only when the user | |
| has installed one (or more) Python disassemblers. So in the common | |
| case (no custom disassembler installed) this flag will be false, | |
| allowing for a quick return. */ | |
| if (!gdb_python_initialized || !python_print_insn_enabled) | |
| return {}; | |
| gdbpy_enter enter_py (get_current_arch (), current_language); | |
| /* Import the gdb.disassembler module. */ | |
| gdbpy_ref<> gdb_python_disassembler_module | |
| (PyImport_ImportModule ("gdb.disassembler")); | |
| if (gdb_python_disassembler_module == nullptr) | |
| { | |
| gdbpy_print_stack (); | |
| return {}; | |
| } | |
| /* Get the _print_insn attribute from the module, this should be the | |
| function we are going to call to actually perform the disassembly. */ | |
| gdbpy_ref<> hook | |
| (PyObject_GetAttrString (gdb_python_disassembler_module.get (), | |
| "_print_insn")); | |
| if (hook == nullptr) | |
| { | |
| gdbpy_print_stack (); | |
| return {}; | |
| } | |
| /* Create the new DisassembleInfo object we will pass into Python. This | |
| object will be marked as invalid when we leave this scope. */ | |
| scoped_disasm_info_object scoped_disasm_info (gdbarch, memaddr, info); | |
| disasm_info_object *disasm_info = scoped_disasm_info.get (); | |
| /* Call into the registered disassembler to (possibly) perform the | |
| disassembly. */ | |
| PyObject *insn_disas_obj = (PyObject *) disasm_info; | |
| gdbpy_ref<> result (PyObject_CallFunctionObjArgs (hook.get (), | |
| insn_disas_obj, | |
| nullptr)); | |
| if (result == nullptr) | |
| { | |
| /* The call into Python code resulted in an exception. If this was a | |
| gdb.MemoryError, then we can figure out an address and call the | |
| disassemble_info::memory_error_func to report the error back to | |
| core GDB. Any other exception type we report back to core GDB as | |
| an unknown error (return -1 without first calling the | |
| memory_error_func callback). */ | |
| if (PyErr_ExceptionMatches (gdbpy_gdb_memory_error)) | |
| { | |
| /* A gdb.MemoryError might have an address attribute which | |
| contains the address at which the memory error occurred. If | |
| this is the case then use this address, otherwise, fallback to | |
| just using the address of the instruction we were asked to | |
| disassemble. */ | |
| gdbpy_err_fetch err; | |
| PyErr_Clear (); | |
| CORE_ADDR addr; | |
| if (err.value () != nullptr | |
| && PyObject_HasAttrString (err.value ().get (), "address")) | |
| { | |
| PyObject *addr_obj | |
| = PyObject_GetAttrString (err.value ().get (), "address"); | |
| if (get_addr_from_python (addr_obj, &addr) < 0) | |
| addr = disasm_info->address; | |
| } | |
| else | |
| addr = disasm_info->address; | |
| info->memory_error_func (-1, addr, info); | |
| return std::optional<int> (-1); | |
| } | |
| else if (PyErr_ExceptionMatches (gdbpy_gdberror_exc)) | |
| { | |
| gdbpy_err_fetch err; | |
| gdb::unique_xmalloc_ptr<char> msg = err.to_string (); | |
| info->fprintf_func (info->stream, "%s", msg.get ()); | |
| return std::optional<int> (-1); | |
| } | |
| else | |
| { | |
| gdbpy_print_stack_or_quit (); | |
| return std::optional<int> (-1); | |
| } | |
| } | |
| else if (result == Py_None) | |
| { | |
| /* A return value of None indicates that the Python code could not, | |
| or doesn't want to, disassemble this instruction. Just return an | |
| empty result and core GDB will try to disassemble this for us. */ | |
| return {}; | |
| } | |
| /* Check the result is a DisassemblerResult (or a sub-class). */ | |
| if (!PyObject_IsInstance (result.get (), | |
| (PyObject *) &disasm_result_object_type)) | |
| { | |
| PyErr_SetString (PyExc_TypeError, | |
| _("Result is not a DisassemblerResult.")); | |
| gdbpy_print_stack (); | |
| return std::optional<int> (-1); | |
| } | |
| /* The result from the Python disassembler has the correct type. Convert | |
| this back to the underlying C++ object and read the state directly | |
| from this object. */ | |
| struct disasm_result_object *result_obj | |
| = (struct disasm_result_object *) result.get (); | |
| /* Validate the length of the disassembled instruction. */ | |
| long length = result_obj->length; | |
| long max_insn_length = (gdbarch_max_insn_length_p (gdbarch) ? | |
| gdbarch_max_insn_length (gdbarch) : INT_MAX); | |
| if (length <= 0) | |
| { | |
| PyErr_SetString | |
| (PyExc_ValueError, | |
| _("Invalid length attribute: length must be greater than 0.")); | |
| gdbpy_print_stack (); | |
| return std::optional<int> (-1); | |
| } | |
| if (length > max_insn_length) | |
| { | |
| PyErr_Format | |
| (PyExc_ValueError, | |
| _("Invalid length attribute: length %d greater than architecture maximum of %d"), | |
| length, max_insn_length); | |
| gdbpy_print_stack (); | |
| return std::optional<int> (-1); | |
| } | |
| /* It is impossible to create a DisassemblerResult object with an empty | |
| parts list. We know that each part results in a non-empty string, so | |
| we know that the instruction disassembly will not be the empty | |
| string. */ | |
| gdb_assert (result_obj->parts->size () > 0); | |
| /* Now print out the parts that make up this instruction. */ | |
| for (auto &p : *result_obj->parts) | |
| { | |
| if (Py_TYPE (p.get ()) == &disasm_text_part_object_type) | |
| { | |
| disasm_text_part_object *text_part | |
| = (disasm_text_part_object *) p.get (); | |
| gdb_assert (text_part->string != nullptr); | |
| info->fprintf_styled_func (info->stream, text_part->style, | |
| "%s", text_part->string->c_str ()); | |
| } | |
| else | |
| { | |
| gdb_assert (Py_TYPE (p.get ()) == &disasm_addr_part_object_type); | |
| disasm_addr_part_object *addr_part | |
| = (disasm_addr_part_object *) p.get (); | |
| /* A DisassemblerAddressPart can only be created by calling a | |
| method on DisassembleInfo, and the gdbarch is copied from the | |
| DisassembleInfo into the DisassemblerAddressPart. As the | |
| DisassembleInfo has its gdbarch initialised from GDBARCH in | |
| this scope, and this architecture can't be changed, then the | |
| following assert should hold. */ | |
| gdb_assert (addr_part->gdbarch == gdbarch); | |
| info->print_address_func (addr_part->address, info); | |
| } | |
| } | |
| return std::optional<int> (length); | |
| } | |
| /* The tp_dealloc callback for the DisassemblerResult type. Takes care of | |
| deallocating the content buffer. */ | |
| static void | |
| disasmpy_dealloc_result (PyObject *self) | |
| { | |
| disasm_result_object *obj = (disasm_result_object *) self; | |
| delete obj->parts; | |
| Py_TYPE (self)->tp_free (self); | |
| } | |
| /* The tp_init callback for the DisassemblerPart type. This just raises an | |
| exception, which prevents the user from creating objects of this type. | |
| Instead the user should create instances of a sub-class. */ | |
| static int | |
| disasmpy_part_init (PyObject *self, PyObject *args, PyObject *kwargs) | |
| { | |
| PyErr_SetString (PyExc_RuntimeError, | |
| _("Cannot create instances of DisassemblerPart.")); | |
| return -1; | |
| } | |
| /* Return a string representing STYLE. The returned string is used as a | |
| constant defined in the gdb.disassembler module. */ | |
| static const char * | |
| get_style_name (enum disassembler_style style) | |
| { | |
| switch (style) | |
| { | |
| case dis_style_text: return "STYLE_TEXT"; | |
| case dis_style_mnemonic: return "STYLE_MNEMONIC"; | |
| case dis_style_sub_mnemonic: return "STYLE_SUB_MNEMONIC"; | |
| case dis_style_assembler_directive: return "STYLE_ASSEMBLER_DIRECTIVE"; | |
| case dis_style_register: return "STYLE_REGISTER"; | |
| case dis_style_immediate: return "STYLE_IMMEDIATE"; | |
| case dis_style_address: return "STYLE_ADDRESS"; | |
| case dis_style_address_offset: return "STYLE_ADDRESS_OFFSET"; | |
| case dis_style_symbol: return "STYLE_SYMBOL"; | |
| case dis_style_comment_start: return "STYLE_COMMENT_START"; | |
| } | |
| gdb_assert_not_reached ("unknown disassembler style"); | |
| } | |
| /* Implement DisassemblerTextPart.__repr__ method. */ | |
| static PyObject * | |
| disasmpy_text_part_repr (PyObject *self) | |
| { | |
| disasm_text_part_object *obj = (disasm_text_part_object *) self; | |
| gdb_assert (obj->string != nullptr); | |
| return PyUnicode_FromFormat ("<%s string='%s', style='%s'>", | |
| Py_TYPE (obj)->tp_name, | |
| obj->string->c_str (), | |
| get_style_name (obj->style)); | |
| } | |
| /* Implement DisassemblerTextPart.__str__ attribute. */ | |
| static PyObject * | |
| disasmpy_text_part_str (PyObject *self) | |
| { | |
| disasm_text_part_object *obj = (disasm_text_part_object *) self; | |
| return PyUnicode_Decode (obj->string->c_str (), obj->string->size (), | |
| host_charset (), nullptr); | |
| } | |
| /* Implement DisassemblerTextPart.string attribute. */ | |
| static PyObject * | |
| disasmpy_text_part_string (PyObject *self, void *closure) | |
| { | |
| return disasmpy_text_part_str (self); | |
| } | |
| /* Implement DisassemblerTextPart.style attribute. */ | |
| static PyObject * | |
| disasmpy_text_part_style (PyObject *self, void *closure) | |
| { | |
| disasm_text_part_object *obj = (disasm_text_part_object *) self; | |
| LONGEST style_val = (LONGEST) obj->style; | |
| return gdb_py_object_from_longest (style_val).release (); | |
| } | |
| /* Implement DisassemblerAddressPart.__repr__ method. */ | |
| static PyObject * | |
| disasmpy_addr_part_repr (PyObject *self) | |
| { | |
| disasm_addr_part_object *obj = (disasm_addr_part_object *) self; | |
| return PyUnicode_FromFormat ("<%s address='%s'>", | |
| Py_TYPE (obj)->tp_name, | |
| core_addr_to_string_nz (obj->address)); | |
| } | |
| /* Implement DisassemblerAddressPart.__str__ attribute. */ | |
| static PyObject * | |
| disasmpy_addr_part_str (PyObject *self) | |
| { | |
| disasm_addr_part_object *obj = (disasm_addr_part_object *) self; | |
| std::string str; | |
| try | |
| { | |
| string_file buf; | |
| print_address (obj->gdbarch, obj->address, &buf); | |
| str = buf.release (); | |
| } | |
| catch (const gdb_exception &except) | |
| { | |
| GDB_PY_HANDLE_EXCEPTION (except); | |
| } | |
| return PyUnicode_Decode (str.c_str (), str.size (), | |
| host_charset (), nullptr); | |
| } | |
| /* Implement DisassemblerAddressPart.string attribute. */ | |
| static PyObject * | |
| disasmpy_addr_part_string (PyObject *self, void *closure) | |
| { | |
| return disasmpy_addr_part_str (self); | |
| } | |
| /* Implement DisassemblerAddressPart.address attribute. */ | |
| static PyObject * | |
| disasmpy_addr_part_address (PyObject *self, void *closure) | |
| { | |
| disasm_addr_part_object *obj = (disasm_addr_part_object *) self; | |
| return gdb_py_object_from_longest (obj->address).release (); | |
| } | |
| /* The get/set attributes of the gdb.disassembler.DisassembleInfo type. */ | |
| static gdb_PyGetSetDef disasm_info_object_getset[] = { | |
| { "address", disasmpy_info_address, nullptr, | |
| "Start address of the instruction to disassemble.", nullptr }, | |
| { "architecture", disasmpy_info_architecture, nullptr, | |
| "Architecture to disassemble in", nullptr }, | |
| { "progspace", disasmpy_info_progspace, nullptr, | |
| "Program space to disassemble in", nullptr }, | |
| { nullptr } /* Sentinel */ | |
| }; | |
| /* The methods of the gdb.disassembler.DisassembleInfo type. */ | |
| static PyMethodDef disasm_info_object_methods[] = { | |
| { "read_memory", (PyCFunction) disasmpy_info_read_memory, | |
| METH_VARARGS | METH_KEYWORDS, | |
| "read_memory (LEN, OFFSET = 0) -> Octets[]\n\ | |
| Read LEN octets for the instruction to disassemble." }, | |
| { "is_valid", disasmpy_info_is_valid, METH_NOARGS, | |
| "is_valid () -> Boolean.\n\ | |
| Return true if this DisassembleInfo is valid, false if not." }, | |
| { "text_part", (PyCFunction) disasmpy_info_make_text_part, | |
| METH_VARARGS | METH_KEYWORDS, | |
| "text_part (STRING, STYLE) -> DisassemblerTextPart\n\ | |
| Create a new text part, with contents STRING styled with STYLE." }, | |
| { "address_part", (PyCFunction) disasmpy_info_make_address_part, | |
| METH_VARARGS | METH_KEYWORDS, | |
| "address_part (ADDRESS) -> DisassemblerAddressPart\n\ | |
| Create a new address part representing ADDRESS." }, | |
| {nullptr} /* Sentinel */ | |
| }; | |
| /* The get/set attributes of the gdb.disassembler.DisassemblerResult type. */ | |
| static gdb_PyGetSetDef disasm_result_object_getset[] = { | |
| { "length", disasmpy_result_length, nullptr, | |
| "Length of the disassembled instruction.", nullptr }, | |
| { "string", disasmpy_result_string, nullptr, | |
| "String representing the disassembled instruction.", nullptr }, | |
| { "parts", disasmpy_result_parts, nullptr, | |
| "List of all the separate disassembly parts", nullptr }, | |
| { nullptr } /* Sentinel */ | |
| }; | |
| /* The get/set attributes of the gdb.disassembler.DisassemblerTextPart type. */ | |
| static gdb_PyGetSetDef disasmpy_text_part_getset[] = { | |
| { "string", disasmpy_text_part_string, nullptr, | |
| "String representing a text part.", nullptr }, | |
| { "style", disasmpy_text_part_style, nullptr, | |
| "The style of this text part.", nullptr }, | |
| { nullptr } /* Sentinel */ | |
| }; | |
| /* The get/set attributes of the gdb.disassembler.DisassemblerAddressPart type. */ | |
| static gdb_PyGetSetDef disasmpy_addr_part_getset[] = { | |
| { "string", disasmpy_addr_part_string, nullptr, | |
| "String representing an address part.", nullptr }, | |
| { "address", disasmpy_addr_part_address, nullptr, | |
| "The address of this address part.", nullptr }, | |
| { nullptr } /* Sentinel */ | |
| }; | |
| /* These are the methods we add into the _gdb.disassembler module, which | |
| are then imported into the gdb.disassembler module. These are global | |
| functions that support performing disassembly. */ | |
| PyMethodDef python_disassembler_methods[] = | |
| { | |
| { "builtin_disassemble", (PyCFunction) disasmpy_builtin_disassemble, | |
| METH_VARARGS | METH_KEYWORDS, | |
| "builtin_disassemble (INFO, MEMORY_SOURCE = None) -> None\n\ | |
| Disassemble using GDB's builtin disassembler. INFO is an instance of\n\ | |
| gdb.disassembler.DisassembleInfo. The MEMORY_SOURCE, if not None, should\n\ | |
| be an object with the read_memory method." }, | |
| { "_set_enabled", (PyCFunction) disasmpy_set_enabled, | |
| METH_VARARGS | METH_KEYWORDS, | |
| "_set_enabled (STATE) -> None\n\ | |
| Set whether GDB should call into the Python _print_insn code or not." }, | |
| {nullptr, nullptr, 0, nullptr} | |
| }; | |
| /* Structure to define the _gdb.disassembler module. */ | |
| static struct PyModuleDef python_disassembler_module_def = | |
| { | |
| PyModuleDef_HEAD_INIT, | |
| "_gdb.disassembler", | |
| nullptr, | |
| -1, | |
| python_disassembler_methods, | |
| nullptr, | |
| nullptr, | |
| nullptr, | |
| nullptr | |
| }; | |
| /* Called to initialize the Python structures in this file. */ | |
| static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION | |
| gdbpy_initialize_disasm () | |
| { | |
| /* Create the _gdb.disassembler module, and add it to the _gdb module. */ | |
| PyObject *gdb_disassembler_module; | |
| gdb_disassembler_module = PyModule_Create (&python_disassembler_module_def); | |
| if (gdb_disassembler_module == nullptr) | |
| return -1; | |
| if (gdb_pymodule_addobject (gdb_module, "disassembler", | |
| gdb_disassembler_module) < 0) | |
| return -1; | |
| /* This is needed so that 'import _gdb.disassembler' will work. */ | |
| PyObject *dict = PyImport_GetModuleDict (); | |
| if (PyDict_SetItemString (dict, "_gdb.disassembler", | |
| gdb_disassembler_module) < 0) | |
| return -1; | |
| for (int i = 0; i <= (int) dis_style_comment_start; ++i) | |
| { | |
| const char *style_name = get_style_name ((enum disassembler_style) i); | |
| if (PyModule_AddIntConstant (gdb_disassembler_module, style_name, i) < 0) | |
| return -1; | |
| } | |
| disasm_info_object_type.tp_new = PyType_GenericNew; | |
| if (PyType_Ready (&disasm_info_object_type) < 0) | |
| return -1; | |
| if (gdb_pymodule_addobject (gdb_disassembler_module, "DisassembleInfo", | |
| (PyObject *) &disasm_info_object_type) < 0) | |
| return -1; | |
| disasm_result_object_type.tp_new = PyType_GenericNew; | |
| if (PyType_Ready (&disasm_result_object_type) < 0) | |
| return -1; | |
| if (gdb_pymodule_addobject (gdb_disassembler_module, "DisassemblerResult", | |
| (PyObject *) &disasm_result_object_type) < 0) | |
| return -1; | |
| disasm_part_object_type.tp_new = PyType_GenericNew; | |
| if (PyType_Ready (&disasm_part_object_type) < 0) | |
| return -1; | |
| if (gdb_pymodule_addobject (gdb_disassembler_module, "DisassemblerPart", | |
| (PyObject *) &disasm_part_object_type) < 0) | |
| return -1; | |
| disasm_addr_part_object_type.tp_new = PyType_GenericNew; | |
| if (PyType_Ready (&disasm_addr_part_object_type) < 0) | |
| return -1; | |
| if (gdb_pymodule_addobject (gdb_disassembler_module, | |
| "DisassemblerAddressPart", | |
| (PyObject *) &disasm_addr_part_object_type) < 0) | |
| return -1; | |
| disasm_text_part_object_type.tp_new = PyType_GenericNew; | |
| if (PyType_Ready (&disasm_text_part_object_type) < 0) | |
| return -1; | |
| if (gdb_pymodule_addobject (gdb_disassembler_module, | |
| "DisassemblerTextPart", | |
| (PyObject *) &disasm_text_part_object_type) < 0) | |
| return -1; | |
| return 0; | |
| } | |
| GDBPY_INITIALIZE_FILE (gdbpy_initialize_disasm); | |
| /* Describe the gdb.disassembler.DisassembleInfo type. */ | |
| PyTypeObject disasm_info_object_type = { | |
| PyVarObject_HEAD_INIT (nullptr, 0) | |
| "gdb.disassembler.DisassembleInfo", /*tp_name*/ | |
| sizeof (disasm_info_object), /*tp_basicsize*/ | |
| 0, /*tp_itemsize*/ | |
| disasm_info_dealloc, /*tp_dealloc*/ | |
| 0, /*tp_print*/ | |
| 0, /*tp_getattr*/ | |
| 0, /*tp_setattr*/ | |
| 0, /*tp_compare*/ | |
| disasmpy_info_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 | Py_TPFLAGS_BASETYPE, /*tp_flags*/ | |
| "GDB instruction disassembler object", /* tp_doc */ | |
| 0, /* tp_traverse */ | |
| 0, /* tp_clear */ | |
| 0, /* tp_richcompare */ | |
| 0, /* tp_weaklistoffset */ | |
| 0, /* tp_iter */ | |
| 0, /* tp_iternext */ | |
| disasm_info_object_methods, /* tp_methods */ | |
| 0, /* tp_members */ | |
| disasm_info_object_getset, /* tp_getset */ | |
| 0, /* tp_base */ | |
| 0, /* tp_dict */ | |
| 0, /* tp_descr_get */ | |
| 0, /* tp_descr_set */ | |
| 0, /* tp_dictoffset */ | |
| disasm_info_init, /* tp_init */ | |
| 0, /* tp_alloc */ | |
| }; | |
| /* Describe the gdb.disassembler.DisassemblerResult type. */ | |
| PyTypeObject disasm_result_object_type = { | |
| PyVarObject_HEAD_INIT (nullptr, 0) | |
| "gdb.disassembler.DisassemblerResult", /*tp_name*/ | |
| sizeof (disasm_result_object), /*tp_basicsize*/ | |
| 0, /*tp_itemsize*/ | |
| disasmpy_dealloc_result, /*tp_dealloc*/ | |
| 0, /*tp_print*/ | |
| 0, /*tp_getattr*/ | |
| 0, /*tp_setattr*/ | |
| 0, /*tp_compare*/ | |
| disasmpy_result_repr, /*tp_repr*/ | |
| 0, /*tp_as_number*/ | |
| 0, /*tp_as_sequence*/ | |
| 0, /*tp_as_mapping*/ | |
| 0, /*tp_hash */ | |
| 0, /*tp_call*/ | |
| disasmpy_result_str, /*tp_str*/ | |
| 0, /*tp_getattro*/ | |
| 0, /*tp_setattro*/ | |
| 0, /*tp_as_buffer*/ | |
| Py_TPFLAGS_DEFAULT, /*tp_flags*/ | |
| "GDB object, representing a disassembler result", /* 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 */ | |
| disasm_result_object_getset, /* tp_getset */ | |
| 0, /* tp_base */ | |
| 0, /* tp_dict */ | |
| 0, /* tp_descr_get */ | |
| 0, /* tp_descr_set */ | |
| 0, /* tp_dictoffset */ | |
| disasmpy_result_init, /* tp_init */ | |
| 0, /* tp_alloc */ | |
| }; | |
| /* Describe the gdb.disassembler.DisassemblerPart type. This type exists | |
| only as an abstract base-class for the various part sub-types. The | |
| init method for this type throws an error. As such we don't both to | |
| provide a tp_repr method for this parent class. */ | |
| PyTypeObject disasm_part_object_type = { | |
| PyVarObject_HEAD_INIT (nullptr, 0) | |
| "gdb.disassembler.DisassemblerPart", /*tp_name*/ | |
| sizeof (PyObject), /*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 object, representing part of a disassembled instruction", /* 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 */ | |
| 0, /* tp_getset */ | |
| 0, /* tp_base */ | |
| 0, /* tp_dict */ | |
| 0, /* tp_descr_get */ | |
| 0, /* tp_descr_set */ | |
| 0, /* tp_dictoffset */ | |
| disasmpy_part_init, /* tp_init */ | |
| 0, /* tp_alloc */ | |
| }; | |
| /* Describe the gdb.disassembler.DisassemblerTextPart type. */ | |
| PyTypeObject disasm_text_part_object_type = { | |
| PyVarObject_HEAD_INIT (nullptr, 0) | |
| "gdb.disassembler.DisassemblerTextPart", /*tp_name*/ | |
| sizeof (disasm_text_part_object_type), /*tp_basicsize*/ | |
| 0, /*tp_itemsize*/ | |
| 0, /*tp_dealloc*/ | |
| 0, /*tp_print*/ | |
| 0, /*tp_getattr*/ | |
| 0, /*tp_setattr*/ | |
| 0, /*tp_compare*/ | |
| disasmpy_text_part_repr, /*tp_repr*/ | |
| 0, /*tp_as_number*/ | |
| 0, /*tp_as_sequence*/ | |
| 0, /*tp_as_mapping*/ | |
| 0, /*tp_hash */ | |
| 0, /*tp_call*/ | |
| disasmpy_text_part_str, /*tp_str*/ | |
| 0, /*tp_getattro*/ | |
| 0, /*tp_setattro*/ | |
| 0, /*tp_as_buffer*/ | |
| Py_TPFLAGS_DEFAULT, /*tp_flags*/ | |
| "GDB object, representing a text part of an instruction", /* 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 */ | |
| disasmpy_text_part_getset, /* tp_getset */ | |
| &disasm_part_object_type, /* tp_base */ | |
| 0, /* tp_dict */ | |
| 0, /* tp_descr_get */ | |
| 0, /* tp_descr_set */ | |
| 0, /* tp_dictoffset */ | |
| 0, /* tp_init */ | |
| 0, /* tp_alloc */ | |
| }; | |
| /* Describe the gdb.disassembler.DisassemblerAddressPart type. */ | |
| PyTypeObject disasm_addr_part_object_type = { | |
| PyVarObject_HEAD_INIT (nullptr, 0) | |
| "gdb.disassembler.DisassemblerAddressPart", /*tp_name*/ | |
| sizeof (disasm_addr_part_object), /*tp_basicsize*/ | |
| 0, /*tp_itemsize*/ | |
| 0, /*tp_dealloc*/ | |
| 0, /*tp_print*/ | |
| 0, /*tp_getattr*/ | |
| 0, /*tp_setattr*/ | |
| 0, /*tp_compare*/ | |
| disasmpy_addr_part_repr, /*tp_repr*/ | |
| 0, /*tp_as_number*/ | |
| 0, /*tp_as_sequence*/ | |
| 0, /*tp_as_mapping*/ | |
| 0, /*tp_hash */ | |
| 0, /*tp_call*/ | |
| disasmpy_addr_part_str, /*tp_str*/ | |
| 0, /*tp_getattro*/ | |
| 0, /*tp_setattro*/ | |
| 0, /*tp_as_buffer*/ | |
| Py_TPFLAGS_DEFAULT, /*tp_flags*/ | |
| "GDB object, representing an address part of an instruction", /* 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 */ | |
| disasmpy_addr_part_getset, /* tp_getset */ | |
| &disasm_part_object_type, /* tp_base */ | |
| 0, /* tp_dict */ | |
| 0, /* tp_descr_get */ | |
| 0, /* tp_descr_set */ | |
| 0, /* tp_dictoffset */ | |
| 0, /* tp_init */ | |
| 0, /* tp_alloc */ | |
| }; |