Previous: TUI Windows In Python, Up: Python API
gdb's builtin disassembler can be extended, or even replaced,
using the Python API. The disassembler related features are contained
within the gdb.disassembler module:
Disassembly is driven by instances of this class. Each time gdb needs to disassemble an instruction, an instance of this class is created and passed to a registered disassembler. The disassembler is then responsible for disassembling an instruction and returning a result.
Instances of this type are usually created within gdb, however, it is possible to create a copy of an instance of this type, see the description of
__init__for more details.This class has the following properties and methods:
— Variable: DisassembleInfo.address
A read-only integer containing the address at which gdb wishes to disassemble a single instruction.
— Variable: DisassembleInfo.architecture
The
gdb.Architecture(see Architectures In Python) for which gdb is currently disassembling, this property is read-only.— Variable: DisassembleInfo.progspace
The
gdb.Progspace(see Program Spaces In Python) for which gdb is currently disassembling, this property is read-only.— Function: DisassembleInfo.is_valid ()
Returns
Trueif theDisassembleInfoobject is valid,Falseif not. ADisassembleInfoobject will become invalid once the disassembly call for which theDisassembleInfowas created, has returned. Calling otherDisassembleInfomethods, or accessingDisassembleInfoproperties, will raise aRuntimeErrorexception if it is invalid.— Function: DisassembleInfo.__init__ (info)
This can be used to create a new
DisassembleInfoobject that is a copy of info. The copy will have the sameaddress,architecture, andprogspacevalues as info, and will become invalid at the same time as info.This method exists so that sub-classes of
DisassembleInfocan be created, these sub-classes must be initialized as copies of an existingDisassembleInfoobject, but sub-classes might choose to override theread_memorymethod, and so control what gdb sees when reading from memory (see builtin_disassemble).— Function: DisassembleInfo.read_memory (length, offset)
This method allows the disassembler to read the bytes of the instruction to be disassembled. The method reads length bytes, starting at offset from
DisassembleInfo.address.It is important that the disassembler read the instruction bytes using this method, rather than reading inferior memory directly, as in some cases gdb disassembles from an internal buffer rather than directly from inferior memory, calling this method handles this detail.
Returns a buffer object, which behaves much like an array or a string, just as
Inferior.read_memorydoes (see Inferior.read_memory). The length of the returned buffer will always be exactly length.If gdb is unable to read the required memory then a
gdb.MemoryErrorexception is raised (see Exception Handling).This method can be overridden by a sub-class in order to control what gdb sees when reading from memory (see builtin_disassemble). When overriding this method it is important to understand how
builtin_disassemblemakes use of this method.While disassembling a single instruction there could be multiple calls to this method, and the same bytes might be read multiple times. Any single call might only read a subset of the total instruction bytes.
If an implementation of
read_memoryis unable to read the requested memory contents, for example, if there's a request to read from an invalid memory address, then agdb.MemoryErrorshould be raised.Raising a
MemoryErrorinsideread_memorydoes not automatically mean aMemoryErrorwill be raised bybuiltin_disassemble. It is possible the gdb's builtin disassembler is probing to see how many bytes are available. Whenread_memoryraises theMemoryErrorthe builtin disassembler might be able to perform a complete disassembly with the bytes it has available, in this casebuiltin_disassemblewill not itself raise aMemoryError.Any other exception type raised in
read_memorywill propagate back and be re-raised bybuiltin_disassemble.
This is a base class from which all user implemented disassemblers must inherit.
— Function: Disassembler.__init__ (name)
The constructor takes name, a string, which should be a short name for this disassembler.
— Function: Disassembler.__call__ (info)
The
__call__method must be overridden by sub-classes to perform disassembly. Calling__call__on this base class will raise aNotImplementedErrorexception.The info argument is an instance of
DisassembleInfo, and describes the instruction that gdb wants disassembling.If this function returns
None, this indicates to gdb that this sub-class doesn't wish to disassemble the requested instruction. gdb will then use its builtin disassembler to perform the disassembly.Alternatively, this function can return a
DisassemblerResultthat represents the disassembled instruction, this type is described in more detail below.The
__call__method can raise agdb.MemoryErrorexception (see Exception Handling) to indicate to gdb that there was a problem accessing the required memory, this will then be displayed by gdb within the disassembler output.Ideally, the only three outcomes from invoking
__call__would be a return ofNone, a successful disassembly returned in aDisassemblerResult, or aMemoryErrorindicating that there was a problem reading memory.However, as an implementation of
__call__could fail due to other reasons, e.g. some external resource required to perform disassembly is temporarily unavailable, then, if__call__raises aGdbError, the exception will be converted to a string and printed at the end of the disassembly output, the disassembly request will then stop.Any other exception type raised by the
__call__method is considered an error in the user code, the exception will be printed to the error stream according to the set python print-stack setting (see set python print-stack).
This class is used to hold the result of calling
Disassembler.__call__, and represents a single disassembled instruction. This class has the following properties and methods:— Function: DisassemblerResult.__init__ (length, string)
Initialize an instance of this class, length is the length of the disassembled instruction in bytes, which must be greater than zero, and string is a non-empty string that represents the disassembled instruction.
The following functions are also contained in the
gdb.disassembler module:
The disassembler must be a sub-class of
gdb.disassembler.DisassemblerorNone.The optional architecture is either a string, or the value
None. If it is a string, then it should be the name of an architecture known to gdb, as returned either fromgdb.Architecture.name(see gdb.Architecture.name), or fromgdb.architecture_names(see gdb.architecture_names).The disassembler will be installed for the architecture named by architecture, or if architecture is
None, then disassembler will be installed as a global disassembler for use by all architectures.gdb only records a single disassembler for each architecture, and a single global disassembler. Calling
register_disassemblerfor an architecture, or for the global disassembler, will replace any existing disassembler registered for that architecture value. The previous disassembler is returned.If disassembler is
Nonethen any disassembler currently registered for architecture is deregistered and returned.When gdb is looking for a disassembler to use, gdb first looks for an architecture specific disassembler. If none has been registered then gdb looks for a global disassembler (one registered with architecture set to
None). Only one disassembler is called to perform disassembly, so, if there is both an architecture specific disassembler, and a global disassembler registered, it is the architecture specific disassembler that will be used.gdb tracks the architecture specific, and global disassemblers separately, so it doesn't matter in which order disassemblers are created or registered; an architecture specific disassembler, if present, will always be used in preference to a global disassembler.
You can use the maint info python-disassemblers command (see maint info python-disassemblers) to see which disassemblers have been registered.
This function calls back into gdb's builtin disassembler to disassemble the instruction identified by info, an instance, or sub-class, of
DisassembleInfo.When the builtin disassembler needs to read memory the
read_memorymethod on info will be called. By sub-classingDisassembleInfoand overriding theread_memorymethod, it is possible to intercept calls toread_memoryfrom the builtin disassembler, and to modify the values returned.It is important to understand that, even when
DisassembleInfo.read_memoryraises agdb.MemoryError, it is the internal disassembler itself that reports the memory error to gdb. The reason for this is that the disassembler might probe memory to see if a byte is readable or not; if the byte can't be read then the disassembler may choose not to report an error, but instead to disassemble the bytes that it does have available.If the builtin disassembler is successful then an instance of
DisassemblerResultis returned frombuiltin_disassemble, alternatively, if something goes wrong, an exception will be raised.A
MemoryErrorwill be raised ifbuiltin_disassembleis unable to read some memory that is required in order to perform disassembly correctly.Any exception that is not a
MemoryError, that is raised in a call toread_memory, will pass throughbuiltin_disassemble, and be visible to the caller.Finally, there are a few cases where gdb's builtin disassembler can fail for reasons that are not covered by
MemoryError. In these cases, aGdbErrorwill be raised. The contents of the exception will be a string describing the problem the disassembler encountered.
Here is an example that registers a global disassembler. The new
disassembler invokes the builtin disassembler, and then adds a
comment, ## Comment, to each line of disassembly output:
class ExampleDisassembler(gdb.disassembler.Disassembler):
def __init__(self):
super().__init__("ExampleDisassembler")
def __call__(self, info):
result = gdb.disassembler.builtin_disassemble(info)
length = result.length
text = result.string + "\t## Comment"
return gdb.disassembler.DisassemblerResult(length, text)
gdb.disassembler.register_disassembler(ExampleDisassembler())
The following example creates a sub-class of DisassembleInfo in
order to intercept the read_memory calls, within
read_memory any bytes read from memory have the two 4-bit
nibbles swapped around. This isn't a very useful adjustment, but
serves as an example.
class MyInfo(gdb.disassembler.DisassembleInfo):
def __init__(self, info):
super().__init__(info)
def read_memory(self, length, offset):
buffer = super().read_memory(length, offset)
result = bytearray()
for b in buffer:
v = int.from_bytes(b, 'little')
v = (v << 4) & 0xf0 | (v >> 4)
result.append(v)
return memoryview(result)
class NibbleSwapDisassembler(gdb.disassembler.Disassembler):
def __init__(self):
super().__init__("NibbleSwapDisassembler")
def __call__(self, info):
info = MyInfo(info)
return gdb.disassembler.builtin_disassemble(info)
gdb.disassembler.register_disassembler(NibbleSwapDisassembler())