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