gdb/python: extend the Python Disassembler API to allow for styling

This commit extends the Python Disassembler API to allow for styling
of the instructions.

Before this commit the Python Disassembler API allowed the user to do
two things:

  - They could intercept instruction disassembly requests and return a
    string of their choosing, this string then became the disassembled
    instruction, or

  - They could call builtin_disassemble, which would call back into
    libopcode to perform the disassembly.  As libopcode printed the
    instruction GDB would collect these print requests and build a
    string.  This string was then returned from the builtin_disassemble
    call, and the user could modify or extend this string as needed.

Neither of these approaches allowed for, or preserved, disassembler
styling, which is now available within libopcodes for many of the more
popular architectures GDB supports.

This commit aims to fill this gap.  After this commit a user will be
able to do the following things:

  - Implement a custom instruction disassembler entirely in Python
    without calling back into libopcodes, the custom disassembler will
    be able to return styling information such that GDB will display
    the instruction fully styled.  All of GDB's existing style
    settings will affect how instructions coming from the Python
    disassembler are displayed in the expected manner.

  - Call builtin_disassemble and receive a result that represents how
    libopcode would like the instruction styled.  The user can then
    adjust or extend the disassembled instruction before returning the
    result to GDB.  Again, the instruction will be styled as expected.

To achieve this I will add two new classes to GDB,
DisassemblerTextPart and DisassemblerAddressPart.

Within builtin_disassemble, instead of capturing the print calls from
libopcodes and building a single string, we will now create either a
text part or address part and store these parts in a vector.

The DisassemblerTextPart will capture a small piece of text along with
the associated style that should be used to display the text.  This
corresponds to the disassembler calling
disassemble_info::fprintf_styled_func, or for disassemblers that don't
support styling disassemble_info::fprintf_func.

The DisassemblerAddressPart is used when libopcodes requests that an
address be printed, and takes care of printing the address and
associated symbol, this corresponds to the disassembler calling
disassemble_info::print_address_func.

These parts are then placed within the DisassemblerResult when
builtin_disassemble returns.

Alternatively, the user can directly create parts by calling two new
methods on the DisassembleInfo class: DisassembleInfo.text_part and
DisassembleInfo.address_part.

Having created these parts the user can then pass these parts when
initializing a new DisassemblerResult object.

Finally, when we return from Python to gdbpy_print_insn, one way or
another, the result being returned will have a list of parts.  Back in
GDB's C++ code we walk the list of parts and call back into GDB's core
to display the disassembled instruction with the correct styling.

The new API lives in parallel with the old API.  Any existing code
that creates a DisassemblerResult using a single string immediately
creates a single DisassemblerTextPart containing the entire
instruction and gives this part the default text style.  This is also
what happens if the user calls builtin_disassemble for an architecture
that doesn't (yet) support libopcode styling.

