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