diff options
Diffstat (limited to 'gdb/python/py-disasm.c')
-rw-r--r-- | gdb/python/py-disasm.c | 1090 |
1 files changed, 1090 insertions, 0 deletions
diff --git a/gdb/python/py-disasm.c b/gdb/python/py-disasm.c new file mode 100644 index 0000000..4c78ca3 --- /dev/null +++ b/gdb/python/py-disasm.c @@ -0,0 +1,1090 @@ +/* Python interface to instruction disassembly. + + Copyright (C) 2021-2022 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 "defs.h" +#include "python-internal.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.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 buffer which, when allocated, holds the disassembled content of an + instruction. */ + string_file *content; +}; + +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_printing_disassembler +{ + /* 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); + static void print_address_func (bfd_vma addr, + struct disassemble_info *info); + static int read_memory_func (bfd_vma memaddr, gdb_byte *buff, + unsigned int len, + struct disassemble_info *info); + + /* 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 gdb::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::string release () + { + return m_string_file.release (); + } + +private: + + /* Where the disassembler result is written. */ + string_file m_string_file; + + /* 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. */ + gdb::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; +}; + +/* 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 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); +} + +/* 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) + +/* Initialise OBJ, a DisassemblerResult object with LENGTH and CONTENT. + OBJ might already have been initialised, in which case any existing + content should be discarded before the new CONTENT is moved in. */ + +static void +disasmpy_init_disassembler_result (disasm_result_object *obj, int length, + std::string content) +{ + if (obj->content == nullptr) + obj->content = new string_file; + else + obj->content->clear (); + + obj->length = length; + *(obj->content) = std::move (content); +} + +/* 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; + try + { + length = gdbarch_print_insn (disasm_info->gdbarch, disasm_info->address, + disassembler.disasm_info ()); + } + catch (gdbpy_err_fetch &pyerr) + { + /* Reinstall the Python exception held in PYERR. This clears to + pointers held in PYERR, hence the need to catch as a non-const + reference. */ + pyerr.restore (); + 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 + { + std::string content = disassembler.release (); + if (!content.empty ()) + PyErr_SetString (gdbpy_gdberror_exc, content.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. */ + std::string content = disassembler.release (); + PyTypeObject *type = &disasm_result_object_type; + gdbpy_ref<disasm_result_object> res + ((disasm_result_object *) type->tp_alloc (type, 0)); + 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); + + LONGEST length, offset = 0; + gdb::unique_xmalloc_ptr<gdb_byte> buffer; + static const char *keywords[] = { "length", "offset", nullptr }; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "L|L", 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 (); +} + +/* 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) +{ + gdbpy_disassembler *dis + = static_cast<gdbpy_disassembler *> (info->application_data); + disasm_info_object *obj = dis->py_disasm_info (); + + /* The DisassembleInfo.read_memory method expects an offset from the + address stored within the DisassembleInfo object; calculate that + offset here. */ + LONGEST offset = (LONGEST) memaddr - (LONGEST) obj->address; + + /* Now call the DisassembleInfo.read_memory method. This might have been + overridden by the user. */ + gdbpy_ref<> result_obj (PyObject_CallMethod ((PyObject *) obj, + "read_memory", + "KL", 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. */ + throw gdbpy_err_fetch (); + } + + /* 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")); + throw gdbpy_err_fetch (); + } + + /* 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); + throw gdbpy_err_fetch (); + } + + /* 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 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) +{ + disasm_result_object *obj = (disasm_result_object *) self; + + gdb_assert (obj->content != nullptr); + gdb_assert (strlen (obj->content->c_str ()) > 0); + gdb_assert (obj->length > 0); + return PyUnicode_Decode (obj->content->c_str (), + obj->content->size (), + host_charset (), nullptr); +} + +/* 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", NULL }; + int length; + const char *string; + if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "is", keywords, + &length, &string)) + return -1; + + if (length <= 0) + { + PyErr_SetString (PyExc_ValueError, + _("Length must be greater than 0.")); + return -1; + } + + if (strlen (string) == 0) + { + PyErr_SetString (PyExc_ValueError, + _("String must not be empty.")); + return -1; + } + + disasm_result_object *obj = (disasm_result_object *) self; + disasmpy_init_disassembler_result (obj, length, std::string (string)); + + return 0; +} + +/* 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) +{ + 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) +{ + gdbpy_disassembler *dis + = static_cast<gdbpy_disassembler *> (info->application_data); + print_address (dis->arch (), addr, (struct ui_file *) info->stream); +} + +/* constructor. */ + +gdbpy_disassembler::gdbpy_disassembler (disasm_info_object *obj, + PyObject *memory_source) + : gdb_printing_disassembler (obj->gdbarch, &m_string_file, + read_memory_func, memory_error_func, + print_address_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_diasm_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. */ + +gdb::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 gdb::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 gdb::optional<int> (-1); + } + else + { + gdbpy_print_stack (); + return gdb::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 gdb::optional<int> (-1); + } + + /* The call into Python neither raised an exception, or returned None. + Check to see if the result looks valid. */ + gdbpy_ref<> length_obj (PyObject_GetAttrString (result.get (), "length")); + if (length_obj == nullptr) + { + gdbpy_print_stack (); + return gdb::optional<int> (-1); + } + + gdbpy_ref<> string_obj (PyObject_GetAttrString (result.get (), "string")); + if (string_obj == nullptr) + { + gdbpy_print_stack (); + return gdb::optional<int> (-1); + } + if (!gdbpy_is_string (string_obj.get ())) + { + PyErr_SetString (PyExc_TypeError, _("String attribute is not a string.")); + gdbpy_print_stack (); + return gdb::optional<int> (-1); + } + + gdb::unique_xmalloc_ptr<char> string + = gdbpy_obj_to_string (string_obj.get ()); + if (string == nullptr) + { + gdbpy_print_stack (); + return gdb::optional<int> (-1); + } + + long length; + if (!gdb_py_int_as_long (length_obj.get (), &length)) + { + gdbpy_print_stack (); + return gdb::optional<int> (-1); + } + + 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 gdb::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 gdb::optional<int> (-1); + } + + if (strlen (string.get ()) == 0) + { + PyErr_SetString (PyExc_ValueError, + _("String attribute must not be empty.")); + gdbpy_print_stack (); + return gdb::optional<int> (-1); + } + + /* Print the disassembled instruction back to core GDB, and return the + length of the disassembled instruction. */ + info->fprintf_func (info->stream, "%s", string.get ()); + return gdb::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->content; + Py_TYPE (self)->tp_free (self); +} + +/* 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." }, + {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 }, + { 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. */ + +int +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; + PyModule_AddObject(gdb_module, "disassembler", gdb_disassembler_module); + + /* This is needed so that 'import _gdb.disassembler' will work. */ + PyObject *dict = PyImport_GetModuleDict (); + PyDict_SetItemString (dict, "_gdb.disassembler", gdb_disassembler_module); + + 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; + + return 0; +} + +/* 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*/ + 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 | 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*/ + 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 | Py_TPFLAGS_BASETYPE, /*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 */ +}; |