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