Home | History | Annotate | Line # | Download | only in gdb.python
      1  1.1.1.2  christos # Copyright (C) 2021-2024 Free Software Foundation, Inc.
      2      1.1  christos 
      3      1.1  christos # This program is free software; you can redistribute it and/or modify
      4      1.1  christos # it under the terms of the GNU General Public License as published by
      5      1.1  christos # the Free Software Foundation; either version 3 of the License, or
      6      1.1  christos # (at your option) any later version.
      7      1.1  christos #
      8      1.1  christos # This program is distributed in the hope that it will be useful,
      9      1.1  christos # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10      1.1  christos # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11      1.1  christos # GNU General Public License for more details.
     12      1.1  christos #
     13      1.1  christos # You should have received a copy of the GNU General Public License
     14      1.1  christos # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     15      1.1  christos 
     16      1.1  christos import struct
     17      1.1  christos import sys
     18      1.1  christos 
     19  1.1.1.2  christos import gdb
     20  1.1.1.2  christos import gdb.disassembler
     21      1.1  christos from gdb.disassembler import Disassembler, DisassemblerResult
     22      1.1  christos 
     23      1.1  christos # A global, holds the program-counter address at which we should
     24      1.1  christos # perform the extra disassembly that this script provides.
     25      1.1  christos current_pc = None
     26      1.1  christos 
     27      1.1  christos 
     28  1.1.1.2  christos def builtin_disassemble_wrapper(info):
     29  1.1.1.2  christos     result = gdb.disassembler.builtin_disassemble(info)
     30  1.1.1.2  christos     assert result.length > 0
     31  1.1.1.2  christos     assert len(result.parts) > 0
     32  1.1.1.2  christos     tmp_str = ""
     33  1.1.1.2  christos     for p in result.parts:
     34  1.1.1.2  christos         assert p.string == str(p)
     35  1.1.1.2  christos         tmp_str += p.string
     36  1.1.1.2  christos     assert tmp_str == result.string
     37  1.1.1.2  christos     return result
     38  1.1.1.2  christos 
     39  1.1.1.2  christos 
     40  1.1.1.2  christos def check_building_disassemble_result():
     41  1.1.1.2  christos     """Check that we can create DisassembleResult objects correctly."""
     42  1.1.1.2  christos 
     43  1.1.1.2  christos     result = gdb.disassembler.DisassemblerResult()
     44  1.1.1.2  christos 
     45  1.1.1.2  christos     print("PASS")
     46  1.1.1.2  christos 
     47  1.1.1.2  christos 
     48      1.1  christos def is_nop(s):
     49  1.1.1.2  christos     return s == "nop" or s == "nop\t0" or s == "nop\t{0}"
     50      1.1  christos 
     51      1.1  christos 
     52      1.1  christos # Remove all currently registered disassemblers.
     53      1.1  christos def remove_all_python_disassemblers():
     54      1.1  christos     for a in gdb.architecture_names():
     55      1.1  christos         gdb.disassembler.register_disassembler(None, a)
     56      1.1  christos     gdb.disassembler.register_disassembler(None, None)
     57      1.1  christos 
     58      1.1  christos 
     59      1.1  christos class TestDisassembler(Disassembler):
     60      1.1  christos     """A base class for disassemblers within this script to inherit from.
     61      1.1  christos     Implements the __call__ method and ensures we only do any
     62      1.1  christos     disassembly wrapping for the global CURRENT_PC."""
     63      1.1  christos 
     64      1.1  christos     def __init__(self):
     65      1.1  christos         global current_pc
     66      1.1  christos 
     67      1.1  christos         super().__init__("TestDisassembler")
     68      1.1  christos         self.__info = None
     69      1.1  christos         if current_pc == None:
     70      1.1  christos             raise gdb.GdbError("no current_pc set")
     71      1.1  christos 
     72      1.1  christos     def __call__(self, info):
     73      1.1  christos         global current_pc
     74      1.1  christos 
     75      1.1  christos         if info.address != current_pc:
     76      1.1  christos             return None
     77      1.1  christos         self.__info = info
     78      1.1  christos         return self.disassemble(info)
     79      1.1  christos 
     80      1.1  christos     def get_info(self):
     81      1.1  christos         return self.__info
     82      1.1  christos 
     83      1.1  christos     def disassemble(self, info):
     84      1.1  christos         raise NotImplementedError("override the disassemble method")
     85      1.1  christos 
     86      1.1  christos 
     87  1.1.1.2  christos class ShowInfoRepr(TestDisassembler):
     88  1.1.1.2  christos     """Call the __repr__ method on the DisassembleInfo, convert the result
     89  1.1.1.2  christos     to a string, and incude it in a comment in the disassembler output."""
     90  1.1.1.2  christos 
     91  1.1.1.2  christos     def disassemble(self, info):
     92  1.1.1.2  christos         comment = "\t## " + repr(info)
     93  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
     94  1.1.1.2  christos         string = result.string + comment
     95  1.1.1.2  christos         length = result.length
     96  1.1.1.2  christos         return DisassemblerResult(length=length, string=string)
     97  1.1.1.2  christos 
     98  1.1.1.2  christos 
     99  1.1.1.2  christos class ShowInfoSubClassRepr(TestDisassembler):
    100  1.1.1.2  christos     """Create a sub-class of DisassembleInfo.  Create an instace of this
    101  1.1.1.2  christos     sub-class and call the __repr__ method on it.  Convert the result
    102  1.1.1.2  christos     to a string, and incude it in a comment in the disassembler
    103  1.1.1.2  christos     output.  The DisassembleInfo sub-class does not override __repr__
    104  1.1.1.2  christos     so we are calling the implementation on the parent class."""
    105  1.1.1.2  christos 
    106  1.1.1.2  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    107  1.1.1.2  christos         """A wrapper around DisassembleInfo, doesn't add any new
    108  1.1.1.2  christos         functionality, just gives a new name in order to check the
    109  1.1.1.2  christos         __repr__ functionality."""
    110  1.1.1.2  christos 
    111  1.1.1.2  christos         def __init__(self, info):
    112  1.1.1.2  christos             super().__init__(info)
    113  1.1.1.2  christos 
    114  1.1.1.2  christos     def disassemble(self, info):
    115  1.1.1.2  christos         info = self.MyInfo(info)
    116  1.1.1.2  christos         comment = "\t## " + repr(info)
    117  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    118  1.1.1.2  christos         string = result.string + comment
    119  1.1.1.2  christos         length = result.length
    120  1.1.1.2  christos         return DisassemblerResult(length=length, string=string)
    121  1.1.1.2  christos 
    122  1.1.1.2  christos 
    123  1.1.1.2  christos class ShowResultRepr(TestDisassembler):
    124  1.1.1.2  christos     """Call the __repr__ method on the DisassemblerResult, convert the
    125  1.1.1.2  christos     result to a string, and incude it in a comment in the disassembler
    126  1.1.1.2  christos     output."""
    127  1.1.1.2  christos 
    128  1.1.1.2  christos     def disassemble(self, info):
    129  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    130  1.1.1.2  christos         comment = "\t## " + repr(result)
    131  1.1.1.2  christos         string = result.string + comment
    132  1.1.1.2  christos         length = result.length
    133  1.1.1.2  christos         return DisassemblerResult(length=length, string=string)
    134  1.1.1.2  christos 
    135  1.1.1.2  christos 
    136  1.1.1.2  christos class ShowResultStr(TestDisassembler):
    137  1.1.1.2  christos     """Call the __str__ method on a DisassemblerResult object, incude the
    138  1.1.1.2  christos     resulting string in a comment within the disassembler output."""
    139  1.1.1.2  christos 
    140  1.1.1.2  christos     def disassemble(self, info):
    141  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    142  1.1.1.2  christos         comment = "\t## " + str(result)
    143  1.1.1.2  christos         string = result.string + comment
    144  1.1.1.2  christos         length = result.length
    145  1.1.1.2  christos         return DisassemblerResult(length=length, string=string, parts=None)
    146  1.1.1.2  christos 
    147  1.1.1.2  christos 
    148      1.1  christos class GlobalPreInfoDisassembler(TestDisassembler):
    149      1.1  christos     """Check the attributes of DisassembleInfo before disassembly has occurred."""
    150      1.1  christos 
    151      1.1  christos     def disassemble(self, info):
    152      1.1  christos         ad = info.address
    153      1.1  christos         ar = info.architecture
    154      1.1  christos 
    155      1.1  christos         if ad != current_pc:
    156      1.1  christos             raise gdb.GdbError("invalid address")
    157      1.1  christos 
    158      1.1  christos         if not isinstance(ar, gdb.Architecture):
    159      1.1  christos             raise gdb.GdbError("invalid architecture type")
    160      1.1  christos 
    161  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    162      1.1  christos 
    163      1.1  christos         text = result.string + "\t## ad = 0x%x, ar = %s" % (ad, ar.name())
    164      1.1  christos         return DisassemblerResult(result.length, text)
    165      1.1  christos 
    166      1.1  christos 
    167      1.1  christos class GlobalPostInfoDisassembler(TestDisassembler):
    168      1.1  christos     """Check the attributes of DisassembleInfo after disassembly has occurred."""
    169      1.1  christos 
    170      1.1  christos     def disassemble(self, info):
    171  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    172      1.1  christos 
    173      1.1  christos         ad = info.address
    174      1.1  christos         ar = info.architecture
    175      1.1  christos 
    176      1.1  christos         if ad != current_pc:
    177      1.1  christos             raise gdb.GdbError("invalid address")
    178      1.1  christos 
    179      1.1  christos         if not isinstance(ar, gdb.Architecture):
    180      1.1  christos             raise gdb.GdbError("invalid architecture type")
    181      1.1  christos 
    182      1.1  christos         text = result.string + "\t## ad = 0x%x, ar = %s" % (ad, ar.name())
    183      1.1  christos         return DisassemblerResult(result.length, text)
    184      1.1  christos 
    185      1.1  christos 
    186      1.1  christos class GlobalReadDisassembler(TestDisassembler):
    187      1.1  christos     """Check the DisassembleInfo.read_memory method.  Calls the builtin
    188      1.1  christos     disassembler, then reads all of the bytes of this instruction, and
    189      1.1  christos     adds them as a comment to the disassembler output."""
    190      1.1  christos 
    191      1.1  christos     def disassemble(self, info):
    192  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    193      1.1  christos         len = result.length
    194      1.1  christos         str = ""
    195      1.1  christos         for o in range(len):
    196      1.1  christos             if str != "":
    197      1.1  christos                 str += " "
    198      1.1  christos             v = bytes(info.read_memory(1, o))[0]
    199      1.1  christos             if sys.version_info[0] < 3:
    200      1.1  christos                 v = struct.unpack("<B", v)
    201      1.1  christos             str += "0x%02x" % v
    202      1.1  christos         text = result.string + "\t## bytes = %s" % str
    203      1.1  christos         return DisassemblerResult(result.length, text)
    204      1.1  christos 
    205      1.1  christos 
    206      1.1  christos class GlobalAddrDisassembler(TestDisassembler):
    207      1.1  christos     """Check the gdb.format_address method."""
    208      1.1  christos 
    209      1.1  christos     def disassemble(self, info):
    210  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    211      1.1  christos         arch = info.architecture
    212      1.1  christos         addr = info.address
    213      1.1  christos         program_space = info.progspace
    214      1.1  christos         str = gdb.format_address(addr, program_space, arch)
    215      1.1  christos         text = result.string + "\t## addr = %s" % str
    216      1.1  christos         return DisassemblerResult(result.length, text)
    217      1.1  christos 
    218      1.1  christos 
    219      1.1  christos class GdbErrorEarlyDisassembler(TestDisassembler):
    220      1.1  christos     """Raise a GdbError instead of performing any disassembly."""
    221      1.1  christos 
    222      1.1  christos     def disassemble(self, info):
    223      1.1  christos         raise gdb.GdbError("GdbError instead of a result")
    224      1.1  christos 
    225      1.1  christos 
    226      1.1  christos class RuntimeErrorEarlyDisassembler(TestDisassembler):
    227      1.1  christos     """Raise a RuntimeError instead of performing any disassembly."""
    228      1.1  christos 
    229      1.1  christos     def disassemble(self, info):
    230      1.1  christos         raise RuntimeError("RuntimeError instead of a result")
    231      1.1  christos 
    232      1.1  christos 
    233      1.1  christos class GdbErrorLateDisassembler(TestDisassembler):
    234      1.1  christos     """Raise a GdbError after calling the builtin disassembler."""
    235      1.1  christos 
    236      1.1  christos     def disassemble(self, info):
    237  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    238      1.1  christos         raise gdb.GdbError("GdbError after builtin disassembler")
    239      1.1  christos 
    240      1.1  christos 
    241      1.1  christos class RuntimeErrorLateDisassembler(TestDisassembler):
    242      1.1  christos     """Raise a RuntimeError after calling the builtin disassembler."""
    243      1.1  christos 
    244      1.1  christos     def disassemble(self, info):
    245  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    246      1.1  christos         raise RuntimeError("RuntimeError after builtin disassembler")
    247      1.1  christos 
    248      1.1  christos 
    249      1.1  christos class MemoryErrorEarlyDisassembler(TestDisassembler):
    250      1.1  christos     """Throw a memory error, ignore the error and disassemble."""
    251      1.1  christos 
    252      1.1  christos     def disassemble(self, info):
    253      1.1  christos         tag = "## FAIL"
    254      1.1  christos         try:
    255      1.1  christos             info.read_memory(1, -info.address + 2)
    256      1.1  christos         except gdb.MemoryError:
    257      1.1  christos             tag = "## AFTER ERROR"
    258  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    259      1.1  christos         text = result.string + "\t" + tag
    260      1.1  christos         return DisassemblerResult(result.length, text)
    261      1.1  christos 
    262      1.1  christos 
    263      1.1  christos class MemoryErrorLateDisassembler(TestDisassembler):
    264      1.1  christos     """Throw a memory error after calling the builtin disassembler, but
    265      1.1  christos     before we return a result."""
    266      1.1  christos 
    267      1.1  christos     def disassemble(self, info):
    268  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    269      1.1  christos         # The following read will throw an error.
    270      1.1  christos         info.read_memory(1, -info.address + 2)
    271      1.1  christos         return DisassemblerResult(1, "BAD")
    272      1.1  christos 
    273      1.1  christos 
    274      1.1  christos class RethrowMemoryErrorDisassembler(TestDisassembler):
    275      1.1  christos     """Catch and rethrow a memory error."""
    276      1.1  christos 
    277      1.1  christos     def disassemble(self, info):
    278      1.1  christos         try:
    279      1.1  christos             info.read_memory(1, -info.address + 2)
    280      1.1  christos         except gdb.MemoryError as e:
    281      1.1  christos             raise gdb.MemoryError("cannot read code at address 0x2")
    282      1.1  christos         return DisassemblerResult(1, "BAD")
    283      1.1  christos 
    284      1.1  christos 
    285      1.1  christos class ResultOfWrongType(TestDisassembler):
    286      1.1  christos     """Return something that is not a DisassemblerResult from disassemble method"""
    287      1.1  christos 
    288      1.1  christos     class Blah:
    289      1.1  christos         def __init__(self, length, string):
    290      1.1  christos             self.length = length
    291      1.1  christos             self.string = string
    292      1.1  christos 
    293      1.1  christos     def disassemble(self, info):
    294      1.1  christos         return self.Blah(1, "ABC")
    295      1.1  christos 
    296      1.1  christos 
    297      1.1  christos class TaggingDisassembler(TestDisassembler):
    298      1.1  christos     """A simple disassembler that just tags the output."""
    299      1.1  christos 
    300      1.1  christos     def __init__(self, tag):
    301      1.1  christos         super().__init__()
    302      1.1  christos         self._tag = tag
    303      1.1  christos 
    304      1.1  christos     def disassemble(self, info):
    305  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    306      1.1  christos         text = result.string + "\t## tag = %s" % self._tag
    307      1.1  christos         return DisassemblerResult(result.length, text)
    308      1.1  christos 
    309      1.1  christos 
    310      1.1  christos class GlobalCachingDisassembler(TestDisassembler):
    311      1.1  christos     """A disassembler that caches the DisassembleInfo that is passed in,
    312      1.1  christos     as well as a copy of the original DisassembleInfo.
    313      1.1  christos 
    314      1.1  christos     Once the call into the disassembler is complete then the
    315      1.1  christos     DisassembleInfo objects become invalid, and any calls into them
    316      1.1  christos     should trigger an exception."""
    317      1.1  christos 
    318      1.1  christos     # This is where we cache the DisassembleInfo objects.
    319      1.1  christos     cached_insn_disas = []
    320      1.1  christos 
    321      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    322      1.1  christos         def __init__(self, info):
    323      1.1  christos             super().__init__(info)
    324      1.1  christos 
    325      1.1  christos     def disassemble(self, info):
    326      1.1  christos         """Disassemble the instruction, add a CACHED comment to the output,
    327      1.1  christos         and cache the DisassembleInfo so that it is not garbage collected."""
    328      1.1  christos         GlobalCachingDisassembler.cached_insn_disas.append(info)
    329      1.1  christos         GlobalCachingDisassembler.cached_insn_disas.append(self.MyInfo(info))
    330  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    331      1.1  christos         text = result.string + "\t## CACHED"
    332      1.1  christos         return DisassemblerResult(result.length, text)
    333      1.1  christos 
    334      1.1  christos     @staticmethod
    335      1.1  christos     def check():
    336      1.1  christos         """Check that all of the methods on the cached DisassembleInfo trigger an
    337      1.1  christos         exception."""
    338      1.1  christos         for info in GlobalCachingDisassembler.cached_insn_disas:
    339      1.1  christos             assert isinstance(info, gdb.disassembler.DisassembleInfo)
    340      1.1  christos             assert not info.is_valid()
    341      1.1  christos             try:
    342      1.1  christos                 val = info.address
    343      1.1  christos                 raise gdb.GdbError("DisassembleInfo.address is still valid")
    344      1.1  christos             except RuntimeError as e:
    345      1.1  christos                 assert str(e) == "DisassembleInfo is no longer valid."
    346      1.1  christos             except:
    347      1.1  christos                 raise gdb.GdbError(
    348      1.1  christos                     "DisassembleInfo.address raised an unexpected exception"
    349      1.1  christos                 )
    350      1.1  christos 
    351      1.1  christos             try:
    352      1.1  christos                 val = info.architecture
    353      1.1  christos                 raise gdb.GdbError("DisassembleInfo.architecture is still valid")
    354      1.1  christos             except RuntimeError as e:
    355      1.1  christos                 assert str(e) == "DisassembleInfo is no longer valid."
    356      1.1  christos             except:
    357      1.1  christos                 raise gdb.GdbError(
    358      1.1  christos                     "DisassembleInfo.architecture raised an unexpected exception"
    359      1.1  christos                 )
    360      1.1  christos 
    361      1.1  christos             try:
    362      1.1  christos                 val = info.read_memory(1, 0)
    363      1.1  christos                 raise gdb.GdbError("DisassembleInfo.read is still valid")
    364      1.1  christos             except RuntimeError as e:
    365      1.1  christos                 assert str(e) == "DisassembleInfo is no longer valid."
    366      1.1  christos             except:
    367      1.1  christos                 raise gdb.GdbError(
    368      1.1  christos                     "DisassembleInfo.read raised an unexpected exception"
    369      1.1  christos                 )
    370      1.1  christos 
    371      1.1  christos         print("PASS")
    372      1.1  christos 
    373      1.1  christos 
    374      1.1  christos class GlobalNullDisassembler(TestDisassembler):
    375      1.1  christos     """A disassembler that does not change the output at all."""
    376      1.1  christos 
    377      1.1  christos     def disassemble(self, info):
    378      1.1  christos         pass
    379      1.1  christos 
    380      1.1  christos 
    381      1.1  christos class ReadMemoryMemoryErrorDisassembler(TestDisassembler):
    382      1.1  christos     """Raise a MemoryError exception from the DisassembleInfo.read_memory
    383      1.1  christos     method."""
    384      1.1  christos 
    385      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    386      1.1  christos         def __init__(self, info):
    387      1.1  christos             super().__init__(info)
    388      1.1  christos 
    389      1.1  christos         def read_memory(self, length, offset):
    390      1.1  christos             # Throw a memory error with a specific address.  We don't
    391      1.1  christos             # expect this address to show up in the output though.
    392      1.1  christos             raise gdb.MemoryError(0x1234)
    393      1.1  christos 
    394      1.1  christos     def disassemble(self, info):
    395      1.1  christos         info = self.MyInfo(info)
    396  1.1.1.2  christos         return builtin_disassemble_wrapper(info)
    397      1.1  christos 
    398      1.1  christos 
    399      1.1  christos class ReadMemoryGdbErrorDisassembler(TestDisassembler):
    400      1.1  christos     """Raise a GdbError exception from the DisassembleInfo.read_memory
    401      1.1  christos     method."""
    402      1.1  christos 
    403      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    404      1.1  christos         def __init__(self, info):
    405      1.1  christos             super().__init__(info)
    406      1.1  christos 
    407      1.1  christos         def read_memory(self, length, offset):
    408      1.1  christos             raise gdb.GdbError("read_memory raised GdbError")
    409      1.1  christos 
    410      1.1  christos     def disassemble(self, info):
    411      1.1  christos         info = self.MyInfo(info)
    412  1.1.1.2  christos         return builtin_disassemble_wrapper(info)
    413      1.1  christos 
    414      1.1  christos 
    415      1.1  christos class ReadMemoryRuntimeErrorDisassembler(TestDisassembler):
    416      1.1  christos     """Raise a RuntimeError exception from the DisassembleInfo.read_memory
    417      1.1  christos     method."""
    418      1.1  christos 
    419      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    420      1.1  christos         def __init__(self, info):
    421      1.1  christos             super().__init__(info)
    422      1.1  christos 
    423      1.1  christos         def read_memory(self, length, offset):
    424      1.1  christos             raise RuntimeError("read_memory raised RuntimeError")
    425      1.1  christos 
    426      1.1  christos     def disassemble(self, info):
    427      1.1  christos         info = self.MyInfo(info)
    428  1.1.1.2  christos         return builtin_disassemble_wrapper(info)
    429      1.1  christos 
    430      1.1  christos 
    431      1.1  christos class ReadMemoryCaughtMemoryErrorDisassembler(TestDisassembler):
    432      1.1  christos     """Raise a MemoryError exception from the DisassembleInfo.read_memory
    433      1.1  christos     method, catch this in the outer disassembler."""
    434      1.1  christos 
    435      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    436      1.1  christos         def __init__(self, info):
    437      1.1  christos             super().__init__(info)
    438      1.1  christos 
    439      1.1  christos         def read_memory(self, length, offset):
    440      1.1  christos             raise gdb.MemoryError(0x1234)
    441      1.1  christos 
    442      1.1  christos     def disassemble(self, info):
    443      1.1  christos         info = self.MyInfo(info)
    444      1.1  christos         try:
    445  1.1.1.2  christos             return builtin_disassemble_wrapper(info)
    446      1.1  christos         except gdb.MemoryError:
    447      1.1  christos             return None
    448      1.1  christos 
    449      1.1  christos 
    450      1.1  christos class ReadMemoryCaughtGdbErrorDisassembler(TestDisassembler):
    451      1.1  christos     """Raise a GdbError exception from the DisassembleInfo.read_memory
    452      1.1  christos     method, catch this in the outer disassembler."""
    453      1.1  christos 
    454      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    455      1.1  christos         def __init__(self, info):
    456      1.1  christos             super().__init__(info)
    457      1.1  christos 
    458      1.1  christos         def read_memory(self, length, offset):
    459      1.1  christos             raise gdb.GdbError("exception message")
    460      1.1  christos 
    461      1.1  christos     def disassemble(self, info):
    462      1.1  christos         info = self.MyInfo(info)
    463      1.1  christos         try:
    464  1.1.1.2  christos             return builtin_disassemble_wrapper(info)
    465      1.1  christos         except gdb.GdbError as e:
    466      1.1  christos             if e.args[0] == "exception message":
    467      1.1  christos                 return None
    468      1.1  christos             raise e
    469      1.1  christos 
    470      1.1  christos 
    471      1.1  christos class ReadMemoryCaughtRuntimeErrorDisassembler(TestDisassembler):
    472      1.1  christos     """Raise a RuntimeError exception from the DisassembleInfo.read_memory
    473      1.1  christos     method, catch this in the outer disassembler."""
    474      1.1  christos 
    475      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    476      1.1  christos         def __init__(self, info):
    477      1.1  christos             super().__init__(info)
    478      1.1  christos 
    479      1.1  christos         def read_memory(self, length, offset):
    480      1.1  christos             raise RuntimeError("exception message")
    481      1.1  christos 
    482      1.1  christos     def disassemble(self, info):
    483      1.1  christos         info = self.MyInfo(info)
    484      1.1  christos         try:
    485  1.1.1.2  christos             return builtin_disassemble_wrapper(info)
    486      1.1  christos         except RuntimeError as e:
    487      1.1  christos             if e.args[0] == "exception message":
    488      1.1  christos                 return None
    489      1.1  christos             raise e
    490      1.1  christos 
    491      1.1  christos 
    492      1.1  christos class MemorySourceNotABufferDisassembler(TestDisassembler):
    493      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    494      1.1  christos         def __init__(self, info):
    495      1.1  christos             super().__init__(info)
    496      1.1  christos 
    497      1.1  christos         def read_memory(self, length, offset):
    498      1.1  christos             return 1234
    499      1.1  christos 
    500      1.1  christos     def disassemble(self, info):
    501      1.1  christos         info = self.MyInfo(info)
    502  1.1.1.2  christos         return builtin_disassemble_wrapper(info)
    503      1.1  christos 
    504      1.1  christos 
    505      1.1  christos class MemorySourceBufferTooLongDisassembler(TestDisassembler):
    506      1.1  christos     """The read memory returns too many bytes."""
    507      1.1  christos 
    508      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    509      1.1  christos         def __init__(self, info):
    510      1.1  christos             super().__init__(info)
    511      1.1  christos 
    512      1.1  christos         def read_memory(self, length, offset):
    513      1.1  christos             buffer = super().read_memory(length, offset)
    514      1.1  christos             # Create a new memory view made by duplicating BUFFER.  This
    515      1.1  christos             # will trigger an error as GDB expects a buffer of exactly
    516      1.1  christos             # LENGTH to be returned, while this will return a buffer of
    517      1.1  christos             # 2*LENGTH.
    518      1.1  christos             return memoryview(
    519      1.1  christos                 bytes([int.from_bytes(x, "little") for x in (list(buffer[0:]) * 2)])
    520      1.1  christos             )
    521      1.1  christos 
    522      1.1  christos     def disassemble(self, info):
    523      1.1  christos         info = self.MyInfo(info)
    524  1.1.1.2  christos         return builtin_disassemble_wrapper(info)
    525  1.1.1.2  christos 
    526  1.1.1.2  christos 
    527  1.1.1.2  christos class ErrorCreatingTextPart_NoArgs(TestDisassembler):
    528  1.1.1.2  christos     """Try to create a DisassemblerTextPart with no arguments."""
    529  1.1.1.2  christos 
    530  1.1.1.2  christos     def disassemble(self, info):
    531  1.1.1.2  christos         part = info.text_part()
    532  1.1.1.2  christos         return None
    533  1.1.1.2  christos 
    534  1.1.1.2  christos 
    535  1.1.1.2  christos class ErrorCreatingAddressPart_NoArgs(TestDisassembler):
    536  1.1.1.2  christos     """Try to create a DisassemblerAddressPart with no arguments."""
    537  1.1.1.2  christos 
    538  1.1.1.2  christos     def disassemble(self, info):
    539  1.1.1.2  christos         part = info.address_part()
    540  1.1.1.2  christos         return None
    541  1.1.1.2  christos 
    542  1.1.1.2  christos 
    543  1.1.1.2  christos class ErrorCreatingTextPart_NoString(TestDisassembler):
    544  1.1.1.2  christos     """Try to create a DisassemblerTextPart with no string argument."""
    545  1.1.1.2  christos 
    546  1.1.1.2  christos     def disassemble(self, info):
    547  1.1.1.2  christos         part = info.text_part(gdb.disassembler.STYLE_TEXT)
    548  1.1.1.2  christos         return None
    549  1.1.1.2  christos 
    550  1.1.1.2  christos 
    551  1.1.1.2  christos class ErrorCreatingTextPart_NoStyle(TestDisassembler):
    552  1.1.1.2  christos     """Try to create a DisassemblerTextPart with no string argument."""
    553  1.1.1.2  christos 
    554  1.1.1.2  christos     def disassemble(self, info):
    555  1.1.1.2  christos         part = info.text_part(string="abc")
    556  1.1.1.2  christos         return None
    557  1.1.1.2  christos 
    558  1.1.1.2  christos 
    559  1.1.1.2  christos class ErrorCreatingTextPart_StringAndParts(TestDisassembler):
    560  1.1.1.2  christos     """Try to create a DisassemblerTextPart with both a string and a parts list."""
    561  1.1.1.2  christos 
    562  1.1.1.2  christos     def disassemble(self, info):
    563  1.1.1.2  christos         parts = []
    564  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, "p1"))
    565  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, "p2"))
    566  1.1.1.2  christos 
    567  1.1.1.2  christos         return DisassemblerResult(length=4, string="p1p2", parts=parts)
    568  1.1.1.2  christos 
    569  1.1.1.2  christos 
    570  1.1.1.2  christos class All_Text_Part_Styles(TestDisassembler):
    571  1.1.1.2  christos     """Create text parts with all styles."""
    572  1.1.1.2  christos 
    573  1.1.1.2  christos     def disassemble(self, info):
    574  1.1.1.2  christos         parts = []
    575  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, "p1"))
    576  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_MNEMONIC, "p2"))
    577  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_SUB_MNEMONIC, "p3"))
    578  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_ASSEMBLER_DIRECTIVE, "p4"))
    579  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_REGISTER, "p5"))
    580  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_IMMEDIATE, "p6"))
    581  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_ADDRESS, "p7"))
    582  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_ADDRESS_OFFSET, "p8"))
    583  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_SYMBOL, "p9"))
    584  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_COMMENT_START, "p10"))
    585  1.1.1.2  christos 
    586  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    587  1.1.1.2  christos         result = DisassemblerResult(length=result.length, parts=parts)
    588  1.1.1.2  christos 
    589  1.1.1.2  christos         tmp_str = ""
    590  1.1.1.2  christos         for p in parts:
    591  1.1.1.2  christos             assert p.string == str(p)
    592  1.1.1.2  christos             tmp_str += str(p)
    593  1.1.1.2  christos         assert tmp_str == result.string
    594  1.1.1.2  christos 
    595  1.1.1.2  christos         return result
    596  1.1.1.2  christos 
    597  1.1.1.2  christos 
    598  1.1.1.2  christos class Build_Result_Using_All_Parts(TestDisassembler):
    599  1.1.1.2  christos     """Disassemble an instruction and return a result that makes use of
    600  1.1.1.2  christos     text and address parts."""
    601  1.1.1.2  christos 
    602  1.1.1.2  christos     def disassemble(self, info):
    603  1.1.1.2  christos         global current_pc
    604  1.1.1.2  christos 
    605  1.1.1.2  christos         parts = []
    606  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_MNEMONIC, "fake"))
    607  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, "\t"))
    608  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_REGISTER, "reg"))
    609  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, ", "))
    610  1.1.1.2  christos         addr_part = info.address_part(current_pc)
    611  1.1.1.2  christos         assert addr_part.address == current_pc
    612  1.1.1.2  christos         parts.append(addr_part)
    613  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_TEXT, ", "))
    614  1.1.1.2  christos         parts.append(info.text_part(gdb.disassembler.STYLE_IMMEDIATE, "123"))
    615  1.1.1.2  christos 
    616  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    617  1.1.1.2  christos         result = DisassemblerResult(length=result.length, parts=parts)
    618  1.1.1.2  christos         return result
    619      1.1  christos 
    620      1.1  christos 
    621      1.1  christos class BuiltinDisassembler(Disassembler):
    622      1.1  christos     """Just calls the builtin disassembler."""
    623      1.1  christos 
    624      1.1  christos     def __init__(self):
    625      1.1  christos         super().__init__("BuiltinDisassembler")
    626      1.1  christos 
    627      1.1  christos     def __call__(self, info):
    628  1.1.1.2  christos         return builtin_disassemble_wrapper(info)
    629      1.1  christos 
    630      1.1  christos 
    631      1.1  christos class AnalyzingDisassembler(Disassembler):
    632      1.1  christos     class MyInfo(gdb.disassembler.DisassembleInfo):
    633      1.1  christos         """Wrapper around builtin DisassembleInfo type that overrides the
    634      1.1  christos         read_memory method."""
    635      1.1  christos 
    636      1.1  christos         def __init__(self, info, start, end, nop_bytes):
    637      1.1  christos             """INFO is the DisassembleInfo we are wrapping.  START and END are
    638      1.1  christos             addresses, and NOP_BYTES should be a memoryview object.
    639      1.1  christos 
    640      1.1  christos             The length (END - START) should be the same as the length
    641      1.1  christos             of NOP_BYTES.
    642      1.1  christos 
    643      1.1  christos             Any memory read requests outside the START->END range are
    644      1.1  christos             serviced normally, but any attempt to read within the
    645      1.1  christos             START->END range will return content from NOP_BYTES."""
    646      1.1  christos             super().__init__(info)
    647      1.1  christos             self._start = start
    648      1.1  christos             self._end = end
    649      1.1  christos             self._nop_bytes = nop_bytes
    650      1.1  christos 
    651      1.1  christos         def _read_replacement(self, length, offset):
    652      1.1  christos             """Return a slice of the buffer representing the replacement nop
    653      1.1  christos             instructions."""
    654      1.1  christos 
    655      1.1  christos             assert self._nop_bytes is not None
    656      1.1  christos             rb = self._nop_bytes
    657      1.1  christos 
    658      1.1  christos             # If this request is outside of a nop instruction then we don't know
    659      1.1  christos             # what to do, so just raise a memory error.
    660      1.1  christos             if offset >= len(rb) or (offset + length) > len(rb):
    661      1.1  christos                 raise gdb.MemoryError("invalid length and offset combination")
    662      1.1  christos 
    663      1.1  christos             # Return only the slice of the nop instruction as requested.
    664      1.1  christos             s = offset
    665      1.1  christos             e = offset + length
    666      1.1  christos             return rb[s:e]
    667      1.1  christos 
    668      1.1  christos         def read_memory(self, length, offset=0):
    669      1.1  christos             """Callback used by the builtin disassembler to read the contents of
    670      1.1  christos             memory."""
    671      1.1  christos 
    672      1.1  christos             # If this request is within the region we are replacing with 'nop'
    673      1.1  christos             # instructions, then call the helper function to perform that
    674      1.1  christos             # replacement.
    675      1.1  christos             if self._start is not None:
    676      1.1  christos                 assert self._end is not None
    677      1.1  christos                 if self.address >= self._start and self.address < self._end:
    678      1.1  christos                     return self._read_replacement(length, offset)
    679      1.1  christos 
    680      1.1  christos             # Otherwise, we just forward this request to the default read memory
    681      1.1  christos             # implementation.
    682      1.1  christos             return super().read_memory(length, offset)
    683      1.1  christos 
    684      1.1  christos     def __init__(self):
    685      1.1  christos         """Constructor."""
    686      1.1  christos         super().__init__("AnalyzingDisassembler")
    687      1.1  christos 
    688      1.1  christos         # Details about the instructions found during the first disassembler
    689      1.1  christos         # pass.
    690      1.1  christos         self._pass_1_length = []
    691      1.1  christos         self._pass_1_insn = []
    692      1.1  christos         self._pass_1_address = []
    693      1.1  christos 
    694      1.1  christos         # The start and end address for the instruction we will replace with
    695      1.1  christos         # one or more 'nop' instructions during pass two.
    696      1.1  christos         self._start = None
    697      1.1  christos         self._end = None
    698      1.1  christos 
    699      1.1  christos         # The index in the _pass_1_* lists for where the nop instruction can
    700      1.1  christos         # be found, also, the buffer of bytes that make up a nop instruction.
    701      1.1  christos         self._nop_index = None
    702      1.1  christos         self._nop_bytes = None
    703      1.1  christos 
    704      1.1  christos         # A flag that indicates if we are in the first or second pass of
    705      1.1  christos         # this disassembler test.
    706      1.1  christos         self._first_pass = True
    707      1.1  christos 
    708      1.1  christos         # The disassembled instructions collected during the second pass.
    709      1.1  christos         self._pass_2_insn = []
    710      1.1  christos 
    711      1.1  christos         # A copy of _pass_1_insn that has been modified to include the extra
    712      1.1  christos         # 'nop' instructions we plan to insert during the second pass.  This
    713      1.1  christos         # is then checked against _pass_2_insn after the second disassembler
    714      1.1  christos         # pass has completed.
    715      1.1  christos         self._check = []
    716      1.1  christos 
    717      1.1  christos     def __call__(self, info):
    718      1.1  christos         """Called to perform the disassembly."""
    719      1.1  christos 
    720      1.1  christos         # Override the info object, this provides access to our
    721      1.1  christos         # read_memory function.
    722      1.1  christos         info = self.MyInfo(info, self._start, self._end, self._nop_bytes)
    723  1.1.1.2  christos         result = builtin_disassemble_wrapper(info)
    724      1.1  christos 
    725      1.1  christos         # Record some informaiton about the first 'nop' instruction we find.
    726      1.1  christos         if self._nop_index is None and is_nop(result.string):
    727      1.1  christos             self._nop_index = len(self._pass_1_length)
    728      1.1  christos             # The offset in the following read_memory call defaults to 0.
    729      1.1  christos             self._nop_bytes = info.read_memory(result.length)
    730      1.1  christos 
    731      1.1  christos         # Record information about each instruction that is disassembled.
    732      1.1  christos         # This test is performed in two passes, and we need different
    733      1.1  christos         # information in each pass.
    734      1.1  christos         if self._first_pass:
    735      1.1  christos             self._pass_1_length.append(result.length)
    736      1.1  christos             self._pass_1_insn.append(result.string)
    737      1.1  christos             self._pass_1_address.append(info.address)
    738      1.1  christos         else:
    739      1.1  christos             self._pass_2_insn.append(result.string)
    740      1.1  christos 
    741      1.1  christos         return result
    742      1.1  christos 
    743      1.1  christos     def find_replacement_candidate(self):
    744      1.1  christos         """Call this after the first disassembly pass.  This identifies a suitable
    745      1.1  christos         instruction to replace with 'nop' instruction(s)."""
    746      1.1  christos 
    747      1.1  christos         if self._nop_index is None:
    748      1.1  christos             raise gdb.GdbError("no nop was found")
    749      1.1  christos 
    750      1.1  christos         nop_idx = self._nop_index
    751      1.1  christos         nop_length = self._pass_1_length[nop_idx]
    752      1.1  christos 
    753      1.1  christos         # First we look for an instruction that is larger than a nop
    754      1.1  christos         # instruction, but whose length is an exact multiple of the nop
    755      1.1  christos         # instruction's length.
    756      1.1  christos         replace_idx = None
    757      1.1  christos         for idx in range(len(self._pass_1_length)):
    758      1.1  christos             if (
    759      1.1  christos                 idx > 0
    760      1.1  christos                 and idx != nop_idx
    761      1.1  christos                 and not is_nop(self._pass_1_insn[idx])
    762      1.1  christos                 and self._pass_1_length[idx] > self._pass_1_length[nop_idx]
    763      1.1  christos                 and self._pass_1_length[idx] % self._pass_1_length[nop_idx] == 0
    764      1.1  christos             ):
    765      1.1  christos                 replace_idx = idx
    766      1.1  christos                 break
    767      1.1  christos 
    768      1.1  christos         # If we still don't have a replacement candidate, then search again,
    769      1.1  christos         # this time looking for an instruciton that is the same length as a
    770      1.1  christos         # nop instruction.
    771      1.1  christos         if replace_idx is None:
    772      1.1  christos             for idx in range(len(self._pass_1_length)):
    773      1.1  christos                 if (
    774      1.1  christos                     idx > 0
    775      1.1  christos                     and idx != nop_idx
    776      1.1  christos                     and not is_nop(self._pass_1_insn[idx])
    777      1.1  christos                     and self._pass_1_length[idx] == self._pass_1_length[nop_idx]
    778      1.1  christos                 ):
    779      1.1  christos                     replace_idx = idx
    780      1.1  christos                     break
    781      1.1  christos 
    782      1.1  christos         # Weird, the nop instruction must be larger than every other
    783      1.1  christos         # instruction, or all instructions are 'nop'?
    784      1.1  christos         if replace_idx is None:
    785      1.1  christos             raise gdb.GdbError("can't find an instruction to replace")
    786      1.1  christos 
    787      1.1  christos         # Record the instruction range that will be replaced with 'nop'
    788      1.1  christos         # instructions, and mark that we are now on the second pass.
    789      1.1  christos         self._start = self._pass_1_address[replace_idx]
    790      1.1  christos         self._end = self._pass_1_address[replace_idx] + self._pass_1_length[replace_idx]
    791      1.1  christos         self._first_pass = False
    792      1.1  christos         print("Replace from 0x%x to 0x%x with NOP" % (self._start, self._end))
    793      1.1  christos 
    794      1.1  christos         # Finally, build the expected result.  Create the _check list, which
    795      1.1  christos         # is a copy of _pass_1_insn, but replace the instruction we
    796      1.1  christos         # identified above with a series of 'nop' instructions.
    797      1.1  christos         self._check = list(self._pass_1_insn)
    798      1.1  christos         nop_count = int(self._pass_1_length[replace_idx] / self._pass_1_length[nop_idx])
    799      1.1  christos         nop_insn = self._pass_1_insn[nop_idx]
    800      1.1  christos         nops = [nop_insn] * nop_count
    801      1.1  christos         self._check[replace_idx : (replace_idx + 1)] = nops
    802      1.1  christos 
    803      1.1  christos     def check(self):
    804      1.1  christos         """Call this after the second disassembler pass to validate the output."""
    805      1.1  christos         if self._check != self._pass_2_insn:
    806      1.1  christos             raise gdb.GdbError("mismatch")
    807      1.1  christos         print("PASS")
    808      1.1  christos 
    809      1.1  christos 
    810      1.1  christos def add_global_disassembler(dis_class):
    811      1.1  christos     """Create an instance of DIS_CLASS and register it as a global disassembler."""
    812      1.1  christos     dis = dis_class()
    813      1.1  christos     gdb.disassembler.register_disassembler(dis, None)
    814      1.1  christos     return dis
    815      1.1  christos 
    816      1.1  christos 
    817      1.1  christos class InvalidDisassembleInfo(gdb.disassembler.DisassembleInfo):
    818      1.1  christos     """An attempt to create a DisassembleInfo sub-class without calling
    819      1.1  christos     the parent class init method.
    820      1.1  christos 
    821      1.1  christos     Attempts to use instances of this class should throw an error
    822      1.1  christos     saying that the DisassembleInfo is not valid, despite this class
    823      1.1  christos     having all of the required attributes.
    824      1.1  christos 
    825      1.1  christos     The reason why this class will never be valid is that an internal
    826      1.1  christos     field (within the C++ code) can't be initialized without calling
    827      1.1  christos     the parent class init method."""
    828      1.1  christos 
    829      1.1  christos     def __init__(self):
    830      1.1  christos         assert current_pc is not None
    831      1.1  christos 
    832      1.1  christos     def is_valid(self):
    833      1.1  christos         return True
    834      1.1  christos 
    835      1.1  christos     @property
    836      1.1  christos     def address(self):
    837      1.1  christos         global current_pc
    838      1.1  christos         return current_pc
    839      1.1  christos 
    840      1.1  christos     @property
    841      1.1  christos     def architecture(self):
    842      1.1  christos         return gdb.selected_inferior().architecture()
    843      1.1  christos 
    844      1.1  christos     @property
    845      1.1  christos     def progspace(self):
    846      1.1  christos         return gdb.selected_inferior().progspace
    847      1.1  christos 
    848      1.1  christos 
    849      1.1  christos # Start with all disassemblers removed.
    850      1.1  christos remove_all_python_disassemblers()
    851      1.1  christos 
    852      1.1  christos print("Python script imported")
    853