| # Copyright (C) 2021-2024 Free Software Foundation, Inc. |
| |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 3 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| |
| import struct |
| import sys |
| |
| import gdb |
| import gdb.disassembler |
| from gdb.disassembler import Disassembler, DisassemblerResult |
| |
| # A global, holds the program-counter address at which we should |
| # perform the extra disassembly that this script provides. |
| 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" |
| |
| |
| # Remove all currently registered disassemblers. |
| def remove_all_python_disassemblers(): |
| for a in gdb.architecture_names(): |
| gdb.disassembler.register_disassembler(None, a) |
| gdb.disassembler.register_disassembler(None, None) |
| |
| |
| class TestDisassembler(Disassembler): |
| """A base class for disassemblers within this script to inherit from. |
| Implements the __call__ method and ensures we only do any |
| disassembly wrapping for the global CURRENT_PC.""" |
| |
| def __init__(self): |
| global current_pc |
| |
| super().__init__("TestDisassembler") |
| self.__info = None |
| if current_pc == None: |
| raise gdb.GdbError("no current_pc set") |
| |
| def __call__(self, info): |
| global current_pc |
| |
| if info.address != current_pc: |
| return None |
| self.__info = info |
| return self.disassemble(info) |
| |
| def get_info(self): |
| return self.__info |
| |
| def disassemble(self, info): |
| raise NotImplementedError("override the disassemble method") |
| |
| |
| class ShowInfoRepr(TestDisassembler): |
| """Call the __repr__ method on the DisassembleInfo, convert the result |
| to a string, and incude it in a comment in the disassembler output.""" |
| |
| def disassemble(self, info): |
| comment = "\t## " + repr(info) |
| result = builtin_disassemble_wrapper(info) |
| string = result.string + comment |
| length = result.length |
| return DisassemblerResult(length=length, string=string) |
| |
| |
| class ShowInfoSubClassRepr(TestDisassembler): |
| """Create a sub-class of DisassembleInfo. Create an instace of this |
| sub-class and call the __repr__ method on it. Convert the result |
| to a string, and incude it in a comment in the disassembler |
| output. The DisassembleInfo sub-class does not override __repr__ |
| so we are calling the implementation on the parent class.""" |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| """A wrapper around DisassembleInfo, doesn't add any new |
| functionality, just gives a new name in order to check the |
| __repr__ functionality.""" |
| |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def disassemble(self, info): |
| info = self.MyInfo(info) |
| comment = "\t## " + repr(info) |
| result = builtin_disassemble_wrapper(info) |
| string = result.string + comment |
| length = result.length |
| return DisassemblerResult(length=length, string=string) |
| |
| |
| class ShowResultRepr(TestDisassembler): |
| """Call the __repr__ method on the DisassemblerResult, convert the |
| result to a string, and incude it in a comment in the disassembler |
| output.""" |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| comment = "\t## " + repr(result) |
| string = result.string + comment |
| length = result.length |
| return DisassemblerResult(length=length, string=string) |
| |
| |
| class ShowResultStr(TestDisassembler): |
| """Call the __str__ method on a DisassemblerResult object, incude the |
| resulting string in a comment within the disassembler output.""" |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| comment = "\t## " + str(result) |
| string = result.string + comment |
| length = result.length |
| return DisassemblerResult(length=length, string=string, parts=None) |
| |
| |
| class GlobalPreInfoDisassembler(TestDisassembler): |
| """Check the attributes of DisassembleInfo before disassembly has occurred.""" |
| |
| def disassemble(self, info): |
| ad = info.address |
| ar = info.architecture |
| |
| if ad != current_pc: |
| raise gdb.GdbError("invalid address") |
| |
| if not isinstance(ar, gdb.Architecture): |
| raise gdb.GdbError("invalid architecture type") |
| |
| result = builtin_disassemble_wrapper(info) |
| |
| text = result.string + "\t## ad = 0x%x, ar = %s" % (ad, ar.name()) |
| return DisassemblerResult(result.length, text) |
| |
| |
| class GlobalPostInfoDisassembler(TestDisassembler): |
| """Check the attributes of DisassembleInfo after disassembly has occurred.""" |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| |
| ad = info.address |
| ar = info.architecture |
| |
| if ad != current_pc: |
| raise gdb.GdbError("invalid address") |
| |
| if not isinstance(ar, gdb.Architecture): |
| raise gdb.GdbError("invalid architecture type") |
| |
| text = result.string + "\t## ad = 0x%x, ar = %s" % (ad, ar.name()) |
| return DisassemblerResult(result.length, text) |
| |
| |
| class GlobalReadDisassembler(TestDisassembler): |
| """Check the DisassembleInfo.read_memory method. Calls the builtin |
| disassembler, then reads all of the bytes of this instruction, and |
| adds them as a comment to the disassembler output.""" |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| len = result.length |
| str = "" |
| for o in range(len): |
| if str != "": |
| str += " " |
| v = bytes(info.read_memory(1, o))[0] |
| if sys.version_info[0] < 3: |
| v = struct.unpack("<B", v) |
| str += "0x%02x" % v |
| text = result.string + "\t## bytes = %s" % str |
| return DisassemblerResult(result.length, text) |
| |
| |
| class GlobalAddrDisassembler(TestDisassembler): |
| """Check the gdb.format_address method.""" |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| arch = info.architecture |
| addr = info.address |
| program_space = info.progspace |
| str = gdb.format_address(addr, program_space, arch) |
| text = result.string + "\t## addr = %s" % str |
| return DisassemblerResult(result.length, text) |
| |
| |
| class GdbErrorEarlyDisassembler(TestDisassembler): |
| """Raise a GdbError instead of performing any disassembly.""" |
| |
| def disassemble(self, info): |
| raise gdb.GdbError("GdbError instead of a result") |
| |
| |
| class RuntimeErrorEarlyDisassembler(TestDisassembler): |
| """Raise a RuntimeError instead of performing any disassembly.""" |
| |
| def disassemble(self, info): |
| raise RuntimeError("RuntimeError instead of a result") |
| |
| |
| class GdbErrorLateDisassembler(TestDisassembler): |
| """Raise a GdbError after calling the builtin disassembler.""" |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| raise gdb.GdbError("GdbError after builtin disassembler") |
| |
| |
| class RuntimeErrorLateDisassembler(TestDisassembler): |
| """Raise a RuntimeError after calling the builtin disassembler.""" |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| raise RuntimeError("RuntimeError after builtin disassembler") |
| |
| |
| class MemoryErrorEarlyDisassembler(TestDisassembler): |
| """Throw a memory error, ignore the error and disassemble.""" |
| |
| def disassemble(self, info): |
| tag = "## FAIL" |
| try: |
| info.read_memory(1, -info.address + 2) |
| except gdb.MemoryError: |
| tag = "## AFTER ERROR" |
| result = builtin_disassemble_wrapper(info) |
| text = result.string + "\t" + tag |
| return DisassemblerResult(result.length, text) |
| |
| |
| class MemoryErrorLateDisassembler(TestDisassembler): |
| """Throw a memory error after calling the builtin disassembler, but |
| before we return a result.""" |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| # The following read will throw an error. |
| info.read_memory(1, -info.address + 2) |
| return DisassemblerResult(1, "BAD") |
| |
| |
| class RethrowMemoryErrorDisassembler(TestDisassembler): |
| """Catch and rethrow a memory error.""" |
| |
| def disassemble(self, info): |
| try: |
| info.read_memory(1, -info.address + 2) |
| except gdb.MemoryError as e: |
| raise gdb.MemoryError("cannot read code at address 0x2") |
| return DisassemblerResult(1, "BAD") |
| |
| |
| class ResultOfWrongType(TestDisassembler): |
| """Return something that is not a DisassemblerResult from disassemble method""" |
| |
| class Blah: |
| def __init__(self, length, string): |
| self.length = length |
| self.string = string |
| |
| def disassemble(self, info): |
| return self.Blah(1, "ABC") |
| |
| |
| class TaggingDisassembler(TestDisassembler): |
| """A simple disassembler that just tags the output.""" |
| |
| def __init__(self, tag): |
| super().__init__() |
| self._tag = tag |
| |
| def disassemble(self, info): |
| result = builtin_disassemble_wrapper(info) |
| text = result.string + "\t## tag = %s" % self._tag |
| return DisassemblerResult(result.length, text) |
| |
| |
| class GlobalCachingDisassembler(TestDisassembler): |
| """A disassembler that caches the DisassembleInfo that is passed in, |
| as well as a copy of the original DisassembleInfo. |
| |
| Once the call into the disassembler is complete then the |
| DisassembleInfo objects become invalid, and any calls into them |
| should trigger an exception.""" |
| |
| # This is where we cache the DisassembleInfo objects. |
| cached_insn_disas = [] |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def disassemble(self, info): |
| """Disassemble the instruction, add a CACHED comment to the output, |
| 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 = builtin_disassemble_wrapper(info) |
| text = result.string + "\t## CACHED" |
| return DisassemblerResult(result.length, text) |
| |
| @staticmethod |
| def check(): |
| """Check that all of the methods on the cached DisassembleInfo trigger an |
| exception.""" |
| for info in GlobalCachingDisassembler.cached_insn_disas: |
| assert isinstance(info, gdb.disassembler.DisassembleInfo) |
| assert not info.is_valid() |
| try: |
| val = info.address |
| raise gdb.GdbError("DisassembleInfo.address is still valid") |
| except RuntimeError as e: |
| assert str(e) == "DisassembleInfo is no longer valid." |
| except: |
| raise gdb.GdbError( |
| "DisassembleInfo.address raised an unexpected exception" |
| ) |
| |
| try: |
| val = info.architecture |
| raise gdb.GdbError("DisassembleInfo.architecture is still valid") |
| except RuntimeError as e: |
| assert str(e) == "DisassembleInfo is no longer valid." |
| except: |
| raise gdb.GdbError( |
| "DisassembleInfo.architecture raised an unexpected exception" |
| ) |
| |
| try: |
| val = info.read_memory(1, 0) |
| raise gdb.GdbError("DisassembleInfo.read is still valid") |
| except RuntimeError as e: |
| assert str(e) == "DisassembleInfo is no longer valid." |
| except: |
| raise gdb.GdbError( |
| "DisassembleInfo.read raised an unexpected exception" |
| ) |
| |
| print("PASS") |
| |
| |
| class GlobalNullDisassembler(TestDisassembler): |
| """A disassembler that does not change the output at all.""" |
| |
| def disassemble(self, info): |
| pass |
| |
| |
| class ReadMemoryMemoryErrorDisassembler(TestDisassembler): |
| """Raise a MemoryError exception from the DisassembleInfo.read_memory |
| method.""" |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def read_memory(self, length, offset): |
| # Throw a memory error with a specific address. We don't |
| # expect this address to show up in the output though. |
| raise gdb.MemoryError(0x1234) |
| |
| def disassemble(self, info): |
| info = self.MyInfo(info) |
| return builtin_disassemble_wrapper(info) |
| |
| |
| class ReadMemoryGdbErrorDisassembler(TestDisassembler): |
| """Raise a GdbError exception from the DisassembleInfo.read_memory |
| method.""" |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def read_memory(self, length, offset): |
| raise gdb.GdbError("read_memory raised GdbError") |
| |
| def disassemble(self, info): |
| info = self.MyInfo(info) |
| return builtin_disassemble_wrapper(info) |
| |
| |
| class ReadMemoryRuntimeErrorDisassembler(TestDisassembler): |
| """Raise a RuntimeError exception from the DisassembleInfo.read_memory |
| method.""" |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def read_memory(self, length, offset): |
| raise RuntimeError("read_memory raised RuntimeError") |
| |
| def disassemble(self, info): |
| info = self.MyInfo(info) |
| return builtin_disassemble_wrapper(info) |
| |
| |
| class ReadMemoryCaughtMemoryErrorDisassembler(TestDisassembler): |
| """Raise a MemoryError exception from the DisassembleInfo.read_memory |
| method, catch this in the outer disassembler.""" |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def read_memory(self, length, offset): |
| raise gdb.MemoryError(0x1234) |
| |
| def disassemble(self, info): |
| info = self.MyInfo(info) |
| try: |
| return builtin_disassemble_wrapper(info) |
| except gdb.MemoryError: |
| return None |
| |
| |
| class ReadMemoryCaughtGdbErrorDisassembler(TestDisassembler): |
| """Raise a GdbError exception from the DisassembleInfo.read_memory |
| method, catch this in the outer disassembler.""" |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def read_memory(self, length, offset): |
| raise gdb.GdbError("exception message") |
| |
| def disassemble(self, info): |
| info = self.MyInfo(info) |
| try: |
| return builtin_disassemble_wrapper(info) |
| except gdb.GdbError as e: |
| if e.args[0] == "exception message": |
| return None |
| raise e |
| |
| |
| class ReadMemoryCaughtRuntimeErrorDisassembler(TestDisassembler): |
| """Raise a RuntimeError exception from the DisassembleInfo.read_memory |
| method, catch this in the outer disassembler.""" |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def read_memory(self, length, offset): |
| raise RuntimeError("exception message") |
| |
| def disassemble(self, info): |
| info = self.MyInfo(info) |
| try: |
| return builtin_disassemble_wrapper(info) |
| except RuntimeError as e: |
| if e.args[0] == "exception message": |
| return None |
| raise e |
| |
| |
| class MemorySourceNotABufferDisassembler(TestDisassembler): |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def read_memory(self, length, offset): |
| return 1234 |
| |
| def disassemble(self, info): |
| info = self.MyInfo(info) |
| return builtin_disassemble_wrapper(info) |
| |
| |
| class MemorySourceBufferTooLongDisassembler(TestDisassembler): |
| """The read memory returns too many bytes.""" |
| |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| def __init__(self, info): |
| super().__init__(info) |
| |
| def read_memory(self, length, offset): |
| buffer = super().read_memory(length, offset) |
| # Create a new memory view made by duplicating BUFFER. This |
| # will trigger an error as GDB expects a buffer of exactly |
| # LENGTH to be returned, while this will return a buffer of |
| # 2*LENGTH. |
| return memoryview( |
| bytes([int.from_bytes(x, "little") for x in (list(buffer[0:]) * 2)]) |
| ) |
| |
| def disassemble(self, info): |
| info = self.MyInfo(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): |
| """Just calls the builtin disassembler.""" |
| |
| def __init__(self): |
| super().__init__("BuiltinDisassembler") |
| |
| def __call__(self, info): |
| return builtin_disassemble_wrapper(info) |
| |
| |
| class AnalyzingDisassembler(Disassembler): |
| class MyInfo(gdb.disassembler.DisassembleInfo): |
| """Wrapper around builtin DisassembleInfo type that overrides the |
| read_memory method.""" |
| |
| def __init__(self, info, start, end, nop_bytes): |
| """INFO is the DisassembleInfo we are wrapping. START and END are |
| addresses, and NOP_BYTES should be a memoryview object. |
| |
| The length (END - START) should be the same as the length |
| of NOP_BYTES. |
| |
| Any memory read requests outside the START->END range are |
| serviced normally, but any attempt to read within the |
| START->END range will return content from NOP_BYTES.""" |
| super().__init__(info) |
| self._start = start |
| self._end = end |
| self._nop_bytes = nop_bytes |
| |
| def _read_replacement(self, length, offset): |
| """Return a slice of the buffer representing the replacement nop |
| instructions.""" |
| |
| assert self._nop_bytes is not None |
| rb = self._nop_bytes |
| |
| # If this request is outside of a nop instruction then we don't know |
| # what to do, so just raise a memory error. |
| if offset >= len(rb) or (offset + length) > len(rb): |
| raise gdb.MemoryError("invalid length and offset combination") |
| |
| # Return only the slice of the nop instruction as requested. |
| s = offset |
| e = offset + length |
| return rb[s:e] |
| |
| def read_memory(self, length, offset=0): |
| """Callback used by the builtin disassembler to read the contents of |
| memory.""" |
| |
| # If this request is within the region we are replacing with 'nop' |
| # instructions, then call the helper function to perform that |
| # replacement. |
| if self._start is not None: |
| assert self._end is not None |
| if self.address >= self._start and self.address < self._end: |
| return self._read_replacement(length, offset) |
| |
| # Otherwise, we just forward this request to the default read memory |
| # implementation. |
| return super().read_memory(length, offset) |
| |
| def __init__(self): |
| """Constructor.""" |
| super().__init__("AnalyzingDisassembler") |
| |
| # Details about the instructions found during the first disassembler |
| # pass. |
| self._pass_1_length = [] |
| self._pass_1_insn = [] |
| self._pass_1_address = [] |
| |
| # The start and end address for the instruction we will replace with |
| # one or more 'nop' instructions during pass two. |
| self._start = None |
| self._end = None |
| |
| # The index in the _pass_1_* lists for where the nop instruction can |
| # be found, also, the buffer of bytes that make up a nop instruction. |
| self._nop_index = None |
| self._nop_bytes = None |
| |
| # A flag that indicates if we are in the first or second pass of |
| # this disassembler test. |
| self._first_pass = True |
| |
| # The disassembled instructions collected during the second pass. |
| self._pass_2_insn = [] |
| |
| # A copy of _pass_1_insn that has been modified to include the extra |
| # 'nop' instructions we plan to insert during the second pass. This |
| # is then checked against _pass_2_insn after the second disassembler |
| # pass has completed. |
| self._check = [] |
| |
| def __call__(self, info): |
| """Called to perform the disassembly.""" |
| |
| # Override the info object, this provides access to our |
| # read_memory function. |
| info = self.MyInfo(info, self._start, self._end, self._nop_bytes) |
| 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): |
| self._nop_index = len(self._pass_1_length) |
| # The offset in the following read_memory call defaults to 0. |
| self._nop_bytes = info.read_memory(result.length) |
| |
| # Record information about each instruction that is disassembled. |
| # This test is performed in two passes, and we need different |
| # information in each pass. |
| if self._first_pass: |
| self._pass_1_length.append(result.length) |
| self._pass_1_insn.append(result.string) |
| self._pass_1_address.append(info.address) |
| else: |
| self._pass_2_insn.append(result.string) |
| |
| return result |
| |
| def find_replacement_candidate(self): |
| """Call this after the first disassembly pass. This identifies a suitable |
| instruction to replace with 'nop' instruction(s).""" |
| |
| if self._nop_index is None: |
| raise gdb.GdbError("no nop was found") |
| |
| nop_idx = self._nop_index |
| nop_length = self._pass_1_length[nop_idx] |
| |
| # First we look for an instruction that is larger than a nop |
| # instruction, but whose length is an exact multiple of the nop |
| # instruction's length. |
| replace_idx = None |
| for idx in range(len(self._pass_1_length)): |
| if ( |
| idx > 0 |
| and idx != nop_idx |
| and not is_nop(self._pass_1_insn[idx]) |
| and self._pass_1_length[idx] > self._pass_1_length[nop_idx] |
| and self._pass_1_length[idx] % self._pass_1_length[nop_idx] == 0 |
| ): |
| replace_idx = idx |
| break |
| |
| # If we still don't have a replacement candidate, then search again, |
| # this time looking for an instruciton that is the same length as a |
| # nop instruction. |
| if replace_idx is None: |
| for idx in range(len(self._pass_1_length)): |
| if ( |
| idx > 0 |
| and idx != nop_idx |
| and not is_nop(self._pass_1_insn[idx]) |
| and self._pass_1_length[idx] == self._pass_1_length[nop_idx] |
| ): |
| replace_idx = idx |
| break |
| |
| # Weird, the nop instruction must be larger than every other |
| # instruction, or all instructions are 'nop'? |
| if replace_idx is None: |
| raise gdb.GdbError("can't find an instruction to replace") |
| |
| # Record the instruction range that will be replaced with 'nop' |
| # instructions, and mark that we are now on the second pass. |
| self._start = self._pass_1_address[replace_idx] |
| self._end = self._pass_1_address[replace_idx] + self._pass_1_length[replace_idx] |
| self._first_pass = False |
| print("Replace from 0x%x to 0x%x with NOP" % (self._start, self._end)) |
| |
| # Finally, build the expected result. Create the _check list, which |
| # is a copy of _pass_1_insn, but replace the instruction we |
| # identified above with a series of 'nop' instructions. |
| self._check = list(self._pass_1_insn) |
| nop_count = int(self._pass_1_length[replace_idx] / self._pass_1_length[nop_idx]) |
| nop_insn = self._pass_1_insn[nop_idx] |
| nops = [nop_insn] * nop_count |
| self._check[replace_idx : (replace_idx + 1)] = nops |
| |
| def check(self): |
| """Call this after the second disassembler pass to validate the output.""" |
| if self._check != self._pass_2_insn: |
| raise gdb.GdbError("mismatch") |
| print("PASS") |
| |
| |
| def add_global_disassembler(dis_class): |
| """Create an instance of DIS_CLASS and register it as a global disassembler.""" |
| dis = dis_class() |
| gdb.disassembler.register_disassembler(dis, None) |
| return dis |
| |
| |
| class InvalidDisassembleInfo(gdb.disassembler.DisassembleInfo): |
| """An attempt to create a DisassembleInfo sub-class without calling |
| the parent class init method. |
| |
| Attempts to use instances of this class should throw an error |
| saying that the DisassembleInfo is not valid, despite this class |
| having all of the required attributes. |
| |
| The reason why this class will never be valid is that an internal |
| field (within the C++ code) can't be initialized without calling |
| the parent class init method.""" |
| |
| def __init__(self): |
| assert current_pc is not None |
| |
| def is_valid(self): |
| return True |
| |
| @property |
| def address(self): |
| global current_pc |
| return current_pc |
| |
| @property |
| def architecture(self): |
| return gdb.selected_inferior().architecture() |
| |
| @property |
| def progspace(self): |
| return gdb.selected_inferior().progspace |
| |
| |
| # Start with all disassemblers removed. |
| remove_all_python_disassemblers() |
| |
| print("Python script imported") |