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:
parent
0af2f23333
commit
4de4e48514
19
gdb/NEWS
19
gdb/NEWS
@ -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.
|
||||
|
@ -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
@ -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}"
|
||||
}
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user