This matches up with what happens when the Python API is not involved,
an architecture without disassembler styling support uses the old
libopcodes printing API (the API that doesn't pass style info), and
GDB just prints everything using the default text style.

The reason that parts are created by calling methods on
DisassembleInfo, rather than calling the class constructor directly,
is DisassemblerAddressPart.  Ideally this part would only hold the
address which the part represents, but in order to support backwards
compatibility we need to be able to convert the
DisassemblerAddressPart into a string.  To do that we need to call
GDB's internal print_address function, and to do that we need an
gdbarch.

What this means is that the DisassemblerAddressPart needs to take a
gdb.Architecture object at creation time.  The only valid place a user
can pull this from is from the DisassembleInfo object, so having the
DisassembleInfo act as a factory ensures that the correct gdbarch is
passed over each time.  I implemented both solutions (the one
presented here, and an alternative where parts could be constructed
directly), and this felt like the cleanest solution.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Reviewed-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
Andrew Burgess 2023-01-24 15:35:45 +00:00
parent 0af2f23333
commit 4de4e48514
5 changed files with 1366 additions and 93 deletions

View File

@ -173,6 +173,25 @@ info main
** It is now no longer possible to sub-class the
gdb.disassembler.DisassemblerResult type.
** The Disassembler API from the gdb.disassembler module has been
extended to include styling support:
- The DisassemblerResult class can now be initialized with a list
of parts. Each part represents part of the disassembled
instruction along with the associated style information. This
list of parts can be accessed with the new
DisassemblerResult.parts property.
- New constants gdb.disassembler.STYLE_* representing all the
different styles part of an instruction might have.
- New methods DisassembleInfo.text_part and
DisassembleInfo.address_part which are used to create the new
styled parts of a disassembled instruction.
- Changes are backwards compatible, the older API can still be
used to disassemble instructions without styling.
*** Changes in GDB 13
* MI version 1 is deprecated, and will be removed in GDB 14.

View File

@ -6870,6 +6870,7 @@ values can be 1 (left), 2 (middle), or 3 (right).
using the Python API. The disassembler related features are contained
within the @code{gdb.disassembler} module:
@anchor{DisassembleInfo Class}
@deftp {class} gdb.disassembler.DisassembleInfo
Disassembly is driven by instances of this class. Each time
@value{GDBN} needs to disassemble an instruction, an instance of this
@ -6971,6 +6972,25 @@ not itself raise a @code{MemoryError}.
Any other exception type raised in @code{read_memory} will propagate
back and be re-raised by @code{builtin_disassemble}.
@end defun
@defun DisassembleInfo.text_part (style, string)
Create a new @code{DisassemblerTextPart} representing a piece of a
disassembled instruction. @var{string} should be a non-empty string,
and @var{style} should be an appropriate style constant
(@pxref{Disassembler Style Constants}).
Disassembler parts are used when creating a @code{DisassemblerResult}
in order to represent the styling within an instruction
(@pxref{DisassemblerResult Class}).
@end defun
@defun DisassembleInfo.address_part (address)
Create a new @code{DisassemblerAddressPart}. @var{address} is the
value of the absolute address this part represents. A
@code{DisassemblerAddressPart} is displayed as an absolute address and
an associated symbol, the address and symbol are styled appropriately.
@end defun
@end deftp
@anchor{Disassembler Class}
@ -7024,6 +7044,7 @@ the error stream according to the @kbd{set python print-stack} setting
@end defun
@end deftp
@anchor{DisassemblerResult Class}
@deftp {class} gdb.disassembler.DisassemblerResult
This class represents the result of disassembling a single
instruction. An instance of this class will be returned from
@ -7037,11 +7058,30 @@ It is not possible to sub-class the @code{DisassemblerResult} class.
The @code{DisassemblerResult} class has the following properties and
methods:
@defun DisassemblerResult.__init__ (length, string)
@defun DisassemblerResult.__init__ (length, string, parts)
Initialize an instance of this class, @var{length} is the length of
the disassembled instruction in bytes, which must be greater than
zero, and @var{string} is a non-empty string that represents the
disassembled instruction.
zero.
Only one of @var{string} or @var{parts} should be used to initialize a
new @code{DisassemblerResult}; the other one should be passed the
value @code{None}. Alternatively, the arguments can be passed by
name, and the unused argument can be ignored.
The @var{string} argument, if not @code{None}, is a non-empty string
that represents the entire disassembled instruction. Building a result
object using the @var{string} argument does not allow for any styling
information to be included in the result. @value{GDBN} will style the
result as a single @code{DisassemblerTextPart} with @code{STYLE_TEXT}
style (@pxref{Disassembler Styling Parts}).
The @var{parts} argument, if not @code{None}, is a non-empty sequence
of @code{DisassemblerPart} objects. Each part represents a small part
of the disassembled instruction along with associated styling
information. A result object built using @var{parts} can be displayed
by @value{GDBN} with full styling information
(@pxref{style_disassembler_enabled,,@kbd{set style disassembler
enabled}}).
@end defun
@defvar DisassemblerResult.length
@ -7051,10 +7091,273 @@ instruction in bytes, this will always be greater than zero.
@defvar DisassemblerResult.string
A read-only property containing a non-empty string representing the
disassembled instruction.
disassembled instruction. The @var{string} is a representation of the
disassembled instruction without any styling information. To see how
the instruction will be styled use the @var{parts} property.
If this instance was initialized using separate
@code{DisassemblerPart} objects, the @var{string} property will still
be valid. The @var{string} value is created by concatenating the
@code{DisassemblerPart.string} values of each component part
(@pxref{Disassembler Styling Parts}).
@end defvar
@defvar DisassemblerResult.parts
A read-only property containing a non-empty sequence of
@code{DisassemblerPart} objects. Each @code{DisassemblerPart} object
contains a small part of the instruction along with information about
how that part should be styled. @value{GDBN} uses this information to
create styled disassembler output
(@pxref{style_disassembler_enabled,,@kbd{set style disassembler
enabled}}).
If this instance was initialized using a single string rather than
with a sequence of @code{DisassemblerPart} objects, the @var{parts}
property will still be valid. In this case the @var{parts} property
will hold a sequence containing a single @code{DisassemblerTextPart}
object, the string of which will represent the entire instruction, and
the style of which will be @code{STYLE_TEXT}.
@end defvar
@end deftp
@anchor{Disassembler Styling Parts}
@deftp {class} gdb.disassembler.DisassemblerPart
This is a parent class from which the different part sub-classes
inherit. Only instances of the sub-classes detailed below will be
returned by the Python API.
It is not possible to directly create instances of either this parent
class, or any of the sub-classes listed below. Instances of the
sub-classes listed below are created by calling
@code{builtin_disassemble} (@pxref{builtin_disassemble}) and are
returned within the @code{DisassemblerResult} object, or can be
created by calling the @code{text_part} and @code{address_part}
methods on the @code{DisassembleInfo} class (@pxref{DisassembleInfo
Class}).
The @code{DisassemblerPart} class has a single property:
@defvar DisassemblerPart.string
A read-only property that contains a non-empty string representing
this part of the disassembled instruction. The string within this
property doesn't include any styling information.
@end defvar
@end deftp
@deftp {class} gdb.disassembler.DisassemblerTextPart
The @code{DisassemblerTextPart} class represents a piece of the
disassembled instruction and the associated style for that piece.
Instances of this class can't be created directly, instead call
@code{DisassembleInfo.text_part} to create a new instance of this
class (@pxref{DisassembleInfo Class}).
As well as the properties of its parent class, the
@code{DisassemblerTextPart} has the following additional property:
@defvar DisassemblerTextPart.style
A read-only property that contains one of the defined style constants.
@value{GDBN} will use this style when styling this part of the
disassembled instruction (@pxref{Disassembler Style Constants}).
@end defvar
@end deftp
@deftp {class} gdb.disassembler.DisassemblerAddressPart
The @code{DisassemblerAddressPart} class represents an absolute
address within a disassembled instruction. Using a
@code{DisassemblerAddressPart} instead of a
@code{DisassemblerTextPart} with @code{STYLE_ADDRESS} is preferred,
@value{GDBN} will display the address as both an absolute address, and
will look up a suitable symbol to display next to the address. Using
@code{DisassemblerAddressPart} also ensures that user settings such as
@code{set print max-symbolic-offset} are respected.
Here is an example of an x86-64 instruction:
@smallexample
call 0x401136 <foo>
@end smallexample
@noindent
In this instruction the @code{0x401136 <foo>} was generated from a
single @code{DisassemblerAddressPart}. The @code{0x401136} will be
styled with @code{STYLE_ADDRESS}, and @code{foo} will be styled with
@code{STYLE_SYMBOL}. The @code{<} and @code{>} will be styled as
@code{STYLE_TEXT}.
If the inclusion of the symbol name is not required then a
@code{DisassemblerTextPart} with style @code{STYLE_ADDRESS} can be
used instead.
Instances of this class can't be created directly, instead call
@code{DisassembleInfo.address_part} to create a new instance of this
class (@pxref{DisassembleInfo Class}).
As well as the properties of its parent class, the
@code{DisassemblerAddressPart} has the following additional property:
@defvar DisassemblerAddressPart.address
A read-only property that contains the @var{address} passed to this
object's @code{__init__} method.
@end defvar
@end deftp
@anchor{Disassembler Style Constants}
The following table lists all of the disassembler styles that are
available. @value{GDBN} maps these style constants onto its style
settings (@pxref{Output Styling}). In some cases, several style
constants produce the same style settings, and thus will produce the
same visual effect on the screen. This could change in future
releases of @value{GDBN}, so care should be taken to select the
correct style constant to ensure correct output styling in future
releases of @value{GDBN}.
@vtable @code
@vindex STYLE_TEXT
@item gdb.disassembler.STYLE_TEXT
This is the default style used by @value{GDBN} when styling
disassembler output. This style should be used for any parts of the
instruction that don't fit any of the other styles listed below.
@value{GDBN} styles text with this style using its default style.
@vindex STYLE_MNEMONIC
@item gdb.disassembler.STYLE_MNEMONIC
This style is used for styling the primary instruction mnemonic, which
usually appears at, or near, the start of the disassembled instruction
string.
@value{GDBN} styles text with this style using the @code{disassembler
mnemonic} style setting.
@vindex STYLE_SUB_MNEMONIC
@item gdb.disassembler.STYLE_SUB_MNEMONIC
This style is used for styling any sub-mnemonics within a disassembled
instruction. A sub-mnemonic is any text within the instruction that
controls the function of the instruction, but which is disjoint from
the primary mnemonic (which will have styled @code{STYLE_MNEMONIC}).
As an example, consider this AArch64 instruction:
@smallexample
add w16, w7, w1, lsl #1
@end smallexample
@noindent
The @code{add} is the primary instruction mnemonic, and would be given
style @code{STYLE_MNEMONIC}, while @code{lsl} is the sub-mnemonic, and
would be given the style @code{STYLE_SUB_MNEMONIC}.
@value{GDBN} styles text with this style using the @code{disassembler
mnemonic} style setting.
@vindex STYLE_ASSEMBLER_DIRECTIVE
@item gdb.disassembler.STYLE_ASSEMBLER_DIRECTIVE
Sometimes a series of bytes doesn't decode to a valid instruction. In
this case the disassembler may choose to represent the result of
disassembling using an assembler directive, for example:
@smallexample
.word 0x1234
@end smallexample
@noindent
In this case, the @code{.word} would be give the
@code{STYLE_ASSEMBLER_DIRECTIVE} style. An assembler directive is
similar to a mnemonic in many ways but is something that is not part
of the architecture's instruction set.
@value{GDBN} styles text with this style using the @code{disassembler
mnemonic} style setting.
@vindex STYLE_REGISTER
@item gdb.disassembler.STYLE_REGISTER
This style is used for styling any text that represents a register
name, or register number, within a disassembled instruction.
@value{GDBN} styles text with this style using the @code{disassembler
register} style setting.
@vindex STYLE_ADDRESS
@item gdb.disassembler.STYLE_ADDRESS
This style is used for styling numerical values that represent
absolute addresses within the disassembled instruction.
When creating a @code{DisassemblerTextPart} with this style, you
should consider if a @code{DisassemblerAddressPart} would be more
appropriate. See @ref{Disassembler Styling Parts} for a description
of what each part offers.
@value{GDBN} styles text with this style using the @code{disassembler
address} style setting.
@vindex STYLE_ADDRESS_OFFSET
@item gdb.disassembler.STYLE_ADDRESS_OFFSET
This style is used for styling numerical values that represent offsets
to addresses within the disassembled instruction. A value is
considered an address offset when the instruction itself is going to
access memory, and the value is being used to offset which address is
accessed.
For example, an architecture might have an instruction that loads from
memory using an address within a register. If that instruction also
allowed for an immediate offset to be encoded into the instruction,
this would be an address offset. Similarly, a branch instruction
might jump to an address in a register plus an address offset that is
encoded into the instruction.
@value{GDBN} styles text with this style using the @code{disassembler
immediate} style setting.
@vindex STYLE_IMMEDIATE
@item gdb.disassembler.STYLE_IMMEDIATE
Use @code{STYLE_IMMEDIATE} for any numerical values within a
disassembled instruction when those values are not addresses, address
offsets, or register numbers (The styles @code{STYLE_ADDRESS},
@code{STYLE_ADDRESS_OFFSET}, or @code{STYLE_REGISTER} can be used in
those cases).
@value{GDBN} styles text with this style using the @code{disassembler
immediate} style setting.
@vindex STYLE_SYMBOL
@item gdb.disassembler.STYLE_SYMBOL
This style is used for styling the textual name of a symbol that is
included within a disassembled instruction. A symbol name is often
included next to an absolute address within a disassembled instruction
to make it easier for the user to understand what the address is
referring too. For example:
@smallexample
call 0x401136 <foo>
@end smallexample
@noindent
Here @code{foo} is the name of a symbol, and should be given the
@code{STYLE_SYMBOL} style.
Adding symbols next to absolute addresses like this is handled
automatically by the @code{DisassemblerAddressPart} class
(@pxref{Disassembler Styling Parts}).
@value{GDBN} styles text with this style using the @code{disassembler
symbol} style setting.
@vindex STYLE_COMMENT_START
@item gdb.disassembler.STYLE_COMMENT_START
This style is used to start a line comment in the disassembly output.
Unlike other styles, which only apply to the single
@code{DisassemblerTextPiece} to which they are applied, the comment
style is sticky, and overrides the style of any further pieces within
this instruction.
This means that, after a @code{STYLE_COMMENT_START} piece has been
seen, @value{GDBN} will apply the comment style until the end of the
line, ignoring the specific style within a piece.
@value{GDBN} styles text with this style using the @code{disassembler
comment} style setting.
@end vtable
The following functions are also contained in the
@code{gdb.disassembler} module:

File diff suppressed because it is too large Load Diff

View File

@ -69,6 +69,12 @@ set nop "(nop|nop\t0)"
set unknown_error_pattern "unknown disassembler error \\(error = -1\\)"
set addr_pattern "\r\n=> ${curr_pc_pattern} <\[^>\]+>:\\s+"
set base_pattern "${addr_pattern}${nop}"
# Helper proc to format a Python exception of TYPE with MSG.
proc make_exception_pattern { type msg } {
return "${::addr_pattern}Python Exception <class '$type'>: $msg\r\n\r\n${::unknown_error_pattern}"
}
set test_plans \
[list \
[list "" "${base_pattern}\r\n.*"] \
@ -90,13 +96,40 @@ set test_plans \
[list "RethrowMemoryErrorDisassembler" "${addr_pattern}Cannot access memory at address $hex"] \
[list "ReadMemoryMemoryErrorDisassembler" "${addr_pattern}Cannot access memory at address ${curr_pc_pattern}"] \
[list "ReadMemoryGdbErrorDisassembler" "${addr_pattern}read_memory raised GdbError\r\n${unknown_error_pattern}"] \
[list "ReadMemoryRuntimeErrorDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: read_memory raised RuntimeError\r\n\r\n${unknown_error_pattern}"] \
[list "ReadMemoryRuntimeErrorDisassembler" \
[make_exception_pattern "RuntimeError" \
"read_memory raised RuntimeError"]] \
[list "ReadMemoryCaughtMemoryErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \
[list "ReadMemoryCaughtGdbErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \
[list "ReadMemoryCaughtRuntimeErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \
[list "MemorySourceNotABufferDisassembler" "${addr_pattern}Python Exception <class 'TypeError'>: Result from read_memory is not a buffer\r\n\r\n${unknown_error_pattern}"] \
[list "MemorySourceBufferTooLongDisassembler" "${addr_pattern}Python Exception <class 'ValueError'>: Buffer returned from read_memory is sized $decimal instead of the expected $decimal\r\n\r\n${unknown_error_pattern}"] \
[list "ResultOfWrongType" "${addr_pattern}Python Exception <class 'TypeError'>: Result is not a DisassemblerResult.\r\n.*"]]
[list "MemorySourceNotABufferDisassembler" \
[make_exception_pattern "TypeError" \
"Result from read_memory is not a buffer"]] \
[list "MemorySourceBufferTooLongDisassembler" \
[make_exception_pattern "ValueError" \
"Buffer returned from read_memory is sized $decimal instead of the expected $decimal"]] \
[list "ResultOfWrongType" \
[make_exception_pattern "TypeError" \
"Result is not a DisassemblerResult."]] \
[list "ErrorCreatingTextPart_NoArgs" \
[make_exception_pattern "TypeError" \
"function missing required argument 'style' \\(pos 1\\)"]] \
[list "ErrorCreatingAddressPart_NoArgs" \
[make_exception_pattern "TypeError" \
"function missing required argument 'address' \\(pos 1\\)"]] \
[list "ErrorCreatingTextPart_NoString" \
[make_exception_pattern "TypeError" \
"function missing required argument 'string' \\(pos 2\\)"]] \
[list "ErrorCreatingTextPart_NoStyle" \
[make_exception_pattern "TypeError" \
"function missing required argument 'style' \\(pos 1\\)"]] \
[list "All_Text_Part_Styles" "${addr_pattern}p1p2p3p4p5p6p7p8p9p10\r\n.*"] \
[list "ErrorCreatingTextPart_StringAndParts" \
[make_exception_pattern "ValueError" \
"Cannot use 'string' and 'parts' when creating gdb\\.disassembler\\.DisassemblerResult\\."]] \
[list "Build_Result_Using_All_Parts" \
"${addr_pattern}fake\treg, ${curr_pc_pattern}(?: <\[^>\]+>)?, 123\r\n.*"] \
]
# Now execute each test plan.
foreach plan $test_plans {
@ -216,13 +249,48 @@ with_test_prefix "Bad DisassembleInfo creation" {
"Error while executing Python code\\."]
}
# Test that we can't inherit from the DisassemblerResult class.
gdb_test_multiline "Sub-class a breakpoint" \
"python" "" \
"class InvalidResultType(gdb.disassembler.DisassemblerResult):" "" \
" def __init__(self):" "" \
" pass" "" \
"end" \
# Some of the disassembler related types should not be sub-typed,
# check these now.
with_test_prefix "check inheritance" {
foreach_with_prefix type {gdb.disassembler.DisassemblerResult \
gdb.disassembler.DisassemblerPart
gdb.disassembler.DisassemblerTextPart \
gdb.disassembler.DisassemblerAddressPart} {
set type_ptn [string_to_regexp $type]
gdb_test_multiline "Sub-class a breakpoint" \
"python" "" \
"class InvalidResultType($type):" "" \
" def __init__(self):" "" \
" pass" "" \
"end" \
[multi_line \
"TypeError: type '${type_ptn}' is not an acceptable base type" \
"Error while executing Python code\\."]
}
}
# Test some error conditions when creating a DisassemblerResult object.
gdb_test "python result = gdb.disassembler.DisassemblerResult()" \
[multi_line \
"TypeError: type 'gdb\\.disassembler\\.DisassemblerResult' is not an acceptable base type" \
"Error while executing Python code\\."]
"TypeError: function missing required argument 'length' \\(pos 1\\)" \
"Error while executing Python code\\."] \
"try to create a DisassemblerResult without a length argument"
foreach len {0 -1} {
gdb_test "python result = gdb.disassembler.DisassemblerResult($len)" \
[multi_line \
"ValueError: Length must be greater than 0\\." \
"Error while executing Python code\\."] \
"try to create a DisassemblerResult with length $len"
}
# Check we can't directly create DisassemblerTextPart or
# DisassemblerAddressPart objects.
foreach type {DisassemblerTextPart DisassemblerAddressPart} {
gdb_test "python result = gdb.disassembler.${type}()" \
[multi_line \
"RuntimeError: Cannot create instances of DisassemblerPart\\." \
"Error while executing Python code\\."] \
"try to create an instance of ${type}"
}

View File

@ -25,6 +25,26 @@ from gdb.disassembler import Disassembler, DisassemblerResult
current_pc = None
def builtin_disassemble_wrapper(info):
result = gdb.disassembler.builtin_disassemble(info)
assert result.length > 0
assert len(result.parts) > 0
tmp_str = ""
for p in result.parts:
assert(p.string == str(p))
tmp_str += p.string
assert tmp_str == result.string
return result
def check_building_disassemble_result():
"""Check that we can create DisassembleResult objects correctly."""
result = gdb.disassembler.DisassemblerResult()
print("PASS")
def is_nop(s):
return s == "nop" or s == "nop\t0"
@ -70,7 +90,7 @@ class ShowInfoRepr(TestDisassembler):
def disassemble(self, info):
comment = "\t## " + repr(info)
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
string = result.string + comment
length = result.length
return DisassemblerResult(length=length, string=string)
@ -94,7 +114,7 @@ class ShowInfoSubClassRepr(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
comment = "\t## " + repr(info)
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
string = result.string + comment
length = result.length
return DisassemblerResult(length=length, string=string)
@ -106,7 +126,7 @@ class ShowResultRepr(TestDisassembler):
output."""
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
comment = "\t## " + repr(result)
string = result.string + comment
length = result.length
@ -118,11 +138,11 @@ class ShowResultStr(TestDisassembler):
resulting string in a comment within the disassembler output."""
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
comment = "\t## " + str(result)
string = result.string + comment
length = result.length
return DisassemblerResult(length=length, string=string)
return DisassemblerResult(length=length, string=string, parts=None)
class GlobalPreInfoDisassembler(TestDisassembler):
@ -138,7 +158,7 @@ class GlobalPreInfoDisassembler(TestDisassembler):
if not isinstance(ar, gdb.Architecture):
raise gdb.GdbError("invalid architecture type")
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
text = result.string + "\t## ad = 0x%x, ar = %s" % (ad, ar.name())
return DisassemblerResult(result.length, text)
@ -148,7 +168,7 @@ class GlobalPostInfoDisassembler(TestDisassembler):
"""Check the attributes of DisassembleInfo after disassembly has occurred."""
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
ad = info.address
ar = info.architecture
@ -169,7 +189,7 @@ class GlobalReadDisassembler(TestDisassembler):
adds them as a comment to the disassembler output."""
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
len = result.length
str = ""
for o in range(len):
@ -187,7 +207,7 @@ class GlobalAddrDisassembler(TestDisassembler):
"""Check the gdb.format_address method."""
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
arch = info.architecture
addr = info.address
program_space = info.progspace
@ -214,7 +234,7 @@ class GdbErrorLateDisassembler(TestDisassembler):
"""Raise a GdbError after calling the builtin disassembler."""
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
raise gdb.GdbError("GdbError after builtin disassembler")
@ -222,7 +242,7 @@ class RuntimeErrorLateDisassembler(TestDisassembler):
"""Raise a RuntimeError after calling the builtin disassembler."""
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
raise RuntimeError("RuntimeError after builtin disassembler")
@ -235,7 +255,7 @@ class MemoryErrorEarlyDisassembler(TestDisassembler):
info.read_memory(1, -info.address + 2)
except gdb.MemoryError:
tag = "## AFTER ERROR"
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
text = result.string + "\t" + tag
return DisassemblerResult(result.length, text)
@ -245,7 +265,7 @@ class MemoryErrorLateDisassembler(TestDisassembler):
before we return a result."""
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
# The following read will throw an error.
info.read_memory(1, -info.address + 2)
return DisassemblerResult(1, "BAD")
@ -282,7 +302,7 @@ class TaggingDisassembler(TestDisassembler):
self._tag = tag
def disassemble(self, info):
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
text = result.string + "\t## tag = %s" % self._tag
return DisassemblerResult(result.length, text)
@ -307,7 +327,7 @@ class GlobalCachingDisassembler(TestDisassembler):
and cache the DisassembleInfo so that it is not garbage collected."""
GlobalCachingDisassembler.cached_insn_disas.append(info)
GlobalCachingDisassembler.cached_insn_disas.append(self.MyInfo(info))
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
text = result.string + "\t## CACHED"
return DisassemblerResult(result.length, text)
@ -373,7 +393,7 @@ class ReadMemoryMemoryErrorDisassembler(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
class ReadMemoryGdbErrorDisassembler(TestDisassembler):
@ -389,7 +409,7 @@ class ReadMemoryGdbErrorDisassembler(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
class ReadMemoryRuntimeErrorDisassembler(TestDisassembler):
@ -405,7 +425,7 @@ class ReadMemoryRuntimeErrorDisassembler(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
class ReadMemoryCaughtMemoryErrorDisassembler(TestDisassembler):
@ -422,7 +442,7 @@ class ReadMemoryCaughtMemoryErrorDisassembler(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
try:
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
except gdb.MemoryError:
return None
@ -441,7 +461,7 @@ class ReadMemoryCaughtGdbErrorDisassembler(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
try:
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
except gdb.GdbError as e:
if e.args[0] == "exception message":
return None
@ -462,7 +482,7 @@ class ReadMemoryCaughtRuntimeErrorDisassembler(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
try:
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
except RuntimeError as e:
if e.args[0] == "exception message":
return None
@ -479,7 +499,7 @@ class MemorySourceNotABufferDisassembler(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
class MemorySourceBufferTooLongDisassembler(TestDisassembler):
@ -501,7 +521,101 @@ class MemorySourceBufferTooLongDisassembler(TestDisassembler):
def disassemble(self, info):
info = self.MyInfo(info)
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
class ErrorCreatingTextPart_NoArgs(TestDisassembler):
"""Try to create a DisassemblerTextPart with no arguments."""
def disassemble(self, info):
part = info.text_part()
return None
class ErrorCreatingAddressPart_NoArgs(TestDisassembler):
"""Try to create a DisassemblerAddressPart with no arguments."""
def disassemble(self, info):
part = info.address_part()
return None
class ErrorCreatingTextPart_NoString(TestDisassembler):
"""Try to create a DisassemblerTextPart with no string argument."""
def disassemble(self, info):
part = info.text_part(gdb.disassembler.STYLE_TEXT)
return None
class ErrorCreatingTextPart_NoStyle(TestDisassembler):
"""Try to create a DisassemblerTextPart with no string argument."""
def disassemble(self, info):
part = info.text_part(string="abc")
return None
class ErrorCreatingTextPart_StringAndParts(TestDisassembler):
"""Try to create a DisassemblerTextPart with both a string and a parts list."""
def disassemble(self, info):
parts = []
parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, "p1"))
parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, "p2"))
return DisassemblerResult(length=4, string="p1p2", parts=parts)
class All_Text_Part_Styles(TestDisassembler):
"""Create text parts with all styles."""
def disassemble(self, info):
parts = []
parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, "p1"))
parts.append(info.text_part(gdb.disassembler.STYLE_MNEMONIC, "p2"))
parts.append(info.text_part(gdb.disassembler.STYLE_SUB_MNEMONIC, "p3"))
parts.append(info.text_part(gdb.disassembler.STYLE_ASSEMBLER_DIRECTIVE, "p4"))
parts.append(info.text_part(gdb.disassembler.STYLE_REGISTER, "p5"))
parts.append(info.text_part(gdb.disassembler.STYLE_IMMEDIATE, "p6"))
parts.append(info.text_part(gdb.disassembler.STYLE_ADDRESS, "p7"))
parts.append(info.text_part(gdb.disassembler.STYLE_ADDRESS_OFFSET, "p8"))
parts.append(info.text_part(gdb.disassembler.STYLE_SYMBOL, "p9"))
parts.append(info.text_part(gdb.disassembler.STYLE_COMMENT_START, "p10"))
result = builtin_disassemble_wrapper(info)
result = DisassemblerResult(length=result.length, parts=parts)
tmp_str = "";
for p in parts:
assert (p.string == str(p))
tmp_str += str(p)
assert tmp_str == result.string
return result
class Build_Result_Using_All_Parts(TestDisassembler):
"""Disassemble an instruction and return a result that makes use of
text and address parts."""
def disassemble(self, info):
global current_pc
parts = []
parts.append(info.text_part(gdb.disassembler.STYLE_MNEMONIC, "fake"))
parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, "\t"))
parts.append(info.text_part(gdb.disassembler.STYLE_REGISTER, "reg"))
parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, ", "))
addr_part = info.address_part(current_pc)
assert addr_part.address == current_pc
parts.append(addr_part)
parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, ", "))
parts.append(info.text_part(gdb.disassembler.STYLE_IMMEDIATE, "123"))
result = builtin_disassemble_wrapper(info)
result = DisassemblerResult(length=result.length, parts=parts)
return result
class BuiltinDisassembler(Disassembler):
@ -511,7 +625,7 @@ class BuiltinDisassembler(Disassembler):
super().__init__("BuiltinDisassembler")
def __call__(self, info):
return gdb.disassembler.builtin_disassemble(info)
return builtin_disassemble_wrapper(info)
class AnalyzingDisassembler(Disassembler):
@ -606,7 +720,7 @@ class AnalyzingDisassembler(Disassembler):
# Override the info object, this provides access to our
# read_memory function.
info = self.MyInfo(info, self._start, self._end, self._nop_bytes)
result = gdb.disassembler.builtin_disassemble(info)
result = builtin_disassemble_wrapper(info)
# Record some informaiton about the first 'nop' instruction we find.
if self._nop_index is None and is_nop(result.string):